用TCP穿透NAT(TCP打洞)的實現


1. TCP穿透原理:

    我們假設在兩個不同的局域網后面分別有2台客戶機A和 B,AB所在的局域網都分別通過一個路由器接入互聯網。互聯網上有一台服務器S。 
    現在AB是無法直接和對方發送信息的,AB都不知道對方在互聯網上真正的IP和端口, AB所在的局域網的路由器只允許內部向外主動發送的信息通過。對於B直接發送給A的路由器的消息,路由會認為其“不被信任”而直接丟棄。 
    要實現 AB直接的通訊,就必須進行以下3步:A首先連接互聯網上的服務器S並發送一條消息(對於UDP這種無連接的協議其實直接初始會話發送消息即可),這樣S就獲取了A在互聯網上的實際終端(發送消息的IP和端口號)。接着 B也進行同樣的步驟,S就知道了AB在互聯網上的終端(這就是“打洞”)。接着S分別告訴A和B對方客戶端在互聯網上的實際終端,也即S告訴A客戶B的會話終端,S告訴B客戶A的會話終端。這樣,在AB都知道了對方的實際終端之后,就可以直接通過實際終端發送消息了(因為先前雙方都向外發送過消息,路由上已經有允許數據進出的消息通道)。

2. 程序思路:

1:啟動服務器,監聽端口8877
2:第一次啟動客戶端(稱為client1),連上服務器,服務器將返回字符串first,標識這個是client1,同時,服務器將記錄下這個客戶端的(經過轉換之后的)IP和端口。
3:第二次啟動客戶端(稱為client2),連上服務器,服務器將向其返回自身的發送端口(稱為port2),以及client1的(經過轉換之后的)IP和端口。
4:然后服務器再發client1返回client2(經過轉換之后的)IP和端口,然后斷開與這兩個客戶端的連接(此時,服務器的工作已經全部完成了)
5:client2嘗試連接client1,這次肯定會失敗,但它會在路由器上留下記錄,以幫忙client1成功穿透,連接上自己,然后設置port2端口為可重用端口,並監聽端口port2。
6:client1嘗試去連接client2,前幾次可能會失敗,因為穿透還沒成功,如果連接10次都失敗,就證明穿透失敗了(可能是硬件不支持),如果成功,則每秒向client2發送一次hello, world
7:如果client1不斷出現send message: Hello, world,client2不斷出現recv message: Hello, world,則證明實驗成功了,否則就是失敗了。

3. 聲明

1:這個程序只是一個DEMO,所以肯定有很多不完善的地方,請大家多多見諒。
2:在很多網絡中,這個程序並不能打洞成功,可能是硬件的問題(畢竟不是每種路由器都支持穿透),也可能是我程序的問題,如果大家有意見或建議,歡迎留言或給我發郵件(郵箱是:aa1080711@163.com)

4. 上代碼:

服務器端:

