Chinaunix首页 | 论坛 | 博客
  • 博客访问: 234170
  • 博文数量: 54
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 431
  • 用 户 组: 普通用户
  • 注册时间: 2014-07-26 09:36
文章分类

全部博文(54)

分类: LINUX

2016-05-16 13:23:29

1. 软件版本:
    ppp-2.4.7.tar.gz

2. 源码框架:

3. 源码详解:
 
说明:此次分析暂时不考虑ipv6相关的部分。
   
pppd函数入口
File: pppd/main.c
----------------------------------------------------------------------------------------------
int main(argc, argv)
    int argc;  char *argv[];                       #NOTE: 注意这种定义命令行参数的形式
{
    int i, t;
    char *p;
    struct passwd *pw;
    struct protent *protp;
    char numbuf[16];

    strlcpy(path_ipup, _PATH_IPUP, sizeof(path_ipup));              
    strlcpy(path_ipdown, _PATH_IPDOWN, sizeof(path_ipdown));      
    strlcpy(path_ipv6up, _PATH_IPV6UP, sizeof(path_ipv6up));         
    strlcpy(path_ipv6down, _PATH_IPV6DOWN, sizeof(path_ipv6down));      
 # File:  pathnames.h
 # #ifndef _ROOT_PATH   #NOTE:该变量通常未定义
 # #define _ROOT_PATH
 # #endif
 # #define _PATH_IPUP        _ROOT_PATH "/etc/ppp/ip-up"     #NOTE: 端口up时调用的脚本
 # #define _PATH_IPDOWN    _ROOT_PATH "/etc/ppp/ip-down" #NOTE: 端口down时调用的脚本

    link_stats_valid = 0;        #NOTE: 记录pppd链接状态的全局变量
    new_phase(PHASE_INITIALIZE); #NOTE: new_phase()函数为pppd状态机函数,此处定义状态机为“初始化阶段”

    script_env = NULL;

    /* Initialize syslog facilities */
    reopen_log(); #NOTE: 调用openlog()打开日志

    if (gethostname(hostname, MAXNAMELEN) < 0 ) { #NOTE:获取主机名,并存储在hostname数组中
        option_error("Couldn't get hostname: %m");
        exit(1);
    }    
    hostname[MAXNAMELEN-1] = 0;    

    /* make sure we don't create world or group writable files. */
    umask(umask(0777) | 022);

    uid = getuid();
    privileged = uid == 0;
    slprintf(numbuf, sizeof(numbuf), "%d", uid);
    script_setenv("ORIG_UID", numbuf, 0);

    ngroups = getgroups(NGROUPS_MAX, groups);

    /*   
     * Initialize magic number generator now so that protocols may
     * use magic numbers in initialization.
     */
    magic_init(); #NOTE: 随机数种子初始
   
 /*
     * Initialize each protocol.
     */
    for (i = 0; (protp = protocols[i]) != NULL; ++i)
        (*protp->init)(0);                    #NOTE: 遍历全局协议调用各个协议的初始化方法
                                                      #            详见"pppd协议集合"

 /*
  * Initialize the default channel.
  */
    tty_init();  #NOTE:tty相关初始化
    
    progname = *argv; 
        
    /*  
     * Parse, in order, the system options file, the user's options file,
     * and the command line arguments.
     */ 
    if (!options_from_file(_PATH_SYSOPTIONS, !privileged, 0, 1) 
        || !options_from_user()
        || !parse_args(argc-1, argv+1)) 
        exit(EXIT_OPTION_ERROR);
    devnam_fixed = 1;           /* can no longer change device name */

# 从此处可以看出,pppd运行的参数来源有三个:
#  options_from_file():  从配置文件中读取参数   #define _PATH_SYSOPTIONS _ROOT_PATH "/etc/ppp/options"
#       options_from_user():从用户文件中读取参数
#       parse_args(): 从命令行参数中读取参数
#    三个函数:返回1时依次解析下一项目,返回0时pppd执行结束
#    详见"pppd参数解析"

    /*
     * Work out the device name, if it hasn't already been specified,
     * and parse the tty's options file.
     */
    if (the_channel->process_extra_options)
        (*the_channel->process_extra_options)();

    if (debug)              #NOTE: 若开启了debug选项,则设置日志掩码
        setlogmask(LOG_UPTO(LOG_DEBUG));

    /*
     * Check that we are running as root.
     */
    if (geteuid() != 0) {   #NOTE: 检测root权限
        option_error("must be root to run %s, since it is not setuid-root",
                     argv[0]);
        exit(EXIT_NOT_ROOT);
    }


    if (!ppp_available()) { #NOTE:检测内核是否支持ppp,即内核选项是否配置
        option_error("%s", no_ppp_msg);
        exit(EXIT_NO_KERNEL_SUPPORT);
    }    

    /*
     * Check that the options given are valid and consistent.
     */
    check_options();   #NOTE:检测日志套接字
    if (!sys_check_options())  #NOTE:系统选项检测,如驱动风格
            exit(EXIT_OPTION_ERROR);
    auth_check_options();  #NOTE:认证选项检测
#ifdef HAVE_MULTILINK
    mp_check_options();
#endif
    for (i = 0; (protp = protocols[i]) != NULL; ++i)
        if (protp->check_options != NULL)
            (*protp->check_options)();  #NOTE:协议选项检测

    if (the_channel->check_options) 
        (*the_channel->check_options)(); #NOTE:通道选项检测
    
    if (dump_options || dryrun) {  #NOTE: dump_options选项用于输出命令字选项
        init_pr_log(NULL, LOG_INFO);
        print_options(pr_log, NULL);
        end_pr_log();
    }   

    if (dryrun)  #NOTE: 调试选项dryrun用于解析并输出options后退出程序
        die(0);

    /* Make sure fds 0, 1, 2 are open to somewhere. */
    fd_devnull = open(_PATH_DEVNULL, O_RDWR);   #NOTE:打开/dev/null设备
    if (fd_devnull < 0)
        fatal("Couldn't open %s: %m", _PATH_DEVNULL);

    while (fd_devnull <= 2) {  #NOTE: 将标准输入,输出,出错重定向到/dev/null
        i = dup(fd_devnull);
        if (i < 0)
            fatal("Critical shortage of file descriptors: dup failed: %m");
        fd_devnull = i;
    }


    /*
     * Initialize system-dependent stuff.
     */
    sys_init();   #NOTE:sock_fd数据包套接字初始化

#ifdef USE_TDB
    pppdb = tdb_open(_PATH_PPPDB, 0, 0, O_RDWR|O_CREAT, 0644);
    if (pppdb != NULL) {
        slprintf(db_key, sizeof(db_key), "pppd%d", getpid());
        update_db_entry();
    } else {
        warn("Warning: couldn't open ppp database %s", _PATH_PPPDB);
        if (multilink) {
            warn("Warning: disabling multilink");
            multilink = 0;
        }
    }
#endif

    /*
     * Detach ourselves from the terminal, if required,
     * and identify who is running us.
     */
    if (!nodetach && !updetach)  #NOTE: 脱离控制终端控制选项
        detach();

    p = getlogin();  #NOTE:获取登录用户名
    if (p == NULL) {
        pw = getpwuid(uid);
        if (pw != NULL && pw->pw_name != NULL)
            p = pw->pw_name;
        else
            p = "(unknown)";
    }
    syslog(LOG_NOTICE, "pppd %s started by %s, uid %d", VERSION, p, uid);
    script_setenv("PPPLOGNAME", p, 0); #NOTE:设置环境变量PPPLOGNAME为登录用户名

    if (devnam[0])  
        script_setenv("DEVICE", devnam, 1); #NOTE:设置环境变量DEVICE为设备名
    slprintf(numbuf, sizeof(numbuf), "%d", getpid());
    script_setenv("PPPD_PID", numbuf, 1);  #NOTE:设置环境变量PPPD_PID为当前pppd的pid


    setup_signals(); #NOTE: 信号注册

    create_linkpidfile(getpid()); #NOTE:根据linkname选项设置创建pid文件,若未指定则不创建

    waiting = 0;

    /*
     * If we're doing dial-on-demand, set up the interface now.
     */
    if (demand) {  #NOTE: demand选项控制用于设置接口属性,如修改接口名
        /*
         * Open the loopback channel and set it up to be the ppp interface.
         */
        fd_loop = open_ppp_loopback(); #NOTE: 打开ppp套接字
        set_ifunit(1);
        /*
         * Configure the interface and mark it up, etc.
         */
        demand_conf();
    }

    do_callback = 0;
    for (;;) {

        bundle_eof = 0;
        bundle_terminating = 0;
        listen_time = 0;
        need_holdoff = 1;
        devfd = -1;
        status = EXIT_OK;
        ++unsuccess;
        doing_callback = do_callback;
        do_callback = 0;


        if (demand && !doing_callback) { #NOTE: 定义demand选项后满足该条件
            /*
             * Don't do anything until we see some activity.
             */
            new_phase(PHASE_DORMANT); #NOTE: 更新状态为DORMANT
            demand_unblock();
            add_fd(fd_loop); #NOTE:将ppp套接字加入套接字集合
            for (;;) {
                handle_events(); #NOTE: 事件处理
                if (asked_to_quit) #NOTE: 接收到结束信号
                    break;
                if (get_loop_output()) #NOTE: 读取ppp输出包决定是否实际启动ppp接口
                    break;
            }

          remove_fd(fd_loop);
            if (asked_to_quit)
                break;


            /*
             * Now we want to bring up the link.
             */
            demand_block();
            info("Starting link");
        }


        check_time();  #NOTE:时间检测
        gettimeofday(&start_time, NULL);
        script_unsetenv("CONNECT_TIME"); #NOTE: 复位环境变量CONNECT_TIME
        script_unsetenv("BYTES_SENT");         #NOTE: 复位环境变量BYTES_SENT
        script_unsetenv("BYTES_RCVD");         #NOTE: 复位环境变量BYTES_RCVD

        lcp_open(0);            /* Start protocol */ #NOTE: 启动lcp协议
        start_link(0);   #NOTE: 启动PPP链接
        while (phase != PHASE_DEAD) { #NOTE: 当状态不是结束状态PHASE_DEAD时,进行事件循环处理
            handle_events();
            get_input();
            if (kill_link)
                lcp_close(0, "User request");
            if (asked_to_quit) {
                bundle_terminating = 1;
                if (phase == PHASE_MASTER)
                    mp_bundle_terminated();
            }

            if (open_ccp_flag) {
                if (phase == PHASE_NETWORK || phase == PHASE_RUNNING) {
                    ccp_fsm[0].flags = OPT_RESTART; /* clears OPT_SILENT */
                    (*ccp_protent.open)(0);
                }
            }
        }
        /* restore FSMs to original state */
        lcp_close(0, ""); #NOTE:当状态机为PHASE_DEAD时,关闭lcp链接

        if (!persist || asked_to_quit || (maxfail > 0 && unsuccess >= maxfail)) #NOTE:满足退出条件时,跳出循环
            break;

        if (demand)
            demand_discard();
        t = need_holdoff? holdoff: 0;
        if (holdoff_hook)
            t = (*holdoff_hook)();
        if (t > 0) {
            new_phase(PHASE_HOLDOFF);
            TIMEOUT(holdoff_end, NULL, t);
            do {
                handle_events();
                if (kill_link)
                    new_phase(PHASE_DORMANT); /* allow signal to end holdoff */
            } while (phase == PHASE_HOLDOFF);
            if (!persist)
                break;
        }
    }

    /* Wait for scripts to finish */
    reap_kids();
    if (n_children > 0) {
        if (child_wait > 0)
            TIMEOUT(childwait_end, NULL, child_wait);
        if (debug) {
            struct subprocess *chp;
            dbglog("Waiting for %d child processes...", n_children);
            for (chp = children; chp != NULL; chp = chp->next)
                dbglog("  script %s, pid %d", chp->prog, chp->pid);
        }
        while (n_children > 0 && !childwait_done) {
            handle_events();
            if (kill_link && !childwait_done)
                childwait_end(NULL);
        }
    }

    die(status);
    return 0;
}


