[ip6] add IPv6 fragmentation and reassembly support (#3948)

This commit is contained in:
Christian Stauffer
2019-08-28 10:31:55 -07:00
committed by Jonathan Hui
parent 3198a3be69
commit 864e3458f5
21 changed files with 770 additions and 37 deletions
+3
View File
@@ -147,6 +147,7 @@ EXTRA_DIST = \
test_common.py \
test_crypto.py \
test_diag.py \
test_ipv6_fragmentation.py \
test_ipv6.py \
test_lowpan.py \
test_mac802154.py \
@@ -166,6 +167,7 @@ check_SCRIPTS = \
test_common.py \
test_crypto.py \
test_diag.py \
test_ipv6_fragmentation.py \
test_ipv6.py \
test_lowpan.py \
test_mac802154.py \
@@ -281,6 +283,7 @@ TESTS = \
XFAIL_NCP_TESTS = \
test_coaps.py \
test_diag.py \
test_ipv6_fragmentation.py \
test_service.py \
Cert_5_3_10_AddressQuery.py \
Cert_8_1_01_Commissioning.py \
+65
View File
@@ -45,6 +45,7 @@ except ImportError:
IPV6_NEXT_HEADER_HOP_BY_HOP = 0
IPV6_NEXT_HEADER_TCP = 6
IPV6_NEXT_HEADER_UDP = 17
IPV6_NEXT_HEADER_FRAGMENT = 44
IPV6_NEXT_HEADER_ICMP = 58
UPPER_LAYER_PROTOCOLS = [
@@ -55,6 +56,7 @@ UPPER_LAYER_PROTOCOLS = [
# ICMP Protocol codes
ICMP_DESTINATION_UNREACHABLE = 1
ICMP_TIME_EXCEEDED = 3
ICMP_ECHO_REQUEST = 128
ICMP_ECHO_RESPONSE = 129
@@ -377,6 +379,7 @@ class IPv6Packet(ConvertibleToBytes):
Extension headers:
- HopByHop
- Routing header (not implemented in this module)
- Fragment Header
Upper layer protocols:
- ICMPv6
@@ -643,6 +646,68 @@ class ICMPv6(UpperLayerProtocol):
return len(self.header) + len(self.body)
class FragmentHeader(ExtensionHeader):
""" Class representing Fragment extension header.
+-------------+----------+-----------------+-----+---+----------------+
| Next Header | Reserved | Fragment Offset | Res | M | Identification |
+-------------+----------+-----------------+-----+---+----------------+
Fragment extention header consists of:
- next_header type (8 bit)
- fragment offset which is multiple of 8 (13 bit)
- more_flag to indicate further data (1 bit)
- identification for all associated fragments (32 bit)
"""
@property
def type(self):
return 44
@property
def identification(self):
return self._identification
@property
def more_flag(self):
return self._more_flag
@property
def offset(self):
return self._fragm_offset
def __init__(self, next_header=None, fragm_offset=0, more_flag=False, identification=0):
super(FragmentHeader, self).__init__(next_header, 0)
self._fragm_offset = fragm_offset
self._more_flag = more_flag
self._identification = identification
def callculate_offset(self, position):
return position >> 3
def to_bytes(self):
data = bytearray([self.next_header, 0x00])
data += bytearray([self._fragm_offset >> 5, ((self._fragm_offset << 3) | self._more_flag) & 0xff])
data += struct.pack(">I", self._identification)
return data
@classmethod
def from_bytes(cls, data):
next_header = struct.unpack(">B", data.read(1))[0]
struct.unpack(">B", data.read(1))[0] # reserved
fragment_offset = struct.unpack(">H", data.read(2))[0]
more_flag = fragment_offset & 0x1
identificaton = struct.unpack(">I", data.read(4))[0]
fragment_offset = fragment_offset >> 3
return cls(next_header, fragment_offset, more_flag, identificaton)
def __len__(self):
return 64
class HopByHop(ExtensionHeader):
""" Class representing HopByHop extension header.
+25
View File
@@ -1052,6 +1052,31 @@ class Node:
payload += tlv.to_hex()
self.commissioner_mgmtset(self.bytes_to_hex_str(payload))
def udp_start(self, local_ipaddr, local_port):
cmd = 'udp open'
self.send_command(cmd)
self._expect('Done')
cmd = 'udp bind %s %s' % (local_ipaddr, local_port)
self.send_command(cmd)
self._expect('Done')
def udp_stop(self):
cmd = 'udp close'
self.send_command(cmd)
self._expect('Done')
def udp_send(self, bytes, ipaddr, port, success=True):
cmd = 'udp send -s %d %s %d' % (bytes, ipaddr, port)
self.send_command(cmd)
if success:
self._expect('Done')
else:
self._expect('Error')
def udp_check_rx(self, bytes_should_rx):
self._expect('%d bytes' % bytes_should_rx)
if __name__ == '__main__':
unittest.main()
+53
View File
@@ -61,6 +61,7 @@ from ipv6 import (
BytesPayloadFactory,
ICMPv6DestinationUnreachable,
UdpBasedOnSrcDstPortsPayloadFactory,
FragmentHeader,
)
import common
@@ -157,6 +158,18 @@ def any_checksum():
return any_uint(16)
def any_fragment_offset():
return any_uint(13)
def any_bool():
return (any_uint(1) == 1)
def any_fragment_identification():
return any_uint(32)
def any_icmp_payload(_type, code, checksum, body):
return bytearray([_type, code, (checksum >> 8) & 0xff, checksum & 0xff]) + body
@@ -752,6 +765,46 @@ class TestUDPDatagram(unittest.TestCase):
self.assertEqual(expected_udp_dgram_bytes, udp_dgram_bytes)
class TestIPv6FragmentHeader(unittest.TestCase):
def test_shold_convert_IPv6_fragment_header_to_bytes_when_to_bytes_method_is_called(self):
# GIVEN
type = any_type()
offset = any_fragment_offset()
more_flag = any_bool()
identification = any_fragment_identification()
ipv6_fragment_header = FragmentHeader(type, offset, more_flag, identification)
# WHEN
actual = ipv6_fragment_header.to_bytes()
# THEN
expected = bytearray([type, 0x00, offset >> 5, ((offset << 3) & 0xff) | more_flag])\
+ struct.pack("!I", identification)
self.assertEqual(expected, actual)
def test_should_create_FragmentHeader_when_from_bytes_classmethod_is_called(self):
# GIVEN
type = any_type()
offset = any_fragment_offset()
more_flag = any_bool()
identification = any_fragment_identification()
data = bytearray([type, 0x00, offset >> 5, ((offset << 3) & 0xff) | more_flag])\
+ struct.pack("!I", identification)
# WHEN
ipv6_fragment_header = FragmentHeader.from_bytes(io.BytesIO(data))
# THEN
self.assertEqual(type, ipv6_fragment_header.next_header)
self.assertEqual(offset, ipv6_fragment_header.offset)
self.assertEqual(more_flag, ipv6_fragment_header.more_flag)
self.assertEqual(identification, ipv6_fragment_header.identification)
class TestICMPv6(unittest.TestCase):
def test_should_creates_bytes_from_ICMPv6Header_and_body_when_to_bytes_method_is_called(self):
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# Copyright (c) 2019, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
import unittest
import config
import node
LEADER = 1
ROUTER = 2
class TestIPv6Fragmentation(unittest.TestCase):
def setUp(self):
self.simulator = config.create_default_simulator()
self.nodes = {}
for i in range(1, 3):
self.nodes[i] = node.Node(i, simulator=self.simulator)
self.nodes[LEADER].set_panid(0xcafe)
self.nodes[LEADER].set_mode('rsdn')
self.nodes[LEADER].add_whitelist(self.nodes[ROUTER].get_addr64())
self.nodes[LEADER].enable_whitelist()
self.nodes[ROUTER].set_panid(0xcafe)
self.nodes[ROUTER].set_mode('rsdn')
self.nodes[ROUTER].add_whitelist(self.nodes[LEADER].get_addr64())
self.nodes[ROUTER].enable_whitelist()
self.nodes[ROUTER].set_router_selection_jitter(1)
def tearDown(self):
for n in list(self.nodes.values()):
n.stop()
n.destroy()
self.simulator.stop()
def test(self):
self.nodes[LEADER].start()
self.simulator.go(5)
self.assertEqual(self.nodes[LEADER].get_state(), 'leader')
self.nodes[ROUTER].start()
self.simulator.go(5)
self.assertEqual(self.nodes[ROUTER].get_state(), 'router')
mleid_leader = self.nodes[LEADER].get_ip6_address(config.ADDRESS_TYPE.ML_EID)
mleid_router = self.nodes[ROUTER].get_ip6_address(config.ADDRESS_TYPE.ML_EID)
self.nodes[LEADER].udp_start("::", 12345)
self.nodes[ROUTER].udp_start("::", 12345)
self.nodes[LEADER].udp_send(1952, mleid_router, 12345)
self.simulator.go(5)
self.nodes[ROUTER].udp_check_rx(1952)
self.nodes[ROUTER].udp_send(1831, mleid_leader, 12345)
self.simulator.go(5)
self.nodes[LEADER].udp_check_rx(1831)
self.nodes[ROUTER].udp_send(1953, mleid_leader, 12345, False)
self.nodes[ROUTER].udp_stop()
self.nodes[LEADER].udp_stop()
if __name__ == '__main__':
unittest.main()