├── README.md ├── .gitattributes ├── .gitignore ├── ClientA.java ├── ClientB.java └── Server.java /README.md: -------------------------------------------------------------------------------- 1 | # holepunchingsample 2 | This project contains java programs for basic hole punching client and server 3 | 4 | Steps: 5 | 1. Compile server, clientA, and clientB. 6 | 2. Run server 7 | 3. Run client A 8 | 4. Run client B 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /ClientA.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | import java.io.BufferedOutputStream; 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.net.DatagramPacket; 12 | import java.net.DatagramSocket; 13 | import java.net.InetAddress; 14 | import java.net.Socket; 15 | import java.net.UnknownHostException; 16 | 17 | /** 18 | * 19 | * @author user 20 | */ 21 | public class ClientA { 22 | 23 | private static InetAddress serverIP; 24 | private static int serverTcpPort; 25 | private static int serverUdpPort; 26 | private Socket socket; 27 | private final BufferedReader in; 28 | private final BufferedOutputStream out; 29 | private final DatagramSocket dgSocket; 30 | private DatagramPacket sendPacket; 31 | private String resp = ""; 32 | private String[] tokens = null; 33 | private boolean respRead; 34 | 35 | public ClientA(InetAddress ip, int tcpPort, int udpPort) throws IOException { 36 | 37 | //create a socket to connect to the server 38 | try { 39 | socket = new Socket(ip, tcpPort); 40 | } catch (IOException ex) { 41 | System.err.println("Exception creating a socket: " + ex); 42 | } 43 | 44 | //create input and output stream 45 | in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 46 | out = new BufferedOutputStream(socket.getOutputStream()); 47 | 48 | //Create a Datagram Socket for UDP messages 49 | dgSocket = new DatagramSocket(); 50 | 51 | //create a byte array to hold the initial message to be sent to the server 52 | byte[] sendData = "one".getBytes(); 53 | 54 | //create a packet to send udp messge 55 | sendPacket = new DatagramPacket(sendData, sendData.length, ip, udpPort); 56 | 57 | //create a loop to send the udp packets to the server 58 | /** 59 | * IMPORTANT! Using a loop to send the packets is just to ensure that 60 | * the UDP packets reach the server. There may be a loss of UDP packets 61 | * for its unreliability. You can change the loop count if required. 62 | */ 63 | System.out.println("sending initial udp message"); 64 | for (int i = 0; i < 100000; i++) { 65 | dgSocket.send(sendPacket); 66 | System.out.println("" + i); 67 | } 68 | System.out.println("Sent initial udp messages"); 69 | //create a loop to read the TCP response from the server 70 | while (respRead != true) { 71 | resp = in.readLine(); 72 | 73 | tokens = resp.split("~~"); //split response into tokens for IP and Port 74 | 75 | System.out.println("****************************************"); 76 | System.out.println("My PUBLIC IP seen by server: " + tokens[0]); 77 | System.out.println("My PUBLIC UDP PORT seen by server: " + tokens[1]); 78 | System.out.println("****************************************\n"); 79 | 80 | System.out.println("****************************************"); 81 | System.out.println("CLIENT B PUBLIC IP seen by server: " + tokens[2]); 82 | System.out.println("CLIENT B PUBLIC UDP PORT seen by server: " + tokens[3]); 83 | System.out.println("****************************************"); 84 | 85 | respRead = true; 86 | 87 | //ACK SERVER 88 | out.write("ackOne".getBytes()); 89 | out.write('\n'); 90 | out.flush(); 91 | 92 | } 93 | 94 | //Create thread to receive UDP packets 95 | new Thread(new Runnable() { 96 | private String udpMsg = ""; 97 | 98 | @Override 99 | public void run() { 100 | //create datagram packet to receive udp messages 101 | DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024); 102 | while (true) { 103 | try { 104 | dgSocket.receive(receivePacket); //receiver udp packet 105 | 106 | udpMsg = new String(receivePacket.getData()); //get data from the received udp packet 107 | 108 | System.out.println("Received: " + udpMsg.trim() + ", From: IP " + receivePacket.getAddress().getHostAddress().trim() + " Port " + receivePacket.getPort()); 109 | } catch (IOException ex) { 110 | System.err.println("Error " + ex); 111 | } 112 | 113 | } 114 | } 115 | 116 | }).start(); 117 | 118 | //create Loop to send udp packets 119 | int j = 0; 120 | String msg = ""; 121 | while (true) { 122 | msg = "I AM CLIENT A " + j; 123 | sendData = msg.getBytes(); 124 | DatagramPacket sp = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(tokens[2].trim()), Integer.parseInt(tokens[3].trim())); 125 | dgSocket.send(sp); 126 | j++; 127 | try{ 128 | Thread.sleep(2000); 129 | }catch(Exception e){ 130 | System.err.println("Exception in Thread sleep"+e); 131 | } 132 | } 133 | 134 | } 135 | 136 | public static void main(String[] args) throws UnknownHostException, IOException { 137 | 138 | if (args.length > 0) { 139 | try { 140 | serverIP = InetAddress.getByName(args[0].trim()); 141 | serverTcpPort = Integer.parseInt(args[1].trim()); 142 | serverUdpPort = Integer.parseInt(args[2].trim()); 143 | } catch (Exception ex) { 144 | System.err.println("Error in input"); 145 | System.out.println("USAGE: java ClientA serverIp serverTcpPort serverUdpPort"); 146 | System.out.println("Example: java ClientA 127.0.0.1 9000 9001"); 147 | System.exit(0); 148 | } 149 | 150 | } else { 151 | System.out.println("ClientA running with default ports 9000 and 9001"); 152 | serverIP = InetAddress.getByName("127.0.0.1"); 153 | serverTcpPort = 9000; 154 | serverUdpPort = 9001; 155 | 156 | } 157 | new ClientA(serverIP, serverTcpPort, serverUdpPort); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /ClientB.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | 8 | import java.io.BufferedOutputStream; 9 | import java.io.BufferedReader; 10 | import java.io.IOException; 11 | import java.io.InputStreamReader; 12 | import java.net.DatagramPacket; 13 | import java.net.DatagramSocket; 14 | import java.net.InetAddress; 15 | import java.net.Socket; 16 | import java.net.UnknownHostException; 17 | 18 | /** 19 | * 20 | * @author user 21 | */ 22 | public class ClientB { 23 | 24 | private static InetAddress serverIP; 25 | private static int serverTcpPort=9000; 26 | private static int serverUdpPort=9001; 27 | private Socket socket; 28 | private final BufferedReader in; 29 | private final BufferedOutputStream out; 30 | private final DatagramSocket dgSocket; 31 | private DatagramPacket sendPacket; 32 | private String resp = ""; 33 | private String[] tokens = null; 34 | private boolean respRead; 35 | 36 | public ClientB(InetAddress ip, int tcpPort, int udpPort) throws IOException { 37 | 38 | //create a socket to connect to the server 39 | try { 40 | socket = new Socket(ip, tcpPort); 41 | } catch (IOException ex) { 42 | System.err.println("Exception creating a socket: " + ex); 43 | } 44 | 45 | //create input and output stream 46 | in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 47 | out = new BufferedOutputStream(socket.getOutputStream()); 48 | 49 | //Create a Datagram Socket for UDP messages 50 | dgSocket = new DatagramSocket(); 51 | 52 | //create a byte array to hold the initial message to be sent to the server 53 | byte[] sendData = "two".getBytes(); 54 | 55 | //create a packet to send udp messge 56 | sendPacket = new DatagramPacket(sendData, sendData.length, ip, udpPort); 57 | 58 | //create a loop to send the udp packets to the server 59 | /** 60 | * IMPORTANT! Using a loop to send the packets is just to ensure that 61 | * the UDP packets reach the server. There may be a loss of UDP packets 62 | * for its unreliability. You can change the loop count if required. 63 | */ 64 | System.out.println("sending initial udp message"); 65 | for (int i = 0; i < 100000; i++) { 66 | dgSocket.send(sendPacket); 67 | System.out.println("" + i); 68 | } 69 | System.out.println("Sent initial udp messages"); 70 | 71 | //create a loop to read the TCP response from the server 72 | while (respRead != true) { 73 | resp = in.readLine(); 74 | 75 | tokens = resp.split("~~"); //split response into tokens for IP and Port 76 | 77 | System.out.println("****************************************"); 78 | System.out.println("My PUBLIC IP seen by server: " + tokens[0]); 79 | System.out.println("My PUBLIC UDP PORT seen by server: " + tokens[1]); 80 | System.out.println("****************************************\n"); 81 | 82 | System.out.println("****************************************"); 83 | System.out.println("CLIENT A PUBLIC IP seen by server: "+tokens[2] ); 84 | System.out.println("CLIENT A PUBLIC UDP PORT seen by server: "+tokens[3] ); 85 | System.out.println("****************************************"); 86 | 87 | respRead = true; 88 | 89 | //ACK SERVER 90 | out.write("ackTwo".getBytes()); 91 | out.write('\n'); 92 | out.flush(); 93 | 94 | } 95 | 96 | //Create thread to receive UDP packets 97 | new Thread(new Runnable() { 98 | private String udpMsg = ""; 99 | 100 | @Override 101 | public void run() { 102 | //create datagram packet to receive udp messages 103 | DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024); 104 | while (true) { 105 | try { 106 | dgSocket.receive(receivePacket); //receiver udp packet 107 | 108 | udpMsg = new String(receivePacket.getData()); //get data from the received udp packet 109 | 110 | System.out.println("Received: " + udpMsg.trim() + ", From: IP " + receivePacket.getAddress().getHostAddress().trim() + " Port " + receivePacket.getPort()); 111 | } catch (IOException ex) { 112 | System.err.println("Error " + ex); 113 | } 114 | 115 | } 116 | } 117 | 118 | }).start(); 119 | 120 | //create Loop to send udp packets 121 | int j = 0; 122 | String msg = ""; 123 | while (true) { 124 | msg = "I AM CLIENT B " + j; 125 | sendData = msg.getBytes(); 126 | DatagramPacket sp = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(tokens[2].trim()), Integer.parseInt(tokens[3].trim())); 127 | dgSocket.send(sp); 128 | j++; 129 | try{ 130 | Thread.sleep(2000); 131 | }catch(Exception e){ 132 | System.err.println("Exception in Thread sleep"+e); 133 | } 134 | 135 | } 136 | 137 | } 138 | 139 | public static void main(String[] args) throws UnknownHostException, IOException { 140 | 141 | if (args.length > 0) { 142 | try { 143 | serverIP = InetAddress.getByName(args[0].trim()); 144 | serverTcpPort = Integer.parseInt(args[1].trim()); 145 | serverUdpPort = Integer.parseInt(args[2].trim()); 146 | } catch (Exception ex) { 147 | System.err.println("Error in input"); 148 | System.out.println("USAGE: java ClientB serverIp serverTcpPort serverUdpPort"); 149 | System.out.println("Example: java ClientB 127.0.0.1 9000 9001"); 150 | System.exit(0); 151 | } 152 | 153 | } else { 154 | System.out.println("ClientB running with default ports 9000 and 9001"); 155 | serverIP = InetAddress.getByName("127.0.0.1"); 156 | serverTcpPort = 9000; 157 | serverUdpPort = 9001; 158 | 159 | } 160 | new ClientB(serverIP, serverTcpPort, serverUdpPort); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Server.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | 8 | import java.io.BufferedOutputStream; 9 | import java.io.BufferedReader; 10 | import java.io.IOException; 11 | import java.io.InputStreamReader; 12 | import java.net.DatagramPacket; 13 | import java.net.DatagramSocket; 14 | import java.net.ServerSocket; 15 | import java.net.Socket; 16 | import java.util.logging.Level; 17 | import java.util.logging.Logger; 18 | 19 | /** 20 | * 21 | * @author user 22 | */ 23 | public class Server{ 24 | 25 | private int tcpPort=9000; 26 | private int udpPort=9001; 27 | 28 | private BufferedReader inA; 29 | private BufferedOutputStream outA; 30 | 31 | private BufferedReader inB; 32 | private BufferedOutputStream outB; 33 | 34 | private ServerSocket serverSocket; 35 | private Socket clientA, clientB; 36 | 37 | private DatagramPacket receivePacket; 38 | 39 | private boolean readClientA = false; 40 | private String clientAIp = ""; 41 | private String clientAPort = ""; 42 | 43 | private boolean readClientB = false; 44 | private String clientBIp = ""; 45 | private String clientBPort = ""; 46 | 47 | private String udpMsg = ""; 48 | 49 | public Server(){ 50 | try { 51 | runServer(); 52 | } catch (IOException ex) { 53 | Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex); 54 | } 55 | } 56 | public Server(int userTcpPort, int userUdpPort){ 57 | this.tcpPort=userTcpPort; 58 | this.udpPort=userUdpPort; 59 | try { 60 | runServer(); 61 | } catch (IOException ex) { 62 | Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex); 63 | } 64 | } 65 | 66 | public static void main(String[] args) throws IOException { 67 | if (args.length>0) { 68 | new Server(Integer.parseInt(args[0].trim()), Integer.parseInt(args[1].trim())); 69 | }else{ 70 | new Server(); 71 | } 72 | 73 | } 74 | 75 | void runServer() throws IOException { 76 | //Create Server Socket for accepting Client TCP connections 77 | serverSocket = new ServerSocket(tcpPort); 78 | System.out.println("Server started with ports, TCP: "+tcpPort+" UDP: "+udpPort); 79 | 80 | System.out.println("Waiting for Client A"); 81 | //Accept first client connection 82 | clientA = serverSocket.accept(); 83 | System.out.println("Client 1 connected " + clientA.getInetAddress() + " " + clientA.getPort()); 84 | 85 | //Create input and output streams to read/write messages for CLIENT A 86 | inA = new BufferedReader(new InputStreamReader(clientA.getInputStream())); 87 | outA = new BufferedOutputStream(clientA.getOutputStream()); 88 | 89 | 90 | System.out.println("Waiting for Client B"); 91 | //Accept second client connection 92 | clientB = serverSocket.accept(); 93 | System.out.println("Client 2 connected " + clientA.getInetAddress() + " " + clientA.getPort()); 94 | 95 | //Create input and output streams to read/write messages for CLIENT B 96 | inB = new BufferedReader(new InputStreamReader(clientB.getInputStream())); 97 | outB = new BufferedOutputStream(clientB.getOutputStream()); 98 | 99 | //Create Datagram Socket for udp messages. 100 | DatagramSocket dgSocket = new DatagramSocket(udpPort); 101 | 102 | //Create Packet to receive UDP messages 103 | receivePacket = new DatagramPacket(new byte[1024], 1024); 104 | 105 | /** 106 | * IMPORTANT *** Create loop to receive initial UDP packets to detect 107 | * *** 108 | * 109 | * *** FIRST CLIENT'S PUBLIC IP AND PORTS **** 110 | */ 111 | while (readClientA != true) { 112 | dgSocket.receive(receivePacket); // Receive UDP Packet 113 | 114 | udpMsg = new String(receivePacket.getData()); //Get Data from UDP packet into a string 115 | 116 | clientAIp = "" + receivePacket.getAddress().getHostAddress(); //get public IP of clientA from UDP Packet 117 | 118 | clientAPort = "" + receivePacket.getPort(); //get public UDP PORT of clientA from UDP Packet 119 | 120 | if (udpMsg.trim().equals("one")) { 121 | readClientA = true; 122 | System.out.println("Inital UDP message from CLIENT A: " + udpMsg); 123 | } 124 | 125 | System.out.println("inside while loop1"); 126 | } 127 | System.out.println("******CLIENT A IP AND PORT DETECTED " + clientAIp + " " + clientAPort + " *****"); 128 | /** 129 | * *** END OF LOOP FOR CLIENT A **** 130 | */ 131 | 132 | /** 133 | * IMPORTANT *** Create loop to receive initial UDP packets to detect 134 | * *** 135 | * 136 | * *** SECOND CLIENT'S PUBLIC IP AND PORTS **** 137 | */ 138 | while (readClientB != true) { 139 | dgSocket.receive(receivePacket); // Receive UDP Packet 140 | 141 | udpMsg = new String(receivePacket.getData()); //Get Data from UDP packet into a string 142 | 143 | clientBIp = "" + receivePacket.getAddress().getHostAddress(); //get public IP of clientA from UDP Packet 144 | 145 | clientBPort = "" + receivePacket.getPort(); //get public UDP PORT of clientA from UDP Packet 146 | 147 | if (udpMsg.trim().equals("two")) { 148 | readClientB = true; 149 | System.out.println("Initial UDP message from CLIENT B: " + udpMsg); 150 | } 151 | 152 | System.out.println("inside while loop2"); 153 | } 154 | System.out.println("******CLIENT B IP AND PORT DETECTED " + clientBIp + " " + clientBPort + " *****"); 155 | /** 156 | * *** END OF LOOP FOR CLIENT B **** 157 | */ 158 | 159 | /* 160 | !!!!!!!!!!!CRITICAL PART!!!!!!!! 161 | The core of hole punching depends on this part. 162 | The exchange of public IP and port between the clients takes place here. 163 | */ 164 | 165 | System.out.println("***** Exchanging public IP and port between the clients *****"); 166 | while (true) { 167 | String string = clientAIp + "~~" + clientAPort + "~~" + clientBIp + "~~" + clientBPort; 168 | outA.write(string.getBytes()); //SENDING CLIENT B's public IP & PORT TO CLIENT A 169 | outA.write('\n'); 170 | outA.flush(); 171 | 172 | String string1 = clientBIp + "~~" + clientBPort + "~~" + clientAIp + "~~" + clientAPort; 173 | outB.write(string1.getBytes()); //SENDING CLIENT A's public IP & PORT TO CLIENT B 174 | outB.write('\n'); 175 | outB.flush(); 176 | } 177 | 178 | } 179 | 180 | } 181 | --------------------------------------------------------------------------------