Home | History | Annotate | Download | only in cros
      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