summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorIWASE Yusuke <iwase.yusuke0@gmail.com>2016-05-17 11:02:07 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2016-05-23 12:48:08 +0900
commita6c3d38789fbd0448ac3c10c88ef194a1293b360 (patch)
tree228b3664a38945c10fb1acb04810a37314a2c352
parent7f383974424e7080dabca217da607dcea2c64e9f (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.py11
-rw-r--r--ryu/lib/packet/vxlan.py90
-rw-r--r--ryu/tests/unit/packet/test_vxlan.py75
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)