使用Python实现基本的DHCP服务器动态分配客户端IP地址

目录

  1. 什么是DHCP
  2. DHCP的工作原理
  3. 环境准备
  4. 实现步骤
  5. 完整代码示例
  6. 总结

什么是DHCP

DHCP(动态主机配置协议)是一种网络管理协议,用于自动分配IP地址及其他配置信息(如子网掩码、默认网关和DNS服务器)给计算机或网络设备。DHCP减少了手动配置每个设备的需要,提高了网络的灵活性和可管理性。

DHCP的工作原理

DHCP协议的基本流程包括以下几个步骤:

  1. DHCP Discover:客户端通过广播请求可用的DHCP服务器。
  2. DHCP Offer:DHCP服务器收到请求后,返回一个包含IP地址及其他配置的报文。
  3. DHCP Request:客户端选择一个DHCP服务器并发送请求以获取提供的IP地址。
  4. DHCP Acknowledgment:DHCP服务器确认分配该IP地址,完成地址分配过程。

环境准备

在开始实现DHCP服务器之前,需要确保以下环境准备好:

  • Python 3.x
  • socket模块(Python内置)
  • struct模块(Python内置)

实现步骤

接下来,我们将通过Python实现一个简单的DHCP服务器:

  1. 创建Socket:使用UDP协议创建Socket。
  2. 监听客户端请求:等待客户端的DHCP Discover消息。
  3. 发送DHCP Offer:当收到请求后,发送DHCP Offer消息。
  4. 处理DHCP Request:等待客户端请求特定的IP地址。
  5. 发送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地址分配过程。