[mlr] send MLR.req for local multicast addresses (#5248)

This commit includes:

- Adding compile configurations necessary for MLR features
- Adding source/header files necessary for MLR features
- Send MLR.req for local MAs on ThreadNetif
  - At most one MLR.req is outstanding
  - Reregistration according to PBBR configuration
  - Renewing periods according to PBBR configuration
- BBRs receive MLR.req with minimal processing and send MLR.rsp
- Add a test to verify that MLR.req is sent for local MAs.
This commit is contained in:
Simon Lin
2020-07-22 08:38:05 -07:00
committed by GitHub
parent 2129b567b4
commit 00f3043a06
29 changed files with 1564 additions and 12 deletions
+3
View File
@@ -336,6 +336,8 @@ def create_deafult_network_tlvs_factories():
# which is in fact MLE Route64 TLV. Thread specificaton v1.1. - Chapter 5.20
network_layer.TlvType.MLE_ROUTING:
create_default_mle_tlv_route64_factory(),
network_layer.TlvType.IPv6_ADDRESSES:
network_layer.IPv6AddressesFactory(),
}
@@ -488,6 +490,7 @@ def create_default_uri_path_based_payload_factories():
'/d/dg': network_diag_tlvs_factory,
'/d/dq': network_diag_tlvs_factory,
'/d/dr': network_diag_tlvs_factory,
'/n/mr': network_layer_tlvs_factory,
}
+19 -1
View File
@@ -27,8 +27,8 @@
# POSSIBILITY OF SUCH DAMAGE.
#
import ipaddress
import struct
from binascii import hexlify
from enum import IntEnum
@@ -47,6 +47,7 @@ class TlvType(IntEnum):
ND_DATA = 9
THREAD_NETWORK_DATA = 10
MLE_ROUTING = 11
IPv6_ADDRESSES = 14
XTAL_ACCURACY = 254
@@ -318,3 +319,20 @@ class ThreadNetworkDataFactory(object):
def parse(self, data, message_info):
tlvs = self._network_data_tlvs_factory.parse(data, message_info)
return ThreadNetworkData(tlvs)
class IPv6Addresses(list):
pass
class IPv6AddressesFactory(object):
def parse(self, data, message_info):
data = bytes(data.read())
assert len(data) % 16 == 0, data
addrs = IPv6Addresses()
for i in range(0, len(data), 16):
addr = ipaddress.IPv6Address(data[i:i + 16])
addrs.append(addr)
return addrs
+5
View File
@@ -727,6 +727,11 @@ class Node:
self.send_command(cmd)
self._expect('Done')
def del_ipmaddr(self, ipmaddr):
cmd = 'ipmaddr del %s' % ipmaddr
self.send_command(cmd)
self._expect('Done')
def get_addrs(self):
self.send_command('ipaddr')
+1 -1
View File
@@ -135,7 +135,7 @@ class VirtualTime(BaseSimulator):
BASE_PORT = 9000
MAX_NODES = 34
MAX_MESSAGE = 1024
END_OF_TIME = 0x7FFFFFFF
END_OF_TIME = float('inf')
PORT_OFFSET = int(os.getenv('PORT_OFFSET', '0'))
BLOCK_TIMEOUT = 10
@@ -0,0 +1,400 @@
#!/usr/bin/env python3
#
# Copyright (c) 2020, 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 ipaddress
import unittest
import config
import network_layer
import thread_cert
_, BBR_1, BBR_2, ROUTER_1_2, ROUTER_1_1, SED_1, MED_1, FED_1 = range(8)
WAIT_ATTACH = 5
WAIT_REDUNDANCE = 3
ROUTER_SELECTION_JITTER = 1
BBR_REGISTRATION_JITTER = 5
SED_POLL_PERIOD = 1000 # 1s
REREG_DELAY = 10
MLR_TIMEOUT = 300
MA1 = 'ff04::1234:777a:1'
MA1g = 'ff0e::1234:777a:1'
MA2 = 'ff05::1234:777a:2'
MA3 = 'ff0e::1234:777a:3'
MA4 = 'ff05::1234:777a:4'
MA5 = 'ff03::1234:777a:5'
MA6 = 'ff02::10'
"""
Initial topology:
BBR_1---BBR_2
| / \ |
| / \ |
ROUTER_1_2 ROUTER_1_1
| | \ |
| | \ |
SED_1 MED_1 FED_1
"""
class TestMulticastListenerRegistration(thread_cert.TestCase):
TOPOLOGY = {
BBR_1: {
'version': '1.2',
'whitelist': [BBR_2, ROUTER_1_2],
'is_bbr': True,
'router_selection_jitter': 1,
},
BBR_2: {
'version': '1.2',
'whitelist': [BBR_1, ROUTER_1_2, ROUTER_1_1],
'is_bbr': True,
'router_selection_jitter': 1,
},
ROUTER_1_2: {
'version': '1.2',
'whitelist': [BBR_1, BBR_2, SED_1, MED_1, FED_1],
'router_selection_jitter': 1,
},
ROUTER_1_1: {
'version': '1.1',
'whitelist': [BBR_2],
'router_selection_jitter': 1,
},
MED_1: {
'mode': 'rsn',
'version': '1.2',
'whitelist': [ROUTER_1_2],
'timeout': config.DEFAULT_CHILD_TIMEOUT,
},
SED_1: {
'mode': 'sn',
'version': '1.2',
'whitelist': [ROUTER_1_2],
'timeout': config.DEFAULT_CHILD_TIMEOUT,
},
FED_1: {
'mode': 'rsdn',
'version': '1.2',
'whitelist': [ROUTER_1_2],
'router_upgrade_threshold': 0,
'timeout': config.DEFAULT_CHILD_TIMEOUT,
},
}
"""All nodes are created with default configurations"""
def test(self):
# starting context id
context_id = 1
# Bring up BBR_1, BBR_1 becomes Leader and Primary Backbone Router
self.nodes[BBR_1].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
self.nodes[BBR_1].set_backbone_router(seqno=1,
reg_delay=REREG_DELAY,
mlr_timeout=MLR_TIMEOUT)
self.nodes[BBR_1].start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_1].get_state(), 'leader')
self.nodes[BBR_1].enable_backbone_router()
WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(),
'Primary')
self.pbbr_seq = 1
# Bring up BBR_2, BBR_2 becomes Router and Secondary Backbone Router
self.nodes[BBR_2].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
self.nodes[BBR_2].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
self.nodes[BBR_2].set_backbone_router(seqno=2,
reg_delay=REREG_DELAY,
mlr_timeout=MLR_TIMEOUT)
self.nodes[BBR_2].start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_2].get_state(), 'router')
self.nodes[BBR_2].enable_backbone_router()
WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(),
'Secondary')
self.simulator.set_lowpan_context(context_id, config.DOMAIN_PREFIX)
domain_prefix_cid = context_id
# Bring up ROUTER_1_2
self.nodes[ROUTER_1_2].set_router_selection_jitter(
ROUTER_SELECTION_JITTER)
self.nodes[ROUTER_1_2].start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[ROUTER_1_2].get_state(), 'router')
# Bring up ROUTER_1_1
self.nodes[ROUTER_1_1].set_router_selection_jitter(
ROUTER_SELECTION_JITTER)
self.nodes[ROUTER_1_1].start()
WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
self.simulator.go(WAIT_TIME)
self.assertEqual(self.nodes[ROUTER_1_1].get_state(), 'router')
# Bring up FED_1
self.nodes[FED_1].start()
self.simulator.go(WAIT_ATTACH)
self.assertEqual(self.nodes[FED_1].get_state(), 'child')
# Bring up MED_1
self.nodes[MED_1].start()
self.simulator.go(WAIT_ATTACH)
self.assertEqual(self.nodes[MED_1].get_state(), 'child')
# Bring up SED_1
self.nodes[SED_1].set_pollperiod(SED_POLL_PERIOD)
self.nodes[SED_1].start()
self.simulator.go(WAIT_ATTACH)
self.assertEqual(self.nodes[SED_1].get_state(), 'child')
# Verify MLR.req for each device when parent is 1.2
self.__check_mlr_ok(ROUTER_1_2, is_ftd=True)
self.__check_mlr_ok(FED_1, is_ftd=True)
self.__check_mlr_ok(MED_1, is_ftd=False)
self.__check_mlr_ok(SED_1, is_ftd=False)
# Switch to parent 1.1
self.__switch_to_1_1_parent()
# Verify MLR.req for each device when parent is 1.1
self.__check_mlr_ok(FED_1, is_ftd=True, is_parent_1p1=True)
self.__check_mlr_ok(MED_1, is_ftd=False, is_parent_1p1=True)
self.__check_mlr_ok(SED_1, is_ftd=False, is_parent_1p1=True)
# Switch to parent 1.2
self.__switch_to_1_2_parent()
def __check_mlr_ok(self, id, is_ftd, is_parent_1p1=False):
"""Check if MLR works for the node"""
# Add MA1 and send MLR.req
print(
"======== checking MLR: Node%d (%s), Parent=%s ========" %
(id, 'FTD' if is_ftd else 'MTD', '1.1' if is_parent_1p1 else '1.2'))
expect_mlr_req = is_ftd or is_parent_1p1
for addr in [MA1, MA1g, MA2, MA3, MA4]:
self.__check_ipmaddr_add(id, addr, expect_mlr_req=expect_mlr_req)
for addr in [MA5, MA6]:
self.__check_ipmaddr_add(id, addr, expect_mlr_req=False)
print('=' * 120)
def __check_ipmaddr_add(self, id, addr, expect_mlr_req=True):
"""Check MLR works for the added multicast address"""
self.flush_all()
self.nodes[id].add_ipmaddr(addr)
self.assertTrue(self.nodes[id].has_ipmaddr(addr))
self.simulator.go(3)
reg_mas = self.__get_registered_MAs(id)
if expect_mlr_req:
assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas)
messages = self.simulator.get_messages_sent_by(BBR_1)
messages.next_coap_message('2.04')
self.__check_rereg(id, addr)
self.__check_renewing(id, addr)
else:
assert ipaddress.IPv6Address(addr) not in reg_mas, (addr, reg_mas)
self.nodes[id].del_ipmaddr(addr)
self.simulator.go(1)
def __get_registered_MAs(self, id):
"""Get MAs registered via MLR.req by the node"""
messages = self.simulator.get_messages_sent_by(id)
reg_mas = []
while True:
msg = messages.next_coap_message('0.02',
'/n/mr',
assert_enabled=False)
if not msg:
break
addrs = msg.get_coap_message_tlv(network_layer.IPv6Addresses)
reg_mas.extend(addrs)
print('Node %d registered MAs: %s' % (id, reg_mas))
return reg_mas
def __check_renewing(self, id, addr):
"""Check if MLR works that a node can renew it's registered MAs"""
self.flush_all()
self.simulator.go(MLR_TIMEOUT + WAIT_REDUNDANCE)
reg_mas = self.__get_registered_MAs(id)
assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas)
def __check_rereg(self, id, addr):
"""Check if MLR works that a node can do MLR reregistration when necessary"""
self.__check_rereg_seqno(id, addr)
self.__check_rereg_pbbr_change(id, addr)
def __check_rereg_seqno(self, id, addr):
"""Check if MLR works that a node can do MLR reregistration when PBBR seqno changes"""
# Change seq on PBBR and expect MLR.req within REREG_DELAY
self.flush_all()
self.pbbr_seq = (self.pbbr_seq + 10) % 256
self.nodes[BBR_1].set_backbone_router(seqno=self.pbbr_seq)
self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE)
reg_mas = self.__get_registered_MAs(id)
assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas)
def __check_rereg_pbbr_change(self, id, addr):
"""Check if MLR works that a node can do MLR reregistration when PBBR changes"""
# Make BBR_2 to be Primary and expect MLR.req within REREG_DELAY
self.flush_all()
self.nodes[BBR_1].disable_backbone_router()
self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE)
self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(),
'Primary')
self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE)
reg_mas = self.__get_registered_MAs(id)
assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas)
# Restore BBR_1 to be Primary and BBR_2 to be Secondary
self.nodes[BBR_2].disable_backbone_router()
self.nodes[BBR_1].enable_backbone_router()
self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE)
self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(),
'Primary')
self.nodes[BBR_2].enable_backbone_router()
self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE)
self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(),
'Secondary')
def __switch_to_1_1_parent(self):
"""Check if MLR works when nodes are switching to a 1.1 parent"""
# Add MA1 to EDs to prepare for parent switching
print("=" * 10, "switching to 1.1 parent", '=' * 10)
self.flush_all()
self.nodes[FED_1].add_ipmaddr(MA1)
self.nodes[MED_1].add_ipmaddr(MA1)
self.nodes[SED_1].add_ipmaddr(MA1)
self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE)
self.assertIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(FED_1))
self.assertNotIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(MED_1))
self.assertNotIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(SED_1))
self.flush_all()
# Turn off Router 1.2 and turn on Router 1.1
self.nodes[ROUTER_1_2].stop()
for id in [FED_1, MED_1, SED_1]:
self.nodes[ROUTER_1_1].add_whitelist(self.nodes[id].get_addr64())
self.nodes[id].add_whitelist(self.nodes[ROUTER_1_1].get_addr64())
self.simulator.go(config.DEFAULT_CHILD_TIMEOUT + WAIT_REDUNDANCE)
self.assertEqual(self.nodes[id].get_state(), 'child')
self.assertEqual(self.nodes[id].get_router_id(),
self.nodes[ROUTER_1_1].get_router_id())
self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE)
# Verify all FED send MLR.req within REREG_DELAY when parent is 1.1
self.assertIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(FED_1))
self.assertIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(MED_1))
self.assertIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(SED_1))
self.nodes[FED_1].del_ipmaddr(MA1)
self.nodes[MED_1].del_ipmaddr(MA1)
self.nodes[SED_1].del_ipmaddr(MA1)
self.simulator.go(WAIT_REDUNDANCE)
def __switch_to_1_2_parent(self):
"""Check if MLR works when nodes are switching to a 1.2 parent"""
# Add MA1 to EDs to prepare for parent switching
print("=" * 10, "switching to 1.2 parent", '=' * 10)
self.flush_all()
self.nodes[FED_1].add_ipmaddr(MA1)
self.nodes[MED_1].add_ipmaddr(MA1)
self.nodes[SED_1].add_ipmaddr(MA1)
self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE)
self.assertIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(FED_1))
self.assertIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(MED_1))
self.assertIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(SED_1))
self.flush_all()
# Turn off Router 1.1 and turn on Router 1.2
self.nodes[ROUTER_1_1].stop()
self.nodes[ROUTER_1_2].start()
for id in [FED_1, MED_1, SED_1]:
self.simulator.go(config.DEFAULT_CHILD_TIMEOUT + WAIT_REDUNDANCE)
self.assertEqual(self.nodes[id].get_state(), 'child')
self.assertEqual(self.nodes[id].get_router_id(),
self.nodes[ROUTER_1_2].get_router_id())
self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE)
# Verify only FTD sends MLR.req within REREG_DELAY when parent is 1.2
self.assertIn(ipaddress.IPv6Address(MA1),
self.__get_registered_MAs(FED_1))
# MED and SED might still send MLR.req during this period because it could be sending to it's 1.2 parent.
self.nodes[FED_1].del_ipmaddr(MA1)
self.nodes[MED_1].del_ipmaddr(MA1)
self.nodes[SED_1].del_ipmaddr(MA1)
self.simulator.go(WAIT_REDUNDANCE)
if __name__ == '__main__':
unittest.main()