1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li""" 7*9c5db199SXin LiTools for serializing and deserializing DHCP packets. 8*9c5db199SXin Li 9*9c5db199SXin LiDhcpPacket is a class that represents a single DHCP packet and contains some 10*9c5db199SXin Lilogic to create and parse binary strings containing on the wire DHCP packets. 11*9c5db199SXin Li 12*9c5db199SXin LiWhile you could call the constructor explicitly, most users should use the 13*9c5db199SXin Listatic factories to construct packets with reasonable default values in most of 14*9c5db199SXin Lithe fields, even if those values are zeros. 15*9c5db199SXin Li 16*9c5db199SXin LiFor example: 17*9c5db199SXin Li 18*9c5db199SXin Lipacket = dhcp_packet.create_offer_packet(transaction_id, 19*9c5db199SXin Li hwmac_addr, 20*9c5db199SXin Li offer_ip, 21*9c5db199SXin Li server_ip) 22*9c5db199SXin Lisocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 23*9c5db199SXin Li# Sending to the broadcast address needs special permissions. 24*9c5db199SXin Lisocket.sendto(response_packet.to_binary_string(), 25*9c5db199SXin Li ("255.255.255.255", 68)) 26*9c5db199SXin Li 27*9c5db199SXin LiNote that if you make changes, make sure that the tests in the bottom of this 28*9c5db199SXin Lifile still pass. 29*9c5db199SXin Li""" 30*9c5db199SXin Li 31*9c5db199SXin Lifrom __future__ import absolute_import 32*9c5db199SXin Lifrom __future__ import division 33*9c5db199SXin Lifrom __future__ import print_function 34*9c5db199SXin Li 35*9c5db199SXin Liimport collections 36*9c5db199SXin Liimport logging 37*9c5db199SXin Liimport random 38*9c5db199SXin Liimport six 39*9c5db199SXin Lifrom six.moves import range 40*9c5db199SXin Liimport socket 41*9c5db199SXin Liimport struct 42*9c5db199SXin Li 43*9c5db199SXin Li 44*9c5db199SXin Lidef get_ord(value): 45*9c5db199SXin Li """ 46*9c5db199SXin Li Helper method for getting the ordinal value of a character in a byte string 47*9c5db199SXin Li during the Python 2 to Python 3 migration. 48*9c5db199SXin Li 49*9c5db199SXin Li In Python 2, the function ord() provides the ordinal value of a character. 50*9c5db199SXin Li In Python 3, the byte is its own ordinal value. 51*9c5db199SXin Li """ 52*9c5db199SXin Li if six.PY2: 53*9c5db199SXin Li return ord(value) 54*9c5db199SXin Li return value 55*9c5db199SXin Li 56*9c5db199SXin Li 57*9c5db199SXin Lidef get_bytes(value): 58*9c5db199SXin Li """ 59*9c5db199SXin Li Helper method for converting a string into a byte string during the Python 2 60*9c5db199SXin Li to Python 3 migration. 61*9c5db199SXin Li """ 62*9c5db199SXin Li if six.PY2: 63*9c5db199SXin Li return value 64*9c5db199SXin Li return value.encode('ISO-8859-1') 65*9c5db199SXin Li 66*9c5db199SXin Li 67*9c5db199SXin Lidef get_string(value): 68*9c5db199SXin Li """ 69*9c5db199SXin Li Helper method for converting a byte string into a string during the Python 2 70*9c5db199SXin Li to Python 3 migration. 71*9c5db199SXin Li """ 72*9c5db199SXin Li 73*9c5db199SXin Li if six.PY2: 74*9c5db199SXin Li return value 75*9c5db199SXin Li return value.decode('ISO-8859-1') 76*9c5db199SXin Li 77*9c5db199SXin Li 78*9c5db199SXin Lidef CreatePacketPieceClass(super_class, field_format): 79*9c5db199SXin Li class PacketPiece(super_class): 80*9c5db199SXin Li @staticmethod 81*9c5db199SXin Li def pack(value): 82*9c5db199SXin Li return struct.pack(field_format, value) 83*9c5db199SXin Li 84*9c5db199SXin Li @staticmethod 85*9c5db199SXin Li def unpack(byte_string): 86*9c5db199SXin Li return struct.unpack(field_format, byte_string)[0] 87*9c5db199SXin Li return PacketPiece 88*9c5db199SXin Li 89*9c5db199SXin Li""" 90*9c5db199SXin LiRepresents an option in a DHCP packet. Options may or may not be present in any 91*9c5db199SXin Ligiven packet, depending on the configurations of the client and the server. 92*9c5db199SXin LiUsing namedtuples as super classes gets us the comparison operators we want to 93*9c5db199SXin Liuse these Options in dictionaries as keys. Below, we'll subclass Option to 94*9c5db199SXin Lireflect that different kinds of options serialize to on the wire formats in 95*9c5db199SXin Lidifferent ways. 96*9c5db199SXin Li 97*9c5db199SXin Li|name| 98*9c5db199SXin LiA human readable name for this option. 99*9c5db199SXin Li 100*9c5db199SXin Li|number| 101*9c5db199SXin LiEvery DHCP option has a number that goes into the packet to indicate 102*9c5db199SXin Liwhich particular option is being encoded in the next few bytes. This 103*9c5db199SXin Liproperty returns that number for each option. 104*9c5db199SXin Li""" 105*9c5db199SXin LiOption = collections.namedtuple("Option", ["name", "number"]) 106*9c5db199SXin Li 107*9c5db199SXin LiByteOption = CreatePacketPieceClass(Option, "!B") 108*9c5db199SXin Li 109*9c5db199SXin LiShortOption = CreatePacketPieceClass(Option, "!H") 110*9c5db199SXin Li 111*9c5db199SXin LiIntOption = CreatePacketPieceClass(Option, "!I") 112*9c5db199SXin Li 113*9c5db199SXin Liclass IpAddressOption(Option): 114*9c5db199SXin Li @staticmethod 115*9c5db199SXin Li def pack(value): 116*9c5db199SXin Li return socket.inet_aton(value) 117*9c5db199SXin Li 118*9c5db199SXin Li @staticmethod 119*9c5db199SXin Li def unpack(byte_string): 120*9c5db199SXin Li return socket.inet_ntoa(byte_string) 121*9c5db199SXin Li 122*9c5db199SXin Li 123*9c5db199SXin Liclass IpListOption(Option): 124*9c5db199SXin Li @staticmethod 125*9c5db199SXin Li def pack(value): 126*9c5db199SXin Li return b"".join([socket.inet_aton(addr) for addr in value]) 127*9c5db199SXin Li 128*9c5db199SXin Li @staticmethod 129*9c5db199SXin Li def unpack(byte_string): 130*9c5db199SXin Li return [socket.inet_ntoa(byte_string[idx:idx+4]) 131*9c5db199SXin Li for idx in range(0, len(byte_string), 4)] 132*9c5db199SXin Li 133*9c5db199SXin Li 134*9c5db199SXin Liclass RawOption(Option): 135*9c5db199SXin Li @staticmethod 136*9c5db199SXin Li def pack(value): 137*9c5db199SXin Li return get_bytes(value) 138*9c5db199SXin Li 139*9c5db199SXin Li @staticmethod 140*9c5db199SXin Li def unpack(byte_string): 141*9c5db199SXin Li return get_string(byte_string) 142*9c5db199SXin Li 143*9c5db199SXin Li 144*9c5db199SXin Liclass ByteListOption(Option): 145*9c5db199SXin Li @staticmethod 146*9c5db199SXin Li def pack(value): 147*9c5db199SXin Li if six.PY2: 148*9c5db199SXin Li return "".join(chr(v) for v in value) 149*9c5db199SXin Li return bytes(value) 150*9c5db199SXin Li 151*9c5db199SXin Li @staticmethod 152*9c5db199SXin Li def unpack(byte_string): 153*9c5db199SXin Li if six.PY2: 154*9c5db199SXin Li return [ord(c) for c in byte_string] 155*9c5db199SXin Li return byte_string 156*9c5db199SXin Li 157*9c5db199SXin Li 158*9c5db199SXin Liclass ClasslessStaticRoutesOption(Option): 159*9c5db199SXin Li """ 160*9c5db199SXin Li This is a RFC 3442 compliant classless static route option parser and 161*9c5db199SXin Li serializer. The symbolic "value" packed and unpacked from this class 162*9c5db199SXin Li is a list (prefix_size, destination, router) tuples. 163*9c5db199SXin Li """ 164*9c5db199SXin Li 165*9c5db199SXin Li @staticmethod 166*9c5db199SXin Li def pack(value): 167*9c5db199SXin Li route_list = value 168*9c5db199SXin Li byte_string = b"" 169*9c5db199SXin Li for prefix_size, destination, router in route_list: 170*9c5db199SXin Li byte_string += get_bytes(chr(prefix_size)) 171*9c5db199SXin Li # Encode only the significant octets of the destination 172*9c5db199SXin Li # that fall within the prefix. 173*9c5db199SXin Li destination_address_count = (prefix_size + 7) // 8 174*9c5db199SXin Li destination_address = socket.inet_aton(destination) 175*9c5db199SXin Li byte_string += destination_address[:destination_address_count] 176*9c5db199SXin Li byte_string += socket.inet_aton(router) 177*9c5db199SXin Li 178*9c5db199SXin Li return byte_string 179*9c5db199SXin Li 180*9c5db199SXin Li @staticmethod 181*9c5db199SXin Li def unpack(byte_string): 182*9c5db199SXin Li route_list = [] 183*9c5db199SXin Li offset = 0 184*9c5db199SXin Li while offset < len(byte_string): 185*9c5db199SXin Li prefix_size = get_ord(byte_string[offset]) 186*9c5db199SXin Li destination_address_count = (prefix_size + 7) // 8 187*9c5db199SXin Li entry_end = offset + 1 + destination_address_count + 4 188*9c5db199SXin Li if entry_end > len(byte_string): 189*9c5db199SXin Li raise Exception("Classless domain list is corrupted.") 190*9c5db199SXin Li offset += 1 191*9c5db199SXin Li destination_address_end = offset + destination_address_count 192*9c5db199SXin Li destination_address = byte_string[offset:destination_address_end] 193*9c5db199SXin Li # Pad the destination address bytes with zero byte octets to 194*9c5db199SXin Li # fill out an IPv4 address. 195*9c5db199SXin Li destination_address += b'\x00' * (4 - destination_address_count) 196*9c5db199SXin Li router_address = byte_string[destination_address_end:entry_end] 197*9c5db199SXin Li route_list.append((prefix_size, 198*9c5db199SXin Li socket.inet_ntoa(destination_address), 199*9c5db199SXin Li socket.inet_ntoa(router_address))) 200*9c5db199SXin Li offset = entry_end 201*9c5db199SXin Li 202*9c5db199SXin Li return route_list 203*9c5db199SXin Li 204*9c5db199SXin Li 205*9c5db199SXin Liclass DomainListOption(Option): 206*9c5db199SXin Li """ 207*9c5db199SXin Li This is a RFC 1035 compliant domain list option parser and serializer. 208*9c5db199SXin Li There are some clever compression optimizations that it does not implement 209*9c5db199SXin Li for serialization, but correctly parses. This should be sufficient for 210*9c5db199SXin Li testing. 211*9c5db199SXin Li """ 212*9c5db199SXin Li # Various RFC's let you finish a domain name by pointing to an existing 213*9c5db199SXin Li # domain name rather than repeating the same suffix. All such pointers are 214*9c5db199SXin Li # two bytes long, specify the offset in the byte string, and begin with 215*9c5db199SXin Li # |POINTER_PREFIX| to distinguish them from normal characters. 216*9c5db199SXin Li POINTER_PREFIX = ord("\xC0") 217*9c5db199SXin Li 218*9c5db199SXin Li @staticmethod 219*9c5db199SXin Li def pack(value): 220*9c5db199SXin Li domain_list = value 221*9c5db199SXin Li byte_string = b"" 222*9c5db199SXin Li for domain in domain_list: 223*9c5db199SXin Li for part in domain.split("."): 224*9c5db199SXin Li byte_string += get_bytes(chr(len(part))) 225*9c5db199SXin Li byte_string += get_bytes(part) 226*9c5db199SXin Li byte_string += b"\x00" 227*9c5db199SXin Li return byte_string 228*9c5db199SXin Li 229*9c5db199SXin Li @staticmethod 230*9c5db199SXin Li def unpack(byte_string): 231*9c5db199SXin Li if six.PY3: 232*9c5db199SXin Li byte_string = byte_string.decode('ISO-8859-1') 233*9c5db199SXin Li domain_list = [] 234*9c5db199SXin Li offset = 0 235*9c5db199SXin Li try: 236*9c5db199SXin Li while offset < len(byte_string): 237*9c5db199SXin Li (new_offset, domain_parts) = DomainListOption._read_domain_name( 238*9c5db199SXin Li byte_string, 239*9c5db199SXin Li offset) 240*9c5db199SXin Li domain_name = ".".join(domain_parts) 241*9c5db199SXin Li domain_list.append(domain_name) 242*9c5db199SXin Li if new_offset <= offset: 243*9c5db199SXin Li raise Exception("Parsing logic error is letting domain " 244*9c5db199SXin Li "list parsing go on forever.") 245*9c5db199SXin Li offset = new_offset 246*9c5db199SXin Li except ValueError: 247*9c5db199SXin Li # Badly formatted packets are not necessarily test errors. 248*9c5db199SXin Li logging.warning("Found badly formatted DHCP domain search list") 249*9c5db199SXin Li return None 250*9c5db199SXin Li return domain_list 251*9c5db199SXin Li 252*9c5db199SXin Li @staticmethod 253*9c5db199SXin Li def _read_domain_name(byte_string, offset): 254*9c5db199SXin Li """ 255*9c5db199SXin Li Recursively parse a domain name from a domain name list. 256*9c5db199SXin Li """ 257*9c5db199SXin Li parts = [] 258*9c5db199SXin Li while True: 259*9c5db199SXin Li if offset >= len(byte_string): 260*9c5db199SXin Li raise ValueError("Domain list ended without a NULL byte.") 261*9c5db199SXin Li maybe_part_len = ord(byte_string[offset]) 262*9c5db199SXin Li offset += 1 263*9c5db199SXin Li if maybe_part_len == 0: 264*9c5db199SXin Li # Domains are terminated with either a 0 or a pointer to a 265*9c5db199SXin Li # domain suffix within |byte_string|. 266*9c5db199SXin Li return (offset, parts) 267*9c5db199SXin Li elif ((maybe_part_len & DomainListOption.POINTER_PREFIX) == 268*9c5db199SXin Li DomainListOption.POINTER_PREFIX): 269*9c5db199SXin Li if offset >= len(byte_string): 270*9c5db199SXin Li raise ValueError("Missing second byte of domain suffix " 271*9c5db199SXin Li "pointer.") 272*9c5db199SXin Li maybe_part_len &= ~DomainListOption.POINTER_PREFIX 273*9c5db199SXin Li pointer_offset = ((maybe_part_len << 8) + 274*9c5db199SXin Li ord(byte_string[offset])) 275*9c5db199SXin Li offset += 1 276*9c5db199SXin Li (_, more_parts) = DomainListOption._read_domain_name( 277*9c5db199SXin Li byte_string, 278*9c5db199SXin Li pointer_offset) 279*9c5db199SXin Li parts.extend(more_parts) 280*9c5db199SXin Li return (offset, parts) 281*9c5db199SXin Li else: 282*9c5db199SXin Li # That byte was actually the length of the next part, not a 283*9c5db199SXin Li # pointer back into the data. 284*9c5db199SXin Li part_len = maybe_part_len 285*9c5db199SXin Li if offset + part_len >= len(byte_string): 286*9c5db199SXin Li raise ValueError("Part of a domain goes beyond data " 287*9c5db199SXin Li "length.") 288*9c5db199SXin Li parts.append(byte_string[offset : offset + part_len]) 289*9c5db199SXin Li offset += part_len 290*9c5db199SXin Li 291*9c5db199SXin Li 292*9c5db199SXin Li""" 293*9c5db199SXin LiRepresents a required field in a DHCP packet. Similar to Option, we'll 294*9c5db199SXin Lisubclass Field to reflect that different fields serialize to on the wire formats 295*9c5db199SXin Liin different ways. 296*9c5db199SXin Li 297*9c5db199SXin Li|name| 298*9c5db199SXin LiA human readable name for this field. 299*9c5db199SXin Li 300*9c5db199SXin Li|offset| 301*9c5db199SXin LiThe |offset| for a field defines the starting byte of the field in the 302*9c5db199SXin Libinary packet string. |offset| is used during parsing, along with 303*9c5db199SXin Li|size| to extract the byte string of a field. 304*9c5db199SXin Li 305*9c5db199SXin Li|size| 306*9c5db199SXin LiFields in DHCP packets have a fixed size that must be respected. This 307*9c5db199SXin Lisize property is used in parsing to indicate that |self._size| number of 308*9c5db199SXin Libytes make up this field. 309*9c5db199SXin Li""" 310*9c5db199SXin LiField = collections.namedtuple("Field", ["name", "offset", "size"]) 311*9c5db199SXin Li 312*9c5db199SXin LiByteField = CreatePacketPieceClass(Field, "!B") 313*9c5db199SXin Li 314*9c5db199SXin LiShortField = CreatePacketPieceClass(Field, "!H") 315*9c5db199SXin Li 316*9c5db199SXin LiIntField = CreatePacketPieceClass(Field, "!I") 317*9c5db199SXin Li 318*9c5db199SXin LiHwAddrField = CreatePacketPieceClass(Field, "!16s") 319*9c5db199SXin Li 320*9c5db199SXin LiServerNameField = CreatePacketPieceClass(Field, "!64s") 321*9c5db199SXin Li 322*9c5db199SXin LiBootFileField = CreatePacketPieceClass(Field, "!128s") 323*9c5db199SXin Li 324*9c5db199SXin Liclass IpAddressField(Field): 325*9c5db199SXin Li @staticmethod 326*9c5db199SXin Li def pack(value): 327*9c5db199SXin Li return socket.inet_aton(value) 328*9c5db199SXin Li 329*9c5db199SXin Li @staticmethod 330*9c5db199SXin Li def unpack(byte_string): 331*9c5db199SXin Li return socket.inet_ntoa(byte_string) 332*9c5db199SXin Li 333*9c5db199SXin Li 334*9c5db199SXin Li# This is per RFC 2131. The wording doesn't seem to say that the packets must 335*9c5db199SXin Li# be this big, but that has been the historic assumption in implementations. 336*9c5db199SXin LiDHCP_MIN_PACKET_SIZE = 300 337*9c5db199SXin Li 338*9c5db199SXin LiIPV4_NULL_ADDRESS = "0.0.0.0" 339*9c5db199SXin Li 340*9c5db199SXin Li# These are required in every DHCP packet. Without these fields, the 341*9c5db199SXin Li# packet will not even pass DhcpPacket.is_valid 342*9c5db199SXin LiFIELD_OP = ByteField("op", 0, 1) 343*9c5db199SXin LiFIELD_HWTYPE = ByteField("htype", 1, 1) 344*9c5db199SXin LiFIELD_HWADDR_LEN = ByteField("hlen", 2, 1) 345*9c5db199SXin LiFIELD_RELAY_HOPS = ByteField("hops", 3, 1) 346*9c5db199SXin LiFIELD_TRANSACTION_ID = IntField("xid", 4, 4) 347*9c5db199SXin LiFIELD_TIME_SINCE_START = ShortField("secs", 8, 2) 348*9c5db199SXin LiFIELD_FLAGS = ShortField("flags", 10, 2) 349*9c5db199SXin LiFIELD_CLIENT_IP = IpAddressField("ciaddr", 12, 4) 350*9c5db199SXin LiFIELD_YOUR_IP = IpAddressField("yiaddr", 16, 4) 351*9c5db199SXin LiFIELD_SERVER_IP = IpAddressField("siaddr", 20, 4) 352*9c5db199SXin LiFIELD_GATEWAY_IP = IpAddressField("giaddr", 24, 4) 353*9c5db199SXin LiFIELD_CLIENT_HWADDR = HwAddrField("chaddr", 28, 16) 354*9c5db199SXin Li# The following two fields are considered "legacy BOOTP" fields but may 355*9c5db199SXin Li# sometimes be used by DHCP clients. 356*9c5db199SXin LiFIELD_LEGACY_SERVER_NAME = ServerNameField("servername", 44, 64); 357*9c5db199SXin LiFIELD_LEGACY_BOOT_FILE = BootFileField("bootfile", 108, 128); 358*9c5db199SXin LiFIELD_MAGIC_COOKIE = IntField("magic_cookie", 236, 4) 359*9c5db199SXin Li 360*9c5db199SXin LiOPTION_TIME_OFFSET = IntOption("time_offset", 2) 361*9c5db199SXin LiOPTION_ROUTERS = IpListOption("routers", 3) 362*9c5db199SXin LiOPTION_SUBNET_MASK = IpAddressOption("subnet_mask", 1) 363*9c5db199SXin LiOPTION_TIME_SERVERS = IpListOption("time_servers", 4) 364*9c5db199SXin LiOPTION_NAME_SERVERS = IpListOption("name_servers", 5) 365*9c5db199SXin LiOPTION_DNS_SERVERS = IpListOption("dns_servers", 6) 366*9c5db199SXin LiOPTION_LOG_SERVERS = IpListOption("log_servers", 7) 367*9c5db199SXin LiOPTION_COOKIE_SERVERS = IpListOption("cookie_servers", 8) 368*9c5db199SXin LiOPTION_LPR_SERVERS = IpListOption("lpr_servers", 9) 369*9c5db199SXin LiOPTION_IMPRESS_SERVERS = IpListOption("impress_servers", 10) 370*9c5db199SXin LiOPTION_RESOURCE_LOC_SERVERS = IpListOption("resource_loc_servers", 11) 371*9c5db199SXin LiOPTION_HOST_NAME = RawOption("host_name", 12) 372*9c5db199SXin LiOPTION_BOOT_FILE_SIZE = ShortOption("boot_file_size", 13) 373*9c5db199SXin LiOPTION_MERIT_DUMP_FILE = RawOption("merit_dump_file", 14) 374*9c5db199SXin LiOPTION_DOMAIN_NAME = RawOption("domain_name", 15) 375*9c5db199SXin LiOPTION_SWAP_SERVER = IpAddressOption("swap_server", 16) 376*9c5db199SXin LiOPTION_ROOT_PATH = RawOption("root_path", 17) 377*9c5db199SXin LiOPTION_EXTENSIONS = RawOption("extensions", 18) 378*9c5db199SXin LiOPTION_INTERFACE_MTU = ShortOption("interface_mtu", 26) 379*9c5db199SXin LiOPTION_VENDOR_ENCAPSULATED_OPTIONS = RawOption( 380*9c5db199SXin Li "vendor_encapsulated_options", 43) 381*9c5db199SXin LiOPTION_REQUESTED_IP = IpAddressOption("requested_ip", 50) 382*9c5db199SXin LiOPTION_IP_LEASE_TIME = IntOption("ip_lease_time", 51) 383*9c5db199SXin LiOPTION_OPTION_OVERLOAD = ByteOption("option_overload", 52) 384*9c5db199SXin LiOPTION_DHCP_MESSAGE_TYPE = ByteOption("dhcp_message_type", 53) 385*9c5db199SXin LiOPTION_SERVER_ID = IpAddressOption("server_id", 54) 386*9c5db199SXin LiOPTION_PARAMETER_REQUEST_LIST = ByteListOption("parameter_request_list", 55) 387*9c5db199SXin LiOPTION_MESSAGE = RawOption("message", 56) 388*9c5db199SXin LiOPTION_MAX_DHCP_MESSAGE_SIZE = ShortOption("max_dhcp_message_size", 57) 389*9c5db199SXin LiOPTION_RENEWAL_T1_TIME_VALUE = IntOption("renewal_t1_time_value", 58) 390*9c5db199SXin LiOPTION_REBINDING_T2_TIME_VALUE = IntOption("rebinding_t2_time_value", 59) 391*9c5db199SXin LiOPTION_VENDOR_ID = RawOption("vendor_id", 60) 392*9c5db199SXin LiOPTION_CLIENT_ID = RawOption("client_id", 61) 393*9c5db199SXin LiOPTION_TFTP_SERVER_NAME = RawOption("tftp_server_name", 66) 394*9c5db199SXin LiOPTION_BOOTFILE_NAME = RawOption("bootfile_name", 67) 395*9c5db199SXin LiOPTION_FULLY_QUALIFIED_DOMAIN_NAME = RawOption("fqdn", 81) 396*9c5db199SXin LiOPTION_DNS_DOMAIN_SEARCH_LIST = DomainListOption("domain_search_list", 119) 397*9c5db199SXin LiOPTION_CLASSLESS_STATIC_ROUTES = ClasslessStaticRoutesOption( 398*9c5db199SXin Li "classless_static_routes", 121) 399*9c5db199SXin LiOPTION_WEB_PROXY_AUTO_DISCOVERY = RawOption("wpad", 252) 400*9c5db199SXin Li 401*9c5db199SXin Li# Unlike every other option, which are tuples like: 402*9c5db199SXin Li# <number, length in bytes, data>, the pad and end options are just 403*9c5db199SXin Li# single bytes "\x00" and "\xff" (without length or data fields). 404*9c5db199SXin LiOPTION_PAD = 0 405*9c5db199SXin LiOPTION_END = 255 406*9c5db199SXin Li 407*9c5db199SXin LiDHCP_COMMON_FIELDS = [ 408*9c5db199SXin Li FIELD_OP, 409*9c5db199SXin Li FIELD_HWTYPE, 410*9c5db199SXin Li FIELD_HWADDR_LEN, 411*9c5db199SXin Li FIELD_RELAY_HOPS, 412*9c5db199SXin Li FIELD_TRANSACTION_ID, 413*9c5db199SXin Li FIELD_TIME_SINCE_START, 414*9c5db199SXin Li FIELD_FLAGS, 415*9c5db199SXin Li FIELD_CLIENT_IP, 416*9c5db199SXin Li FIELD_YOUR_IP, 417*9c5db199SXin Li FIELD_SERVER_IP, 418*9c5db199SXin Li FIELD_GATEWAY_IP, 419*9c5db199SXin Li FIELD_CLIENT_HWADDR, 420*9c5db199SXin Li ] 421*9c5db199SXin Li 422*9c5db199SXin LiDHCP_REQUIRED_FIELDS = DHCP_COMMON_FIELDS + [ 423*9c5db199SXin Li FIELD_MAGIC_COOKIE, 424*9c5db199SXin Li ] 425*9c5db199SXin Li 426*9c5db199SXin LiDHCP_ALL_FIELDS = DHCP_COMMON_FIELDS + [ 427*9c5db199SXin Li FIELD_LEGACY_SERVER_NAME, 428*9c5db199SXin Li FIELD_LEGACY_BOOT_FILE, 429*9c5db199SXin Li FIELD_MAGIC_COOKIE, 430*9c5db199SXin Li ] 431*9c5db199SXin Li 432*9c5db199SXin Li# The op field in an ipv4 packet is either 1 or 2 depending on 433*9c5db199SXin Li# whether the packet is from a server or from a client. 434*9c5db199SXin LiFIELD_VALUE_OP_CLIENT_REQUEST = 1 435*9c5db199SXin LiFIELD_VALUE_OP_SERVER_RESPONSE = 2 436*9c5db199SXin Li# 1 == 10mb ethernet hardware address type (aka MAC). 437*9c5db199SXin LiFIELD_VALUE_HWTYPE_10MB_ETH = 1 438*9c5db199SXin Li# MAC addresses are still 6 bytes long. 439*9c5db199SXin LiFIELD_VALUE_HWADDR_LEN_10MB_ETH = 6 440*9c5db199SXin LiFIELD_VALUE_MAGIC_COOKIE = 0x63825363 441*9c5db199SXin Li 442*9c5db199SXin LiOPTIONS_START_OFFSET = 240 443*9c5db199SXin Li 444*9c5db199SXin LiMessageType = collections.namedtuple('MessageType', 'name option_value') 445*9c5db199SXin Li# From RFC2132, the valid DHCP message types are: 446*9c5db199SXin LiMESSAGE_TYPE_UNKNOWN = MessageType('UNKNOWN', 0) 447*9c5db199SXin LiMESSAGE_TYPE_DISCOVERY = MessageType('DISCOVERY', 1) 448*9c5db199SXin LiMESSAGE_TYPE_OFFER = MessageType('OFFER', 2) 449*9c5db199SXin LiMESSAGE_TYPE_REQUEST = MessageType('REQUEST', 3) 450*9c5db199SXin LiMESSAGE_TYPE_DECLINE = MessageType('DECLINE', 4) 451*9c5db199SXin LiMESSAGE_TYPE_ACK = MessageType('ACK', 5) 452*9c5db199SXin LiMESSAGE_TYPE_NAK = MessageType('NAK', 6) 453*9c5db199SXin LiMESSAGE_TYPE_RELEASE = MessageType('RELEASE', 7) 454*9c5db199SXin LiMESSAGE_TYPE_INFORM = MessageType('INFORM', 8) 455*9c5db199SXin LiMESSAGE_TYPE_BY_NUM = [ 456*9c5db199SXin Li None, 457*9c5db199SXin Li MESSAGE_TYPE_DISCOVERY, 458*9c5db199SXin Li MESSAGE_TYPE_OFFER, 459*9c5db199SXin Li MESSAGE_TYPE_REQUEST, 460*9c5db199SXin Li MESSAGE_TYPE_DECLINE, 461*9c5db199SXin Li MESSAGE_TYPE_ACK, 462*9c5db199SXin Li MESSAGE_TYPE_NAK, 463*9c5db199SXin Li MESSAGE_TYPE_RELEASE, 464*9c5db199SXin Li MESSAGE_TYPE_INFORM 465*9c5db199SXin Li] 466*9c5db199SXin Li 467*9c5db199SXin LiOPTION_VALUE_PARAMETER_REQUEST_LIST_DEFAULT = [ 468*9c5db199SXin Li OPTION_REQUESTED_IP.number, 469*9c5db199SXin Li OPTION_IP_LEASE_TIME.number, 470*9c5db199SXin Li OPTION_SERVER_ID.number, 471*9c5db199SXin Li OPTION_SUBNET_MASK.number, 472*9c5db199SXin Li OPTION_ROUTERS.number, 473*9c5db199SXin Li OPTION_DNS_SERVERS.number, 474*9c5db199SXin Li OPTION_HOST_NAME.number, 475*9c5db199SXin Li ] 476*9c5db199SXin Li 477*9c5db199SXin Li# These are possible options that may not be in every packet. 478*9c5db199SXin Li# Frequently, the client can include a bunch of options that indicate 479*9c5db199SXin Li# that it would like to receive information about time servers, routers, 480*9c5db199SXin Li# lpr servers, and much more, but the DHCP server can usually ignore 481*9c5db199SXin Li# those requests. 482*9c5db199SXin Li# 483*9c5db199SXin Li# Eventually, each option is encoded as: 484*9c5db199SXin Li# <option.number, option.size, [array of option.size bytes]> 485*9c5db199SXin Li# Unlike fields, which make up a fixed packet format, options can be in 486*9c5db199SXin Li# any order, except where they cannot. For instance, option 1 must 487*9c5db199SXin Li# follow option 3 if both are supplied. For this reason, potential 488*9c5db199SXin Li# options are in this list, and added to the packet in this order every 489*9c5db199SXin Li# time. 490*9c5db199SXin Li# 491*9c5db199SXin Li# size < 0 indicates that this is variable length field of at least 492*9c5db199SXin Li# abs(length) bytes in size. 493*9c5db199SXin LiDHCP_PACKET_OPTIONS = [ 494*9c5db199SXin Li OPTION_TIME_OFFSET, 495*9c5db199SXin Li OPTION_ROUTERS, 496*9c5db199SXin Li OPTION_SUBNET_MASK, 497*9c5db199SXin Li OPTION_TIME_SERVERS, 498*9c5db199SXin Li OPTION_NAME_SERVERS, 499*9c5db199SXin Li OPTION_DNS_SERVERS, 500*9c5db199SXin Li OPTION_LOG_SERVERS, 501*9c5db199SXin Li OPTION_COOKIE_SERVERS, 502*9c5db199SXin Li OPTION_LPR_SERVERS, 503*9c5db199SXin Li OPTION_IMPRESS_SERVERS, 504*9c5db199SXin Li OPTION_RESOURCE_LOC_SERVERS, 505*9c5db199SXin Li OPTION_HOST_NAME, 506*9c5db199SXin Li OPTION_BOOT_FILE_SIZE, 507*9c5db199SXin Li OPTION_MERIT_DUMP_FILE, 508*9c5db199SXin Li OPTION_SWAP_SERVER, 509*9c5db199SXin Li OPTION_DOMAIN_NAME, 510*9c5db199SXin Li OPTION_ROOT_PATH, 511*9c5db199SXin Li OPTION_EXTENSIONS, 512*9c5db199SXin Li OPTION_INTERFACE_MTU, 513*9c5db199SXin Li OPTION_VENDOR_ENCAPSULATED_OPTIONS, 514*9c5db199SXin Li OPTION_REQUESTED_IP, 515*9c5db199SXin Li OPTION_IP_LEASE_TIME, 516*9c5db199SXin Li OPTION_OPTION_OVERLOAD, 517*9c5db199SXin Li OPTION_DHCP_MESSAGE_TYPE, 518*9c5db199SXin Li OPTION_SERVER_ID, 519*9c5db199SXin Li OPTION_PARAMETER_REQUEST_LIST, 520*9c5db199SXin Li OPTION_MESSAGE, 521*9c5db199SXin Li OPTION_MAX_DHCP_MESSAGE_SIZE, 522*9c5db199SXin Li OPTION_RENEWAL_T1_TIME_VALUE, 523*9c5db199SXin Li OPTION_REBINDING_T2_TIME_VALUE, 524*9c5db199SXin Li OPTION_VENDOR_ID, 525*9c5db199SXin Li OPTION_CLIENT_ID, 526*9c5db199SXin Li OPTION_TFTP_SERVER_NAME, 527*9c5db199SXin Li OPTION_BOOTFILE_NAME, 528*9c5db199SXin Li OPTION_FULLY_QUALIFIED_DOMAIN_NAME, 529*9c5db199SXin Li OPTION_DNS_DOMAIN_SEARCH_LIST, 530*9c5db199SXin Li OPTION_CLASSLESS_STATIC_ROUTES, 531*9c5db199SXin Li OPTION_WEB_PROXY_AUTO_DISCOVERY, 532*9c5db199SXin Li ] 533*9c5db199SXin Li 534*9c5db199SXin Lidef get_dhcp_option_by_number(number): 535*9c5db199SXin Li for option in DHCP_PACKET_OPTIONS: 536*9c5db199SXin Li if option.number == number: 537*9c5db199SXin Li return option 538*9c5db199SXin Li return None 539*9c5db199SXin Li 540*9c5db199SXin Liclass DhcpPacket(object): 541*9c5db199SXin Li @staticmethod 542*9c5db199SXin Li def create_discovery_packet(hwmac_addr): 543*9c5db199SXin Li """ 544*9c5db199SXin Li Create a discovery packet. 545*9c5db199SXin Li 546*9c5db199SXin Li Fill in fields of a DHCP packet as if it were being sent from 547*9c5db199SXin Li |hwmac_addr|. Requests subnet masks, broadcast addresses, router 548*9c5db199SXin Li addresses, dns addresses, domain search lists, client host name, and NTP 549*9c5db199SXin Li server addresses. Note that the offer packet received in response to 550*9c5db199SXin Li this packet will probably not contain all of that information. 551*9c5db199SXin Li """ 552*9c5db199SXin Li # MAC addresses are actually only 6 bytes long, however, for whatever 553*9c5db199SXin Li # reason, DHCP allocated 12 bytes to this field. Ease the burden on 554*9c5db199SXin Li # developers and hide this detail. 555*9c5db199SXin Li while len(hwmac_addr) < 12: 556*9c5db199SXin Li hwmac_addr += get_bytes(chr(OPTION_PAD)) 557*9c5db199SXin Li 558*9c5db199SXin Li packet = DhcpPacket() 559*9c5db199SXin Li packet.set_field(FIELD_OP, FIELD_VALUE_OP_CLIENT_REQUEST) 560*9c5db199SXin Li packet.set_field(FIELD_HWTYPE, FIELD_VALUE_HWTYPE_10MB_ETH) 561*9c5db199SXin Li packet.set_field(FIELD_HWADDR_LEN, FIELD_VALUE_HWADDR_LEN_10MB_ETH) 562*9c5db199SXin Li packet.set_field(FIELD_RELAY_HOPS, 0) 563*9c5db199SXin Li packet.set_field(FIELD_TRANSACTION_ID, random.getrandbits(32)) 564*9c5db199SXin Li packet.set_field(FIELD_TIME_SINCE_START, 0) 565*9c5db199SXin Li packet.set_field(FIELD_FLAGS, 0) 566*9c5db199SXin Li packet.set_field(FIELD_CLIENT_IP, IPV4_NULL_ADDRESS) 567*9c5db199SXin Li packet.set_field(FIELD_YOUR_IP, IPV4_NULL_ADDRESS) 568*9c5db199SXin Li packet.set_field(FIELD_SERVER_IP, IPV4_NULL_ADDRESS) 569*9c5db199SXin Li packet.set_field(FIELD_GATEWAY_IP, IPV4_NULL_ADDRESS) 570*9c5db199SXin Li packet.set_field(FIELD_CLIENT_HWADDR, hwmac_addr) 571*9c5db199SXin Li packet.set_field(FIELD_MAGIC_COOKIE, FIELD_VALUE_MAGIC_COOKIE) 572*9c5db199SXin Li packet.set_option(OPTION_DHCP_MESSAGE_TYPE, 573*9c5db199SXin Li MESSAGE_TYPE_DISCOVERY.option_value) 574*9c5db199SXin Li return packet 575*9c5db199SXin Li 576*9c5db199SXin Li @staticmethod 577*9c5db199SXin Li def create_offer_packet(transaction_id, 578*9c5db199SXin Li hwmac_addr, 579*9c5db199SXin Li offer_ip, 580*9c5db199SXin Li server_ip): 581*9c5db199SXin Li """ 582*9c5db199SXin Li Create an offer packet, given some fields that tie the packet to a 583*9c5db199SXin Li particular offer. 584*9c5db199SXin Li """ 585*9c5db199SXin Li packet = DhcpPacket() 586*9c5db199SXin Li packet.set_field(FIELD_OP, FIELD_VALUE_OP_SERVER_RESPONSE) 587*9c5db199SXin Li packet.set_field(FIELD_HWTYPE, FIELD_VALUE_HWTYPE_10MB_ETH) 588*9c5db199SXin Li packet.set_field(FIELD_HWADDR_LEN, FIELD_VALUE_HWADDR_LEN_10MB_ETH) 589*9c5db199SXin Li # This has something to do with relay agents 590*9c5db199SXin Li packet.set_field(FIELD_RELAY_HOPS, 0) 591*9c5db199SXin Li packet.set_field(FIELD_TRANSACTION_ID, transaction_id) 592*9c5db199SXin Li packet.set_field(FIELD_TIME_SINCE_START, 0) 593*9c5db199SXin Li packet.set_field(FIELD_FLAGS, 0) 594*9c5db199SXin Li packet.set_field(FIELD_CLIENT_IP, IPV4_NULL_ADDRESS) 595*9c5db199SXin Li packet.set_field(FIELD_YOUR_IP, offer_ip) 596*9c5db199SXin Li packet.set_field(FIELD_SERVER_IP, server_ip) 597*9c5db199SXin Li packet.set_field(FIELD_GATEWAY_IP, IPV4_NULL_ADDRESS) 598*9c5db199SXin Li packet.set_field(FIELD_CLIENT_HWADDR, hwmac_addr) 599*9c5db199SXin Li packet.set_field(FIELD_MAGIC_COOKIE, FIELD_VALUE_MAGIC_COOKIE) 600*9c5db199SXin Li packet.set_option(OPTION_DHCP_MESSAGE_TYPE, 601*9c5db199SXin Li MESSAGE_TYPE_OFFER.option_value) 602*9c5db199SXin Li return packet 603*9c5db199SXin Li 604*9c5db199SXin Li @staticmethod 605*9c5db199SXin Li def create_request_packet(transaction_id, 606*9c5db199SXin Li hwmac_addr): 607*9c5db199SXin Li packet = DhcpPacket() 608*9c5db199SXin Li packet.set_field(FIELD_OP, FIELD_VALUE_OP_CLIENT_REQUEST) 609*9c5db199SXin Li packet.set_field(FIELD_HWTYPE, FIELD_VALUE_HWTYPE_10MB_ETH) 610*9c5db199SXin Li packet.set_field(FIELD_HWADDR_LEN, FIELD_VALUE_HWADDR_LEN_10MB_ETH) 611*9c5db199SXin Li # This has something to do with relay agents 612*9c5db199SXin Li packet.set_field(FIELD_RELAY_HOPS, 0) 613*9c5db199SXin Li packet.set_field(FIELD_TRANSACTION_ID, transaction_id) 614*9c5db199SXin Li packet.set_field(FIELD_TIME_SINCE_START, 0) 615*9c5db199SXin Li packet.set_field(FIELD_FLAGS, 0) 616*9c5db199SXin Li packet.set_field(FIELD_CLIENT_IP, IPV4_NULL_ADDRESS) 617*9c5db199SXin Li packet.set_field(FIELD_YOUR_IP, IPV4_NULL_ADDRESS) 618*9c5db199SXin Li packet.set_field(FIELD_SERVER_IP, IPV4_NULL_ADDRESS) 619*9c5db199SXin Li packet.set_field(FIELD_GATEWAY_IP, IPV4_NULL_ADDRESS) 620*9c5db199SXin Li packet.set_field(FIELD_CLIENT_HWADDR, hwmac_addr) 621*9c5db199SXin Li packet.set_field(FIELD_MAGIC_COOKIE, FIELD_VALUE_MAGIC_COOKIE) 622*9c5db199SXin Li packet.set_option(OPTION_DHCP_MESSAGE_TYPE, 623*9c5db199SXin Li MESSAGE_TYPE_REQUEST.option_value) 624*9c5db199SXin Li return packet 625*9c5db199SXin Li 626*9c5db199SXin Li @staticmethod 627*9c5db199SXin Li def create_acknowledgement_packet(transaction_id, 628*9c5db199SXin Li hwmac_addr, 629*9c5db199SXin Li granted_ip, 630*9c5db199SXin Li server_ip): 631*9c5db199SXin Li packet = DhcpPacket() 632*9c5db199SXin Li packet.set_field(FIELD_OP, FIELD_VALUE_OP_SERVER_RESPONSE) 633*9c5db199SXin Li packet.set_field(FIELD_HWTYPE, FIELD_VALUE_HWTYPE_10MB_ETH) 634*9c5db199SXin Li packet.set_field(FIELD_HWADDR_LEN, FIELD_VALUE_HWADDR_LEN_10MB_ETH) 635*9c5db199SXin Li # This has something to do with relay agents 636*9c5db199SXin Li packet.set_field(FIELD_RELAY_HOPS, 0) 637*9c5db199SXin Li packet.set_field(FIELD_TRANSACTION_ID, transaction_id) 638*9c5db199SXin Li packet.set_field(FIELD_TIME_SINCE_START, 0) 639*9c5db199SXin Li packet.set_field(FIELD_FLAGS, 0) 640*9c5db199SXin Li packet.set_field(FIELD_CLIENT_IP, IPV4_NULL_ADDRESS) 641*9c5db199SXin Li packet.set_field(FIELD_YOUR_IP, granted_ip) 642*9c5db199SXin Li packet.set_field(FIELD_SERVER_IP, server_ip) 643*9c5db199SXin Li packet.set_field(FIELD_GATEWAY_IP, IPV4_NULL_ADDRESS) 644*9c5db199SXin Li packet.set_field(FIELD_CLIENT_HWADDR, hwmac_addr) 645*9c5db199SXin Li packet.set_field(FIELD_MAGIC_COOKIE, FIELD_VALUE_MAGIC_COOKIE) 646*9c5db199SXin Li packet.set_option(OPTION_DHCP_MESSAGE_TYPE, 647*9c5db199SXin Li MESSAGE_TYPE_ACK.option_value) 648*9c5db199SXin Li return packet 649*9c5db199SXin Li 650*9c5db199SXin Li @staticmethod 651*9c5db199SXin Li def create_nak_packet(transaction_id, hwmac_addr): 652*9c5db199SXin Li """ 653*9c5db199SXin Li Create a negative acknowledge packet. 654*9c5db199SXin Li 655*9c5db199SXin Li @param transaction_id: The DHCP transaction ID. 656*9c5db199SXin Li @param hwmac_addr: The client's MAC address. 657*9c5db199SXin Li """ 658*9c5db199SXin Li packet = DhcpPacket() 659*9c5db199SXin Li packet.set_field(FIELD_OP, FIELD_VALUE_OP_SERVER_RESPONSE) 660*9c5db199SXin Li packet.set_field(FIELD_HWTYPE, FIELD_VALUE_HWTYPE_10MB_ETH) 661*9c5db199SXin Li packet.set_field(FIELD_HWADDR_LEN, FIELD_VALUE_HWADDR_LEN_10MB_ETH) 662*9c5db199SXin Li # This has something to do with relay agents 663*9c5db199SXin Li packet.set_field(FIELD_RELAY_HOPS, 0) 664*9c5db199SXin Li packet.set_field(FIELD_TRANSACTION_ID, transaction_id) 665*9c5db199SXin Li packet.set_field(FIELD_TIME_SINCE_START, 0) 666*9c5db199SXin Li packet.set_field(FIELD_FLAGS, 0) 667*9c5db199SXin Li packet.set_field(FIELD_CLIENT_IP, IPV4_NULL_ADDRESS) 668*9c5db199SXin Li packet.set_field(FIELD_YOUR_IP, IPV4_NULL_ADDRESS) 669*9c5db199SXin Li packet.set_field(FIELD_SERVER_IP, IPV4_NULL_ADDRESS) 670*9c5db199SXin Li packet.set_field(FIELD_GATEWAY_IP, IPV4_NULL_ADDRESS) 671*9c5db199SXin Li packet.set_field(FIELD_CLIENT_HWADDR, hwmac_addr) 672*9c5db199SXin Li packet.set_field(FIELD_MAGIC_COOKIE, FIELD_VALUE_MAGIC_COOKIE) 673*9c5db199SXin Li packet.set_option(OPTION_DHCP_MESSAGE_TYPE, 674*9c5db199SXin Li MESSAGE_TYPE_NAK.option_value) 675*9c5db199SXin Li return packet 676*9c5db199SXin Li 677*9c5db199SXin Li def __init__(self, byte_str=None): 678*9c5db199SXin Li """ 679*9c5db199SXin Li Create a DhcpPacket, filling in fields from a byte string if given. 680*9c5db199SXin Li 681*9c5db199SXin Li Assumes that the packet starts at offset 0 in the binary string. This 682*9c5db199SXin Li includes the fields and options. Fields are different from options in 683*9c5db199SXin Li that we bother to decode these into more usable data types like 684*9c5db199SXin Li integers rather than keeping them as raw byte strings. Fields are also 685*9c5db199SXin Li required to exist, unlike options which may not. 686*9c5db199SXin Li 687*9c5db199SXin Li Each option is encoded as a tuple <option number, length, data> where 688*9c5db199SXin Li option number is a byte indicating the type of option, length indicates 689*9c5db199SXin Li the number of bytes in the data for option, and data is a length array 690*9c5db199SXin Li of bytes. The only exceptions to this rule are the 0 and 255 options, 691*9c5db199SXin Li which have 0 data length, and no length byte. These tuples are then 692*9c5db199SXin Li simply appended to each other. This encoding is the same as the BOOTP 693*9c5db199SXin Li vendor extention field encoding. 694*9c5db199SXin Li """ 695*9c5db199SXin Li super(DhcpPacket, self).__init__() 696*9c5db199SXin Li self._options = {} 697*9c5db199SXin Li self._fields = {} 698*9c5db199SXin Li if byte_str is None: 699*9c5db199SXin Li return 700*9c5db199SXin Li if len(byte_str) < OPTIONS_START_OFFSET + 1: 701*9c5db199SXin Li logging.error("Invalid byte string for packet.") 702*9c5db199SXin Li return 703*9c5db199SXin Li for field in DHCP_ALL_FIELDS: 704*9c5db199SXin Li self._fields[field] = field.unpack(byte_str[field.offset : 705*9c5db199SXin Li field.offset + 706*9c5db199SXin Li field.size]) 707*9c5db199SXin Li offset = OPTIONS_START_OFFSET 708*9c5db199SXin Li domain_search_list_byte_string = b"" 709*9c5db199SXin Li while offset < len(byte_str) and get_ord( 710*9c5db199SXin Li byte_str[offset]) != OPTION_END: 711*9c5db199SXin Li data_type = get_ord(byte_str[offset]) 712*9c5db199SXin Li offset += 1 713*9c5db199SXin Li if data_type == OPTION_PAD: 714*9c5db199SXin Li continue 715*9c5db199SXin Li data_length = get_ord(byte_str[offset]) 716*9c5db199SXin Li offset += 1 717*9c5db199SXin Li data = byte_str[offset: offset + data_length] 718*9c5db199SXin Li offset += data_length 719*9c5db199SXin Li option = get_dhcp_option_by_number(data_type) 720*9c5db199SXin Li if option is None: 721*9c5db199SXin Li logging.warning("Unsupported DHCP option found. " 722*9c5db199SXin Li "Option number: %d", data_type) 723*9c5db199SXin Li continue 724*9c5db199SXin Li if option == OPTION_DNS_DOMAIN_SEARCH_LIST: 725*9c5db199SXin Li # In a cruel twist of fate, the server is allowed to give 726*9c5db199SXin Li # multiple options with this number. The client is expected to 727*9c5db199SXin Li # concatenate the byte strings together and use it as a single 728*9c5db199SXin Li # value. 729*9c5db199SXin Li domain_search_list_byte_string += data 730*9c5db199SXin Li continue 731*9c5db199SXin Li option_value = option.unpack(data) 732*9c5db199SXin Li if option == OPTION_PARAMETER_REQUEST_LIST: 733*9c5db199SXin Li logging.info("Requested options: %s", str(option_value)) 734*9c5db199SXin Li self._options[option] = option_value 735*9c5db199SXin Li if domain_search_list_byte_string: 736*9c5db199SXin Li self._options[OPTION_DNS_DOMAIN_SEARCH_LIST] = \ 737*9c5db199SXin Li DomainListOption.unpack(domain_search_list_byte_string) 738*9c5db199SXin Li 739*9c5db199SXin Li 740*9c5db199SXin Li @property 741*9c5db199SXin Li def client_hw_address(self): 742*9c5db199SXin Li return self._fields.get(FIELD_CLIENT_HWADDR) 743*9c5db199SXin Li 744*9c5db199SXin Li @property 745*9c5db199SXin Li def is_valid(self): 746*9c5db199SXin Li """ 747*9c5db199SXin Li Checks that we have (at a minimum) values for all the required fields, 748*9c5db199SXin Li and that the magic cookie is set correctly. 749*9c5db199SXin Li """ 750*9c5db199SXin Li for field in DHCP_REQUIRED_FIELDS: 751*9c5db199SXin Li if self._fields.get(field) is None: 752*9c5db199SXin Li logging.warning("Missing field %s in packet.", field) 753*9c5db199SXin Li return False 754*9c5db199SXin Li if self._fields[FIELD_MAGIC_COOKIE] != FIELD_VALUE_MAGIC_COOKIE: 755*9c5db199SXin Li return False 756*9c5db199SXin Li return True 757*9c5db199SXin Li 758*9c5db199SXin Li @property 759*9c5db199SXin Li def message_type(self): 760*9c5db199SXin Li """ 761*9c5db199SXin Li Gets the value of the DHCP Message Type option in this packet. 762*9c5db199SXin Li 763*9c5db199SXin Li If the option is not present, or the value of the option is not 764*9c5db199SXin Li recognized, returns MESSAGE_TYPE_UNKNOWN. 765*9c5db199SXin Li 766*9c5db199SXin Li @returns The MessageType for this packet, or MESSAGE_TYPE_UNKNOWN. 767*9c5db199SXin Li """ 768*9c5db199SXin Li if (OPTION_DHCP_MESSAGE_TYPE in self._options and 769*9c5db199SXin Li self._options[OPTION_DHCP_MESSAGE_TYPE] > 0 and 770*9c5db199SXin Li self._options[OPTION_DHCP_MESSAGE_TYPE] < len(MESSAGE_TYPE_BY_NUM)): 771*9c5db199SXin Li return MESSAGE_TYPE_BY_NUM[self._options[OPTION_DHCP_MESSAGE_TYPE]] 772*9c5db199SXin Li else: 773*9c5db199SXin Li return MESSAGE_TYPE_UNKNOWN 774*9c5db199SXin Li 775*9c5db199SXin Li @property 776*9c5db199SXin Li def transaction_id(self): 777*9c5db199SXin Li return self._fields.get(FIELD_TRANSACTION_ID) 778*9c5db199SXin Li 779*9c5db199SXin Li def get_field(self, field): 780*9c5db199SXin Li return self._fields.get(field) 781*9c5db199SXin Li 782*9c5db199SXin Li def get_option(self, option): 783*9c5db199SXin Li return self._options.get(option) 784*9c5db199SXin Li 785*9c5db199SXin Li def set_field(self, field, field_value): 786*9c5db199SXin Li self._fields[field] = field_value 787*9c5db199SXin Li 788*9c5db199SXin Li def set_option(self, option, option_value): 789*9c5db199SXin Li self._options[option] = option_value 790*9c5db199SXin Li 791*9c5db199SXin Li def to_binary_string(self): 792*9c5db199SXin Li if not self.is_valid: 793*9c5db199SXin Li return None 794*9c5db199SXin Li # A list of byte strings to be joined into a single string at the end. 795*9c5db199SXin Li data = b"" 796*9c5db199SXin Li offset = 0 797*9c5db199SXin Li for field in DHCP_ALL_FIELDS: 798*9c5db199SXin Li if field not in self._fields: 799*9c5db199SXin Li continue 800*9c5db199SXin Li field_data = field.pack(self._fields[field]) 801*9c5db199SXin Li while offset < field.offset: 802*9c5db199SXin Li # This should only happen when we're padding the fields because 803*9c5db199SXin Li # we're not filling in legacy BOOTP stuff. 804*9c5db199SXin Li data += b"\x00" 805*9c5db199SXin Li offset += 1 806*9c5db199SXin Li data += field_data 807*9c5db199SXin Li offset += field.size 808*9c5db199SXin Li # Last field processed is the magic cookie, so we're ready for options. 809*9c5db199SXin Li # Have to process options 810*9c5db199SXin Li for option in DHCP_PACKET_OPTIONS: 811*9c5db199SXin Li option_value = self._options.get(option) 812*9c5db199SXin Li if option_value is None: 813*9c5db199SXin Li continue 814*9c5db199SXin Li serialized_value = option.pack(option_value) 815*9c5db199SXin Li data += struct.pack("BB", option.number, len(serialized_value)) 816*9c5db199SXin Li offset += 2 817*9c5db199SXin Li data += serialized_value 818*9c5db199SXin Li offset += len(serialized_value) 819*9c5db199SXin Li data += get_bytes(chr(OPTION_END)) 820*9c5db199SXin Li offset += 1 821*9c5db199SXin Li while offset < DHCP_MIN_PACKET_SIZE: 822*9c5db199SXin Li data += get_bytes(chr(OPTION_PAD)) 823*9c5db199SXin Li offset += 1 824*9c5db199SXin Li return data 825*9c5db199SXin Li 826*9c5db199SXin Li def __str__(self): 827*9c5db199SXin Li options = [k.name + "=" + str(v) for k, v in self._options.items()] 828*9c5db199SXin Li fields = [k.name + "=" + str(v) for k, v in self._fields.items()] 829*9c5db199SXin Li return "<DhcpPacket fields=%s, options=%s>" % (fields, options) 830