isc-dhcp读配置文件探秘

   别的不多说,直接看代码(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_token
intern(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_token
peek_token(const char **rval, unsigned *rlen, struct parse *cfile) {
    return do_peek_token(rval, rlen, cfile, ISC_FALSE);
}

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

enum dhcp_token
do_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_token
get_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_token
next_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_token
get_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还有什么方面讨论的也可以留言。

你可能感兴趣的:(配置,DHCP)