目录
1.2 为什么选dhclient/dhcpd作为DHCPv6客户端/服务器端工具
1 停掉路由器的IPV6功能,如下所示,确保局域网中没有别的能作为IPV6服务器的设备
1 dhclient脚本dhclient6_wifi-script
1 前言
本文主要介绍嵌入式移植DHCPv6功能的通用流程,帮助小白快速完成能力积累。
1.1 何为DHCPv6
DHCPv6 是一种运行在客户端和服务端之间的协议,是一个用来配置工作在IPv6网络上的IPv6主机所需的IP地址、IP前缀和/或其他配置的网络协议。与 IPv4 中的 DHCP 一样,所有的协议报文都是基于 UDP 的(DHCPv6客户端默认使用UDP 端口号546,服务端默认使用端口号547)。但是由于在 IPv6中没有广播报文,因此DHCPv6 使用组播(默认所有DHCPv6服务器都会加入并侦听该组播组:FF02::1:2)报文,客户端也无需配置服务端的IPv6地址。
1.2 为什么选dhclient/dhcpd作为DHCPv6客户端/服务器端工具
DHCPv4可以采用busybox集成的udhcpc/udhcpd工具来搭建客户端和服务器端,而DHCPv6则推荐采用开源库dhcp中的dhclient/dhcpd工具。就协议支持来讲,dhclient/dhcpd不仅支持DHCPv4也支持DHCPv6,因此可以考虑后续只用dhclient/dhcpd进行双栈支持的。
同时笔者也花时间去研究了下busybox里面的DHCPv6工具,发现DHCPv6的客户端udhcpc6在作为客户端的时候申请ipv6的地址需要指定地址参数,通过修改源码才能达到自动分配的效果,所以认为该工具通用性、适配性仍存在不足(个人实验结果分析,不一定对),而且目前busybox(1.34)并不支持DHCPv6的服务端。
后续如果busybox全面支持了DHCPv6,可以考虑仅使用busybox的工具来实现,一方面busybox工具比较轻量级,另一方面也省去了像开源库那样复杂的配置、编译工作。
1.3 移植dhclient/dhcpd
为了向大家展示移植DHCPv6工具的通用性,我们假设移植的设备芯片平台为A,使用的编译链为arm-linux-gnueabihf-,后续调试和集成都在该平台完成。请读者依葫芦画瓢根据自己调试的平台选择对应的编译器编译出对应的开源工具。
1.3.1 交叉编译dhclient/dhcpd
本次移植dhclient/dhcpd的版本为dhcp-4.4.3,当然大家也可以用其它稍早或者稍晚的版本。
1.获取开源库
获取开源库可以去该地址:
https://ftp.isc.org/isc/dhcp/4.4.3/dhcp-4.4.3.tar.gz
也可以上github官网下载。
2.交叉编译
解压dhcp-4.4.3.tar.gz后进入到源码路径。
tar -zxvf dhcp-4.4.3.tar.gz
cd dhcp-4.4.3/
然后根据自己的平台替换--host参数中的编译器进行编译,如下所示
./configure CC=arm-linux-gnueabihf-gcc BUILD_CC=gcc --host=arm-linux-gnueabihf --with-randomdev=no ac_cv_file__dev_random=yes
make
编译完成最终生成我们想要的工具dhclient/dhcpd,其中dhclient是DHCPv6的客户端程序,该工具编译生成后位于dhcp-4.4.3/client/目录下,dhcpd则是DHCPv6的服务器端程序,该工具编译生成后位于dhcp-4.4.3/server/目录下。
1.3.2 适配客户端以及服务端的配置文件
客户端dhclient的示例配置文件在dhcp4.4.3源码中也已经给出,按不同的平台取了不同的名字,如下所示,其中dhcp-4.4.3/client/scripts/linux这个文件就是适用于linux的配置文件,后面调试需要将其作为参考并进行适配。
root@virtual-machine:~/dhcpv6_debug/dhcp-4.4.3/client/scripts$ ls -al
total 116
drwxr-xr-x 2 root root 4096 3月 8 2022 .
drwxr-xr-x 5 root root 4096 10月 15 2022 ..
-rwxr-xr-x 1 root root 9798 3月 8 2022 bsdos
-rwxr-xr-x 1 root root 14694 3月 8 2022 freebsd
-rwxr-xr-x 1root root 16542 3月 8 2022 linux //该文件即为linux下的配置文件示例
-rwxr-xr-x 1root root 8176 3月 8 2022 macos
-rwxr-xr-x 1 root root 12407 3月 8 2022 netbsd
-rw-r--r-- 1 root root 2082 3月 8 2022 nextstep
-rwxr-xr-x 1 root root 12282 3月 8 2022 openbsd
-rwxr-xr-x 1 root root 8385 3月 8 2022 openwrt
-rwxr-xr-x 1 root root 6633 3月 8 2022 solaris
相应地,服务端的示例配置文件也可以在开源库找到,其位于dhcp-4.4.3/doc/examples/dhcpd-dhcpv6.conf,如下所示。
root@virtual-machine:~/dhcpv6_debug/dhcp-4.4.3/doc/examples$ ls -al
total 16
drwxr-xr-x 2 root root 4096 3月 8 2022 .
drwxr-xr-x 5 root root 4096 3月 8 2022 ..
-rw-r--r-- 1 root root 3370 3月 8 2022 dhcpd-dhcpv6.conf //dhcpd的示例配置文件
不过,在实际应用过程中,配置文件在不同平台不通用,需要花点时间修改并适配,适配后的服务器端和客户端的配置文件如1.5小节所示(笔者自己只研究了少部分内容,没办法对每一个参数都进行详细的解答)
将dhclient/dhcpd以及相应的配置文件拷贝到U盘中,然后挂载U盘(或者直接拷贝到嵌入式设备的文件系统里也行)。接下来就可以调试DHCPv6的客户端和服务端了,后续我们会在调试过程中对一些配置的含义进行解释。
1.3.3 客户端dhclient调试
1.搭建IPV6环境
在进行客户端调试的时候需要先搭建IPV6服务器的环境,我们需要用到路由器搭建一个支持IPV6的局域网,既然设备dhclient作为客户端,路由器理所应当地成为服务器端。将设备、PC、和路由器组成一个局域网环境,同时开放路由器的IPV6服务器配置,笔者使用的TPlink的路由器通过如下如下的方式开放DHCPv6功能的配置。
如下所示配置路由器的局域网参数。
如果是其它品牌路由器,请读者按照对应的方法设置/开放IPV6功能。
2 运行dhclient
dhclient的语法格式:
dhclient[参数] [网络接口]
示例:dhclient -6 -v -d -sf /usr/local/etc/dhcp/dhclient/dhclient6_wifi-script -lf /var/run/dhclient6.leases eth0
支持的各个参数如下表,实际应用中我们会用到其中几个而已,对此有兴趣的可以去做测试或者研究源码,这里不展开来讲。
(1)通用选项
选项 | 说明 |
-4 | 使用DHCPv4协议获取IPv4地址和配置参数。 这是默认值,不能与-6结合使用。 |
-6 | 使用DHCPv6协议获取可用的IPv6地址以及配置参数。 它不能与-4结合使用。 -S -T -P和-N参数提供对DHCPv6处理方面的更多控制。 注意:不建议将不同类型的查询混合在一起,甚至不要在它们之间共享租用文件。 |
-l | 尝试获得一次租约。 在使用代码2退出故障时。在DHCPv6中,这将初始交换的最大持续时间设置为超时(来自dhclient.conf,默认值为60秒)。 |
-d | 强制dhclient作为前台进程运行。 通常情况下,DHCP客户端将在前台运行,直到配置了一个接口,此时它将恢复为在后台运行。 在调试器下运行客户端或在System V系统上从inittab运行客户端时,此选项很有用。 这意味着-v。 |
-nw | 立即成为守护进程(nowait),而不是等到获取IP地址。 |
-q | 在启动时保持安静,这是默认设置。 |
-v | 启用详细日志消息。 |
-w | 即使没有找到广播接口,也继续运行。 通常,如果DHCP客户端无法识别要配置的任何网络接口,则它将退出。 在笔记本电脑和其他具有热插拔I / O总线的计算机上,可能会在系统启动后添加广播接口。 此标志可用于使客户端在找不到任何此类接口时不退出。 然后,可以使用omshell(1)程序在添加或删除网络接口时通知客户端,以便客户端可以尝试在该接口上配置IP地址。 |
-n | 不要配置任何接口。 这最有可能与-w标志结合使用。 |
-e VAR=val | 为dhclient-script(8)执行的环境定义其他环境变量。 您可以在命令行上指定多个-e选项。 |
-r | 释放当前租约并停止正在运行的DHCP客户端,如先前在PID文件中记录的那样。 通过此方法关闭时,将执行dhclient-script(8),具体原因是调用脚本集。 客户端通常不会释放当前租约,因为这不是DHCP协议所要求的,但是一些有线ISP要求其客户端通知服务器是否希望释放分配的IP地址。 |
-x | 停止正在运行的DHCP客户端而不释放当前租约。 杀死先前记录在PID文件中的现有dhclient进程。 通过此方法关闭时,将执行dhclient-script(8),具体原因是调用脚本集。 |
-p port | DHCP客户端应侦听和传输的UDP端口号。 如果未指定,则dhclient使用默认端口68.这对于调试目的非常有用。 如果指定了客户端应监听和传输的其他端口,则客户端还将使用不同的目标端口 - 比指定端口少一个。 |
-s server-addr | 在dhclient获取IP地址之前,指定用作DHCP协议消息目标的服务器IP地址或完全限定域名。 通常,dhclient将这些消息发送到255.255.255.255(IP受限广播地址)。 覆盖它主要用于调试目的。 DHCPv6(-6)模式不支持此功能。 |
-g relay | 将所有数据包的giaddr字段设置为模拟中继代理的中继IP地址。 这仅用于测试,不应该以任何一致或有用的方式工作。 |
--version | 打印版本号并退出。 |
(2)可用于DHCPv6模式的选项
选项 | 说明 |
-S | 使用信息请求仅获取无状态配置参数(即,没有地址)。 这意味着-6。 它也不会重写租赁数据库。 |
-T | 请求IPv6临时地址,每个-T标志一组。 这意味着-6并且还禁用正常地址查询。 请参见-N以恢复它。 |
-P | 启用IPv6前缀委派。 这意味着-6并且还禁用正常地址查询。 请参见-N以恢复它。 请注意,只允许一个请求的接口。 |
-N | 恢复IPv6的正常地址查询。 这意味着-6。 它用于在使用-T或-P后恢复正常操作。 |
(3) 修改默认文件位置
以下选项可用于修改客户端用于其文件的位置。 例如,如果在启动DHCP客户端时尚未安装DBDIR或RUNDIR,则它们特别有用。
选项 | 说明 |
-cf config-file | 客户端配置文件的路径。 如果未指定,则使用默认的ETCDIR / dhclient.conf。 有关此文件的说明,请参阅dhclient.conf(5)。 |
-lf lease-file | 租用数据库文件的路径。 如果未指定,则使用默认的DBDIR / dhclient.leases。 有关此文件的说明,请参阅dhclient.leases(5)。 |
-pf pid-file | 进程标识文件的路径。 如果未指定,则使用默认的RUNDIR / dhclient.pid。 |
--no-pid | 禁用写入pid文件的选项。 默认情况下,程序将写入一个pid文件。 如果使用此选项调用该程序,即使使用-r或-x调用,也不会尝试终止任何现有的客户端进程。 |
-sf script-file | dhclient获取租约时调用的网络配置脚本的路径。 如果未指定,则使用默认的CLIENTBINDIR / dhclient-script。有关此文件的说明,请参阅dhclient-script(8)。 |
在设备上执行指令向路由器申请IP并设置:/mnt/usbstate/dhcpv6/bin_normal/dhclient -6 -v -d -sf /mnt/usbstate/dhcpv6/dhcp/dhclient/dhclient6-script -lf /var/run/dhclient6.leases eth0 &
/var/mnt/usbstate/dhcpv6/bin # /mnt/usbstate/dhcpv6/bin_normal/dhclient -6 -v -
d -sf /mnt/usbstate/dhcpv6/dhcp/dhclient/dhclient6-script -lf /var/run/dhclient
6.leases eth0 &
/var/mnt/usbstate/dhcpv6/bin # Internet Systems Consortium DHCP Client 4.4.3
Copyright 2004-2022 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/env para :envp [1] is dad_wait_time=0
env para :envp [2] is pid=3590
env para :envp [3] is reason=PREINIT6
env para :envp [4] is interface=eth0
Listening on Socket/eth0
Sending on Socket/eth0
PRC: Confirming active lease (INIT-REBOOT).
XMT: Forming Confirm, 0 ms elapsed.
XMT: X-- IA_NA b2:b0:1e:be
XMT: | X-- Confirm Address 2001:bcff:fea6:6c01::1001
XMT: V IA_NA appended.
XMT: Confirm on eth0, interval 1020ms.
RCV: Reply message on eth0 from fe80::d276:e7ff:fe0a:9105.
RCV: X-- IA_NA b2:b0:1e:be
RCV: | X-- starts 101713
RCV: | X-- t1 - renew +43200
RCV: | X-- t2 - rebind +69120
RCV: | X-- [Options]
RCV: | | X-- IAADDR 2001:bcff:fea6:6c01::1001
RCV: | | | X-- Preferred lifetime 86400.
RCV: | | | X-- Max lifetime 86400.
RCV: X-- Server ID: 00:03:00:01:d0:76:e7:0a:91:05
message status code Success.
PRC: Bound to lease 00:03:00:01:d0:76:e7:0a:91:05.
env para :envp [1] is requested_dhcp6_domain_search=1 //以下env para是笔者通过在源码加打印打印出的数据
env para :envp [2] is requested_dhcp6_name_servers=1
env para :envp [3] is new_rebind=5400
env para :envp [4] is new_renew=3600
env para :envp [5] is new_starts=100965
env para :envp [6] is new_iaid=b2:b0:1e:be
env para :envp [7] is new_max_life=7500
env para :envp [8] is new_preferred_life=7200
env para :envp [9] is new_life_starts=100965
env para :envp [10] is new_ip6_address=2001:bcff:fea6:6c01::1001 //申请到的ip
env para :envp [11] is new_ip6_prefixlen=128
env para :envp [12] is new_dhcp6_status_code=success success
env para :envp [13] is new_dhcp6_name_servers=2404:6800:8005::71 2404:6800:8005::64
env para :envp [14] is new_dhcp6_server_id=0:3:0:1:d0:76:e7:a:91:5
env para :envp [15] is new_dhcp6_client_id=0:1:0:1:c7:94:46:e3:4e:45:b2:b0:1e:be
env para :envp [16] is dad_wait_time=0
env para :envp [17] is pid=3590
env para :envp [18] is reason=BOUND6
env para :envp [19] is interface=eth0
PRC: Renewal event scheduled in 2852 seconds, to run for 1800 seconds.
PRC: Depreference scheduled in 6452 seconds.
PRC: Expiration scheduled in 6752 seconds.
申请的IP成功设置到网卡中,这个是/mnt/usbstate/DHCPv6/dhcp/dhclient/dhclient6-script脚本的工作(主要完成将开源库申请到IP、DNS等配置信息从环境变量中读取出来,并用busybox 中ip addr等linux指令设置相应的参数使之生效),查询网卡信息如下所示,表明成功设置下去了
上述只是对eth0的IPV6的分配,同理如果想让wifi网卡ra0也申请到IPV6的地址,只需将运行指令中的网卡改掉即可,读者可以自行测试。
1.3.4 服务端dhcpd调试
当设备作为服务端时,即需要具备向客户端分配地址和管理ipv6地址池的能力。
1 停掉路由器的IPV6功能,如下所示,确保局域网中没有别的能作为IPV6服务器的设备
这样就能确保整个局域网环境中我们的设备将要跑的dhcpd成为唯一的服务器了。(当然这个也不是必须要做的,当环境中存在多个DHCPv6服务器,可能给客户端分配地址就不是dhcpd的宿主机了,这里为了保证我们dhcpd作为唯一的服务端才做这样的操作)
2 运行dhcpd
dhcpd的语法格式为:
dhcpd [参数] [网络接口]
示例:/usr/local/bin/dhcpd -6 -cf /usr/local/etc/dhcp/dhcpd/dhcpd6.conf -lf /var/run/dhcpd6.leases -d ra0
dhcpd支持的各个参数如下所示,实际应用中我们会用到其中几个。
-p <端口> | 指定dhcpd监听的端口 |
-f | 作为前台进程运行dhcpd |
-d | 启用调试模式 |
-q | 在启动时不显示版权信息 |
-t | 简单地测试配置文件的语法是否正确的,但不会尝试执行任何网络操作 |
-T | 可以用来测试租约数据库文件 |
-4 | 运行DHCP服务器 |
-6 | 运行DHCPv6服务器 |
-s <服务器> | 指定发送回复的服务器 |
-cf <配置文件> | 指定配置文件 |
-lf <租约文件> | 指定租约文件 |
-pf <PID文件> | 指定PID文件 |
-tf <跟踪输出文件> | 指定文件记录DHCP服务器的整个启动状态 |
为了完成设备DHCPv6服务端的建立,需要在设备端执行指令:
ifconfig eth0 3ffe:501:ffff:100::9000(step1:设置本地服务器ip,需要保持跟conf文件设置的子网在同一个网段)
touch /var/run/dhcpd6.leases(step2:创建服务器的租约数据库文件,该文件会保存服务器的历史租约信息)
/var/mnt/usbstate/DHCPv6/bin # /usr/local/bin/dhcpd -6 -cf /usr/local/etc/dhcp/d
hcpd/dhcpd6.conf -lf /var/run/dhcpd6.leases -d eth0 &(step3:运行dhcpd进程)
执行dhcpd的结果会出现如下打印信息:
/var/mnt/usbstate/DHCPv6/bin # /usr/local/bin/dhcpd -6 -cf /usr/local/etc/dhcp/d
hcpd/dhcpd6.conf -lf /var/run/dhcpd6.leases -d eth0 &
/var/mnt/usbstate/DHCPv6/bin # Internet Systems Consortium DHCP Server 4.4.3
Copyright 2004-2022 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Config file: /usr/local/etc/dhcp/dhcpd/dhcpd6.conf
Database file: /var/run/dhcpd6.leases
PID file: /var/run/dhcpd6.pid
Wrote 0 deleted host decls to leases file.
Wrote 0 new dynamic host decls to leases file.
Wrote 0 NA, 0 TA, 0 PD leases to lease file.
Bound to *:547
Listening on Socket/4/eth0/3ffe:501:ffff:100::/64
Sending on Socket/4/eth0/3ffe:501:ffff:100::/64
Server starting service.
这个时候我们的服务器就起来了,为了验证服务器是否能给别的客户端分配地址,我们用PC做实验(也可以用设备起dhclient去申请),在电脑上的网络适配器上先:禁能IPV6,点击确定以后,然后再使能IPV6。如下所示,这样做的目的是为了触发一次PC向服务器申请IPV6的过程。
服务器端收到PC端的请求然后分配IPv6会有下所示打印。
查看PC端获取的IP地址: 2001:bcff:fea6:6c01::1000即为设备分配的IP地址,如下所示。
为什么会给PC分配这个地址呢,这就跟服务器配置的地址池有关了,服务端的地址池配置在dhcpd6.conf中,摘取其中的信息如下,因为服务器配置中文件中有一个地址池的参数,限定了服务器可以分配的地址的范围和个数。如下所示,现在可以理解为什么分配的地址是3ffe:501:ffff:100::1000了。
# The subnet where the server is attached
# (i.e., the server has an address in this subnet)
subnet6 3ffe:501:ffff:100::/64 { //这里看本地是属于哪个子网,相同子网下配置的参数才生效,比如我配置本机ip地址为3ffe:501:ffff:100::9000,那么该设备做服务器时候,这个子网的参数会生效
# Two addresses available to clients
# (the third client should get NoAddrsAvail)
range6 3ffe:501:ffff:100::10 3ffe:501:ffff:100::1000;//ipv6地址池,说明可以分配的地址池在10-1000之间
# Use the whole /64 prefix for temporary addresses
# (i.e., direct application of RFC 4941)
range6 3ffe:501:ffff:100:: temporary;
# Some /64 prefixes available for Prefix Delegation (RFC 3633)
prefix6 3ffe:501:ffff:100:: 3ffe:501:ffff:111:: /64;
}
有兴趣的读者可以这样组一个环境,用两个嵌入式设备,一个跑dhclient,一个跑dhcpd,这样是不是就直接同时验证了客户端与服务端的功能呢?
1.4 系统集成-移植dhcpd/dhclient
上一章的调试过程给了系统移植dhclient/dhcpd的必要流程,因此我们也需要按照流程去起相关的进程。
1 dhclient移植
前面调试的时候已经知道起dhclient工具的方式为:dhclient -6 -v -d -sf /usr/local/etc/dhcp/dhclient/dhclient6_wifi-script -lf /var/run/dhclient6.leases eth0
在进行集成的时候该工具中有几个文件需要提前在文件系统中放好:
1 dhclient放置工具的位置
2 脚本文件dhclient6_script的放置位置
3 租约数据库文件dhclient6.leases,一般在起进程之前创建,一般创建路径为:/var/run/
如下所示:
#ifdef SUPPORT_DHCPV6
//创建DHCPv6 database文件
system((char*)"touch /var/run/dhclient6.leases");//创建客户端租约数据库文件
system((char*)"touch /var/run/dhcpd6.leases");//创建服务端租约数据库文件
#endif
如下示例代码中以太网(eth0)的DHCPv6客户端启动方式
#ifdef SUPPORT_DHCPv6
//开启DHCPv6 Client
system((char*)"touch /var/run/dhclient6.leases");
snprintf(cmd, sizeof(cmd), "/usr/local/bin/dhclient -6 -v -d -sf /usr/local/etc/dhcp/dhclient/dhclient6-script -lf /var/run/dhclient6.leases %s", m_szMainIfName);//m_szMainIfName为eth0
system(cmd);
#endif
2 dhcpd移植
前面调试的时候已经知道,dhcpd的方式为:/usr/local/bin/dhcpd -6 -cf /usr/local/etc/dhcp/dhcpd/dhcpd6.conf -lf /var/run/dhcpd6.leases -d ra0
该工具有几个点需要注意:
1 设置本地网卡的ipv6d地址,这个ip要跟dhcpd6.conf中分配的地址池为同一网段。
2 dhcpd的路径
3 配置文件的路径dhcpd6.conf
4 dhcpd6.leases租约数据库的文件,一般在工具运行之前先创建,一般的创建路径为:/var/run/
以wifi支持AP模式为例,代码中起服务端的方式如下所示:
#ifdef SUPPORT_DHCPv6
//DHCPv6 Server
system((char*)"touch /var/run/dhcpd6.leases");//创建租约数据库文件
SetIp6Addr(m_insideModuleInfo.stuHardInfo.ifName, "3ffe:501:ffff:100::9000", 64);//设置本地IPV6地址
xbyte_t cmd[128] = {0};
snprintf(cmd, sizeof(cmd), "/usr/local/bin/dhcpd -6 -cf /usr/local/etc/dhcp/dhcpd/dhcpd6.conf -lf /var/run/dhcpd6.leases -d %s", m_insideModuleInfo.stuHardInfo.ifName);// ifName为wlan0
system(cmd);//起dhcpd进程
#endif
以上示例都是以eth0,有线网为基准,读者如果有兴趣的话也可以尝试改为wifi的实现,只需要将eth0网卡替换为wifi的网卡名就行。
1.5 关键脚本
1 dhclient脚本dhclient6_wifi-script
脚本内容如下所示:
#!/bin/sh
# dhclient-script for Linux. Dan Halbert, March, 1997.
# Updated for Linux 2.[12] by Brian J. Murrell, January 1999.
# No guarantees about this. I'm a novice at the details of Linux
# networking.# Notes:
# 0. This script is based on the netbsd script supplied with dhcp-970306.
# 1. ifconfig down apparently deletes all relevant routes and flushes
# the arp cache, so this doesn't need to be done explicitly.# 2. The alias address handling here has not been tested AT ALL.
# I'm just going by the doc of modern Linux ip aliasing, which uses
# notations like eth0:0, eth0:1, for each alias.# 3. I have to calculate the network address, and calculate the broadcast
# address if it is not supplied. This might be much more easily done
# by the dhclient C code, and passed on.# 4. TIMEOUT not tested. ping has a flag I don't know, and I'm suspicious
# of the $1 in its args.# 5. Script refresh in 2017. The aliasing code was too convoluted and needs
# to go away. Migrated DHCPv4 script to ip command from iproute2 suite.
# This is based on Debian script with some tweaks. ifconfig is no longer
# used. Everything is done using ip tool from ip-route2.# 'ip' just looks too weird. Also, we now have unit-tests! Those unit-tests
# overwirte this line to use a fake ip-echo tool. It's also convenient
# if your system holds ip tool in a non-standard location.
ip=/sbin/ip# update /etc/resolv.conf based on received values
# This updated version mostly follows Debian script by Andrew Pollock et al.
make_resolv_conf() {
local new_resolv_conf# DHCPv6
if [ -n "$new_dhcp6_domain_search" ] || [ -n "$new_dhcp6_name_servers" ]; then
new_resolv_conf=/var/run/resolv6.conf.dhclient-new
rm -f $new_resolv_confif [ -n "$new_dhcp6_domain_search" ]; then
echo "search ${new_dhcp6_domain_search}" >> $new_resolv_conf
fiif [ -n "$new_dhcp6_name_servers" ]; then
for nameserver in $new_dhcp6_name_servers; do
# append %interface to link-local-address nameservers
if [ "${nameserver##fe80::}" != "$nameserver" ] ||
[ "${nameserver##FE80::}" != "$nameserver" ]; then
nameserver="${nameserver}%${interface}"
fi
echo nameserver $nameserver >>$new_resolv_conf
done
else # keep 'old' nameservers
sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p /var/run/resolv6.conf >>$new_resolv_conf
fimv -f $new_resolv_conf /var/run/resolv6.conf
#UPDATE DNS
cp /var/run/resolv6.conf /var/run/etherdns6.conf
fi
}# set host name
set_hostname() {
local current_hostnameif [ -n "$new_host_name" ]; then
current_hostname=$(hostname)# current host name is empty, '(none)' or 'localhost' or differs from new one from DHCP
if [ -z "$current_hostname" ] ||
[ "$current_hostname" = '(none)' ] ||
[ "$current_hostname" = 'localhost' ] ||
[ "$current_hostname" = "$old_host_name" ]; then
if [ "$new_host_name" != "$old_host_name" ]; then
hostname "$new_host_name"
fi
fi
fi
}# run given script
run_hook() {
local script
local exit_status
script="$1"if [ -f $script ]; then
. $script
fiif [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ]; then
logger -p daemon.err "$script returned non-zero exit status $exit_status"
fireturn $exit_status
}# run scripts in given directory
run_hookdir() {
local dir
local exit_status
dir="$1"if [ -d "$dir" ]; then
for script in $(run-parts --list $dir); do
run_hook $script || true
exit_status=$?
done
fireturn $exit_status
}# Must be used on exit. Invokes the local dhcp client exit hooks, if any.
exit_with_hooks() {
exit_status=$1# Source the documented exit-hook script, if it exists
if ! run_hook /etc/dhclient-exit-hooks; then
exit_status=$?
fi# Now run scripts in the Debian-specific directory.
if ! run_hookdir /etc/dhclient-exit-hooks.d; then
exit_status=$?
fiexit $exit_status
}# This function was largely borrowed from dhclient-script that
# ships with Centos, authored by Jiri Popelka and David Cantrell
# of Redhat. Thanks guys.
add_ipv6_addr_with_DAD() {
${ip} -6 addr replace ${new_ip6_address}/${new_ip6_prefixlen} \
dev ${interface} scope globalif [ ${dad_wait_time} -le 0 ]
then
# if we're not waiting for DAD, assume we're good
return 0
fi# Repeatedly test whether newly added address passed
# duplicate address detection (DAD)
for i in $(seq 1 ${dad_wait_time}); do
sleep 1 # give the DAD some timeaddr=$(${ip} -6 addr show dev ${interface} \
| grep ${new_ip6_address}/${new_ip6_prefixlen})# tentative flag == DAD is still not complete
tentative=$(echo "${addr}" | grep tentative)
# dadfailed flag == address is already in use somewhere else
dadfailed=$(echo "${addr}" | grep dadfailed)if [ -n "${dadfailed}" ] ; then
# address was added with valid_lft/preferred_lft 'forever',
# remove it
${ip} -6 addr del ${new_ip6_address}/${new_ip6_prefixlen} \
dev ${interface}exit_with_hooks 3
fiif [ -z "${tentative}" ] ; then
if [ -n "${addr}" ]; then
# DAD is over
return 0
else
# address was auto-removed (or not added at all)
exit_with_hooks 3
fi
fi
donereturn 0
}# Invoke the local dhcp client enter hooks, if they exist.
run_hook /etc/dhclient-enter-hooks
run_hookdir /etc/dhclient-enter-hooks.d# Execute the operation
case "$reason" in### DHCPv6 Handlers
# TODO handle prefix change: ?based on ${old_ip6_prefix} and ${new_ip6_prefix}?PREINIT6)
# ensure interface is up
${ip} link set ${interface} up# We need to give the kernel some time to active interface
interface_up_wait_time=5
for i in $(seq 0 ${interface_up_wait_time})
do
${ip} link show dev ${interface} | grep -q LOWER_UP 2>&1
if [ $? -eq 0 ]; then
break;
fi
sleep 1
done# flush any stale global permanent IPs from interface
${ip} -6 addr flush dev ${interface} scope global
tentative_check_time=5
# Wait for duplicate address detection for this interface if the
# --dad-wait-time parameter has been specified and is greater than
# zero.
# Check if any IPv6 address on this interface is marked as
# tentative.
${ip} addr show ${interface} | grep inet6 | grep tentative \
&> /dev/null
if [ $? -eq 0 ]; then
# Wait for duplicate address detection to complete or for
# the timeout specified as --dad-wait-time.
echo "still exist tentative address"
for i in $(seq 0 ${tentative_check_time})
do
# We're going to poll for the tentative flag every second.
sleep 1
${ip} addr show ${interface} | grep inet6 | grep tentative \
&> /dev/null
if [ $? -ne 0 ]; then
break;
fi
echo "poll operation:still exist tentative address"
done
fi
;;
BOUND6|RENEW6|REBIND6)
if [ "${new_ip6_address}" ] && [ "${new_ip6_prefixlen}" ]; then
# set leased IP
add_ipv6_addr_with_DAD
fi# update /etc/resolv.conf
if [ "${reason}" = BOUND6 ] ||
[ "${new_dhcp6_name_servers}" != "${old_dhcp6_name_servers}" ] ||
[ "${new_dhcp6_domain_search}" != "${old_dhcp6_domain_search}" ]; then
make_resolv_conf
fi;;
DEPREF6)
if [ -z "${cur_ip6_prefixlen}" ]; then
exit_with_hooks 2
fi# set preferred lifetime of leased IP to 0
${ip} -6 addr change ${cur_ip6_address}/${cur_ip6_prefixlen} \
dev ${interface} scope global;;
EXPIRE6|RELEASE6|STOP6)
if [ -z "${old_ip6_address}" ] || [ -z "${old_ip6_prefixlen}" ]; then
exit_with_hooks 2
fi# delete leased IP
${ip} -6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \
dev ${interface};;
esacexit_with_hooks 0
2 dhcpd关键配置文件
dhcpv6.conf内容如下:
# Server configuration file example for DHCPv6
# From the file used for TAHI tests - addresses chosen
# to match TAHI rather than example block.# IPv6 address valid lifetime
# (at the end the address is no longer usable by the client)
# (set to 30 days, the usual IPv6 default)
default-lease-time 86400;# IPv6 address preferred lifetime
# (at the end the address is deprecated, i.e., the client should use
# other addresses for new connections)
# (set to 7 days, the usual IPv6 default)
preferred-lifetime 86400;# T1, the delay before Renew
# (default is 1/2 preferred lifetime)
# (set to 1 hour)
option dhcp-renewal-time 3600;# T2, the delay before Rebind (if Renews failed)
# (default is 3/4 preferred lifetime)
# (set to 2 hours)
option dhcp-rebinding-time 7200;# Enable RFC 5007 support (same than for DHCPv4)
allow leasequery;# Global definitions for name server address(es) and domain search list
option dhcp6.name-servers 3ffe:501:ffff:100:200:ff:fe00:3f3e;
option dhcp6.domain-search "test.example.com","example.com";# Set preference to 255 (maximum) in order to avoid waiting for
# additional servers when there is only one
##option dhcp6.preference 255;# Server side command to enable rapid-commit (2 packet exchange)
##option dhcp6.rapid-commit;# The delay before information-request refresh
# (minimum is 10 minutes, maximum one day, default is to not refresh)
# (set to 6 hours)
option dhcp6.info-refresh-time 21600;# The path of the lease file
dhcpv6-lease-file-name "/var/run/dhcpd6.leases";# Static definition (must be global)
host myclient {
# The entry is looked up by this
host-identifier option
dhcp6.client-id 00:01:00:01:00:04:93:e0:00:00:00:00:a2:a2;# A fixed address
fixed-address6 3ffe:501:ffff:100::1234;# A fixed prefix
fixed-prefix6 3ffe:501:ffff:101::/64;# Override of the global definitions,
# works only when a resource (address or prefix) is assigned
option dhcp6.name-servers 3ffe:501:ffff:100:200:ff:fe00:4f4e;# For debug (to see when the entry statements are executed)
# (log "sol" when a matching Solicitation is received)
##if packet(0,1) = 1 { log(debug,"sol"); }
}host otherclient {
# This host entry is hopefully matched if the client supplies a DUID-LL
# or DUID-LLT containing this MAC address.
hardware ethernet 01:00:80:a2:55:67;fixed-address6 3ffe:501:ffff:100::4321;
}# The subnet where the server is attached
# (i.e., the server has an address in this subnet)
subnet6 3ffe:501:ffff:100::/64 {
# Two addresses available to clients
# (the third client should get NoAddrsAvail)
range6 3ffe:501:ffff:100::10 3ffe:501:ffff:100::1000;# Use the whole /64 prefix for temporary addresses
# (i.e., direct application of RFC 4941)
range6 3ffe:501:ffff:100:: temporary;# Some /64 prefixes available for Prefix Delegation (RFC 3633)
prefix6 3ffe:501:ffff:100:: 3ffe:501:ffff:111:: /64;
}# A second subnet behind a relay agent
subnet6 3ffe:501:ffff:101::/64 {
range6 3ffe:501:ffff:101::10 3ffe:501:ffff:101::1000;# Override of the global definitions,
# works only when a resource (address or prefix) is assigned
option dhcp6.name-servers 3ffe:501:ffff:101:200:ff:fe00:3f3e;}
# A third subnet behind a relay agent chain
subnet6 3ffe:501:ffff:102::/64 {
range6 3ffe:501:ffff:102::10 3ffe:501:ffff:102::1000;
}
1.6 小结
读者可能已经发现dhclient/dhcpd除了能实现DHCPv6功能,也能实现DHCPv4功能,只需要在后面加-4参数就可以了。后续如果有时间,笔者会将该工具用于DHCPv4的过程分享给大家。