PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/extras-3.10/pyroute2/dhcp/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/extras-3.10/pyroute2/dhcp/__init__.py |
''' DHCP protocol ============= The DHCP implementation here is far from complete, but already provides some basic functionality. Later it will be extended with IPv6 support and more DHCP options will be added. Right now it can be interesting mostly to developers, but not users and/or system administrators. So, the development hints first. The packet structure description is intentionally implemented as for netlink packets. Later these two parsers, netlink and generic, can be merged, so the syntax is more or less compatible. Packet fields ------------- There are two big groups of items within any DHCP packet. First, there are BOOTP/DHCP packet fields, they're defined with the `fields` attribute:: class dhcp4msg(msg): fields = ((name, format, policy), (name, format, policy), ... (name, format, policy)) The `name` can be any literal. Format should be specified as for the struct module, like `B` for `uint8`, or `i` for `int32`, or `>Q` for big-endian uint64. There are also aliases defined, so one can write `uint8` or `be16`, or like that. Possible aliases can be seen in the `pyroute2.protocols` module. The `policy` is a bit complicated. It can be a number or literal, and it will mean that it is a default value, that should be encoded if no other value is given. But when the `policy` is a dictionary, it can contain keys as follows:: 'l2addr': {'format': '6B', 'decode': ..., 'encode': ...} Keys `encode` and `decode` should contain filters to be used in decoding and encoding procedures. The encoding filter should accept the value from user's definition and should return a value that can be packed using `format`. The decoding filter should accept a value, decoded according to `format`, and should return value that can be used by a user. The `struct` module can not decode IP addresses etc, so they should be decoded as `4s`, e.g. Further transformation from 4 bytes string to a string like '10.0.0.1' performs the filter. DHCP options ------------ DHCP options are described in a similar way:: options = ((code, name, format), (code, name, format), ... (code, name, format)) Code is a `uint8` value, name can be any string literal. Format is a string, that must have a corresponding class, inherited from `pyroute2.dhcp.option`. One can find these classes in `pyroute2.dhcp` (more generic) or in `pyroute2.dhcp.dhcp4msg` (IPv4-specific). The option class must reside within dhcp message class. Every option class can be decoded in two ways. If it has fixed width fields, it can be decoded with ordinary `msg` routines, and in this case it can look like that:: class client_id(option): fields = (('type', 'uint8'), ('key', 'l2addr')) If it must be decoded by some custom rules, one can define the policy just like for the fields above:: class array8(option): policy = {'format': 'string', 'encode': lambda x: array('B', x).tobytes(), 'decode': lambda x: array('B', x).tolist()} In the corresponding modules, like in `pyroute2.dhcp.dhcp4msg`, one can define as many custom DHCP options, as one need. Just be sure, that they are compatible with the DHCP server and all fit into 1..254 (`uint8`) -- the 0 code is used for padding and the code 255 is the end of options code. ''' import struct import sys from array import array from pyroute2.common import basestring from pyroute2.protocols import msg BOOTREQUEST = 1 BOOTREPLY = 2 DHCPDISCOVER = 1 DHCPOFFER = 2 DHCPREQUEST = 3 DHCPDECLINE = 4 DHCPACK = 5 DHCPNAK = 6 DHCPRELEASE = 7 DHCPINFORM = 8 if not hasattr(array, 'tobytes'): # Python2 and Python3 versions of array differ, # but we need here a consistent API w/o warnings class array(array): tobytes = array.tostring class option(msg): code = 0 data_length = 0 policy = None value = None def __init__(self, content=None, buf=b'', offset=0, value=None, code=0): msg.__init__( self, content=content, buf=buf, offset=offset, value=value ) self.code = code @property def length(self): if self.data_length is None: return None if self.data_length == 0: return 1 else: return self.data_length + 2 def encode(self): # pack code self.buf += struct.pack('B', self.code) if self.code in (0, 255): return self # save buf save = self.buf self.buf = b'' # pack data into the new buf if self.policy is not None: value = self.policy.get('encode', lambda x: x)(self.value) if self.policy['format'] == 'string': fmt = '%is' % len(value) else: fmt = self.policy['format'] if sys.version_info[0] == 3 and isinstance(value, str): value = value.encode('utf-8') self.buf = struct.pack(fmt, value) else: msg.encode(self) # get the length data = self.buf self.buf = save self.buf += struct.pack('B', len(data)) # attach the packed data self.buf += data return self def decode(self): self.data_length = struct.unpack( 'B', self.buf[self.offset + 1 : self.offset + 2] )[0] if self.policy is not None: if self.policy['format'] == 'string': fmt = '%is' % self.data_length else: fmt = self.policy['format'] value = struct.unpack( fmt, self.buf[self.offset + 2 : self.offset + 2 + self.data_length], ) if len(value) == 1: value = value[0] value = self.policy.get('decode', lambda x: x)(value) if ( isinstance(value, basestring) and self.policy['format'] == 'string' ): value = value[: value.find(b'\x00')] self.value = value else: # remember current offset as msg.decode() will advance it offset = self.offset # move past the code and option length bytes so that msg.decode() # starts parsing at the right location self.offset += 2 msg.decode(self) # restore offset so that dhcpmsg.decode() advances it correctly self.offset = offset return self class dhcpmsg(msg): options = () l2addr = None _encode_map = {} _decode_map = {} def _register_options(self): for option in self.options: code, name, fmt = option[:3] self._decode_map[code] = self._encode_map[name] = { 'name': name, 'code': code, 'format': fmt, } def decode(self): msg.decode(self) self._register_options() self['options'] = {} while self.offset < len(self.buf): code = struct.unpack('B', self.buf[self.offset : self.offset + 1])[ 0 ] if code == 0: self.offset += 1 continue if code == 255: return self # code is unknown -- bypass it if code not in self._decode_map: length = struct.unpack( 'B', self.buf[self.offset + 1 : self.offset + 2] )[0] self.offset += length + 2 continue # code is known, work on it option_class = getattr(self, self._decode_map[code]['format']) option = option_class(buf=self.buf, offset=self.offset) option.decode() self.offset += option.length if option.value is not None: value = option.value else: value = option self['options'][self._decode_map[code]['name']] = value return self def encode(self): msg.encode(self) self._register_options() # put message type options = self.get('options') or { 'message_type': DHCPDISCOVER, 'parameter_list': [1, 3, 6, 12, 15, 28], } self.buf += ( self.uint8(code=53, value=options['message_type']).encode().buf ) self.buf += ( self.client_id({'type': 1, 'key': self['chaddr']}, code=61) .encode() .buf ) self.buf += self.string(code=60, value='pyroute2').encode().buf for name, value in options.items(): if name in ('message_type', 'client_id', 'vendor_id'): continue fmt = self._encode_map.get(name, {'format': None})['format'] if fmt is None: continue # name is known, ok option_class = getattr(self, fmt) if isinstance(value, dict): option = option_class( value, code=self._encode_map[name]['code'] ) else: option = option_class( code=self._encode_map[name]['code'], value=value ) self.buf += option.encode().buf self.buf += self.none(code=255).encode().buf return self class none(option): pass class be16(option): policy = {'format': '>H'} class be32(option): policy = {'format': '>I'} class uint8(option): policy = {'format': 'B'} class string(option): policy = {'format': 'string'} class array8(option): policy = { 'format': 'string', 'encode': lambda x: array('B', x).tobytes(), 'decode': lambda x: array('B', x).tolist(), } class client_id(option): fields = (('type', 'uint8'), ('key', 'l2addr'))