pppd状态机制:
File:pppd/main.c
----------------------------------------------------------------------------------------------
int phase;
# 该状态机变量的取值定义如下:
# File: pppd.h
# #define PHASE_DEAD                 0
# #define PHASE_INITIALIZE          1
#define PHASE_SERIALCONN      2
# #define PHASE_DORMANT          3
# #define PHASE_ESTABLISH         4 
# #define PHASE_AUTHENTICATE  5
# #define PHASE_CALLBACK          6
#define PHASE_NETWORK          7 
# #define PHASE_RUNNING           8
# #define PHASE_TERMINATE        9
#define PHASE_DISCONNECT     10
# #define PHASE_HOLDOFF           11
# #define PHASE_MASTER             12

void new_phase(p)
    int p;
{
    phase = p;
    if (new_phase_hook)       #NOTE: 函数new_phase_hook()钩子函数默认为空,此处预留来为当状态发生改变时需要进行
        (*new_phase_hook)(p); #           处理的预留钩子函数
    notify(phasechange, p);
}


void notify(notif, val)
    struct notifier *notif;
    int val;
{
    struct notifier *np;

    while ((np = notif) != 0) {
        notif = np->next;
        (*np->func)(np->arg, val);
    }    
}

pppd协议集合
File: pppd.h
struct protent {
       u_short protocol;           /* PPP protocol number */
       void (*init) __P((int unit));  /* Initialization procedure */
       void (*input) __P((int unit, u_char *pkt, int len));    /* Process a received packet */
       void (*protrej) __P((int unit));    /* Process a received protocol-reject */
           void (*lowerup) __P((int unit));    /* Lower layer has come up */
           void (*lowerdown) __P((int unit));    /* Lower layer has gone down */
           void (*open) __P((int unit));    /* Open the protocol */
           void (*close) __P((int unit, char *reason));    /* Close the protocol */
           int  (*printpkt) __P((u_char *pkt, int len, printer_func printer,void *arg));    /* Print a packet in readable form */ 
           void (*datainput) __P((int unit, u_char *pkt, int len));    /* Process a received data packet */
           bool enabled_flag;          /* 0 iff protocol is disabled */
           char *name;                 /* Text name of protocol */
           char *data_name;            /* Text name of corresponding data protocol */
           option_t *options;          /* List of command-line options */
           void (*check_options) __P((void));    /* Check requested options, assign defaults */
           int  (*demand_conf) __P((int unit));    /* Configure interface for demand-dial */
           int  (*active_pkt) __P((u_char *pkt, int len));    /* Say whether to bring up link for this pkt */
};

 File: main.c
struct protent *protocols[] = {   #NOTE:从协议表可以看出,默认支持lcp,pap,chap,ipcp,ccp,ecp,eap协议
        &lcp_protent,
        &pap_protent,
        &chap_protent,
 #ifdef CBCP_SUPPORT
        &cbcp_protent,
 #endif
        &ipcp_protent,
#ifdef INET6 
       &ipv6cp_protent,
#endif
   &ccp_protent,
     &ecp_protent,
#ifdef IPX_CHANGE
       &ipxcp_protent,
#endif
#ifdef AT_CHANGE
       &atcp_protent,
#endif
       &eap_protent,
       NULL
};


pppd参数解析:
File: options.c                      
                         默认调用参数值:        1                  0            1

