Python的网络编程[4] -> DHCP 协议[1] -> DHCP 的 Python 实现
DHCP实现 / DHCP Implement
目录
下面介绍建立一个简单的DHCP服务器,主要用于对基本的DHCP请求进行响应,目前只提供一个IP为客户端使用,实现最基本的通信示例。理论内容可参考 DHCP 理论部分。
首先是基本服务器的建立,这个服务器实现了最基本的对DISCOVER和REQUEST报文的响应,在验证时会对魔术字进行验证,此处未对BOOTP进行处理,验证通过后会对Options字段进行验证,此处利用生成器来对字段进行获取,最终处理完所有信息后结束。
1 import socket 2 import struct 3 import binascii 4 import logging 5 from threading import Thread 6 from dhcp_offer import Offer 7 8 logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] %(threadName)s: %(message)s') 9 10 class DHCPServer(): 11 """ 12 This class implements parts of RFC-2131 13 Only DHCPDISCOVER and DHCPREQUEST allowed 14 """ 15 def __init__(self, boot_file=None, server_ip=None, offer_ip=None, tftp_ip=None): 16 Thread.__init__(self) 17 self._port = 67 18 self._boot_file = boot_file 19 self._file_index = 0 20 self._offer_ip = offer_ip 21 self._tftp_ip = tftp_ip 22 self.server_ip = server_ip 23 24 @property 25 def server_ip(self): 26 return self._server_ip 27 28 @server_ip.setter 29 def server_ip(self, server_ip): 30 self._server_ip = server_ip 31 self.send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 32 self.send_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) 33 self.send_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) 34 self.send_socket.bind((self._server_ip, self._port)) 35 36 @property 37 def boot_file(self): 38 return self._boot_file 39 40 @boot_file.setter 41 def boot_file(self, boot_file): 42 if not isinstance(boot_file, list): 43 boot_file = [boot_file] 44 self._boot_file = boot_file 45 self._file_index = 0 46 47 @property 48 def offer_ip(self): 49 return self._offer_ip 50 51 @offer_ip.setter 52 def offer_ip(self, offer_ip): 53 self._offer_ip = offer_ip 54 55 def check_msg(self, m): 56 is_valid, msg_type, select_ip = False, None, None 57 if (m[0] == b'\x01' and 58 m[1] == b'\x01' and 59 m[2] == b'\x06' and 60 m[3] == b'\x00' and 61 m[10:12] == [b'\x00', b'\x00'] and 62 m[12:16] == [b'\x00', b'\x00', b'\x00', b'\x00'] and 63 m[16:20] == [b'\x00', b'\x00', b'\x00', b'\x00'] and 64 m[20:24] == [b'\x00', b'\x00', b'\x00', b'\x00'] and 65 m[236:240] == [b'\x63', b'\x82', b'\x53', b'\x63'] 66 ): 67 logging.warning('Valid DHCP message') 68 # Valid DHCPDISCOVER 69 opt = (x for x in m[240:]) 70 while opt: 71 try: 72 func_code = next(opt) 73 if func_code == b'\x00': 74 break 75 length = next(opt) 76 items = b'' 77 for i in range(ord(length)): 78 items += next(opt) 79 except StopIteration: 80 break 81 else: 82 if func_code == b'\x35' and length == b'\x01': 83 if items == b'\x01': 84 logging.warning('DHCP Discover') 85 msg_type = 'DSCV' 86 is_valid = True 87 if items == b'\x03': 88 logging.warning('DHCP Request') 89 msg_type = 'RQST' 90 91 # Assure DHCP server selected 92 if func_code == b'\x36' and msg_type == 'RQST': 93 logging.warning('DHCP Server Identifier check') 94 select_ip = socket.inet_ntoa(items) 95 96 # Double check DHCP offer ip 97 if func_code == b'\x32' and select_ip == self._server_ip: 98 offer_ip = socket.inet_ntoa(items) 99 if offer_ip == self._offer_ip: 100 is_valid = True 101 else: 102 logging.warning('Offer ip double check failed') 103 104 return is_valid, msg_type 105 106 def serve_forever(self): 107 108 self.recv_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 109 self.recv_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) 110 self.recv_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) 111 self.recv_socket.bind(('', self._port)) 112 113 if self._boot_file: 114 if self._file_index >= len(self._boot_file): 115 self._file_index = 0 116 117 def handle_msg(msg, addr): 118 send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 119 send_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) 120 send_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) 121 send_socket.bind((self._server_ip, self._port)) 122 m = list(struct.unpack('c'*len(msg), msg)) 123 is_valid, msg_type = self.check_msg(m) 124 if is_valid: 125 logging.warning('Valid %s message, try to response' % msg_type) 126 pass_sec = ord(m[8]) * 256 + ord(m[9]) 127 transaction_id = ''.join(['%02x' % ord(x) for x in m[4:8]]) 128 client_mac_id = ':'.join(['%02x' % ord(x) for x in m[28:34]]) 129 if msg_type: 130 offer = Offer(transaction_id=transaction_id, 131 client_ip_offer=self._offer_ip, 132 server_ip=self._server_ip, 133 client_mac_id=client_mac_id, 134 file_path=self._boot_file, 135 pass_sec=pass_sec, 136 msg_type=msg_type, 137 tftp_ip = self._tftp_ip) 138 send_socket.sendto(offer.packet, ('<broadcast>', 68)) 139 self._file_index += 1 140 logging.warning('Respone done') 141 else: 142 logging.warning('Invalid message, discard...') 143 send_socket.close() 144 145 while True: 146 logging.warning('Waiting discovery...') 147 msg, addr = self.recv_socket.recvfrom(8192) 148 logging.warning('Receive message from %s, port %s' % addr) 149 handler = Thread(target=handle_msg, args=(msg, addr)) 150 handler.start() 151 152 if __name__ == '__main__': 153 dhcp = DHCPServer(server_ip='127.0.0.1', offer_ip='127.0.0.10') 154 dhcp.serve_forever()
Note: 此处为做示例,对网关等信息都设置为固定值,匹配服务器ip。
1 import binascii 2 import struct 3 import socket 4 5 class Offer(): 6 def __init__(self, transaction_id, client_ip_offer, server_ip, client_mac_id, file_path, pass_sec, msg_type, tftp_ip=None, lease_time=172800): 7 SERVER_NAME = '' 8 self._server_ip = server_ip 9 self._offer_ip = client_ip_offer 10 self._lease_time = lease_time 11 if not tftp_ip: 12 tftp_ip = server_ip 13 self._tftp_ip = tftp_ip 14 if not file_path: 15 file_path = '' 16 pass_sec = struct.pack('!H', pass_sec) 17 client_mac_id = binascii.unhexlify(client_mac_id.replace(':', '')) 18 transaction_id = binascii.unhexlify(transaction_id) 19 20 self.packet = b'' 21 self.packet += b'\x02' # op 22 self.packet += b'\x01' # htype 23 self.packet += b'\x06' # hlen 24 self.packet += b'\x00' # hops 25 self.packet += transaction_id 26 self.packet += pass_sec # secs 27 self.packet += b'\x00\x00' # flags 28 self.packet += b'\x00\x00\x00\x00' # current client ip 29 self.packet += socket.inet_aton(client_ip_offer) # offer ip 30 self.packet += socket.inet_aton(server_ip) # server ip 31 self.packet += b'\x00\x00\x00\x00' # gateway ip 32 self.packet += client_mac_id # client mac id 33 self.packet += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # client mac id padding 34 self.packet += SERVER_NAME.encode('utf-8') 35 self.packet += b'\x00'*(64-len(SERVER_NAME)) 36 self.packet += file_path.encode('utf-8') 37 self.packet += b'\x00'*(128-len(file_path)) 38 self.packet += self.optional(msg_type) 39 40 def optional(self, msg_type): 41 magic = b'\x63\x82\x53\x63' 42 opt = b'' 43 # Message type 44 if msg_type == 'DSCV': 45 opt += self.encode_int(53, 1, 2) 46 if msg_type == 'RQST': 47 opt += self.encode_int(53, 1, 5) 48 # Server identifier 49 opt += self.encode_ip(54, 4, self._server_ip) 50 # Subnet mask 51 opt += self.encode_ip(1, 4, '255.255.255.0') 52 # Router 53 opt += self.encode_ip(3, 4, '127.0.0.1') 54 # DNS server 55 opt += self.encode_ip(6, 4, '127.0.0.1') 56 # NetBios server 57 opt += self.encode_ip(44, 4, '127.0.0.1') 58 # IP address lease time 59 opt += self.encode_int(51, 4, self._lease_time) 60 # Extend lease time T1 61 opt += self.encode_int(58, 4, int(self._lease_time*0.5)) 62 # Extend lease time T2 63 opt += self.encode_int(59, 4, int(self._lease_time*0.8)) 64 # Log server 65 opt += self.encode_ip(7, 4, '127.0.0.1') 66 # TFTP server name 67 opt += bytes([66, 11]) + self._tftp_ip.encode() 68 # Tail 69 # TODO: find out why a b'\xff' for end 70 opt += b'\xff' 71 return magic+opt 72 73 def encode_int(self, func, length, item): 74 m = {1: '!B', 2: '!H', 4: '!I'} 75 s = b'' 76 s += (bytes([func, length]) + struct.pack(m[length], item)) 77 return s 78 79 def encode_ip(self, func, length, item): 80 s = b'' 81 s += bytes([func, length]) 82 s += socket.inet_aton(item) 83 return s
相关阅读
1. DHCP 理论
2. 生成器