mirror of
https://github.com/espressif/openthread.git
synced 2026-08-30 22:09:54 +00:00
[firewall] implement packet filtering in OT core (#9402)
This commit implements the packet logic in OT core. It aims to have the same effect as what's already achieved by our iptables-based firewall. Instead of leveraging iptables, this commit filters the border routing packets in user space by checking the source/destination addresses of a packet. This commit also adds a job to do BR regression test when this feature is enabled and iptables-based firewall is disabled.
This commit is contained in:
@@ -156,6 +156,13 @@ jobs:
|
||||
packet_verification: 2
|
||||
nat64: 0
|
||||
description: ""
|
||||
- otbr_mdns: "avahi"
|
||||
otbr_trel: 0
|
||||
cert_scripts: ./tests/scripts/thread-cert/border_router/*.py
|
||||
packet_verification: 1
|
||||
nat64: 0
|
||||
use_core_firewall: 1
|
||||
description: "core firewall"
|
||||
name: BR ${{ matrix.description }} (${{ matrix.otbr_mdns }}, TREL=${{matrix.otbr_trel}})
|
||||
env:
|
||||
REFERENCE_DEVICE: 1
|
||||
@@ -173,6 +180,10 @@ jobs:
|
||||
MAX_JOBS: 3
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
- name: Set firewall environment variables
|
||||
if: ${{ matrix.use_core_firewall }}
|
||||
run: |
|
||||
echo "FIREWALL=0" >> $GITHUB_ENV
|
||||
- name: Build OTBR Docker
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
@@ -342,6 +342,7 @@ do_build_otbr_docker()
|
||||
"REST_API=0"
|
||||
"WEB_GUI=0"
|
||||
"MDNS=${OTBR_MDNS:-mDNSResponder}"
|
||||
"FIREWALL=${FIREWALL:-1}"
|
||||
)
|
||||
|
||||
if [[ ${NAT64} != 1 ]]; then
|
||||
|
||||
+24
-2
@@ -847,7 +847,7 @@ Error Ip6::HandleExtensionHeaders(Message &aMessage,
|
||||
|
||||
case kProtoFragment:
|
||||
IgnoreError(PassToHost(aMessage, aOrigin, aMessageInfo, aNextHeader,
|
||||
/* aApplyFilter */ false, Message::kCopyToUse));
|
||||
/* aApplyFilter */ false, aReceive, Message::kCopyToUse));
|
||||
SuccessOrExit(error = HandleFragment(aMessage, aOrigin, aMessageInfo));
|
||||
break;
|
||||
|
||||
@@ -947,6 +947,7 @@ Error Ip6::PassToHost(Message &aMessage,
|
||||
const MessageInfo &aMessageInfo,
|
||||
uint8_t aIpProto,
|
||||
bool aApplyFilter,
|
||||
bool aReceive,
|
||||
Message::Ownership aMessageOwnership)
|
||||
{
|
||||
// This method passes the message to host by invoking the
|
||||
@@ -977,6 +978,13 @@ Error Ip6::PassToHost(Message &aMessage,
|
||||
// Do not pass IPv6 packets that exceed kMinimalMtu.
|
||||
VerifyOrExit(aMessage.GetLength() <= kMinimalMtu, error = kErrorDrop);
|
||||
|
||||
// If the sender used mesh-local address as source, do not pass to
|
||||
// host unless this message is intended for this device itself.
|
||||
if (Get<Mle::Mle>().IsMeshLocalAddress(aMessageInfo.GetPeerAddr()))
|
||||
{
|
||||
VerifyOrExit(aReceive, error = kErrorDrop);
|
||||
}
|
||||
|
||||
if (mIsReceiveIp6FilterEnabled && aApplyFilter)
|
||||
{
|
||||
#if !OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
|
||||
@@ -1091,6 +1099,20 @@ Error Ip6::SendRaw(Message &aMessage, bool aAllowLoopBackToHost)
|
||||
SuccessOrExit(error = header.ParseFrom(aMessage));
|
||||
VerifyOrExit(!header.GetSource().IsMulticast(), error = kErrorInvalidSourceAddress);
|
||||
|
||||
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
|
||||
// The filtering rules don't apply to packets from DUA.
|
||||
if (!Get<BackboneRouter::Leader>().IsDomainUnicast(header.GetSource()))
|
||||
#endif
|
||||
{
|
||||
// When the packet is forwarded from host to Thread, if its source is on-mesh or its destination is
|
||||
// mesh-local, we'll drop the packet unless the packet originates from this device.
|
||||
if (Get<NetworkData::Leader>().IsOnMesh(header.GetSource()) ||
|
||||
Get<Mle::Mle>().IsMeshLocalAddress(header.GetDestination()))
|
||||
{
|
||||
VerifyOrExit(Get<ThreadNetif>().HasUnicastAddress(header.GetSource()), error = kErrorDrop);
|
||||
}
|
||||
}
|
||||
|
||||
if (header.GetDestination().IsMulticast())
|
||||
{
|
||||
SuccessOrExit(error = InsertMplOption(aMessage, header));
|
||||
@@ -1217,7 +1239,7 @@ start:
|
||||
if ((forwardHost || receive) && !aIsReassembled)
|
||||
{
|
||||
error = PassToHost(aMessage, aOrigin, messageInfo, nextHeader,
|
||||
/* aApplyFilter */ !forwardHost,
|
||||
/* aApplyFilter */ !forwardHost, receive,
|
||||
(receive || forwardThread) ? Message::kCopyToUse : Message::kTakeCustody);
|
||||
|
||||
// Need to free the message if we did not pass its
|
||||
|
||||
@@ -391,6 +391,7 @@ private:
|
||||
const MessageInfo &aMessageInfo,
|
||||
uint8_t aIpProto,
|
||||
bool aApplyFilter,
|
||||
bool aReceive,
|
||||
Message::Ownership aMessageOwnership);
|
||||
Error HandleExtensionHeaders(Message &aMessage,
|
||||
MessageOrigin aOrigin,
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
import logging
|
||||
import time
|
||||
import unittest
|
||||
import ipaddress
|
||||
|
||||
@@ -45,12 +46,13 @@ import thread_cert
|
||||
# ----------------(eth)----------------------
|
||||
# | |
|
||||
# BR1 (Leader) HOST
|
||||
# |
|
||||
# ROUTER1
|
||||
# | \
|
||||
# ROUTER1 ROUTER2
|
||||
|
||||
BR1 = 1
|
||||
ROUTER1 = 2
|
||||
HOST = 3
|
||||
ROUTER2 = 3
|
||||
HOST = 4
|
||||
|
||||
|
||||
class Firewall(thread_cert.TestCase):
|
||||
@@ -59,14 +61,19 @@ class Firewall(thread_cert.TestCase):
|
||||
TOPOLOGY = {
|
||||
BR1: {
|
||||
'name': 'BR_1',
|
||||
'allowlist': [ROUTER1],
|
||||
'allowlist': [ROUTER1, ROUTER2],
|
||||
'is_otbr': True,
|
||||
'version': '1.2',
|
||||
'version': '1.3',
|
||||
},
|
||||
ROUTER1: {
|
||||
'name': 'Router_1',
|
||||
'allowlist': [BR1],
|
||||
'version': '1.2',
|
||||
'version': '1.3',
|
||||
},
|
||||
ROUTER2: {
|
||||
'name': 'Router_2',
|
||||
'allowlist': [BR1],
|
||||
'version': '1.3',
|
||||
},
|
||||
HOST: {
|
||||
'name': 'Host',
|
||||
@@ -78,16 +85,20 @@ class Firewall(thread_cert.TestCase):
|
||||
br1 = self.nodes[BR1]
|
||||
self.br1 = br1
|
||||
router1 = self.nodes[ROUTER1]
|
||||
router2 = self.nodes[ROUTER2]
|
||||
host = self.nodes[HOST]
|
||||
|
||||
br1.start()
|
||||
self.simulator.go(config.LEADER_STARTUP_DELAY)
|
||||
self.assertEqual('leader', br1.get_state())
|
||||
br1.set_log_level(5)
|
||||
|
||||
router1.start()
|
||||
router2.start()
|
||||
host.start(start_radvd=True)
|
||||
self.simulator.go(config.ROUTER_STARTUP_DELAY)
|
||||
self.assertEqual('router', router1.get_state())
|
||||
self.assertEqual('router', router2.get_state())
|
||||
|
||||
br1.set_domain_prefix(config.DOMAIN_PREFIX, 'prosD')
|
||||
br1.register_netdata()
|
||||
@@ -125,13 +136,13 @@ class Firewall(thread_cert.TestCase):
|
||||
interface=router1.get_rloc(),
|
||||
add_interface=True))
|
||||
|
||||
# 4. Host pings router1's OMR from BR1's OMR.
|
||||
# 4. Host pings router1's OMR from router2'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],
|
||||
interface=router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
|
||||
add_interface=True))
|
||||
|
||||
# 5. Host pings router1's OMR from router1's MLE-ID.
|
||||
# 5. Host pings router1's OMR from router1's ML-EID.
|
||||
self.assertFalse(
|
||||
host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
|
||||
interface=router1.get_mleid(),
|
||||
@@ -147,7 +158,7 @@ class Firewall(thread_cert.TestCase):
|
||||
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.
|
||||
# 7. Host pings router1's ML-EID from host's ULA address.
|
||||
self.assertFalse(
|
||||
host_ping_ether(router1.get_mleid(),
|
||||
interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0],
|
||||
@@ -175,13 +186,21 @@ class Firewall(thread_cert.TestCase):
|
||||
interface=router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
|
||||
add_interface=True))
|
||||
|
||||
# 12. Host pings MA1 from router1's MLE-ID.
|
||||
# 12. Host pings MA1 from router1's ML-EID.
|
||||
self.assertFalse(host_ping_ether(MA1, ttl=10, interface=router1.get_mleid(), add_interface=True))
|
||||
|
||||
# 13. Router1 pings Host from router1's MLE-ID.
|
||||
# 13. Router1 pings Host from router1's ML-EID.
|
||||
self.assertFalse(
|
||||
router1.ping(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0], interface=router1.get_mleid()))
|
||||
|
||||
# 14. BR pings router1's ML-EID from BR's ML-EID.
|
||||
self.assertTrue(br1.ping_ether(router1.get_mleid(), interface=br1.get_mleid()))
|
||||
|
||||
# 15. BR pings router1's OMR from BR's infra interface.
|
||||
self.assertTrue(
|
||||
br1.ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
|
||||
interface=br1.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
|
||||
|
||||
self.collect_ipaddrs()
|
||||
self.collect_rlocs()
|
||||
self.collect_rloc16s()
|
||||
@@ -215,13 +234,13 @@ class Firewall(thread_cert.TestCase):
|
||||
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.
|
||||
# 4. Host pings router1's OMR from router2'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()
|
||||
vars['Router_2_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.
|
||||
# 5. Host pings router1's OMR from router1's ML-EID.
|
||||
_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(
|
||||
@@ -233,7 +252,7 @@ class Firewall(thread_cert.TestCase):
|
||||
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.
|
||||
# 7. Host pings router1's ML-EID 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(
|
||||
@@ -263,16 +282,25 @@ class Firewall(thread_cert.TestCase):
|
||||
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.
|
||||
# 12. Host pings MA1 from router1's ML-EID.
|
||||
_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()
|
||||
|
||||
# 13. Router1 pings Host from router1's MLE-ID.
|
||||
# 13. Router1 pings Host from router1's ML-EID.
|
||||
pkts.filter_eth_src(vars['BR_1_ETH']).filter_ipv6_src_dst(
|
||||
vars['Router_1_MLEID'], vars['Host_BGUA']).filter_ping_request().must_not_next()
|
||||
|
||||
# 14. BR pings router1's ML-EID from BR's infra interface.
|
||||
_pkt = pkts.filter_wpan_src64(vars['BR_1']).filter_ipv6_src_dst(
|
||||
vars['BR_1_MLEID'], vars['Router_1_MLEID']).filter_ping_request().must_next()
|
||||
pkts.filter_wpan_src64(vars['Router_1']).filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).must_next()
|
||||
|
||||
# 15. BR pings router1's OMR from BR's infra interface.
|
||||
_pkt = pkts.filter_wpan_src64(vars['BR_1']).filter_ping_request().must_next()
|
||||
pkts.filter_wpan_src64(vars['Router_1']).filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).must_next()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -3630,6 +3630,24 @@ class LinuxHost():
|
||||
|
||||
return resp_count
|
||||
|
||||
def get_ip6_address(self, address_type: config.ADDRESS_TYPE):
|
||||
"""Get specific type of IPv6 address configured on thread device.
|
||||
|
||||
Args:
|
||||
address_type: the config.ADDRESS_TYPE type of IPv6 address.
|
||||
|
||||
Returns:
|
||||
IPv6 address string.
|
||||
"""
|
||||
if address_type == config.ADDRESS_TYPE.BACKBONE_GUA:
|
||||
return self._getBackboneGua()
|
||||
elif address_type == config.ADDRESS_TYPE.ONLINK_ULA:
|
||||
return self._getInfraUla()
|
||||
elif address_type == config.ADDRESS_TYPE.ONLINK_GUA:
|
||||
return self._getInfraGua()
|
||||
else:
|
||||
raise ValueError(f'unsupported address type: {address_type}')
|
||||
|
||||
def _getBackboneGua(self) -> Optional[str]:
|
||||
for addr in self.get_ether_addrs():
|
||||
if re.match(config.BACKBONE_PREFIX_REGEX_PATTERN, addr, re.I):
|
||||
@@ -3885,6 +3903,12 @@ class OtbrNode(LinuxHost, NodeImpl, OtbrDocker):
|
||||
cmd = f'python3 /app/third_party/openthread/repo/tests/scripts/thread-cert/mcast6.py {self.TUN_DEV} {ip} &'
|
||||
self.bash(cmd)
|
||||
|
||||
def get_ip6_address(self, address_type: config.ADDRESS_TYPE):
|
||||
try:
|
||||
return super(OtbrNode, self).get_ip6_address(address_type)
|
||||
except Exception as e:
|
||||
return super(LinuxHost, self).get_ip6_address(address_type)
|
||||
|
||||
|
||||
class HostNode(LinuxHost, OtbrDocker):
|
||||
is_host = True
|
||||
@@ -3922,25 +3946,6 @@ class HostNode(LinuxHost, OtbrDocker):
|
||||
|
||||
return addrs
|
||||
|
||||
def get_ip6_address(self, address_type: config.ADDRESS_TYPE):
|
||||
"""Get specific type of IPv6 address configured on thread device.
|
||||
|
||||
Args:
|
||||
address_type: the config.ADDRESS_TYPE type of IPv6 address.
|
||||
|
||||
Returns:
|
||||
IPv6 address string.
|
||||
"""
|
||||
|
||||
if address_type == config.ADDRESS_TYPE.BACKBONE_GUA:
|
||||
return self._getBackboneGua()
|
||||
elif address_type == config.ADDRESS_TYPE.ONLINK_ULA:
|
||||
return self._getInfraUla()
|
||||
elif address_type == config.ADDRESS_TYPE.ONLINK_GUA:
|
||||
return self._getInfraGua()
|
||||
else:
|
||||
raise ValueError(f'unsupported address type: {address_type}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user