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