diff --git a/.github/workflows/otbr.yml b/.github/workflows/otbr.yml index c34548dcf..b4c185d10 100644 --- a/.github/workflows/otbr.yml +++ b/.github/workflows/otbr.yml @@ -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 }}" diff --git a/script/test b/script/test index 3e53df1b2..9629dd2f9 100755 --- a/script/test +++ b/script/test @@ -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 diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp index b6693d2af..487093a66 100644 --- a/src/core/net/ip6.cpp +++ b/src/core/net/ip6.cpp @@ -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().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().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().IsOnMesh(header.GetSource()) || + Get().IsMeshLocalAddress(header.GetDestination())) + { + VerifyOrExit(Get().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 diff --git a/src/core/net/ip6.hpp b/src/core/net/ip6.hpp index f3e14a8ef..9fc5bd5f3 100644 --- a/src/core/net/ip6.hpp +++ b/src/core/net/ip6.hpp @@ -391,6 +391,7 @@ private: const MessageInfo &aMessageInfo, uint8_t aIpProto, bool aApplyFilter, + bool aReceive, Message::Ownership aMessageOwnership); Error HandleExtensionHeaders(Message &aMessage, MessageOrigin aOrigin, diff --git a/tests/scripts/thread-cert/border_router/test_firewall.py b/tests/scripts/thread-cert/border_router/test_firewall.py index 7201035dd..443da698d 100755 --- a/tests/scripts/thread-cert/border_router/test_firewall.py +++ b/tests/scripts/thread-cert/border_router/test_firewall.py @@ -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() diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 7b794ca23..6fd893f72 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -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()