在启动dhcpd进程的日志记录开始部分,我们都会看到类似如下的打印:

create for interface for eth5
dhcp_interface_setup_hook eth5, for addr 10.40.124.205
create for interface for base0
dhcp_interface_setup_hook base0, for addr 169.254.1.1
add to interface for base0
dhcp_interface_setup_hook base0, for addr 169.254.100.100
add to interface for VIF2
dhcp_interface_setup_hook VIF2, for addr 192.168.1.10
Listening on LPF/VIF2/b8:c8:55:ac:03:81/network-VIF2
Sending on   LPF/VIF2/b8:c8:55:ac:03:81/network-VIF2
Sending on   Socket/fallback/fallback-net

   如果你再使用ifconfig查看一下系统的网口信息就会发现,eth5/base0/VIF2都是有ip地址,其他的网口是没有地址的(当然lo除外),并且它们与上面的前8行对应。

   我将dhcpd监听网口的步骤理解为两大步:发现网口监听网口。在common/discover.c中我们能找到函数void discover_interfaces(int state)。所有被发现的网口都会存放到全局变量interfaces链表中,然后调用判断网口信息是否合法,与地址池是否匹配等等,最后完成网口接收和发送报文的初始化。


一、发现网口

   在这一步主要使用了三个函数(begin_iface_scan/next_iface/end_iface_scan)组成。但对于不同的系统它们的实现也有所不同。在这里支持三个类型的系统Solaris、Linux、BSD,他们都是类UNIX系统,之间有很多相似之处,也有很多不同。为什么要做这样的区分呢?

   Solaris:ioctl()支持SIOCGLIFCONF参数扩展来获取网口配置信息。

   Linux:通过/proc/net/dev文件来读取本机所有网口名,同样也通过ioctl()来获取ip/flag等信息。

   BSD:提供getifaddrs() 函数来获取所有网口。


   在进行上述三个函数来发现所有网口时,同时过滤掉不可用的网口(loopback或者没有ip的网口),并调用dhcp_interface_setup_hook找出每个可用的网口所在的网段(subnet)。


二、监听网口

   这一步只需要关注两个函数(if_register_receive/if_register_send)。这两个函数只是针对dhcpv4来说,dhcpv6则需要使用if_register6

   与第一步类似,对于不同的系统也有所区分。主要有以下几种:

1. LPF

   全称Linux Packet Filter,主要适用一般linux系统。接收/发送报文使用raw socket,并在接收报文时通过setsockopt()过滤指定端口的报文。dhcpv4使用67端口,v6则使用547。

2. Socket

   主要适用BSD系统,使用udp socket。

3. UPF

   全称 Ultrix Packet Filter。然后打一个upf设备,通过ioctl()来关联对应的网口进行收发报文。

4. BPF

   与UPF类似,只是打开的设备文件不同。

5. NIT

   全称Network Interface Tap。与UPF也类似,区别在于打开的设备文件。

6. DLPI

   全称Data Link Provider Interface。打开一个dlpi设备,同样将这个打开的设备文件与网口绑定。


   在根据上面各种不同情况,初始化了接收/发送报文的文件描述符,然后调用omapi_register_io_object()来注册回调函数,接收报文,并调用处理报文函数。


   over