mirror of
https://github.com/espressif/openthread.git
synced 2026-10-23 16:08:01 +00:00
[ip6] add IPv6 fragmentation and reassembly support (#3948)
This commit is contained in:
committed by
Jonathan Hui
parent
3198a3be69
commit
864e3458f5
@@ -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 \
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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()
|
||||
Reference in New Issue
Block a user