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 """ 6 Tools for serializing and deserializing DHCP packets. 7 8 DhcpPacket is a class that represents a single DHCP packet and contains some 9 logic to create and parse binary strings containing on the wire DHCP packets. 10 11 While you could call the constructor explicitly, most users should use the 12 static factories to construct packets with reasonable default values in most of 13 the fields, even if those values are zeros. 14 15 For example: 16 17 packet = dhcp_packet.create_offer_packet(transaction_id, 18 hwmac_addr, 19 offer_ip, 20 server_ip) 21 socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 22 # Sending to the broadcast address needs special permissions. 23 socket.sendto(response_packet.to_binary_string(), 24 ("255.255.255.255", 68)) 25 26 Note that if you make changes, make sure that the tests in the bottom of this 27 file still pass. 28 """ 29 30 import collections 31 import logging 32 import random 33 import socket 34 import struct 35 36 37 def 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 """ 49 Represents an option in a DHCP packet. Options may or may not be present in any 50 given packet, depending on the configurations of the client and the server. 51 Using namedtuples as super classes gets us the comparison operators we want to 52 use these Options in dictionaries as keys. Below, we'll subclass Option to 53 reflect that different kinds of options serialize to on the wire formats in 54 different ways. 55 56 |name| 57 A human readable name for this option. 58 59 |number| 60 Every DHCP option has a number that goes into the packet to indicate 61 which particular option is being encoded in the next few bytes. This 62 property returns that number for each option. 63 """ 64 Option = collections.namedtuple("Option", ["name", "number"]) 65 66 ByteOption = CreatePacketPieceClass(Option, "!B") 67 68 ShortOption = CreatePacketPieceClass(Option, "!H") 69 70 IntOption = CreatePacketPieceClass(Option, "!I") 71 72 class 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 82 class 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 93 class 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 103 class 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 113 class 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 160 class 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 """ 246 Represents a required field in a DHCP packet. Similar to Option, we'll 247 subclass Field to reflect that different fields serialize to on the wire formats 248 in different ways. 249 250 |name| 251 A human readable name for this field. 252 253 |offset| 254 The |offset| for a field defines the starting byte of the field in the 255 binary packet string. |offset| is used during parsing, along with 256 |size| to extract the byte string of a field. 257 258 |size| 259 Fields in DHCP packets have a fixed size that must be respected. This 260 size property is used in parsing to indicate that |self._size| number of 261 bytes make up this field. 262 """ 263 Field = collections.namedtuple("Field", ["name", "offset", "size"]) 264 265 ByteField = CreatePacketPieceClass(Field, "!B") 266 267 ShortField = CreatePacketPieceClass(Field, "!H") 268 269 IntField = CreatePacketPieceClass(Field, "!I") 270 271 HwAddrField = CreatePacketPieceClass(Field, "!16s") 272 273 ServerNameField = CreatePacketPieceClass(Field, "!64s") 274 275 BootFileField = CreatePacketPieceClass(Field, "!128s") 276 277 class 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. 289 DHCP_MIN_PACKET_SIZE = 300 290 291 IPV4_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 295 FIELD_OP = ByteField("op", 0, 1) 296 FIELD_HWTYPE = ByteField("htype", 1, 1) 297 FIELD_HWADDR_LEN = ByteField("hlen", 2, 1) 298 FIELD_RELAY_HOPS = ByteField("hops", 3, 1) 299 FIELD_TRANSACTION_ID = IntField("xid", 4, 4) 300 FIELD_TIME_SINCE_START = ShortField("secs", 8, 2) 301 FIELD_FLAGS = ShortField("flags", 10, 2) 302 FIELD_CLIENT_IP = IpAddressField("ciaddr", 12, 4) 303 FIELD_YOUR_IP = IpAddressField("yiaddr", 16, 4) 304 FIELD_SERVER_IP = IpAddressField("siaddr", 20, 4) 305 FIELD_GATEWAY_IP = IpAddressField("giaddr", 24, 4) 306 FIELD_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. 309 FIELD_LEGACY_SERVER_NAME = ServerNameField("servername", 44, 64); 310 FIELD_LEGACY_BOOT_FILE = BootFileField("bootfile", 108, 128); 311 FIELD_MAGIC_COOKIE = IntField("magic_cookie", 236, 4) 312 313 OPTION_TIME_OFFSET = IntOption("time_offset", 2) 314 OPTION_ROUTERS = IpListOption("routers", 3) 315 OPTION_SUBNET_MASK = IpAddressOption("subnet_mask", 1) 316 OPTION_TIME_SERVERS = IpListOption("time_servers", 4) 317 OPTION_NAME_SERVERS = IpListOption("name_servers", 5) 318 OPTION_DNS_SERVERS = IpListOption("dns_servers", 6) 319 OPTION_LOG_SERVERS = IpListOption("log_servers", 7) 320 OPTION_COOKIE_SERVERS = IpListOption("cookie_servers", 8) 321 OPTION_LPR_SERVERS = IpListOption("lpr_servers", 9) 322 OPTION_IMPRESS_SERVERS = IpListOption("impress_servers", 10) 323 OPTION_RESOURCE_LOC_SERVERS = IpListOption("resource_loc_servers", 11) 324 OPTION_HOST_NAME = RawOption("host_name", 12) 325 OPTION_BOOT_FILE_SIZE = ShortOption("boot_file_size", 13) 326 OPTION_MERIT_DUMP_FILE = RawOption("merit_dump_file", 14) 327 OPTION_DOMAIN_NAME = RawOption("domain_name", 15) 328 OPTION_SWAP_SERVER = IpAddressOption("swap_server", 16) 329 OPTION_ROOT_PATH = RawOption("root_path", 17) 330 OPTION_EXTENSIONS = RawOption("extensions", 18) 331 OPTION_INTERFACE_MTU = ShortOption("interface_mtu", 26) 332 OPTION_VENDOR_ENCAPSULATED_OPTIONS = RawOption( 333 "vendor_encapsulated_options", 43) 334 OPTION_REQUESTED_IP = IpAddressOption("requested_ip", 50) 335 OPTION_IP_LEASE_TIME = IntOption("ip_lease_time", 51) 336 OPTION_OPTION_OVERLOAD = ByteOption("option_overload", 52) 337 OPTION_DHCP_MESSAGE_TYPE = ByteOption("dhcp_message_type", 53) 338 OPTION_SERVER_ID = IpAddressOption("server_id", 54) 339 OPTION_PARAMETER_REQUEST_LIST = ByteListOption("parameter_request_list", 55) 340 OPTION_MESSAGE = RawOption("message", 56) 341 OPTION_MAX_DHCP_MESSAGE_SIZE = ShortOption("max_dhcp_message_size", 57) 342 OPTION_RENEWAL_T1_TIME_VALUE = IntOption("renewal_t1_time_value", 58) 343 OPTION_REBINDING_T2_TIME_VALUE = IntOption("rebinding_t2_time_value", 59) 344 OPTION_VENDOR_ID = RawOption("vendor_id", 60) 345 OPTION_CLIENT_ID = RawOption("client_id", 61) 346 OPTION_TFTP_SERVER_NAME = RawOption("tftp_server_name", 66) 347 OPTION_BOOTFILE_NAME = RawOption("bootfile_name", 67) 348 OPTION_FULLY_QUALIFIED_DOMAIN_NAME = RawOption("fqdn", 81) 349 OPTION_DNS_DOMAIN_SEARCH_LIST = DomainListOption("domain_search_list", 119) 350 OPTION_CLASSLESS_STATIC_ROUTES = ClasslessStaticRoutesOption( 351 "classless_static_routes", 121) 352 OPTION_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). 357 OPTION_PAD = 0 358 OPTION_END = 255 359 360 DHCP_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 375 DHCP_REQUIRED_FIELDS = DHCP_COMMON_FIELDS + [ 376 FIELD_MAGIC_COOKIE, 377 ] 378 379 DHCP_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. 387 FIELD_VALUE_OP_CLIENT_REQUEST = 1 388 FIELD_VALUE_OP_SERVER_RESPONSE = 2 389 # 1 == 10mb ethernet hardware address type (aka MAC). 390 FIELD_VALUE_HWTYPE_10MB_ETH = 1 391 # MAC addresses are still 6 bytes long. 392 FIELD_VALUE_HWADDR_LEN_10MB_ETH = 6 393 FIELD_VALUE_MAGIC_COOKIE = 0x63825363 394 395 OPTIONS_START_OFFSET = 240 396 397 MessageType = collections.namedtuple('MessageType', 'name option_value') 398 # From RFC2132, the valid DHCP message types are: 399 MESSAGE_TYPE_UNKNOWN = MessageType('UNKNOWN', 0) 400 MESSAGE_TYPE_DISCOVERY = MessageType('DISCOVERY', 1) 401 MESSAGE_TYPE_OFFER = MessageType('OFFER', 2) 402 MESSAGE_TYPE_REQUEST = MessageType('REQUEST', 3) 403 MESSAGE_TYPE_DECLINE = MessageType('DECLINE', 4) 404 MESSAGE_TYPE_ACK = MessageType('ACK', 5) 405 MESSAGE_TYPE_NAK = MessageType('NAK', 6) 406 MESSAGE_TYPE_RELEASE = MessageType('RELEASE', 7) 407 MESSAGE_TYPE_INFORM = MessageType('INFORM', 8) 408 MESSAGE_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 420 OPTION_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. 446 DHCP_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 487 def 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 493 class 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