基於TCP的NAT子網穿透實驗

      不得不說,在國內IP緊缺的現狀下,NAT發揮了無比巨大的做用:它以把IP和端口從新分配的方式,知足了廣大人民羣衆上網的強烈需求。可是對於我的服務器以及在內網中基於網絡的嵌入式設備,倒是個比較尷尬的事情:由於它把端口和IP進行了從新分配,外網客戶端訪問的時候很難知道server端的IP和監聽端口,尤爲是監聽端口。這個時候,子網穿透技術就應運而生了。
      這兩天看了一些簡單的子網穿透的基本原理,大多數都是UDP的。可是看完後以爲TCP實現起來更爲簡單,所以簡單的寫了一個測試程序實驗了一下,結果還真行。下面是實現原理:
      首先是搭建了一下網絡模型,一個是在子網中的須要穿透的Server A,一個是在公網上的輔助穿透的Server S,另外一個是須要鏈接Server A的ClientB,因爲是客戶端,在不在子網倒無所謂了。下面是簡圖:服務器

      其次簡單介紹一下實現過程:首先是Server S創建一個監聽socket,用於輔助打洞。而後在Server A上創建一個socket去鏈接ServerS的監聽端口,而後這個時候NAT A就會給此鏈接分配一個端口和IP,而Server S是知道此IP和端口的,而後Server S將此信息記錄下來。 接着Server A要在剛纔那個鏈接ServerS上的端口上再創建一個監聽socket。而後Client B先去和Server S創建聯繫,獲取Server A的端口和IP信息,而後就能夠去鏈接ServerA了。
      其中有兩個地方要注意,其中ServerA上的兩個socket要設置SO_REUSEADDR的屬性;其次是建議ServerA在鏈接ServerS前也先綁定一下端口。
      最後是上代碼,固然其中我爲了省事兒,沒有搭建NAT B,也沒有去實現框圖中的Step2,而是採起Server S打印的方式,而後配合手動修改ClientB的鏈接端口實現的……而後個人Server S的IP是10.10.10.66;而後client B的IP是10.10.10.88;NAT A用的是一個TP-LINK的家用路由器,WAN口的IP是10.10.10.77,LAN口是192.168.1.1;Server A的IP是192.168.1.99,網關是192.168.1.1。
      首先是ServerS上的代碼,爲了省事兒,請忽視句柄泄露等問題……網絡

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <time.h>

int main()
{
        int fd_socket;
        int fd_connect;
        struct sockaddr_in serv_addr;
        struct sockaddr_in client_addr;
        socklen_t cli_len;

        fd_socket= socket(AF_INET, SOCK_STREAM, 0);
        if(fd_socket < 0)
        {
                printf("Init socket failed!\n");
                return -1;
        }
        int iOption_value = 1;
        int iLength = sizeof(int);
        if(setsockopt(fd_socket,SOL_SOCKET,SO_REUSEADDR,&iOption_value, iLength)<0)
        {
                printf("setsockopt error\n");
                return -1;
        }
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(1234);
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        if(bind(fd_socket, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < 0)
        {
                perror("bind");
                printf("Bind failed!\n");
                return -2;
        }
        if(listen(fd_socket, 4) < 0)
        {
                printf("Listen failed!\n");
        }
        printf("start to accept!\n");
        while(1)
        {
                fd_connect = accept(fd_socket, (struct sockaddr *)&client_addr, &cli_len);
                printf("the ServerA ip is %s, port is %d\n",inet_ntoa(client_addr.sin_addr),htons(client_addr.sin_port));
                usleep(0);
        }
        printf("over\n");
        
        return 0;
}

     而後是ClientB的代碼,其中IP(10.10.10.77)和端口(1043)是在ServerS打印以後才寫進去進行編譯運行的。socket

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <time.h>

int main()
{
        int fd_socket;
        struct sockaddr_in serv_addr;
        fd_socket= socket(AF_INET, SOCK_STREAM, 0);
        if(fd_socket < 0)
        {
                printf("Init socket failed!\n");
                return -1;
        }

        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(1043);
        serv_addr.sin_addr.s_addr = inet_addr("10.10.10.77");
        if(connect(fd_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        {
                printf("connect error\n");
                return -1;
        }
        printf("connect success\n");
        sleep(20);        
        return 0;
}

     最後是ServerA的代碼:測試

    

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <time.h>

int main()
{
        int fd_socket;
        int fd_connect;
        int fd_server;
        struct sockaddr_in serv_addr;
        struct sockaddr_in client_addr;
        fd_socket= socket(AF_INET, SOCK_STREAM, 0);
        if(fd_socket < 0)
        {
                printf("Init socket failed!\n");
                return -1;
        }
        int iOption_value = 1;
        int iLength = sizeof(int);
        if(setsockopt(fd_socket,SOL_SOCKET,SO_REUSEADDR,&iOption_value, iLength)<0)
        {
                printf("setsockopt error\n");
                return -1;
        }
        memset(&client_addr, 0, sizeof(client_addr));
        client_addr.sin_family = AF_INET;
        client_addr.sin_port = htons(6666);
        client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        if(bind(fd_socket,(struct sockaddr *)&client_addr, sizeof(client_addr)) < 0)
        {
                perror("bind");
                return -1;
        }
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(1234);
        serv_addr.sin_addr.s_addr = inet_addr("10.10.10.66");
        if(connect(fd_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        {
                printf("connect error\n");
                return -1;
        }
        memset(&client_addr, 0, sizeof(client_addr));
        int iAddrLen = sizeof(client_addr);
        if(getsockname(fd_socket, (struct sockaddr *)&client_addr, &iAddrLen) < 0)
        {
                printf("getsockname\n");
                return -1;
        }
        printf("the port is %d\n", htons(client_addr.sin_port));
        fd_server = socket(AF_INET, SOCK_STREAM, 0);
        if(fd_server < 0)
        {
                printf("Init socket failed!\n");
                return -1;
        }
        if(setsockopt(fd_server,SOL_SOCKET,SO_REUSEADDR,&iOption_value, iLength)<0)
        {
                printf("setsockopt error\n");
                return -1;
        }
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = client_addr.sin_port;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        if(bind(fd_server,(struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        {
                perror("bind");
                return -1;
        }
        if(listen(fd_server, 12) < 0)
        {
                perror("listen");        
        }
        printf("listen success\n");
        sleep(20);
        
        return 0;
}

      根據打印消息以及抓包數據分析來看,clientB是能夠和ServerA創建鏈接的。
      雖然這個實驗是成功了,可是當我把這種方式應用到實際項目中卻出現了兩個問題:
      一是以這種模式創建的通信模式,帶寬很不穩定,有時候數據會阻塞好久才能發過去,應該是中間有丟包。
      二是一旦ServerA斷掉ClientB的鏈接,ClientB就沒法再與ServerA創建鏈接了。
      問題二我以爲還比較好理解,由於通常NAT創建TCP的端口映射是根據SYS和FIN包爲起始終止標識來創建的,當ServerA向ClientB發FIN的時候,就會被NAT檢測到,而後關掉這種端口映射關係。
     可是問題一我就想不明白了,由於當我把路由器的DMZ打開後,數據是很流暢的,能夠證實物理線路是能保證足夠帶寬的,只有在這種應用下才會出現帶寬降低問題,但願有哪位高手能夠幫忙解答一下……spa

相關文章
相關標籤/搜索