关于c:dhcp10源码分析讲解dhcpd的源码流程

9次阅读

共计 12018 个字符,预计需要花费 31 分钟才能阅读完成。

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 公布

正文完
 0