Cpp代碼   收藏代碼
  1. /* 
  2. 文件:server.c 
  3. PS:第一個連接上服務器的客戶端,稱為client1,第二個連接上服務器的客戶端稱為client2 
  4. 這個服務器的功能是: 
  5. 1:對於client1,它返回"first",並在client2連接上之后,將client2經過轉換后的IP和port發給client1; 
  6. 2:對於client2,它返回client1經過轉換后的IP和port和自身的port,並在隨后斷開與他們的連接。 
  7. */  
  8.   
  9. #include <stdio.h>  
  10. #include <unistd.h>  
  11. #include <signal.h>  
  12. #include <sys/socket.h>  
  13. #include <fcntl.h>  
  14. #include <stdlib.h>  
  15. #include <errno.h>  
  16. #include <string.h>  
  17. #include <arpa/inet.h>  
  18.   
  19. #define MAXLINE 128  
  20. #define SERV_PORT 8877  
  21.   
  22. //發生了致命錯誤,退出程序  
  23. void error_quit(const char *str)      
  24. {      
  25.     fprintf(stderr, "%s", str);    
  26.     //如果設置了錯誤號,就輸入出錯原因  
  27.     if( errno != 0 )  
  28.         fprintf(stderr, " : %s", strerror(errno));  
  29.     printf("\n");  
  30.     exit(1);      
  31. }     
  32.   
  33. int main(void)        
  34. {            
  35.     int i, res, cur_port;   
  36.     int connfd, firstfd, listenfd;     
  37.     int count = 0;  
  38.     char str_ip[MAXLINE];    //緩存IP地址  
  39.     char cur_inf[MAXLINE];   //當前的連接信息[IP+port]  
  40.     char first_inf[MAXLINE];    //第一個鏈接的信息[IP+port]  
  41.     char buffer[MAXLINE];    //臨時發送緩沖區  
  42.     socklen_t clilen;        
  43.     struct sockaddr_in cliaddr;        
  44.     struct sockaddr_in servaddr;  
  45.   
  46.     //創建用於監聽TCP協議套接字          
  47.     listenfd = socket(AF_INET, SOCK_STREAM, 0);        
  48.     memset(&servaddr, 0, sizeof(servaddr));        
  49.     servaddr.sin_family = AF_INET;        
  50.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);        
  51.     servaddr.sin_port = htons(SERV_PORT);        
  52.   
  53.     //把socket和socket地址結構聯系起來         
  54.     res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));      
  55.     if( -1 == res )  
  56.         error_quit("bind error");  
  57.   
  58.     //開始監聽端口         
  59.     res = listen(listenfd, INADDR_ANY);      
  60.     if( -1 == res )  
  61.         error_quit("listen error");  
  62.   
  63.     while( 1 )  
  64.     {  
  65.         //接收來自客戶端的連接  
  66.         connfd = accept(listenfd,(struct sockaddr *)&cliaddr, &clilen);    
  67.         if( -1 == connfd )  
  68.             error_quit("accept error");  
  69.         inet_ntop(AF_INET, (void*)&cliaddr.sin_addr, str_ip, sizeof(str_ip));  
  70.   
  71.         count++;  
  72.         //對於第一個鏈接,將其的IP+port存儲到first_inf中,  
  73.         //並和它建立長鏈接,然后向它發送字符串'first',  
  74.         if( count == 1 )  
  75.         {  
  76.             firstfd = connfd;  
  77.             cur_port = ntohs(cliaddr.sin_port);  
  78.             snprintf(first_inf, MAXLINE, "%s %d", str_ip, cur_port);     
  79.             strcpy(cur_inf, "first\n");  
  80.             write(connfd, cur_inf, strlen(cur_inf)+1);  
  81.         }  
  82.         //對於第二個鏈接,將其的IP+port發送給第一個鏈接,  
  83.         //將第一個鏈接的信息和他自身的port返回給它自己,  
  84.         //然后斷開兩個鏈接,並重置計數器  
  85.         else if( count == 2 )  
  86.         {  
  87.             cur_port = ntohs(cliaddr.sin_port);  
  88.             snprintf(cur_inf, MAXLINE, "%s %d\n", str_ip, cur_port);  
  89.             snprintf(buffer, MAXLINE, "%s %d\n", first_inf, cur_port);  
  90.             write(connfd, buffer, strlen(buffer)+1);  
  91.             write(firstfd, cur_inf, strlen(cur_inf)+1);   
  92.             close(connfd);  
  93.             close(firstfd);  
  94.             count = 0;  
  95.         }  
  96.         //如果程序運行到這里,那肯定是出錯了  
  97.         else  
  98.             error_quit("Bad required");  
  99.     }  
  100.     return 0;  
  101. }  

 客戶端:

