diff options
author | IWASE Yusuke <iwase.yusuke0@gmail.com> | 2016-05-17 11:02:07 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2016-05-23 12:48:08 +0900 |
commit | a6c3d38789fbd0448ac3c10c88ef194a1293b360 (patch) | |
tree | 228b3664a38945c10fb1acb04810a37314a2c352 | |
parent | 7f383974424e7080dabca217da607dcea2c64e9f (diff) |
packet: Add VXLAN parser
IANA has assigned the value 4789 for the VXLAN UDP destination port,
this patch registers dst_port 4789 into UDP packet parser.
Additionally, early adopters might be using UDP dst_port 8472,
we register this number for the backward compatibility.
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/lib/packet/udp.py | 11 | ||||
-rw-r--r-- | ryu/lib/packet/vxlan.py | 90 | ||||
-rw-r--r-- | ryu/tests/unit/packet/test_vxlan.py | 75 |
3 files changed, 173 insertions, 3 deletions
diff --git a/ryu/lib/packet/udp.py b/ryu/lib/packet/udp.py index bae6d735..b67bd1a4 100644 --- a/ryu/lib/packet/udp.py +++ b/ryu/lib/packet/udp.py @@ -18,6 +18,7 @@ import struct from . import packet_base from . import packet_utils from . import dhcp +from . import vxlan class udp(packet_base.PacketBase): @@ -49,10 +50,14 @@ class udp(packet_base.PacketBase): self.total_length = total_length self.csum = csum - @classmethod - def get_packet_type(cls, src_port, dst_port): - if (src_port == 68 and dst_port == 67) or (src_port == 67 and dst_port == 68): + @staticmethod + def get_packet_type(src_port, dst_port): + if ((src_port == 68 and dst_port == 67) or + (src_port == 67 and dst_port == 68)): return dhcp.dhcp + if (dst_port == vxlan.UDP_DST_PORT or + dst_port == vxlan.UDP_DST_PORT_OLD): + return vxlan.vxlan return None @classmethod diff --git a/ryu/lib/packet/vxlan.py b/ryu/lib/packet/vxlan.py new file mode 100644 index 00000000..d68b9b62 --- /dev/null +++ b/ryu/lib/packet/vxlan.py @@ -0,0 +1,90 @@ +# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +VXLAN packet parser/serializer + +RFC 7348 +VXLAN Header: ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|R|R|R|R|I|R|R|R| Reserved | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| VXLAN Network Identifier (VNI) | Reserved | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +- Flags (8 bits): where the I flag MUST be set to 1 for a valid + VXLAN Network ID (VNI). The other 7 bits (designated "R") are + reserved fields and MUST be set to zero on transmission and + ignored on receipt. + +- VXLAN Segment ID/VXLAN Network Identifier (VNI): this is a + 24-bit value used to designate the individual VXLAN overlay + network on which the communicating VMs are situated. VMs in + different VXLAN overlay networks cannot communicate with each + other. + +- Reserved fields (24 bits and 8 bits): MUST be set to zero on + transmission and ignored on receipt. +""" + +import struct +import logging + +from . import packet_base + + +LOG = logging.getLogger(__name__) + +UDP_DST_PORT = 4789 +UDP_DST_PORT_OLD = 8472 # for backward compatibility like Linux + + +class vxlan(packet_base.PacketBase): + """VXLAN (RFC 7348) header encoder/decoder class. + + 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. + + ============== ==================== + Attribute Description + ============== ==================== + vni VXLAN Network Identifier + ============== ==================== + """ + + # Note: Python has no format character for 24 bits field. + # we use uint32 format character instead and bit-shift at serializing. + _PACK_STR = '!II' + _MIN_LEN = struct.calcsize(_PACK_STR) + + def __init__(self, vni): + super(vxlan, self).__init__() + self.vni = vni + + @classmethod + def parser(cls, buf): + (flags_reserved, vni_rserved) = struct.unpack_from(cls._PACK_STR, buf) + + # Check VXLAN flags is valid + assert (1 << 3) == (flags_reserved >> 24) + + # Note: To avoid cyclic import, import ethernet module here + from ryu.lib.packet import ethernet + return cls(vni_rserved >> 8), ethernet.ethernet, buf[cls._MIN_LEN:] + + def serialize(self, payload, prev): + return struct.pack(self._PACK_STR, + 1 << (3 + 24), self.vni << 8) diff --git a/ryu/tests/unit/packet/test_vxlan.py b/ryu/tests/unit/packet/test_vxlan.py new file mode 100644 index 00000000..fe418ff7 --- /dev/null +++ b/ryu/tests/unit/packet/test_vxlan.py @@ -0,0 +1,75 @@ +# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import unittest + +from nose.tools import eq_ +from nose.tools import raises + +from ryu.lib.packet import ethernet +from ryu.lib.packet import vxlan + + +LOG = logging.getLogger(__name__) + + +class Test_vxlan(unittest.TestCase): + """ + Test case for VXLAN (RFC 7348) header encoder/decoder class. + """ + + vni = 0x123456 + buf = ( + b'\x08\x00\x00\x00' # flags = R|R|R|R|I|R|R|R (8 bits) + b'\x12\x34\x56\x00' # vni = 0x123456 (24 bits) + b'test_payload' # for test + ) + pkt = vxlan.vxlan(vni) + jsondict = { + 'vxlan': { + 'vni': vni + } + } + + def test_init(self): + eq_(self.vni, self.pkt.vni) + + def test_parser(self): + parsed_pkt, next_proto_cls, rest_buf = vxlan.vxlan.parser(self.buf) + eq_(self.vni, parsed_pkt.vni) + eq_(ethernet.ethernet, next_proto_cls) + eq_(b'test_payload', rest_buf) + + @raises(AssertionError) + def test_invalid_flags(self): + invalid_flags_bug = ( + b'\x00\x00\x00\x00' # all bits are set to zero + b'\x12\x34\x56\x00' # vni = 0x123456 (24 bits) + ) + vxlan.vxlan.parser(invalid_flags_bug) + + def test_serialize(self): + serialized_buf = self.pkt.serialize(payload=None, prev=None) + eq_(self.buf[:vxlan.vxlan._MIN_LEN], serialized_buf) + + def test_from_jsondict(self): + pkt_from_json = vxlan.vxlan.from_jsondict( + self.jsondict[vxlan.vxlan.__name__]) + eq_(self.vni, pkt_from_json.vni) + + def test_to_jsondict(self): + jsondict_from_pkt = self.pkt.to_jsondict() + eq_(self.jsondict, jsondict_from_pkt) |