
该项目的目的是:
- 通过IPv4网络学习DHCP
- 学习Python(仅是从零开始;)
- 替换了原来的DB2DHCP服务器(我的叉子),在新的OS下组装起来越来越难。 是的,我不喜欢该二进制文件,无法“立即更改”
- 获得可以使用订户的mac或一堆mac交换机+端口选择订户IP地址的功能的DHCP服务器(选项82)
- 写另一辆自行车(哦!这是我最喜欢的消遣)
- 让Lyuli谈论他对Habrahabr的s眼(或最好邀请);)
结果:它有效;)在FreeBSD和Ubuntu OS上测试。 从理论上讲,可以要求代码在任何操作系统下都可以工作,因为 代码中没有特定的绑定。
注意事项 还有很多。
链接到存储库,以使粉丝
“动起来” 。
安装,配置和使用“硬件研究”结果的过程要少得多,其次是有关DHCP协议的一些理论。 为了我自己。 对于这个故事;)
一点理论
什么是DHCP?
这是一种网络协议,允许设备从DHCP服务器中找到其IP地址(以及其他参数,如网关,DNS等)。 数据包通过UDP交换。 请求网络参数时设备的一般操作原理如下:
- 设备(客户端)通过网络发出UDP广播请求(DHCPDISCOVER),请求为“嗯,有人,给我IP地址”。 通常(但并非总是),请求来自端口68(源),而目的地是端口67(目的地)。 一些设备还从端口67发送数据包。 在DHCPDISCOVER数据包内,包括客户端设备的MAC地址。
- 网络上所有(可能有多个)DHCP服务器的形式,是发送带有网络设置的DHCPDISCOVER DHCPOFFER语句的设备,它也通过网络广播该设备。 此数据包旨在提供给谁的标识将到达DHCPDISCOVER请求中较早提供的客户端的MAC地址。
- 客户端接收具有网络设置提议的数据包,选择最具吸引力的数据包(条件可能有所不同,例如,根据数据包传递的时间,中间路由的数量),并使DHCP服务器通过网络设置“正式请求” DHCPREQUEST。 在这种情况下,数据包将到达特定的DHCP服务器。
- 收到DHCPREQUEST的服务器发送DHCPACK数据包,在该数据包中它再次列出此客户端的网络设置

此外,还有来自客户端的DHCPINFORM数据包,其目的是通知DHCP服务器“客户端处于活动状态”并使用发出的网络设置。 在此服务器的实现中,将忽略这些数据包。
封包格式
通常,以太网数据包帧如下所示:

