udp隧道(STUN)

归档 Linux 网络编程 隧道

2013-11-15 00:00 AM

前两天稍微研究了下通过UDP建隧道穿过NAT路由器, 自己写了个实现, 中间因为考试等事宜耽误了几天, 今天终于能用了.


说起udp隧道, 不得不说下nat. nat现在在市面上再常见不过, 市面上几乎所有的家用路由器, 网吧, 企业, 学校, 手机wifi热点, 无一不使用nat. nat是当前共享上网的主要方式.

不过nat的弊端也是明显的, nat一般只有少数个外网端口, 但却为多数个内网主机提供网络服务. 大家想想, 家里宽带是不是只有一个口接WAN, 其余四个都接LAN. 这就造成了内网主机无法直接与外网主机进行点对点通讯. 首先, 外部主机对内部主机是无知的, 无法对其发出tcp连接, 这还好说, 可以让内部主机主动对外部主机发出连接, 但假如两天主机都在nat内, 就很苦恼了. udp隧道是通过一个在公共互联网的主机作为服务器, 扮演一个中介的角色, 让两台处于nat内部的主机接上线.

首先先要说一下nat的种类, 在stun的标准文档(RFC3489)中定义了两大种nat类型: 1.对称型nat, 2.圆锥形nat, 对于第一种对称性nat, stun是无能为力的. 圆锥形又分三种, 完全圆锥型NAT、受限圆锥型NAT和端口受限圆锥型NAT, 这三种stun都可以穿过.

对称型nat.

如下图所示:

image_1bl02vap6ei5s7s112g1mbk14lc9.png-27.2kB

对称型nat在转发同一主机同一端口号(ip和port都相同)发出的的数据时, 如果对端服务器不是同一个, 则选用不同的端口号进行转发, 这将导致无法进行nat穿越.
如图所示

  • 在客户端: nat内主机192.168.1.100通过同一端口8000分别向5.6.7.8:56214和5.6.7.9:12540发出数据包. 地址对: { 192.168.1.100:8000 – 5.6.7.8:56214 } { 192.168.1.100:8000 – 5.6.7.9:12540 }
  • 在路由器: nat路由器1.2.3.4分别选用了两个不同的端口转发数据包(1.2.3.4:34594和1.2.3.4:34595 ), 地址对: { 1.2.3.4:34594 – 5.6.7.8:56214 } { 1.2.3.4:34595 – 5.6.7.9:12540 }
  • 在服务器端: 两个服务器分别接收到不同的端口发来的数据
圆锥形nat.

如下如所示:

image_1bl03203113feqll1ha0nmr1mitm.png-27.4kB

圆锥形nat在转发同一主机同一端口号(ip和port都相同)发出的的数据时, 选用同一端口号进行转发. 所以可以利用这一点进行nat穿越
如图所示:

  • 在客户端: nat内主机192.168.1.100通过同一端口8000分别向5.6.7.8:56214和5.6.7.9:12540发出数据包. 地址对: { 192.168.1.100:8000 – 5.6.7.8:56214 } { 192.168.1.100:8000 – 5.6.7.9:12540 }
  • 在路由器: nat路由器1.2.3.4选用相同的的端口转发数据包(1.2.3.4:34594 ), 地址对: { 1.2.3.4:34594 – 5.6.7.8:56214 } { 1.2.3.4:34594 – 5.6.7.9:12540 }
  • 在服务器端: 两个服务器分别接收到相同的端口发来的数据

而圆锥形nat又分三种:

  • 完全圆锥型NAT : 同一主机同一端口号的数据都映射到nat路由器的同一端口, 任意外部主机向1.2.3.4:34594发送数据192.168.1.100:8000都能收到.
  • 受限圆锥型NAT :同一主机同一端口号的数据都映射到nat路由器的同一端口, 但必须内部主机192.168.1.100用8000向外部主机1.2.3.4发送一个数据报之后才能接受外部主机发来的数据
  • 端口受限圆锥型NAT : 同一主机同一端口号的数据都映射到nat路由器的同一端口, 但必须内部主机192.168.1.100用8000向外部主机1.2.3.4的34594端口发送一个数据报之后才能接受外部主机发来的数据

nat原理明白了, 接下来就可以设计如何nat穿越了.

image_1bl033glufm211471ra5f481eh413.png-38.3kB

分三步

  • 内部主机分别向处于外网的一台公共STUN服务器发送login信息, 报告自己的ip和端口 (图中的黑色)
  • 当服务器收到两台主机发来login后分别向两台主机发送对端的ip和端口 (图中的蓝色)
  • 两台内部主机用原先想服务器发login信息的端口向对端ip和端口通讯 (图中的粉色)
    原理明白了很简单

下面是我自己的一个实现

服务端:

  1. #include <arpa/inet.h>
  2. #include <netinet/in.h>
  3. #include <sys/socket.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <errno.h>
  8. #include <string.h>
  9. typedef struct sockaddr SA;
  10. const unsigned short PORT = 38000;
  11. const unsigned char LOGIN = 0x01;
  12. const unsigned char PEER = 0x02;
  13. void wait_login(int sock, struct sockaddr_in* remoteaddr, socklen_t* paddrlen)
  14. {
  15. int index = 0;
  16. while (index < 2)
  17. {
  18. int n;
  19. char buf[16];
  20. if ((n = recvfrom(sock, buf, 16, 0, (SA*)&remoteaddr[index], paddrlen)) < 0)
  21. {
  22. perror("recvfrom");
  23. exit(errno);
  24. }
  25. if (n != 1 || buf[0] != LOGIN)
  26. continue; /* ignore it */
  27. ++index;
  28. }
  29. }
  30. void send_peer(int sock, struct sockaddr_in* remoteaddr, int from, int to)
  31. {
  32. char ip[INET_ADDRSTRLEN];
  33. char buf[1024];
  34. buf[0] = PEER;
  35. if (inet_ntop(AF_INET, &remoteaddr[from].sin_addr, ip, INET_ADDRSTRLEN) == NULL)
  36. {
  37. perror("inet_ntop");
  38. exit(errno);
  39. }
  40. snprintf(buf + 1, 1023, "%s:%d", ip, ntohs(remoteaddr[from].sin_port));
  41. printf("sending message: %s\n", buf + 1);
  42. if (sendto(sock, buf, strlen(buf + 1) + 1, 0, (SA*)&remoteaddr[to], sizeof(remoteaddr[to])) < 0)
  43. {
  44. perror("sendto");
  45. exit(errno);
  46. }
  47. }
  48. int main()
  49. {
  50. int sock;
  51. struct sockaddr_in localaddr;
  52. struct sockaddr_in remoteaddr[2];
  53. socklen_t addrlen = sizeof(remoteaddr[0]);
  54. sock = socket(AF_INET, SOCK_DGRAM, 0);
  55. if (sock < 0)
  56. {
  57. perror("socket");
  58. exit(errno);
  59. }
  60. bzero(&localaddr, sizeof(localaddr));
  61. localaddr.sin_family = AF_INET;
  62. localaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  63. localaddr.sin_port = htons(PORT);
  64. if (bind(sock, (SA*)&localaddr, sizeof(localaddr)) < 0)
  65. {
  66. perror("bind");
  67. exit(errno);
  68. }
  69. while (1)
  70. {
  71. wait_login(sock, remoteaddr, &addrlen);
  72. send_peer(sock, remoteaddr, 0, 1);
  73. send_peer(sock, remoteaddr, 1, 0);
  74. }
  75. return 0;
  76. }

客户端:

  1. #include <netdb.h>
  2. #include <arpa/inet.h>
  3. #include <netinet/in.h>
  4. #include <sys/socket.h>
  5. #include <unistd.h>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #define max(a, b) ((a) > (b)?(a):(b))
  11. const unsigned short PORT = 38000;
  12. const unsigned char LOGIN = 0x01;
  13. const unsigned char PEER = 0x02;
  14. const unsigned char HELLO = 0x03;
  15. const unsigned char MSG = 0x04;
  16. typedef struct sockaddr SA;
  17. int init_svraddr(const char* server, struct sockaddr_in* svrAddr, socklen_t len)
  18. {
  19. struct hostent* ent = gethostbyname(server);
  20. if (ent == NULL)
  21. {
  22. fprintf(stderr, "gethostbyname:%s\n", hstrerror(h_errno));
  23. return -1;
  24. }
  25. bzero(svrAddr, len);
  26. svrAddr->sin_family = AF_INET;
  27. svrAddr->sin_port = htons(PORT);
  28. svrAddr->sin_addr = *(in_addr*)*ent->h_addr_list;
  29. return 0;
  30. }
  31. int login(int sock, struct sockaddr_in* svrAddr, socklen_t len)
  32. {
  33. if (connect(sock, (SA*)svrAddr, len) < 0)
  34. return -1;
  35. if (write(sock, &LOGIN, sizeof(LOGIN)) < 0)
  36. return -1;
  37. return 0;
  38. }
  39. int get_peer_info(int sock, char* addrport, int len)
  40. {
  41. int i, j;
  42. if ((i = read(sock, addrport, len)) < 0)
  43. return -1;
  44. if (*addrport != PEER)
  45. return -1;
  46. for (j = 0; j < i - 1; ++j)
  47. addrport[j] = addrport[j + 1];
  48. addrport[j] = '\0';
  49. return 0;
  50. }
  51. int say_hello_peer(int sock, struct sockaddr* addr, socklen_t len)
  52. {
  53. if (connect(sock, addr, len) < 0)
  54. return -1;
  55. if (write(sock, &HELLO, sizeof(HELLO)) < 0)
  56. return -1;
  57. return 0;
  58. }
  59. int ptoap(char* str, struct sockaddr_in* addr, socklen_t len)
  60. {
  61. char* p;
  62. bzero(addr, len);
  63. addr->sin_family = AF_INET;
  64. p = strchr(str, ':');
  65. *p = '\0';
  66. if (inet_pton(AF_INET, str, &addr->sin_addr) < 0)
  67. return -1;
  68. addr->sin_port = htons(atoi(p + 1));
  69. return 0;
  70. }
  71. const char* SERVER = "198.56.248.214";
  72. int main()
  73. {
  74. int sock;
  75. char addrport[256];
  76. struct sockaddr_in svrAddr;
  77. struct sockaddr_in peeraddr;
  78. sock = socket(AF_INET, SOCK_DGRAM, 0);
  79. if (sock < 0)
  80. {
  81. perror("socket");
  82. exit(errno);
  83. }
  84. if (init_svraddr(SERVER, &svrAddr, sizeof(svrAddr)) == -1)
  85. exit(-1);
  86. printf("Sending login\n");
  87. if (login(sock, &svrAddr, sizeof(svrAddr)) == -1)
  88. {
  89. perror("login");
  90. exit(errno);
  91. }
  92. printf("Getting peer info\n");
  93. if (get_peer_info(sock, addrport, 256) == -1)
  94. {
  95. perror("get_peer_info");
  96. exit(errno);
  97. }
  98. printf("Peer info: %s\n", addrport);
  99. if (ptoap(addrport, &peeraddr, sizeof(peeraddr)) == -1)
  100. {
  101. perror("ptoap");
  102. exit(errno);
  103. }
  104. if (say_hello_peer(sock, (SA*)&peeraddr, sizeof(peeraddr)) == -1)
  105. {
  106. perror("say_hello_peer");
  107. exit(errno);
  108. }
  109. while (true)
  110. {
  111. fd_set sockstdin;
  112. int maxFd;
  113. int n;
  114. char buf[4096];
  115. FD_ZERO(&sockstdin);
  116. FD_SET(STDIN_FILENO, &sockstdin);
  117. FD_SET(sock, &sockstdin);
  118. maxFd = max(sock, STDIN_FILENO);
  119. if (select(maxFd + 1, &sockstdin, NULL, NULL, NULL) < 0)
  120. {
  121. perror("select");
  122. exit(errno);
  123. }
  124. if (FD_ISSET(STDIN_FILENO, &sockstdin))
  125. {
  126. if ((n = read(STDIN_FILENO, buf, 4096)) < 0)
  127. {
  128. perror("read");
  129. exit(errno);
  130. }
  131. else if (n == 0)
  132. {
  133. exit(0); /* exit normally */
  134. }
  135. if (write(sock, buf, n) < 0)
  136. {
  137. perror("sendto");
  138. exit(errno);
  139. }
  140. }
  141. if (FD_ISSET(sock, &sockstdin))
  142. {
  143. if ((n = read(sock, buf, 4096)) < 0)
  144. {
  145. perror("read");
  146. exit(errno);
  147. }
  148. if (write(STDOUT_FILENO, buf, n) < 0)
  149. {
  150. perror("write");
  151. exit(errno);
  152. }
  153. }
  154. }
  155. return 0;
  156. }

发表于 2013-11-15 00:00 AM,最后更新于 2018-11-26 18:39:33 PM。

本文使用 署名 - 非商业性使用 - 相同方式共享 4.0 国际 协议


评论加载中...

首页