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