int  options_from_file(filename, must_exist, check_prot, priv)
    char *filename;   #NOTE: 参数文件地址
    int must_exist;    #NOTE: 参数文件是否必须存在
    int check_prot; 
    int priv;
{       
    FILE *f;
    int i, newline, ret, err;
    option_t *opt;
    int oldpriv, n;
    char *oldsource;
    uid_t euid;
    char *argv[MAXARGS];
    char args[MAXARGS][MAXWORDLEN];
    char cmd[MAXWORDLEN];

    euid = geteuid();
    if (check_prot && seteuid(getuid()) == -1) {     #NOTE:check_prot为0,不执行此分支
        option_error("unable to drop privileges to open %s: %m", filename);
        return 0;
    }
    f = fopen(filename, "r");   #NOTE:打开配置文件
    err = errno;
    if (check_prot && seteuid(euid) == -1) #NOTE: check_prot为0,不执行此分支
        fatal("unable to regain privileges");
    if (f == NULL) { #NOTE: 配置文件打开失败
        errno = err;
        if (!must_exist) {  #NOTE: 当必须存在标记为1时,不执行此分支,此解析函数返回0,pppd结束执行
             #     必须存在标记为0时,执行此分支,忽略找不到文件错误,返回1,pppd继续解析执行

            if (err != ENOENT && err != ENOTDIR)
                warn("Warning: can't open options file %s: %m", filename);
            return 1;
        }
        option_error("Can't open options file %s: %m", filename);
        return 0;
    }

    oldpriv = privileged_option;
    privileged_option = priv;
    oldsource = option_source;
    option_source = strdup(filename);
    if (option_source == NULL)
        option_source = "file";
    ret = 0;
    while (getword(f, cmd, &newline, filename)) { #NOTE:读取配置文件,并将命令字存储在cmd变量中
        opt = find_option(cmd);
        if (opt == NULL) {
            option_error("In file %s: unrecognized option '%s'",
                         filename, cmd);
            goto err;
        }
        n = n_arguments(opt);
        for (i = 0; i < n; ++i) {
            if (!getword(f, args[i], &newline, filename)) {
                option_error(
                        "In file %s: too few parameters for option '%s'",
                        filename, cmd);
                goto err;
            }
            argv[i] = args[i];
        }
        if (!process_option(opt, cmd, argv)) #NOTE:处理命令字选项
            goto err;
    }
    ret = 1;


err:
    fclose(f);
    privileged_option = oldpriv;
    option_source = oldsource;
    return ret;
}


static option_t * find_option(name)
    const char *name;
{        
        option_t *opt;   
        struct option_list *list;
        int i, dowild;

        for (dowild = 0; dowild <= 1; ++dowild) {
                for (opt = general_options; opt->name != NULL; ++opt)
                        if (match_option(name, opt, dowild))
                                return opt;
                for (opt = auth_options; opt->name != NULL; ++opt)
                        if (match_option(name, opt, dowild))
                                return opt;
                for (list = extra_options; list != NULL; list = list->next)
                        for (opt = list->options; opt->name != NULL; ++opt)
                                if (match_option(name, opt, dowild))
                                        return opt;
                for (opt = the_channel->options; opt->name != NULL; ++opt)
                        if (match_option(name, opt, dowild))
                                return opt;
                for (i = 0; protocols[i] != NULL; ++i)
                        if ((opt = protocols[i]->options) != NULL)
                                for (; opt->name != NULL; ++opt)
                                        if (match_option(name, opt, dowild))
                                                return opt;
        }
        return NULL;
}
先看看配置选项相关的基础数据结构:
File: pppd.h
enum opt_type {
        o_special_noarg = 0,  #NOTE:无参数
        o_special = 1,    #NOTE:特殊参数
        o_bool,        
        o_int,
        o_uint32,
        o_string,
        o_wild
};

typedef struct {
        char    *name;          #NOTE:参数选项名
        enum opt_type type; #NOTE:参数选项类型
        void    *addr;     #NOTE:参数选项变量地址
        char    *description;   #NOTE:参数选项描述
        unsigned int flags;       #NOTE:参数选项标识
        void    *addr2;
        int     upper_limit;
        int     lower_limit;
        const char *source;
        short int priority;
        short int winner;
} option_t;

#define OPT_VALUE       0xff    /* mask for presupplied value,flag的低8位为设置该标识后,该变量的值 */
#define OPT_HEX         0x100   /* int option is in hex */
#define OPT_NOARG       0x200   /* option doesn't take argument */
#define OPT_OR          0x400   /* for u32, OR in argument to value */
#define OPT_INC         0x400   /* for o_int, increment value */
#define OPT_A2OR        0x800   /* for o_bool, OR arg to *(u_char *)addr2 */
#define OPT_PRIV        0x1000  /* privileged option */
#define OPT_STATIC      0x2000  /* string option goes into static array */
#define OPT_NOINCR      0x2000  /* for o_int, value mustn't be increased */
#define OPT_LLIMIT      0x4000  /* check value against lower limit */
#define OPT_ULIMIT      0x8000  /* check value against upper limit */
#define OPT_LIMITS      (OPT_LLIMIT|OPT_ULIMIT)
#define OPT_ZEROOK      0x10000 /* 0 value is OK even if not within limits */
#define OPT_HIDE        0x10000 /* for o_string, print value as ?????? */
#define OPT_A2LIST      0x20000 /* for o_special, keep list of values */
#define OPT_A2CLRB      0x20000 /* o_bool, clr val bits in *(u_char *)addr2 */
#define OPT_ZEROINF     0x40000 /* with OPT_NOINCR, 0 == infinity */
#define OPT_PRIO        0x80000 /* process option priorities for this option */
#define OPT_PRIOSUB     0x100000 /* subsidiary member of priority group */
#define OPT_ALIAS       0x200000 /* option is alias for previous option */
#define OPT_A2COPY      0x400000 /* addr2 -> second location to rcv value */
#define OPT_ENABLE      0x800000 /* use *addr2 as enable for option */
#define OPT_A2CLR       0x1000000 /* clear *(bool *)addr2 */
#define OPT_PRIVFIX     0x2000000 /* user can't override if set by root */
#define OPT_INITONLY    0x4000000 /* option can only be set in init phase */
#define OPT_DEVEQUIV    0x8000000 /* equiv to device name */
#define OPT_DEVNAM      (OPT_INITONLY | OPT_DEVEQUIV)
#define OPT_A2PRINTER   0x10000000 /* *addr2 printer_func to print option */
#define OPT_A2STRVAL    0x20000000 /* *addr2 points to current string value */
#define OPT_NOPRINT     0x40000000 /* don't print this option at all */


