1#!/usr/bin/env python3
2#
3#  Copyright (c) 2021, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29import logging
30import time
31import unittest
32import ipaddress
33
34import config
35import pktverify
36import pktverify.packet_verifier
37from pktverify.consts import MA1
38import thread_cert
39
40# Test description:
41#   This test verifies the functionality of firewall. OTBR will only
42#   forward specific kinds of packets between the infra interface and the thread
43#   interface.
44#
45# Topology:
46#    ----------------(eth)----------------------
47#           |                  |
48#          BR1 (Leader)      HOST
49#           |      \
50#         ROUTER1  ROUTER2
51
52BR1 = 1
53ROUTER1 = 2
54ROUTER2 = 3
55HOST = 4
56
57
58class Firewall(thread_cert.TestCase):
59    USE_MESSAGE_FACTORY = False
60
61    TOPOLOGY = {
62        BR1: {
63            'name': 'BR_1',
64            'allowlist': [ROUTER1, ROUTER2],
65            'is_otbr': True,
66            'version': '1.4',
67        },
68        ROUTER1: {
69            'name': 'Router_1',
70            'allowlist': [BR1],
71            'version': '1.4',
72        },
73        ROUTER2: {
74            'name': 'Router_2',
75            'allowlist': [BR1],
76            'version': '1.4',
77        },
78        HOST: {
79            'name': 'Host',
80            'is_host': True,
81        }
82    }
83
84    def test(self):
85        br1 = self.nodes[BR1]
86        self.br1 = br1
87        router1 = self.nodes[ROUTER1]
88        router2 = self.nodes[ROUTER2]
89        host = self.nodes[HOST]
90
91        br1.start()
92        self.simulator.go(config.LEADER_STARTUP_DELAY)
93        self.assertEqual('leader', br1.get_state())
94        br1.set_log_level(5)
95
96        router1.start()
97        router2.start()
98        host.start(start_radvd=True)
99        self.simulator.go(config.ROUTER_STARTUP_DELAY)
100        self.assertEqual('router', router1.get_state())
101        self.assertEqual('router', router2.get_state())
102
103        br1.set_domain_prefix(config.DOMAIN_PREFIX, 'prosD')
104        br1.register_netdata()
105
106        router1.add_ipmaddr(MA1)
107        router1.register_netdata()
108
109        self.simulator.go(20)
110
111        def host_ping_ether(dest, interface, ttl=10, add_interface=False, add_route=False, gateway=None):
112            if add_interface:
113                host.bash(f'ip -6 addr add {interface}/64 dev {host.ETH_DEV} scope global')
114                route = str(ipaddress.IPv6Network(f'{interface}/64', strict=False))
115                host.bash(f'ip -6 route del {route} dev {host.ETH_DEV}')
116            if add_route:
117                host.bash(f'ip -6 route add {dest} dev {host.ETH_DEV} via {gateway}')
118            self.simulator.go(2)
119            result = host.ping_ether(dest, ttl=ttl, interface=interface)
120            if add_route:
121                host.bash(f'ip -6 route del {dest}')
122            if add_interface:
123                host.bash(f'ip -6 addr del {interface}/64 dev {host.ETH_DEV} scope global')
124            self.simulator.go(1)
125            return result
126
127        # 1. Host pings router1's OMR from host's infra address.
128        self.assertTrue(host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], interface=host.ETH_DEV))
129
130        # 2. Host pings router1's DUA from host's infra address.
131        self.assertTrue(host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.DUA), interface=host.ETH_DEV))
132
133        # 3. Host pings router1's OMR from router1's RLOC.
134        self.assertFalse(
135            host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
136                            interface=router1.get_rloc(),
137                            add_interface=True))
138
139        # 4. Host pings router1's OMR from router2's OMR.
140        self.assertFalse(
141            host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
142                            interface=router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
143                            add_interface=True))
144
145        # 5. Host pings router1's OMR from router1's ML-EID.
146        self.assertFalse(
147            host_ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
148                            interface=router1.get_mleid(),
149                            add_interface=True))
150
151        host.bash(f'ip -6 route add {config.MESH_LOCAL_PREFIX} dev {host.ETH_DEV}')
152        self.simulator.go(5)
153
154        # 6. Host pings router1's RLOC from host's ULA address.
155        self.assertFalse(
156            host_ping_ether(router1.get_rloc(),
157                            interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0],
158                            add_route=True,
159                            gateway=br1.get_ip6_address(config.ADDRESS_TYPE.BACKBONE_GUA)))
160
161        # 7. Host pings router1's ML-EID from host's ULA address.
162        self.assertFalse(
163            host_ping_ether(router1.get_mleid(),
164                            interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0],
165                            add_route=True,
166                            gateway=br1.get_ip6_address(config.ADDRESS_TYPE.BACKBONE_GUA)))
167
168        # 8. Host pings router1's link-local address from host's infra address.
169        self.assertFalse(
170            host_ping_ether(router1.get_linklocal(),
171                            interface=host.ETH_DEV,
172                            add_route=True,
173                            gateway=br1.get_ip6_address(config.ADDRESS_TYPE.BACKBONE_GUA)))
174
175        # 9. Host pings MA1 from host's ULA address.
176        self.assertTrue(host_ping_ether(MA1, ttl=10,
177                                        interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
178
179        # 10. Host pings MA1 from router1's RLOC.
180        self.assertFalse(host_ping_ether(MA1, ttl=10, interface=router1.get_rloc(), add_interface=True))
181
182        # 11. Host pings MA1 from router1's OMR.
183        self.assertFalse(
184            host_ping_ether(MA1,
185                            ttl=10,
186                            interface=router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
187                            add_interface=True))
188
189        # 12. Host pings MA1 from router1's ML-EID.
190        self.assertFalse(host_ping_ether(MA1, ttl=10, interface=router1.get_mleid(), add_interface=True))
191
192        # 13. Router1 pings Host from router1's ML-EID.
193        self.assertFalse(
194            router1.ping(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0], interface=router1.get_mleid()))
195
196        # 14. BR pings router1's ML-EID from BR's ML-EID.
197        self.assertTrue(br1.ping_ether(router1.get_mleid(), interface=br1.get_mleid()))
198
199        # 15. BR pings router1's OMR from BR's infra interface.
200        self.assertTrue(
201            br1.ping_ether(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
202                           interface=br1.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
203
204        # 16. Host sends a UDP packet to router1's OMR address's TMF port.
205        host.udp_send_host(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], config.TMF_PORT, "HELLO")
206
207        # 17. BR1 sends a UDP packet to router1's ML-EID's TMF port.
208        br1.udp_send_host(router1.get_mleid(), config.TMF_PORT, "BYE")
209
210        # 18. BR1 sends a UDP packet to its own ML-EID's TMF port.
211        br1.udp_send_host(br1.get_mleid(), config.TMF_PORT, "SELF")
212
213        self.collect_ipaddrs()
214        self.collect_rlocs()
215        self.collect_rloc16s()
216        self.collect_extra_vars()
217        self.collect_omrs()
218        self.collect_duas()
219
220    def verify(self, pv: pktverify.packet_verifier.PacketVerifier):
221        pkts = pv.pkts
222        vars = pv.vars
223        pv.summary.show()
224        logging.info(f'vars = {vars}')
225
226        pv.verify_attached('Router_1', 'BR_1')
227
228        # 1. Host pings router1's OMR from host's infra address.
229        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
230            vars['Router_1_OMR'][0]).filter_ping_request().must_next()
231        pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
232            vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_next()
233
234        # 2. Host pings router1's DUA from host's infra address.
235        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
236            vars['Router_1_DUA']).filter_ping_request().must_next()
237        pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
238            vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_next()
239
240        # 3. Host pings router1's OMR from router1's RLOC.
241        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(
242            vars['Router_1_RLOC'], vars['Router_1_OMR'][0]).filter_ping_request().must_next()
243        pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
244            vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
245
246        # 4. Host pings router1's OMR from router2's OMR.
247        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(
248            vars['Router_2_OMR'][0], vars['Router_1_OMR'][0]).filter_ping_request().must_next()
249        pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst64(
250            vars['Router_1']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
251
252        # 5. Host pings router1's OMR from router1's ML-EID.
253        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(
254            vars['Router_1_MLEID'], vars['Router_1_OMR'][0]).filter_ping_request().must_next()
255        pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
256            vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
257
258        # 6. Host pings router1's RLOC from host's ULA address.
259        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
260            vars['Router_1_RLOC']).filter_ping_request().must_next()
261        pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
262            vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
263
264        # 7. Host pings router1's ML-EID from host's ULA address.
265        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
266            vars['Router_1_MLEID']).filter_ping_request().must_next()
267        pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
268            vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
269
270        # 8. Host pings router1's link-local address from host's infra address.
271        # Skip this scenario as for now
272        # _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(
273        #     vars['Router_1_LLA']).filter_ping_request().must_next()
274        pkts.filter_wpan_src64(vars['BR_1']).filter_wpan_dst16(
275            vars['Router_1_RLOC16']).filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
276
277        # 9. Host pings MA1 from host's ULA address.
278        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(MA1).filter_ping_request().must_next()
279        pkts.filter_wpan_src64(
280            vars['BR_1']).filter_AMPLFMA().filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_next()
281
282        # 10. Host pings MA1 from router1's RLOC.
283        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(vars['Router_1_RLOC'],
284                                                                         MA1).filter_ping_request().must_next()
285        pkts.filter_wpan_src64(
286            vars['BR_1']).filter_AMPLFMA().filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
287
288        # 11. Host pings MA1 from router1's OMR.
289        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(vars['Router_1_OMR'][0],
290                                                                         MA1).filter_ping_request().must_next()
291        pkts.filter_wpan_src64(
292            vars['BR_1']).filter_AMPLFMA().filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
293
294        # 12. Host pings MA1 from router1's ML-EID.
295        _pkt = pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_src_dst(vars['Router_1_MLEID'],
296                                                                         MA1).filter_ping_request().must_next()
297        pkts.filter_wpan_src64(
298            vars['BR_1']).filter_AMPLFMA().filter_ping_request(identifier=_pkt.icmpv6.echo.identifier).must_not_next()
299
300        # 13. Router1 pings Host from router1's ML-EID.
301        pkts.filter_eth_src(vars['BR_1_ETH']).filter_ipv6_src_dst(
302            vars['Router_1_MLEID'], vars['Host_BGUA']).filter_ping_request().must_not_next()
303
304        # 14. BR pings router1's ML-EID from BR's ML-EID.
305        _pkt = pkts.filter_wpan_src64(vars['BR_1']).filter_ipv6_src_dst(
306            vars['BR_1_MLEID'], vars['Router_1_MLEID']).filter_ping_request().must_next()
307        pkts.filter_wpan_src64(vars['Router_1']).filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).must_next()
308
309        # 15. BR pings router1's OMR from BR's infra interface.
310        _pkt = pkts.filter_wpan_src64(vars['BR_1']).filter_ping_request().must_next()
311        pkts.filter_wpan_src64(vars['Router_1']).filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).must_next()
312
313        # 16. Host sends a UDP packet to router1's OMR address's TMF port (61631).
314        # The packet should be able to reach BR1 but BR1 won't forward it to Thread.
315        pkts.filter_eth_src(vars['Host_ETH']).filter_ipv6_dst(vars['Router_1_OMR'][0]).filter(
316            lambda p: p.udp.dstport == config.TMF_PORT and p.udp.length == len("HELLO") + 8).must_next()
317        pkts.filter_wpan_src64(vars['BR_1']).filter_ipv6_dst(vars['Router_1_OMR'][0]).filter(
318            lambda p: p.udp.dstport == config.TMF_PORT and p.udp.length == len("HELLO") + 8).must_not_next()
319
320        # 17. BR1 sends a UDP packet to router1's ML-EID's TMF port (61631).
321        # The packet should be dropped by BR1, so it should not be present in Thread.
322        pkts.filter_wpan_src64(vars['BR_1']).filter_ipv6_dst(vars['Router_1_MLEID']).filter(
323            lambda p: p.udp.dstport == config.TMF_PORT and p.udp.length == len("BYE") + 8).must_not_next()
324
325        # 18. BR1 sends a UDP packet to its own ML-EID's TMF port (61631).
326        # The packet should be dropped by BR1, so it should not be present anywhere.
327        pkts.filter_wpan_src64(vars['BR_1']).filter_ipv6_dst(vars['BR_1_MLEID']).filter(
328            lambda p: p.udp.dstport == config.TMF_PORT and p.udp.length == len("SELF") + 8).must_not_next()
329
330
331if __name__ == '__main__':
332    unittest.main()
333