在我们的情况下,我们将仅考虑直接来自UDP数据包内容的数据,而不考虑OSI层协议标头,即DHCP结构:
DHCP发现
因此,获取设备IP地址的过程始于DHCP客户端从端口68向255.255.255.255:67发送广播请求的事实。 在此程序包中,客户端包括其MAC地址以及希望从DHCP服务器接收的内容。 下表描述了包装的结构。
DHCPDISCOVER数据包结构表包装位置 | 值名称 | 例子 | 投稿 | 字节数 | 解说 |
1个 | 开机要求 | 1个 | 十六进制 | 1个 | 消息类型。 1-从客户端到服务器的请求,2-从服务器到客户端的响应 |
2 | 硬件类型 | 1个 | 十六进制 | 1个 | 硬件地址的类型,在此协议中为1-MAC |
3 | 硬件长度 | 6 | 十六进制 | 1个 | 设备MAC地址长度 |
4 | 啤酒花 | 1个 | 十六进制 | 1个 | 中间路线数 |
5 | 交易编号 | 23:比照:de:1天 | 十六进制 | 4 | 唯一交易标识符。 由客户端在请求操作开始时生成 |
7 | 秒逝去 | 0 | 十六进制 | 4 | 从获取地址开始的时间(以秒为单位) |
9 | Bootp标志 | 0 | 十六进制 | 2 | 可以设置一些标志来指示协议参数 |
11 | 客户端IP地址 | 0.0.0.0 | 弦乐 | 4 | 客户端IP地址(如果有) |
15 | 您的客户IP地址 | 0.0.0.0 | 弦乐 | 4 | 服务器建议的IP地址(如果有) |
19 | 下一个服务器IP地址 | 0.0.0.0 | 弦乐 | 4 | 服务器IP地址(如果知道) |
23 | 中继代理IP地址 | 172.16.114.41 | 弦乐 | 4 | 中继代理IP地址(例如,交换机) |
27 | 客户端MAC地址 | 14:d6:4d:a7:c9:55 | 十六进制 | 6 | 数据包发送方(客户端)的MAC地址 |
31 | 客户端硬件地址填充 | | 十六进制 | 10 | 预留的地方。 通常为零 |
41 | 服务器主机名 | | 弦乐 | 64 | DHCP服务器的名称。 通常不传送 |
105 | 启动文件名 | | 弦乐 | 128 | 启动期间无盘工作站使用的服务器上的文件名 |
235 | 魔术饼干 | 63:82:53:63 | 十六进制 | 4 | “魔术”数字,包括。 您可以确定此数据包属于DHCP协议 |
DHCP选项。 可以按任何顺序进行 |
236 | 选件编号 | 53 | 十二月 | 1个 | 选项53指定DHCP数据包的类型
1-DHCP发现 3-DHCP请求 2-DHCPOFFER 5-DHCPACK 8-DHCPINFORM |
| 选项长度 | 1个 | 十二月 | 1个 |
| 期权价值 | 1个 | 十二月 | 1个 |
| 选件编号 | 50 | 十二月 | 1个 | 客户想要接收什么IP地址 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 172.16.134.61 | 弦乐 | 4 |
| 选件编号 | 55 | | 1个 | 客户端请求的网络参数。 成分可能不同
01-网络掩码 03-网关 06-DNS oc-主机名 0f-网络域名 1c-广播请求的地址(广播) 42-TFTP服务器名称 79-无类静态路由 |
| 选项长度 | 8 | | 1个 |
| 期权价值 | 01:03:06:0c:0f:1c:42:79 | | 8 |
| 选件编号 | 82 | 十二月 | | 选项82,其中转发器设备的MAC地址和一些其他值被传输。
最常见的是运行DHCP终端客户端的交换机端口,此选项“嵌入”了其他选项,第一个字节是“ suboption”的编号,第二个字节是其长度,然后是其值。
在这种情况下,在选项82中,子选项是嵌套的: 代理电路ID = 00:04:00:01:00:04,其中最后两个字节是发出请求的DHCP客户端端口
代理远程ID = 00:06:c8:是:19:93:11:48-DHCP中继设备的MAC地址 |
| 选项长度 | 18岁 | 十二月 | |
| 期权价值 | 01:06 00:04:00:01:00:04 02:08 00:06:c8:是:19:93:11:48 | 十六进制 | |
| 包装结束 | 255 | 十二月 | 1个 | 255表示包装的结尾 |
DHCP优惠
服务器一旦收到DHCPDISCOVER数据包,并且如果发现它可以向客户端提供所请求的内容,则对它形成响应-DHCPOFFER。 答案被发送到端口“从那里来”,广播,因为 目前,客户端还没有IP地址,因此,只有发送广播后,他才能接收数据包。 客户端通过该数据包内其地址处的MAC识别出该数据包是他的数据包,以及该客户在创建第一个数据包时生成的交易号。
DHCPOFFER数据包结构表包装位置 | 价值名称(通用) | 例子 | 投稿 | 字节数 | 解说 |
1个 | 开机要求 | 1个 | 十六进制 | 1个 | 消息类型。 1-从客户端到服务器的请求,2-从服务器到客户端的响应 |
2 | 硬件类型 | 1个 | 十六进制 | 1个 | 硬件地址的类型,在此协议中为1-MAC |
3 | 硬件长度 | 6 | 十六进制 | 1个 | 设备MAC地址长度 |
4 | 啤酒花 | 1个 | 十六进制 | 1个 | 中间路线数 |
5 | 交易编号 | 23:比照:de:1天 | 十六进制 | 4 | 唯一交易标识符。 由客户端在请求操作开始时生成 |
7 | 秒逝去 | 0 | 十六进制 | 4 | 从获取地址开始的时间(以秒为单位) |
9 | Bootp标志 | 0 | 十六进制 | 2 | 可以将某些标志设置为协议参数的指示。 在这种情况下,0表示单播请求类型 |
11 | 客户端IP地址 | 0.0.0.0 | 弦乐 | 4 | 客户端IP地址(如果有) |
15 | 您的客户IP地址 | 172.16.134.61 | 弦乐 | 4 | 服务器建议的IP地址(如果有) |
19 | 下一个服务器IP地址 | 0.0.0.0 | 弦乐 | 4 | 服务器IP地址(如果知道) |
23 | 中继代理IP地址 | 172.16.114.41 | 弦乐 | 4 | 中继代理IP地址(例如,交换机) |
27 | 客户端MAC地址 | 14:d6:4d:a7:c9:55 | 十六进制 | 6 | 数据包发送方(客户端)的MAC地址 |
31 | 客户端硬件地址填充 | | 十六进制 | 10 | 预留的地方。 通常为零 |
41 | 服务器主机名 | | 弦乐 | 64 | DHCP服务器的名称。 通常不传送 |
105 | 启动文件名 | | 弦乐 | 128 | 启动期间无盘工作站使用的服务器上的文件名 |
235 | 魔术饼干 | 63:82:53:63 | 十六进制 | 4 | “魔术”数字,包括。 您可以确定此数据包属于DHCP协议 |
DHCP选项。 可以按任何顺序进行 |
236 | 选件编号 | 53 | 十二月 | 1个 | 选项53指定DHCP 2数据包的类型-DHCPOFFER |
| 选项长度 | 1个 | 十二月 | 1个 |
| 期权价值 | 2 | 十二月 | 1个 |
| 选件编号 | 1个 | 十二月 | 1个 | 提供DHCP客户端网络掩码的选件 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 255.255.224.0 | 弦乐 | 4 |
| 选件编号 | 3 | 十二月 | 1个 | 提供DHCP客户端默认网关的选件 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 172.16.12.1 | 弦乐 | 4 |
| 选件编号 | 6 | 十二月 | 1个 | 向DHCP客户端提供DHCP的选项 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 8.8.8.8 | 弦乐 | 4 |
| 选件编号 | 51 | 十二月 | 1个 | 发出的网络参数的生存时间(以秒为单位),之后DHCP客户端必须再次请求它们 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 86400 | 十二月 | 4 |
| 选件编号 | 82 | 十二月 | 1个 | 选项82,重复DHCPDISCOVER中的内容 |
| 选项长度 | 18岁 | 十二月 | 1个 |
| 期权价值 | 01:08:00:06:00 01:01:00:00:01 02:06:00:03:0f 26:4天:ec | 十二月 | 18岁 |
| 包装结束 | 255 | 十二月 | 1个 | 255表示包装的结尾 |
DHCP请求
客户端收到DHCPOFFER之后,它会形成一个包,其中包含对网络参数的请求,该请求不是发给网络中的所有DHCP服务器的,而是发给一个特定的服务器,它最喜欢“ DHCPOFFER”的提议。 “喜欢”的标准可以不同,并且取决于DHCP客户端的实现。 使用DHCP服务器的MAC地址指示请求的接收者。 另外,如果先前已收到服务器的IP地址,则客户端可以发送DHCPREQUEST数据包,而不必提前形成DHCPDISCOVER。
DHCPREQUEST数据包结构表包装位置 | 价值名称(通用) | 例子 | 投稿 | 字节数 | 解说 |
1个 | 开机要求 | 1个 | 十六进制 | 1个 | 消息类型。 1-从客户端到服务器的请求,2-从服务器到客户端的响应 |
2 | 硬件类型 | 1个 | 十六进制 | 1个 | 硬件地址的类型,在此协议中为1-MAC |
3 | 硬件长度 | 6 | 十六进制 | 1个 | 设备MAC地址长度 |
4 | 啤酒花 | 1个 | 十六进制 | 1个 | 中间路线数 |
5 | 交易编号 | 23:比照:de:1天 | 十六进制 | 4 | 唯一交易标识符。 由客户端在请求操作开始时生成 |
7 | 秒逝去 | 0 | 十六进制 | 4 | 从获取地址开始的时间(以秒为单位) |
9 | Bootp标志 | 8000 | 十六进制 | 2 | 可以将某些标志设置为协议参数的指示。 在这种情况下,“广播” |
11 | 客户端IP地址 | 0.0.0.0 | 弦乐 | 4 | 客户端IP地址(如果有) |
15 | 您的客户IP地址 | 172.16.134.61 | 弦乐 | 4 | 服务器建议的IP地址(如果有) |
19 | 下一个服务器IP地址 | 0.0.0.0 | 弦乐 | 4 | 服务器IP地址(如果知道) |
23 | 中继代理IP地址 | 172.16.114.41 | 弦乐 | 4 | 中继代理IP地址(例如,交换机) |
27 | 客户端MAC地址 | 14:d6:4d:a7:c9:55 | 十六进制 | 6 | 数据包发送方(客户端)的MAC地址 |
31 | 客户端硬件地址填充 | | 十六进制 | 10 | 预留的地方。 通常为零 |
41 | 服务器主机名 | | 弦乐 | 64 | DHCP服务器的名称。 通常不传送 |
105 | 启动文件名 | | 弦乐 | 128 | 启动期间无盘工作站使用的服务器上的文件名 |
235 | 魔术饼干 | 63:82:53:63 | 十六进制 | 4 | “魔术”数字,包括。 您可以确定此数据包属于DHCP协议 |
DHCP选项。 可以按任何顺序进行 |
236 | 选件编号 | 53 | 十二月 | 3 | 选项53指定DHCP 3数据包的类型-DHCPREQUEST |
| 选项长度 | 1个 | 十二月 | 1个 |
| 期权价值 | 3 | 十二月 | 1个 |
| 选件编号 | 61 | 十二月 | 1个 | 客户端ID:01(用于Ehernet)+客户端的MAC地址 |
| 选项长度 | 7 | 十二月 | 1个 |
| 期权价值 | 01:2c:ab:25:ff:72:a6 | 十六进制 | 7 |
| 选件编号 | 60 | 十二月 | | “供应商类别标识符”。 就我而言,报告了DHCP客户端的版本。 也许其他设备返回了其他内容。 Windows例如报告MSFT 5.0 |
| 选项长度 | 11 | 十二月 | |
| 期权价值 | udhcp 0.9.8 | 弦乐 | |
| 选件编号 | 55 | | 1个 | 客户端请求的网络参数。 成分可能不同
01-网络掩码 03-网关 06-DNS oc-主机名 0f-网络域名 1c-广播请求的地址(广播) 42-TFTP服务器名称 79-无类静态路由 |
| 选项长度 | 8 | | 1个 |
| 期权价值 | 01:03:06:0c:0f:1c:42:79 | | 8 |
| 选件编号 | 82 | 十二月 | 1个 | 选项82,重复DHCPDISCOVER中的内容 |
| 选项长度 | 18岁 | 十二月 | 1个 |
| 期权价值 | 01:08:00:06:00 01:01:00:00:01 02:06:00:03:0f 26:4天:ec | 十二月 | 18岁 |
| 包装结束 | 255 | 十二月 | 1个 | 255表示包装的结尾 |
DHCP确认
为了确认DHCP服务器“是的,这是您的IP地址,我不会将其提供给其他任何人”这一事实的确认,从服务器到客户端都有DHCPACK格式的数据包。 它与其余发送广播的数据包相同。 虽然,在下面用Python实现的DHCP服务器的代码中,以防万一,我通过将数据包发送到特定的客户端IP(如果已知)来复制任何广播请求。 此外,DHCP服务器根本不在乎DHCPACK数据包是否已到达客户端。 如果客户端未收到DHCPACK,则过一会儿,它将重复DHCPREQUEST
DHCPACK数据包结构表包装位置 | 价值名称(通用) | 例子 | 投稿 | 字节数 | 解说 |
1个 | 开机要求 | 2 | 十六进制 | 1个 | 消息类型。 1-从客户端到服务器的请求,2-从服务器到客户端的响应 |
2 | 硬件类型 | 1个 | 十六进制 | 1个 | 硬件地址的类型,在此协议中为1-MAC |
3 | 硬件长度 | 6 | 十六进制 | 1个 | 设备MAC地址长度 |
4 | 啤酒花 | 1个 | 十六进制 | 1个 | 中间路线数 |
5 | 交易编号 | 23:比照:de:1天 | 十六进制 | 4 | 唯一交易标识符。 由客户端在请求操作开始时生成 |
7 | 秒逝去 | 0 | 十六进制 | 4 | 从获取地址开始的时间(以秒为单位) |
9 | Bootp标志 | 8000 | 十六进制 | 2 | 可以将某些标志设置为协议参数的指示。 在这种情况下,“广播” |
11 | 客户端IP地址 | 0.0.0.0 | 弦乐 | 4 | 客户端IP地址(如果有) |
15 | 您的客户IP地址 | 172.16.134.61 | 弦乐 | 4 | 服务器建议的IP地址(如果有) |
19 | 下一个服务器IP地址 | 0.0.0.0 | 弦乐 | 4 | 服务器IP地址(如果知道) |
23 | 中继代理IP地址 | 172.16.114.41 | 弦乐 | 4 | 中继代理IP地址(例如,交换机) |
27 | 客户端MAC地址 | 14:d6:4d:a7:c9:55 | 十六进制 | 6 | 数据包发送方(客户端)的MAC地址 |
31 | 客户端硬件地址填充 | | 十六进制 | 10 | 预留的地方。 通常为零 |
41 | 服务器主机名 | | 弦乐 | 64 | DHCP服务器的名称。 通常不传送 |
105 | 启动文件名 | | 弦乐 | 128 | 启动期间无盘工作站使用的服务器上的文件名 |
235 | 魔术饼干 | 63:82:53:63 | 十六进制 | 4 | “魔术”数字,包括。 您可以确定此数据包属于DHCP协议 |
DHCP选项。 可以按任何顺序进行 |
236 | 选件编号 | 53 | 十二月 | 3 | 选项53指定DHCP数据包类型5-DHCPACK |
| 选项长度 | 1个 | 十二月 | 1个 |
| 期权价值 | 5 | 十二月 | 1个 |
| 选件编号 | 1个 | 十二月 | 1个 | 提供DHCP客户端网络掩码的选件 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 255.255.224.0 | 弦乐 | 4 |
| 选件编号 | 3 | 十二月 | 1个 | 提供DHCP客户端默认网关的选件 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 172.16.12.1 | 弦乐 | 4 |
| 选件编号 | 6 | 十二月 | 1个 | 向DHCP客户端提供DHCP的选项 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 8.8.8.8 | 弦乐 | 4 |
| 选件编号 | 51 | 十二月 | 1个 | 发出的网络参数的生存时间(以秒为单位),之后DHCP客户端必须再次请求它们 |
| 选项长度 | 4 | 十二月 | 1个 |
| 期权价值 | 86400 | 十二月 | 4 |
| 选件编号 | 82 | 十二月 | 1个 | 选项82,重复DHCPDISCOVER中的内容 |
| 选项长度 | 18岁 | 十二月 | 1个 |
| 期权价值 | 01:08:00:06:00 01:01:00:00:01 02:06:00:03:0f 26:4天:ec | 十二月 | 18岁 |
| 包装结束 | 255 | 十二月 | 1个 | 255表示包装的结尾 |
安装方式
安装实际上包括安装作业所需的python模块。 假定已经安装和配置了MySQL。
Freebsd
pkg安装python3
python3 -m确保
pip3安装mysql连接器
的Ubuntu
须藤apt-get install python3
须藤apt-get install pip3
sudo pip3安装mysql连接器
我们创建MySQL数据库,填写转储pydhcp.sql,配置配置文件。
构型
所有服务器设置均为xml文件格式。 参考文件:
<?xml version =“ 1.0”?>
<配置>
<dhcpserver>
<host> 0.0.0.0 </ host>
<broadcast> 255.255.255.255 </ broadcast>
<DHCPServer> 192.168.0.71 </ DHCPServer>
<LeaseTime> 8600 </ LeaseTime>
<ThreadLimit> 1 </ ThreadLimit>
<defaultMask> 255.255.255.0 </ defaultMask>
<defaultRouter> 192.168.0.1 </ defaultRouter>
<defaultDNS> 8.8.8.8 </ defaultDNS>
</ dhcpserver>
<mysql>
<host>本地主机</ host>
<username>测试</ username>
<password>测试</ password>
<basename> pydhcp </ basename>
</ mysql>
<选项>
<option> option_82_hex:sw_port1:20:22 </ option>
<option> option_82_hex:sw_port2:16:18 </ option>
<option> option_82_hex:sw_mac:26:40 </ option>
</ options>
<查询>
<offer_count> 3 </ offer_count>
<offer_1>从上位(mac)=上位('{option_82_AgentRemoteId_hex}')和上位(端口)=上位('{option_82_AgentCircuitId_port_hex}')的用户中选择ip,掩码,路由器和dns </ offer_1>
<offer_2>从上层(mac)=上层('{sw_mac}')和上层(端口)=上层('{sw_port2}')的用户中选择IP,掩码,路由器,dns </ offer_2>
<offer_3>从上位(mac)=上位('{ClientMacAddress}')的用户中选择IP,掩码,路由器,dns </ offer_3>
<history_sql>插入到历史记录(id,dt,mac,ip,注释)值(null,now(),'{ClientMacAddress}','{RequestedIpAddress}','DHCPACK / INFORM')</ history_sql>
</ query>
</ config>
现在更详细地介绍标签:
dhcpserver部分描述了启动服务器的基本设置,即:
- 主机-服务器在端口67上侦听的IP地址
- 广播-该IP是DHCPOFFER和DHCPACK的广播
- DHCPServer-什么是DHCP服务器的IP
- 颁发的IP地址的LeaseTime租赁时间
- ThreadLimit-同时运行多少个线程来处理端口67上的传入UDP数据包。假定这将对高负载的项目有所帮助;)
- defaultMask,defaultRouter,defaultDNS-如果在数据库中找到IP,但默认未提供给订户,则为订户默认提供什么
Mysql部分:
主机,用户名,密码,基本名-一切都能说明一切。
GitHub上发布的示例数据库结构
查询部分:本部分描述对OFFER / ACK的请求:
- offer_count-请求的行数,返回的格式为ip,mask,router,dns
- offer_n是查询字符串。 如果退货为空,则执行以下报价请求
- history_sql-例如,订户向“授权历史记录”的写入请求
选项部分中的任何变量或DHCP协议中的选项都可以参与请求。
部分选项。 在这里,它已经变得更加有趣了。 在这里,我们可以创建变量,以便稍后在查询部分中使用。
例如:
option_82_hex:sw_port1:20:22
,此行是将选项82的DHCP请求中出现的整行(十六进制格式)从20到22字节(包括20和22字节)的命令全部放入并放入新变量sw_port1(请求来自的交换机端口)中
option_82_hex:sw_mac:26:40
,定义sw_mac变量,取26:40范围内的十六进制
通过使用-d开关启动服务器,可以查看可以在查询中使用的所有可能选项。 我们将看到类似以下日志的内容:
-DHCPINFORM数据包来自端口0025224ad764的67,b'\ x91 \ xa5 \ xe0 \ xa3 \ xa5 \ xa9- \ x8f \ x8a',('172.30.114.25',68)
{'ClientMacAddress':'0025224ad764',
'ClientMacAddressByte':b'\ x00%“ J \ xd7d”,
'HType':'以太网',
'HostName':b'\ x91 \ xa5 \ xe0 \ xa3 \ xa5 \ xa9- \ x8f \ x8a',
'ReqListDNS':是的,
'ReqListDomainName':是的,
'ReqListPerfowmRouterDiscover':是的,
'ReqListRouter':是的,
'ReqListStaticRoute':是的,
'ReqListSubnetMask':是的,
'ReqListVendorSpecInfo':43
'RequestedIpAddress':'0.0.0.0',
'供应商':b'MSFT 5.0',
'chaddr':'0025224ad764',
'ciaddr':'172.30.128.13',
'标志':b'\ x00 \ x00',
'giaddr':'172.30.114.25',
'gpoz':308,
'海伦':6,
'hops':1
'htype':'MAC',
'magic_cookie':b'c \ x82Sc',
'op':'DHCPINFORM',
'option12':12,
'option53':53
'option55':55,
'option60':60,
'option61':61,
'option82':82,
'option_82_byte':b'\ x12 \ x01 \ x06 \ x00 \ x04 \ x00 \ x01 \ x00 \ x06 \ x02 \ x08 \ x00'
b'\ x06 \ x00 \ x1eX \ x9e \ xb2 \ xad',
'option_82_hex':'12010600040001000602080006001e589eb2ad',
'option_82_len':18,
'option_82_str':“ b'\\ x12 \\ x01 \\ x06 \\ x00 \\ x04 \\ x00 \\ x01 \\ x00 \\ x06 \\ x02 \\ x08 \\ x00 \\ x06 \\ x00 \ \ x1eX \\ x9e \\ xb2 \\ xad'“,
“结果”:错误,
'秒':768,
'siaddr':'0.0.0.0',
'sw_mac':'001e589eb2ad',
'sw_port1':'06',
'xidbyte':b'<\ x89} \ x8c',
'xidhex':'3c897d8c',
'yiaddr':'0.0.0.0'}
因此,我们可以将任何变量包装在{}中,并将其用于SQL查询中。
让我们获取客户端接收IP地址的历史记录:


