UDP hole punching, intranet penetration (with JAVA code)

Directory

1. Introduction to Intranet Penetration

1.1 The relationship between public network IP ports, NAT forwarding, and private network IP ports

1.2 Principles of NAT forwarding and UDP hole punching

1.3 Core code

1. Introduction to Intranet Penetration

I don’t like to talk about some academic terms in a long way, and I am confused in the end. Just use the simplest vernacular to briefly talk about the principle and function of intranet penetration.

1.1 The relationship between public network IP ports, NAT forwarding, and intranet IP ports

To understand and use intranet penetration, you must first understand how messages are exchanged between them.

For example: Enter www.baidu.com on the webpage to open Baidu. This is because you know the domain name. The domain name is mapped to the corresponding public network IP on the DNS server. The domain name is bound to the ip address. Visit www.baidu.com xxx.com is equivalent to accessing the public network IP, you can use the cmd ping command to see the returned ip address.

When we visit www.baidu.com, Baidu’s server needs to know your public network ip and port to return the information to you (the final result is that the Baidu page is displayed on the browser)

Enter www.baidu.com in the browser to return to the path of the whole process of Baidu page

Intranet IP: port -> public network IP of nat (forwarding) network cable: port -> Baidu server public network IP: port -> public network IP of nat (forwarding) network cable: port -> internal network IP: port

Among them, intranet and nat (forwarding) will refresh the port every time a request is made, and the intranet IP (the ipv4 address that can be viewed by entering ipconfig /all in cmd) is as shown below:

Those who understand the network transmission protocol should know that information transmission on the Internet requires the target address, port and response address, port

One piece of knowledge involved here is nat, because ipv4 addresses are limited, and now most of the network cables of operators are a public network corresponding to users in a region. Simply put, you use a public network ip with other people.

NAT acts as a forwarding function, so your address and port on the local computer cannot be accessed by external network users, but can be accessed by users under the same public network ip. It can be understood that a public network ip corresponds to a nat.

So why do udp hole punching? Because it is necessary to exchange information with users outside the nat.

The previous content is just an introduction, the following is the key point (look carefully)

1.2 NAT forwarding and UDP hole punching principle

What should I do when two intranets A and B want to communicate with each other? First you need to know the nat address and port of A and B

Every time you send information to the Baidu server, the address returned is actually the address and port of nat, and then nat forwards it to your intranet address and port and generates a record. The record is similar to: (nat address: port – intranet address: port ).

The nat address port and the intranet address port are bound 1 to 1 (you can draw an equal sign, The port will be refreshed every time it is called)

Because A and B are both intranets and cannot communicate directly, a public network server is needed to record the nat addresses and ports of A and B.

Then let A send a UDP message to the address of the public network server, so that a record will be left on A’s NAT A IP: port – natA public network IP: port.

Let B send a UDP message to the address of the public network server, so that a record will be left on B’s NAT B IP: port – natB public network IP: port.

In this way, the nat addresses and ports of A and B are known, and then let A and B send UDP requests to each other and leave records in nat, so that A and B can communicate through UDP.

focus! ! ! When using java new DatagramSocket(), you must use the singleton mode. Do not close it, otherwise the port mapped by nat will be different every time. Nat will not be able to obtain the correct address if it changes. I have stepped on the pit before.

1.3 Core Code

import com.alibaba.fastjson.JSON;
import lombok. SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static spike.controller.CheckSyncStateController.doGet;

/**
 * @PACKAGE_NAME: spike.schedule
 * @NAME: NatSchedule
 * @USER: spike
 * @DATE: 2023/5/12 10:27
 * @PROJECT_NAME: Springcolud_Spike
 */
@Slf4j
@Component
public class NatSchedule {

    @Value("${tarurl}")
    private String TAR_URL;

    @Value("${isserver}")
    private boolean IS_SERVER;

    @Value("${key}")
    private String KEY;

    @Value("${sendport}")
    private Integer SEND_PORT;

    @Value("${receiveport}")
    private Integer RECEIVE_PORT;

    public static DatagramSocket RECEIVE_SOCKET = null;
    public static DatagramSocket SEND_SOCKET = null;

    public static Map<String, Object> NAT_MAP = new HashMap<>();

    // Execute every 1 minute
    @PostConstruct
    @Scheduled(cron = "0/60 * * * * ?")
    private void SendUdp() throws IOException {
        sendUpdMsg();
    }

    // Execute every 1 second
    @Scheduled(cron = "*/1 * * * * ?")
    private void receiveUpd() throws IOException {
        receiveUpdMsg();
    }

