summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authortakahashi.minoru <takahashi.minoru7@gmail.com>2014-04-24 15:35:18 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2014-04-24 23:09:41 +0900
commit9c5e9288ccb1612246356130525611484c620dd5 (patch)
tree56f4fa44b61c27221ca24a3e21baccfeb8a6e8e4
parent0f8407a9ce96e0f07f9660dd1e4e99e3dd2ec580 (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.py159
-rw-r--r--ryu/tests/unit/packet/test_ipv6.py319
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):