TCPでNAT(TCPホール)を貫通する実現
10054 ワード
1.TCP透過原理:2つの異なるローカルエリアネットワークの後ろにそれぞれ2台のクライアントAとBがあり、ABのあるローカルエリアネットワークはそれぞれ1つのルータを通じてインターネットに接続すると仮定する.インターネット上にサーバーSがあります.
現在ABは直接相手と情報を送信することができなくて、ABはすべて相手がインターネットの上で本当のIPとポートを知らないで、ABの所在するローカルエリアネットワークのルータは内部が外に自発的に送信する情報だけを許可します.Bが直接Aに送信するルータのメッセージに対しては,ルーティングは「信頼されていない」としてそのまま破棄される.
AB直接の通信を実現するには、まず、Aがインターネット上のサーバSに接続してメッセージを送信する(UDPのような接続されていないプロトコルに対しては、実際には初期セッションでメッセージを送信すればよい)ことで、Sは、Aのインターネット上の実際の端末(メッセージを送信するIPとポート番号)を取得する必要がある.次にBも同様の手順を行い、SはABのインターネット上の端末(これが「穴を開ける」こと)を知った.次に、Sは、インターネット上の実際の端末、すなわちSは、AクライアントBのセッション端末、Sは、BクライアントAのセッション端末にそれぞれ通知する.これにより,ABが相手の実端末を知ってから,直接実端末を介してメッセージを送信することができる(以前は双方がメッセージを外部に送信していたため,ルーティングにデータの出入りを許可するメッセージチャネルがあった).
2.プログラム構想:1:サーバー起動、傍受ポート8877
2:クライアント(client 1と呼ばれる)を初めて起動し、サーバに接続すると、サーバは文字列firstを返し、これがclient 1であることを識別し、同時に、サーバはこのクライアントの(変換された)IPとポートを記録する.
3:クライアント(client 2と呼ばれる)を2回目に起動し、サーバに接続すると、サーバは自身の送信ポート(port 2と呼ばれる)とclient 1の(変換された)IPとポートを返します.
4:その後、サーバーはクライアントt 1を再送信してクライアントt 2(変換後の)IPとポートを返し、この2つのクライアントとの接続を切断する(この時点で、サーバーの作業はすべて完了した)
5:client 2はclient 1に接続しようとしますが、今回は失敗するに違いありませんが、client 1が正常に貫通し、自分に接続し、port 2ポートを再利用可能なポートとして設定し、ポートport 2を傍受するためにルータに記録を残します.
6:client 1はclient 2に接続しようとしますが、前回は失敗する可能性があります.まだ貫通が成功していないため、10回も接続に失敗した場合は、貫通に失敗したことを証明します(ハードウェアがサポートされていない可能性があります)、成功した場合は、毎秒client 2にhello,worldを送信します.
7:client 1にsend message:Hello,world,client 2にrecv message:Hello,worldが絶えず現れた場合、実験が成功したことを証明し、そうでなければ失敗した.
3.声明1:このプログラムはただのDEMOなので、きっと不完全なところがたくさんあると思いますので、ご了承ください.
2:多くのネットワークでは、このプログラムは成功に穴を開けることができません.ハードウェアの問題かもしれません(結局、すべてのルータが貫通をサポートしているわけではありません)、私のプログラムの問題かもしれません.もし皆さんが意見やアドバイスがあれば、伝言を歓迎したり、メールを送ったりしてください(メールアドレスは:[email protected])
4.上のコード:
サーバ側:
5.実行例:(第1端末)
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
.................
2番目の端末:
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
..................
現在ABは直接相手と情報を送信することができなくて、ABはすべて相手がインターネットの上で本当のIPとポートを知らないで、ABの所在するローカルエリアネットワークのルータは内部が外に自発的に送信する情報だけを許可します.Bが直接Aに送信するルータのメッセージに対しては,ルーティングは「信頼されていない」としてそのまま破棄される.
AB直接の通信を実現するには、まず、Aがインターネット上のサーバSに接続してメッセージを送信する(UDPのような接続されていないプロトコルに対しては、実際には初期セッションでメッセージを送信すればよい)ことで、Sは、Aのインターネット上の実際の端末(メッセージを送信するIPとポート番号)を取得する必要がある.次にBも同様の手順を行い、SはABのインターネット上の端末(これが「穴を開ける」こと)を知った.次に、Sは、インターネット上の実際の端末、すなわちSは、AクライアントBのセッション端末、Sは、BクライアントAのセッション端末にそれぞれ通知する.これにより,ABが相手の実端末を知ってから,直接実端末を介してメッセージを送信することができる(以前は双方がメッセージを外部に送信していたため,ルーティングにデータの出入りを許可するメッセージチャネルがあった).
2.プログラム構想:1:サーバー起動、傍受ポート8877
2:クライアント(client 1と呼ばれる)を初めて起動し、サーバに接続すると、サーバは文字列firstを返し、これがclient 1であることを識別し、同時に、サーバはこのクライアントの(変換された)IPとポートを記録する.
3:クライアント(client 2と呼ばれる)を2回目に起動し、サーバに接続すると、サーバは自身の送信ポート(port 2と呼ばれる)とclient 1の(変換された)IPとポートを返します.
4:その後、サーバーはクライアントt 1を再送信してクライアントt 2(変換後の)IPとポートを返し、この2つのクライアントとの接続を切断する(この時点で、サーバーの作業はすべて完了した)
5:client 2はclient 1に接続しようとしますが、今回は失敗するに違いありませんが、client 1が正常に貫通し、自分に接続し、port 2ポートを再利用可能なポートとして設定し、ポートport 2を傍受するためにルータに記録を残します.
6:client 1はclient 2に接続しようとしますが、前回は失敗する可能性があります.まだ貫通が成功していないため、10回も接続に失敗した場合は、貫通に失敗したことを証明します(ハードウェアがサポートされていない可能性があります)、成功した場合は、毎秒client 2にhello,worldを送信します.
7:client 1にsend message:Hello,world,client 2にrecv message:Hello,worldが絶えず現れた場合、実験が成功したことを証明し、そうでなければ失敗した.
3.声明1:このプログラムはただのDEMOなので、きっと不完全なところがたくさんあると思いますので、ご了承ください.
2:多くのネットワークでは、このプログラムは成功に穴を開けることができません.ハードウェアの問題かもしれません(結局、すべてのルータが貫通をサポートしているわけではありません)、私のプログラムの問題かもしれません.もし皆さんが意見やアドバイスがあれば、伝言を歓迎したり、メールを送ったりしてください(メールアドレスは:[email protected])
4.上のコード:
サーバ側:
/*
:server.c
PS: , client1, client2
:
1: client1, "first", client2 , client2 IP port client1;
2: client2, client1 IP port port, 。
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 128
#define SERV_PORT 8877
// ,
void error_quit(const char *str)
{
fprintf(stderr, "%s", str);
// ,
if( errno != 0 )
fprintf(stderr, " : %s", strerror(errno));
printf("
");
exit(1);
}
int main(void)
{
int i, res, cur_port;
int connfd, firstfd, listenfd;
int count = 0;
char str_ip[MAXLINE]; // IP
char cur_inf[MAXLINE]; // [IP+port]
char first_inf[MAXLINE]; // [IP+port]
char buffer[MAXLINE]; //
socklen_t clilen;
struct sockaddr_in cliaddr;
struct sockaddr_in servaddr;
// TCP
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
// socket socket
res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if( -1 == res )
error_quit("bind error");
//
res = listen(listenfd, INADDR_ANY);
if( -1 == res )
error_quit("listen error");
while( 1 )
{
//
connfd = accept(listenfd,(struct sockaddr *)&cliaddr, &clilen);
if( -1 == connfd )
error_quit("accept error");
inet_ntop(AF_INET, (void*)&cliaddr.sin_addr, str_ip, sizeof(str_ip));
count++;
// , IP+port first_inf ,
// , 'first',
if( count == 1 )
{
firstfd = connfd;
cur_port = ntohs(cliaddr.sin_port);
snprintf(first_inf, MAXLINE, "%s %d", str_ip, cur_port);
strcpy(cur_inf, "first
");
write(connfd, cur_inf, strlen(cur_inf)+1);
}
// , IP+port ,
// port ,
// ,
else if( count == 2 )
{
cur_port = ntohs(cliaddr.sin_port);
snprintf(cur_inf, MAXLINE, "%s %d
", str_ip, cur_port);
snprintf(buffer, MAXLINE, "%s %d
", first_inf, cur_port);
write(connfd, buffer, strlen(buffer)+1);
write(firstfd, cur_inf, strlen(cur_inf)+1);
close(connfd);
close(firstfd);
count = 0;
}
// ,
else
error_quit("Bad required");
}
return 0;
}
クライアント:/*
:client.c
PS: , client1, client2
: , client1 client2,
client1, client2 IP Port, client2,
client2, client1 IP Port port,
client1 ( ), port 。
, 。
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 128
#define SERV_PORT 8877
typedef struct
{
char ip[32];
int port;
}server;
// ,
void error_quit(const char *str)
{
fprintf(stderr, "%s", str);
// ,
if( errno != 0 )
fprintf(stderr, " : %s", strerror(errno));
printf("
");
exit(1);
}
int main(int argc, char **argv)
{
int i, res, port;
int connfd, sockfd, listenfd;
unsigned int value = 1;
char buffer[MAXLINE];
socklen_t clilen;
struct sockaddr_in servaddr, sockaddr, connaddr;
server other;
if( argc != 2 )
error_quit("Using: ./client ");
// ( )
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &sockaddr.sin_addr);
//
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
//
res = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
if( res < 0 )
error_quit("connect error");
//
res = read(sockfd, buffer, MAXLINE);
if( res < 0 )
error_quit("read error");
printf("Get: %s", buffer);
// first,
if( 'f' == buffer[0] )
{
// IP+port
res = read(sockfd, buffer, MAXLINE);
sscanf(buffer, "%s %d", other.ip, &other.port);
printf("ff: %s %d
", other.ip, other.port);
//
connfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&connaddr, 0, sizeof(connaddr));
connaddr.sin_family = AF_INET;
connaddr.sin_addr.s_addr = htonl(INADDR_ANY);
connaddr.sin_port = htons(other.port);
inet_pton(AF_INET, other.ip, &connaddr.sin_addr);
// , , ,
// 10 , ( )
while( 1 )
{
static int j = 1;
res = connect(connfd, (struct sockaddr *)&connaddr, sizeof(connaddr));
if( res == -1 )
{
if( j >= 10 )
error_quit("can't connect to the other client
");
printf("connect error, try again. %d
", j++);
sleep(1);
}
else
break;
}
strcpy(buffer, "Hello, world
");
// , ( 2) hello, world
while( 1 )
{
res = write(connfd, buffer, strlen(buffer)+1);
if( res <= 0 )
error_quit("write error");
printf("send message: %s", buffer);
sleep(1);
}
}
//
else
{
// 1 IP+port port
sscanf(buffer, "%s %d %d", other.ip, &other.port, &port);
// TCP
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&connaddr, 0, sizeof(connaddr));
connaddr.sin_family = AF_INET;
connaddr.sin_addr.s_addr = htonl(INADDR_ANY);
connaddr.sin_port = htons(other.port);
inet_pton(AF_INET, other.ip, &connaddr.sin_addr);
//
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
// 1, , ,
// 1 ,
res = connect(sockfd, (struct sockaddr *)&connaddr, sizeof(connaddr));
if( res < 0 )
printf("connect error
");
//
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
//
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
// socket socket
res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if( -1 == res )
error_quit("bind error");
//
res = listen(listenfd, INADDR_ANY);
if( -1 == res )
error_quit("listen error");
while( 1 )
{
// 1
connfd = accept(listenfd,(struct sockaddr *)&sockaddr, &clilen);
if( -1 == connfd )
error_quit("accept error");
while( 1 )
{
// 1
res = read(connfd, buffer, MAXLINE);
if( res <= 0 )
error_quit("read error");
printf("recv message: %s", buffer);
}
close(connfd);
}
}
return 0;
}
5.実行例:(第1端末)
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
.................
2番目の端末:
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
..................