    public void sendUpdMsg() throws IOException {
        if (!IS_SERVER) {
            if (SEND_SOCKET == null) {
                //clear key value
                doGet("http://" + TAR_URL + "/Nat/clearNatMap?key=" + KEY);
                SEND_SOCKET = new DatagramSocket(SEND_PORT);
                Thread thread = new Thread() {
                    @SneakyThrows
                    @Override
                    public void run() {
                        log.info("start receiving upd request");
                        responseSend();
                    }
                };
                thread. start();
            }
            sendUpdToServer();
        }
    }

    private void responseSend() throws IOException {
        byte[] bytes = new byte[1024];
        DatagramPacket packet1 = new DatagramPacket(bytes, bytes. length);
        while (true) {
            SEND_SOCKET.receive(packet1);
            String receive = new String(bytes, 0, packet1. getLength(), "utf-8");
            if (receive. contains("client")) {
                String msg = "****************************Drilled successfully**************** ***************";
                log.info(msg);
            } else {
                log.info("************************** upd information starts************** *************\\
 {}", receive);
                log.info("************************** upd information end************** *************");
            }
        }
    }

    private void sendUpdToServer() throws IOException {
        String ipV4 = InetAddress.getLocalHost().getHostAddress();
        String[] ipArr = TAR_URL. split(":");
        // The client segment sends an upd request to the server (the server records the client's internal network ip/external network ip/external network port)
        String text = KEY + ":" + ipV4 + ":" + SEND_SOCKET.getLocalPort();
        byte[] buf = text. getBytes();
        sendUdp(buf, ipArr[0], RECEIVE_PORT, SEND_SOCKET);
        //Query the nat information of two client segments with the same key
        String result = doGet("http://" + TAR_URL + "/Nat/getNatMap?key=" + KEY);
        if (result. isEmpty()) {
            return;
        }
        List<String> list = JSON. parseObject(result, List. class);
        // The same key requires nat to penetrate the ip array
        log.info("Key value: {} to penetrate the ip array {}", KEY, JSON.toJSONString(list));
        String client = "";
        for (String str : list) {
            if (str == null || str. isEmpty()) {
                continue;
            }
            String[] strArr = str. split(":");
            if (strArr[0].equals(ipV4)) {
                client = str;
            }
            if (!strArr[0].equals(ipV4)) {
                //upd hole
                String[] udpArr = str.split("/")[1].split(":");
                // 2. Specify the specific data to be sent.
                String clientMsg = "From client " + client + "upd message";
                byte[] data = clientMsg. getBytes();
                sendUdp(data, udpArr[0], Integer. valueOf(udpArr[1]), SEND_SOCKET);
            }
        }
    }

    public static void sendUdp(byte[] data, String ip, int port, DatagramSocket datagramSocket) throws IOException {
        // 3. Encapsulate the data into a data packet.
        DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(ip), port);
        log.info("Send upd information to the server" + ip + ":" + port + "");
        datagramSocket. send(packet);
    }

    /**
     * The server receives the upd request
     *
     * @throws IOException
     */
    public void receiveUpdMsg() throws IOException {
        if (IS_SERVER) {
            //Create a data packet for receiving data
            byte[] bytes = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bytes, bytes. length);
            //DatagramSocket(int port) constructs a datagram socket and binds it to the specified port on the local host
            if (RECEIVE_SOCKET == null) {
                RECEIVE_SOCKET = new DatagramSocket(RECEIVE_PORT);
            }
            DatagramSocket ds = RECEIVE_SOCKET;
            //Call the method of the DatagramSocket object to receive data
            ds. receive(dp);
            InetAddress address = dp. getAddress();
            int port = dp. getPort();
            String url = address. getHostAddress() + ":" + port;
            //Parse the packet and display the data on the console
            byte[] data = dp. getData();
            //int getLength() returns the length of the data to be sent or the length of the received data
            int length = dp. getLength();
            String dataString = new String(data, 0, length);
            log.info("\\
Client Nat address: **** {}**** \\
Client intranet address: **** {} **** ", url, dataString) ;
            String[] clientArr = dataString. split(":");
            if (IS_SERVER) {
                String key = clientArr[0];
                putNatMap(key, clientArr[1] + ":" + clientArr[2] + "/" + url);
                log.info("key :{} mapping array update completed", KEY);
            }
        }
    }

