PPTP源码分析

1. 软件版本:
      pptpd-1.4.0.tar.gz

2.源码框架 :


3.源码详解:

3.1 pptpctrl详解 :
    pptpctrl是一个可独立调用的二进制可执行文件。
    pptpctrl是pptp服务的管理程序,其通常被pptp程序调用,而不是提供给用户使用,其具体使用方法可查看man手册man pptpctrl.

命令格式:
    pptpctrl  pptp-debug-flag  \
                no-ipparam-flag  \
                ppp-options-value  \
                ppp-speed-value  \
                ppp-local-ip-value \
                ppp-remote-ip-value \
                [ pptp-call-id ] \
                [ ppp-binary ] \
                [ pptp-logwtmp ]

       该控制程序包含6个必选参数和3个可选参数,这些参数都是非常规参数,因为其通常不被用户调用。
         参数列表中,Flag参数包含两个可选值0或1;value参数由0或1后跟一个value构成。必选参数和可选
    参数的列表必须严格遵循命令参数表中的顺序。

  pptp-debug-flag: 设置为1时使能pptpctrl输出调试信息达到syslog中。这个选项对ppp的debug选项没有效果。
  no-ipparam-flag: 设置为1时,该程序不会传输客户端的IP地址给PPPD而使用ipparam选项
  ppp-options-value: 用于指定PPPD配置选项文件,等价于PPPD中的file选项。
  ppp-speed-value:用于为PPPD指定选项speed, NOTE: 在linux中该参数无效。
  ppp-local-ip-value: 若设置该项,则PPPD以该选项值作为localip,否则PPPD根据options配置文件中的选项决定。
  ppp-remote-ip-value:若设置该项,则PPPD以该选项值作为remoteip,否则PPPD根据options配置文件中的选项决定。
                               该选项值通常为客户端的ethernet address或chap-secrets中的地址。若ppp-local-ip-value
                               选项开启时,必须配置该选项。
  pptp-call-id:  若设置该选项,则指定用户pptp session的会话ID;如果省略此选项, call identificatio为0.通常,此ID由
                     pptpd分配并将其传递给pptpctrl.
  ppp-binary:  若指定该选项,则使用对应的ppp程序,否则使用默认的ppp程序。
  ppp-logwtmp: 该选项用户指定wtmp在用户上线或下线的时候是否更新。



  源码Part1: 参数列表解析
File: pptpctrl.c                 
-------------------------------------------------------------------------
#define clientSocket 0 

int pptpctrl_debug = 0;
static int noipparam;
static char pppdxfig[256];
static char speed[32];
uint16_t unique_call_id = 0xFFFF;
static char *ppp_binary = PPP_BINARY;
static int pptp_logwtmp;

#define GETARG_INT(X) \              #NOTE:从命令行获取参数,初始化对应整数变量
        X = atoi(argv[arg++])

#define GETARG_STRING(X) \   #NOTE:从命令行获取参数,初始化对应字符串变量
        X = strdup(argv[arg++])

