dhcp1.0源码分析,讲解dhcpd的源码流程。

Posted lvan_linux

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dhcp1.0源码分析,讲解dhcpd的源码流程。相关的知识,希望对你有一定的参考价值。

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:同样也是包的处理。
    
    
    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 ()
{
}

以上是关于dhcp1.0源码分析,讲解dhcpd的源码流程。的主要内容,如果未能解决你的问题,请参考以下文章

Vue源码 流程讲解

Android 7.0 Gallery图库源码分析2 - 分析启动流程

SpringBoot源码核心源码讲解

SpringMVC DispatcherServlet执行流程及源码分析

SpringCloud技术专题「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)

第三篇:Spark SQL Catalyst源码分析之Analyzer