别的不多说,直接看代码(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还有什么方面讨论的也可以留言。
转载于:https://blog.51cto.com/cizyzhang/1377532