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