
我喜欢自动化该过程,并编写自己的自行车来研究这种或那种材料。 我的新目标是在小型网络中发布地址的DHCP服务器,以便可以执行设备的初始配置。
在本文中,我将讨论DHCP协议以及bash的一些细微之处。
最终结果
让我们从头开始,以便清楚地知道我们正在争取什么。
工作示范:

带有脚本的存储库:
firemoon777 / bash-dhcp-server最初的问题
我需要的配置是通过以下方式完成的:我们通过双绞线直接连接到设备,通过DHCP发出临时地址,并使用已创建的脚本对其进行配置。 如此连续十到二十次。
对许多人来说,著名的isc-dhcp-server可以很好地完成其工作,但是,las,它不会通知我的脚本地址已发布,因此您需要以某种方式阻止执行,直到地址被发布为止。
解决方案似乎在表面上:ping直到脸部呈蓝色,直到设备响应为止:
while ! ping -c1 -W1 "$DHCP" | grep -q "time=" do echo "Waiting for $DHCP..." done
但是这一决定绝对缺乏冒险精神。
理论部分
使用单个DHCP服务器获取地址
DHCP协议在端口67和68上通过UDP运行。服务器始终仅在67上运行,客户端仅在68上运行。由于客户端没有地址(具有地址0.0.0.0),因此广播DHCP数据包。 即 客户端始终将数据包从地址0.0.0.0:68发送到地址255.255.255.255:67,服务器从其地址67发送到地址255.255.255.255:68。
客户端
通过四个数据包(
DORA )
接收地址:
- 客户端找出DHCP服务器在哪里( D iscover)
- 服务器响应并提供其地址(ffer)
- 客户端从特定服务器请求建议的地址( R equest)
- 服务器同意并发布地址( A ck)
在视觉上,该方案可以表示如下:

使用多个DHCP服务器获取地址
当客户端发送发现时,所有可以听到的服务器都将其要约发送给客户端。 但是客户必须选择一个。 客户端选项在带有选项54(DHCP服务器)的“请求”消息中宣布,其中包含首选DHCP服务器的IP地址。 尽管还将请求发送给网络上的每个人,但只有选项54中指定IP的DHCP服务器会响应。