上述进行了5次遍历,进行匹配,分别看看这些变量的容器:
option_t general_options[] = {
    { "debug", o_int, &debug, "Increase debugging level", OPT_INC | OPT_NOARG | 1 },
    { "-d", o_int, &debug,"Increase debugging level",OPT_ALIAS | OPT_INC | OPT_NOARG | 1 },
    { "kdebug", o_int, &kdebugflag,"Set kernel driver debug level", OPT_PRIO },
    { "nodetach", o_bool, &nodetach,"Don't detach from controlling tty", OPT_PRIO | 1 },
    { "-detach", o_bool, &nodetach,"Don't detach from controlling tty", OPT_ALIAS | OPT_PRIOSUB | 1 },
    { "updetach", o_bool, &updetach,"Detach from controlling tty once link is up",OPT_PRIOSUB | OPT_A2CLR | 1, &nodetach },
    { "master_detach", o_bool, &master_detach,"Detach when we're multilink master but have no link", 1 },
    { "holdoff", o_int, &holdoff,"Set time in seconds before retrying connection",OPT_PRIO, &holdoff_specified },
    { "idle", o_int, &idle_time_limit,"Set time in seconds before disconnecting idle link", OPT_PRIO },
    { "maxconnect", o_int, &maxconnect,"Set connection time limit",OPT_PRIO | OPT_LLIMIT | OPT_NOINCR | OPT_ZEROINF },
    { "domain", o_special, (void *)setdomain,"Add given domain name to hostname",OPT_PRIO | OPT_PRIV | OPT_A2STRVAL, &domain },
    { "file", o_special, (void *)readfile,"Take options from a file", OPT_NOPRINT },
    { "call", o_special, (void *)callfile,"Take options from a privileged file", OPT_NOPRINT },
    { "persist", o_bool, &persist,"Keep on reopening connection after close", OPT_PRIO | 1 },
    { "nopersist", o_bool, &persist,"Turn off persist option", OPT_PRIOSUB },
    { "demand", o_bool, &demand,"Dial on demand", OPT_INITONLY | 1, &persist },
    { "--version", o_special_noarg, (void *)showversion,"Show version number" },
    { "--help", o_special_noarg, (void *)showhelp,"Show brief listing of options" },
    { "-h", o_special_noarg, (void *)showhelp,"Show brief listing of options", OPT_ALIAS },
    { "logfile", o_special, (void *)setlogfile,"Append log messages to this file",OPT_PRIO | OPT_A2STRVAL | OPT_STATIC, &logfile_name },
    { "logfd", o_int, &log_to_fd,"Send log messages to this file descriptor",OPT_PRIOSUB | OPT_A2CLR, &log_default },
    { "nolog", o_int, &log_to_fd,"Don't send log messages to any file",OPT_PRIOSUB | OPT_NOARG | OPT_VAL(-1) },
    { "nologfd", o_int, &log_to_fd,"Don't send log messages to any file descriptor",OPT_PRIOSUB | OPT_ALIAS | OPT_NOARG | OPT_VAL(-1) },
    { "linkname", o_string, linkname,"Set logical name for link",OPT_PRIO | OPT_PRIV | OPT_STATIC, NULL, MAXPATHLEN },
    { "ifname", o_string, use_ifname,"Set physical name for PPP interface",OPT_PRIO | OPT_PRIV | OPT_STATIC, NULL, IFNAMSIZ },
    { "maxfail", o_int, &maxfail,"Maximum number of unsuccessful connection attempts to allow",OPT_PRIO },
    { "ktune", o_bool, &tune_kernel,"Alter kernel settings as necessary", OPT_PRIO | 1 },
    { "noktune", o_bool, &tune_kernel,"Don't alter kernel settings", OPT_PRIOSUB },
    { "connect-delay", o_int, &connect_delay,"Maximum time (in ms) to wait after connect script finishes",OPT_PRIO },
    { "unit", o_int, &req_unit,"PPP interface unit number to use if possible",OPT_PRIO | OPT_LLIMIT, 0, 0 },
    { "dump", o_bool, &dump_options,"Print out option values after parsing all options", 1 },
    { "dryrun", o_bool, &dryrun,"Stop after parsing, printing, and checking options", 1 },
    { "child-timeout", o_int, &child_wait,"Number of seconds to wait for child processes at exit",OPT_PRIO },
    { "set", o_special, (void *)user_setenv,"Set user environment variable",OPT_A2PRINTER | OPT_NOPRINT, (void *)user_setprint },
    { "unset", o_special, (void *)user_unsetenv,"Unset user environment variable",OPT_A2PRINTER | OPT_NOPRINT, (void *)user_unsetprint },
    { "ip-up-script", o_string, path_ipup,"Set pathname of ip-up script",OPT_PRIV|OPT_STATIC, NULL, MAXPATHLEN },
    { "ip-down-script", o_string, path_ipdown,"Set pathname of ip-down script",OPT_PRIV|OPT_STATIC, NULL, MAXPATHLEN },
    { "ipv6-up-script", o_string, path_ipv6up,"Set pathname of ipv6-up script",OPT_PRIV|OPT_STATIC, NULL, MAXPATHLEN },
    { "ipv6-down-script", o_string, path_ipv6down,"Set pathname of ipv6-down script",OPT_PRIV|OPT_STATIC, NULL, MAXPATHLEN },

#ifdef HAVE_MULTILINK
    { "multilink", o_bool, &multilink,"Enable multilink operation", OPT_PRIO | 1 },
    { "mp", o_bool, &multilink,"Enable multilink operation", OPT_PRIOSUB | OPT_ALIAS | 1 },
    { "nomultilink", o_bool, &multilink,"Disable multilink operation", OPT_PRIOSUB | 0 },
    { "bundle", o_string, &bundle_name,"Bundle name for multilink", OPT_PRIO },
#endif /* HAVE_MULTILINK */

    { "nomp", o_bool, &multilink,"Disable multilink operation", OPT_PRIOSUB | OPT_ALIAS | 0 },

#ifdef PLUGIN
    { "plugin", o_special, (void *)loadplugin,"Load a plug-in module into pppd", OPT_PRIV | OPT_A2LIST },
#endif

#ifdef PPP_FILTER
    { "pass-filter", o_special, setpassfilter,"set filter for packets to pass", OPT_PRIO },
    { "active-filter", o_special, setactivefilter,"set filter for active pkts", OPT_PRIO },
#endif

#ifdef PPP_PRECOMPILED_FILTER
    { "precompiled-pass-filter", 1, setprecompiledpassfilter,"set precompiled filter for packets to pass", OPT_PRIO },
    { "precompiled-active-filter", 1, setprecompiledactivefilter,"set precompiled filter for active pkts", OPT_PRIO },
#endif

#ifdef MAXOCTETS
    { "maxoctets", o_int, &maxoctets,"Set connection traffic limit",OPT_PRIO | OPT_LLIMIT | OPT_NOINCR | OPT_ZEROINF },
    { "mo", o_int, &maxoctets,"Set connection traffic limit",OPT_ALIAS | OPT_PRIO | OPT_LLIMIT | OPT_NOINCR | OPT_ZEROINF },
    { "mo-direction", o_special, setmodir,"Set direction for limit traffic (sum,in,out,max)" },
    { "mo-timeout", o_int, &maxoctets_timeout,"Check for traffic limit every N seconds", OPT_PRIO | OPT_LLIMIT | 1 },
#endif
    { NULL }
};

