This title uses two Don's and three names. It is actually the same thing, but there are different sayings on the Internet, in addition, it seems that some people call TCP drilling. (My friend Niu asked me, "Do you want to punch the hole? Would you like to help me borrow an electric drill?" "~! · ¥ % ...... ·!").
Let's take a look at the technical background:
The rapid development of the Internet and the limit on the number of IPv4 addresses make Network Address Translation (NAT, network address trans2lation) devices widely used. A nat device allows multiple hosts in the same NAT device to share the same public network (the network in the same NAT device is called a private network, and the network in the NAT device is called a public network) ip address. A private IP address communicates with other hosts through the NAT device. Shows the Public and Private IP address domains:
Wan and Private Network
Generally, a connection is actively initiated by a host in the private network (such as the "Computer A-01"), the packet is converted to the server on the public network (such as the "server "), after the connection is established, data can be transmitted in two directions. The NAT device allows hosts in the private network to send data to hosts in the public network, but does not allow active transmission in the opposite direction, however, in some special cases, it is necessary to connect hosts in different private networks (such as P2P software, network conferencing, and video transmission). The problem of tcp nat traversal must be solved. There are a lot of articles on UDP traversing NAT on the Internet, and there are supporting source code, but I personally think that although UDP data is fast, it is not guaranteed, in addition, the temporary port number prepared by Nat for UDP has a life cycle limit, which is not convenient enough. TCP connections are preferred for applications that require high transmission quality (for example, file transmission ).
There are also a lot of articles about TCP traversal NAT (TCP hole) on the Internet, but unfortunately I have not found the relevant source code for reference, I used my spare time to write a source code that enables TCP traversal through NAT, so that different Intranet hosts can establish direct TCP communication.
Here we need to introduce the NAT type:
The type of NAT device has a very important impact on tcp nat traversal. According to the port ing method, Nat can be divided into the following four categories. The first three Nat types can be collectively referred to as the cone type.
(1) Full Cone: Nat maps all requests from the same internal IP address and port to the same external IP address and port. Any external host can send IP packets to the internal host through the ing.
(2) Restricted Cone: Nat maps all requests from the same internal IP address and port to the same external IP address and port. However, an internal host can only send an IP packet to an external host whose IP address is X.
(3) Port restricted Cone: the port restriction clone is similar to the restriction clone, but the port number restriction is exceeded, that is, only the internal host is directed to the IP address X first, only when an external host whose port is P sends an IP packet can the external host send an IP packet whose source port is P to the internal host.
(4) symmetric NAT: This type of NAT is different from the preceding three types, when the same internal host uses the same port to communicate with the external host with different addresses, Nat maps the internal host differently. Symmetric Nat does not guarantee the consistency between the private and public IP addresses in all sessions. Instead, it assigns a new port number for each new session.
Let's assume that there is a server s there is an IP address on the public network, two private networks are connected to the public network by the NAT-A and NAT-B, there is a client a behind the NAT-A, there is a client B behind the NAT-B, now, we need to use s to establish a direct TCP connection between A and B, that is, B to a hole, let a connect directly to host B along this hole, as if the NAT-B does not exist.
The implementation process is as follows (see source code ):
1. s starts two network listeners. One is the listener of the primary connection and the other is the listener of the assisted hole.
2. A and B maintain contact with the master connection of S.
3. When a needs to establish a direct TCP connection with B, first connect to the "assist hole" Port of S and send a request for assistance with the connection. At the same time, start listening on the port number. Note that to bind a socket to the same network terminal, you must set the so_reuseaddr attribute (that is, allow reuse) for these sockets. Otherwise, the listening will fail.
4, s [assist hole] After receiving the application of a through the [master connection] to notify B, and a after the NAT-A conversion of public network IP address and port information to B.
5. B First connects to the "assist hole" Port of s after receiving the connection notification from S, and disconnects immediately after sending some data, the purpose of this is to let s know the Internet IP address and port number of B after NAT-B conversion.
6. B tries to connect with a's public IP address and port after NAT-A conversion, according to different routers will have different results, some routers can establish connections in this operation (for example, the tplink R402 I use). Most routers directly discard the self-sent SYN request packet, leading to connect failure, but the NAT-A will record the source address and port number of the connection, for the next real connection to prepare, this is the so-called hole, that is, B to a hole, next time, a can directly connect to the port number used by B.
7. Start listening on the same port while logging in to client B. B. After everything is ready, reply to the message "I am ready" through the master connection with S ", s tells a after receiving the Internet IP address and port number of B after NAT-B conversion.
8. After receiving the Internet IP address and port number of B replied by S, A starts to connect to Internet IP address and port number of B, since B tried to connect to a's public IP address and port in step 6, the NAT-A records the information of this connection, so when a actively connects to B, the NAT-B will think of it as valid SYN data and allow it to be established through a direct TCP connection.
I am afraid it is difficult to clearly explain the entire implementation process. In addition, my language skills are poor (I scored 75 points for the Chinese language in the college entrance examination, with a total score of 150 points. I am ashamed ), so I had to use code to explain the problem.
// Server address and port number Definition
# Define srv_tcp_main_port 4000 // The server master connection port number
# Define srv_tcp_hole_port 8000 // The port number of the server responding to the client hole request
The two ports are fixed, and the server starts listening to the two ports when S is started.
//
// Send the new client logon information to all logged-on clients, but do not send it to yourself
//
Bool sendnewuserloginnotifytoall (maid, uint nclientport, DWORD dwid)
{
Assert (lpszclientip & nclientport> 0 );
G_csfor_ptrary_sockclient.lock ();
For (INT I = 0; I <g_ptrary_sockclient.getsize (); I ++)
{
Csockclient * psockclient = (csockclient *) g_ptrary_sockclient.getat (I );
If (psockclient & psockclient-> m_bmainconn & psockclient-> m_dwid> 0 & psockclient-> m_dwid! = Dwid)
{
If (! Psockclient-> sendnewuserloginnotify (lpszclientip, nclientport, dwid ))
{
G_csfor_ptrary_sockclient.unlock ();
Return false;
}
}
}
G_csfor_ptrary_sockclient.unlock ();
Return true;
}
When a new client is connected to the server, the server sends the client information (IP address and port number) to other clients.
//
// Performer: Client
// A new client B is logged on. I (Client A) connected to the server port srv_tcp_hole_port and applied to establish a direct TCP connection with client B.
//
Bool handle_newuserlogin (csocket & mainsock, t_newuserloginpkt * pnewuserloginpkt)
{
Printf ("new user (% s: % u) Login Server
", Pnewuserloginpkt-> szclientip,
Pnewuserloginpkt-> nclientport, pnewuserloginpkt-> dwid );
Bool Bret = false;
DWORD dwthreadid = 0;
T_reqconnclientpkt reqconnclientpkt;
Csocket sock;
Cstring cssocketaddress;
Char szrecvbuffer [net_buffer_size] = {0 };
Int nrecvbytes = 0;
// Create a socket to connect to the server's port number srv_tcp_hole_port
Try
{
If (! Sock. socket ())
{
Printf ("create socket failed: % s
", Hwformatmessage (getlasterror ()));
Goto finished;
}
Uint noptvalue = 1;
If (! Sock. setsockopt (so_reuseaddr, & noptvalue, sizeof (uint )))
{
Printf ("setsockopt socket failed: % s
", Hwformatmessage (getlasterror ()));
Goto finished;
}
If (! Sock. BIND (0 ))
{
Printf ("bind socket failed: % s
", Hwformatmessage (getlasterror ()));
Goto finished;
}
If (! Sock. Connect (g_pserveraddess, srv_tcp_hole_port ))
{
Printf ("connect to [% s: % d] failed: % s
", G_pserveraddess,
Srv_tcp_hole_port, hwformatmessage (getlasterror ()));
Goto finished;
}
}
Catch (cexception E)
{
Char szerror [255] = {0 };
E. geterrormessage (szerror, sizeof (szerror ));
Printf ("exception occur, % s
", Szerror );
Goto finished;
}
G_psock_makehole = & sock;
Assert (g_nholeport = 0 );
Verify (sock. getsockname (cssocketaddress, g_nholeport ));
// Create a thread to listen for connection requests from Port g_nholeport
Dwthreadid = 0;
G_hthread_listen =: createthread (null, 0,: threadproc_listen, lpvoid (null), 0, & dwthreadid );
If (! Handle_is_valid (g_hthread_listen) return false;
Sleep (3000 );
// I (Client A) sends an application to the srv_tcp_hole_port Port port used by the server to assist in the holes. I hope to establish a connection with the newly logged-on client B.
// The server will tell client B the external IP address and port number used for punching in.
Assert (g_welcomepkt.dwid> 0 );
Reqconnclientpkt. dwinviterid = g_welcomepkt.dwid;
Reqconnclientpkt. dwinvitedid = pnewuserloginpkt-> dwid;
If (sock. Send (& reqconnclientpkt, sizeof (t_reqconnclientpkt ))! = Sizeof (t_reqconnclientpkt ))
Goto finished;
// Wait for the server to respond and tell me the external IP address and port number of client B (Client)
Nrecvbytes = sock. Receive (szrecvbuffer, sizeof (szrecvbuffer ));
If (nrecvbytes> 0)
{
Assert (nrecvbytes = sizeof (t_srvreqdirectconnectpkt ));
Packet_type * pepackettype = (packet_type *) szrecvbuffer;
Assert (pepackettype & * pepackettype = packet_type_tcp_direct_connect );
Sleep (1000 );
Handle_srvreqdirectconnect (t_srvreqdirectconnectpkt *) szrecvbuffer );
Printf ("handle_srvreqdirectconnect end
");
}
// The other party is disconnected.
Else
{
Goto finished;
}
Bret = true;
Finished:
G_psock_makehole = NULL;
Return Bret;
}
Assume that client a is started first. After client B is started, client a receives the logon notification from the new client of server s and obtains the public IP address and port of client B, client A starts the thread to connect to the [assist hole] port of S (the local port number can be obtained using the getsocketname () function, assuming m), and requests s to assist in TCP hole punching, then start the thread to listen for connection requests on the local port (M), and wait for the response from the server.
//
// Client A requests me (server) to connect to client B. This package should be received in the socket
//
Bool csockclient: handle_reqconnclientpkt (t_reqconnclientpkt * preqconnclientpkt)
{
Assert (! M_bmainconn );
Csockclient * psockclient_ B = findsocketclient (preqconnclientpkt-> dwinvitedid );
If (! Psockclient_ B) return false;
Printf ("% s: % u invite % s: % u connection
", M_cspeeraddress, m_npeerport, m_dwid,
Psockclient_ B-> m_cspeeraddress, psockclient_ B-> m_npeerport, psockclient_ B-> m_dwid );
// Client A wants to establish a direct TCP connection with client B, and the server is responsible for notifying the external IP address and port number of A to client B
T_srvreqmakeholepkt srvreqmakeholepkt;
Srvreqmakeholepkt. dwinviterid = preqconnclientpkt-> dwinviterid;
Srvreqmakeholepkt. dwinviterholeid = m_dwid;
Srvreqmakeholepkt. dwinvitedid = preqconnclientpkt-> dwinvitedid;
Strncpy_cs (srvreqmakeholepkt. szclientholeip, m_cspeeraddress );
Srvreqmakeholepkt. nclientholeport = m_npeerport;
If (psockclient_ B-> sendchunk (& srvreqmakeholepkt, sizeof (t_srvreqmakeholepkt), 0 )! = Sizeof (t_srvreqmakeholepkt ))
Return false;
// Wait until client B holes are completed. After the holes are completed, notify Client A to directly connect the client's external IP address and port number.
If (! Handle_is_valid (m_hevtwaitclientbhole ))
Return false;
If (waitforsingleobject (m_hevtwaitclientbhole, 6000*1000) = wait_object_0)
{
If (sendchunk (& m_srvreqdirectconnectpkt, sizeof (t_srvreqdirectconnectpkt), 0)
= Sizeof (t_srvreqdirectconnectpkt ))
Return true;
}
Return false;
}
After receiving the request from client a, server s notifies client B that client B needs to open a hole to Client A, that is, let client B try to connect with the public IP address and port of Client.
//
// Performer: client B
// The processing server asks me (client B) to open a hole in another client (A) and perform the hole operation in the thread.
// First connect to the srv_tcp_hole_port port used by the server to assist in the holes, tell client a external IP address and port number of client a (client B) through the server, and then start the thread to perform holes,
// After receiving this information, client a will initiate a connection to the external IP address and port number of my (client B) (this connection will be completed after client B holes, so
// Client B's Nat will not discard this Syn packet, so that the connection can be established)
//
Bool handle_srvreqmakehole (csocket & mainsock, t_srvreqmakeholepkt * psrvreqmakeholepkt)
{
Assert (psrvreqmakeholepkt );
// Create a socket and connect to the server's port number srv_tcp_hole_port. After the connection is established, a disconnect request is sent to the server, and the connection is closed.
// The purpose of the connection here is to let the server know the external IP address and port number of my (client B) to notify Client
Csocket sock;
Try
{
If (! Sock. Create ())
{
Printf ("create socket failed: % s
", Hwformatmessage (getlasterror ()));
Return false;
}
If (! Sock. Connect (g_pserveraddess, srv_tcp_hole_port ))
{
Printf ("connect to [% s: % d] failed: % s
", G_pserveraddess,
Srv_tcp_hole_port, hwformatmessage (getlasterror ()));
Return false;
}
}
Catch (cexception E)
{
Char szerror [255] = {0 };
E. geterrormessage (szerror, sizeof (szerror ));
Printf ("exception occur, % s
", Szerror );
Return false;
}
Cstring cssocketaddress;
Assert (g_nholeport = 0 );
Verify (sock. getsockname (cssocketaddress, g_nholeport ));
// The port number srv_tcp_hole_port used by the connection server to assist in punching in, send a disconnect request, and then disconnect the connection. When the server receives this package, it will also
// Disconnect
T_reqsrvdisconnectpkt reqsrvdisconnectpkt;
Reqsrvdisconnectpkt. dwinviterid = psrvreqmakeholepkt-> dwinvitedid;
Reqsrvdisconnectpkt. dwinviterholeid = psrvreqmakeholepkt-> dwinviterholeid;
Reqsrvdisconnectpkt. dwinvitedid = psrvreqmakeholepkt-> dwinvitedid;
Assert (reqsrvdisconnectpkt. dwinvitedid = g_welcomepkt.dwid );
If (sock. Send (& reqsrvdisconnectpkt, sizeof (t_reqsrvdisconnectpkt ))! = Sizeof (t_reqsrvdisconnectpkt ))
Return false;
Sleep (100 );
Sock. Close ();
// Create a thread to Hole in the external IP address and port number of client
T_srvreqmakeholepkt * psrvreqmakeholepkt_new = new t_srvreqmakeholepkt;
If (! Psrvreqmakeholepkt_new) return false;
Memcpy (psrvreqmakeholepkt_new, psrvreqmakeholepkt, sizeof (t_srvreqmakeholepkt ));
DWORD dwthreadid = 0;
G_hthread_makehole =: createthread (null, 0,: threadproc_makehole,
Lpvoid (psrvreqmakeholepkt_new), 0, & dwthreadid );
If (! Handle_is_valid (g_hthread_makehole) return false;
// Create a thread to listen for connection requests from Port g_nholeport
Dwthreadid = 0;
G_hthread_listen =: createthread (null, 0,: threadproc_listen, lpvoid (null), 0, & dwthreadid );
If (! Handle_is_valid (g_hthread_listen) return false;
// Wait until the hole hitting and listening are completed
Handle hevtary [] = {g_hevt_listenfinished, g_hevt_makeholefinished };
If (: waitformultipleobjects (length (hevtary), hevtary, true, 30*1000) = wait_timeout)
Return false;
T_holelistenreadypkt holelistenreadypkt;
Holelistenreadypkt. dwinvitedid = psrvreqmakeholepkt-> dwinvitedid;
Holelistenreadypkt. dwinviterholeid = psrvreqmakeholepkt-> dwinviterholeid;
Holelistenreadypkt. dwinvitedid = psrvreqmakeholepkt-> dwinvitedid;
If (mainsock. Send (& holelistenreadypkt, sizeof (t_holelistenreadypkt ))! = Sizeof (t_holelistenreadypkt ))
{
Printf ("Send holelistenreadypkt to % s: % u failed: % s
",
G_welcomepkt.szclientip, g_welcomepkt.nclientport,
Hwformatmessage (getlasterror ()));
Return false;
}
Return true;
}
After receiving the hole logging notification from server s, client B First connects to the [assist hole] Port Number of S (the local port number can be obtained using the getsocketname () function, which is assumed to be X ), the startup thread tries to connect to the public IP address and port number of client a. The connection varies depending on the vro. If you are lucky, the connection is successful. Even if the connection fails, the hole is completed. At the same time, the startup thread needs to listen for the incoming connection on the same port (that is, the local port number X that establishes the connection with the "assist hole" Port Number of S) and wait for client a to directly connect to the port number.
//
// Performer: Client
// The server requires the active end (Client A) to directly connect to the external IP address and port number of the passive end (client B ).
//
Bool handle_srvreqdirectconnect (t_srvreqdirectconnectpkt * psrvreqdirectconnectpkt)
{
Assert (psrvreqdirectconnectpkt );
Printf ("You can connect direct to (IP: % s port: % d id: % u)
", Psrvreqdirectconnectpkt-> szinvitedip,
Psrvreqdirectconnectpkt-> ninvitedport, psrvreqdirectconnectpkt-> dwinvitedid );
// Directly establish a TCP connection with client B. If the connection is successful, the TCP hole has been successful.
Csocket sock;
Try
{
If (! Sock. socket ())
{
Printf ("create socket failed: % s
", Hwformatmessage (getlasterror ()));
Return false;
}
Uint noptvalue = 1;
If (! Sock. setsockopt (so_reuseaddr, & noptvalue, sizeof (uint )))
{
Printf ("setsockopt socket failed: % s
", Hwformatmessage (getlasterror ()));
Return false;
}
If (! Sock. BIND (g_nholeport ))
{
Printf ("bind socket failed: % s
", Hwformatmessage (getlasterror ()));
Return false;
}
For (int ii = 0; II <100; II ++)
{
If (waitforsingleobject (g_hevt_connectok, 0) = wait_object_0)
Break;
DWORD dwarg = 1;
If (! Sock. IOCTL (fionbio, & dwarg ))
{
Printf ("IOCTL failed: % s
", Hwformatmessage (getlasterror ()));
}
If (! Sock. Connect (psrvreqdirectconnectpkt-> szinvitedip, psrvreqdirectconnectpkt-> ninvitedport ))
{
Printf ("connect to [% s: % d] failed: % s
",
Psrvreqdirectconnectpkt-> szinvitedip,
Psrvreqdirectconnectpkt-> ninvitedport,
Hwformatmessage (getlasterror ()));
Sleep (100 );
}
Else break;
}
If (waitforsingleobject (g_hevt_connectok, 0 )! = Wait_object_0)
{
If (handle_is_valid (g_hevt_connectok) setevent (g_hevt_connectok );
Printf ("connect to [% s: % d] successfully !!!
",
Psrvreqdirectconnectpkt-> szinvitedip, psrvreqdirectconnectpkt-> ninvitedport );
// Receives Test Data
Printf ("inserting ing data...
");
Char szrecvbuffer [net_buffer_size] = {0 };
Int nrecvbytes = 0;
For (INT I = 0; I <1000; I ++)
{
Nrecvbytes = sock. Receive (szrecvbuffer, sizeof (szrecvbuffer ));
If (nrecvbytes> 0)
{
Printf ("-- >>> received data: % s
", Szrecvbuffer );
Memset (szrecvbuffer, 0, sizeof (szrecvbuffer ));
Sleep_break (1 );
}
Else
{
Sleep_break (300 );
}
}
}
}
Catch (cexception E)
{
Char szerror [255] = {0 };
E. geterrormessage (szerror, sizeof (szerror ));
Printf ("exception occur, % s
", Szerror );
Return false;
}
Return true;
}
After client B holes and listening are ready, the server s replies to Client A, and client a connects directly to the public IP address and port of client B, and the data can be sent and received normally, to test whether a direct TCP connection is enabled, server s can be forcibly terminated during data transmission and receiving to check whether data transmission and receiving are still normal.
Procedure and method:
To prepare the environment, use two local networks connected to the public network and one computer with a public IP address if you want to test the environment, xiao CaO and Niu have spent a lot of time, and I have occupied their computer. I would like to express my thanks ). If this is not the case, program execution may be abnormal, because I have not processed the same LAN for the time being.
Execute the “tcpholesrv.exe program on a computer with a public address. Assume that the public IP address of this computer is "129.208.12.38 ".
Execute “tcpholeclt-a.exe 129.208.12.38 on a computer in the Local Network"
Execute “tcpholeclt-b.exe 129.208.12.38 on a computer in the Local Network B"
Interface after the program is successfully executed: "send data" or "sent Ed data" on the client indicates that the TCP connection through NAT has been established and the data sending and receiving has been OK.
Server s
Client
Client B
This code is successfully tested on Windows XP, A tianwei LAN, a telecom LAN, and a Telephone Dialing Network.
Due to the relationship between time and level, the Code and articles are not well written, and I hope they can play a role in attracting others. The Code only implements client connection between different LAN, as for the problem that hosts in the same LAN or one of the clients itself have a public IP address, it is not considered for the moment (because the processing is too simple, compare the mask or public IP address to determine). In addition, the code reuse of the program's error prevention code is not good, but the function is implemented. I think
From: http://tech.ddvip.com/2007-08/118640712430986.html