背景
問題:HTTP服務器在內網,終端在另一個內網,需要終端可以訪問HTTP服務器。
解決:在公網布置一個TCP代理,HTTP服務器上也布置一個代理(只能內網主動訪問外網)。
1. IP分片、TCP流和應用層消息提取
- UDP發大包是否會IP分片?
會。IP層進行重組。UDP頭部8字節(源端口,目的端口,長度,校驗和),比較簡單。
- TCP發大包?
SYN報文中會攜帶MSS,后續流的發送過程中,超過MSS的報文會進行分段傳輸。
- 應用層消息提取?
如果在一個tcp長連接中連續發送多個應用層消息,則承載的應用層協議需要能夠划分消息。tcp面向流,socket接收的可能是兩個拼在一起的消息。應用層需要能夠將流拆分成獨立的消息。
2. socket option
setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #當一個有相同地址和端口的socket處於TIME_WAIT狀態時,新socket要占用該地址和端口
setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) #socket保活打開
setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 10) #首次探測前TCP空閑時間
setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 30) #發送保活報文的間隔
setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 3) #判定斷開前的探測次數
3. Python Tcp reverse proxy
流程:
實現:ServerA & ServerB
#!/usr/bin/env python |
C proxy client
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <netinet/tcp.h> #include <pthread.h>
#define PROXY_PORT 10099 #define RESTFUL_PORT 5000 #define BUFFER_SIZE 1024
int proxy_cli,restful_cli;
void close_sockets() { if(0 != shutdown(proxy_cli,SHUT_RDWR)){ //perror("shutdown proxy sock error"); }else{ printf("shutdown proxy\n"); }
if(0 != close(proxy_cli)){ //perror("close proxy sock error"); }else{ printf("close proxy\n"); }
if(0 != shutdown(restful_cli,SHUT_RDWR)){ //perror("shutdown restful sock error"); }else{ printf("shutdown restful\n"); }
if(0 != close(restful_cli)){ //perror("close restful sock error"); }else{ printf("close restful\n"); } }
void proxy_handler() { char recbuf[BUFFER_SIZE]; int cnt; int sendcnt; while(1){ cnt = recv(proxy_cli, recbuf, BUFFER_SIZE,0); if(cnt > 0) { //正常處理數據 printf("recv from proxy %d\n",cnt); sendcnt = send(restful_cli, recbuf, cnt,0);
if (sendcnt > 0){ printf("send to restful %d\n",sendcnt); } else{ printf("send to restful error %d\n",sendcnt); break; } } else { if((cnt<0) &&(errno == EAGAIN||errno == EWOULDBLOCK||errno == EINTR)) { printf("proxy cnt < 0 and error:%d\n",errno); continue;//繼續接收數據 }
if (0 == cnt){ printf("proxy recv close.\n"); } else{ printf("proxy recv:%d\n",cnt); }
break;//跳出接收循環 } } printf("proxy close sockets\n"); close_sockets(); return; }
void restful_handler() { char recbuf[BUFFER_SIZE]; int cnt; int sendcnt; while(1){ cnt = recv(restful_cli, recbuf, BUFFER_SIZE,0); if(cnt > 0) { //正常處理數據 printf("recv from restful %d\n",cnt); sendcnt = send(proxy_cli, recbuf, cnt,0);
if (sendcnt > 0){ printf("send to proxy %d\n",sendcnt); } else{ printf("send to proxy error %d\n",sendcnt); break; } } else { if((cnt<0) &&(errno == EAGAIN||errno == EWOULDBLOCK||errno == EINTR)) { printf("restful cnt < 0 and error:%d\n",errno); continue;//繼續接收數據 }
if(0==cnt){ printf("restful close\n"); }else{ printf("restful recv:%d\n",cnt); } break;//跳出接收循環 } } printf("restful close sockets\n"); close_sockets(); return; }
void setSocketOpt(int sk) { int keepalive = 1; // 開啟keepalive屬性 int keepidle = 6; // 如該連接在6秒內沒有任何數據往來,則進行探測 int keepinterval = 5; // 探測時發包的時間間隔為5 秒 int keepcount = 3; // 探測嘗試的次數.如果第1次探測包就收到響應了,則后2次的不再發. setsockopt(sk,SOL_SOCKET,SO_KEEPALIVE, (void *)&keepalive , sizeof(keepalive )); setsockopt(sk,SOL_TCP,TCP_KEEPIDLE, (void*)&keepidle , sizeof(keepidle )); setsockopt(sk,SOL_TCP,TCP_KEEPINTVL, (void *)&keepinterval , sizeof(keepinterval )); setsockopt(sk,SOL_TCP,TCP_KEEPCNT, (void *)&keepcount , sizeof(keepcount )); }
int main(int argc,char **argv) { if (1 == argc){ printf("usage: proxyproxy proxy_ip\n"); return 0; }else{ printf("proxy ip:%s\n",argv[1]); } pthread_t thread1, thread2; int tmp1, tmp2; struct sockaddr_in proxyaddr,restfuladdr; memset(&proxyaddr, 0, sizeof(proxyaddr)); proxyaddr.sin_family = AF_INET; proxyaddr.sin_port = htons(PROXY_PORT); proxyaddr.sin_addr.s_addr = inet_addr(argv[1]);
memset(&restfuladdr, 0, sizeof(restfuladdr)); restfuladdr.sin_family = AF_INET; restfuladdr.sin_port = htons(RESTFUL_PORT); restfuladdr.sin_addr.s_addr = inet_addr("127.0.0.1");
while(1) { printf("**********************************************\n"); proxy_cli = socket(AF_INET,SOCK_STREAM, 0); ///連接服務器,成功返回0,錯誤返回-1 if (connect(proxy_cli, (struct sockaddr *)&proxyaddr, sizeof(proxyaddr)) < 0) { perror("connect proxy error"); sleep(5); continue; } setSocketOpt(proxy_cli);
restful_cli = socket(AF_INET,SOCK_STREAM, 0); if (connect(restful_cli, (struct sockaddr *)&restfuladdr, sizeof(restfuladdr)) < 0) { perror("connect restful error"); shutdown(proxy_cli,SHUT_RDWR); close(proxy_cli); sleep(5); continue; } setSocketOpt(restful_cli);
int ret_thrd1, ret_thrd2;
ret_thrd1 = pthread_create(&thread1, NULL, (void *)&proxy_handler, NULL); ret_thrd2 = pthread_create(&thread2, NULL, (void *)&restful_handler, NULL);
// 線程創建成功,返回0,失敗返回失敗號 if (ret_thrd1 != 0) { printf("create proxy thread error\n"); close_sockets(); } else { printf("create proxy thread\n"); }
if (ret_thrd2 != 0) { printf("create restful thread error\n"); close_sockets(); } else { printf("create restful thread\n"); }
//同樣,pthread_join的返回值成功為0 tmp1 = pthread_join(thread1, NULL); //printf("thread1 return value(tmp) is %d\n", tmp1); if (tmp1 != 0) { printf("cannot join with thread1\n"); } //printf("thread1 end\n");
tmp2 = pthread_join(thread2, NULL); //printf("thread2 return value(tmp) is %d\n", tmp1); if (tmp2 != 0) { printf("cannot join with thread2\n"); } //printf("thread2 end\n"); printf("------------------------------------------------\n"); }
return 0; } |
延伸:
兩個客戶端之間創建隧道?