option_t auth_options[] = {
    { "auth", o_bool, &auth_required,"Require authentication from peer", OPT_PRIO | 1 }, 
    { "noauth", o_bool, &auth_required,"Don't require peer to authenticate", OPT_PRIOSUB | OPT_PRIV,&allow_any_ip },
    { "require-pap", o_bool, &lcp_wantoptions[0].neg_upap,"Require PAP authentication from peer",OPT_PRIOSUB | 1, &auth_required },
    { "+pap", o_bool, &lcp_wantoptions[0].neg_upap,"Require PAP authentication from peer",OPT_ALIAS | OPT_PRIOSUB | 1, &auth_required },
    { "require-chap", o_bool, &auth_required,"Require CHAP authentication from peer",OPT_PRIOSUB | OPT_A2OR | MDTYPE_MD5,&lcp_wantoptions[0].chap_mdtype },
    { "+chap", o_bool, &auth_required,"Require CHAP authentication from peer",OPT_ALIAS | OPT_PRIOSUB | OPT_A2OR | MDTYPE_MD5,&lcp_wantoptions[0].chap_mdtype },

#ifdef CHAPMS
    { "require-mschap", o_bool, &auth_required,"Require MS-CHAP authentication from peer",OPT_PRIOSUB | OPT_A2OR | MDTYPE_MICROSOFT,&lcp_wantoptions[0].chap_mdtype },
    { "+mschap", o_bool, &auth_required,"Require MS-CHAP authentication from peer",OPT_ALIAS | OPT_PRIOSUB | OPT_A2OR | MDTYPE_MICROSOFT,&lcp_wantoptions[0].chap_mdtype },
    { "require-mschap-v2", o_bool, &auth_required,"Require MS-CHAPv2 authentication from peer",OPT_PRIOSUB | OPT_A2OR | MDTYPE_MICROSOFT_V2,&lcp_wantoptions[0].chap_mdtype },
    { "+mschap-v2", o_bool, &auth_required,"Require MS-CHAPv2 authentication from peer",OPT_ALIAS | OPT_PRIOSUB | OPT_A2OR | MDTYPE_MICROSOFT_V2,&lcp_wantoptions[0].chap_mdtype },
#endif

    { "refuse-pap", o_bool, &refuse_pap,"Don't agree to auth to peer with PAP", 1 },
    { "-pap", o_bool, &refuse_pap,"Don't allow PAP authentication with peer", OPT_ALIAS | 1 },
    { "refuse-chap", o_bool, &refuse_chap,"Don't agree to auth to peer with CHAP",OPT_A2CLRB | MDTYPE_MD5,&lcp_allowoptions[0].chap_mdtype },
    { "-chap", o_bool, &refuse_chap,"Don't allow CHAP authentication with peer",OPT_ALIAS | OPT_A2CLRB | MDTYPE_MD5,&lcp_allowoptions[0].chap_mdtype },

#ifdef CHAPMS
    { "refuse-mschap", o_bool, &refuse_mschap,"Don't agree to auth to peer with MS-CHAP",OPT_A2CLRB | MDTYPE_MICROSOFT,&lcp_allowoptions[0].chap_mdtype },
    { "-mschap", o_bool, &refuse_mschap,"Don't allow MS-CHAP authentication with peer",OPT_ALIAS | OPT_A2CLRB | MDTYPE_MICROSOFT,&lcp_allowoptions[0].chap_mdtype },
    { "refuse-mschap-v2", o_bool, &refuse_mschap_v2,"Don't agree to auth to peer with MS-CHAPv2",OPT_A2CLRB | MDTYPE_MICROSOFT_V2,&lcp_allowoptions[0].chap_mdtype },
    { "-mschap-v2", o_bool, &refuse_mschap_v2,"Don't allow MS-CHAPv2 authentication with peer",OPT_ALIAS | OPT_A2CLRB | MDTYPE_MICROSOFT_V2,&lcp_allowoptions[0].chap_mdtype },
#endif

    { "require-eap", o_bool, &lcp_wantoptions[0].neg_eap,"Require EAP authentication from peer", OPT_PRIOSUB | 1,&auth_required },
    { "refuse-eap", o_bool, &refuse_eap,"Don't agree to authenticate to peer with EAP", 1 },
    { "name", o_string, our_name,"Set local name for authentication",OPT_PRIO | OPT_PRIV | OPT_STATIC, NULL, MAXNAMELEN },
    { "+ua", o_special, (void *)setupapfile,"Get PAP user and password from file",OPT_PRIO | OPT_A2STRVAL, &uafname },
    { "user", o_string, user,"Set name for auth with peer", OPT_PRIO | OPT_STATIC,&explicit_user, MAXNAMELEN },
    { "password", o_string, passwd,"Password for authenticating us to the peer",OPT_PRIO | OPT_STATIC | OPT_HIDE,&explicit_passwd, MAXSECRETLEN },
    { "usehostname", o_bool, &usehostname,"Must use hostname for authentication", 1 },
    { "remotename", o_string, remote_name,"Set remote name for authentication", OPT_PRIO | OPT_STATIC,&explicit_remote, MAXNAMELEN },
    { "login", o_bool, &uselogin,"Use system password database for PAP", OPT_A2COPY | 1 ,&session_mgmt },
    { "enable-session", o_bool, &session_mgmt,"Enable session accounting for remote peers", OPT_PRIV | 1 },
    { "papcrypt", o_bool, &cryptpap,"PAP passwords are encrypted", 1 },
    { "privgroup", o_special, (void *)privgroup,"Allow group members to use privileged options", OPT_PRIV | OPT_A2LIST },
    { "allow-ip", o_special, (void *)set_noauth_addr,"Set IP address(es) which can be used without authentication",OPT_PRIV | OPT_A2LIST },
    { "remotenumber", o_string, remote_number,"Set remote telephone number for authentication", OPT_PRIO | OPT_STATIC,NULL, MAXNAMELEN },
    { "allow-number", o_special, (void *)set_permitted_number,"Set telephone number(s) which are allowed to connect",OPT_PRIV | OPT_A2LIST },
    { NULL }
};

