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>
This commit is contained in:
IWASE Yusuke 2016-05-17 11:02:07 +09:00 committed by FUJITA Tomonori
parent 7f38397442
commit a6c3d38789
3 changed files with 173 additions and 3 deletions

View File

@ -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

90
ryu/lib/packet/vxlan.py Normal file
View File

@ -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)

View File

@ -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)