DHCP报文内容
DHCP数据包由两部分组成:一个常量(大小为236个字节)和一个携带选项(DHCP选项)的变量。
该表包含来自维基百科的DHCP数据包的所有字段领域 | 内容描述 | 长度(以字节为单位) |
---|
运
| 消息类型。 例如,它可以采用以下值:BOOTREQUEST(0x01,从客户端到服务器的请求)和BOOTREPLY(0x02,从服务器到客户端的响应)。
| 1个
|
类型
| 硬件地址的类型。 RFC 1700分配的编号中定义了此字段的有效值。 例如,对于以太网MAC地址,此字段设置为0x01。
| 1个
|
海伦
| 硬件地址的长度,以字节为单位。 以太网MAC地址为0x06。
| 1个
|
啤酒花
| 消息通过的中间路由器(所谓的DHCP中继代理 )的数量。 客户端将此字段设置为0x00。
| 1个
|
西德
| 客户端在获取地址的过程开始时生成的4字节的唯一事务标识符。
| 4
|
秒
| 从开始获取地址的过程开始的时间(以秒为单位)。 可能无法使用(在这种情况下,它设置为0x0000)。
| 2
|
标志
| 标志字段是DHCP协议的特殊参数。
| 2
|
ciaddr
| 客户端IP地址。 仅当客户端已经具有其自己的IP地址并且能够响应ARP请求时才填写该字段(如果客户端在租约期满后执行更新地址的过程,则可以这样做)。
| 4
|
yiaddr
| 服务器建议的新客户端IP地址。
| 4
|
西阿德
| 服务器IP地址。 在DHCP子句中返回(请参见下文)。
| 4
|
贾德
| 中继代理程序的IP地址(如果涉及到将DHCP消息传递到服务器的过程)。
| 4
|
乍得
| 客户端的硬件地址(通常是MAC地址)。
| 16
|
名字
| 可选的服务器名称,以空终止的字符串表示。
| 64
|
档案
| 远程下载时,无盘工作站使用的可选服务器文件名。 像sname一样,它表示为以null结尾的字符串。
| 128
|
选项
| DHCP选项字段。 此处显示了各种其他配置选项。 在该字段的开头,指示了四个特殊字节,其值分别为99、130、83、99(“幻数”),从而允许服务器确定该字段的存在。 该字段的长度是可变的,但是DHCP客户端应准备好接收576字节的DHCP消息(在此消息中, options字段的长度为340字节)。
| 变数
|
RFC 2132中所有DHCP选项的列表DHCP选项的编码如下:
例如,参数3(建议的网关)的值为10.0.0.1:
如果需要传递多个参数,则参数的长度会增加。
例如,在参数6(DNS服务器)中,我们将传输两个地址(1.1.1.1和8.8.4.4):
选项字段结尾的符号是参数,其值为255(0xFF),长度为0。
多数情况下,客户端将参数55(他希望接收作为响应的参数列表)放入DHCP Discover中,但是,我们有权不向他提供他所请求的所有内容。
实践部分
最初计划用某种更合适的语言(C)来编写服务器,但是这将是平凡而又简单的。 编写脚本来接管dhcp服务器的功能都可以。
简化版
由于应该将开发的服务器用于通过补丁连接的两个节点的网络,因此采用了以下简化方法:
- 保证网络上有一个客户端;
- 确保网络中没有更多的dhcp服务器
- 发起人决定发出哪个地址
- DHCP释放和DHCP拒绝被忽略
听众
首先,您需要学习如何接收数据包。 这需要
经过认证的同情听众,例如nc。 但并非每个nc都适合这些目的。 Debian的OpenBSD netcat 1.130是合适的,但是Ubuntu的1.105已经不存在了。 运行nc以侦听到达端口67的所有UDP数据包。
nc -l 0.0.0.0 -up 67 -w0
由于-w开关的值为0,因此还需要OpenBSD netcat。在接收到一个数据包(UDP广播)之后,传统的nc不会接收更多的数据包,但是不会结束。
原始字节处理
在外壳程序中,使用不可打印的字符(例如空字符)非常困难:它只会忽略它。 DHCP数据包包含许多字节0x00(例如,文件字段)。 问题的解决方案以十六进制转储的形式出现:
nc -l 0.0.0.0 -up 67 -w0 | stdbuf -o0 od -v -w1 -t x1 -An
每行一个字节,不输出地址,不跳过重复的字节。 您还可以为stdbuf -o0加香料,以便不缓冲输出。
接收,存储和处理包裹
从od命令stdout中,字节由read命令获取,并添加到数组中。
msg=() for i in {0..235}; do read -r tmp msg[$i]=$tmp done
尽管所有值均以十六进制表示,但DHCP选项编号和选项长度最好以通常的十进制形式显示在屏幕/日志中。 为此,您可以使用短输入bash'a:
$ op=AC $ echo $((16
根据请求的类型(发现或请求)编辑接收到的数据包并发回。
回应
但是,发送包裹并不是一件容易的事。 首先,您需要将转储中的字节转换为原始字节,然后一次发送所有数据包。
可以使用printf实用程序使用转义序列进行转换。 为了避免丢失任何内容,请立即将字节写入文件。
OpenBSD netcat也用于发送。 但是,如果适用于Ubuntu的1.105版本适合用作侦听器,则它不适合广播UDP消息:我们收到协议不可用错误。
cat /tmp/dhcp.payload | nc -ub 255.255.255.255 68 -s $SERVER -p 67 -w0
-b开关允许发送广播消息,这是为什么必须从超级用户下运行服务器的第二个原因。
有什么限制?
此DHCP服务器经过简化设计,就像网络上的单个客户端一样。 但是,它将与多个客户端一起使用。 只要获取最快的地址即可。

结论
尽管尽管很难将bash脚本称为成熟的编程语言,但是您甚至可以解决诸如在网络上发布IP地址之类的问题,而无需为此使用专门设计的软件。 解决特定问题不仅带来欢乐,而且还带来解决方案时打开的新知识。
资料来源
- DHCP-维基百科
- DHCP和BOOTP参数-IANA