static option_t lcp_option_list[] = {
    /* LCP options */
    { "-all", o_special_noarg, (void *)noopt,"Don't request/allow any LCP options" },
    { "noaccomp", o_bool, &lcp_wantoptions[0].neg_accompression,"Disable address/control compression",OPT_A2CLR, &lcp_allowoptions[0].neg_accompression },
    { "-ac", o_bool, &lcp_wantoptions[0].neg_accompression,"Disable address/control compression",OPT_ALIAS | OPT_A2CLR, &lcp_allowoptions[0].neg_accompression },
    { "asyncmap", o_uint32, &lcp_wantoptions[0].asyncmap,"Set asyncmap (for received packets)",OPT_OR, &lcp_wantoptions[0].neg_asyncmap },
    { "-as", o_uint32, &lcp_wantoptions[0].asyncmap,"Set asyncmap (for received packets)",OPT_ALIAS | OPT_OR, &lcp_wantoptions[0].neg_asyncmap },
    { "default-asyncmap", o_uint32, &lcp_wantoptions[0].asyncmap,"Disable asyncmap negotiation",OPT_OR | OPT_NOARG | OPT_VAL(~0U) | OPT_A2CLR,&lcp_allowoptions[0].neg_asyncmap },
    { "-am", o_uint32, &lcp_wantoptions[0].asyncmap,"Disable asyncmap negotiation",OPT_ALIAS | OPT_OR | OPT_NOARG | OPT_VAL(~0U) | OPT_A2CLR,&lcp_allowoptions[0].neg_asyncmap },
    { "nomagic", o_bool, &lcp_wantoptions[0].neg_magicnumber,"Disable magic number negotiation (looped-back line detection)",OPT_A2CLR, &lcp_allowoptions[0].neg_magicnumber },
    { "-mn", o_bool, &lcp_wantoptions[0].neg_magicnumber,"Disable magic number negotiation (looped-back line detection)",OPT_ALIAS | OPT_A2CLR, &lcp_allowoptions[0].neg_magicnumber },
    { "mru", o_int, &lcp_wantoptions[0].mru,"Set MRU (maximum received packet size) for negotiation",OPT_PRIO, &lcp_wantoptions[0].neg_mru },
    { "default-mru", o_bool, &lcp_wantoptions[0].neg_mru,"Disable MRU negotiation (use default 1500)",OPT_PRIOSUB | OPT_A2CLR, &lcp_allowoptions[0].neg_mru },
    { "-mru", o_bool, &lcp_wantoptions[0].neg_mru,"Disable MRU negotiation (use default 1500)",OPT_ALIAS | OPT_PRIOSUB | OPT_A2CLR, &lcp_allowoptions[0].neg_mru },
    { "mtu", o_int, &lcp_allowoptions[0].mru,"Set our MTU", OPT_LIMITS, NULL, MAXMRU, MINMRU },
    { "nopcomp", o_bool, &lcp_wantoptions[0].neg_pcompression,"Disable protocol field compression",OPT_A2CLR, &lcp_allowoptions[0].neg_pcompression },
    { "-pc", o_bool, &lcp_wantoptions[0].neg_pcompression,"Disable protocol field compression",OPT_ALIAS | OPT_A2CLR, &lcp_allowoptions[0].neg_pcompression },
    { "passive", o_bool, &lcp_wantoptions[0].passive,"Set passive mode", 1 },
    { "-p", o_bool, &lcp_wantoptions[0].passive,"Set passive mode", OPT_ALIAS | 1 },
    { "silent", o_bool, &lcp_wantoptions[0].silent,"Set silent mode", 1 },
    { "lcp-echo-failure", o_int, &lcp_echo_fails,"Set number of consecutive echo failures to indicate link failure",OPT_PRIO },
    { "lcp-echo-interval", o_int, &lcp_echo_interval,"Set time in seconds between LCP echo requests", OPT_PRIO },
    { "lcp-echo-adaptive", o_bool, &lcp_echo_adaptive,"Suppress LCP echo requests if traffic was received", 1 },
    { "lcp-restart", o_int, &lcp_fsm[0].timeouttime,"Set time in seconds between LCP retransmissions", OPT_PRIO },
    { "lcp-max-terminate", o_int, &lcp_fsm[0].maxtermtransmits,"Set maximum number of LCP terminate-request transmissions", OPT_PRIO },
    { "lcp-max-configure", o_int, &lcp_fsm[0].maxconfreqtransmits,"Set maximum number of LCP configure-request transmissions", OPT_PRIO },
    { "lcp-max-failure", o_int, &lcp_fsm[0].maxnakloops,"Set limit on number of LCP configure-naks", OPT_PRIO },
    { "receive-all", o_bool, &lax_recv,"Accept all received control characters", 1 },

#ifdef HAVE_MULTILINK
    { "mrru", o_int, &lcp_wantoptions[0].mrru,"Maximum received packet size for multilink bundle",OPT_PRIO, &lcp_wantoptions[0].neg_mrru },
    { "mpshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf,"Use short sequence numbers in multilink headers",OPT_PRIO | 1, &lcp_allowoptions[0].neg_ssnhf },
    { "nompshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf,"Don't use short sequence numbers in multilink headers",OPT_PRIOSUB | OPT_A2CLR, &lcp_allowoptions[0].neg_ssnhf },
    { "endpoint", o_special, (void *) setendpoint,"Endpoint discriminator for multilink",OPT_PRIO | OPT_A2PRINTER, (void *) printendpoint },
#endif /* HAVE_MULTILINK */

    { "noendpoint", o_bool, &noendpoint,"Don't send or accept multilink endpoint discriminator", 1 },
    {NULL}
};


static option_t pap_option_list[] = {
    { "hide-password", o_bool, &hide_password,"Don't output passwords to log", OPT_PRIO | 1 },
    { "show-password", o_bool, &hide_password,"Show password string in debug log messages", OPT_PRIOSUB | 0 },
    { "pap-restart", o_int, &upap[0].us_timeouttime,"Set retransmit timeout for PAP", OPT_PRIO },
    { "pap-max-authreq", o_int, &upap[0].us_maxtransmits,"Set max number of transmissions for auth-reqs", OPT_PRIO },
    { "pap-timeout", o_int, &upap[0].us_reqtimeout,"Set time limit for peer PAP authentication", OPT_PRIO },
    { NULL }
};

static option_t chap_option_list[] = {
        { "chap-restart", o_int, &chap_timeout_time,"Set timeout for CHAP", OPT_PRIO },
        { "chap-max-challenge", o_int, &chap_max_transmits,"Set max #xmits for challenge", OPT_PRIO },
        { "chap-interval", o_int, &chap_rechallenge_time,"Set interval for rechallenge", OPT_PRIO },
        { "chapms-strip-domain", o_bool, &chapms_strip_domain,"Strip the domain prefix before the Username", 1 },
        { NULL }        
};  

static option_t ipcp_option_list[] = {
    { "noip", o_bool, &ipcp_protent.enabled_flag,"Disable IP and IPCP" },
    { "-ip", o_bool, &ipcp_protent.enabled_flag,"Disable IP and IPCP", OPT_ALIAS },
    { "novj", o_bool, &ipcp_wantoptions[0].neg_vj,"Disable VJ compression", OPT_A2CLR, &ipcp_allowoptions[0].neg_vj },
    { "-vj", o_bool, &ipcp_wantoptions[0].neg_vj,"Disable VJ compression", OPT_ALIAS | OPT_A2CLR,&ipcp_allowoptions[0].neg_vj },
    { "novjccomp", o_bool, &ipcp_wantoptions[0].cflag,"Disable VJ connection-ID compression", OPT_A2CLR,&ipcp_allowoptions[0].cflag },
    { "-vjccomp", o_bool, &ipcp_wantoptions[0].cflag,"Disable VJ connection-ID compression", OPT_ALIAS | OPT_A2CLR,&ipcp_allowoptions[0].cflag },
    { "vj-max-slots", o_special, (void *)setvjslots,"Set maximum VJ header slots",OPT_PRIO | OPT_A2STRVAL | OPT_STATIC, vj_value },
    { "ipcp-accept-local", o_bool, &ipcp_wantoptions[0].accept_local,"Accept peer's address for us", 1 },
    { "ipcp-accept-remote", o_bool, &ipcp_wantoptions[0].accept_remote,"Accept peer's address for it", 1 },
   { "ipparam", o_string, &ipparam,"Set ip script parameter", OPT_PRIO },
    { "noipdefault", o_bool, &disable_defaultip,"Don't use name for default IP adrs", 1 },
    { "ms-dns", 1, (void *)setdnsaddr,"DNS address for the peer's use" },
    { "ms-wins", 1, (void *)setwinsaddr,"Nameserver for SMB over TCP/IP for peer" },
    { "ipcp-restart", o_int, &ipcp_fsm[0].timeouttime,"Set timeout for IPCP", OPT_PRIO },
    { "ipcp-max-terminate", o_int, &ipcp_fsm[0].maxtermtransmits,"Set max #xmits for term-reqs", OPT_PRIO },
    { "ipcp-max-configure", o_int, &ipcp_fsm[0].maxconfreqtransmits,"Set max #xmits for conf-reqs", OPT_PRIO },
    { "ipcp-max-failure", o_int, &ipcp_fsm[0].maxnakloops,"Set max #conf-naks for IPCP", OPT_PRIO },
    { "defaultroute", o_bool, &ipcp_wantoptions[0].default_route,"Add default route", OPT_ENABLE|1, &ipcp_allowoptions[0].default_route },
    { "nodefaultroute", o_bool, &ipcp_allowoptions[0].default_route,"disable defaultroute option", OPT_A2CLR,&ipcp_wantoptions[0].default_route },
    { "-defaultroute", o_bool, &ipcp_allowoptions[0].default_route,"disable defaultroute option", OPT_ALIAS | OPT_A2CLR,&ipcp_wantoptions[0].default_route },
    { "replacedefaultroute", o_bool,&ipcp_wantoptions[0].replace_default_route,"Replace default route", 1},
    { "noreplacedefaultroute", o_bool,&ipcp_allowoptions[0].replace_default_route,"Never replace default route", OPT_A2COPY,&ipcp_wantoptions[0].replace_default_route },
    { "proxyarp", o_bool, &ipcp_wantoptions[0].proxy_arp,"Add proxy ARP entry", OPT_ENABLE|1, &ipcp_allowoptions[0].proxy_arp },
    { "noproxyarp", o_bool, &ipcp_allowoptions[0].proxy_arp,"disable proxyarp option", OPT_A2CLR,&ipcp_wantoptions[0].proxy_arp },
    { "-proxyarp", o_bool, &ipcp_allowoptions[0].proxy_arp,"disable proxyarp option", OPT_ALIAS | OPT_A2CLR,&ipcp_wantoptions[0].proxy_arp },
    { "usepeerdns", o_bool, &usepeerdns,"Ask peer for DNS address(es)", 1 },
    { "netmask", o_special, (void *)setnetmask,"set netmask", OPT_PRIO | OPT_A2STRVAL | OPT_STATIC, netmask_str },
    { "ipcp-no-addresses", o_bool, &ipcp_wantoptions[0].old_addrs,"Disable old-style IP-Addresses usage", OPT_A2CLR,&ipcp_allowoptions[0].old_addrs },
    { "ipcp-no-address", o_bool, &ipcp_wantoptions[0].neg_addr,"Disable IP-Address usage", OPT_A2CLR,&ipcp_allowoptions[0].neg_addr },
#ifdef __linux__
    { "noremoteip", o_bool, &noremoteip,"Allow peer to have no IP address", 1 },
#endif
    { "nosendip", o_bool, &ipcp_wantoptions[0].neg_addr,"Don't send our IP address to peer", OPT_A2CLR,&ipcp_wantoptions[0].old_addrs},
    { "IP addresses", o_wild, (void *) &setipaddr,"set local and remote IP addresses",OPT_NOARG | OPT_A2PRINTER, (void *) &printipaddr },
    { NULL }
};


static option_t ccp_option_list[] = {
    { "noccp", o_bool, &ccp_protent.enabled_flag,"Disable CCP negotiation" },
    { "-ccp", o_bool, &ccp_protent.enabled_flag,"Disable CCP negotiation", OPT_ALIAS },
    { "bsdcomp", o_special, (void *)setbsdcomp,"Request BSD-Compress packet compression",OPT_PRIO | OPT_A2STRVAL | OPT_STATIC, bsd_value },
    { "nobsdcomp", o_bool, &ccp_wantoptions[0].bsd_compress,"don't allow BSD-Compress", OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].bsd_compress },
    { "-bsdcomp", o_bool, &ccp_wantoptions[0].bsd_compress,"don't allow BSD-Compress", OPT_ALIAS | OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].bsd_compress },
    { "deflate", o_special, (void *)setdeflate,"request Deflate compression",OPT_PRIO | OPT_A2STRVAL | OPT_STATIC, deflate_value },
    { "nodeflate", o_bool, &ccp_wantoptions[0].deflate,"don't allow Deflate compression", OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].deflate },
    { "-deflate", o_bool, &ccp_wantoptions[0].deflate,"don't allow Deflate compression", OPT_ALIAS | OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].deflate },
    { "nodeflatedraft", o_bool, &ccp_wantoptions[0].deflate_draft,"don't use draft deflate #", OPT_A2COPY,&ccp_allowoptions[0].deflate_draft },
    { "predictor1", o_bool, &ccp_wantoptions[0].predictor_1,"request Predictor-1", OPT_PRIO | 1 },
    { "nopredictor1", o_bool, &ccp_wantoptions[0].predictor_1,"don't allow Predictor-1", OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].predictor_1 },
    { "-predictor1", o_bool, &ccp_wantoptions[0].predictor_1,"don't allow Predictor-1", OPT_ALIAS | OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].predictor_1 },
    { "lzs", o_bool, &ccp_wantoptions[0].lzs,"request Stac LZS", 1, &ccp_allowoptions[0].lzs, OPT_PRIO },
    { "+lzs", o_bool, &ccp_wantoptions[0].lzs,"request Stac LZS", 1, &ccp_allowoptions[0].lzs, OPT_ALIAS | OPT_PRIO },
    { "nolzs", o_bool, &ccp_wantoptions[0].lzs,"don't allow Stac LZS", OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].lzs },
    { "-lzs", o_bool, &ccp_wantoptions[0].lzs,"don't allow Stac LZS", OPT_ALIAS | OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].lzs },

