1:首先阐明dhcpd代码下载方式,请参考我这篇文章dhcp源码下载
2:接下来咱们解析dhcp源码。(这里只是大体介绍dhcp源码)
a:首先咱们来剖析各个目录中的文件。
如图1所示有如下的各种文件。
alloc.c文件时管制内存的操作,咱们在其余文件中应用申请和开释内存都是通过这个文件中的函数来实现。
confpars文件用于解析配置文件内容的文件。也就是说,当咱们读取配置文件时,咱们调用这个文件中的函数
来接写配置文件中的内容。
dhcpd.c文件时DHCP服务程序的主流程管制,咱们次要剖析这个文件中的源码。
options.c文件用于解析咱们的option的,能够通过man手册查看dhcpd的option。(eg:man dhcpd)
socket.c是对套接字的封装,用于传递包的。
convert.c用于对一些类型的转换。
dhcpxlt.c同样用于对一些地址进行转换。
inet.c用于对网络地址进行转换。
packet.c用于对包的解决。
tables.c是一些表的治理(eg:dhcp硬件类型、option。咱们解析一些内容的时候回去这些表中进行查问)。
bpf.c:对于套解析一些ioctl设置(eg:设置关上的套接字传输速率)。
db.c:是对DHCP数据库的治理,也就是说将一些数据存储到文件中进行治理。
dispatch.c:这个文件设置。
memory.c:对dhcp用到的内存进行治理,例如咱们动静租任的IP
print.c:打印一些信息。
tree.c
conflex.c
errwarn.c:谬误和正告信息
raw.c:包的发送
upf.c:同样也是包的解决。
图1
b:在这里咱们先剖析一下DHCP包的内容,如图所示DHCP包的内容。如图1所示。option字段如图2所示
OP:报文的操作类型。分为申请报文和响应报文。1:申请报文,2:应答报文。即client送给server的封包,设为1,反之为2。申请报文: DHCP Discover、DHCP Request、DHCP Release、DHCP Inform和DHCP Decline。
应答报文: DHCP Offer、DHCP ACK和DHCP NAK。
Htype: DHCP客户端的MAC地址类型。 MAC地址类型其实是指明网络类型 ,Htype值为1时示意为最常见的以太网 MAC地址类型。
Hlen: DHCP客户端的MAC地址 长度。以太网MAC地址长度为6个字节,即以太网时Hlen值为6。
Hops:DHCP报文通过的DHCP中继的数目,默认为0。DHCP申请报文每通过一个DHCP中继,该字段就会减少1。没有通过DHCP中继时值为0。( 若数据包需通过router传送,每站加1,若在同一网内,为0。 )
Xid:客户端通过DHCP Discover报文发动一次IP地址申请时抉择的随机数,相当于申请标识。用来标识一次IP地址申请过程。在一次申请中所有报文的Xid都是一样的。
Secs:DHCP客户端从获取到IP地址或者续约过程开始到当初所耗费的工夫,以秒为单位。在没有取得IP地址前该字段始终为0。( DHCP客户端开始DHCP申请后所通过的工夫。目前尚未应用,固定为0。)
Flags:标记位,只应用第0比特位,是播送应答标识位,用来标识DHCP服务器应答报文是采纳单播还是播送发送,0示意采纳单播发送形式,1示意采纳播送发送形式。其余位 尚未应用 。(即 从0-15bits,最左1bit为1时示意server将以播送形式传送封包给client。 )
【留神】在客户端正式调配了IP地址之前的第一次IP地址申请过程中,所有DHCP报文都是以播送形式发送的,包含客户端发送的DHCP Discover和DHCP Request报文,以及DHCP服务器发送的DHCP Offer、DHCP ACK和DHCP NAK报文。当然,如果是由DHCP中继器转的报文,则都是以单播形式发送的。另外,IP地址续约、IP地址开释的相干报文都是采纳单播形式进行发送的。
Ciaddr:DHCP客户端的IP地址。仅在DHCP服务器发送的ACK报文中显示,在其余报文中均显示0,因为在失去DHCP服务器确认前,DHCP客户端是还没有调配到IP地址的。只有客户端是Bound、Renew、Rebinding状态,并且能响应ARP申请时,能力被填充。
Yiaddr:DHCP服务器调配给客户端的IP地址。仅在DHCP服务器发送的Offer和ACK报文中显示,其余报文中显示为0。
Siaddr:下一个为DHCP客户端调配IP地址等信息的DHCP服务器IP地址。仅在DHCP Offer、DHCP ACK报文中显示,其余报文中显示为0。( 用于bootstrap过程中的IP地址)
Giaddr:DHCP客户端发出请求报文后通过的第一个DHCP中继的IP地址。如果没有通过DHCP中继, 则显示为0。( 转发代理(网关)IP地址 )
Chaddr:DHCP客户端的MAC地址。在每个报文中都会显示对应DHCP客户端的MAC地址。
Sname:为DHCP客户端调配IP地址的DHCP服务器名称(DNS域名格局)。在Offer和ACK报文中显示发送报文的DHCP服务器名称,其余报文显示为0。
File:DHCP服务器为DHCP客户端指定的启动配置文件名称及门路信息。仅在DHCP Offer报文中显示,其余报文中显示为空。
Options: 可选项字段,长度可变,格局为"代码+长度+数据"。
c:接下来我这里介绍一下DHCP次要流程调用,如下图1所示
这里次要是指文件之间的调用,不做具体介绍。在接下来的局部会做具体介绍。(对于存储客户数据局部同样不做
具体介绍,只是对主题流程做一些具体的介绍)。
我当初这里列出这个文件中的函数:
dhcpd.c
#ifndef lint
static char ocopyright[] =
"$Id: dhcpd.c,v 1.36.2.1 1997/03/29 08:11:03 mellon Exp $ Copyright 1995, 1996 The Internet Software Consortium.";
#endif
//这部分能够不必管,因为这部分是对版权和一些音讯的定义。用到时权当字符串应用就能够了。
static char copyright[] =
"Copyright 1995, 1996 The Internet Software Consortium.";
static char arr [] = "All rights reserved.";
static char message [] = "Internet Software Consortium DHCPD $Name: V1-0-0 $";
/***这里我阐明这个dhcpd.h头文件
这个头文件定义了所有对于dhcp用到的函数,和一些公共的
全局变量等***/
#include "dhcpd.h"
//这个函数次要是在解析参数的时候显示一些应用的配置信息
static void usage PROTO ((void));
//如函数名字所示,次要是获取以后的工夫信息。
TIME cur_time;
//用于保留一些公共成员,如最大租任的工夫等,具体的看头文件中的变量名字即可。
struct group root_group;
//服务端的以太网地址。
struct iaddr server_identifier;
int server_identifier_matched;
#ifdef USE_FALLBACK
//定义接口的信息,在应用fallback时候。具体的我也不是很分明,这里只是剖析代码。
//如果想分明具体性能的时候,请参考rfcDHCP文档。
struct interface_info fallback_interface;
#endif
//服务端的端口
u_int16_t server_port;
//定义log的优先级,不同的优先级打印显示音讯是不同的。{eg,在有缓冲区输入的状况下,
优先级高的可能不期待缓冲区满就间接输入,例如stderror就是间接输入。而优先级低的可能须要期待缓冲区满再输入}
int log_priority;
//在debug的状况下,定义log_perror,这可可能影响一些输入。
#ifdef DEBUG
int log_perror = -1;
#else
int log_perror = 1;
#endif
//这里是一些配置文件的门路。如下别离是配置文件、数据存储文件、过程id文件。
char *path_dhcpd_conf = _PATH_DHCPD_CONF;
char *path_dhcpd_db = _PATH_DHCPD_DB;
char *path_dhcpd_pid = _PATH_DHCPD_PID;
//main函数,程序的入口,这个main函数中次要负责读取配置文件,初始化一些参数,之后调用外围dispatch函数用于ip的散发。
int main (argc, argv, envp)
int argc;
char **argv, **envp;
{
int i, status;
struct servent *ent;
char *s;
#ifndef DEBUG
int pidfilewritten = 0;
int pid;
char pbuf [20];
int daemon = 1;
#endif
//这一部分次要是显示一些音讯,对于剖析DHCP主流程无关紧要。然而如果想要剖析DHCP的log工作原理,就非常重要了。
/* Initially, log errors to stderr as well as to syslogd. */
#ifdef SYSLOG_4_2
openlog ("dhcpd", LOG_NDELAY);
log_priority = DHCPD_LOG_FACILITY;
#else
openlog ("dhcpd", LOG_NDELAY, DHCPD_LOG_FACILITY);
#endif
#ifndef DEBUG
#ifndef SYSLOG_4_2
setlogmask (LOG_UPTO (LOG_INFO));
#endif
#endif
note (message);
note (copyright);
note (arr);
//在这里次要是对命令行参数进行解析,依据不同的参数进行不同的初始化操作。具体的能够参考man dhcp进行比拟看。
//在这里我举一个例子,-d选项用于debug,如果咱们指定-d选项那么咱们将显示一些debug信息。
for (i = 1; i < argc; i++) {
if (!strcmp (argv [i], "-p")) {
if (++i == argc)
usage ();
for (s = argv [i]; *s; s++)
if (!isdigit (*s))
error ("%s: not a valid UDP port",
argv [i]);
status = atoi (argv [i]);
if (status < 1 || status > 65535)
error ("%s: not a valid UDP port",
argv [i]);
server_port = htons (status);
debug ("binding to user-specified port %d",
ntohs (server_port));
} else if (!strcmp (argv [i], "-f")) {
#ifndef DEBUG
daemon = 0;
#endif
} else if (!strcmp (argv [i], "-d")) {
#ifndef DEBUG
daemon = 0;
#endif
log_perror = -1;
} else if (!strcmp (argv [i], "-cf")) {
if (++i == argc)
usage ();
path_dhcpd_conf = argv [i];
} else if (!strcmp (argv [i], "-lf")) {
if (++i == argc)
usage ();
path_dhcpd_db = argv [i];
} else if (argv [i][0] == '-') {
usage ();
} else {
struct interface_info *tmp =
((struct interface_info *)
dmalloc (sizeof *tmp, "get_interface_list"));
if (!tmp)
error ("Insufficient memory to %s %s",
"record interface", argv [i]);
memset (tmp, 0, sizeof *tmp);
strcpy (tmp -> name, argv [i]);
tmp -> next = interfaces;
tmp -> flags = INTERFACE_REQUESTED;
interfaces = tmp;
}
}
#ifndef DEBUG
if (daemon) {
/* First part of becoming a daemon... */
if ((pid = fork ()) < 0)
error ("Can't fork daemon: %m");
else if (pid)
exit (0);
}
/* Read previous pid file. */
if ((i = open (path_dhcpd_pid, O_RDONLY)) >= 0) {
status = read (i, pbuf, (sizeof pbuf) - 1);
close (i);
pbuf [status] = 0;
pid = atoi (pbuf);
/* If the previous server process is not still running,
write a new pid file immediately. */
if (pid && kill (pid, 0) < 0) {
unlink (path_dhcpd_pid);
if ((i = open (path_dhcpd_pid,
O_WRONLY | O_CREAT, 0640)) >= 0) {
sprintf (pbuf, "%d\n", (int)getpid ());
write (i, pbuf, strlen (pbuf));
close (i);
pidfilewritten = 1;
}
}
}
#endif /* !DEBUG */
/* Default to the DHCP/BOOTP port. */
//在这里如果没有指定端口号的环,咱们将通过函数获取端口好的一些信息,当然默认是67。
if (!server_port)
{
ent = getservbyname ("dhcp", "udp");
if (!ent)
server_port = htons (67);
else
server_port = ent -> s_port;
endservent ();
}
//很容易了解,获取以后的工夫
/* Get the current time... */
GET_TIME (&cur_time);
/* 读取配置文件,在之后的会剖析这个函数 */
if (!readconf ())
error ("Configuration file errors encountered -- exiting");
/* 对数据库做一些初始化操作 */
db_startup ();
/* discover所有网络,并且初始化他们 */
discover_interfaces (1);
#ifndef DEBUG
/* If we were requested to log to stdout on the command line,
keep doing so; otherwise, stop. */
if (log_perror == -1)
log_perror = 1;
else
log_perror = 0;
if (daemon) {
/* Become session leader and get pid... */
close (0);
close (1);
close (2);
pid = setsid ();
}
/* If we didn't write the pid file earlier because we found a
process running the logged pid, but we made it to here,
meaning nothing is listening on the bootp port, then write
the pid file out - what's in it now is bogus anyway. */
if (!pidfilewritten) {
unlink (path_dhcpd_pid);
if ((i = open (path_dhcpd_pid,
O_WRONLY | O_CREAT, 0640)) >= 0) {
sprintf (pbuf, "%d\n", (int)getpid ());
write (i, pbuf, strlen (pbuf));
close (i);
pidfilewritten = 1;
}
}
#endif /* !DEBUG */
/* 这个函数将进行接管包,并且散发ip地址*/
dispatch ();
/* Not reached */
return 0;
}
/* 此函数用于打印一些应用的信息,在参数中能够指定显示实用信息,程序就会显示一些应用信息 */
static void usage ()
{
error ("Usage: dhcpd [-p <UDP port #>] [-d] [-f] [-cf config-file]%s",
"\n [-lf lease-file] [if0 [...ifN]]");
}
//此函数临时没有用到
void cleanup ()
{
}
图1
db.c文件:
/* db.c
Persistent database management routines for DHCPD... */
/*
* Copyright (c) 1995, 1996 The Internet Software Consortium.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of The Internet Software Consortium nor the names
* of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
* CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This software has been written for the Internet Software Consortium
* by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
* Enterprises. To learn more about the Internet Software Consortium,
* see ``http://www.vix.com/isc''. To learn more about Vixie
* Enterprises, see ``http://www.vix.com''.
*/
#ifndef lint
static char copyright[] =
"$Id: db.c,v 1.8.2.3 1997/12/02 09:30:38 mellon Exp $ Copyright (c) 1995, 1996 The Internet Software Consortium. All rights reserved.\n";
#endif /* not lint */
#include "dhcpd.h"
FILE *db_file;
static int counting = 0;
static int count = 0;
TIME write_time;
/* 写入一个指定租任无关的信息到数据库中. */
int write_lease (lease)
struct lease *lease;
{
struct tm *t;
char tbuf [64];
int errors = 0;
if (counting)
++count;
errno = 0;
fprintf (db_file, "lease %s {\n", piaddr (lease -> ip_addr));
if (errno) {
++errors;
}
t = gmtime (&lease -> starts);
sprintf (tbuf, "%d %d/%02d/%02d %02d:%02d:%02d;",
t -> tm_wday, t -> tm_year + 1900,
t -> tm_mon + 1, t -> tm_mday,
t -> tm_hour, t -> tm_min, t -> tm_sec);
errno = 0;
fprintf (db_file, "\tstarts %s\n", tbuf);
if (errno) {
++errors;
}
t = gmtime (&lease -> ends);
sprintf (tbuf, "%d %d/%02d/%02d %02d:%02d:%02d;",
t -> tm_wday, t -> tm_year + 1900,
t -> tm_mon + 1, t -> tm_mday,
t -> tm_hour, t -> tm_min, t -> tm_sec);
errno = 0;
fprintf (db_file, "\tends %s", tbuf);
if (errno) {
++errors;
}
if (lease -> hardware_addr.hlen) {
errno = 0;
fprintf (db_file, "\n\thardware %s %s;",
hardware_types [lease -> hardware_addr.htype],
print_hw_addr (lease -> hardware_addr.htype,
lease -> hardware_addr.hlen,
lease -> hardware_addr.haddr));
if (errno) {
++errors;
}
}
if (lease -> uid_len) {
int i;
errno = 0;
fprintf (db_file, "\n\tuid %x", lease -> uid [0]);
if (errno) {
++errors;
}
for (i = 1; i < lease -> uid_len; i++) {
errno = 0;
fprintf (db_file, ":%x", lease -> uid [i]);
if (errno) {
++errors;
}
}
putc (';', db_file);
}
if (lease -> flags & BOOTP_LEASE) {
errno = 0;
fprintf (db_file, "\n\tdynamic-bootp;");
if (errno) {
++errors;
}
}
if (lease -> flags & ABANDONED_LEASE) {
errno = 0;
fprintf (db_file, "\n\tabandoned;");
if (errno) {
++errors;
}
}
errno = 0;
fputs ("\n}\n", db_file);
if (errno) {
++errors;
}
if (errors)
note ("write_lease: unable to write lease %s",
piaddr (lease -> ip_addr));
return !errors;
}
/*将规范输入问价写入到lease文件中。同时做一些查看,如果写入次数
超过一千次或者在一个小时内没有被写入,那么咱们将从新写入一个新的数据库*/
int commit_leases ()
{
/* Commit any outstanding writes to the lease database file.
We need to do this even if we're rewriting the file below,
just in case the rewrite fails. */
if (fflush (db_file) == EOF) {
note ("commit_leases: unable to commit: %m");
return 0;
}
if (fsync (fileno (db_file)) < 0) {
note ("commit_leases: unable to commit: %m");
return 0;
}
/* If we've written more than a thousand leases or if
we haven't rewritten the lease database in over an
hour, rewrite it now. */
if (count > 1000 || (count && cur_time - write_time > 3600)) {
count = 0;
write_time = cur_time;
new_lease_file ();
}
return 1;
}
/*做lease的初始化,首先读取已有文件的lease内容。而后获取以后工夫。
最火新写入一个leases文件*/
void db_startup ()
{
/* Read in the existing lease file... */
read_leases ();
GET_TIME (&write_time);
new_lease_file ();
}
/*此文件用于创立一个新的leases问价,并将leases内容写入到新的文件中。
调用leases_write循环遍历以后所有的lease,一一写入到数据库中。*/
void new_lease_file ()
{
char newfname [512];
char backfname [512];
TIME t;
int db_fd;
/* If we already have an open database, close it. */
if (db_file) {
fclose (db_file);
}
/* Make a temporary lease file... */
GET_TIME (&t);
sprintf (newfname, "%s.%d", path_dhcpd_db, (int)t);
db_fd = open (newfname, O_WRONLY | O_TRUNC | O_CREAT, 0664);
if (db_fd < 0) {
error ("Can't create new lease file: %m");
}
if ((db_file = fdopen (db_fd, "w")) == NULL) {
error ("Can't fdopen new lease file!");
}
/* Write out all the leases that we know of... */
counting = 0;
write_leases ();
/* Get the old database out of the way... */
sprintf (backfname, "%s~", path_dhcpd_db);
if (unlink (backfname) < 0 && errno != ENOENT)
error ("Can't remove old lease database backup %s: %m",
backfname);
if (link (path_dhcpd_db, backfname) < 0)
error ("Can't backup lease database %s to %s: %m",
path_dhcpd_db, backfname);
/* Move in the new file... */
if (rename (newfname, path_dhcpd_db) < 0)
error ("Can't install new lease database %s to %s: %m",
newfname, path_dhcpd_db);
counting = 1;
}
dispatch.c:此文件用于散发IP地址。
本文由博客群发一文多发等经营工具平台 OpenWrite 公布
发表回复