1 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 DHCP packet parser/serializer
22 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
23 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24 # | op (1) | htype (1) | hlen (1) | hops (1) |
25 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28 # | secs (2) | flags (2) |
29 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
42 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
45 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
50 # | options (variable) |
51 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
58 from ryu.lib import addrconv
59 from ryu.lib import stringify
60 from . import packet_base
65 # DHCP message type code
71 # DHCP options tag code
73 DHCP_SUBNET_MASK_OPT = 1
74 DHCP_GATEWAY_ADDR_OPT = 3
75 DHCP_DNS_SERVER_ADDR_OPT = 6
76 DHCP_HOST_NAME_OPT = 12
77 DHCP_DOMAIN_NAME_OPT = 15
78 DHCP_INTERFACE_MTU_OPT = 26
79 DHCP_REQUESTED_IP_ADDR_OPT = 50
80 DHCP_IP_ADDR_LEASE_TIME_OPT = 51
81 DHCP_MESSAGE_TYPE_OPT = 53
82 DHCP_SERVER_IDENTIFIER_OPT = 54
83 DHCP_PARAMETER_REQUEST_LIST_OPT = 55
84 DHCP_RENEWAL_TIME_OPT = 58
85 DHCP_REBINDING_TIME_OPT = 59
86 DHCP_CLASSLESS_ROUTE_OPT = 121
90 class dhcp(packet_base.PacketBase):
91 """DHCP (RFC 2131) header encoder/decoder class.
93 The serialized packet would looks like the ones described
94 in the following sections.
96 * RFC 2131 DHCP packet format
98 An instance has the following attributes at least.
99 Most of them are same to the on-wire counterparts but in host byte order.
100 __init__ takes the corresponding args in this order.
102 .. tabularcolumns:: |l|L|
104 ============== ====================
105 Attribute Description
106 ============== ====================
107 op Message op code / message type.\
108 1 = BOOTREQUEST, 2 = BOOTREPLY
109 htype Hardware address type (e.g. '1' = 10mb ethernet).
110 hlen Hardware address length (e.g. '6' = 10mb ethernet).
111 hops Client sets to zero, optionally used by relay agent\
112 when booting via a relay agent.
113 xid Transaction ID, a random number chosen by the client,\
114 used by the client and serverto associate messages\
115 and responses between a client and a server.
116 secs Filled in by client, seconds elapsed since client\
117 began address acquisition or renewal process.
119 ciaddr Client IP address; only filled in if client is in\
120 BOUND, RENEW or REBINDING state and can respond\
122 yiaddr 'your' (client) IP address.
123 siaddr IP address of next server to use in bootstrap;\
124 returned in DHCPOFFER, DHCPACK by server.
125 giaddr Relay agent IP address, used in booting via a\
127 chaddr Client hardware address.
128 sname Optional server host name, null terminated string.
129 boot_file Boot file name, null terminated string; "generic"\
130 name or null in DHCPDISCOVER, fully qualified\
131 directory-path name in DHCPOFFER.
132 options Optional parameters field\
133 ('DHCP message type' option must be included in\
135 ============== ====================
137 _DHCP_PACK_STR = '!BBBBIHH4s4s4s4s16s64s128s'
138 _MIN_LEN = struct.calcsize(_DHCP_PACK_STR)
140 _HARDWARE_TYPE_ETHERNET = 1
141 _class_prefixes = ['options']
144 'ciaddr', 'yiaddr', 'siaddr', 'giaddr', 'chaddr',
149 def __init__(self, op, chaddr, options=None, htype=_HARDWARE_TYPE_ETHERNET,
150 hlen=0, hops=0, xid=None, secs=0, flags=0,
151 ciaddr='0.0.0.0', yiaddr='0.0.0.0', siaddr='0.0.0.0',
152 giaddr='0.0.0.0', sname='', boot_file=''):
153 super(dhcp, self).__init__()
159 self.xid = random.randint(0, 0xffffffff)
170 self.boot_file = boot_file
171 self.options = options
174 def parser(cls, buf):
175 (op, htype, hlen, hops, xid, secs, flags,
176 ciaddr, yiaddr, siaddr, giaddr, chaddr, sname,
177 boot_file) = struct.unpack_from(cls._DHCP_PACK_STR, buf)
179 if hlen == cls._MAC_ADDRESS_LEN:
180 chaddr = addrconv.mac.bin_to_text(chaddr[:cls._MAC_ADDRESS_LEN])
182 length = cls._MIN_LEN
184 if len(buf) > length:
185 parse_opt = options.parser(buf[length:])
186 length += parse_opt.options_len
187 return (cls(op, chaddr, parse_opt,
188 htype, hlen, hops, xid, secs, flags,
189 addrconv.ipv4.bin_to_text(ciaddr),
190 addrconv.ipv4.bin_to_text(yiaddr),
191 addrconv.ipv4.bin_to_text(siaddr),
192 addrconv.ipv4.bin_to_text(giaddr),
193 sname.decode('ascii'), boot_file.decode('ascii')),
196 def serialize(self, _payload=None, _prev=None):
197 opt_buf = bytearray()
198 if self.options is not None:
199 opt_buf = self.options.serialize()
200 if netaddr.valid_mac(self.chaddr):
201 chaddr = addrconv.mac.text_to_bin(self.chaddr)
204 self.hlen = len(chaddr)
205 return struct.pack(self._DHCP_PACK_STR, self.op, self.htype, self.hlen,
206 self.hops, self.xid, self.secs, self.flags,
207 addrconv.ipv4.text_to_bin(self.ciaddr),
208 addrconv.ipv4.text_to_bin(self.yiaddr),
209 addrconv.ipv4.text_to_bin(self.siaddr),
210 addrconv.ipv4.text_to_bin(self.giaddr),
212 self.sname.encode('ascii'),
213 self.boot_file.encode('ascii')) + opt_buf
216 class options(stringify.StringifyMixin):
217 """DHCP (RFC 2132) options encoder/decoder class.
219 This is used with ryu.lib.packet.dhcp.dhcp.
221 An instance has the following attributes at least.
222 Most of them are same to the on-wire counterparts but in host byte order.
223 __init__ takes the corresponding args in this order.
225 .. tabularcolumns:: |l|L|
227 ============== ====================
228 Attribute Description
229 ============== ====================
230 option_list 'end option' and 'pad option' are added automatically\
231 after the option class is stored in array.
232 options_len Option's byte length.\
233 ('magic cookie', 'end option' and 'pad option'\
235 magic_cookie The first four octets contain the decimal values\
237 ============== ====================
239 _MAGIC_COOKIE_UNPACK_STR = '!4s'
240 # same magic cookie as is defined in RFC 1497
241 _MAGIC_COOKIE = '99.130.83.99'
242 _OPT_TAG_LEN_BYTE = 2
243 _class_prefixes = ['option']
250 def __init__(self, option_list=None, options_len=0,
251 magic_cookie=_MAGIC_COOKIE):
252 super(options, self).__init__()
253 self.option_list = option_list or []
254 self.options_len = options_len
255 self.magic_cookie = magic_cookie
258 def parser(cls, buf):
260 offset = struct.calcsize(cls._MAGIC_COOKIE_UNPACK_STR)
261 magic_cookie = struct.unpack_from(cls._MAGIC_COOKIE_UNPACK_STR, buf)[0]
262 while len(buf) > offset:
263 opt_buf = buf[offset:]
265 opt = option.parser(opt_buf)
267 opt_parse_list.append(opt_buf)
271 opt_parse_list.append(opt)
272 offset += opt.length + cls._OPT_TAG_LEN_BYTE
273 return cls(opt_parse_list, len(buf),
274 addrconv.ipv4.bin_to_text(magic_cookie))
277 seri_opt = addrconv.ipv4.text_to_bin(self.magic_cookie)
278 for opt in self.option_list:
279 if isinstance(opt, option):
280 seri_opt += opt.serialize()
283 if isinstance(self.option_list[-1], option):
285 self.options_len = len(seri_opt)
289 class option(stringify.StringifyMixin):
290 """DHCP (RFC 2132) options encoder/decoder class.
292 This is used with ryu.lib.packet.dhcp.dhcp.options.
294 An instance has the following attributes at least.
295 Most of them are same to the on-wire counterparts but in host byte order.
296 __init__ takes the corresponding args in this order.
298 ============== ====================
299 Attribute Description
300 ============== ====================
302 (except for the 'magic cookie', 'pad option'\
304 value Option's value.\
305 (set the value that has been converted to hexadecimal.)
306 length Option's value length.\
307 (calculated automatically from the length of value.)
308 ============== ====================
311 _MIN_LEN = struct.calcsize(_UNPACK_STR)
313 def __init__(self, tag, value, length=0):
314 super(option, self).__init__()
320 def parser(cls, buf):
321 tag = struct.unpack_from(cls._UNPACK_STR, buf)[0]
322 if tag == DHCP_END_OPT or tag == DHCP_PAD_OPT:
324 buf = buf[cls._MIN_LEN:]
325 length = struct.unpack_from(cls._UNPACK_STR, buf)[0]
326 buf = buf[cls._MIN_LEN:]
327 value_unpack_str = '%ds' % length
328 value = struct.unpack_from(value_unpack_str, buf)[0]
329 return cls(tag, value, length)
332 self.length = len(self.value)
333 options_pack_str = '!BB%ds' % self.length
334 return struct.pack(options_pack_str, self.tag, self.length, self.value)