#ifdef MPPE
    { "mppc", o_bool, &ccp_wantoptions[0].mppc,"request MPPC compression", 1, &ccp_allowoptions[0].mppc },
    { "+mppc", o_bool, &ccp_wantoptions[0].mppc,"request MPPC compression", 1, &ccp_allowoptions[0].mppc, OPT_ALIAS },
    { "nomppc", o_bool, &ccp_wantoptions[0].mppc,"don't allow MPPC compression", OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].mppc },
    { "-mppc", o_bool, &ccp_wantoptions[0].mppc,"don't allow MPPC compression", OPT_ALIAS | OPT_PRIOSUB | OPT_A2CLR,&ccp_allowoptions[0].mppc },
    { "mppe", o_special, (void *)setmppe,"request MPPE encryption" },
    { "+mppe", o_special, (void *)setmppe,"request MPPE encryption" },
    { "nomppe", o_special_noarg, (void *)setnomppe,"don't allow MPPE encryption" },
    { "-mppe", o_special_noarg, (void *)setnomppe,"don't allow MPPE encryption" },
#endif /* MPPE */
    { NULL }
};

static option_t ecp_option_list[] = {
    { "noecp", o_bool, &ecp_protent.enabled_flag,"Disable ECP negotiation" },
    { "-ecp", o_bool, &ecp_protent.enabled_flag,"Disable ECP negotiation", OPT_ALIAS },
    { NULL }
};

static option_t eap_option_list[] = {
    { "eap-restart", o_int, &eap_states[0].es_server.ea_timeout,"Set retransmit timeout for EAP Requests (server)" },
    { "eap-max-sreq", o_int, &eap_states[0].es_server.ea_maxrequests,"Set max number of EAP Requests sent (server)" },
    { "eap-timeout", o_int, &eap_states[0].es_client.ea_timeout,"Set time limit for peer EAP authentication" },
    { "eap-max-rreq", o_int, &eap_states[0].es_client.ea_maxrequests,"Set max number of EAP Requests allows (client)" },
    { "eap-interval", o_int, &eap_states[0].es_rechallenge,"Set interval for EAP rechallenge" },
#ifdef USE_SRP
    { "srp-interval", o_int, &eap_states[0].es_lwrechallenge,"Set interval for SRP lightweight rechallenge" },
    { "srp-pn-secret", o_string, &pn_secret,"Long term pseudonym generation secret" },
    { "srp-use-pseudonym", o_bool, &eap_states[0].es_usepseudo,
      "Use pseudonym if offered one by server", 1 },
#endif
    { NULL }
};


----------------------------------------------------------------------------------------------------
File:options.c
int options_from_user()
{
    char *user, *path, *file;
    int ret; 
    struct passwd *pw; 
    size_t pl;


    pw = getpwuid(getuid());
    if (pw == NULL || (user = pw->pw_dir) == NULL || user[0] == 0)
        return 1;
    file = _PATH_USEROPT;  #NOTE: #define _PATH_USEROPT    ".ppprc"
    pl = strlen(user) + strlen(file) + 2; 
    path = malloc(pl);
    if (path == NULL)
        novm("init file name");
    slprintf(path, pl, "%s/%s", user, file);
    option_priority = OPRIO_CFGFILE;
    ret = options_from_file(path, 0, 1, privileged); #NOTE: 从用户配置文件.ppprc中读取配置选项
    free(path);
    return ret; 
}

----------------------------------------------------------------------------------------------------------------
File: options.c
int parse_args(argc, argv)
    int argc;
    char **argv;
{
    char *arg;
    option_t *opt;
    int n;

    privileged_option = privileged;
    option_source = "command line";
    option_priority = OPRIO_CMDLINE;
    while (argc > 0) {
        arg = *argv++;
        --argc;
        opt = find_option(arg); #NOTE:查找选项
        if (opt == NULL) {
            option_error("unrecognized option '%s'", arg);
            usage();
            return 0;
        }
        n = n_arguments(opt);
        if (argc < n) {
            option_error("too few parameters for option %s", arg);
            return 0;
        }
        if (!process_option(opt, arg, argv))
            return 0;
        argc -= n;
        argv += n;
    }
    return 1;
}


pppd工具脚本:

    pppd默认提供如下五个脚本接口供其调用,脚本以root用户权限,将标准输入,输出,出错重定向到/dev/null,并采用后台执行的方式运行。应用程序与脚本之间的参数传递利用exec带环境变量的方式进行传递。
#define _PATH_IPUP      _ROOT_PATH "/etc/ppp/ip-up"   #NOTE: 三层协议IPCP协商完成可收发IP报文时执行该脚本,执行时传递参数:
                                                                             interface-name tty-device speed local-IP-address remote-IP-address ipparam

#define _PATH_IPDOWN     _ROOT_PATH "/etc/ppp/ip-down" #NOTE: 三层协议IPCP无法再收发IP数据报时执行该脚本,参数与UP一致

#define _PATH_IPPREUP    _ROOT_PATH "/etc/ppp/ip-pre-up"

#define _PATH_AUTHUP     _ROOT_PATH "/etc/ppp/auth-up" #NOTE: 认证通过时执行该脚本,并传递如下参数: interface-name
                                                                                                               peer-name user-name tty-device speed

                                #  注意: 若指定参数noauth时,此脚本不执行

#define _PATH_AUTHDOWN   _ROOT_PATH "/etc/ppp/auth-down"   #NOTE: 认证失效时执行此脚本,参数与UP时一致

脚本运行时的可用参数如下: (可通过如下命令进行搜索支持的环境变量: grep "script_setenv" ./ )

 
DEVICE:控制端网络端口名,如eth0
 IFNAME:网络接口名,如ppp1
 IPLOCAL:本端IP地址,只有IPCP成功协商才设置该值 
 
IPREMOTE:远端IP地址,只有IPCP成功协商才设置该值
 
PEERNAME:对端认证的名字,这仅在对端认证自己的情况下会设置。
 
SPEED:串口设备波特率
 
ORIG_UID:运行pppd进程的用户的实际UID
 
