diff options
author | takahashi.minoru <takahashi.minoru7@gmail.com> | 2014-04-24 15:35:18 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2014-04-24 23:09:41 +0900 |
commit | 9c5e9288ccb1612246356130525611484c620dd5 (patch) | |
tree | 56f4fa44b61c27221ca24a3e21baccfeb8a6e8e4 | |
parent | 0f8407a9ce96e0f07f9660dd1e4e99e3dd2ec580 (diff) |
packet lib: ipv6: support Routing header (type3)
Signed-off-by: TAKAHASHI Minoru <takahashi.minoru7@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/lib/packet/ipv6.py | 159 | ||||
-rw-r--r-- | ryu/tests/unit/packet/test_ipv6.py | 319 |
2 files changed, 476 insertions, 2 deletions
diff --git a/ryu/lib/packet/ipv6.py b/ryu/lib/packet/ipv6.py index b02ca51c..20e52341 100644 --- a/ryu/lib/packet/ipv6.py +++ b/ryu/lib/packet/ipv6.py @@ -167,8 +167,6 @@ class header(stringify.StringifyMixin): def __len__(self): pass -# TODO: implement a class for routing header - class opt_header(header): """an abstract class for Hop-by-Hop Options header and destination @@ -330,6 +328,163 @@ class option(stringify.StringifyMixin): return self._MIN_LEN + self.len_ +@ipv6.register_header_type(inet.IPPROTO_ROUTING) +class routing(header): + """An IPv6 Routing Header decoder class. + This class has only the parser method. + + IPv6 Routing Header types. + + http://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml + + +-----------+----------------------------------+-------------------+ + | Value | Description | Reference | + +===========+==================================+===================+ + | 0 | Source Route (DEPRECATED) | [[IPV6]][RFC5095] | + +-----------+----------------------------------+-------------------+ + | 1 | Nimrod (DEPRECATED 2009-05-06) | | + +-----------+----------------------------------+-------------------+ + | 2 | Type 2 Routing Header | [RFC6275] | + +-----------+----------------------------------+-------------------+ + | 3 | RPL Source Route Header | [RFC6554] | + +-----------+----------------------------------+-------------------+ + | 4 - 252 | Unassigned | | + +-----------+----------------------------------+-------------------+ + | 253 | RFC3692-style Experiment 1 [2] | [RFC4727] | + +-----------+----------------------------------+-------------------+ + | 254 | RFC3692-style Experiment 2 [2] | [RFC4727] | + +-----------+----------------------------------+-------------------+ + | 255 | Reserved | | + +-----------+----------------------------------+-------------------+ + """ + + TYPE = inet.IPPROTO_ROUTING + + _OFFSET_LEN = struct.calcsize('!2B') + + # IPv6 Routing Header Type + ROUTING_TYPE_2 = 0x02 + ROUTING_TYPE_3 = 0x03 + + @classmethod + def parser(cls, buf): + (type_, ) = struct.unpack_from('!B', buf, cls._OFFSET_LEN) + switch = { + # TODO: make parsers of type2. + cls.ROUTING_TYPE_2: None, + cls.ROUTING_TYPE_3: routing_type3 + } + cls_ = switch.get(type_) + if cls_: + return cls_.parser(buf) + else: + return None + + +class routing_type3(header): + """ + An IPv6 Routing Header for Source Routes with the RPL (RFC 6554) + encoder/decoder class. + + This is used with ryu.lib.packet.ipv6.ipv6. + + An instance has the following attributes at least. + Most of them are same to the on-wire counterparts but in host byte order. + __init__ takes the corresponding args in this order. + + .. tabularcolumns:: |l|L| + + ============== ======================================= + Attribute Description + ============== ======================================= + nxt Next Header + size The length of the Routing header, + not include the first 8 octet. + (0 means automatically-calculate when encoding) + type Identifies the particular Routing header variant. + seg Number of route segments remaining. + cmpi Number of prefix octets from segments 1 through n-1. + cmpe Number of prefix octets from segment n. + pad Number of octets that are used for padding + after Address[n] at the end of the SRH. + adrs Vector of addresses, numbered 1 to n. + ============== ======================================= + """ + + _PACK_STR = '!BBBBBB2x' + _MIN_LEN = struct.calcsize(_PACK_STR) + + def __init__(self, nxt=inet.IPPROTO_TCP, size=0, + type_=3, seg=0, cmpi=0, cmpe=0, adrs=None): + super(routing_type3, self).__init__(nxt) + self.size = size + self.type_ = type_ + self.seg = seg + self.cmpi = cmpi + self.cmpe = cmpe + adrs = adrs or [] + assert isinstance(adrs, list) + self.adrs = adrs + self._pad = (8 - ((len(self.adrs) - 1) * (16 - self.cmpi) + + (16 - self.cmpe) % 8)) % 8 + + @classmethod + def _get_size(cls, size): + return (int(size) + 1) * 8 + + @classmethod + def parser(cls, buf): + (nxt, size, type_, seg, cmp_, pad) = struct.unpack_from( + cls._PACK_STR, buf) + data = cls._MIN_LEN + header_len = cls._get_size(size) + cmpi = int(cmp_ >> 4) + cmpe = int(cmp_ & 0xf) + pad = int(pad >> 4) + adrs = [] + if size: + # Address[1..n-1] has size (16 - CmprI) octets + adrs_len_i = 16 - cmpi + # Address[n] has size (16 - CmprE) octets + adrs_len_e = 16 - cmpe + form_i = "%ds" % adrs_len_i + form_e = "%ds" % adrs_len_e + while data < (header_len - (adrs_len_e + pad)): + (adr, ) = struct.unpack_from(form_i, buf[data:]) + adr = ('\x00' * cmpi) + adr + adrs.append(addrconv.ipv6.bin_to_text(adr)) + data += adrs_len_i + (adr, ) = struct.unpack_from(form_e, buf[data:]) + adr = ('\x00' * cmpe) + adr + adrs.append(addrconv.ipv6.bin_to_text(adr)) + return cls(nxt, size, type_, seg, cmpi, cmpe, adrs) + + def serialize(self): + if self.size == 0: + self.size = ((len(self.adrs) - 1) * (16 - self.cmpi) + + (16 - self.cmpe) + self._pad) / 8 + buf = struct.pack(self._PACK_STR, self.nxt, self.size, + self.type_, self.seg, (self.cmpi << 4) | self.cmpe, + self._pad << 4) + buf = bytearray(buf) + if self.size: + form_i = "%ds" % (16 - self.cmpi) + form_e = "%ds" % (16 - self.cmpe) + slice_i = slice(self.cmpi, 16) + slice_e = slice(self.cmpe, 16) + for adr in self.adrs[:-1]: + buf.extend( + struct.pack( + form_i, addrconv.ipv6.text_to_bin(adr)[slice_i])) + buf.extend(struct.pack( + form_e, + addrconv.ipv6.text_to_bin(self.adrs[-1])[slice_e])) + return buf + + def __len__(self): + return routing_type3._get_size(self.size) + + @ipv6.register_header_type(inet.IPPROTO_FRAGMENT) class fragment(header): """IPv6 (RFC 2460) fragment header encoder/decoder class. diff --git a/ryu/tests/unit/packet/test_ipv6.py b/ryu/tests/unit/packet/test_ipv6.py index 4229b18c..134a1c02 100644 --- a/ryu/tests/unit/packet/test_ipv6.py +++ b/ryu/tests/unit/packet/test_ipv6.py @@ -113,6 +113,34 @@ class Test_ipv6(unittest.TestCase): addrconv.ipv6.text_to_bin(self.dst)) self.buf += self.dst_opts.serialize() + def setUp_with_routing_type3(self): + self.routing_nxt = 6 + self.routing_size = 6 + self.routing_type = 3 + self.routing_seg = 2 + self.routing_cmpi = 0 + self.routing_cmpe = 0 + self.routing_adrs = ["2001:db8:dead::1", "2001:db8:dead::2", + "2001:db8:dead::3"] + self.routing = ipv6.routing_type3( + self.routing_nxt, self.routing_size, + self.routing_type, self.routing_seg, + self.routing_cmpi, self.routing_cmpe, + self.routing_adrs) + self.ext_hdrs = [self.routing] + self.payload_length += len(self.routing) + self.nxt = ipv6.routing.TYPE + self.ip = ipv6.ipv6( + self.version, self.traffic_class, self.flow_label, + self.payload_length, self.nxt, self.hop_limit, self.src, + self.dst, self.ext_hdrs) + self.buf = struct.pack( + ipv6.ipv6._PACK_STR, self.v_tc_flow, + self.payload_length, self.nxt, self.hop_limit, + addrconv.ipv6.text_to_bin(self.src), + addrconv.ipv6.text_to_bin(self.dst)) + self.buf += self.routing.serialize() + def setUp_with_fragment(self): self.fragment_nxt = 6 self.fragment_offset = 50 @@ -218,6 +246,10 @@ class Test_ipv6(unittest.TestCase): self.setUp_with_dst_opts() self.test_init() + def test_init_with_routing_type3(self): + self.setUp_with_routing_type3() + self.test_init() + def test_init_with_fragment(self): self.setUp_with_fragment() self.test_init() @@ -255,6 +287,10 @@ class Test_ipv6(unittest.TestCase): self.setUp_with_dst_opts() self.test_parser() + def test_parser_with_routing_type3(self): + self.setUp_with_routing_type3() + self.test_parser() + def test_parser_with_fragment(self): self.setUp_with_fragment() self.test_parser() @@ -301,6 +337,16 @@ class Test_ipv6(unittest.TestCase): dst_opts = ipv6.dst_opts.parser(str(buf[ipv6.ipv6._MIN_LEN:])) eq_(repr(self.dst_opts), repr(dst_opts)) + def test_serialize_with_routing_type3(self): + self.setUp_with_routing_type3() + self.test_serialize() + + data = bytearray() + prev = None + buf = self.ip.serialize(data, prev) + routing = ipv6.routing.parser(str(buf[ipv6.ipv6._MIN_LEN:])) + eq_(repr(self.routing), repr(routing)) + def test_serialize_with_fragment(self): self.setUp_with_fragment() self.test_serialize() @@ -384,6 +430,10 @@ class Test_ipv6(unittest.TestCase): self.setUp_with_dst_opts() eq_(len(self.ip), 40 + len(self.dst_opts)) + def test_len_with_routing_type3(self): + self.setUp_with_routing_type3() + eq_(len(self.ip), 40 + len(self.routing)) + def test_len_with_fragment(self): self.setUp_with_fragment() eq_(len(self.ip), 40 + len(self.fragment)) @@ -438,6 +488,10 @@ class Test_ipv6(unittest.TestCase): self.setUp_with_dst_opts() self.test_json() + def test_json_with_routing_type3(self): + self.setUp_with_routing_type3() + self.test_json() + def test_json_with_fragment(self): self.setUp_with_fragment() self.test_json() @@ -692,6 +746,271 @@ class Test_option_padN(Test_option): eq_(self.len_, res[1]) +class Test_routing(unittest.TestCase): + + def setUp(self): + self.nxt = 0 + self.size = 6 + self.type_ = ipv6.routing.ROUTING_TYPE_3 + self.seg = 0 + self.cmpi = 0 + self.cmpe = 0 + self.adrs = ["2001:db8:dead::1", + "2001:db8:dead::2", + "2001:db8:dead::3"] + # calculate pad + self.pad = (8 - ((len(self.adrs) - 1) * (16 - self.cmpi) + + (16 - self.cmpe) % 8)) % 8 + # create buf + self.form = '!BBBBBB2x16s16s16s' + self.buf = struct.pack(self.form, self.nxt, self.size, + self.type_, self.seg, + (self.cmpi << 4) | self.cmpe, + self.pad << 4, + addrconv.ipv6.text_to_bin(self.adrs[0]), + addrconv.ipv6.text_to_bin(self.adrs[1]), + addrconv.ipv6.text_to_bin(self.adrs[2])) + + def tearDown(self): + pass + + def test_parser(self): + _res = ipv6.routing.parser(self.buf) + if type(_res) is tuple: + res = _res[0] + else: + res = _res + eq_(self.nxt, res.nxt) + eq_(self.size, res.size) + eq_(self.type_, res.type_) + eq_(self.seg, res.seg) + eq_(self.cmpi, res.cmpi) + eq_(self.cmpe, res.cmpe) + eq_(self.pad, res._pad) + eq_(self.adrs[0], res.adrs[0]) + eq_(self.adrs[1], res.adrs[1]) + eq_(self.adrs[2], res.adrs[2]) + + def test_not_implemented_type(self): + not_implemented_buf = struct.pack( + '!BBBBBB2x', 0, 6, ipv6.routing.ROUTING_TYPE_2, 0, 0, 0) + instance = ipv6.routing.parser(not_implemented_buf) + assert None == instance + + def test_invalid_type(self): + invalid_type = 99 + invalid_buf = struct.pack('!BBBBBB2x', 0, 6, invalid_type, 0, 0, 0) + instance = ipv6.routing.parser(invalid_buf) + assert None == instance + + +class Test_routing_type3(unittest.TestCase): + + def setUp(self): + self.nxt = 0 + self.size = 6 + self.type_ = 3 + self.seg = 0 + self.cmpi = 0 + self.cmpe = 0 + self.adrs = ["2001:db8:dead::1", + "2001:db8:dead::2", + "2001:db8:dead::3"] + # calculate pad + self.pad = (8 - ((len(self.adrs) - 1) * (16 - self.cmpi) + + (16 - self.cmpe) % 8)) % 8 + + self.routing = ipv6.routing_type3( + self.nxt, self.size, self.type_, self.seg, self.cmpi, + self.cmpe, self.adrs) + self.form = '!BBBBBB2x16s16s16s' + self.buf = struct.pack(self.form, self.nxt, self.size, + self.type_, self.seg, + (self.cmpi << 4) | self.cmpe, + self.pad << 4, + addrconv.ipv6.text_to_bin(self.adrs[0]), + addrconv.ipv6.text_to_bin(self.adrs[1]), + addrconv.ipv6.text_to_bin(self.adrs[2])) + + def test_init(self): + eq_(self.nxt, self.routing.nxt) + eq_(self.size, self.routing.size) + eq_(self.type_, self.routing.type_) + eq_(self.seg, self.routing.seg) + eq_(self.cmpi, self.routing.cmpi) + eq_(self.cmpe, self.routing.cmpe) + eq_(self.pad, self.routing._pad) + eq_(self.adrs[0], self.routing.adrs[0]) + eq_(self.adrs[1], self.routing.adrs[1]) + eq_(self.adrs[2], self.routing.adrs[2]) + + def test_parser(self): + _res = ipv6.routing.parser(self.buf) + if type(_res) is tuple: + res = _res[0] + else: + res = _res + eq_(self.nxt, res.nxt) + eq_(self.size, res.size) + eq_(self.type_, res.type_) + eq_(self.seg, res.seg) + eq_(self.cmpi, res.cmpi) + eq_(self.cmpe, res.cmpe) + eq_(self.pad, res._pad) + eq_(self.adrs[0], res.adrs[0]) + eq_(self.adrs[1], res.adrs[1]) + eq_(self.adrs[2], res.adrs[2]) + + def test_serialize(self): + buf = self.routing.serialize() + res = struct.unpack_from(self.form, str(buf)) + eq_(self.nxt, res[0]) + eq_(self.size, res[1]) + eq_(self.type_, res[2]) + eq_(self.seg, res[3]) + eq_(self.cmpi, res[4] >> 4) + eq_(self.cmpe, res[4] & 0xf) + eq_(self.pad, res[5]) + eq_(addrconv.ipv6.text_to_bin(self.adrs[0]), res[6]) + eq_(addrconv.ipv6.text_to_bin(self.adrs[1]), res[7]) + eq_(addrconv.ipv6.text_to_bin(self.adrs[2]), res[8]) + + def test_parser_with_adrs_zero(self): + nxt = 0 + size = 0 + type_ = 3 + seg = 0 + cmpi = 0 + cmpe = 0 + adrs = [] + # calculate pad + pad = (8 - ((len(adrs) - 1) * (16 - cmpi) + (16 - cmpe) % 8)) % 8 + + form = '!BBBBBB2x' + buf = struct.pack(form, nxt, size, type_, seg, + (cmpi << 4) | cmpe, pad << 4) + _res = ipv6.routing.parser(buf) + if type(_res) is tuple: + res = _res[0] + else: + res = _res + eq_(nxt, res.nxt) + eq_(size, res.size) + eq_(type_, res.type_) + eq_(seg, res.seg) + eq_(cmpi, res.cmpi) + eq_(cmpe, res.cmpe) + eq_(pad, res._pad) + + def test_serialize_with_adrs_zero(self): + nxt = 0 + size = 0 + type_ = 3 + seg = 0 + cmpi = 0 + cmpe = 0 + adrs = [] + # calculate pad + pad = (8 - ((len(adrs) - 1) * (16 - cmpi) + (16 - cmpe) % 8)) % 8 + routing = ipv6.routing_type3( + nxt, size, type_, seg, cmpi, + cmpe, pad) + buf = routing.serialize() + form = '!BBBBBB2x' + res = struct.unpack_from(form, str(buf)) + eq_(nxt, res[0]) + eq_(size, res[1]) + eq_(type_, res[2]) + eq_(seg, res[3]) + eq_(cmpi, res[4] >> 4) + eq_(cmpe, res[4] & 0xf) + eq_(pad, res[5]) + + def test_parser_with_compression(self): + pass + nxt = 0 + size = 3 + type_ = 3 + seg = 0 + cmpi = 8 + cmpe = 12 + adrs = ["2001:0db8:dead:0123:4567:89ab:cdef:0001", + "2001:0db8:dead:0123:4567:89ab:cdef:0002", + "2001:0db8:dead:0123:4567:89ab:cdef:0003"] + # calculate pad + pad = (8 - ((len(adrs) - 1) * (16 - cmpi) + (16 - cmpe) % 8)) % 8 + form = '!BBBBBB2x%ds%ds%ds' % (16-cmpi, 16-cmpi, 16-cmpe) + slice_i = slice(cmpi, 16) + slice_e = slice(cmpe, 16) + buf = struct.pack(form, nxt, size, type_, seg, + (cmpi << 4) | cmpe, pad << 4, + addrconv.ipv6.text_to_bin(adrs[0])[slice_i], + addrconv.ipv6.text_to_bin(adrs[1])[slice_i], + addrconv.ipv6.text_to_bin(adrs[2])[slice_e]) + _res = ipv6.routing.parser(buf) + if type(_res) is tuple: + res = _res[0] + else: + res = _res + eq_(nxt, res.nxt) + eq_(size, res.size) + eq_(type_, res.type_) + eq_(seg, res.seg) + eq_(cmpi, res.cmpi) + eq_(cmpe, res.cmpe) + eq_(pad, res._pad) + eq_("::4567:89ab:cdef:1", res.adrs[0]) + eq_("::4567:89ab:cdef:2", res.adrs[1]) + eq_("::205.239.0.3", res.adrs[2]) + + def test_serialize_with_compression(self): + nxt = 0 + size = 3 + type_ = 3 + seg = 0 + cmpi = 8 + cmpe = 8 + adrs = ["2001:db8:dead::1", + "2001:db8:dead::2", + "2001:db8:dead::3"] + # calculate pad + pad = (8 - ((len(adrs) - 1) * (16 - cmpi) + (16 - cmpe) % 8)) % 8 + slice_i = slice(cmpi, 16) + slice_e = slice(cmpe, 16) + routing = ipv6.routing_type3( + nxt, size, type_, seg, cmpi, cmpe, adrs) + buf = routing.serialize() + form = '!BBBBBB2x8s8s8s' + res = struct.unpack_from(form, str(buf)) + eq_(nxt, res[0]) + eq_(size, res[1]) + eq_(type_, res[2]) + eq_(seg, res[3]) + eq_(cmpi, res[4] >> 4) + eq_(cmpe, res[4] & 0xf) + eq_(pad, res[5]) + eq_(addrconv.ipv6.text_to_bin(adrs[0])[slice_i], res[6]) + eq_(addrconv.ipv6.text_to_bin(adrs[1])[slice_i], res[7]) + eq_(addrconv.ipv6.text_to_bin(adrs[2])[slice_e], res[8]) + + def test_len(self): + eq_((6 + 1) * 8, len(self.routing)) + + def test_default_args(self): + hdr = ipv6.routing_type3() + buf = hdr.serialize() + LOG.info(repr(buf)) + res = struct.unpack_from(ipv6.routing_type3._PACK_STR, str(buf)) + LOG.info(res) + + eq_(res[0], 6) + eq_(res[1], 0) + eq_(res[2], 3) + eq_(res[3], 0) + eq_(res[4], (0 << 4) | 0) + eq_(res[5], 0) + + class Test_fragment(unittest.TestCase): def setUp(self): |