Proxy Server源碼及分析(TCP Proxy源碼 Socket實現端口映射)

版權聲明:本文爲博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:https://blog.csdn.net/u014530704/article/details/78804163
本文主要講代理服務器源碼,是一位叫Carl Harris大神寫的,很是簡單易懂,把代理服務器(Proxy Server)本質徹底體現出來。相信讀懂了這段代碼,之後想搞定http代理等其餘類型的代理服務器也能行。在附錄中會貼出proxy所有源碼,僅供學習使用。git

1、代理服務器的定義
代理服務器(Proxy Server)是一種重要的服務器安全功能,它的工做主要在開放系統互聯(OSI)模型的會話層,從而起到防火牆的做用。代理(英語:Proxy),也稱網絡代理,是一種特殊的網絡服務,容許一個網絡終端(通常爲客戶端)經過這個服務與另外一個網絡終端(通常爲服務器)進行非直接的鏈接。一些網關、路由器等網絡設備具有網絡代理功能。通常認爲代理服務有利於保障網絡終端的隱私或安全,防止攻擊。
以上截取了網上Proxy Server的定義。看起來晦澀難懂。簡單來講,代理服務器就是起到一個轉發功能。好比,你在本機A想要訪問國外的服務器C,你本機沒權限訪問C,須要經過向服務器B發送數據,B再把你的數據發送給C,C返回數據也是先把數據交給了B,而後B再轉交給你。這裏B服務器別名爲代理服務器(Proxy Server)。等會分析到proxy源碼,就更加清楚了。api

2、proxy源碼分析
如下是proxy的主程序。安全

int main(int argc, char **argv)
{
    int clilen;
    int childpid;
    int sockfd, newsockfd;
    struct sockaddr_in servaddr, cliaddr;
    parse_args(argc, argv);//prepare an address struct to listen for connect
    bzero((char*)&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = proxy_port;
    //get asocket ..
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      fputs("failed to crate server socket\r\n", stderr);
      exit(1);
    }
    // and bind our address and port to it
    if(bind(sockfd, (struct sockaddr_in*)&servaddr, sizeof(servaddr)) < 0)
    {
      fputs("failed to bind server socket to specified/r/n", stderr);
      exit(1);
    }
    // get ready to accept with at most 5 clients waitting to connect
    listen(sockfd, 5);
    // turn ourselves into daemon
    daemonize(sockfd);
    //fall into a loop to accept new connections and spawn children
    while(1)
    {
        //accept the next connection
        clilen = sizeof(cliaddr);
        newsockfd = accept(sockfd, (struct sockaddr*)&cliaddr, &clilen);
        if(newsockfd <0 && errno == EINTR)
          continue;
        //a signal might interrupt our accept call
        else if(newsockfd < 0)
          errorout("failed to accept connection");//sth quiet amiss--kill server
        //fork a child to handle this connection
        if((childpid = fork()) == 0)
        {
            close(sockfd);// inherit
            do_proxy(newsockfd);
            exit(0);
        }
        // if fork falied the connection is silently dropped --oops
        close(newsockfd);
    }
    return 0;
}

 


從上面的程序看出,先是用函數parse_args(argc, argv) 解析得到用戶輸入的信息,包括想要綁定的代理服務器端口號、遠程主機的host name 和 遠程主機的服務號。獲取以後設置到全局變量。接着用socket函數建立tcp 套接字,用bind函數綁定剛纔獲取到的端口號,用listen監聽有沒有客戶端鏈接到代理服務器。而後,程序調用daemonize(sockfd)函數,這個函數的做用是建立一個守護進程。程序往下是在while循環裏面調用accept函數阻塞,若是發現有客戶端鏈接,就返回一個套接字,並fork出子進程處理這個套接字發過來的請求。do_proxy函數就是專門來幹這事的。
do_proxy函數代碼以下:服務器

