[firewall] ingress filtering (#7107)

This commit implements OTBR firewall. This implementation focuses on
ingress filtering. We may also introduce egress filtering when
necessary.

For security purpose, there are some packet forwarding rules to
follow, which were originally introduced in the spec.

- Inbound packets initiated with On-Link addresses source (OMR and
  mesh local prefix based addresses) should be blocked.

- Inbound unicast packets whose destination address is not OMR address
  or DUA should be blocked.

- Inbound unicast packets whose source address or destination address
  is link-local should be blocked. Note that we don’t need to
  explicitly add rules for link-local addresses since this should
  already be handled by the kernel.

These rules can be easily implemented by iptables and ipset.

Before otbr-agent starts, there is a script creating the iptables
rules. The rules themselves are constant so we don't need to change
them dynamically. During the runtime of otbr-agent, otbr-agent updates
ipsets accordingly whenever there's a change of on-link prefixes.
This commit is contained in:
whd
2021-12-13 09:17:21 -08:00
committed by GitHub
parent b8df628dfb
commit 13870f5e57
17 changed files with 676 additions and 3 deletions
+2
View File
@@ -369,6 +369,7 @@ LOCAL_SRC_FILES := \
src/posix/platform/backbone.cpp \
src/posix/platform/daemon.cpp \
src/posix/platform/entropy.cpp \
src/posix/platform/firewall.cpp \
src/posix/platform/hdlc_interface.cpp \
src/posix/platform/infra_if.cpp \
src/posix/platform/logging.cpp \
@@ -384,6 +385,7 @@ LOCAL_SRC_FILES := \
src/posix/platform/system.cpp \
src/posix/platform/trel_udp6.cpp \
src/posix/platform/udp.cpp \
src/posix/platform/utils.cpp \
third_party/mbedtls/repo/library/aes.c \
third_party/mbedtls/repo/library/aesni.c \
third_party/mbedtls/repo/library/arc4.c \
+2
View File
@@ -246,6 +246,8 @@ do_cert_suite()
export PYTHONPATH=tests/scripts/thread-cert
export VIRTUAL_TIME
sudo modprobe ip6table_filter
python3 tests/scripts/thread-cert/run_cert_suite.py --multiply "${MULTIPLY:-1}" "$@"
exit 0
}
+2
View File
@@ -78,6 +78,7 @@ add_library(openthread-posix
backbone.cpp
daemon.cpp
entropy.cpp
firewall.cpp
hdlc_interface.cpp
infra_if.cpp
logging.cpp
@@ -93,6 +94,7 @@ add_library(openthread-posix
system.cpp
trel_udp6.cpp
udp.cpp
utils.cpp
virtual_time.cpp
)
+2
View File
@@ -48,6 +48,7 @@ libopenthread_posix_a_SOURCES = \
backbone.cpp \
daemon.cpp \
entropy.cpp \
firewall.cpp \
hdlc_interface.cpp \
infra_if.cpp \
logging.cpp \
@@ -63,6 +64,7 @@ libopenthread_posix_a_SOURCES = \
system.cpp \
trel_udp6.cpp \
udp.cpp \
utils.cpp \
virtual_time.cpp \
$(NULL)
+132
View File
@@ -0,0 +1,132 @@
/*
* Copyright (c) 2021, 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.
*/
/**
* @file
* @brief
* This file includes the implementation of OTBR firewall.
*/
#include "firewall.hpp"
#include <string.h>
#include "common/code_utils.hpp"
#include "openthread/netdata.h"
#include "posix/platform/utils.hpp"
namespace ot {
namespace Posix {
#if defined(__linux__) && OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
#if !OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || !OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
#error Configurations 'OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE' and 'OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE' are required.
#endif
static const char kIpsetCommand[] = OPENTHREAD_POSIX_CONFIG_IPSET_BINARY;
static const char kIngressDenySrcIpSet[] = "otbr-ingress-deny-src";
static const char kIngressDenySrcSwapIpSet[] = "otbr-ingress-deny-src-swap";
static const char kIngressAllowDstIpSet[] = "otbr-ingress-allow-dst";
static const char kIngressAllowDstSwapIpSet[] = "otbr-ingress-allow-dst-swap";
class IpSetManager
{
public:
otError FlushIpSet(const char *aName);
otError AddToIpSet(const char *aSetName, const char *aAddress);
otError SwapIpSets(const char *aSetName1, const char *aSetName2);
};
inline otError IpSetManager::FlushIpSet(const char *aName)
{
return ExecuteCommand("%s flush %s", kIpsetCommand, aName);
}
inline otError IpSetManager::AddToIpSet(const char *aSetName, const char *aAddress)
{
return ExecuteCommand("%s add %s %s -exist", kIpsetCommand, aSetName, aAddress);
}
inline otError IpSetManager::SwapIpSets(const char *aSetName1, const char *aSetName2)
{
return ExecuteCommand("%s swap %s %s", kIpsetCommand, aSetName1, aSetName2);
}
void UpdateIpSets(otInstance *aInstance)
{
otError error = OT_ERROR_NONE;
otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
otBorderRouterConfig config;
otIp6Prefix prefix;
char prefixBuf[OT_IP6_PREFIX_STRING_SIZE];
IpSetManager ipSetManager;
// 1. Flush the '*-swap' ipsets
SuccessOrExit(error = ipSetManager.FlushIpSet(kIngressAllowDstSwapIpSet));
SuccessOrExit(error = ipSetManager.FlushIpSet(kIngressDenySrcSwapIpSet));
// 2. Update otbr-deny-src-swap
while (otNetDataGetNextOnMeshPrefix(aInstance, &iterator, &config) == OT_ERROR_NONE)
{
if (config.mDp)
{
continue;
}
otIp6PrefixToString(&config.mPrefix, prefixBuf, sizeof(prefixBuf));
SuccessOrExit(error = ipSetManager.AddToIpSet(kIngressDenySrcSwapIpSet, prefixBuf));
}
memcpy(prefix.mPrefix.mFields.m8, otThreadGetMeshLocalPrefix(aInstance)->m8,
sizeof(otThreadGetMeshLocalPrefix(aInstance)->m8));
prefix.mLength = OT_IP6_PREFIX_BITSIZE;
otIp6PrefixToString(&prefix, prefixBuf, sizeof(prefixBuf));
SuccessOrExit(error = ipSetManager.AddToIpSet(kIngressDenySrcSwapIpSet, prefixBuf));
// 3. Update otbr-allow-dst-swap
iterator = OT_NETWORK_DATA_ITERATOR_INIT;
while (otNetDataGetNextOnMeshPrefix(aInstance, &iterator, &config) == OT_ERROR_NONE)
{
otIp6PrefixToString(&config.mPrefix, prefixBuf, sizeof(prefixBuf));
SuccessOrExit(error = ipSetManager.AddToIpSet(kIngressAllowDstSwapIpSet, prefixBuf));
}
// 4. Swap ipsets to let them take effect
SuccessOrExit(error = ipSetManager.SwapIpSets(kIngressDenySrcSwapIpSet, kIngressDenySrcIpSet));
SuccessOrExit(error = ipSetManager.SwapIpSets(kIngressAllowDstSwapIpSet, kIngressAllowDstIpSet));
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnPlat("Failed to update ipsets: %s", otThreadErrorToString(error));
}
}
#endif // defined(__linux__) && OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
} // namespace Posix
} // namespace ot
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2021, 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.
*/
#ifndef OT_POSIX_PLATFORM_FIREWALL_HPP_
#define OT_POSIX_PLATFORM_FIREWALL_HPP_
#include "posix/platform/openthread-posix-config.h"
#if OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
#include "common/logging.hpp"
#include "openthread/thread.h"
namespace ot {
namespace Posix {
void UpdateIpSets(otInstance *aInstance);
} // namespace Posix
} // namespace ot
#endif // OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
#endif // OT_POSIX_PLATFORM_FIREWALL_HPP_
+9
View File
@@ -163,6 +163,9 @@ unsigned int otSysGetThreadNetifIndex(void)
}
#if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
#if OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
#include "firewall.hpp"
#endif
#include "posix/platform/ip6_utils.hpp"
using namespace ot::Posix::Ip6Utils;
@@ -742,6 +745,12 @@ void platformNetifStateChange(otInstance *aInstance, otChangedFlags aFlags)
UpdateExternalRoutes(aInstance);
}
#endif
#if OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
if (OT_CHANGED_THREAD_NETDATA & aFlags)
{
ot::Posix::UpdateIpSets(aInstance);
}
#endif
}
static void processReceive(otMessage *aMessage, void *aContext)
@@ -188,6 +188,38 @@
#define OPENTHREAD_POSIX_CONFIG_MAX_EXTERNAL_ROUTE_NUM 8
#endif
/**
* @def OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
*
* Define as 1 to enable firewall.
*
* The rules are implemented using ip6tables and ipset. The rules are as follows.
*
* ip6tables -A $OTBR_FORWARD_INGRESS_CHAIN -m pkttype --pkt-type unicast -i $THREAD_IF -p ip -j DROP
* ip6tables -A $OTBR_FORWARD_INGRESS_CHAIN -m set --match-set otbr-ingress-deny-src src -p ip -j DROP
* ip6tables -A $OTBR_FORWARD_INGRESS_CHAIN -m set --match-set otbr-ingress-allow-dst dst -p ip -j ACCEPT
* ip6tables -A $OTBR_FORWARD_INGRESS_CHAIN -m pkttype --pkt-type unicast -p ip -j DROP
* ip6tables -A $OTBR_FORWARD_INGRESS_CHAIN -p ip -j ACCEPT
*
*/
#ifdef __linux__
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE & OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
#ifndef OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
#define OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE 1
#endif
#endif
#endif
#ifndef OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
#define OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE 0
#endif
#if OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
#ifndef OPENTHREAD_POSIX_CONFIG_IPSET_BINARY
#define OPENTHREAD_POSIX_CONFIG_IPSET_BINARY "ipset"
#endif
#endif
#ifdef __APPLE__
/**
+1
View File
@@ -49,6 +49,7 @@
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "posix/platform/daemon.hpp"
#include "posix/platform/firewall.hpp"
#include "posix/platform/infra_if.hpp"
#include "posix/platform/mainloop.hpp"
#include "posix/platform/radio_url.hpp"
+88
View File
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2021, 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.
*/
/**
* @file
* The file implements POSIX system utilities.
*/
#include "utils.hpp"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "common/code_utils.hpp"
#include "common/logging.hpp"
namespace ot {
namespace Posix {
enum
{
kSystemCommandMaxLength = 1024, ///< Max length of a system call command.
kOutputBufferSize = 1024, ///< Buffer size of command output.
};
otError ExecuteCommand(const char *aFormat, ...)
{
char cmd[kSystemCommandMaxLength];
char buf[kOutputBufferSize];
va_list args;
FILE * file;
int exitCode;
otError error = OT_ERROR_NONE;
va_start(args, aFormat);
vsnprintf(cmd, sizeof(cmd), aFormat, args);
va_end(args);
file = popen(cmd, "r");
VerifyOrExit(file != nullptr, error = OT_ERROR_FAILED);
while (fgets(buf, sizeof(buf), file))
{
size_t length = strlen(buf);
if (buf[length] == '\n')
{
buf[length] = '\0';
}
otLogInfoPlat("%s", buf);
}
exitCode = pclose(file);
otLogInfoPlat("Execute command `%s` = %d", cmd, exitCode);
VerifyOrExit(exitCode == 0, error = OT_ERROR_FAILED);
exit:
if (error != OT_ERROR_NONE && errno != 0)
{
otLogInfoPlat("Got an error when executing command `%s`: `%s`", cmd, strerror(errno));
}
return error;
}
} // namespace Posix
} // namespace ot
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2021, 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.
*/
#ifndef OT_POSIX_PLATFORM_UTILS_HPP_
#define OT_POSIX_PLATFORM_UTILS_HPP_
#include "openthread/error.h"
namespace ot {
namespace Posix {
/**
* This method formats a system command to execute.
*
* @param[in] aFormat A pointer to the format string.
* @param[in] ... Arguments for the format specification.
*
* @retval OT_ERROR_NONE The command was executed successfully.
* @retval OT_ERROR_FAILED It failed to execute the command.
*
*/
otError ExecuteCommand(const char *aFormat, ...);
} // namespace Posix
} // namespace ot
#endif // OT_POSIX_PLATFORM_UTILS_HPP_
@@ -0,0 +1,269 @@
#!/usr/bin/env python3
#
# Copyright (c) 2021, 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 logging
import unittest
import ipaddress
import config
import pktverify
import pktverify.packet_verifier
from pktverify.consts import MA1
import thread_cert
# Test description:
# This test verifies the functionality of firewall. OTBR will only
# forward specific kinds of packets between the infra interface and the thread
# interface.
#
# Topology:
# ----------------(eth)----------------------
# | |
# BR1 (Leader) HOST
# |
# ROUTER1
BR1 = 1
ROUTER1 = 2
HOST = 3
class Firewall(thread_cert.TestCase):
USE_MESSAGE_FACTORY = False
TOPOLOGY = {
BR1: {
'name': 'BR_1',
'allowlist': [ROUTER1],
'is_otbr': True,
'version': '1.2',
},
ROUTER1: {
'name': 'Router_1',
'allowlist': [BR1],
'version': '1.2',
},
HOST: {
'name': 'Host',
'is_host': True,
}
}
def test(self):
br1 = self.nodes[BR1]
self.br1 = br1
router1 = self.nodes[ROUTER1]
host = self.nodes[HOST]
br1.start()
self.simulator.go(5)
self.assertEqual('leader', br1.get_state())
router1.start()
host.start(start_radvd=True)
self.simulator.go(5)
self.assertEqual('router', router1.get_state())
br1.set_domain_prefix(config.DOMAIN_PREFIX, 'prosD')
br1.register_netdata()
router1.add_ipmaddr(MA1)
router1.register_netdata()
self.simulator.go(20)
def host_ping_ether(dest, interface, ttl=10, add_interface=False, add_route=False, gateway=None):
if add_interface:
host.bash(f'ip -6 addr add {interface}/64 dev {host.ETH_DEV} scope global')
route = str(ipaddress.IPv6Network(f'{interface}/64', strict=False))
host.bash(f'ip -6 route del {route} dev {host.ETH_DEV}')
if add_route:
host.bash(f'ip -6 route add {dest} dev {host.ETH_DEV} via {gateway}')
self.simulator.go(2)
result = host.ping_ether(dest, ttl=ttl, interface=interface)
if add_route:
host.bash(f'ip -6 route del {dest}')
if add_interface:
host.bash(f'ip -6 addr del {interface}/64 dev {host.ETH_DEV} scope global')
self.simulator.go(1)
return result
# 1. Host pings router1's OMR from host's infra address.
self.assertTrue(host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], interface=host.ETH_DEV))
# 2. Host pings router1's DUA from host's infra address.
self.assertTrue(host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.DUA), interface=host.ETH_DEV))
# 3. Host pings router1's OMR from router1's RLOC.
self.assertFalse(
host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
interface=router1.get_rloc(),
add_interface=True))
# 4. Host pings router1's OMR from BR1's OMR.
self.assertFalse(
host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
interface=br1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
add_interface=True))
# 5. Host pings router1's OMR from router1's MLE-ID.
self.assertFalse(
host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
interface=router1.get_mleid(),
add_interface=True))
host.bash(f'ip -6 route add {config.MESH_LOCAL_PREFIX} dev {host.ETH_DEV}')
self.simulator.go(5)
# 6. Host pings router1's RLOC from host's ULA address.
self.assertFalse(
host_ping_ether(router1.get_rloc(),
interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0],
add_route=True,
gateway=br1.get_ip6_address(config.ADDRESS_TYPE.BACKBONE_GUA)))
# 7. Host pings router1's MLE-ID from host's ULA address.
self.assertFalse(
host_ping_ether(router1.get_mleid(),
interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0],
add_route=True,
gateway=br1.get_ip6_address(config.ADDRESS_TYPE.BACKBONE_GUA)))
# 8. Host pings router1's link-local address from host's infra address.
self.assertFalse(
host_ping_ether(router1.get_linklocal(),
interface=host.ETH_DEV,
add_route=True,
gateway=br1.get_ip6_address(config.ADDRESS_TYPE.BACKBONE_GUA)))
# 9. Host pings MA1 from host's ULA address.
self.assertTrue(host_ping_ether(MA1, ttl=10,
interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
# 10. Host pings MA1 from router1's RLOC.
self.assertFalse(host_ping_ether(MA1, ttl=10, interface=router1.get_rloc(), add_interface=True))
# 11. Host pings MA1 from router1's OMR.
self.assertFalse(
host_ping_ether(MA1,
ttl=10,
interface=router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
add_interface=True))
# 12. Host pings MA1 from router1's MLE-ID.
self.assertFalse(host_ping_ether(MA1, ttl=10, interface=router1.get_mleid(), add_interface=True))
self.collect_ipaddrs()
self.collect_rlocs()
self.collect_rloc16s()
self.collect_extra_vars()
self.collect_omrs()
self.collect_duas()
def verify(self, pv: pktverify.packet_verifier.PacketVerifier):
pkts = pv.pkts
vars = pv.vars
pv.summary.show()
logging.info(f'vars = {vars}')
pv.verify_attached('Router_1', 'BR_1')
# 1. Host pings router1's OMR from host's infra address.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
vars['Router_1_OMR'][0]).filter_ping_request().must_next()
pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_next()
# 2. Host pings router1's DUA from host's infra address.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
vars['Router_1_DUA']).filter_ping_request().must_next()
pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_next()
# 3. Host pings router1's OMR from router1's RLOC.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(
vars['Router_1_RLOC'], vars['Router_1_OMR'][0]).filter_ping_request().must_next()
pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
# 4. Host pings router1's OMR from BR1's OMR.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(
vars['BR_1_OMR'][0], vars['Router_1_OMR'][0]).filter_ping_request().must_next()
pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst64(
vars['Router_1']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
# 5. Host pings router1's OMR from router1's MLE-ID.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(
vars['Router_1_MLEID'], vars['Router_1_OMR'][0]).filter_ping_request().must_next()
pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
# 6. Host pings router1's RLOC from host's ULA address.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
vars['Router_1_RLOC']).filter_ping_request().must_next()
pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
# 7. Host pings router1's MLE-ID from host's ULA address.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
vars['Router_1_MLEID']).filter_ping_request().must_next()
pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
# 8. Host pings router1's link-local address from host's infra address.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
vars['Router_1_LLA']).filter_ping_request().must_next()
pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
# 9. Host pings MA1 from host's ULA address.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(MA1).filter_ping_request().must_next()
pkts.filter_wpan_src64(
vars['BR_1']).filter_AMPLFMA().filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_next()
# 10. Host pings MA1 from router1's RLOC.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(vars['Router_1_RLOC'],
MA1).filter_ping_request().must_next()
pkts.filter_wpan_src64(
vars['BR_1']).filter_AMPLFMA().filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
# 11. Host pings MA1 from router1's OMR.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(vars['Router_1_OMR'][0],
MA1).filter_ping_request().must_next()
pkts.filter_wpan_src64(
vars['BR_1']).filter_AMPLFMA().filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
# 12. Host pings MA1 from router1's MLE-ID.
_pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(vars['Router_1_MLEID'],
MA1).filter_ping_request().must_next()
pkts.filter_wpan_src64(
vars['BR_1']).filter_AMPLFMA().filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
if __name__ == '__main__':
unittest.main()
+1 -1
View File
@@ -1719,7 +1719,7 @@ class NodeImpl:
omr_addrs = []
for addr in self.get_addrs():
for prefix in prefixes:
if (addr.startswith(prefix)):
if (addr.startswith(prefix)) and (addr != self.__getDua()):
omr_addrs.append(addr)
break
@@ -157,6 +157,14 @@ class PacketVerifier(object):
key = self.test_info.get_node_name(i) + '_RLOC'
self._vars[key] = rloc
for i, omr in self.test_info.omrs.items():
key = self.test_info.get_node_name(i) + '_OMR'
self._vars[key] = omr
for i, dua in self.test_info.duas.items():
key = self.test_info.get_node_name(i) + '_DUA'
self._vars[key] = dua
if self.test_info.leader_aloc:
self._vars['LEADER_ALOC'] = self.test_info.leader_aloc
@@ -53,6 +53,8 @@ class TestInfo(object):
self.mleids = {int(k): Ipv6Addr(v) for k, v in test_info.get('mleids', {}).items()}
self.rlocs = {int(k): Ipv6Addr(v) for k, v in test_info.get('rlocs', {}).items()}
self.rloc16s = self._convert_hex_values(self._convert_keys_to_ints(test_info.get('rloc16s', {})))
self.omrs = {int(k): [Ipv6Addr(x) for x in l] for k, l in test_info.get('omrs', {}).items()}
self.duas = {int(k): Ipv6Addr(v) for k, v in test_info.get('duas', {}).items()}
self.extra_vars = test_info.get('extra_vars', {})
self.leader_aloc = Ipv6Addr(test_info.get('leader_aloc')) if 'leader_aloc' in test_info else ''
@@ -94,8 +94,6 @@ def cleanup_backbone_env():
def setup_backbone_env():
bash('sudo modprobe ip6table_filter')
if THREAD_VERSION != '1.2':
raise RuntimeError('Backbone tests only work with THREAD_VERSION=1.2')
+26
View File
@@ -361,6 +361,32 @@ class TestCase(NcpSupportMixin, unittest.TestCase):
test_info['rlocs'][i] = node.get_rloc()
def collect_omrs(self):
if not self._do_packet_verification:
return
test_info = self._test_info
test_info['omrs'] = {}
for i, node in self.nodes.items():
if node.is_host:
continue
test_info['omrs'][i] = node.get_ip6_address(config.ADDRESS_TYPE.OMR)
def collect_duas(self):
if not self._do_packet_verification:
return
test_info = self._test_info
test_info['duas'] = {}
for i, node in self.nodes.items():
if node.is_host:
continue
test_info['duas'][i] = node.get_ip6_address(config.ADDRESS_TYPE.DUA)
def collect_leader_aloc(self, node):
if not self._do_packet_verification:
return