zoukankan      html  css  js  c++  java
  • 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. 生成器

  • 相关阅读:
    php总结4——数组的定义及函数、冒泡排序
    php总结3——基本函数、流程控制中的循环
    php总结2——php中的变量、数据类型及转换、运算符、流程控制中的分支结构
    php总结1 ——php简介、工作原理、运行环境、文件构成、语法结构、注释
    php中$t=date()函数参数意义及时间更改
    80端口未被占用,apache无法启动,命令行运行httpd.exe提示文档内容有错
    创建node.js一个简单的应用实例
    windows系统下nodejs、npm、express的下载和安装教程——2016.11.09
    前端工程师必备技能
    用于string对象中字符截取的几种函数总结——语法、参数意义及用途举例
  • 原文地址:https://www.cnblogs.com/stacklike/p/8149888.html
Copyright © 2011-2022 走看看