mirror of
https://github.com/espressif/openthread.git
synced 2026-07-29 15:17:47 +00:00
[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:
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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__
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user