From a4d4291d100e685e222e379aedeca88fdb54aa73 Mon Sep 17 00:00:00 2001 From: Shinpei Muraoka Date: Wed, 4 Jan 2017 15:42:53 +0900 Subject: [PATCH] packet/dhcp: Support fragmented options Currently, if you set fragmented option data for the packet library of dhcp, Traceback occurs. This patch fixes to analyze up to options not corrupting data. The remaining corrupting data will be appended at the end of option list. Signed-off-by: Shinpei Muraoka Signed-off-by: FUJITA Tomonori --- ryu/lib/packet/dhcp.py | 168 ++++++++++++++++++++--------------------- 1 file changed, 81 insertions(+), 87 deletions(-) diff --git a/ryu/lib/packet/dhcp.py b/ryu/lib/packet/dhcp.py index c8e849fa..327c8925 100644 --- a/ryu/lib/packet/dhcp.py +++ b/ryu/lib/packet/dhcp.py @@ -15,51 +15,49 @@ """ DHCP packet parser/serializer - -RFC 2131 -DHCP packet format - - 0 1 2 3 - 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 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | op (1) | htype (1) | hlen (1) | hops (1) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | xid (4) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | secs (2) | flags (2) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | ciaddr (4) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | yiaddr (4) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | siaddr (4) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | giaddr (4) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | chaddr (16) | - | | - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | sname (64) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | file (128) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | options (variable) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - """ -import binascii +# RFC 2131 +# DHCP packet format +# 0 1 2 3 +# 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 +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | op (1) | htype (1) | hlen (1) | hops (1) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | xid (4) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | secs (2) | flags (2) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | ciaddr (4) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | yiaddr (4) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | siaddr (4) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | giaddr (4) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | | +# | chaddr (16) | +# | | +# | | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | | +# | sname (64) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | | +# | file (128) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | | +# | options (variable) | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + import random import struct -from . import packet_base +import netaddr + from ryu.lib import addrconv from ryu.lib import stringify - +from . import packet_base DHCP_BOOT_REQUEST = 1 DHCP_BOOT_REPLY = 2 @@ -136,12 +134,9 @@ class dhcp(packet_base.PacketBase): every DHCP message). ============== ==================== """ - _MIN_LEN = 236 - _HLEN_UNPACK_STR = '!BBB' - _HLEN_UNPACK_LEN = struct.calcsize(_HLEN_UNPACK_STR) - _DHCP_UNPACK_STR = '!BIHH4s4s4s4s%ds%ds64s128s' _DHCP_PACK_STR = '!BBBBIHH4s4s4s4s16s64s128s' - _DHCP_CHADDR_LEN = 16 + _MIN_LEN = struct.calcsize(_DHCP_PACK_STR) + _MAC_ADDRESS_LEN = 6 _HARDWARE_TYPE_ETHERNET = 1 _class_prefixes = ['options'] _TYPE = { @@ -150,17 +145,14 @@ class dhcp(packet_base.PacketBase): ] } - def __init__(self, op, chaddr, options, htype=_HARDWARE_TYPE_ETHERNET, + def __init__(self, op, chaddr, options=None, htype=_HARDWARE_TYPE_ETHERNET, hlen=0, hops=0, xid=None, secs=0, flags=0, ciaddr='0.0.0.0', yiaddr='0.0.0.0', siaddr='0.0.0.0', giaddr='0.0.0.0', sname='', boot_file=b''): super(dhcp, self).__init__() self.op = op self.htype = htype - if hlen == 0: - self.hlen = len(addrconv.mac.text_to_bin(chaddr)) - else: - self.hlen = hlen + self.hlen = hlen self.hops = hops if xid is None: self.xid = random.randint(0, 0xffffffff) @@ -178,20 +170,20 @@ class dhcp(packet_base.PacketBase): self.options = options @classmethod - def _parser(cls, buf): - (op, htype, hlen) = struct.unpack_from(cls._HLEN_UNPACK_STR, buf) - buf = buf[cls._HLEN_UNPACK_LEN:] - unpack_str = cls._DHCP_UNPACK_STR % (hlen, - (cls._DHCP_CHADDR_LEN - hlen)) - min_len = struct.calcsize(unpack_str) - (hops, xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr, chaddr, - dummy, sname, boot_file - ) = struct.unpack_from(unpack_str, buf) - length = min_len - if len(buf) > min_len: - parse_opt = options.parser(buf[min_len:]) + def parser(cls, buf): + (op, htype, hlen, hops, xid, secs, flags, + ciaddr, yiaddr, siaddr, giaddr, chaddr, sname, + boot_file) = struct.unpack_from(cls._DHCP_PACK_STR, buf) + + if hlen == cls._MAC_ADDRESS_LEN: + chaddr = addrconv.mac.bin_to_text(chaddr[:cls._MAC_ADDRESS_LEN]) + + length = cls._MIN_LEN + parse_opt = None + if len(buf) > length: + parse_opt = options.parser(buf[length:]) length += parse_opt.options_len - return (cls(op, addrconv.mac.bin_to_text(chaddr), parse_opt, + return (cls(op, chaddr, parse_opt, htype, hlen, hops, xid, secs, flags, addrconv.ipv4.bin_to_text(ciaddr), addrconv.ipv4.bin_to_text(yiaddr), @@ -200,25 +192,24 @@ class dhcp(packet_base.PacketBase): sname.decode('ascii'), boot_file), None, buf[length:]) - @classmethod - def parser(cls, buf): - try: - return cls._parser(buf) - except: - return None, None, buf - - def serialize(self, payload, prev): - seri_opt = self.options.serialize() - pack_str = '%s%ds' % (self._DHCP_PACK_STR, - self.options.options_len) - return struct.pack(pack_str, self.op, self.htype, self.hlen, + def serialize(self, _payload=None, _prev=None): + opt_buf = bytearray() + if self.options is not None: + opt_buf = self.options.serialize() + if netaddr.valid_mac(self.chaddr): + chaddr = addrconv.mac.text_to_bin(self.chaddr) + else: + chaddr = self.chaddr + self.hlen = len(chaddr) + return struct.pack(self._DHCP_PACK_STR, self.op, self.htype, self.hlen, self.hops, self.xid, self.secs, self.flags, addrconv.ipv4.text_to_bin(self.ciaddr), addrconv.ipv4.text_to_bin(self.yiaddr), addrconv.ipv4.text_to_bin(self.siaddr), addrconv.ipv4.text_to_bin(self.giaddr), - addrconv.mac.text_to_bin(self.chaddr), - self.sname.encode('ascii'), self.boot_file, seri_opt) + chaddr, + self.sname.encode('ascii'), + self.boot_file) + opt_buf class options(stringify.StringifyMixin): @@ -258,10 +249,7 @@ class options(stringify.StringifyMixin): def __init__(self, option_list=None, options_len=0, magic_cookie=_MAGIC_COOKIE): super(options, self).__init__() - if option_list is None: - self.option_list = [] - else: - self.option_list = option_list + self.option_list = option_list or [] self.options_len = options_len self.magic_cookie = magic_cookie @@ -272,7 +260,11 @@ class options(stringify.StringifyMixin): magic_cookie = struct.unpack_from(cls._MAGIC_COOKIE_UNPACK_STR, buf)[0] while len(buf) > offset: opt_buf = buf[offset:] - opt = option.parser(opt_buf) + try: + opt = option.parser(opt_buf) + except struct.error: + opt_parse_list.append(opt_buf) + break if opt is None: break opt_parse_list.append(opt) @@ -283,10 +275,13 @@ class options(stringify.StringifyMixin): def serialize(self): seri_opt = addrconv.ipv4.text_to_bin(self.magic_cookie) for opt in self.option_list: - seri_opt += opt.serialize() - seri_opt += binascii.a2b_hex('%x' % DHCP_END_OPT) - if self.options_len == 0: - self.options_len = len(seri_opt) + if isinstance(opt, option): + seri_opt += opt.serialize() + else: + seri_opt += opt + if isinstance(self.option_list[-1], option): + seri_opt += b'\xff' + self.options_len = len(seri_opt) return seri_opt @@ -333,7 +328,6 @@ class option(stringify.StringifyMixin): return cls(tag, value, length) def serialize(self): - if self.length == 0: - self.length = len(self.value) + self.length = len(self.value) options_pack_str = '!BB%ds' % self.length return struct.pack(options_pack_str, self.tag, self.length, self.value)