summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorYgor Amaral <yabls@cin.ufpe.br>2013-09-02 03:56:17 -0300
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2013-09-02 16:00:04 +0900
commita6b71f671e5bb5c91a91f73d66ee8fb316814759 (patch)
treeb7465841a412e885747783d3196ca230443727ea
parentcdbc7394c76f4ff25d0a43b2fc8f7f4494c02cee (diff)
packet lib: icmpv6: add nd_router messages
add ICMPv6 sub encoder/decoder class for Router Solicitation and Router Advertisement messages. add ICMPv6 sub encoder/decoder class for Neighbor discovery Prefix Information Option. Signed-off-by: Ygor Amaral <yabls@cin.ufpe.br> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r--ryu/lib/packet/icmpv6.py241
-rw-r--r--ryu/tests/unit/packet/test_icmpv6.py100
2 files changed, 316 insertions, 25 deletions
diff --git a/ryu/lib/packet/icmpv6.py b/ryu/lib/packet/icmpv6.py
index ac1d5349..1ad2e68e 100644
--- a/ryu/lib/packet/icmpv6.py
+++ b/ryu/lib/packet/icmpv6.py
@@ -214,8 +214,192 @@ class nd_neighbor(stringify.StringifyMixin):
return hdr
+@icmpv6.register_icmpv6_type(ND_ROUTER_SOLICIT)
+class nd_router_solicit(stringify.StringifyMixin):
+ """ICMPv6 sub encoder/decoder class for Router Solicitation messages.
+ (RFC 4861)
+
+ This is used with ryu.lib.packet.icmpv6.icmpv6.
+
+ 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 correspondig args in this order.
+
+ .. tabularcolumns:: |l|p{35em}|
+
+ ============== ====================
+ Attribute Description
+ ============== ====================
+ res This field is unused. It MUST be initialized to zero.
+ type\_ "Type" field of the first option. None if no options. \
+ NOTE: This implementation doesn't support two or more \
+ options.
+ length "Length" field of the first option. None if no options.
+ data An object to describe the first option. \
+ None if no options. \
+ Either ryu.lib.packet.icmpv6.nd_option_la object \
+ or a bytearray.
+ ============== ====================
+ """
+
+ _PACK_STR = '!I'
+ _MIN_LEN = struct.calcsize(_PACK_STR)
+ _ND_OPTION_TYPES = {}
+
+ # ND option type
+ ND_OPTION_SLA = 1 # Source Link-Layer Address
+
+ @staticmethod
+ def register_nd_option_type(*args):
+ def _register_nd_option_type(cls):
+ for type_ in args:
+ nd_router_solicit._ND_OPTION_TYPES[type_] = cls
+ return cls
+ return _register_nd_option_type
+
+ def __init__(self, res, type_=None, length=None, data=None):
+ self.res = res
+ self.type_ = type_
+ self.length = length
+ self.data = data
+
+ @classmethod
+ def parser(cls, buf, offset):
+ res = struct.unpack_from(cls._PACK_STR, buf, offset)
+ msg = cls(res)
+ offset += cls._MIN_LEN
+ if len(buf) > offset:
+ (msg.type_, msg.length) = struct.unpack_from('!BB', buf, offset)
+ cls_ = cls._ND_OPTION_TYPES.get(msg.type_, None)
+ offset += 2
+ if cls_:
+ msg.data = cls_.parser(buf, offset)
+ else:
+ msg.data = buf[offset:]
+
+ return msg
+
+ def serialize(self):
+ hdr = bytearray(struct.pack(nd_router_solicit._PACK_STR, self.res))
+
+ if self.type_ is not None:
+ hdr += bytearray(struct.pack('!BB', self.type_, self.length))
+ if self.type_ in nd_router_solicit._ND_OPTION_TYPES:
+ hdr += self.data.serialize()
+ elif self.data is not None:
+ hdr += bytearray(self.data)
+
+ return hdr
+
+
+@icmpv6.register_icmpv6_type(ND_ROUTER_ADVERT)
+class nd_router_advert(stringify.StringifyMixin):
+ """ICMPv6 sub encoder/decoder class for Router Advertisement messages.
+ (RFC 4861)
+
+ This is used with ryu.lib.packet.icmpv6.icmpv6.
+
+ 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 correspondig args in this order.
+
+ .. tabularcolumns:: |l|p{35em}|
+
+ ============== ====================
+ Attribute Description
+ ============== ====================
+ ch_l Cur Hop Limit.
+ res M,O Flags for Router Advertisement.
+ rou_l Router Lifetime.
+ rea_t Reachable Time.
+ ret_t Retrans Timer.
+ type\_ List of option type. Each index refers to an option. \
+ None if no options. \
+ NOTE: This implementation support one or more \
+ options.
+ length List of option length. Each index refers to an option. \
+ None if no options. \
+ data List of option data. Each index refers to an option. \
+ None if no options. \
+ ryu.lib.packet.icmpv6.nd_option_la object, \
+ ryu.lib.packet.icmpv6.nd_option_pi object \
+ or a bytearray.
+ ============== ====================
+ """
+
+ _PACK_STR = '!BBHII'
+ _MIN_LEN = struct.calcsize(_PACK_STR)
+ _ND_OPTION_TYPES = {}
+
+ # ND option type
+ ND_OPTION_SLA = 1 # Source Link-Layer Address
+ ND_OPTION_PI = 3 # Prefix Information
+ ND_OPTION_MTU = 5 # MTU
+
+ @staticmethod
+ def register_nd_option_type(*args):
+ def _register_nd_option_type(cls):
+ for type_ in args:
+ nd_router_advert._ND_OPTION_TYPES[type_] = cls
+ return cls
+ return _register_nd_option_type
+
+ def __init__(self, ch_l, res, rou_l, rea_t, ret_t, type_=None, length=None,
+ data=None):
+ self.ch_l = ch_l
+ self.res = res << 6
+ self.rou_l = rou_l
+ self.rea_t = rea_t
+ self.ret_t = ret_t
+ self.type_ = type_
+ self.length = length
+ self.data = data
+
+ @classmethod
+ def parser(cls, buf, offset):
+ (ch_l, res, rou_l, rea_t, ret_t) = struct.unpack_from(cls._PACK_STR,
+ buf, offset)
+ msg = cls(ch_l, res >> 6, rou_l, rea_t, ret_t)
+ offset += cls._MIN_LEN
+ msg.type_ = list()
+ msg.length = list()
+ msg.data = list()
+ while len(buf) > offset:
+ (type_, length) = struct.unpack_from('!BB', buf, offset)
+ msg.type_.append(type_)
+ msg.length.append(length)
+ cls_ = cls._ND_OPTION_TYPES.get(type_, None)
+ offset += 2
+ if cls_:
+ msg.data.append(cls_.parser(buf[:offset+cls_._MIN_LEN],
+ offset))
+ offset += cls_._MIN_LEN
+ else:
+ msg.data.append(buf[offset:])
+ offset = len(buf)
+
+ return msg
+
+ def serialize(self):
+ hdr = bytearray(struct.pack(nd_router_advert._PACK_STR, self.ch_l,
+ self.res, self.rou_l, self.rea_t,
+ self.ret_t))
+ if self.type_ is not None:
+ for i in range(len(self.type_)):
+ hdr += bytearray(struct.pack('!BB', self.type_[i],
+ self.length[i]))
+ if self.type_[i] in nd_router_advert._ND_OPTION_TYPES:
+ hdr += self.data[i].serialize()
+ elif self.data[i] is not None:
+ hdr += bytearray(self.data[i])
+
+ return hdr
+
+
@nd_neighbor.register_nd_option_type(nd_neighbor.ND_OPTION_SLA,
nd_neighbor.ND_OPTION_TLA)
+@nd_router_solicit.register_nd_option_type(nd_router_solicit.ND_OPTION_SLA)
+@nd_router_advert.register_nd_option_type(nd_router_advert.ND_OPTION_SLA)
class nd_option_la(stringify.StringifyMixin):
"""ICMPv6 sub encoder/decoder class for Neighbor discovery
Source/Target Link-Layer Address Option. (RFC 4861)
@@ -270,6 +454,63 @@ class nd_option_la(stringify.StringifyMixin):
return hdr
+@nd_router_advert.register_nd_option_type(nd_router_advert.ND_OPTION_PI)
+class nd_option_pi(stringify.StringifyMixin):
+ """ICMPv6 sub encoder/decoder class for Neighbor discovery
+ Prefix Information Option. (RFC 4861)
+
+ This is used with ryu.lib.packet.icmpv6.nd_neighbor.
+
+ 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 correspondig args in this order.
+
+ .. tabularcolumns:: |l|p{35em}|
+
+ ============== ====================
+ Attribute Description
+ ============== ====================
+ pl Prefix Length.
+ res1 L,A,R* Flags for Prefix Information.
+ val_l Valid Lifetime.
+ pre_l Preferred Lifetime.
+ res2 This field is unused. It MUST be initialized to zero.
+ prefix An IP address or a prefix of an IP address.
+ ============== ====================
+
+ *R flag is defined in (RFC 3775)
+ """
+
+ _PACK_STR = '!BBIII16s'
+ _MIN_LEN = struct.calcsize(_PACK_STR)
+
+ def __init__(self, pl, res1, val_l, pre_l, res2, prefix):
+ self.pl = pl
+ self.res1 = res1 << 5
+ self.val_l = val_l
+ self.pre_l = pre_l
+ self.res2 = res2
+ self.prefix = prefix
+
+ @classmethod
+ def parser(cls, buf, offset):
+ (pl, res1, val_l, pre_l, res2, prefix) = struct.unpack_from(cls.
+ _PACK_STR,
+ buf,
+ offset)
+ msg = cls(pl, res1 >> 5, val_l, pre_l, res2,
+ addrconv.ipv6.bin_to_text(prefix))
+
+ return msg
+
+ def serialize(self):
+ hdr = bytearray(struct.pack(self._PACK_STR, self.pl, self.res1,
+ self.val_l, self.pre_l, self.res2,
+ addrconv.ipv6.text_to_bin(self.prefix)))
+
+ return hdr
+
+
@icmpv6.register_icmpv6_type(ICMPV6_ECHO_REPLY, ICMPV6_ECHO_REQUEST)
class echo(stringify.StringifyMixin):
"""ICMPv6 sub encoder/decoder class for Echo Request and Echo Reply
diff --git a/ryu/tests/unit/packet/test_icmpv6.py b/ryu/tests/unit/packet/test_icmpv6.py
index 848ecf9d..0a33ca0a 100644
--- a/ryu/tests/unit/packet/test_icmpv6.py
+++ b/ryu/tests/unit/packet/test_icmpv6.py
@@ -347,10 +347,11 @@ class Test_icmpv6_router_solict(unittest.TestCase):
res = 0
nd_type = 1
nd_length = 1
- nd_data = None
- nd_hw_src = '\x12\x2d\xa5\x6d\xbc\x0f'
+ nd_hw_src = '12:2d:a5:6d:bc:0f'
data = '\x00\x00\x00\x00\x01\x01\x12\x2d\xa5\x6d\xbc\x0f'
buf = '\x85\x00\x97\xd9'
+ src_ipv6 = '3ffe:507:0:1:200:86ff:fe05:80da'
+ dst_ipv6 = '3ffe:501:0:1001::2'
def setUp(self):
pass
@@ -359,7 +360,11 @@ class Test_icmpv6_router_solict(unittest.TestCase):
pass
def test_init(self):
- pass
+ rs = icmpv6.nd_router_solicit(self.res)
+ eq_(rs.res, self.res)
+ eq_(rs.type_, None)
+ eq_(rs.length, None)
+ eq_(rs.data, None)
def _test_parser(self, data=None):
buf = self.buf + str(data or '')
@@ -368,7 +373,15 @@ class Test_icmpv6_router_solict(unittest.TestCase):
eq_(msg.type_, self.type_)
eq_(msg.code, self.code)
eq_(msg.csum, self.csum)
- eq_(msg.data, data)
+ if data is not None:
+ eq_(msg.data.res[0], self.res)
+ eq_(n, None)
+ if data:
+ rs = msg.data
+ eq_(rs.type_, self.nd_type)
+ eq_(rs.length, self.nd_length)
+ eq_(rs.data.hw_src, self.nd_hw_src)
+ eq_(rs.data.data, None)
def test_parser_without_data(self):
self._test_parser()
@@ -376,38 +389,75 @@ class Test_icmpv6_router_solict(unittest.TestCase):
def test_parser_with_data(self):
self._test_parser(self.data)
- def _test_serialize(self, nd_data=None):
- nd_data = str(nd_data or '')
- buf = self.buf + nd_data
- src_ipv6 = 'fe80::102d:a5ff:fe6d:bc0f'
- dst_ipv6 = 'ff02::2'
- prev = ipv6(6, 0, 0, len(buf), 58, 255, src_ipv6, dst_ipv6)
- nd_csum = icmpv6_csum(prev, buf)
+ def test_serialize_without_data(self):
+ rs = icmpv6.nd_router_solicit(self.res)
+ prev = ipv6(6, 0, 0, 8, 64, 255, self.src_ipv6, self.dst_ipv6)
+ rs_csum = icmpv6_csum(prev, self.buf)
- icmp = icmpv6.icmpv6(self.type_, self.code, 0, nd_data)
+ icmp = icmpv6.icmpv6(self.type_, self.code, 0, rs)
buf = buffer(icmp.serialize(bytearray(), prev))
+
(type_, code, csum) = struct.unpack_from(icmp._PACK_STR, buf, 0)
- data = buf[icmp._MIN_LEN:]
+ res = struct.unpack_from(rs._PACK_STR, buf, icmp._MIN_LEN)
+ data = buf[(icmp._MIN_LEN + rs._MIN_LEN):]
eq_(type_, self.type_)
eq_(code, self.code)
- eq_(csum, nd_csum)
- eq_(data, nd_data)
-
- def test_serialize_without_data(self):
- self._test_serialize()
+ eq_(csum, rs_csum)
+ eq_(res[0], self.res)
+ eq_(data, '')
def test_serialize_with_data(self):
- self._test_serialize(self.data)
+ nd_opt = icmpv6.nd_option_la(self.nd_hw_src)
+ rs = icmpv6.nd_router_solicit(self.res, self.nd_type, self.nd_length,
+ nd_opt)
+ prev = ipv6(6, 0, 0, 16, 64, 255, self.src_ipv6, self.dst_ipv6)
+ rs_csum = icmpv6_csum(prev, self.buf + self.data)
+
+ icmp = icmpv6.icmpv6(self.type_, self.code, 0, rs)
+ buf = buffer(icmp.serialize(bytearray(), prev))
+
+ (type_, code, csum) = struct.unpack_from(icmp._PACK_STR, buf, 0)
+ res = struct.unpack_from(rs._PACK_STR, buf, icmp._MIN_LEN)
+ (nd_type, nd_length, nd_hw_src) = struct.unpack_from(
+ '!BB6s', buf, icmp._MIN_LEN + rs._MIN_LEN)
+ data = buf[(icmp._MIN_LEN + rs._MIN_LEN + 8):]
+
+ eq_(type_, self.type_)
+ eq_(code, self.code)
+ eq_(csum, rs_csum)
+ eq_(res[0], self.res)
+ eq_(nd_type, self.nd_type)
+ eq_(nd_length, self.nd_length)
+ eq_(nd_hw_src, addrconv.mac.text_to_bin(self.nd_hw_src))
def test_to_string(self):
- ic = icmpv6.icmpv6(self.type_, self.code, self.csum, self.data)
+ nd_opt = icmpv6.nd_option_la(self.nd_hw_src)
+ rs = icmpv6.nd_router_solicit(
+ self.res, self.nd_type, self.nd_length, nd_opt)
+ ic = icmpv6.icmpv6(self.type_, self.code, self.csum, rs)
- icmp_values = {'type_': self.type_,
- 'code': self.code,
- 'csum': self.csum,
- 'data': self.data}
- _ic_str = ','.join(['%s=%s' % (k, repr(icmp_values[k]))
+ nd_opt_values = {'hw_src': self.nd_hw_src,
+ 'data': None}
+ _nd_opt_str = ','.join(['%s=%s' % (k, repr(nd_opt_values[k]))
+ for k, v in inspect.getmembers(nd_opt)
+ if k in nd_opt_values])
+ nd_opt_str = '%s(%s)' % (icmpv6.nd_option_la.__name__, _nd_opt_str)
+
+ rs_values = {'res': repr(rs.res),
+ 'type_': repr(self.nd_type),
+ 'length': repr(self.nd_length),
+ 'data': nd_opt_str}
+ _rs_str = ','.join(['%s=%s' % (k, rs_values[k])
+ for k, v in inspect.getmembers(rs)
+ if k in rs_values])
+ rs_str = '%s(%s)' % (icmpv6.nd_router_solicit.__name__, _rs_str)
+
+ icmp_values = {'type_': repr(self.type_),
+ 'code': repr(self.code),
+ 'csum': repr(self.csum),
+ 'data': rs_str}
+ _ic_str = ','.join(['%s=%s' % (k, icmp_values[k])
for k, v in inspect.getmembers(ic)
if k in icmp_values])
ic_str = '%s(%s)' % (icmpv6.icmpv6.__name__, _ic_str)