void do_proxy (int usersockfd)
{
    int isosockfd;
    fd_set rdfdset;
    int connstat;
    int iolen;
    char buf[2048];
    /* open a socket to connect to the isolated host */
    if ((isosockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
      errorout("failed to create socket to host");
    /* attempt a connection */
    connstat = connect(isosockfd,(struct sockaddr *) &hostaddr, sizeof(hostaddr));
    switch (connstat)
    {
        case 0:
            break;
        case ETIMEDOUT:
        case ECONNREFUSED:
        case ENETUNREACH:
            strcpy(buf,sys_myerrlist[errno]);
            strcat(buf,"\r\n"); 
            write(usersockfd, buf, strlen(buf));
            close(usersockfd);
            exit(1);
                    /* die peacefully if we can't establish a connection */
            break;
        default:
                errorout("failed to connect to host");
    }
/* now we're connected, serve fall into the data echo loop */
    while (1)
    {
        /* Select for readability on either of our two sockets */
        FD_ZERO(&rdfdset);
        FD_SET(usersockfd,&rdfdset);
        FD_SET(isosockfd,&rdfdset);
        if (select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL) < 0)
        errorout("select failed");
        /* is the client sending data? */
        if (FD_ISSET(usersockfd,&rdfdset)) 
        {
            if ((iolen = read(usersockfd,buf,sizeof(buf))) <= 0)
              break; /* zero length means the client disconnected */
            write(isosockfd,buf,iolen);
            /* copy to host -- blocking semantics */
        }
        /* is the host sending data? */
        if(FD_ISSET(isosockfd,&rdfdset)) 
        {
            if((iolen = read(isosockfd,buf,sizeof(buf))) <= 0)
              break; /* zero length means the host disconnected */
            write(usersockfd,buf,iolen);
            /* copy to client -- blocking semantics */
        }
    }
    /* we're done with the sockets */
    close(isosockfd);
    close(usersockfd);
}

 


do_proxy函數是整個程序的核心,代理服務器的工做原理就是從這裏體現出來的。函數有一個參數,是客戶端鏈接好的套接字,也就是accept函數的返回值,可用於和客戶端發送或者接收消息。在這個函數中,會再用socket函數建立一個新的套接字,這個套接字是用於和遠程服務器鏈接的。在以前parse_args函數中,咱們獲取過遠程服務器的主機名和服務端口號。接着調用connect函數向遠程服務器發起鏈接請求。從這能夠看出,代理服務器既是服務器,同時也是遠程服務器的客戶端。當兩邊都創建鏈接好之後,調用select函數,把兩邊的套接字描述符都加入監視。select函數可參考TCP socket select用法分析。若是發現是客戶端A有發送信息請求訪問遠程服務器C時,本代理服務器就讀取A的套接字描述符,把信息存放到buf中,而後再把buf中的數據寫到C。反過來,當C有數據響應要發給A時,一樣也是先發送給B,而後B再發送給A。網絡

關於代碼,這裏再講一下守護進程,函數以下所示:socket

void daemonize(int servfd)
{
    int childpid, fd, fdtablesize;
    // ignore terminal I/O,stop signals
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
    /* fork to put us in the background (whether or not the user
      specified '&' on the command line */
    if((childpid=fork()) < 0)
    {
        fputs("failed to fork first child/r/n",stderr);
        exit(1);
    }
    else if(childpid >0)
      exit(0);// terminate parent, continue child
    //dissociate from process group
    if (setpgrp(0,getpid()) < 0) 
    {
        fputs("failed to become process group leader/r/n",stderr);
        exit(1);
    }
    /* lose controlling terminal */
    if ((fd = open("/dev/tty", O_RDWR)) >= 0)
    {
        ioctl(fd,TIOCNOTTY,NULL);
        close(fd);
    }
    /* close any open file descriptors */
    for(fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++)
      if (fd != servfd)
        close(fd);
    /* set working directory to allow filesystems to be unmounted */
    chdir("/");
    /* clear the inherited umask */
    umask(0);
    /* setup zombie prevention */
    signal(SIGCLD,(sigfunc *)reap_status);
}

 


從上面能夠看出,程序調用signal忽略一些信號,而後父進程fork出子進程,父進程退出,子進程調用setpgrp(0,getpid()),與進程組分離。接着使子進程與終端脫離,關閉不相關的文件描述符,調用umask將文件模式建立屏蔽字設置爲0,最後處理信號SIGCLD。全部這些過程都是爲了建立一個守護進程。讓程序可以在後臺運行,而且不受終端控制。tcp

3、proxy完整源碼ide

/****************************************************************************    
  program:      proxyd                                                           
  module:       proxyd.c                                                         
  summary:      provides proxy tcp service for a host on an isolated network.    
  programmer:   Carl Harris (ceharris@vt.edu)                                    
  date:         22 Feb 94                                                        
  description:                                                                   
        This code implements a daemon process which listens for tcp connec-      
        tions on a specified port number.  When a connection is established,     
        a child is forked to handle the new client.  The child then estab-       
        lishes a tcp connection to a port on the isolated host.  The child       
        then falls into a loop in which it writes data to the isolated host      
        for the client and vice-versa.  Once a child has been forked, the        
        parent resumes listening for additional connections.                     
        The name of the isolated host and the port to serve as proxy for,        
        as well as the port number the server listen on are specified as         
        command line arguments.                                                  
 ****************************************************************************/  
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
//#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>

#define TCP_PROTO "tcp"

int proxy_port; //port to listen for proxy connections on
struct sockaddr_in hostaddr; //host addr assembled form gethostbyname()
extern int errno; //define by libc.a
 char *sys_myerrlist[];

void parse_args(int argc, char **argv);
void daemonize(int servfd);
void do_proxy(int usersockfd);
void reap_status(void);
void errout(char *msg);

typedef void sigfunc(int );//sigfunc能夠聲明一個函數類型
/*description:   Main level driver. After daemonizing the process,
*  a socket is opened to listen for connections on the proxy port, 
*  connections are accepted and children are spawned to handle each 
*  new connection.
*/

int main(int argc, char **argv)
{
    int clilen;
    int childpid;
    int sockfd, newsockfd;
    struct sockaddr_in servaddr, cliaddr;
    parse_args(argc, argv);//prepare an address struct to listen for connect
    bzero((char*)&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = proxy_port;
    //get asocket ..
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      fputs("failed to crate server socket\r\n", stderr);
      exit(1);
    }
    // and bind our address and port to it
    if(bind(sockfd, (struct sockaddr_in*)&servaddr, sizeof(servaddr)) < 0)
    {
      fputs("failed to bind server socket to specified/r/n", stderr);
      exit(1);
    }
    // get ready to accept with at most 5 clients waitting to connect
    listen(sockfd, 5);
    // turn ourselves into daemon
    daemonize(sockfd);
    //fall into a loop to accept new connections and spawn children
    while(1)
    {
        //accept the next connection
        clilen = sizeof(cliaddr);
        newsockfd = accept(sockfd, (struct sockaddr*)&cliaddr, &clilen);
        if(newsockfd <0 && errno == EINTR)
          continue;
        //a signal might interrupt our accept call
        else if(newsockfd < 0)
          errorout("failed to accept connection");//sth quiet amiss--kill server
        //fork a child to handle this connection
        if((childpid = fork()) == 0)
        {
            close(sockfd);// inherit
            do_proxy(newsockfd);
            exit(0);
        }
        // if fork falied the connection is silently dropped --oops
        close(newsockfd);
    }
    return 0;
}

//parse the command line args
void parse_args(int argc, char **argv)
{
    int i;
    struct hostent *hostp;//host entry
    struct servent *servp;
    unsigned long inaddr;
    struct {
        char proxy_port[16];
        char isolate_host[64];
        char service_name[32];
    }pargs;
    if(argc < 4)
    {
      printf("usage:%s<proxy-port> <host> <service-name|port-number> \r\n", argv[0]);
      exit(1);
    }
    strcpy(pargs.proxy_port, argv[1]);
    strcpy(pargs.isolate_host, argv[2]);
    strcpy(pargs.service_name, argv[3]);
    for(i=0; i<strlen(pargs.proxy_port); i++)
      if(!isdigit(*(pargs.proxy_port+i)))
        break;
    if(i ==  strlen(pargs.proxy_port))
      proxy_port = htons(atoi(pargs.proxy_port));//port short
    else
    {
      printf("invalid proxy port\r\n", pargs.proxy_port);
      exit(0);
    }
    bzero(&hostaddr, sizeof(hostaddr));
    hostaddr.sin_family = AF_INET;
    if((inaddr= inet_addr(pargs.isolate_host))!= INADDR_NONE)
      bcopy(&inaddr, &hostaddr.sin_addr, sizeof(inaddr));
    else if((hostp == gethostbyname(pargs.isolate_host)) != NULL)
      bcopy(hostp->h_addr,&hostaddr.sin_addr,hostp->h_length);
    else
    {
        printf("%s unknow host \r\n", pargs.isolate_host);
        exit(1);
    }
    if((servp = getservbyname(pargs.service_name, TCP_PROTO)) != NULL)
      hostaddr.sin_port = servp->s_port;
    else if(atoi(pargs.service_name) >0)
      hostaddr.sin_port = htons(atoi(pargs.service_name));
    else
    {
        printf("%s invalid unknow service or port no.\r\n", pargs.service_name);
        exit(1);
    }
}

/*detach the server process from the current context, creating a pristine,
* predictable environment in which it will */

void daemonize(int servfd)
{
    int childpid, fd, fdtablesize;
    // ignore terminal I/O,stop signals
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
    /* fork to put us in the background (whether or not the user
      specified '&' on the command line */
    if((childpid=fork()) < 0)
    {
        fputs("failed to fork first child/r/n",stderr);
        exit(1);
    }
    else if(childpid >0)
      exit(0);// terminate parent, continue child
    //dissociate from process group
    if (setpgrp(0,getpid()) < 0) 
    {
        fputs("failed to become process group leader/r/n",stderr);
        exit(1);
    }
    /* lose controlling terminal */
    if ((fd = open("/dev/tty", O_RDWR)) >= 0)
    {
        ioctl(fd,TIOCNOTTY,NULL);
        close(fd);
    }
    /* close any open file descriptors */
    for(fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++)
      if (fd != servfd)
        close(fd);
    /* set working directory to allow filesystems to be unmounted */
    chdir("/");
    /* clear the inherited umask */
    umask(0);
    /* setup zombie prevention */
    signal(SIGCLD,(sigfunc *)reap_status);
}

//handle a SIGCLD signal by reaping the exit status of the perished child, 
//and discarding it.
void reap_status()
{
    int pid;
    union wait status;
    while ((pid = wait3(&status,WNOHANG,NULL)) > 0)
      ; /* loop while there are more dead children */
}

//does the actual work of virtually connecting a client to the telnet
//service on the isolated host.

void do_proxy (int usersockfd)
{
    int isosockfd;
    fd_set rdfdset;
    int connstat;
    int iolen;
    char buf[2048];
    /* open a socket to connect to the isolated host */
    if ((isosockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
      errorout("failed to create socket to host");
    /* attempt a connection */
    connstat = connect(isosockfd,(struct sockaddr *) &hostaddr, sizeof(hostaddr));
    switch (connstat)
    {
        case 0:
            break;
        case ETIMEDOUT:
        case ECONNREFUSED:
        case ENETUNREACH:
            strcpy(buf,sys_myerrlist[errno]);
            strcat(buf,"\r\n"); 
            write(usersockfd, buf, strlen(buf));
            close(usersockfd);
            exit(1);
                    /* die peacefully if we can't establish a connection */
            break;
        default:
                errorout("failed to connect to host");
    }
/* now we're connected, serve fall into the data echo loop */
    while (1)
    {
        /* Select for readability on either of our two sockets */
        FD_ZERO(&rdfdset);
        FD_SET(usersockfd,&rdfdset);
        FD_SET(isosockfd,&rdfdset);
        if (select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL) < 0)
        errorout("select failed");
        /* is the client sending data? */
        if (FD_ISSET(usersockfd,&rdfdset)) 
        {
            if ((iolen = read(usersockfd,buf,sizeof(buf))) <= 0)
              break; /* zero length means the client disconnected */
            write(isosockfd,buf,iolen);
            /* copy to host -- blocking semantics */
        }
        /* is the host sending data? */
        if(FD_ISSET(isosockfd,&rdfdset)) 
        {
            if((iolen = read(isosockfd,buf,sizeof(buf))) <= 0)
              break; /* zero length means the host disconnected */
            write(usersockfd,buf,iolen);
            /* copy to client -- blocking semantics */
        }
    }
    /* we're done with the sockets */
    close(isosockfd);
    close(usersockfd);
}

//displays an error message on the console and kills the current process.
void errorout (char *msg)
{
    FILE *console;
    console = fopen("/dev/console","a");
    fprintf(console,"proxyd: %s\r\n",msg);
    fclose(console);
    exit(1);
}
相關文章
相關標籤/搜索