看了”子清行“朋友博客里的一篇文章,讲述了一个叫”DuplexPipe“的小工具的实现。最开始没怎么懂意思,看了他公开的源代码,是用java写的,一个jar包。可惜我不太会java,因此没法看。
回来想了半天,决定自己用C语言写一个。刚开始的目的是做一个能从外网连接到藏在NAT后面的内网的机子的程序,写了一天,大概300多行,能工作了,可是代码很糟糕,结构混乱,思路自己还蒙着。
第二天仔细想清楚了思路,决定按照Unix的思想,简单化,并且只做一件事。于是慢慢的思路清晰了,这个工具也就出炉了。
名字叫pf, port forwarding的简称,也就是端口转发。指定两个端口A,B,pf将不停的把A出来的数据转发到B,并开启一个子进程做相反的过程,把从B出来的数据转发到A。这样起到了连接两个端口的目的,并且数据可以从两个方向同时工作,是全双工的。
主要有两个选项:
-l port -l指定一个本地端口,将在此端口监听。可有连接到来则转发数据
-c ip:port -c指定一个目的地址,因此需要ip地址和端口号。pf
将连到该地址。并转发数据
具体使用时有4个组合:
两个 -l 指定两个端口A,B, 在A,B之间交换数据。
一个 -l, 一个 -c. 一个被动连接,一个主动连接,交换数据
两个 -c 两个都主动连接。
使用例子:
1。转发端口。假设web服务器在 8000 端口,此时不方便修改端口号,则可以用pf:
$ ./pf -c 8000 -l 80
-c 意思是主动连到 8000 的web服务器,然后自己在 80 端口监听,在两者间交换数据。别人再连到80 端口,就好像直接连到8000端口一样。
2。从外网连接藏在NAT后的主机:
假如内网的主机开了端口 3389,先在此主机上运行pf:
(这里假设外网主机的IP是12.34.56.78)
$ ./pf -c 3389 -c 12.34.56.78:2000
意思是先连到本机的 3389 端口,把他和 12.34.56.78 的2000端口连起来。
接下来在外网的机子12.34.56.78上运行 pf:
$ ./pf -l 2000 -l 5000
意思是接受外头来的到2000端口号的连接,并把这个连接同端口号 5000 连起来。
此时在外网主机上用客户端工具连接本地的 5000 端口号,就相当于和 NAT 后面的内网的主机的 3389 端口连接起来了.
$ tsclient localhost 5000
当然这里的远程桌面工具不一定是这个了。
讲了这2个例子不知道讲清楚没有。把这个工具和netcat结合起来用,就特别好玩了。此时即使不能控制网关做端口映射,也能穿透NAT了。当然前提是在NAT后面的主机上要运行起来这个工具。
这个工具现在还只能运行在Linux下,等以后慢慢完善了再移植到windows.
源代码如下,200多行
/* $Id: pf.c,v 1.5 2010/01/14 13:29:08 hh Exp $ */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#define BUFSIZE 128*1024
#define CLIENT 1
#define SERVER 0
#define fprintf if(debug)fprintf
#define perror if(debug)perror
unsigned char* buf = NULL;
int flag_udp = 0;
int interval = 2;
int debug = 0;
struct sockfd{
int type; /* 0: server; 1: client */
int fd; /* socket */
char* addr;
char* port;
int server; /* server socket */
};
int init_client_sock(struct sockfd* fd)
{
int tmpsock = socket(AF_INET, SOCK_STREAM, 0);
if(tmpsock < 0){
perror("socket");
exit(errno);
}
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(fd->addr ? fd->addr : "127.0.0.1");
addr.sin_port = htons((short)atoi(fd->port));
while(connect(tmpsock, (struct sockaddr*)&addr, sizeof(addr)) < 0){
fprintf(stderr,"connect to %s:%s error. sleep %d sec, then try again.../n",fd->addr,fd->port,interval);
close(tmpsock);
tmpsock = socket(AF_INET, SOCK_STREAM, 0);
sleep(interval);
}
fd->fd = tmpsock;
fprintf(stderr,"connected to %s:%s succeed./n",fd->addr,fd->port);
return tmpsock;
}
int init_server_sock(struct sockfd* fd)
{
int tmpsock = socket(AF_INET, SOCK_STREAM, 0);
if(tmpsock < 0){
perror("socket");
exit(errno);
}
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons((short)atoi(fd->port));
if(bind(tmpsock, (struct sockaddr*)&addr, sizeof(addr)) < 0){
perror("bind error");
exit(errno);
}
if(listen(tmpsock, 5) < 0){
perror("listen error");
exit(errno);
}
fd->server = tmpsock;
tmpsock = accept(tmpsock, (struct sockaddr*)NULL, NULL);
if(tmpsock < 0){
perror("accept");
exit(errno);
}
fd->fd = tmpsock;
fprintf(stderr,"one client connected in port %s./n",fd->port);
return tmpsock;
}
void initsock(struct sockfd* fd)
{
if(SERVER == fd->type)
init_server_sock(fd);
else
init_client_sock(fd);
return;
}
void regetsock(struct sockfd* fd)
{
if(SERVER == fd->type){
fprintf(stderr,"now waiting for connect.../n");
int client = accept(fd->server, (struct sockaddr*)NULL, NULL);
if(client < 0){
perror("accept");
exit(errno);
}
fd->fd = client;
fprintf(stderr,"there is one client connected in port %s./n",fd->port);
}else if(CLIENT == fd->type){
init_client_sock(fd);
}
return;
}
int main(int argc, char *argv[])
{
int i;
if(argc <= 2){
printf("/n");
printf("port forward v1.0: forward data between two ports./n");
printf("you can use it as:/n");
printf("pf -l xx -l xx/n");
printf("pf -l xx -c xx.xx.xx.xx:xx/n");
printf("pf -c xx.xx.xx.xx:xx -c xx.xx.xx.xx:xx/n");
printf("the ip address can be ignored when it will be set to 127.0.0.1/n");
printf("usage: %s -l port -c [ip:]port [-u] [-t sec] [-v]/n",argv[0]);
printf("/n");
printf("-u/tuse udp mode. If not specified -u, default mode is tcp. currently this will be ignored./n");
printf("-l/tlisten ports/n");
printf("-c/tip and port that actively connect/n");
printf("-n/tinterval=<seconds>/n");
printf("-v/tverbose information./n");
printf("/n");
printf("welcome to report bugs to <qianlongwydhh@163.com>/n");
exit(0);
}
struct sockfd fd[2];
int nfd = 0;
for (i = 1; i < argc; i++) {
switch( argv[i][1] ){
case 'l':
if(nfd > 1)
break;
if( argc == ++i ){
printf( "-l need a port number./n" );
exit( 1 );
}
if(atoi(argv[i]) == 0){
printf("%s is not valid port number!/n",argv[i]);
exit(errno);
}
fd[nfd].type = SERVER;
fd[nfd].addr = NULL;
fd[nfd].port = argv[i];
nfd++;
break;
case 'c':
if(nfd > 1)
break;
if(argc == ++i){
printf("-f need a port number./n");
exit(1);
}
fd[nfd].type = CLIENT;
char *ptr = strchr(argv[i],':');
if(!ptr){
if(atoi(argv[i]) == 0){
printf("%s is not a valid port number!/n",argv[i]);
exit(errno);
}
fd[nfd].addr = "127.0.0.1";
fd[nfd].port = argv[i];
}else {
fd[nfd].addr = (char*)malloc(ptr - argv[i] + 1);
strncpy(fd[nfd].addr,argv[i],ptr - argv[i]);
fd[nfd].addr[ptr - argv[i]] = 0;
if(inet_addr(fd[nfd].addr) == -1){
printf("%s is not a valid ip!/n",fd[nfd].addr);
exit(errno);
}
fd[nfd].port = ++ptr;
}
ptr = NULL;
nfd++;
break;
case 'n':
if(argc == ++i){
printf("-f need a seconds value./n");
exit(1);
}
if(atoi(argv[i]) == 0){
printf("%s is not a valid number./n",argv[i]);
exit(errno);
}
interval = atoi(argv[i]);
break;
case 'v':
debug = 1;
break;
case 'u':
flag_udp = 1;
break;
default:
printf("unkown options: %s/n",argv[i]);
}
}
int num;
buf = (unsigned char*)malloc(BUFSIZE);
signal(SIGCHLD,SIG_IGN);
for(i=0;i<2;i++)
initsock(&fd[i]);
pid_t pid;
while(1){
pid = fork();
if(pid == 0){
while((num = read(fd[0].fd, buf, BUFSIZE)) > 0){
int tmp = (int)write(fd[1].fd, buf, num);
fprintf(stderr,"chile:0-->1 %d bytes/n",tmp);
}
if(num <= 0)
perror("child:read error");
kill(getppid(),SIGKILL);
if(CLIENT == fd[0].type){
close(fd[0].fd);
}
regetsock(&fd[0]);
} else{
while((num = read(fd[1].fd, buf, BUFSIZE)) > 0){
int tmp = (int)write(fd[0].fd, buf, num);
fprintf(stderr,"parent:1-->0 %d bytes/n",tmp);
}
if(num <= 0)
perror("child:read error");
kill(pid,SIGKILL);
if(CLIENT == fd[1].type){
close(fd[1].fd);
}
regetsock(&fd[1]);
}
}
return 0;
}