int main(int argc, char **argv)
{
        char pppLocal[16];              /* local IP to pass to pppd */
        char pppRemote[16];             /* remote IP address to pass to pppd */
        struct sockaddr_in addr;        /* client address */
        socklen_t addrlen;
        int arg = 1;
        int flags;
        struct in_addr inetaddrs[2];
        char *pppaddrs[2] = { pppLocal, pppRemote };

        gargc = argc;
        gargv = argv;

        /* fail if argument count invalid */
        if (argc < 7) {
                fprintf(stderr, "pptpctrl: insufficient arguments, see man pptpctrl\n");
                exit(2);
        }

        /* open a connection to the syslog daemon */
        openlog("pptpd", LOG_PID, PPTP_FACILITY);

        /* autoreap if supported */
        signal(SIGCHLD, SIG_IGN);

        /* note: update pptpctrl.8 if the argument list format is changed */
        GETARG_INT(pptpctrl_debug);
        GETARG_INT(noipparam);
#ifdef VRF                #NOTE: VRF控制选项,即pptpctrl支持VRF参数配置。
        GETARG_STRING(vrf);
#endif
        GETARG_VALUE(pppdxfig);
        GETARG_VALUE(speed);
        GETARG_VALUE(pppLocal);
        GETARG_VALUE(pppRemote);
        if (arg < argc) GETARG_INT(unique_call_id);
        if (arg < argc) GETARG_STRING(ppp_binary);
        if (arg < argc) GETARG_INT(pptp_logwtmp);
(#NOTE: 上述过程,依次获取命令行参数来初始化对应的控制变量:
                pptp-debug-flag     -->   pptpctrl_debug
                no-ipparam-flag     -->   noipparam
                ppp-options-value   -->   pppdxfig
                ppp-speed-value     -->  speed
                ppp-local-ip-value   -->   pppLocal
                ppp-remote-ip-value  -->  pppRemote
                [ pptp-call-id ]          -->  unique_call_id
                [ ppp-binary ]          -->   ppp_binary
                [ pptp-logwtmp ]      -->   pptp_logwtmp
)
#ifdef VRF
        if (!*vrf) {
                free(vrf);
                vrf = NULL;
        }
#endif
        if (pptpctrl_debug) {   #NOTE:根据参数列表,输出参数信息
#ifdef VRF
                syslog(LOG_DEBUG, "CTRL: VRF used = %s", vrf ? vrf : "NONE");
#endif
                if (*pppLocal)
                        syslog(LOG_DEBUG, "CTRL: local address = %s", pppLocal);
                if (*pppRemote)
                        syslog(LOG_DEBUG, "CTRL: remote address = %s", pppRemote);
                if (*speed)
                        syslog(LOG_DEBUG, "CTRL: pppd speed = %s", speed);
                if (*pppdxfig)
                        syslog(LOG_DEBUG, "CTRL: pppd options file = %s", pppdxfig);
        }


        addrlen = sizeof(addr);
        if (getsockname(clientSocket, (struct sockaddr *) &addr, &addrlen) != 0) {
                syslog(LOG_ERR, "CTRL: getsockname() failed");
                syslog_perror("getsockname");
                close(clientSocket);
                bail(0);        /* NORETURN */
        }
        inetaddrs[0] = addr.sin_addr;

        addrlen = sizeof(addr);
        if (getpeername(clientSocket, (struct sockaddr *) &addr, &addrlen) != 0) {
                syslog(LOG_ERR, "CTRL: getpeername() failed");
                syslog_perror("getpeername");
                close(clientSocket);
                bail(0);        /* NORETURN */
        }
        inetaddrs[1] = addr.sin_addr;
(#NOTE: 
    这里通过调用getsocketname()和getpeername()获取套接字两端的IP地址,但这里的套接字是宏定义
且其值为0,通过打印调试可以得到套接字两端的地址。那为何这里为0呢?
    这是因为在pptp_manager()函数中调用的connectCall()函数中,对客户端的链接套接字调用了复制函数
dup(clientSocket,0).

)
        /* Set non-blocking */
        if ((flags = fcntl(clientSocket, F_GETFL, arg /* ignored */)) == -1 ||
            fcntl(clientSocket, F_SETFL, flags|OUR_NB_MODE) == -1) {                    #NOTE:设置客户端链接套接字为非阻塞的形式
                syslog(LOG_ERR, "CTRL: Failed to set client socket non-blocking");
                syslog_perror("fcntl");
                close(clientSocket);
                bail(0);        /* NORETURN */
        }

    
        /* Fiddle with argv */
        my_setproctitle(gargc, gargv, "pptpd [%s]%20c",
            inet_ntoa(addr.sin_addr), ' ');

        /* be ready for a grisly death */
        sigpipe_create();
        sigpipe_assign(SIGTERM);
        NOTE_VALUE(PAC, call_id_pair, htons(-1));
        NOTE_VALUE(PNS, call_id_pair, htons(-1));

        syslog(LOG_INFO, "CTRL: Client %s control connection started", inet_ntoa(addr.sin_addr));
        pptp_handle_ctrl_connection(pppaddrs, inetaddrs);
        syslog(LOG_DEBUG, "CTRL: Reaping child PPP[%i]", pppfork);
        if (pppfork > 0)
                waitpid(pppfork, NULL, 0); 
        syslog(LOG_INFO, "CTRL: Client %s control connection finished", inet_ntoa(addr.sin_addr));

        bail(0);                /* NORETURN */
        return 1;               /* make gcc happy */
}


