使用Python实现基本的DHCP服务器动态分配客户端IP地址
目录
- 什么是DHCP
- DHCP的工作原理
- 环境准备
- 实现步骤
- 完整代码示例
- 总结
什么是DHCP
DHCP(动态主机配置协议)是一种网络管理协议,用于自动分配IP地址及其他配置信息(如子网掩码、默认网关和DNS服务器)给计算机或网络设备。DHCP减少了手动配置每个设备的需要,提高了网络的灵活性和可管理性。
DHCP的工作原理
DHCP协议的基本流程包括以下几个步骤:
- DHCP Discover:客户端通过广播请求可用的DHCP服务器。
- DHCP Offer:DHCP服务器收到请求后,返回一个包含IP地址及其他配置的报文。
- DHCP Request:客户端选择一个DHCP服务器并发送请求以获取提供的IP地址。
- DHCP Acknowledgment:DHCP服务器确认分配该IP地址,完成地址分配过程。
环境准备
在开始实现DHCP服务器之前,需要确保以下环境准备好:
- Python 3.x
socket
模块(Python内置)struct
模块(Python内置)
实现步骤
接下来,我们将通过Python实现一个简单的DHCP服务器:
- 创建Socket:使用UDP协议创建Socket。
- 监听客户端请求:等待客户端的DHCP Discover消息。
- 发送DHCP Offer:当收到请求后,发送DHCP Offer消息。
- 处理DHCP Request:等待客户端请求特定的IP地址。
- 发送DHCP Acknowledgment:确认并分配IP地址。
完整代码示例
DHCP服务器代码示例
import socket
import struct
# DHCP服务器的基本设置
SERVER_IP = '192.168.1.21' # 指定的服务器IP地址
SERVER_PORT = 11167 # 修改DHCP服务端口67,因为本机已经获取DHCP分配的地址,实验用使用固定的端口67无法正常运行
CLIENT_PORT = 11168 # 修改DHCP客户端端口68
IP_POOL_START = 100 # 可用IP地址池起始部分
IP_POOL_END = 200 # 可用IP地址池结束部分
# 创建UDP Socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((SERVER_IP, SERVER_PORT))
# 存储已分配的IP地址
allocated_ips = set()
'''
遍历可用IP地址池,检查每个IP是否已被分配。
如果找到未分配的IP,则返回该IP;如果没有可用IP,则返回 None。
'''
def get_available_ip():
"""获取可用的IP地址"""
for ip in range(IP_POOL_START, IP_POOL_END + 1):
if ip not in allocated_ips:
return f'192.168.0.{ip}' # 返回可用IP地址
return None
'''
send_offer 函数构建并发送DHCP Offer消息:
首先,调用 get_available_ip() 获取一个可用的IP地址。
然后,使用 struct.pack 函数将数据打包成符合DHCP报文格式的二进制字符串。各个字段对应于DHCP协议的要求。
使用 sock.sendto 方法将构建的消息发送回客户端,目标为客户端的IP和客户端端口。
将分配的IP添加到 allocated_ips 集合中,以避免重复分配。'''
def send_offer(client_ip, transaction_id):
"""发送DHCP Offer消息"""
offered_ip = get_available_ip()
if offered_ip is None:
print("没有可用的IP地址")
return
print(f"Sending DHCP Offer to {client_ip} with offered IP {offered_ip}")
op = 2 # Boot Reply
htype = 1 # Ethernet
hlen = 6 # Hardware address length
hops = 0 # Client hops
secs = 0 # Seconds elapsed
flags = 0 # Flags
ciaddr = 0 # Client IP address (通常为0)
yiaddr = socket.inet_aton(offered_ip) # Your IP address
siaddr = 0 # Server IP address (通常为0)
giaddr = 0 # Gateway IP address (通常为0)
chaddr = b'\x00' * 16 # Client hardware address
# 打包消息
try:
message = struct.pack('!BBBBIHHIIII16s',
op, htype, hlen, hops,
transaction_id, secs, flags,
ciaddr, int.from_bytes(yiaddr, 'big'), siaddr, giaddr,
chaddr)
except Exception as e:
print(f"Error packing message: {e}")
return
sock.sendto(message, (client_ip, CLIENT_PORT))
allocated_ips.add(int(offered_ip.split('.')[-1]))
print(f"已分配IP地址: {offered_ip}")
def main():
print("DHCP服务器正在运行...")
while True:
data, addr = sock.recvfrom(1024)
print(f"收到来自 {addr} 的信息") # 打印发送者的地址
transaction_id = struct.unpack('!I', data[4:8])[0]
client_ip = addr[0] # 确保客户端IP地址是字符串
if data[0] == 1: # op: Boot Request
print("收到DHCP Discover消息")
send_offer(client_ip, transaction_id)
if __name__ == "__main__":
main()
DHCP客户端代码示例
import socket
import struct
import time
# DHCP客户端配置
SERVER_IP = '192.168.1.21' # 广播地址,用于发现DHCP服务器,如果用255.255.255.255受网络限制,直接指定本机IP地址。
CLIENT_PORT = 11168 # 使用一个非特权端口
SERVER_PORT = 11167 # DHCP服务器端口
def create_discover_message():
"""创建DHCP Discover消息"""
transaction_id = int(time.time()) & 0xFFFFFFFF # 获取事务ID
message = struct.pack('!BBBBIHHIIII16s',
1, # op: Boot Request
1, # htype: Ethernet
6, # hlen: Hardware address length
0, # hops
transaction_id, # xid: Transaction ID
0, # secs: Seconds elapsed
0, # flags: Flags
0, # ciaddr: Client IP address
0, # yiaddr: Your IP address
0, # siaddr: Server IP address
0, # giaddr: Gateway IP address
b'\x00' * 16) # chaddr: Client hardware address
return message, transaction_id
def main():
# 创建UDP Socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', CLIENT_PORT)) # 绑定到客户端端口
# 创建Discover消息
discover_message, transaction_id = create_discover_message()
# 广播Discover消息
sock.sendto(discover_message, (SERVER_IP, SERVER_PORT))
print("发送DHCP Discover消息...")
# 接收Offer消息
sock.settimeout(5) # 设置超时为5秒
try:
data, addr = sock.recvfrom(1024)
response_transaction_id = struct.unpack('!I', data[4:8])[0]
if response_transaction_id == transaction_id:
offered_ip = socket.inet_ntoa(data[16:20]) # 提取分配的IP地址
print(f"收到DHCP Offer消息,分配的IP地址: {offered_ip}")
else:
print("收到不相关的Offer消息")
except socket.timeout:
print("未收到DHCP Offer消息")
if __name__ == "__main__":
main()
运行结果如下:
服务器端:
收到来自 ('192.168.1.21', 11168) 的信息
收到DHCP Discover消息
Sending DHCP Offer to 192.168.1.21 with offered IP 192.168.0.103
已分配IP地址: 192.168.0.103
客户端:
发送DHCP Discover消息...
收到DHCP Offer消息,分配的IP地址: 192.168.0.103
注意事项
• 以上代码仅作为模拟DHCP动态分配IP地址的示例,实际的DHCP协议要复杂得多,包括更多的报文类型和选项。
• 在实际部署时,需要确保服务器IP地址、客户端MAC地址等配置正确,并且服务器具有分配IP地址的能力。
• 这个示例没有实现完整的DHCP协议流程,包括请求(Request)、确认(Ack)等步骤。
• 在实际应用中,可能需要处理更多的网络异常和安全问题。
这个示例提供了一个基础框架,可以根据需要进行扩展和完善,以更准确地模拟DHCP协议的动态IP地址分配过程。