UNIX source code-DHCP

DHCP

基础知识

什么是DHCP

  • DHCP:Dynamic Host Configuration Protocol 动态主机配置协议
  • 作用:集中管理、分配IP地址,使得所管辖的网络环境中的主机动态的获取IP地址,Gateway地址、DNS地址等信息。
  • 应用层协议(port: 67[Server]、68[Client])

为什么要使用DHCP

​ (IP不够用)每台主机都需要分配唯一的IP地址,才可以进行网络通信。通过手动配置的情况往往会出现IP地址冲突导致通信异常。而DHCP也使得IP地址配置准确化,管理自动化,大大提升了效率。

  • 准确的IP配置
  • 减少IP地址冲突
  • IP地址管理的自动化
  • 高效的变更管理

IP地址分配机制

  • 自动分配方式(Automatic Allocation

    DHCP服务器为主机指定一个永久性的IP地址,一旦DHCP客户端第一次成功从DHCP服务器端租用到IP地址后,就可以永久性的使用该地址。

  • 动态分配方式(Dynamic Allocation

    DHCP服务器给主机指定一个具有时间限制的IP地址,时间到期或主机明确表示放弃该地址时,该地址可以被其他主机使用。

  • 手工分配方式(Manual Allocation

    客户端的IP地址是由网络管理员指定的,DHCP服务器只是将指定的IP地址告诉客户端主机。

    自动分配和动态分配的区别?

工作原理

网上资源图片,侵权必删

报文类型

DHCP报文类型(Client–>Server)作用
DHCP Discover客户端来查找可用的服务器
DHCP Request客户端发送给服务器来请求配置参数或者请求配置确认或者续借租期
DHCP Decline客户端发现地址已经被使用时,用来通知服务器
DHCP Inform客户端已经有ip地址时来请求其他的配置参数
DHCP Release客户端要释放地址时用来通知服务器
DHCP报文类型(Server–>Client)作用
DHCP offer服务器用来响应客户端的DHCP Discover报文,并指定相应的配置参数
DHCP ACK由服务器对客户端的Request报文的确认响应,含有配置参数acknowledge
DHCP NAK由服务器对客户端的Request报文的拒绝响应,告知请求失败,无法分配合适的IP地址 negative acknowledge

工作大致步骤:

  1. 客户端通过广播发送DHCP Discover报文寻找服务器端

  2. 服务器通过单播发送DHCP Offer报文向客户提供IP地址等信息

  3. 客户端通过广播发送DHCP Request报文告知服务端本地选择使用哪个IP

  4. 服务器通过单播发送DHCP Ack报文告知客户端IP地址是合法可用的

    (无ip,服务器通过单播发送DHCP Nak报文告知客户端IP地址无法分配)

  5. 客户端通过广播发送DHCP Release报文来释放IP地址

    • 为什么客户端是广播,而服务器端是单播?

    • 步骤1的广播之后的步骤2的单播是为什么可以定位到客户端的? ANY

基本步骤

  1. DHCP Discover

    • source ip:0.0.0.0
    • destination ip:255.255.255.255
    • 含有Client的MAC地址(chaddr字段)

    注意:请求参数列表中有 广播标志位flags,可以决定服务器单播0还是广播1发送响应报文

    • 如果是单播响应,那么目标ip应该是什么? ANY
  2. DHCP Offer

    • source ip:DHCP server(option字段)
    • destination ip:255.255.255.255(广播)/ Any(单播)
    • 含有分配给客户端IP地址(yiaddr字段)等配置信息
  3. DHCP Request

    • source ip:0.0.0.0
    • destination ip:255.255.255.255
    • 含有选择了的DHCP Server和IP
  4. DHCP ACK

    • source ip:DHCP server

    • destination ip:255.255.255.255(广播)/Any(单播)

    • 客户端接收之后会进行冲突检测,广播发送免费ARP报文验证该IP地址是否被使用

      ARP和免费ARP(Gratuitous:无端的、免费的)Address Resolution Protocol

      • 包的结构不同

        报文的区别在于报文中的目标 IP 地址。

      • 作用不同

        1. 宣告作用
        2. 检测 IP 地址冲突
        3. 更新其他主机的 ARP 缓存表

中继

请求和提供阶段会有中继检查,之后两阶段基本一致

在这里插入图片描述

  1. DHCP Discover(Client)
    • 检查报文hops字段(中继跳数),超过16,丢弃报文(每经过中继,hops字段+1)
    • 检查报文giaddr字段,如果为0,设置为Discover报文的接口IP。
      • giaddr填入第一个中继的IP,后面(中继)不再修改
      • 目的IP改为下一中继的IP或者Server的IP
      • 源IP改为中继连接Client的接口IP
  2. DHCP Offer(Server)
    • 根据giaddr字段,分配同一网段的IP
    • Server单播发送给giaddr指定的中继
    • 中继检查是否一致,否则直接丢弃

重用IP

前提:Client并非首次连入该网络

在实际情况中,我们发现 DHCP Client 重启后,也能获得相同的 IP 地址。那是因为DHCP Server 为 DHCP Client 分配 IP 地址时,采用如下的顺序:

  1. DHCP Server 中与 DHCP Client 的 MAC 地址静态绑定的 IP 地址;
  2. DHCP Client 曾经使用过的 IP 地址;
  3. 最先找到的可用 IP 地址。
  4. 如果没找到可用的 IP 地址,就依次查询超过租期、发生冲突的 IP 地址,如果找到就进行分配,否则报错处理。
  1. DHCP Request(广播)
    • 包含原IP地址信息(”带着目的去申请“)
  2. DHCP ACK or DHCP NAK
    • Server(根据MAC)查看记录,有记录则ACK,无记录则NAK或者沉默(和“我”无关)

租赁期限

  1. T1(50%)

    • Client单播向Server发送DHCP Request,请求更新

      Server

      • 有回复 DHCP ACK更新成功

        ​ DHCP NAK更新失败,并且停止使用(Client需要Discover)

      • 无回复 继续使用,继续等待回复,等到87.5%

  2. T2(87.5%)

    • Client广播向Server发送DHCP Request,请求更新

      Server

      • 有回复 DHCP ACK更新成功

        ​ DHCP NAK更新失败,并且停止使用(Client需要Discover)

      • 无回复 继续使用,继续等待回复,等到100%

  3. 100%

    • Client停止使用当前IP,广播DHCP Discover

代码解析

文件作用

file namerole
arrping.c分配IP时候会检测IP是否冲突
clientpacket.cDHCP client报文的构造和发送
clientsocket.cDHCP client 套接字的建立
common.c用于调试和记录日志以及其他帮助函数
dhcpcmain.cClient的入口
dhcpdmain.cServer的入口
dhcprelay.cDHCP中继
domain_codec.c
dumpleases.c
files.c负责服务器的文件操作,通过read_config读取配置信息
getopt32.c命令行参数解析(Busybox)
leases.c针对DHCP offer结构体的操作函数,服务器向客户端提供租约信息
llist.c链表辅助函数
options.c针对DHCP报文的options字段的操作函数
packet.cDHCP数据报文的构造和发送
parse_config.c解析配置信息
script.c调用DHCP client通知脚本的函数
serverpacket.c数据报文发送之前,根据不同的情况对各个字段的填充相应的信息
socket.c套接字的创建,interface信息的读取
static_leases.c对在dhcpd.h里定义的struct static_lease结构体的相应操作函数
udhcpc.cDHCP client运行的主线,实现DHCP客户端的功能
udhcpd.cDHCP server运行的主线,实现DHCP服务器的功能

udhcpd.c

udhcp DHCP Server

能够接收并解析DHCP客户端直接发送或经由DHCP中继转发的 DHCP 请求报文,根据报文请求的内容并结合管理员的配置策略,为 DHCP 客户端进行一定范围内的IP 地址租约或网络参数分配,或对 IP 地址租约进行续约,或释放客户端不再需要的 IP 地址租约,从而保证该 IP 地址能够及时分配给其它有需要的 DHCP 客户端。

结构体
struct server_config_t {
	uint32_t server;                /* Our IP, in network order */ 我们的IP 网络字节序
#if ENABLE_FEATURE_UDHCP_PORT
	uint16_t port;
#endif
	/* start,end are in host order: we need to compare start <= ip <= end */
	uint32_t start_ip;             /* Start address of leases, in host order */ 租赁的开始地址 主机字节序
	uint32_t end_ip;               /* End of leases, in host order */租赁的结束 主机字节序
	struct option_set *options;     /* List of DHCP options loaded from the config file */从配置文件加载的DHCP选项列表
	char *interface;                /* The name of the interface to use */要使用的接口名称
	int ifindex;                         /* Index number of the interface to use */要使用的接口索引号
	uint8_t arp[6];                  /* Our arp address */我们的arp地址
	char remaining;                /* should the lease file be interpreted as lease time remaining, or
	                                            /* as the time the lease expires */租约剩余时间或者租约到期的时间
	uint32_t lease;	          /* lease time in seconds (host order) */租赁时间(秒)主机字节序
	uint32_t max_leases;      /* maximum number of leases (including reserved address) */最大组约数(包含保留地址)
	uint32_t auto_time;        /* how long should udhcpd wait before writing a config file.在写入配置文件之前需要等待多久
	                                            /* if this is zero, it will only write one on SIGUSR1 */
	uint32_t decline_time;   /* how long an address is reserved if a client returns a decline message */如果客户端返回“”Deline“则保留地址多长时间
	uint32_t conflict_time;   /* how long an arp conflict offender is leased for */ARP冲突应该租赁多长时间
	uint32_t offer_time;       /* how long an offered address is reserved */保留提供地址多长时间
	uint32_t min_lease;        /* minimum lease a client can request */客户可要求的最低限度租约
	char *lease_file;
	char *pidfile;
	char *notify_file;              /* What to run whenever leases are written */每当编写租约时运行什么
	char *offer_file;	          /*What to run whenever Ack are sent*/每当发送ACK运行什么
	uint32_t siaddr;                /* next server bootp option */下一个服务器引导选项
	char *sname;                    /* bootp server name */bootp 服务名称
	char *boot_file;                /* bootp boot file option */bootp引导文件选项
	struct static_lease *static_leases; /* List of ip/mac pairs to assign static leases */IP-MAC的列表来分配静态租赁
	uint32_t static_netmask;
	uint32_t static_router;
};
  • start_ip–end_ip

    表示了可分配的地址空间 (IP地址池)

  • struct option_set *options

    struct option_set {
    	uint8_t *data;
    	struct option_set *next;
    };
    

    选项集合(链表存储),决定了dhcpMessage中options的填写。

  • 租赁相关

    lease

    client请求的租赁期限最大值,>lease,就以lease为租赁期限;

    client请求未指明租赁期限,lease作为其租赁期限(静态租赁的默认租期)。

    decline_time

    DHCP Decline发给server,server将IP地址添加到动态租赁数组中,租期为decline_time

    confict_time

    DHCP Offer之前serverIP检测,IP不可用,添加到动态租赁数组中,租期为confict_time

    offer_time

    DHCP Offer,server将IP-MAC对添加到动态租赁表里,还未确定,offer_time(默认60s)

    min_lease

    client请求租赁的最小时间

  • 静态租赁

    struct static_lease *static_lease

    struct static_lease{
           uint8_t  *mac;
           uint32_t *ip;
           struct static_lease   *next;
    };
    

    DHCP的自动分配地址就是使用该结构体(也叫静态配置)

代码逻辑(流程)
  1. set up parameters(server_config)

    • read_config(files.c) 配置信息(租赁时间,IP地址池等等)
  2. 将pid写入pid_file

    • write_pidfile(getopt32.c)
  3. 根据server_config的option设置lease

    • find_option(files.c)
  4. 初始化和配置leases

    • read_leases(files.c)
  5. 读取接口信息

    • read_interface(socket.c)
  6. 创建内部socket通信,注册信号处理器

    • udcp_sp_setup(signalpipe.c) AF_UNIX
  7. 进入循环,进行DHCP的服务

    • 建立socket监听和single处理器

      这一步的主要目的就是对server_socket和socketpair建立监听,并根据对socketpair的信号情况及leases结构的内容,执行write_leases函数,该函数将最新的leases结构体里的内容写入lease_file,根据yiaddr、remaining和当前时间来更新lease_time,每次执行完write_leases函数和,都有更新time_end时刻,write_leases定义于files.c中。

      • listen_socket

      • select + FD宏函数(更好的操作fd_set)

      /* 添加server_socket和signal_pipe进rfds集合*/
      max_sock = udhcp_sp_fd_set(&rfds, server_socket);
      /* int udhcp_sp_fd_set(fd_set *rfds, int extra_fd)
      {
          FD_ZERO(rfds);
          FD_SET(signal_pipe[0], rfds);
          if (extra_fd >= 0) {
          fcntl(extra_fd, F_SETFD, FD_CLOEXEC);
          FD_SET(extra_fd, rfds);
      }
      return signal_pipe[0] > extra_fd ? signal_pipe[0] : extra_fd;*/
      /* 如果auto_time不为0,更新等待时间tv.tv_sec为auto_time的剩余时间 */
      if (server_config.auto_time) {
          tv.tv_sec = timeout_end - monotonic_sec();
          tv.tv_usec = 0;
      }
      /* 如果auto_time为0,或tv_sec大于0时,建立select等待server_socket和signal_pipe的信号 */
      if (!server_config.auto_time || tv.tv_sec > 0) {
          max_sock = server_socket > signal_pipe[0] ? server_socket : signal_pipe[0];
          /* 对两个fd都进行可读性检测 */
      retval = select(max_sock + 1, &rfds, NULL, NULL, 
                  /*如果auto_time不为0,则非阻塞,超时时间为上述的剩余时间;如果为0,则time设为NULL,select将一直阻塞直到某个fd上接收到信号*/
      server_config.auto_time ? &tv : NULL);
      } else retval = 0; /* If we already timed out, fall through */
      /* 若直到超时都没有接收到信号,则立即写lease文件,并更新time_end */
      if (retval == 0) {
          write_leases();
          timeout_end = monotonic_sec() + server_config.auto_time;
          continue;
      }
      if (retval < 0 && errno != EINTR) {
          DEBUG("error on select");
          continue;
      }
      
          /* 若signal_pipe接收到可读signal(signal_handler将signal写入signal_pipe[1],根据socketpair的特性,此时signal_pipe[0]将可读,产生一个可读的信号) */
      switch (udhcp_sp_read(&rfds)) {
      /* 接收到SIGUSR1,立即写leases,并更新time_end */
          case SIGUSR1:
              bb_info_msg("Received a SIGUSR1");
              write_leases();
              /* why not just reset the timeout, eh */
              timeout_end = monotonic_sec() + server_config.auto_time;
          continue;
          case SIGTERM:
              bb_info_msg("Received a SIGTERM");
              goto ret0;
          case 0: break;      /* no signal */
          default: continue;  /* signal or error (probably EINTR) */
      }
      
  8. 获取和提取报文

    • udhcp_get_kernel_packet

    • get_option获取状态state信息

    • getIPbyMac寻找静态IP

    • 根据state和packet内容响应报文

      • DHCPDISCOVER : send_offer

      • DCHPREQUEST

      • DHCPDECLINE

      • DHCPRELEASE

      • DHCPINFORM

udhcpc.c

udhcp DHCP client

用来获取 IP 地址或其它网络资源的主机,它会维持一个有限状态机,一旦启动 DHCP 客户端 DHCP 功能,它就会在状态机的控制下完成 IP 地址租约的申请、刷新和释放等功能,同时也会完成对其它自身所需的网络资源的获取。

侵权必删

结构体
struct dhcpMessage { /*DHCP报文格式*/
	uint8_t op; //操作码 1:请求报文 2:响应报文 具体报文类型在option字段标识
	uint8_t htype;//硬件地址类型 
	uint8_t hlen;//硬件地址长度
	uint8_t hops;//DHCP保文经过的中继数量
	uint32_t xid;//处理ID,随机数,顺序号
	uint16_t secs;//从获取IP或者续约开始到现在所消耗的时间
	uint16_t flags;//标志广播还是单播 0:单播 1:广播
	uint32_t ciaddr;//客户机IP client自己的IP 或者 server分配给client的IP
	uint32_t yiaddr;//客户机IP server分配给client的IP
	uint32_t siaddr;//下一个为DHCP客户端分配IP的DHCP服务器IP
	uint32_t giaddr;//DHCP客户端发出请求报文后经过的第一个DHCP中继的IP地址
	uint8_t chaddr[16];//DHCP客户端的MAC地址
	uint8_t sname[64];//为DHCP客户端分配IP地址的DHCP服务器名称(DNS域名格式)
	uint8_t file[128];//DHCP服务器为DHCP客户端指定的启动配置文件名称及路径信息
	uint32_t cookie;//使得客户端和服务端清楚后面的解释模式,即options field
	uint8_t options[DHCP_OPTIONS_BUFSIZE + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS];
    //可选参数字段:这个字段包含了终端的初始配置信息和网络配置信息,包括报文类型,有效租期,DNS服务器的IP地址等配置信息
} PACKED;

/* 
允许厂商定议选项( Uendor-Specific Area),
以提供更多的设定信息(如:Netmask、Gateway、DNS、Subnet等等)。其长度可变,可同时带多个选项。



在选项字段里面里三类信息:
	1-DHCP_PADDING填充字节
	2-DHCP_END选项结束字节
	3-选项信息,<CLV>code(1B) + length(1B) + value(length决定)
*/

typedef struct client_config_tt {
	uint8_t arp[6];                 /* Our arp address */ arp地址
	/* TODO: combine flag fields into single "unsigned opt" */ 合并标识字段为单个无符号opt
	/* (can be set directly to the result of getopt32) */
	char no_default_options;        /* Do not include default optins in request */不包括默认的options请求
	USE_FEATURE_UDHCP_PORT(uint16_t port;)
	int ifindex;                    /* Index number of the interface to use */ 使用接口的索引号
	uint8_t opt_mask[256 / 8];      /* Bitmask of options to send (-O option) */发送选项的位掩码(-o选项)
	const char *interface;          /* The name of the interface to use */使用接口的名字
	char *pidfile;                  /* Optionally store the process ID */可选择地存储进程ID
	const char *script;             /* User script to run at dhcp events */在dhcp事件处运行的用户脚本
	struct option_set *options;     /* list of DHCP options to send to server */要发送到服务器的DHCP选项列表
	uint8_t *clientid;              /* Optional client id to use */要选择的客户端ID使用
	uint8_t *vendorclass;           /* Optional vendor class-id to use */可选供应商类别-id使用
	uint8_t *hostname;              /* Optional hostname to use */可选主机名使用
	uint8_t *fqdn;                  /* Optional fully qualified domain name to use */可选的完全限定域名使用
} client_config_t;
代码逻辑(流程)
  1. 初始化和配置,对传入参数进行解析和处理

  2. 读取接口信息,udhcp_run_script

  3. 建立socket和pipe,并进行监听

  4. 超时未收到信号

file.c

DHCP server file manipulation

  • read_config函数初始化server配置信息server_config

  • read_lease函数

结构体(read_config)
struct config_keyword {
	const char *keyword; //关键字 辅助读取配置信息 对应server_config_t(udhcpd.c)的成员变量名
	int (*handler)(const char *line, void *var);//处理操作
	void *var;//存储位置
	const char *def;//默认信息
};

关键:对照keyword,去var指定的位置进行对应的操作handler,填上line(也可以是def) 消息地图

static const struct config_keyword keywords[] = {
	/* keyword       handler   variable address               default */
	{"start",        read_ip,  &(server_config.start_ip),     "192.168.0.20"},
	{"end",          read_ip,  &(server_config.end_ip),       "192.168.0.254"},
	{"interface",    read_str, &(server_config.interface),    "eth0"},
	/* Avoid "max_leases value not sane" warning by setting default
	 * to default_end_ip - default_start_ip + 1: */
	{"max_leases",   read_u32, &(server_config.max_leases),   "235"},
	{"remaining",    read_yn,  &(server_config.remaining),    "yes"},
	{"auto_time",    read_u32, &(server_config.auto_time),    "7200"},
	{"decline_time", read_u32, &(server_config.decline_time), "3600"},
	{"conflict_time",read_u32, &(server_config.conflict_time),"3600"},
	{"offer_time",   read_u32, &(server_config.offer_time),   "60"},
	{"min_lease",    read_u32, &(server_config.min_lease),    "60"},
	{"lease_file",   read_str, &(server_config.lease_file),   LEASES_FILE},
	{"pidfile",      read_str, &(server_config.pidfile),      "/var/run/udhcpd.pid"},
	{"siaddr",       read_ip,  &(server_config.siaddr),       "0.0.0.0"},
	/* keywords with no defaults must be last! */
	{"option",       read_opt, &(server_config.options),      ""},
	{"opt",          read_opt, &(server_config.options),      ""},
	{"notify_file",  read_str, &(server_config.notify_file),  ""},
	{"offer_file",  read_str, &(server_config.offer_file),  ""},
	{"sname",        read_str, &(server_config.sname),        ""},
	{"boot_file",    read_str, &(server_config.boot_file),    ""},
	{"static_lease", read_staticlease, &(server_config.static_leases), ""},
	/* ADDME: static lease */
	{"static_netmask", read_ip, &(server_config.static_netmask), ""},
	{"static_router", read_ip, &(server_config.static_router), ""},
};
  • 各信息操作函数的作用
    • read_ip 将字符串格式的IP转换成IP格式
    • read_u32 将字符串格式的数转换成数字
    • read_yn line字符串yes:var(1),no:var(0)
    • read_str 释放var原值,再根据line大小申请空间,赋新值
    • read_opt line读取options,写到所指向的struct options_set链表中
    • read_staticlease line读取MAC、IP地址,静态绑定写入static_lease链表中
    • read_mac 将字符串格式的MAC转换成MAC
代码逻辑(流程)
  1. 准备读取
  2. 配置默认信息
  3. 从配置文件(/etc/udhcpd.conf)读取配置信息line
  4. 配置得到的信息(IP地址池、接口、租期等非默认信息)
函数关系
  1. 准备读取

    • 准备工作(文件描述符,记录行值等)
  2. 配置默认信息

    • 结构体中的对应的操作函数
  3. 从配置文件(/etc/udhcpd.conf)读取配置信息

    line(对应def,此处便是区别默认,该变量被封装在parser_t结构体中)

    tokens(对应keyword)

    • config_open

    • config_read

  4. 配置得到的信息(IP地址池、接口、租期等非默认信息)

    • 使用3读取出来的配置信息,进行比对,然后使用操作函数处理
结构体(read_lease)
struct dhcpOfferedAddr {
	uint8_t hostname[16]; 主机名
	uint8_t chaddr[16]; Client MAC
	uint32_t yiaddr;	/* network order */租赁IP
	uint32_t expires;	/* host order */租赁IP的到期时间
	uint8_t vendor[16]; /*add by liusong 2011-08-08 for hy required*/
};

目的:dhcp server启动后(可能是异常重启)读取文件,获取与租赁相关的信息

代码逻辑(流程)
  • 同上(read_config)
函数关系
  • 同上(read_config)

leases.c

tools to manage DHCP leases

结构体
struct dhcpOfferedAddr {
	uint8_t hostname[16]; 主机名
	uint8_t chaddr[16]; Client MAC
	uint32_t yiaddr;	/* network order */租赁IP
	uint32_t expires;	/* host order */租赁IP的到期时间
	uint8_t vendor[16]; /*add by liusong 2011-08-08 for hy required*/
};
代码逻辑(模块)
  • add_lease

  • find_lease_by_chaddr

  • find_lease_by_yiaddr

  • find_address

    使用场景:该函数的调用是在,server端接收到DHCPDISOCVER的报文的时候,会为client提供一个IP地址:

    • server首先利用client的MAC地址在leases数组里查找该client以前是否在这里租赁过IP,租赁过的话,把以前的IP提供给client

    • 第一种情况不满足的话,server会检查DHCPDISCOVER报文的选项字段,client是否有请求的IP(该选项信息的CODE :DHCP_REQUESTED_IP),有的话检查该IP是否为Free,可以的话把Request IP提供给client。

    上面两种情况都不满足的话,就调用find_address这个函数了

options.c

DHCP server option packet tools

结构体
struct dhcp_option {
	uint8_t flags;
	uint8_t code;
};

options字段存储三类数据:

  • DHCP_PADDING 填充字节, 没有任何意义,填充 0x00
  • DHCP_END potions字段结束的标志 0xFF
  • 选项信息 对于DHCP过程真正有价值的信息,承载了选项数据(V)
代码逻辑
  • get_option

     * 参数struct dhcpMessage *packet DHCP报文  
     * int code需要获得什么选项信息(选项信息的标识) 
     * 
     * 返回指向选项信息的指针(去除了 OPT_CODE,OPT_LEN) 
     * 未找到返回NULL 
     */  
    uint8_t *get_option(struct dhcpMessage *packet, int code)  
    {  
        int i, length;  
        uint8_t *optionptr;  
        int over = 0, done = 0, curr = OPTION_FIELD;  
      
        optionptr = packet->options;  
        i = 0;  
        length = 308;   /* 整个options字段的长度308 */  
      
        /* 在options字段里查找code选项标识信息*/  
        while (!done) {  
            if (i >= length) {   /* 查找完所有字段都未找到code标识的信息,返回NULL */  
                LOG(LOG_WARNING, "bogus packet, option fields too long.");  
                return NULL;  
            }  
      
            //CLV方式存储数据  
            //这里与struct option_set的data存储相似  
            //OPT_CODE字节上存储code标识  
            //OPT_LEN 字节上存储信息长度  
            //OPT_LEN后就是存储信息  
      
            if (optionptr[i + OPT_CODE] == code) {  //Found  
                if (i + 1 + optionptr[i + OPT_LEN] >= length) {  //检查选项信息长度  
                    LOG(LOG_WARNING, "bogus packet, option fields too long.");  
                    return NULL;  
                }  
                return optionptr + i + 2;   //Found,返回选项信息的首地址  
            }  
              
            switch (optionptr[i + OPT_CODE]) {  
            case DHCP_PADDING:  //DHCP_PADING(填充)字节,直接 i++;  
                i++;  
                break;  
            case DHCP_OPTION_OVER:  //选项过载DHCP_OPTION_OVER  
                if (i + 1 + optionptr[i + OPT_LEN] >= length) {  
                    LOG(LOG_WARNING, "bogus packet, option fields too long.");  
                    return NULL;  
                }  
      
                /* 
                    optionptr[i + OPT_CODE] == DHCP_OPTION_OVER选项过载; 
                    optionptr[i + 3]存放了采用哪个字段来存储过载的选项 
                    可能存储过载选项的字段: 
                        uint8_t sname[64]; //server host name (ASCIZ)  
                        uint8_t file[128];  // boot file name (ASCIZ)  
     
                    over = optionptr[i + 3];记录下使用那个字段存储过载选项 
                */  
      
      
                /* 
                 * 
                The code for this option is 52, and its length is 1.  Legal values 
                for this option are: 
                 
                        Value   Meaning 
                        -----   -------- 
                          1     the 'file' field is used to hold options 
                          2     the 'sname' field is used to hold options 
                          3     both fields are used to hold options 
                 
                 Code   Len  Value 
                +-----+-----+-----+ 
                |  52 |  1  |1/2/3| 
                +-----+-----+-----+ 
                */  
      
                over = optionptr[i + OPT_DATA];  
                i += optionptr[i + OPT_LEN] + 2;  
                  
            //  over = optionptr[i + 3];      /* Error */  
            //  i += optionptr[OPT_LEN] + 2;  /* Error */  
                break;  
            case DHCP_END:  //选项字段结束标志 DHCP_END 0xff  
      
                /*  
                 * 当选项过载的时候(curr == OPTION_FILE允许选项过载) 
                 *  首先用file字段,不够的话再用sname字段 
                 *  使用file字段的时候: 
                 *      over的右起的第0位必须为1 
                 *  使用sname字段: 
                 *      over的右起的第一位必须为1 
                 */  
                if (curr == OPTION_FIELD && over & FILE_FIELD) {  
                    optionptr = packet->file;  
                    i = 0;  
                    length = 128;  
                    curr = FILE_FIELD;  
                } else if (curr == FILE_FIELD && over & SNAME_FIELD) {  
                    optionptr = packet->sname;  
                    i = 0;  
                    length = 64;  
                    curr = SNAME_FIELD;  
      
                            //没有或不允许选项过载或over(options[i + 3])标志不允许,结束查找返回NULL  
                } else done = 1;  
                break;  
      
      
            /* 
             * 不是填充信息:DHCP_PADDING 
             * 选项过载:DHCP_OPTION_OVER 
             * 选项结束:DHCP_END 
             * 
             * 表明是属于选项信息,所以可以直接改变偏移量: 
             * i += option[OPT_LEN + i] + 2; 
             */  
            default:  
                i += optionptr[OPT_LEN + i] + 2;  
            }  
        }  
        return NULL;  
    } 
    
  • end_options

    /* return the position of the 'end' option (no bounds checking) */  
    int end_option(uint8_t *optionptr)  
    {  
        int i = 0;  
      
        /* 在选项字段里找到DHCP_END的偏移 */  
        /* 在选项字段里面里三类信息: 
            1.DHCP_PADDING 填充字节 
            2.DHCP_END  选项结束字节 
            3.选项信息<CLV> code + length + value 
            */  
        while (optionptr[i] != DHCP_END) {  
            if (optionptr[i] == DHCP_PADDING) i++;  //填充字节DHCP_PADDING  
            else i += optionptr[i + OPT_LEN] + 2;   //选项信息  
        }  
        return i;  
    }  
    
  • add_option_string

    /* add an option string to the options (an option string contains an option code, 
     * length, then data) */  
    int add_option_string(uint8_t *optionptr, uint8_t *string)  
    {  
        int end = end_option(optionptr);//找到DHCP_END在选项字段里偏移  
      
        /* end position + string length + option code/length + end option */  
            //检查需要添加的选项信息后的长度是否大于选项字段的最大长度  
        if (end + string[OPT_LEN] + 2 + 1 >= 308) {  
            LOG(LOG_ERR,"Option 0x%02x did not fit into packet!",string[OPT_CODE]);  
            return 0;  
        }  
        DEBUG(LOG_INFO, "adding option 0x%02x", string[OPT_CODE]);  
        memcpy(optionptr + end, string, string[OPT_LEN] + 2);  
        optionptr[end + string[OPT_LEN] + 2] = DHCP_END;//在<CLV>的最后添加上DHCP_END  
        return string[OPT_LEN] + 2; //返回<CLV>长度  
    }  
    
  • add_simple_option

    /* add a one to four byte option to a packet */  
    /* add_simple_option函数只能想选项字段添加OPT_LEN = 4 bytes的选项 */  
    /*  optionptr: 报文选项字段的首地址 
        code:   选项code 
        data:   选项value 
     
        返回值是 <CLV>的长度 
        返回0 表示添加失败 
        */  
    int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data)  
    {  
        struct dhcp_option *dh;  
      
        /* 检查需要添加的选项是否符合标准的选项 */  
        /* 在dhcp_options数组里查找 */  
        for (dh=dhcp_options; dh->code; dh++) {  
            if (dh->code == code) {      //Found  
                uint8_t option[6], len;  
      
                option[OPT_CODE] = code;    //添加code  
                len = option_lengths[dh->flags & TYPE_MASK];//计算length  
                option[OPT_LEN] = len;      //添加length  
      
                            /*  
                             * 假设data长度是一个字节,但在大端字节序的机器里 
                                 * 但存放在uint32_t里的放在最高地址的地方, 
                                 * 所以data << 8 * (4 - len) 把她移到低位 
                                 */  
                if (BB_BIG_ENDIAN) data <<= 8 * (4 - len);  
      
                /* This memcpy is for broken processors which can't 
                 * handle a simple unaligned 32-bit assignment */  
                memcpy(&option[OPT_DATA], &data, 4);  
                return add_option_string(optionptr, option);  
            }  
        }  
      
        DEBUG(LOG_ERR, "Could not add option 0x%02x", code);  
        return 0;  
    }  
    

arpping.c

检测IP是否被使用

  • DHCP Server发出DHCP Offer之前,会进行IP检测

  • DHCP Client得到Server的ACK后,进行IP检测 (gratuitous 免费ARP)

结构体
typedef	struct arpMsg {
		/* Ethernet header */ 以太网帧头
		uint8_t  h_dest[6];     /* 00 destination ether addr */ 目的MAC地址  FF-FF-FF-FF-FF-FF
		uint8_t  h_source[6];   /* 06 source ether addr */   源MAC地址 发送ARP节点的MAC
		uint16_t h_proto;       /* 0c packet type ID field */  报文类型ID字段
	
		/* ARP packet */
		uint16_t htype;         /* 0e hardware type (must be ARPHRD_ETHER) */ 硬件类型 必须是ARPHRD_ETHER
		uint16_t ptype;         /* 10 protocol type (must be ETH_P_IP) */ 协议类型 必须是ETH_P_IP(ip协议)
		uint8_t  hlen;          /* 12 hardware address length (must be 6) */ MAC 6Byte
		uint8_t  plen;          /* 13 protocol address length (must be 4) */  IP 4Byte
		uint16_t operation;     /* 14 ARP opcode */ ARP 操作码 1 表示请求报文 2 表示应答报文
		uint8_t  sHaddr[6];     /* 16 sender's hardware address */发送方MAC
		uint8_t  sInaddr[4];    /* 1c sender's IP address */发送方IP
		uint8_t  tHaddr[6];     /* 20 target's hardware address */目标MAC 
		uint8_t  tInaddr[4];    /* 26 target's IP address */目的IP
		uint8_t  pad[18];       /* 2a pad for min. ethernet payload (60 bytes) */有效载荷(数据部分)
} PACKED;
代码逻辑(流程)
  1. set socket

  2. send arp request

  3. wait for arp reply, and check it

函数关系
  • arpping(IP冲突检测)
  1. set socket

    • socket

    • setsockopt_b(广播)

      • setsockopt

        setsockopt函数用于任意类型、任意状态套接字接口的设置选项值。

  2. send arp request

    • memset、memcpy、safe_strncpy设置好相应的arpMsg

    • sendto 发送设置好了的报文

  3. wait for arp reply, and check it

    • safe_poll (IO多路复用)

      • poll

        监视并等待多个文件描述符的属性变化

    • read、memcmp、gettimeofday 读取数据,进行对比

script.c

Functions to call the DHCP client notification scripts

代码逻辑(模块)
  • upper_length(option字段占用的最大字节数)

  • alloc_fill_option(读取options,分配空间并且填充它)

  • fill_envp(按指定格式填充环境变量)

reference

  1. 一篇文章让你掌握神秘的 DHCP
  2. 计算机网络之DHCP原理与配置
  3. DHCP协议详解

感谢观看,侵权必删

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值