服务器启动
./pydhcpdb.py -d -c config.xml
-d输出模式到DEBUG控制台
-c <文件名>配置文件
汇报
现在,更多有关在Python中实现服务器的信息。 这很痛苦。 Python是动态研究的。 很多时候都是这样的:“哇,我以某种方式做了什么。” 根本没有优化,之所以采用这种形式,主要是因为对python的开发经验很少。 我将在“代码”中介绍服务器实现的最有趣的时刻。
XML配置文件解析器
使用标准的Python模块xml.dom。 这似乎很简单,但是在实施过程中,使用此模块的网络上明显缺乏明智的文档和示例。
树= minidom.parse(gconfig [“ config_file”])
mconfig = tree.getElementsByTagName(“ mysql”)
对于mconfig中的elem:
gconfig [“ mysql_host”] = elem.getElementsByTagName(“主机”)[0] .firstChild.data
gconfig [“ mysql_username”] = elem.getElementsByTagName(“用户名”)[0] .firstChild.data
gconfig [“ mysql_password”] = elem.getElementsByTagName(“ password”)[0] .firstChild.data
gconfig [“ mysql_basename”] = elem.getElementsByTagName(“ basename”)[0] .firstChild.data
dconfig = tree.getElementsByTagName(“ dhcpserver”)
对于dconfig中的elem:
gconfig [“ broadcast”] = elem.getElementsByTagName(“ broadcast”)[0] .firstChild.data
gconfig [“ dhcp_host”] = elem.getElementsByTagName(“主机”)[0] .firstChild.data
gconfig [“ dhcp_LeaseTime”] = elem.getElementsByTagName(“ LeaseTime”)[0] .firstChild.data
gconfig [“ dhcp_ThreadLimit”] = int(elem.getElementsByTagName(“ ThreadLimit”)[0] .firstChild.data)
gconfig [“ dhcp_Server”] = elem.getElementsByTagName(“ DHCPServer”)[0] .firstChild.data
gconfig [“ dhcp_defaultMask”] = elem.getElementsByTagName(“ defaultMask”)[0] .firstChild.data
gconfig [“ dhcp_defaultRouter”] = elem.getElementsByTagName(“ defaultRouter”)[0] .firstChild.data
gconfig [“ dhcp_defaultDNS”] = elem.getElementsByTagName(“ defaultDNS”)[0] .firstChild.data
qconfig = tree.getElementsByTagName(“查询”)
对于qconfig中的elem:
gconfig [“ offer_count”] = elem.getElementsByTagName(“ offer_count”)[0] .firstChild.data
对于范围内的num(int(gconfig [“ offer_count”])):
gconfig [“ offer _” + str(num + 1)] = elem.getElementsByTagName(“ offer _” + str(num + 1))[0] .firstChild.data
gconfig [“ history_sql”] = elem.getElementsByTagName(“ history_sql”)[0] .firstChild.data
选项= tree.getElementsByTagName(“选项”)
对于选项中的元素:
节点= elem.getElementsByTagName(“选项”)
对于节点中的选项:
optionsMod.append(options.firstChild.data)
多线程
奇怪的是,Python中的多线程实现非常清晰,简单。
def PacketWork(数据,地址):
...
#对收到的包进行分析,并给出答案
...
而True:
data,addr = udp_socket.recvfrom(1024)#等待UDP数据包
thread = threading.Thread(target = PacketWork,args =(data,addr,))。start()#-运行前面在后台使用参数定义的PacketWork函数
而threading.active_count()> gconfig [“ dhcp_ThreadLimit”]:
time.sleep(1)#如果已运行的线程数大于设置中的线程数,请等待直到线程数减少
接收/发送DHCP数据包
为了拦截通过网卡的UDP数据包,您需要“提升”套接字:
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP)
udp_socket.bind((gconfig [“ dhcp_host”],67))
标志是:
- AF_INET-表示地址格式为IP:端口。 也许是AF_UNIX-地址由文件名给出。
- SOCK_DGRAM-表示我们不接受“原始数据包”,而是已经通过防火墙,并且部分切断了数据包。 即 我们仅获得UDP数据包,而没有UDP数据包包装程序的“物理”组件。 如果使用SOCK_RAW标志,则仍然需要解析此“包装器”。
发送数据包就像广播一样:
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)#将套接字切换到广播模式
rz = udp_socket.sendto(packetack,(gconfig [“ broadcast”],68)
,然后到地址“软件包的来源”:
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#将套接字切换为“许多侦听器”模式
rz = udp_socket.sendto(packetack,addr)
其中SOL_SOCKET表示设置选项的“协议级别”,
SO_BROADCAST选项是“广播”头盔包
SO_REUSEADDR选项将套接字切换到多侦听器模式。 从理论上讲,在这种情况下是没有必要的,但是在我测试过的一台FreeBSD服务器上,如果没有该选项,代码将无法正常工作。
DHCP报文解析
这是我真正喜欢Python的地方。 事实证明,“框”使您可以轻松地处理字节码。 允许将其转换为十进制值,字符串和十六进制非常简单-即 我们实际上需要了解软件包的结构。 因此,例如,您可以获得十六进制的一个字节范围,而只有字节:
res [“ xidhex”] =数据[4:8] .hex()
res [“ xidbyte”] =数据[4:8]
,将字节打包成一个结构:
res [“ flags”] =包(“ BB”,数据[10],数据[11])
从结构获取IP:
res [“ ciaddr”] = socket.inet_ntoa(pack('BBBB',data [12],data [13],data [14],data [15]));
反之亦然:
res = res + socket.inet_pton(socket.AF_INET,gconfig [“ dhcp_Server”])
就这些;)