3.2 pptpmanager详解:
    pptpmanager是一个函数库,其为pptpd和pptpctrl之间衔接的桥梁。
    int pptp_manager(int argc, char **argv)为该衔接函数的入口,其具体功能如下:

File: pptpmanager.c
-------------------------------------------------------------------------
int pptp_manager(int argc, char **argv)
{
        int firstOpen = -1; 
        int ctrl_pid;
        socklen_t addrsize;

        int hostSocket;
        fd_set connSet;

        int rc, sig_fd;

        rc = sigpipe_create();  #NOTE: 建立管道用于处理信号
        if (rc < 0) {
                syslog(LOG_ERR, "MGR: unable to setup sigchld pipe!");
                syslog_perror("sigpipe_create");
                exit(-1);
        }
    
        sigpipe_assign(SIGCHLD);  #NOTE: 注册SIGCHLD函数处理函数
        sigpipe_assign(SIGTERM);  #NOTE: 注册SIGTERM函数处理函数
        sig_fd = sigpipe_fd();        #NOTE:获取信号管道的读端文件描述符

        /* openlog() not required, done in pptpd.c */

        syslog(LOG_INFO, "MGR: Manager process started");

        if (!pptp_delegate) {
                syslog(LOG_INFO, "MGR: Maximum of %d connections available", 
                       pptp_connections);
        }

        /* Connect the host socket and activate it for listening */
        if (createHostSocket(&hostSocket) < 0) {
                syslog(LOG_ERR, "MGR: Couldn't create host socket");
                syslog_perror("createHostSocket");
                exit(-1);
        }

        while (1) {
                int max_fd;
                FD_ZERO(&connSet);
                if (pptp_delegate) {
                        FD_SET(hostSocket, &connSet);
                } else {
                        firstOpen = slot_find_empty();  #NOTE:从slot池中取个空闲(pid=0)的对象
                        if (firstOpen == -1) {
                                syslog(LOG_ERR, "MGR: No free connection slots or IPs - no more clients can connect!");
                        } else {
                                FD_SET(hostSocket, &connSet);
                        }
                }
                max_fd = hostSocket;

                FD_SET(sig_fd, &connSet);
                if (max_fd < sig_fd) max_fd = sig_fd;

                while (1) {
                        if (select(max_fd + 1, &connSet, NULL, NULL, NULL) != -1) break;  #NOTE: 读到信号或请求时跳出循环
                        if (errno == EINTR) continue;
                        syslog(LOG_ERR, "MGR: Error with manager select()!");
                        syslog_perror("select");
                        exit(-1);
                }

                if (FD_ISSET(sig_fd, &connSet)) {       /* SIGCHLD */  #NOTE:探测是否读到信号
                        int signum = sigpipe_read();
                        if (signum == SIGCHLD)
                                sigchld_responder(signum);
                        else if (signum == SIGTERM)
                                return signum;
                }

                if (FD_ISSET(hostSocket, &connSet)) {   /* A call came! */   #NOTE:探测是否有链接请求
                        int clientSocket;
                        struct sockaddr_in client_addr;

                        /* Accept call and launch PPTPCTRL */
                        addrsize = sizeof(client_addr);
                        clientSocket = accept(hostSocket, (struct sockaddr *) &client_addr, &addrsize);

#if HAVE_LIBWRAP
                        if (clientSocket != -1) {
                                struct request_info r;
                                request_init(&r, RQ_DAEMON, "pptpd", RQ_FILE, clientSocket, NULL);
                                fromhost(&r);
                                if (!hosts_access(&r)) {
                                        /* send a permission denied message? this is a tcp wrapper
                                         * type deny so probably best to just drop it immediately like
                                         * this, as tcp wrappers usually do.
                                         */
                                        close(clientSocket);
                                        /* this would never be file descriptor 0, so use it as a error
                                         * value
                                         */
                                        clientSocket = 0;
                                }
                        }
#endif

                        if (clientSocket == -1) {
                                /* accept failed, but life goes on... */
                                syslog(LOG_ERR, "MGR: accept() failed");
                                syslog_perror("accept");
                        } else if (clientSocket != 0) {
                                fd_set rfds;
                                struct timeval tv;
                                struct pptp_header ph;   #NOTE: pptp封装协议头

                                /* TODO: this select below prevents
                                   other connections from being
                                   processed during the wait for the
                                   first data packet from the
                                   client. */

                                /*
                                 * DOS protection: get a peek at the first packet
                                 * and do some checks on it before we continue.
                                 * A 10 second timeout on the first packet seems reasonable
                                 * to me,  if anything looks sus,  throw it away.
                                 */

                                FD_ZERO(&rfds);
                                FD_SET(clientSocket, &rfds);
                                tv.tv_sec = pptp_stimeout;
                                tv.tv_usec = 0;
                                if (select(clientSocket + 1, &rfds, NULL, NULL, &tv) <= 0) {  #NOTE: 用于监听通信链接
                                        syslog(LOG_ERR, "MGR: dropped slow initial connection");
                                        close(clientSocket);
                                        continue;
                                }

                                if (recv(clientSocket, &ph, sizeof(ph), MSG_PEEK) !=    
                                                sizeof(ph)) {                          #NOTE: 读取pptp头部信息
                                        syslog(LOG_ERR, "MGR: dropped small initial connection");
                                        close(clientSocket);
                                        continue;
                                }

                                ph.length = ntohs(ph.length);
                                ph.pptp_type = ntohs(ph.pptp_type);
                                ph.magic = ntohl(ph.magic);
                                ph.ctrl_type = ntohs(ph.ctrl_type);

                                if (ph.length <= 0 || ph.length > PPTP_MAX_CTRL_PCKT_SIZE) {
                                        syslog(LOG_WARNING, "MGR: initial packet length %d outside "
                                                        "(0 - %d)",  ph.length, PPTP_MAX_CTRL_PCKT_SIZE);
                                        goto dos_exit;
                                }

                                if (ph.magic != PPTP_MAGIC_COOKIE) {
                                        syslog(LOG_WARNING, "MGR: initial packet bad magic");
                                        goto dos_exit;
                                }

                                if (ph.pptp_type != PPTP_CTRL_MESSAGE) {
                                        syslog(LOG_WARNING, "MGR: initial packet has bad type");
                                        goto dos_exit;
                                }

                                if (ph.ctrl_type != START_CTRL_CONN_RQST) {
                                        syslog(LOG_WARNING, "MGR: initial packet has bad ctrl type "
                                                        "0x%x", ph.ctrl_type);
                dos_exit:
                                        close(clientSocket);
                                        continue;
                                }

#ifndef HAVE_FORK
                                switch (ctrl_pid = vfork()) {
#else
                                switch (ctrl_pid = fork()) {      #NOTE:成功获取pptp后,为链接创建子进程
#endif
                                case -1:        /* error */
                                        syslog(LOG_ERR, "MGR: fork() failed launching " PPTP_CTRL_BIN);
                                        close(clientSocket);
                                        break;

                                case 0: /* child */
                                        close(hostSocket);
                                        if (pptp_debug)
                                                syslog(LOG_DEBUG, "MGR: Launching " PPTP_CTRL_BIN " to handle client");
                                        connectCall(clientSocket, !pptp_delegate ? firstOpen : 0); #NOTE: 调用pptpctrl程序,将管理任务交接给pptpctrl来完成.
                                     _exit(1);
                                        /* NORETURN */
                                default:        /* parent */
                                        close(clientSocket);
                                        unique_call_id += MAX_CALLS_PER_TCP_LINK;
                                        if (!pptp_delegate)
                                                slot_set_pid(firstOpen, ctrl_pid);
                                        break;
                                }
                        }
                }               /* FD_ISSET(hostSocket, &connSet) */
        }                       /* while (1) */
}                               /* pptp_manager() */



  createHostSocket() 函数原型如下:
static int createHostSocket(int *hostSocket)
{
        int opt = 1;
        struct sockaddr_in address;
#ifdef HAVE_GETSERVBYNAME
        struct servent *serv;
#endif

        /* create the master socket and check it worked */
        if ((*hostSocket = vrf_socket(vrf, AF_INET, SOCK_STREAM, 0)) <= 0)
                return -1; 
(#NOTE: 
  #define vrf_socket(vrf, dom, typ, prot) socket(dom, typ, prot)  //pptpctrl.h
)
        /* set master socket to allow daemon to be restarted with connections active  */
        if (setsockopt(*hostSocket, SOL_SOCKET, SO_REUSEADDR,          #NOTE:设置地址重用
                       (char *) &opt, sizeof(opt)) < 0)
                return -2; 

        /* set up socket */
        memset(&address, 0, sizeof(address));
        address.sin_family = AF_INET;
        if(bindaddr)    #NOTE:从此处可以看出,若不指定地址,则pptpd监听INADDR_ANY,即0.0.0.0
                address.sin_addr.s_addr = inet_addr(bindaddr);
        else
                address.sin_addr.s_addr = INADDR_ANY;
#ifdef HAVE_GETSERVBYNAME
        if ((serv = getservbyname("pptp", "tcp")) != NULL) {  #NOTE:获取知名服务/etc/services
                address.sin_port = serv->s_port;
        } else
#endif
                address.sin_port = htons(PPTP_PORT);

        /* bind the socket to the pptp port */
        if (bind(*hostSocket, (struct sockaddr *) &address, sizeof(address)) < 0)
                return -3; 

        /* minimal backlog to avoid DoS */
        if (listen(*hostSocket, 3) < 0)
                return -4; 

        return 0;
}



connectCall()函数原型如下:
File: pptpmanager.c
-------------------------------------------------------------------------
static void connectCall(int clientSocket, int clientNumber)
{

#define NUM2ARRAY(array, num) snprintf(array, sizeof(array), "%d", num)
    
        char *ctrl_argv[16];    /* arguments for launching 'pptpctrl' binary */

        int pptpctrl_argc = 0;  /* count the number of arguments sent to pptpctrl */

        /* lame strings to hold passed args. */
        char ctrl_debug[2];
        char ctrl_noipparam[2];
        char pppdoptfile_argv[2];
        char speedgiven_argv[2];
        extern char **environ;

        char callid_argv[16];

        /*  
         * Launch the CTRL manager binary; we send it some information such as
         * speed and option file on the command line.
         */

        ctrl_argv[pptpctrl_argc++] = PPTP_CTRL_BIN "                                ";

        /* Pass socket as stdin */
        if (clientSocket != 0) {   #NOTE: 注意,此处将clineSocket复制为0,pptpctrl中利用文件描述符0调用getsockname()和getpeername()获取两端地址。
                dup2(clientSocket, 0); 
                close(clientSocket);
        }

        /* get argv set up */
        NUM2ARRAY(ctrl_debug, pptp_debug ? 1 : 0);    #NOTE: pptp-debug-flag参数。
        ctrl_debug[1] = '\0';
        ctrl_argv[pptpctrl_argc++] = ctrl_debug;

        NUM2ARRAY(ctrl_noipparam, pptp_noipparam ? 1 : 0);   #NOTE:no-ipparam-flag参数
        ctrl_noipparam[1] = '\0';
        ctrl_argv[pptpctrl_argc++] = ctrl_noipparam;

#ifdef VRF
        ctrl_argv[pptpctrl_argc++] = vrf ? vrf : "";   #NOTE:VRF参数
#endif

        /* optionfile = TRUE or FALSE; so the CTRL manager knows whether to load a non-standard options file */
        NUM2ARRAY(pppdoptfile_argv, pppdoptstr ? 1 : 0);  #NOTE:ppp-options-value配置文件参数
        pppdoptfile_argv[1] = '\0';
        ctrl_argv[pptpctrl_argc++] = pppdoptfile_argv;
        if (pppdoptstr) {
                /* send the option filename so the CTRL manager can launch pppd with this alternate file */
                ctrl_argv[pptpctrl_argc++] = pppdoptstr;
        }
        /* tell the ctrl manager whether we were given a speed */
        NUM2ARRAY(speedgiven_argv, speedstr ? 1 : 0);   #NOTE:ppp-speed-value 速度参数
        speedgiven_argv[1] = '\0';
        ctrl_argv[pptpctrl_argc++] = speedgiven_argv;
        if (speedstr) {
                /* send the CTRL manager the speed of the connection so it can fire pppd at that speed */
                ctrl_argv[pptpctrl_argc++] = speedstr;
        }
        if (pptp_delegate) {                 #NOTE:通过代理标识,设置ppp-local-ip-value和ppp-remote-ip-value参数
                /* no local or remote address to specify */
                ctrl_argv[pptpctrl_argc++] = "0";
                ctrl_argv[pptpctrl_argc++] = "0";
        } else {
                /* specify local & remote addresses for this call */
                ctrl_argv[pptpctrl_argc++] = "1";
                ctrl_argv[pptpctrl_argc++] = slot_get_local(clientNumber);
                ctrl_argv[pptpctrl_argc++] = "1";
                ctrl_argv[pptpctrl_argc++] = slot_get_remote(clientNumber);
        }

        /* our call id to be included in GRE packets the client
         * will send to us */
        NUM2ARRAY(callid_argv, unique_call_id);    #NOTE:  [ pptp-call-id ]可选参数
        ctrl_argv[pptpctrl_argc++] = callid_argv;

        /* pass path to ppp binary */
        ctrl_argv[pptpctrl_argc++] = ppp_binary;  #NOTE: ppp可执行程序参数 ppp-binary

        /* pass logwtmp flag */
        ctrl_argv[pptpctrl_argc++] = pptp_logwtmp ? "1" : "0";  #NOTE:logwtmp参数pptp-logwtmp

        /* note: update pptpctrl.8 if the argument list format is changed */

        /* terminate argv array with a NULL */
        ctrl_argv[pptpctrl_argc] = NULL;
        pptpctrl_argc++;

        /* ok, args are setup: invoke the call handler */
        execve(PPTP_CTRL_BIN, ctrl_argv, environ);   
(#NOTE:
  通过execve函数调用pptpctrl程序,交接该链接的管理权。

   注意,pptp_manager()函数中调用的sigpipe_create(),创建的信号处理管道由于使用了fcntl(fd,F_SETFD,FD_CLOEXEC);对应的文件描述符,在该函数调用后将不可用
)  
    syslog(LOG_ERR, "MGR: Failed to exec " PPTP_CTRL_BIN "!");
        _exit(1);
}

你可能感兴趣的:(PPTP)