Objective-C (iOS)实现TCP反向代理(Port forward隧道)
之前有一篇文章《华为AnyOffice eSDK建立TCP端口转发隧道》介绍了如何使用Java建立一条TCP端口隧道。实现一个将远程端口反向代理到本地的效果,类似于Nginx。比如你有一个远程Web服务器1.2.3.4:80,你建立起TCP转发隧道后,访问本地的127.0.0.1:8080端口就等于访问远程服务器的80端口。那么反向代理的轮子很多,比如Nginx,Haproxy,NodeJS,Caddy,Apache等等,为什么要自己写一个呢。我当初写这个主要目的还是为了在Android和iPhone手机端使用反向代理功能,并基于此兼容华为AnyOffice提供的eSDK,通过建立隧道来实现4层TCP协议的VPN通讯(注:华为的eSDK只提供了4层的HTTP协议的API,对4层协议没有很好的支持)。
所以这里,就介绍一下如何在IOS上实现一条TCP反向代理隧道。虽然说是用Objc开发的,但主要还是使用C语言实现的,好在IOS和MAC OS都可以在.m文件中混用C/C++和Objc代码,使用C语言的原因是华为的eSDK是用的C语言接口,他们重写了recv()和send(),变成了svn_recv()和svn_send()方法,所以为了实现最好的兼容,我们的隧道也采用C书写。Objc也有很大的任务,那就是使用GCD开启线程,使得整个代码与JAVA代码非常相似,简直如同JAVA代码一句一句翻译过来的一样,所有线程的调度也是一样的,所以在这里我就只贴代码,原理可以去我之前的博客查看。
下面就是在IOS和MAC OS中建立TCP隧道的代码了,下面代码使用的是原生socket,可以跑在任意一台iPhone和MAC主机上,对于MAC,直接在xcode中新建一个命令行应用然后执行main函数即可。对于iOS,需要把main函数放在一条子线程中异步执行。如果要和华为eSDK对接,只需把某些recv()/send()调用换成svn_recv(),svn_send()调用即可。并把下面的remote_socket初始化成svn_socket。并引入华为eSDK的头文件。具体需要更换的地方我都写在注释中了。
源代码中有两处IP地址的赋值,分别是0.0.0.0:8080 和 1.2.3.4:80,其中
0.0.0.0:8080是隧道的入口地址,你使用其他程序(比如浏览器)访问127.0.0.1:8080就等同与访问1.2.3.4:80
1.2.3.4:80是隧道出口地址,也是被反向代理的地址,使用过Nginx的攻城狮们应该不陌生。
#import <Foundation/Foundation.h> #include <stdio.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> //int startTunnel(); int main(int argc, const char * argv[]) { int result = -8; while(result == -8){//在断网的时候,accept函数会失效然后退出,然后不断的尝试重建隧道 result = startTunnel(); sleep(1); } } int startTunnel() { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); struct sockaddr_in server_addr; server_addr.sin_len = sizeof(struct sockaddr_in); server_addr.sin_family = AF_INET;//Address families AF_INET互联网地址簇 server_addr.sin_port = htons(8080); server_addr.sin_addr.s_addr = inet_addr("0.0.0.0"); bzero(&(server_addr.sin_zero),8); //创建socket int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有连接 int haha=1; setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &haha, sizeof(haha));//程序退出后可以解除端口占用 if (server_socket == -1) { perror("socket error"); return 1; } //绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信 int bind_result = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (bind_result == -1) { perror("bind error"); return -9; } //listen侦听 第一个参数是套接字,第二个参数为等待接受的连接的队列的大小,在connect请求过来的时候,完成三次握手后先将连接放到这个队列中,直到被accept处理。如果这个队列满>了,且有新的连接的时候,对方可能会收到出错信息。 if (listen(server_socket, 5) == -1) { perror("listen error"); return 1; } while(1){ struct sockaddr_in client_address; socklen_t address_len; memset(&client_address,0,sizeof(client_address)); address_len = sizeof(client_address); int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &address_len); printf(" client socket is %d ip address is %s:%d ",client_socket,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));//打印客户端的请求地址 if(client_socket == -1){ printf("what the fuck "); shutdown(client_socket, SHUT_RDWR); return -8; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //在这里应该新开一个线程执行下面的操作 printf("接收到了新的连接 "); //返回的client_socket为一个全相关的socket,其中包含client的地址和端口信息,通过client_socket可以和客户端进行通信。 if (client_socket == -1) { perror("accept error"); close(client_socket); return; } //-----------初始化发送到真实服务器的svn_socket-------- struct sockaddr_in remote_addr; remote_addr.sin_len = sizeof(struct sockaddr_in); remote_addr.sin_family = AF_INET; remote_addr.sin_port = htons(80);//emessage服务器的端口 remote_addr.sin_addr.s_addr = inet_addr("1.2.3.4");//emessage服务器的IP地址 bzero(&(remote_addr.sin_zero),8); int remote_socket = socket(AF_INET, SOCK_STREAM, 0);//这个socket应该是华为的svn_socket,用于和真实服务器通信 if (remote_socket == -1) { perror("socket error"); close(client_socket); close(remote_socket);//此处应该为svn_close return; } if (connect(remote_socket, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr_in))==0) {//此处应该是svn_connect printf("destination is %d ip address is %s:%d ",remote_socket,inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port)); //connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。 printf("远程服务器连接成功 "); } else { close(server_socket); return; //此处应终止,不要再往下走了 } //----------下面是原生socket接收请求--------- //下面代码是接收手机APP发出的请求,然后转发到VPN里面 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //下面这个while循环应该单独跑在一个线程里,是把客户端转发到服务器 long byte_num = 10000; char recv_msg[2048];//xmpp向外发送的消息 while (byte_num>0) { bzero(recv_msg, 2048); byte_num=0; byte_num = recv(client_socket,recv_msg,2048,0);//接收到了xmpp向外发送的数据 if(byte_num < 1)break; printf("length==%ld ",byte_num); //把接收到的字节再发到真实服务器 if (send(remote_socket, recv_msg, byte_num, 0) == -1) {//此处应该是svn_send perror("send error"); close(remote_socket);//此处应该换成svn_socket close(client_socket); }else{ printf("向远程服务器发送数据成功 "); } } close(client_socket); close(remote_socket); printf("全部请求都发送完毕 "); }); //下面的代码是接收服务器发来的消息,然后再发给手机APP dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //下面这个while也要单独跑在一个线程里,是把真实服务器转发给客户端 long reply_num = 10000; char server_msg[2048];//服务器响应的消息 while (reply_num>0) { reply_num=0; bzero(server_msg, 2048); reply_num = recv(remote_socket, server_msg, sizeof(server_msg), 0);//接收到了真实服务器响应的消息,此处应为svn_recv printf("length==%ld ",reply_num); if(reply_num < 1)break; if (send(client_socket, server_msg, reply_num, 0) == -1) {//把服务器响应的信息发送给xmpp perror("send error"); close(remote_socket);//此处应该换成svn_socket close(client_socket); } else { //server_msg[reply_num]=" "; //printf("向客户端发送数据成功%s ",server_msg); } } printf("全部数据都发送完毕 "); close(remote_socket);//此处应该换成svn_socket close(client_socket); }); printf("全部的线程都退出了 "); }); } } return 0; }
- 上一篇:没有了
- 下一篇: 科学收集并分析Android用户敏感信息实战