PPPLOGNAME:运行pppd进程的用户的用户名
 PPPD_PID: 当前pppd的PID
 IFUNIT: 当前pppd使用的unit
 
  
对于ip-down和auth-down脚本,pppd设置下列环境变量提供连接的统计值:
 
CONNECT_TIME:从PPP协商开始到连接中断之间的秒数。
 
BYTES_SENT:连接期间发送的字节数。
 
BYTES_RCVD:连接期间接受的字节数。
 
LINKNAME:linkname选项设置的链路逻辑名称。
 
DNS1:第一个DNS server地址。
 
DNS2:第二个DNS server地址

PPPOE支持的环境变量:
 MACREMOTE: 对端的MAC地址

RADIUS支持的环境变量:
RADIUS_FILTER_ID: radius过滤器ID
RADIUS_FRAMED_ROUTE:


pppd插件
     pppd的插件位于源码目录的pppd/plugins/目录下,往往一个此插件即为该目录下的一个.so文件.
     pppd的插件是一个标准的动态链接库文件,pppd通过函数loadplugin()调用函数dlopen()来进行动态库的装载,该函数以插件名为参数.
     pppd插件主要有如下三种实现方式:
        (1).调用函数add_options(),增加options.   
        (2).指定hook,挂接钩子函数.
        (3).执行add_notifiler()实现异步消息机制.

     下面是插件装载函数的源码:
static int loadplugin(argv)
    char **argv;
{
    char *arg = *argv;
    void *handle;
    const char *err;
    void (*init) __P((void));
    char *path = arg; 
    const char *vers;


    if (strchr(arg, '/') == 0) {            #NOTE:处理动态库的装载路劲
        const char *base = _PATH_PLUGIN;
        int l = strlen(base) + strlen(arg) + 2; 
        path = malloc(l);
        if (path == 0)
            novm("plugin file path");
        strlcpy(path, base, l);
        strlcat(path, "/", l);
        strlcat(path, arg, l);
    }    
    handle = dlopen(path, RTLD_GLOBAL | RTLD_NOW);  #NOTE: 打开动态库
    if (handle == 0) { 
        err = dlerror();
        if (err != 0)
            option_error("%s", err);
        option_error("Couldn't load plugin %s", arg);
        goto err; 
    }    
    init = (void (*)(void))dlsym(handle, "plugin_init");  #NOTE: 动态库的入口函数plugin_init()
    if (init == 0) { 
        option_error("%s has no initialization entry point", arg);
        goto errclose;
    }
    vers = (const char *) dlsym(handle, "pppd_version"); #NOTE: pppd版本号字符串
    if (vers == 0) {
        warn("Warning: plugin %s has no version information", arg);
    } else if (strcmp(vers, VERSION) != 0) {  #NOTE: 版本号不匹配时无法装载
        option_error("Plugin %s is for pppd version %s, this is %s",
                     arg, vers, VERSION);
        goto errclose;
    }
    info("Plugin %s loaded.", arg);
    (*init)();
    return 1;


 errclose:
    dlclose(handle);
 err:
    if (path != arg)
        free(path);
    return 0;
}
  下面是pppd插件中常用的钩子函数:
int (*idle_time_hook)(struct ppp_idle *idlep);
idle_time_hook在这个连接第一次开始的时候被调用(比如,第一个网络协议开始的时候),那之后被定时调用。在第一次调用的时候,idlep参数是NULL,返回值则是pppd检查连接活跃前的秒数,或者0表示没有超时。在后来的调用中,idlep指令一个指向最包被发送、接收秒数的结构体。如果返回的值大于0,pppd会在再次检查之前等一些的秒数。如果小于等于0,就是说该连接在不活跃的情况下应该被终结。


int (*holdoff_hook)(void);
当尝试拌连接失败或是连接被终结的时候,holdoff_hook被调用,persist或是demand选项被使用。它返回PPTP重新建立连接应该等待的秒数(0表示立即)。


int (*chap_check_hook)(void);
int (*chap_passwd_hook)(char *user, char *passwd);
int (*chap_auth_hook)(char *user, u_char *remmd, int remmd_len, chap_state *cstate);
这些hook被设计用来在插件里面替换常规的CHAP密码处理过程(比如外部服务器的认证)。

chap_check_hook被调用来检查对端是否有必要向我们认证其自身。如果返回1,pppd将询问询问其自身,否则返回0(如果该认证被要求了,在网络协议协商之前pppd会退出或是终结当前连接)。如果返回-1,pppd将查找chap-secrets文件使用常规方式处理。

chap_passwd_hook决定pppd应该使用什么密码来使用CHAP来向对端认证自身。user字符串使用’user’选项或者’name’选项、主机名进行初始化,有必须可以进行修改。这个钩子只有当ppdp是客户客户端而非服务的时候被调用。passwd可以容纳最多MAXSECRETLEN 字节。如果钩子返回0,PPPD使用 *passwd,如果返回 -1,pppd认证失败。

chap_auth_hook 钩子确定由对应提供的CHAP挑战响应是否有效。user指向一个包含对端提供用户名的非NULL结束的字符串。remmd向向对端提供的响应,由remmd_len表示其长度 。cstate是PPTP维护的内部CHAP状态结构体。chap_auth_hook应该返回CHAP_SUCCESS 或者CHAP_FAILURE。


int (*null_auth_hook)(struct wordlist **paddrs, struct wordlist **popts);
这个钩子允许插件确定当请求的认证被对端拒绝的时候应该采取的策略。 如果返回0,连接被终结;1,连接被允许处理,这种情况下 *paddrs和*popts可像pap_auth_hook一样被设置,以指定被允许的IP地址列表和任意扩展属性。如果返回-1,pppd查找pap-secret文件按常规处理。


void (*ip_choose_hook)(u_int32_t *addrp);
该钩子在IPCP协商开始的时候调用。它使得插件有机会设置对端的IP地址。地方应该保存在*addrp。如果*addrp里什么也没有存储的话,pppd使用常规方式决定对端的地址。


int (*allowed_address_hook)(u_int32_t addr)
这个钩子确认对端是否可以使用指定的IP地址。如果钩子返回1,地址可以接受,返回0为拒绝。如果返回-1,将使用常规方式查找适当的选项和secrets文件来决定。


void (*snoop_recv_hook)(unsigned char *p, int len)
void (*snoop_send_hook)(unsigned char *p, int len)
这些钩子在接受或是发送数据包的时候被调用。数据包在p里同,长度由 len表式。使得插件可以检查pppd的会话。这些钩子在实现L2TP的时候将会起到很大的作用。

可注册的消息列表

插件可以使用notifier注册自身,通过申明一个下面形式的过程:

void my_notify_proc(void *opaque, int arg);

然后使用适当的notifier,以下面形式调用来注册这个过程。

add_notifier(&interesting_notifier, my_notify_proc, opaque);

add_notifier 中的’opaque’参数每次调用notifier的时候传递给my_notify_proc 。传递的’arg’参数决定于notifier一个nofify过程可以使用下面的方式从当前的notifier列表中被移除。

remove_notifier(&interesting_notifier, my_notify_proc, opaque);

下面是目前pppd实现的notifier列表。

pidchange 由其父进程在pppd已经forked并且子进程在继续pppd’s处理时调用,比如pppd从其控制终端中分离出来的时候。参数就是这个子进程的pid。

phasechange 当pppd从一个阶段操作转移到另一个的时候调用。参数是新阶段的编号。

exitnotify 在pppd退出前调用。参数是pppd退出的状态。(比如exit()的参数)。

sigreceived 在收到信号的时候调用,存在于signal handle里面。参数是signal的编号。

ip_up_notifier 在IPCP发生的时候调用。

ip_down_notifier 在IPCP关闭的时候调用。

auth_up_notifier 在对端认证自身成功的时候调用。

link_down_notifier 在连接关闭的时候调用。



阅读(6789) | 评论(0) | 转发(0) |
0

上一篇:PPTP源码分析

下一篇:xl2tpd源码分析

给主人留下些什么吧!~~