别的不多说,直接看代码(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还有什么方面讨论的也可以留言。