别的不多说,直接看代码(isc dhcp-4.2.4-p2)。示例配置文件可以百度/google,很多的。

关键数据结构

struct parse {    int lexline; 与line对应    int lexchar;  与lpos对应    char *token_line; // 关键字所在行    char *prev_line;  // 前一行    char *cur_line;   // 当前行    const char *tlname;  // 文件名    int eol_token;    /*     * In order to give nice output when we have a parsing error     * in our file, we keep track of where we are in the line so     * that we can show the user.     *     * We need to keep track of two lines, because we can look     * ahead, via the "peek" function, to the next line sometimes.     *     * The "line1" and "line2" variables act as buffers for this     * information. The "lpos" variable tells us where we are in the     * line.     *     * When we "put back" a character from the parsing context, we     * do not want to have the character appear twice in the error     * output. So, we set a flag, the "ugflag", which the     * get_char() function uses to check for this condition.     */    char line1 [81];  // 存放文件中字符串行    char line2 [81];    int lpos;  // 行内位置标记    int line;  // 行号    int tlpos; // 行内位置标记缓存变量    int tline; // 行号缓存变量    enum dhcp_token token; //关键字    int ugflag;    char *tval;    int tlen;    char tokbuf [1500];    int warnings_occurred;    int file;   // 文件描述符    char *inbuf;  // 内存映射区指针    size_t bufix, buflen;  // bufix为buf偏移标记    size_t bufsiz;  // 映射区大小    struct parse *saved_state;#if defined(LDAP_CONFIGURATION)    /*     * LDAP configuration uses a call-back to iteratively read config     * off of the LDAP repository.     * XXX: The token stream can not be rewound reliably, so this must     * be addressed for DHCPv6 support.     */    int (*read_function)(struct parse *);#endif};

读配置文件入口

isc_result_t readconf (){    isc_result_t res;    res = read_conf_file (path_dhcpd_conf, root_group, ROOT_GROUP, 0);#if defined(LDAP_CONFIGURATION)    ……#else    return (res);#endif}

   这里有两个宏定义,是针对个别情况的处理。我也没有深入了解,暂时就不予分析,代码就直接屏蔽了。

isc_result_t read_conf_file (const char *filename, struct group *group,                 int group_type, int leasep){    int file;    struct parse *cfile;    isc_result_t status;#if defined (TRACING)    ……#endif    /* 打开文件,大家都能看懂 */    if ((file = open (filename, O_RDONLY)) < 0) {        if (leasep) {            log_error ("Can't open lease database %s: %m --",                   path_dhcpd_db);            log_error ("  check for failed database %s!",                   "rewrite attempt");            log_error ("Please read the dhcpd.leases manual%s",                   " page if you");            log_fatal ("don't know what to do about this.");        } else {            log_fatal ("Can't open %s: %m", filename);        }    }    cfile = (struct parse *)0;#if defined (TRACING)    ……#else    /* 申请内存空间 */    status = new_parse(&cfile, file, NULL, 0, filename, 0);#endif    if (status != ISC_R_SUCCESS || cfile == NULL)        return status;    if (leasep)        /* 解析dhcpd.leases文件 */        status = lease_file_subparse (cfile);    else        /* 解析dhcpd.conf文件 */        status = conf_file_subparse (cfile, group, group_type);    end_parse (&cfile);#if defined (TRACING)    ……#endif    return status;}

文件解析流程

   由于lease文件和conf文件的解析流程类似,本文就以为conf文件的解析为例。

1. 关键数据结构内存申请和初始化

isc_result_t new_parse (cfile, file, inbuf, buflen, name, eolp)    struct parse **cfile;    int file;    char *inbuf;    unsigned buflen;    const char *name;    int eolp;{    isc_result_t status = ISC_R_SUCCESS;    struct parse *tmp;    /* 封装过的malloc函数 */    tmp = dmalloc(sizeof(struct parse), MDL);    if (tmp == NULL) {        return (ISC_R_NOMEMORY);    }    /*     * We don't need to initialize things to zero here, since     * dmalloc() returns memory that is set to zero.     */    tmp->tlname = name;   // conf文件名    tmp->lpos = tmp -> line = 1;   // 位置标记和行号置成1    tmp->cur_line = tmp->line1;    // 当前行指针cur_line指向line1    tmp->prev_line = tmp->line2;   // 前一行指针prev_line指向line2    tmp->token_line = tmp->cur_line;    tmp->cur_line[0] = tmp->prev_line[0] = 0;    tmp->file = file;    // 设置文件描述符    tmp->eol_token = eolp;    if (inbuf != NULL) {        tmp->inbuf = inbuf;        tmp->buflen = buflen;        tmp->bufsiz = 0;    } else {        struct stat sb;        /* fstat为C库函数,获取文件的信息 */        if (fstat(file, &sb) < 0) {            status = ISC_R_IOERROR;            goto cleanup;        }        if (sb.st_size == 0)            goto cleanup;        tmp->bufsiz = tmp->buflen = (size_t) sb.st_size;        /* 将conf文件映射到内存,映射区指针存入inbuf */        tmp->inbuf = mmap(NULL, tmp->bufsiz, PROT_READ, MAP_SHARED,                  file, 0);        if (tmp->inbuf == MAP_FAILED) {            status = ISC_R_IOERROR;            goto cleanup;        }    }    *cfile = tmp;    return (ISC_R_SUCCESS);cleanup:    dfree(tmp, MDL);    return (status);}

2. 开始解析conf文件

isc_result_t conf_file_subparse (struct parse *cfile, struct group *group,                 int group_type){    const char *val;    /* 枚举关键字 */    enum dhcp_token token;    int declaration = 0;    int status;    do {        /* 仅仅查看下一个关键字 */        token = peek_token (&val, (unsigned *)0, cfile);        if (token == END_OF_FILE)            break;        /* 解析'声明'关键字(subnet/pool/host等) */        declaration = parse_statement (cfile, group, group_type,                           (struct host_decl *)0,                           declaration);    } while (1);    /* 获取下一个关键字,相关指针同时向后移动 */    token = next_token (&val, (unsigned *)0, cfile);    status = cfile->warnings_occurred ? DHCP_R_BADPARSE : ISC_R_SUCCESS;    return status;}

3. token是通过intern函数解析关键字字符串返回的,其值是整型枚举变量。

static enum dhcp_tokenintern(char *atom, enum dhcp_token dfv) {    if (!isascii(atom[0]))        return dfv;    switch (tolower((unsigned char)atom[0])) {          case '-':        if (atom [1] == 0)            return MINUS;        break;          case 'a':        if (!strcasecmp(atom + 1, "bandoned"))            return TOKEN_ABANDONED;        if (!strcasecmp(atom + 1, "ctive"))            return TOKEN_ACTIVE;        if (!strncasecmp(atom + 1, "dd", 2)) {            if (atom[3] == '\0')                return TOKEN_ADD;            else if (!strcasecmp(atom + 3, "ress"))                return ADDRESS;            break;        }        if (!strcasecmp(atom + 1, "fter"))            return AFTER;        if (isascii(atom[1]) &&            (tolower((unsigned char)atom[1]) == 'l')) {            if (!strcasecmp(atom + 2, "gorithm"))                return ALGORITHM;            if (!strcasecmp(atom + 2, "ias"))                return ALIAS;            if (isascii(atom[2]) &&                (tolower((unsigned char)atom[2]) == 'l')) {                if (atom[3] == '\0')                    return ALL;                else if (!strcasecmp(atom + 3, "ow"))                    return ALLOW;                break;            }            if (!strcasecmp(atom + 2, "so"))                return TOKEN_ALSO;            break;        }……}

4. peek_token,其实调用了do_peek_token函数,raw==ISC_FALSE则忽略空字符。

enum dhcp_tokenpeek_token(const char **rval, unsigned *rlen, struct parse *cfile) {    return do_peek_token(rval, rlen, cfile, ISC_FALSE);}

只会查看下一个关键字,inbuf中的标记位置不做偏移。2

enum dhcp_tokendo_peek_token(const char **rval, unsigned int *rlen,          struct parse *cfile, isc_boolean_t raw) {    int x;    if (!cfile->token || (!raw && (cfile->token == WHITESPACE))) {        cfile -> tlpos = cfile -> lexchar;  // 记录行内标记        cfile -> tline = cfile -> lexline;  // 记录行号        do {            /* 读取关键字 */            cfile->token = get_raw_token(cfile);        } while (!raw && (cfile->token == WHITESPACE));        if (cfile -> lexline != cfile -> tline)            cfile -> token_line = cfile -> prev_line;        x = cfile -> lexchar;        cfile -> lexchar = cfile -> tlpos; // 还原行内标记        cfile -> tlpos = x;        x = cfile -> lexline;         cfile -> lexline = cfile -> tline;  // 还原行号        cfile -> tline = x;    }    if (rval)        *rval = cfile -> tval;    if (rlen)        *rlen = cfile -> tlen;#ifdef DEBUG_TOKENS    fprintf (stderr, "(%s:%d) ", cfile -> tval, cfile -> token);#endif    return cfile -> token;}

get_raw_token()函数主要调用read_whitespace()、read_string()、read_num()等函数来读取空字符、字符串、数字等。它们都是调用get_char()从内存映射区(inbuf)中读取字符的。

static enum dhcp_tokenget_raw_token(struct parse *cfile) {    int c;    enum dhcp_token ttok;    static char tb [2];    int l, p;    do {        l = cfile -> line;        p = cfile -> lpos;        c = get_char (cfile);        if (!((c == '\n') && cfile->eol_token) &&            isascii(c) && isspace(c)) {                ttok = read_whitespace(c, cfile);            break;        }        if (c == '#') {            skip_to_eol (cfile);            continue;        }        if (c == '"') {            cfile -> lexline = l;            cfile -> lexchar = p;            ttok = read_string (cfile);            break;        }        if ((isascii (c) && isdigit (c)) || c == '-') {            cfile -> lexline = l;            cfile -> lexchar = p;            ttok = read_number (c, cfile);            break;        } else if (isascii (c) && isalpha (c)) {            cfile -> lexline = l;            cfile -> lexchar = p;            ttok = read_num_or_name (c, cfile);            break;        } else if (c == EOF) {            ttok = END_OF_FILE;            cfile -> tlen = 0;            break;        } else {            cfile -> lexline = l;            cfile -> lexchar = p;            tb [0] = c;            tb [1] = 0;            cfile -> tval = tb;            cfile -> tlen = 1;            ttok = c;            break;        }    } while (1);    return ttok;}

5. next_token

enum dhcp_tokennext_token(const char **rval, unsigned *rlen, struct parse *cfile) {    return get_next_token(rval, rlen, cfile, ISC_FALSE);}

与do_peek_token函数相似,最终调用get_raw_token获取关键字。

static enum dhcp_tokenget_next_token(const char **rval, unsigned *rlen,           struct parse *cfile, isc_boolean_t raw) {    int rv;    if (cfile -> token) {        if (cfile -> lexline != cfile -> tline)            cfile -> token_line = cfile -> cur_line;        cfile -> lexchar = cfile -> tlpos;        cfile -> lexline = cfile -> tline;        rv = cfile -> token;        cfile -> token = 0;    } else {        rv = get_raw_token(cfile);        cfile -> token_line = cfile -> cur_line;    }    if (!raw) {        while (rv == WHITESPACE) {            rv = get_raw_token(cfile);            cfile->token_line = cfile->cur_line;        }    }    /* 与do_peek_token()函数相比,这里就不需要还原了 */                                 if (rval)        *rval = cfile -> tval;    if (rlen)        *rlen = cfile -> tlen;#ifdef DEBUG_TOKENS    fprintf (stderr, "%s:%d ", cfile -> tval, rv);#endif    return rv;}

6. 解析“声明”

   由于对各个“声明”的解析也是很类似,为了节省篇幅,这里只对pool进行分析。

int parse_statement (cfile, group, type, host_decl, declaration)    struct parse *cfile;    struct group *group;    int type;    struct host_decl *host_decl;    int declaration;{    enum dhcp_token token;    const char *val;    struct shared_network *share;    char *n;    struct hardware hardware;    struct executable_statement *et, *ep;    struct option *option = NULL;    struct option_cache *cache;    int lose;    int known;    isc_result_t status;    unsigned code;    token = peek_token (&val, (unsigned *)0, cfile);    switch (token) {        /* include了其他conf文件 */          case INCLUDE:        next_token (&val, (unsigned *)0, cfile);        token = next_token (&val, (unsigned *)0, cfile);        if (token != STRING) {            parse_warn (cfile, "filename string expected.");            skip_to_semi (cfile);        } else {            /* 解析被include的conf文件 */            status = read_conf_file (val, group, type, 0);            if (status != ISC_R_SUCCESS)                parse_warn (cfile, "%s: bad parse.", val);            parse_semi (cfile);        }        return 1;                                                                                                                                                                                                                                                                                                                                                         ……          case POOL:        next_token (&val, (unsigned *)0, cfile);        if (type == POOL_DECL) {            parse_warn (cfile, "pool declared within pool.");            skip_to_semi(cfile);  // 跳到本定义块结束(以‘{’‘}’为分界符)        } else if (type != SUBNET_DECL && type != SHARED_NET_DECL) {            parse_warn (cfile, "pool declared outside of network");            skip_to_semi(cfile);        } else            /* 解析pool定义块 */            parse_pool_statement (cfile, group, type);        return declaration;……}

7. 解析pool块

void parse_pool_statement (cfile, group, type)    struct parse *cfile;    struct group *group;    int type;{    enum dhcp_token token;    const char *val;    int done = 0;    struct pool *pool, **p, *pp;    struct permit *permit;    struct permit **permit_head;    int declaration = 0;    isc_result_t status;    struct lease *lpchain = (struct lease *)0, *lp;    TIME t;    int is_allow = 0;    pool = (struct pool *)0;    /* 给pool分配内存 */    status = pool_allocate (&pool, MDL);    if (status != ISC_R_SUCCESS)        log_fatal ("no memory for pool: %s",               isc_result_totext (status));    /* 如果pool定义在subnet里,则将pool加入该subnet */    if (type == SUBNET_DECL)        shared_network_reference (&pool -> shared_network,                      group -> subnet -> shared_network,                      MDL);    /* 如果pool定义在shared_network里,则将pool加入该shared_network */    else if (type == SHARED_NET_DECL)        shared_network_reference (&pool -> shared_network,                      group -> shared_network, MDL);    else {        parse_warn(cfile, "Dynamic pools are only valid inside "                  "subnet or shared-network statements.");        skip_to_semi(cfile);        return;    }    if (pool->shared_network == NULL ||            !clone_group(&pool->group, pool->shared_network->group, MDL))        log_fatal("can't clone pool group.");#if defined (FAILOVER_PROTOCOL)   // dhcp热备    ……#endif    /* pool关键字后不是以‘{’开始,则销毁该pool */    if (!parse_lbrace (cfile)) {        pool_dereference (&pool, MDL);        return;    }    do {        /* 查看下一个关键字,token为整型枚举变量 */        token = peek_token (&val, (unsigned *)0, cfile);        switch (token) {              case TOKEN_NO:            next_token (&val, (unsigned *)0, cfile);            token = next_token (&val, (unsigned *)0, cfile);            if (token != FAILOVER ||                (token = next_token (&val, (unsigned *)0,                         cfile)) != PEER) {                parse_warn (cfile,                        "expecting \"failover peer\".");                skip_to_semi (cfile);                continue;            }#if defined (FAILOVER_PROTOCOL)            ……#endif            break;                                                                                                                                                                                                                                                                                                                            #if defined (FAILOVER_PROTOCOL)              ……;#endif            /* 关键字为range,则开始解析该地址范围 */              case RANGE:            next_token (&val, (unsigned *)0, cfile);            parse_address_range (cfile, group, type,                         pool, &lpchain);            break;            /* 关键字为allow,设置该pool权限属性 */              case ALLOW:            permit_head = &pool -> permit_list;            /* remember the clause which leads to get_permit */            is_allow = 1;              get_permit:            permit = new_permit (MDL);            if (!permit)                log_fatal ("no memory for permit");            next_token (&val, (unsigned *)0, cfile);            token = next_token (&val, (unsigned *)0, cfile);            switch (token) {                  case UNKNOWN:                permit -> type = permit_unknown_clients;                  get_clients:                if (next_token (&val, (unsigned *)0,                        cfile) != CLIENTS) {                    parse_warn (cfile,                            "expecting \"clients\"");                    skip_to_semi (cfile);                    free_permit (permit, MDL);                    continue;                }                break;                                                                                                                                                                                                                                                                                                                                              case KNOWN_CLIENTS:                permit -> type = permit_known_clients;                break;                  case UNKNOWN_CLIENTS:                permit -> type = permit_unknown_clients;                break;                  case KNOWN:                permit -> type = permit_known_clients;                goto get_clients;                                                                                                                                                                                                                                                                                                                                              case AUTHENTICATED:                permit -> type = permit_authenticated_clients;                goto get_clients;                                                                                                                                                                                                                                                                                                                                              case UNAUTHENTICATED:                permit -> type =                    permit_unauthenticated_clients;                goto get_clients;                  case ALL:                permit -> type = permit_all_clients;                goto get_clients;                break;                                                                                                                                                                                                                                                                                                                                              case DYNAMIC:                permit -> type = permit_dynamic_bootp_clients;                if (next_token (&val, (unsigned *)0,                        cfile) != TOKEN_BOOTP) {                    parse_warn (cfile,                            "expecting \"bootp\"");                    skip_to_semi (cfile);                    free_permit (permit, MDL);                    continue;                }                goto get_clients;                                                                                                                                                                                                                                                                                                                                              case MEMBERS:                if (next_token (&val, (unsigned *)0,                        cfile) != OF) {                    parse_warn (cfile, "expecting \"of\"");                    skip_to_semi (cfile);                    free_permit (permit, MDL);                    continue;                }                if (next_token (&val, (unsigned *)0,                        cfile) != STRING) {                    parse_warn (cfile,                            "expecting class name.");                    skip_to_semi (cfile);                    free_permit (permit, MDL);                    continue;                }                permit -> type = permit_class;                permit -> class = (struct class *)0;                find_class (&permit -> class, val, MDL);                if (!permit -> class)                    parse_warn (cfile,                            "no such class: %s", val);                break;                  case AFTER:                if (pool->valid_from || pool->valid_until) {                    parse_warn(cfile,                            "duplicate \"after\" clause.");                    skip_to_semi(cfile);                    free_permit(permit, MDL);                    continue;                }                t = parse_date_core(cfile);                permit->type = permit_after;                permit->after = t;                if (is_allow) {                    pool->valid_from = t;                } else {                    pool->valid_until = t;                }                break;                  default:                parse_warn (cfile, "expecting permit type.");                skip_to_semi (cfile);                break;            }            while (*permit_head)                permit_head = &((*permit_head) -> next);            *permit_head = permit;            parse_semi (cfile);            break;              case DENY:            permit_head = &pool -> prohibit_list;            /* remember the clause which leads to get_permit */            is_allow = 0;            goto get_permit;                                                                                                                                                                                                                                                                                                                                      case RBRACE:            next_token (&val, (unsigned *)0, cfile);            done = 1;            break;              case END_OF_FILE:            /*             * We can get to END_OF_FILE if, for instance,             * the parse_statement() reads all available tokens             * and leaves us at the end.             */            parse_warn(cfile, "unexpected end of file");            goto cleanup;              default:            declaration = parse_statement (cfile, pool -> group,                               POOL_DECL,                               (struct host_decl *)0,                               declaration);            break;        }    } while (!done);    /* See if there's already a pool into which we can merge this one. */    for (pp = pool -> shared_network -> pools; pp; pp = pp -> next) {        if (pp -> group -> statements != pool -> group -> statements)            continue;#if defined (FAILOVER_PROTOCOL)        ……#endif        if (!permit_list_match (pp -> permit_list,                    pool -> permit_list) ||            !permit_list_match (pool -> permit_list,                    pp -> permit_list) ||            !permit_list_match (pp -> prohibit_list,                    pool -> prohibit_list) ||            !permit_list_match (pool -> prohibit_list,                    pp -> prohibit_list))            continue;        /* Okay, we can merge these two pools.    All we have to           do is fix up the leases, which all point to their pool. */        for (lp = lpchain; lp; lp = lp -> next) {            pool_dereference (&lp -> pool, MDL);            pool_reference (&lp -> pool, pp, MDL);        }        break;    }    /* If we didn't succeed in merging this pool into another, put       it on the list. */    if (!pp) {        p = &pool -> shared_network -> pools;        for (; *p; p = &((*p) -> next))            ;        pool_reference (p, pool, MDL);    }    /* Don't allow a pool declaration with no addresses, since it is       probably a configuration error. */    if (!lpchain) {        parse_warn (cfile, "Pool declaration with no address range.");        log_error ("Pool declarations must always contain at least");        log_error ("one range statement.");    }cleanup:    /* Dereference the lease chain. */    lp = (struct lease *)0;    while (lpchain) {        lease_reference (&lp, lpchain, MDL);        lease_dereference (&lpchain, MDL);        if (lp -> next) {            lease_reference (&lpchain, lp -> next, MDL);            lease_dereference (&lp -> next, MDL);            lease_dereference (&lp, MDL);        }    }    pool_dereference (&pool, MDL);}

   个人水平有限,如有错误之处,欢迎指正。关于isc-dhcp还有什么方面讨论的也可以留言。