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