我已经尝试TCP打孔一段时间了,论坛似乎对基于TCP的方法和C编程语言没有多大帮助。主要参考文献如下,
我的设置是
客户端A--NAT-A--Internet--NAT-B--Client B。
假设客户机A知道B的公共endpoint和私有endpoint,而B知道A的endpoint(我已经编写了一个服务器'S',用于在对等点之间交换endpoint信息),并且两个NAT都不对称,如果两个客户机都尝试重复连接()到对方的公共endpoint(对于上述设置),这是否足够(实现TCP穿孔)?
如果不是,到底要做些什么才能实现tcp打孔呢?
我在每个客户端上都有两个线程,一个反复对其他客户端进行connect调用,另一个监听来自其他客户端的传入连接。我已经确保两个线程中的套接字都绑定到给对等体的本地端口。另外,我看到两个NAT都保留了端口映射,即本地端口和公共端口是相同的。但是,我的程序不起作用。
是不是让我上面提到的会合服务器“S”在打孔或创建允许SYN请求通过的NAT映射中发挥作用。若有,应如何处理?
后面附上了代码的相关部分。
connect_with_peer()是入口点,服务器“%s”提供了对等方的公共ip:port元组,该元组连同绑定到的本地端口一起提供给此函数。这个函数生成一个线程(accept_handler()),它也绑定到本地端口,并侦听来自对等端口的传入连接。如果connect()[主线程]或accept()[子线程]成功,则connect_with_peer()返回一个套接字。
谢谢,
丁卡
volatile int quit_connecting=0;
void *accept_handler(void *arg)
{
int i,psock,cnt=0;
int port = *((int *)arg);
ssize_t len;
int asock,opt,fdmax;
char str[BUF_SIZE];
struct sockaddr_in peer,local;
socklen_t peer_len = sizeof(peer);
fd_set master,read_fds; // master file descriptor list
struct timeval tv = {10, 0}; // 10 sec timeout
int *ret_sock = NULL;
struct linger lin;
lin.l_onoff=1;
lin.l_linger=0;
opt=1;
//Create socket
asock = socket(AF_INET , SOCK_STREAM, IPPROTO_TCP);
if (asock == -1)
{
fprintf(stderr,"Could not create socket");
goto quit_ah;
}
else if (setsockopt(asock, SOL_SOCKET, SO_LINGER, &lin,
(socklen_t) sizeof lin) < 0)
{
fprintf(stderr,"\nTCP set linger socket options failure");
goto quit_ah;
}
else if (setsockopt(asock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,
(socklen_t) sizeof opt) < 0)
{
fprintf(stderr,"\nTCP set csock options failure");
goto quit_ah;
}
local.sin_family = AF_INET; /* host byte order */
local.sin_port = htons(port); /* short, network byte order */
local.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
bzero(&(local.sin_zero), 8); /* zero the rest of the struct */
fprintf(stderr,"\naccept_handler: binding to port %d",port);
if (bind(asock, (struct sockaddr *)&local, sizeof(struct sockaddr)) == -1) {
perror("accept_handler bind error :");
goto quit_ah;
}
if (listen(asock, 1) == -1) {
perror(" accept_handler listen");
goto quit_ah;
}
memset(&peer, 0, sizeof(peer));
peer.sin_addr.s_addr = inet_addr(peer_global_address);
peer.sin_family = AF_INET;
peer.sin_port = htons( peer_global_port );
FD_ZERO(&master); // clear the master and temp sets
FD_SET(asock, &master);
fdmax = asock; // so far, it's this one
// Try accept
fprintf(stderr,"\n listen done; accepting next ... ");
while(quit_connecting == 0){
read_fds = master; // copy it
if (select(fdmax+1, &read_fds, NULL, NULL, &tv) == -1) {
perror("accept_handler select");
break;
}
// run through the existing connections looking for data to read
for(i = 0; i <= fdmax; i++) {
if (FD_ISSET(i, &read_fds)) { // we got one!!
if (i == asock) {
// handle new connections
psock = accept(asock, (struct sockaddr *)&peer, (socklen_t*)&peer_len);
if (psock == -1) {
perror("accept_handler accept");
} else {
fprintf(stderr,"\n Punch accept in thread succeeded soc=%d....",psock);
quit_connecting = 1;
ret_sock = malloc(sizeof(int));
if(ret_sock){
*ret_sock = psock;
}
}
}
}
} // end for
}
quit_ah:
if(asock>=0) {
shutdown(asock,2);
close(asock);
}
pthread_exit((void *)ret_sock);
return (NULL);
}
int connect_with_peer(char *ip, int port, int lport)
{
int retval=-1, csock=-1;
int *psock=NULL;
int attempts=0, cnt=0;
int rc=0, opt;
ssize_t len=0;
struct sockaddr_in peer, apeer;
struct sockaddr_storage from;
socklen_t peer_len = sizeof(peer);
socklen_t fromLen = sizeof(from);
char str[64];
int connected = 0;
pthread_t accept_thread;
long arg;
struct timeval tv;
fd_set myset;
int so_error;
struct linger lin;
lin.l_onoff=1;
lin.l_linger=0;
opt=1;
//Create socket
csock = socket(AF_INET , SOCK_STREAM, IPPROTO_TCP);
if (csock == -1)
{
fprintf(stderr,"Could not create socket");
return -1;
}
else if (setsockopt(csock, SOL_SOCKET, SO_LINGER, &lin,
(socklen_t) sizeof lin) < 0)
{
fprintf(stderr,"\nTCP set linger socket options failure");
}
#if 1
else if (setsockopt(csock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,
(socklen_t) sizeof opt) < 0)
{
fprintf(stderr,"\nTCP set csock options failure");
}
#endif
quit_connecting = 0;
///////////
if( pthread_create( &accept_thread , NULL , accept_handler , &lport) < 0)
{
perror("could not create thread");
return 1;
}
sleep(2); // wait for listen/accept to begin in accept_thread.
///////////
peer.sin_family = AF_INET; /* host byte order */
peer.sin_port = htons(lport); /* short, network byte order */
peer.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
bzero(&(peer.sin_zero), 8); /* zero the rest of the struct */
fprintf(stderr,"\n connect_with_peer: binding to port %d",lport);
if (bind(csock, (struct sockaddr *)&peer, sizeof(struct sockaddr)) == -1) {
perror("connect_with_peer bind error :");
goto quit_connect_with_peer;
}
// Set non-blocking
arg = fcntl(csock, F_GETFL, NULL);
arg |= O_NONBLOCK;
fcntl(csock, F_SETFL, arg);
memset(&peer, 0, sizeof(peer));
peer.sin_addr.s_addr = inet_addr(ip);
peer.sin_family = AF_INET;
peer.sin_port = htons( port );
//Connect to remote server
fprintf(stderr,"\n Attempting to connect/punch to %s; attempt=%d",ip,attempts);
rc = connect(csock , (struct sockaddr *)&peer , peer_len);
if(rc == 0){ //succeeded
fprintf(stderr,"\n Punch Connect succeeded first time....");
} else {
if (errno == EINPROGRESS) {
while((attempts<5) && (quit_connecting==0)){
tv.tv_sec = 10;
tv.tv_usec = 0;
FD_ZERO(&myset);
FD_SET(csock, &myset);
if (select(csock+1, NULL, &myset, NULL, &tv) > 0) {
len = sizeof(so_error);
getsockopt(csock, SOL_SOCKET, SO_ERROR, &so_error, (socklen_t *)&len);
if (so_error == 0) {
fprintf(stderr,"\n Punch Connect succeeded ....");
// Set it back to blocking mode
arg = fcntl(csock, F_GETFL, NULL);
arg &= ~(O_NONBLOCK);
fcntl(csock, F_SETFL, arg);
quit_connecting=1;
retval = csock;
} else { // error
fprintf(stderr,"\n Punch select error: %s\n", strerror(so_error));
goto quit_connect_with_peer;
}
} else {
fprintf(stderr,"\n Punch select timeout: %s\n", strerror(so_error));
}
attempts++;
}// end while
} else { //errorno is not EINPROGRESS
fprintf(stderr, "\n Punch connect error: %s\n", strerror(errno));
}
}
quit_connect_with_peer:
quit_connecting=1;
fprintf(stderr,"\n Waiting for accept_thread to close..");
pthread_join(accept_thread,(void **)&psock);
if(retval == -1 ) {
if(psock && ((*psock) != -1)){
retval = (*psock); // Success from accept socket
}
}
fprintf(stderr,"\n After accept_thread psock = %d csock=%d, retval=%d",psock?(*psock):-1,csock,retval);
if(psock) free(psock); // Free the socket pointer , not the socket.
if((retval != csock) && (csock>=0)){ // close connect socket if accept succeeded
shutdown(csock,2);
close(csock);
}
return retval;
}
首先,阅读这个非常相似的问题:
TCP打孔
并阅读EDIT2之后的部分(此处节选)。那可能是失败的原因。
一旦第二个套接字成功绑定,绑定到该端口的所有套接字的行为都是不确定的。
不要担心linux在带有so_reuseaddr的socket(7)中也有类似的限制:
对于AF_INET套接字,这意味着套接字可以绑定,除非有一个激活的侦听套接字绑定到该地址。当侦听套接字绑定到具有特定端口的INADDR_ANY时,则不可能为任何本地地址绑定到此端口
我不认为听后而听前会有什么不同。
建立TCP连接的步骤总结:左侧:(客户端C连接到服务器S)是通常的情况,右侧是两个对等体a和B的同时连接(您正在尝试执行的操作):
C A B
\ (SYN) \ /
\ (SYN)\ /(SYN)
> S X
/ / \
/(SYN+ACK) / \
/ A < > B
C< \ /
\ (SYN+ACK)\ / (SYN+ACK)
\(ACK) X
\ / \
\ / \
> S A < > B
ESTABLISHED ESTABLISHED
参考文献:
https://tools.ietf.org/html/rfc793#section-3.4图8。+图8第7行更正:
https://tools.ietf.org/html/rfc1122#第87页(第4.2.2.10节)
区别是同步的syn*2/syn+ack*2,而不是syn/syn+ack/ack(在我对两个linux对等项的测试中,通常只有“第一个”回答syn+ack,因为它从来不是同步的,这并不重要)。
两个对等体都主动发起连接。它们最初并不等待连接,您根本不需要调用listen()/accept()。您根本不需要使用任何线程。
每个对等方都应该(通过%S)交换他们想要使用的本地端口给另一个对等方(并且在%S的帮助下,他们将交换他们的公共IP),假设端口不会被转换。
现在您只需尝试与您的4倍信息连接。每个将与(INADDR_ANY,lport)绑定并连接到(peer_global_address,peer_global_port),同时B也会这样做。最后,双方之间建立了一种独特的联系。
两个NAT框都将看到传出数据包并准备反向路径。
现在会有什么问题呢?
>
NAT设备(来自请求执行得太晚的对等体,例如B前面的NAT-B)使用RST数据包应答,而不是像大多数NAT设备所做的那样静默丢弃仍然未知的数据包。A接收RST并中止连接。然后B发送它,类似的命运发生了。ping往返的速度越快,就越容易得到它。为了避免这种情况,可以:
我可以告诉你,只需在两个对等体之间使用netcat就可以让TCP打孔“手工”可靠地工作,就像你的情况一样。
例如在带有Netcat的Linux上:同时键入两个对等体A和B上的数据,每个对等体都在其NAT设备后面的专用LAN中。对于通常的NAT设备(丢弃未知数据包),不需要任何完美的同步,即使是这两个命令之间的5s也可以(当然第一个命令会等待):
host-a$ nc -p 7777 public-ip-host-b 8888
host-b$ nc -p 8888 public-ip-host-a 7777
完成后,两个netcat都在一起建立了相同的唯一连接,而不是建立了两个连接。不需要重试(不需要循环)。当然,程序将使用connect(),如果第二个命令(因此connect())被延迟,则操作系统可能在connect()期间发送多个SYN数据包作为自动重试机制。这是在系统/内核级别,而不是在您的级别。
我希望这有助于您简化您的程序并使其工作。记住,不需要listen()、accept(),必须分叉,使用线程。您甚至不需要select(),只需要正常使用connect()块而不使用O_nonblock。
主要内容:本节引言:,1.运行效果图:,2.实现流程图:,3.代码示例:,4.代码下载:,5.本节小结:本节引言: 上节中我们给大家接触了Socket的一些基本概念以及使用方法,然后写了一个小猪简易聊天室的 Demo,相信大家对Socket有了初步的掌握,本节我们来学习下使用Socket来实现大文件的断点续传! 这里讲解的是别人写好的一个Socket上传大文件的例子,不要求我们自己可以写出来,需要的时候会用 就好! 1.运行效果图: 1.先把我们编写好的Socket服务端运行起来: 2.将一个音
主要内容:本节引言:,1.什么是Socket?,2.Socket通信模型:,3.Socket服务端的编写:,4.Socket客户端的编写:,5.增强版案例:小猪简易聊天室,本节小结:本节引言: 上一节的概念课枯燥无味是吧,不过总有点收获是吧,本节开始我们来研究基于TCP协议的Socket 通信,先来了解下Socket的概念,以及Socket通信的模型,实现Socket的步骤,以及作为Socket服务 端与客户端的两位各做要做什么事情!好的,我们由浅入深来扣这个Socket吧! 1.什么是Sock
2. 基于TCP协议的网络程序 下图是基于TCP协议的客户端/服务器程序的一般流程: 图 37.2. TCP协议通讯流程 服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()
本文向大家介绍nginx基于tcp做负载均衡的方法,包括了nginx基于tcp做负载均衡的方法的使用技巧和注意事项,需要的朋友参考一下 配置多台服务器时,经常需要让各个服务器之间的时间保持同步,如果服务器有外网环境,可以直接同外部的时间服务器更新时间,可以采用rdate命令更新时间: rdate -s tick.greyware.com 可以写个脚本放在/etc/cron.hourly中每小时校
问题在下面。这是我当前的测试代码,它没有成功。 如何做TCP打孔?我正在使用远程服务器进行测试。我正在运行。我已经为端口80设置了路由器,所以它不需要打孔。我的代码有关联。现在我尝试其他端口,但我不知道如何打孔。 我所做的是(C#代码) 如何做TCP打孔?
本文向大家介绍python 基于TCP协议的套接字编程详解,包括了python 基于TCP协议的套接字编程详解的使用技巧和注意事项,需要的朋友参考一下 基于TCP协议的套接字编程 实现电话沟通为例,这里传递的是字符,可以自己尝试去发送一个文件 模拟ssh远程执行命令(linux系统) 学习最有效的方法一定是有输入,一定要输出,这样学的东西才能真正得有用 以上就是本文的全部内容,希望对大家的学习有所