メールやらHTTP等の通信プログラムのプロトコルは大抵Scoketを使用して通信している。HTTPプロトコルが実際がどういうデータの流れになっているか知りたかったのだが、Socketを中継してみてそのデータを標準出力するプログラムを書いてみた。プロキシサーバのような外部Hostへの中継は難しいが、ローカルで動作しているサーバはポート番号だけ入れ替えることで出来るようになる。
通常は
ブラウザ ←→ HTTPサーバ(ポート80)
となっているのを
ブラウザ ←→ SocketRelayサーバ(ポート8080)←→ HTTPサーバ(ポート80)
のように中継させる事によって、ブラウザからはSocketRelayサーバがHTTPサーバとして動作しているように見えるようになる。SocketRelayサーバは単純にSocketの入出力ストリームを中継しているだけで実質何もしていない。HTTPだけではなくてPOPやSMTPやFTPなんかも同様に出来る。
ソースコード
//SocketRelay.c
#include <stdio.h>
#include <sys/types.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFFER_SIZE ( 1024 * 16 ) // 16Kバッファ
static int serverSock;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//system signalのhandler
void signalHandler( int signal ){
shutdown(serverSock, 2);
fprintf(stderr,"EXIT SIGNAL:%d\n",signal);
exit(EXIT_FAILURE);
}
//ファイル記述子を使って入力を出力に渡す
void *readToWrite(int file[]){
char buffer[BUFFER_SIZE];
ssize_t read_size;
int in_file = file[0];
int out_file = file[1];
while(1){
// 入力ストリームから読み込み
read_size = read(in_file, buffer, sizeof(buffer));
if (read_size == 0){
break; //EOF
}
//close
if(read_size < 0){
break;
}
pthread_mutex_lock( &mutex );
// 標準出力ストリームへ書き出し
write(1,buffer,(unsigned int) read_size);
pthread_mutex_unlock( &mutex );
// 出力側ストリームへ書き出し
write(out_file,buffer,(unsigned int) read_size);
}
return (file);
}
// サーバソケットを作成する
int createServerSoket(int port){
int soc;
char name[MAXHOSTNAMELEN];
struct sockaddr_in socin;
struct hostent *servhost;
bzero(&socin, sizeof(struct sockaddr_in));
gethostname(name, MAXHOSTNAMELEN);
servhost=gethostbyname(name);
if(servhost==NULL){
fprintf(stderr, "ERROR: gethostbyname\n");
exit(EXIT_FAILURE);
}
socin.sin_family = servhost->h_addrtype;
socin.sin_port = htons(port);
if((soc=socket(AF_INET,SOCK_STREAM,0))<0){
fprintf(stderr, "ERROR: create socket\n");
exit(EXIT_FAILURE);
}
if(bind(soc,(struct sockaddr *)&socin,sizeof(socin))<0){
close(soc);
fprintf(stderr, "ERROR: socket bind fail\n");
exit(EXIT_FAILURE);
}
listen(soc,SOMAXCONN);
return (soc);
}
//クライアントソケットを作成する
int createClientSoket(char *host, int port){
int soc;
struct hostent *servhost;
struct sockaddr_in server;
servhost = gethostbyname(host);
if ( servhost == NULL ){
fprintf(stderr, "ERROR:gethostbyname(%s)\n", host);
exit(EXIT_FAILURE);
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
bcopy(servhost->h_addr, &server.sin_addr, servhost->h_length);
server.sin_port = htons(port);
if ( ( soc = socket(AF_INET, SOCK_STREAM, 0) ) < 0 ){
fprintf(stderr, "ERROR: create socket\n");
exit(EXIT_FAILURE);
}
if ( connect(soc, (struct sockaddr *)&server, sizeof(server)) == -1 ){
fprintf(stderr, "ERROR: connect socket\n");
exit(EXIT_FAILURE);
}
return (soc);
}
void *socketRelay(int file[]){
int connectedSocket = file[0];
int clientSock = file[1];
int files_in[2] = {connectedSocket,clientSock};
pthread_t thread_id1, thread_id2;
int status;
status=pthread_create(&thread_id1,NULL,(void *(*)(void *))readToWrite, files_in);
if(status!=0){
fprintf(stderr,"pthread_create : %s",strerror(status));
exit(EXIT_FAILURE);
}
int files_out[2] = {clientSock,connectedSocket};
status=pthread_create(&thread_id2,NULL,(void *(*)(void *))readToWrite, files_out);
if(status!=0){
fprintf(stderr,"pthread_create : %s",strerror(status));
exit(EXIT_FAILURE);
}
pthread_join(thread_id2,NULL);
pthread_cancel(thread_id1);
close(connectedSocket);
close(clientSock);
return file;
}
void start(char *host, int serverPort, int clientPort){
int connectedSocket;
socklen_t len;
pthread_t thread_id1;
int status;
serverSock = createServerSoket(serverPort);
while(1){
struct sockaddr_in peer_sin;
len = sizeof(peer_sin);
connectedSocket = accept(serverSock, (struct sockaddr *)&peer_sin, &len);
if ( connectedSocket == -1 ){
fprintf(stderr, "ERROR:accept socket\n");
exit(EXIT_FAILURE);
}
int clientSock = createClientSoket(host,clientPort);
int files[2] = {connectedSocket,clientSock};
status=pthread_create(&thread_id1,NULL,(void *(*)(void *))socketRelay, files);
if(status!=0){
fprintf(stderr,"pthread_create : %s",strerror(status));
exit(EXIT_FAILURE);
}
};
}
int main(int argc, char * argv[])
{
if (argc < 4) {
printf("Invalid arguments");
return EXIT_FAILURE;
}
char *host = argv[1];
struct hostent *hn;
hn = gethostbyname(host);
if(hn == NULL){
printf("Invalid hostname");
return EXIT_FAILURE;
}
int i=2;
for (; i<4; i++) {
char *port = argv[i];
while(*port) {
if(*port >= '0' && *port <= '9') port++;
else break;
}
if (*port) {
printf("Invalid port number:%s", argv[i]);
return EXIT_FAILURE;
}
}
signal( SIGINT, signalHandler );
signal( SIGKILL, signalHandler );
signal( SIGTERM, signalHandler );
start(host, atoi(argv[2]), atoi(argv[3]));
return EXIT_SUCCESS;
}
ネットワークやスレッドが絡むといろいろ知らない事が多かったので面倒だった。他の言語でも似たような感じになると思う。
使い方
$gcc -o socket-relay SocketRelay.c
$./socket-relay localhost 8080 80
ブラウザ等でhttp://localhost:8080/へアクセス
ctrl+cで終了
出力例
GET / HTTP/1.1 Host: localhost:8080 Connection: keep-alive Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31 Accept-Encoding: gzip,deflate,sdch Accept-Language: ja,en-US;q=0.8,en;q=0.6 Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.3 HTTP/1.1 200 OK Date: Tue, 16 Apr 2013 14:59:19 GMT Server: Apache/2.2.22 (Unix) DAV/2 PHP/5.3.15 with Suhosin-Patch mod_ssl/2.2.22 OpenSSL/0.9.8r Content-Location: index.html.en Vary: negotiate TCN: choice Last-Modified: Mon, 11 Feb 2013 08:55:12 GMT ETag: "35d48-2c-4d56f111c5c00" Accept-Ranges: bytes Content-Length: 44 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html Content-Language: en <html><body><h1>It works!</h1></body></html>GET /favicon.ico HTTP/1.1 Host: localhost:8080 Connection: keep-alive Accept: */* User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31 Accept-Encoding: gzip,deflate,sdch Accept-Language: ja,en-US;q=0.8,en;q=0.6 Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.3 HTTP/1.1 404 Not Found Date: Tue, 16 Apr 2013 14:59:20 GMT Server: Apache/2.2.22 (Unix) DAV/2 PHP/5.3.15 with Suhosin-Patch mod_ssl/2.2.22 OpenSSL/0.9.8r Content-Length: 209 Keep-Alive: timeout=5, max=99 Connection: Keep-Alive Content-Type: text/html; charset=iso-8859-1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>404 Not Found</title> </head><body> <h1>Not Found</h1> <p>The requested URL /favicon.ico was not found on this server.</p> </body></html> ^CEXIT SIGNAL:2