Cpp代碼   收藏代碼
  1. /* 
  2. 文件:client.c 
  3. PS:第一個連接上服務器的客戶端,稱為client1,第二個連接上服務器的客戶端稱為client2 
  4. 這個程序的功能是:先連接上服務器,根據服務器的返回決定它是client1還是client2, 
  5. 若是client1,它就從服務器上得到client2的IP和Port,連接上client2, 
  6. 若是client2,它就從服務器上得到client1的IP和Port和自身經轉換后的port, 
  7. 在嘗試連接了一下client1后(這個操作會失敗),然后根據服務器返回的port進行監聽。 
  8. 這樣以后,就能在兩個客戶端之間進行點對點通信了。 
  9. */  
  10.   
  11. #include <stdio.h>  
  12. #include <unistd.h>  
  13. #include <signal.h>  
  14. #include <sys/socket.h>  
  15. #include <fcntl.h>  
  16. #include <stdlib.h>  
  17. #include <errno.h>  
  18. #include <string.h>  
  19. #include <arpa/inet.h>  
  20.   
  21. #define MAXLINE 128  
  22. #define SERV_PORT 8877  
  23.   
  24. typedef struct  
  25. {  
  26.     char ip[32];  
  27.     int port;  
  28. }server;  
  29.   
  30. //發生了致命錯誤,退出程序  
  31. void error_quit(const char *str)      
  32. {      
  33.     fprintf(stderr, "%s", str);   
  34.     //如果設置了錯誤號,就輸入出錯原因  
  35.     if( errno != 0 )  
  36.         fprintf(stderr, " : %s", strerror(errno));  
  37.     printf("\n");  
  38.     exit(1);      
  39. }     
  40.   
  41. int main(int argc, char **argv)       
  42. {            
  43.     int i, res, port;  
  44.     int connfd, sockfd, listenfd;   
  45.     unsigned int value = 1;  
  46.     char buffer[MAXLINE];        
  47.     socklen_t clilen;          
  48.     struct sockaddr_in servaddr, sockaddr, connaddr;    
  49.     server other;  
  50.   
  51.     if( argc != 2 )  
  52.         error_quit("Using: ./client <IP Address>");  
  53.   
  54.     //創建用於鏈接(主服務器)的套接字          
  55.     sockfd = socket(AF_INET, SOCK_STREAM, 0);   
  56.     memset(&sockaddr, 0, sizeof(sockaddr));        
  57.     sockaddr.sin_family = AF_INET;        
  58.     sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);        
  59.     sockaddr.sin_port = htons(SERV_PORT);        
  60.     inet_pton(AF_INET, argv[1], &sockaddr.sin_addr);  
  61.     //設置端口可以被重用  
  62.     setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));  
  63.   
  64.     //連接主服務器  
  65.     res = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));   
  66.     if( res < 0 )  
  67.         error_quit("connect error");  
  68.   
  69.     //從主服務器中讀取出信息  
  70.     res = read(sockfd, buffer, MAXLINE);  
  71.     if( res < 0 )  
  72.         error_quit("read error");  
  73.     printf("Get: %s", buffer);  
  74.   
  75.     //若服務器返回的是first,則證明是第一個客戶端  
  76.     if( 'f' == buffer[0] )  
  77.     {  
  78.         //從服務器中讀取第二個客戶端的IP+port  
  79.         res = read(sockfd, buffer, MAXLINE);  
  80.         sscanf(buffer, "%s %d", other.ip, &other.port);  
  81.         printf("ff: %s %d\n", other.ip, other.port);  
  82.   
  83.         //創建用於的套接字          
  84.         connfd = socket(AF_INET, SOCK_STREAM, 0);   
  85.         memset(&connaddr, 0, sizeof(connaddr));        
  86.         connaddr.sin_family = AF_INET;        
  87.         connaddr.sin_addr.s_addr = htonl(INADDR_ANY);        
  88.         connaddr.sin_port = htons(other.port);      
  89.         inet_pton(AF_INET, other.ip, &connaddr.sin_addr);  
  90.   
  91.         //嘗試去連接第二個客戶端,前幾次可能會失敗,因為穿透還沒成功,  
  92.         //如果連接10次都失敗,就證明穿透失敗了(可能是硬件不支持)  
  93.         while( 1 )  
  94.         {  
  95.             static int j = 1;  
  96.             res = connect(connfd, (struct sockaddr *)&connaddr, sizeof(connaddr));   
  97.             if( res == -1 )  
  98.             {  
  99.                 if( j >= 10 )  
  100.                     error_quit("can't connect to the other client\n");  
  101.                 printf("connect error, try again. %d\n", j++);  
  102.                 sleep(1);  
  103.             }  
  104.             else   
  105.                 break;  
  106.         }  
  107.   
  108.         strcpy(buffer, "Hello, world\n");  
  109.         //連接成功后,每隔一秒鍾向對方(客戶端2)發送一句hello, world  
  110.         while( 1 )  
  111.         {  
  112.             res = write(connfd, buffer, strlen(buffer)+1);  
  113.             if( res <= 0 )  
  114.                 error_quit("write error");  
  115.             printf("send message: %s", buffer);  
  116.             sleep(1);  
  117.         }  
  118.     }  
  119.     //第二個客戶端的行為  
  120.     else  
  121.     {  
  122.         //從主服務器返回的信息中取出客戶端1的IP+port和自己公網映射后的port  
  123.         sscanf(buffer, "%s %d %d", other.ip, &other.port, &port);  
  124.   
  125.         //創建用於TCP協議的套接字          
  126.         sockfd = socket(AF_INET, SOCK_STREAM, 0);   
  127.         memset(&connaddr, 0, sizeof(connaddr));        
  128.         connaddr.sin_family = AF_INET;        
  129.         connaddr.sin_addr.s_addr = htonl(INADDR_ANY);        
  130.         connaddr.sin_port = htons(other.port);        
  131.         inet_pton(AF_INET, other.ip, &connaddr.sin_addr);  
  132.         //設置端口重用  
  133.         setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));  
  134.   
  135.         //嘗試連接客戶端1,肯定會失敗,但它會在路由器上留下記錄,  
  136.         //以幫忙客戶端1成功穿透,連接上自己   
  137.         res = connect(sockfd, (struct sockaddr *)&connaddr, sizeof(connaddr));   
  138.         if( res < 0 )  
  139.             printf("connect error\n");  
  140.   
  141.         //創建用於監聽的套接字          
  142.         listenfd = socket(AF_INET, SOCK_STREAM, 0);   
  143.         memset(&servaddr, 0, sizeof(servaddr));        
  144.         servaddr.sin_family = AF_INET;        
  145.         servaddr.sin_addr.s_addr = htonl(INADDR_ANY);        
  146.         servaddr.sin_port = htons(port);  
  147.         //設置端口重用  
  148.         setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));  
  149.   
  150.         //把socket和socket地址結構聯系起來   
  151.         res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));      
  152.         if( -1 == res )  
  153.             error_quit("bind error");  
  154.   
  155.         //開始監聽端口         
  156.         res = listen(listenfd, INADDR_ANY);      
  157.         if( -1 == res )  
  158.             error_quit("listen error");  
  159.   
  160.         while( 1 )  
  161.         {  
  162.             //接收來自客戶端1的連接  
  163.             connfd = accept(listenfd,(struct sockaddr *)&sockaddr, &clilen);    
  164.             if( -1 == connfd )  
  165.                 error_quit("accept error");  
  166.   
  167.             while( 1 )  
  168.             {  
  169.                 //循環讀取來自於客戶端1的信息  
  170.                 res = read(connfd, buffer, MAXLINE);  
  171.                 if( res <= 0 )  
  172.                     error_quit("read error");  
  173.                 printf("recv message: %s", buffer);  
  174.             }  
  175.             close(connfd);  
  176.         }  
  177.     }  
  178.   
  179.     return 0;  
  180. }  

 

5. 運行示例:

(第一個終端)
qch@qch ~/program/tcode $ gcc server.c -o server
qch@qch ~/program/tcode $ ./server &
[1] 4688
qch@qch ~/program/tcode $ gcc client.c -o client
qch@qch ~/program/tcode $ ./client localhost
Get: first
ff: 127.0.0.1 38052
send message: Hello, world
send message: Hello, world
send message: Hello, world
.................


第二個終端:
qch@qch ~/program/tcode $ ./client localhost
Get: 127.0.0.1 38073 38074
connect error
recv message: Hello, world
recv message: Hello, world
recv message: Hello, world
..................


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM