最近研究SIP(freeswitch), 看到有用STUN做P2P,但是没有成功。
所以就做了个小实验尝试使用STUN打洞。
第一阶段 使用STUN打洞
实验过程如下:
1、在公网阿里云上搭建stun服务器
# git clone https://github.com/coturn/coturn
或者
# git clone https://github.com/jselbie/stunserver
这里以cotrun为例
# cd coturn
# ./configure
# make
# make install
修改stun server的配置文件
# vim /usr/local/etc/turnserver.conf
启动stun server
# turnserver &
2、客户端发起stun binding request,获得自己的公网IP和Port
3、在客户端上使用udp发包工具,向自己的公网IP和Port上发送UDP测试数据包
udp_send.c 源代码
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define BUFF_LEN 512
void udp_msg_sender(int fd, struct sockaddr* dst)
{
socklen_t len;
struct sockaddr_in src;
char buf[BUFF_LEN] = "TEST UDP MSG!\n";
len = sizeof(*dst);
printf("client:%s\n",buf); //打印自己发送的信息
sendto(fd, buf, BUFF_LEN, 0, dst, len);
}
int main(int argc, char* argv[])
{
int client_fd;
struct sockaddr_in ser_addr;
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(client_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = inet_addr(argv[1]);
ser_addr.sin_port = htons(atoi(argv[2]));
udp_msg_sender(client_fd, (struct sockaddr*)&ser_addr);
close(client_fd);
return 0;
}
Makefile文件内容
udp_send:
gcc udp_send.c -o udp_send
执行发包命令
# udp_send 1.2.3.4 3333
4、用wireshark抓包工具抓包发现只有发出去的一个包,没有回来的包,而且抓到了对应的Destination Unreachable 的ICMP包。
第二阶段 确定自己的NAT是否是对称型NAT
使用上述的udp_send测试程序分别向两台不同公网IP的阿里云主机发包,
在阿里云公网服务器上抓包,发现数据包的公网源地址和源端口都是一致的,
说明自己的NAT是锥形NAT不是对称型NAT。
按道理是能够打洞成功的,但第一阶段的确是数据包没穿透打的洞。
怀疑是洞的生存时间问题导致的。
第三阶段 测试NAT打洞的有效时间
1、在公网阿里云主机上使用如下服务端udp接收程序
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <time.h>
#define BUFF_LEN 512
void udp_msg_sender(int fd, struct sockaddr* dst)
{
socklen_t len;
char buf[BUFF_LEN] = "REPLY TEST UDP MSG!\n";
len = sizeof(*dst);
printf("reply message: %s\n",buf); //打印自己发送的信息
sendto(fd, buf, BUFF_LEN, 0, dst, len);
}
int main(int argc, char* argv[])
{
time_t now;
int client_fd;
struct sockaddr_in ser_addr;
struct sockaddr_in self_addr;
struct sockaddr_in peer_addr;
socklen_t peer_addr_sz;
char recv_buff[1024];
int recv_len;
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(client_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
self_addr.sin_family = AF_INET;
self_addr.sin_port = htons(1234);
self_addr.sin_addr.s_addr = INADDR_ANY;
bind(client_fd, (struct sockaddr *)&self_addr, sizeof(self_addr));
while(1)
{
recv_len = recvfrom(client_fd, recv_buff, 1024, 0,(struct sockaddr*) &peer_addr, &peer_addr_sz);
if(recv_len)
{
time(&now);
printf("Current Time: %s", ctime(&now));
printf("peer_addr: [ip] %s, [port] %d, [recv_msg] %s \n", inet_ntoa(peer_addr.sin_addr), peer_addr.sin_port, recv_buff);
udp_msg_sender(client_fd, (struct sockaddr*)&peer_addr);
}
}
close(client_fd);
return 0;
}
编译并运行服务端UDP程序
# gcc udp_server.c -o udp_server
# ./udp_server
2、在自己局域网使用如下客户端udp发包程序
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define BUFF_LEN 512
void udp_msg_sender(int fd, struct sockaddr* dst, int ttl)
{
socklen_t len;
char buf[BUFF_LEN];
sprintf(buf, "TEST UDP MSG! TTL:%d.\n", ttl);
len = sizeof(*dst);
printf("client: %s\n",buf); //打印自己发送的信息
sendto(fd, buf, BUFF_LEN, 0, dst, len);
/* sleep some seconds to observe the change of port */
sleep(ttl);
sendto(fd, buf, BUFF_LEN, 0, dst, len);
}
void usage_print(void)
{
printf("./udp_send IP Port TTL For Example: ./udp_send 1.2.3.4 1234 30\n");
return;
}
int main(int argc, char* argv[])
{
int client_fd;
struct sockaddr_in ser_addr;
struct sockaddr_in self_addr;
if(argc < 4)
{
usage_print();
return -1;
}
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(client_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
self_addr.sin_family = AF_INET;
self_addr.sin_port = htons(1234);
self_addr.sin_addr.s_addr = INADDR_ANY;
bind(client_fd, (struct sockaddr *)&self_addr, sizeof(self_addr));
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = inet_addr(argv[1]);
ser_addr.sin_port = htons(atoi(argv[2])); //注意网络序转换
udp_msg_sender(client_fd, (struct sockaddr*)&ser_addr, atoi(argv[3]));
close(client_fd);
return 0;
}
在私网内编译客户端UDP程序
# gcc udp_send.c -o udp_send
3、使用二分法测出NAT打洞的有效生存时间为30秒
1、服务端 ./udp_server 一直在侦听udp数据包
2、客户端 ./udp_send 发送测试udp数据包,根据间隔时间发送两个测试数据包
3、服务端打印客户端连接的公网IP和Port,比较两次公网IP和Port的变化
4、测试结果发现只有时间小于30秒时,客户端的公网IP和Port是没有变化的,即NAT打洞的有效时间只有30秒
客户端发包间隔30秒如下:
$ ./udp_send 1.2.3.4 1234 30
client: TEST UDP MSG! TTL:30.
服务端收包打印信息如下:
# ./udp_server
Current Time: Tue Jun 29 15:58:45 2021
peer_addr: [ip] 218.90.141.182, [port] 34133, [recv_msg] TEST UDP MSG! TTL:30.
reply message: REPLY TEST UDP MSG!
Current Time: Tue Jun 29 15:59:15 2021
peer_addr: [ip] 218.90.141.182, [port] 34133, [recv_msg] TEST UDP MSG! TTL:30.
reply message: REPLY TEST UDP MSG!
客户端发包间隔31秒如下:
./udp_send 1.2.3.4 1234 31
client: TEST UDP MSG! TTL:31.
服务端收包打印信息如下:
Current Time: Tue Jun 29 16:01:11 2021
peer_addr: [ip] 218.90.141.182, [port] 65534, [recv_msg] TEST UDP MSG! TTL:31.
reply message: REPLY TEST UDP MSG!
Current Time: Tue Jun 29 16:01:42 2021
peer_addr: [ip] 218.90.141.182, [port] 20376, [recv_msg] TEST UDP MSG! TTL:31.
reply message: REPLY TEST UDP MSG!
穿透NAT仍然失败
为了保证内网内IP和Port映射出去的时候保持不变,内网主机一直向阿里云服务器上发送间隔时间小于30秒的心跳包,此时阿里云上打印内网主机的公网地址,的确一直保持不变。
但阿里云上随机的源UDP端口给内网主机的公网地址发送测试数据包,内网主机仍然不能成功接收到;阿里云主机上以测试数据包的目的端口为源端口向内网主机的公网地方发送测试数据包,内网主机能接收到。只能怀疑我的内网处在锥形NAT之下。