    private void putNatMap(String key, String value) {
        if (NAT_MAP. get(KEY) == null) {
            List<String> tempArr = new ArrayList<>();
            tempArr.add(value);
            NAT_MAP.put(key, tempArr);
        } else {
            List<String> tempArr = (List<String>) NAT_MAP.get(key);
            String refresh = null;
            for (String str : tempArr) {
                String uuid = StringUtils. substringBeforeLast(str, ":");
                String uuidValue = StringUtils. substringBeforeLast(value, ":");
                if (uuid. equals(uuidValue)) {
                    refresh = str;
                    break;
                }
            }
            if (refresh != null) {
                tempArr. remove(refresh);
            }
            tempArr.add(value);
            NAT_MAP.put(key, tempArr);
        }
    }
}
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import spike.schedule.NatSchedule;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.DatagramSocket;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import static spike.schedule.NatSchedule.RECEIVE_SOCKET;
import static spike.schedule.NatSchedule.SEND_SOCKET;
import static spike.schedule.NatSchedule.sendUdp;

/**
 * @PACKAGE_NAME: com.example.controller
 * @NAME: CheckSyncState
 * @USER: spike
 * @DATE: 2023/5/8 8:57
 * @PROJECT_NAME: SendAlertMsg
 */
@Slf4j
@RestController
@RequestMapping("Nat")
public class CheckSyncStateController {
    @Value("${isserver}")
    private boolean IS_SERVER;

    /**
     * Query NAT_MAP
     */
    @ApiOperation(value = "Query NAT_MAP", tags = "Nat Upd hole punching")
    @RequestMapping(value = "/getNatMap", method = RequestMethod.GET)
    public Object getSyncState(@RequestParam(required = false, value = "key") String key) {
        if (!key. isEmpty()) {
            return NatSchedule.NAT_MAP.get(key);
        }
        return NatSchedule.NAT_MAP;
    }

    /**
     * Clear the specified NAT_MAP key
     */
    @ApiOperation(value = "Clear the specified NAT_MAP key", tags = "Nat Upd hole punching")
    @RequestMapping(value = "/clearNatMap", method = RequestMethod.GET)
    public Object clearNatMap(@RequestParam(required = false, value = "key") String key) {
        if (NatSchedule. NAT_MAP. get(key) != null) {
            NatSchedule.NAT_MAP.put(key, null);
            return NatSchedule.NAT_MAP.get(key);
        }
        return NatSchedule.NAT_MAP.get(key);
    }

  
    /**
     * ask
     */
    @ApiOperation(value = "udp punching", tags = "CheckSyncStateController check synchronization status")
    @RequestMapping(value = "/sendToUdp", method = RequestMethod.GET)
    public String sendToUdp(@RequestParam(value = "key") String key, @RequestParam(value = "client_ip") String client_ip, @RequestParam(value = "client_port") Integer client_port) throws IOException {
        DatagramSocket datagramSocket;
        if (IS_SERVER) {
            datagramSocket = RECEIVE_SOCKET;
        } else {
            datagramSocket = SEND_SOCKET;
        }
        sendUdp(key.getBytes(), client_ip, client_port, datagramSocket);
        log.info("GET interface sends upd information to server {}:{}: {}", client_ip, client_port, key);
        return "Drilling completed";
    }

    public static String doGet(String httpurl) {
        HttpURLConnection connection = null;
        InputStream is = null;
        BufferedReader br = null;
        String result = null;// Return the result string
        try {
            // Create remote url connection object
            URL url = new URL(httpurl);
            // Open a connection through the remote url connection object and force it into the httpURLConnection class
            connection = (HttpURLConnection) url. openConnection();
            // Set the connection method: get
            connection.setRequestMethod("GET");
            // Set the timeout for connecting to the host server: 15000 milliseconds
            connection.setConnectTimeout(15000);
            // Set the time to read the data returned by the remote: 60000 milliseconds
            connection.setReadTimeout(60000);
            // send request
            connection. connect();
            // Connect through connection to get the input stream
            if (connection. getResponseCode() == 200) {
                is = connection. getInputStream();
                // Encapsulate the input stream is, and specify the character set
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                // store data
                StringBuffer sbf = new StringBuffer();
                String temp = null;
                while ((temp = br. readLine()) != null) {
                    sbf.append(temp);
                    sbf.append("\r\\
");
                }
                result = sbf.toString();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // close resource
            if (null != br) {
                try {
                    br. close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (null != is) {
                try {
                    is. close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            connection.disconnect();// close the remote connection
        }

        return result;
    }

}
server:
  port: 5001
#Server public network address
tarurl: xx.xxx.xx.xxx:5001
# Whether the server
isserver: false
#Client connection unique key value
key: define a uuid yourself
sendport: 5001
receiveport: 7091

Nat intranet penetration to obtain the map of the connection server

http://server address:5001/Nat/getNatMap?key=unique uuid defined

Return example:

[

“192.168.0.117:50979/x11.xx2.89.xx2:15623”

]

upd send message interface

http://intranet IPV4:5001/Nat/sendToUdp?client_ip=target public network ip & amp;client_port=target nat port & amp;key=send content

Return example:

Drilling completed

The result display: