Python的网络编程[4] -> DHCP 协议[1] -> DHCP 的 Python 实现

DHCP实现 / DHCP Implement


目录

  1. DHCP 服务器建立过程
  2. DHCP 报文加码实现过程

下面介绍建立一个简单的DHCP服务器,主要用于对基本的DHCP请求进行响应,目前只提供一个IP为客户端使用,实现最基本的通信示例。理论内容可参考 DHCP 理论部分。

1 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()

2 DHCP 报文加码实现过程

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'x00x00'  # flags
28         self.packet += b'x00x00x00x00'  # 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'x00x00x00x00'  # gateway ip
32         self.packet += client_mac_id # client mac id
33         self.packet += b'x00x00x00x00x00x00x00x00x00x00'  # 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'x63x82x53x63'
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. 生成器

原文地址:https://www.cnblogs.com/stacklike/p/8149888.html