Beispiel #1
0
    def test_inbound_ip_match(self):
        """
        Inbound ip match test, checks that packets are properly matched when
        the inbound traffic matches an ip in the blacklist.

        Assert:
            Both packets are matched
            Ip match flows are added
        """
        # Set up subscribers
        sub = SubContextConfig('IMSI001010000000013', '192.168.128.74',
                               self._tbl_num)

        isolator = RyuDirectTableIsolator(
            RyuForwardFlowArgsBuilder.from_subscriber(sub).build_requests(),
            self.testing_controller,
        )

        # Set up packets
        pkt_sender = ScapyPacketInjector(self.BRIDGE)
        packets = [
            self._build_default_ip_packet(self.INBOUND_TEST_IP, sub.ip),
            self._build_default_ip_packet(self.BOTH_DIR_TEST_IP, sub.ip),
        ]

        # Check if these flows were added (queries should return flows)
        inbound_flow_queries = [
            FlowQuery(self._tbl_num,
                      self.testing_controller,
                      match=MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
                                       direction=Direction.OUT,
                                       ipv4_dst=self.INBOUND_TEST_IP)),
            FlowQuery(self._tbl_num,
                      self.testing_controller,
                      match=MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
                                       direction=Direction.OUT,
                                       ipv4_dst=self.BOTH_DIR_TEST_IP)),
        ]

        # =========================== Verification ===========================
        # packets matched, ip match flows installed
        flow_verifier = FlowVerifier([
            FlowTest(FlowQuery(self._tbl_num, self.testing_controller), 2),
        ] + [
            FlowTest(query, 1, flow_count=1) for query in inbound_flow_queries
        ], lambda: wait_after_send(self.testing_controller))

        with isolator, flow_verifier:
            for packet in packets:
                pkt_sender.send(packet)

        flow_verifier.verify()
        assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #2
0
    def test_gre_peer_rules(self):
        """
        Inbound ip match test, checks that packets are properly matched when
        the inbound traffic matches an ip in the blocklist.

        Assert:
            Both packets are matched
            Ip match flows are added
        """
        assert_bridge_snapshot_match(self,
                                     self.BRIDGE,
                                     self.service_manager)
Beispiel #3
0
    def testFlowSnapshotMatch(self):
        cls = self.__class__
        assert_bridge_snapshot_match(
            self,
            self.UPLINK_BRIDGE,
            self.service_manager,
            include_stats=False,
        )

        self.assertIn(cls.SGi_IP, get_iface_ipv4(cls.UPLINK_BRIDGE),
                      "ip not found")
        self.assertIn(cls.SGi_IPv6, get_iface_ipv6(cls.UPLINK_BRIDGE),
                      "ipv6 not found")
Beispiel #4
0
    def testFlowSnapshotMatch(self):
        cls = self.__class__
        # after Non NAT init, router shld be accessible.
        # manually start DHCP client on up-br

        check_connectivity(cls.ROUTER_IP, cls.UPLINK_BRIDGE, False)
        check_connectivity_v6(cls.ROUTER_IPV6)

        assert_bridge_snapshot_match(
            self, self.UPLINK_BRIDGE, self.service_manager,
            include_stats=False,
        )
        self.assertEqual(get_ovsdb_port_tag(cls.UPLINK_BRIDGE), '[]')
Beispiel #5
0
    def test_remove_app_rules(self):
        """
        Test DPI classifier flows are properly removed

        Assert:
            Remove the facebook match flow
        """
        flow_match1 = FlowMatch(ip_proto=FlowMatch.IPPROTO_TCP,
                                ipv4_dst='45.10.0.0/24',
                                ipv4_src='1.2.3.0/24',
                                tcp_dst=80,
                                tcp_src=51115,
                                direction=FlowMatch.UPLINK)
        self.dpi_controller.remove_classify_flow(flow_match1)
        hub.sleep(5)
        assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #6
0
    def test_add_valid_quota_subscriber(self):
        """
        Add flows for two subscribers
        """
        imsi_1 = 'IMSI010000000088888'
        mac_1 = '5e:cc:cc:b1:49:4b'

        # Add subscriber with UE MAC address """
        self.check_quota_controller.update_subscriber_quota_state(
            SubscriberQuotaUpdate(
                sid=SubscriberID(id=imsi_1),
                mac_addr=mac_1,
                update_type=SubscriberQuotaUpdate.VALID_QUOTA))

        wait_after_send(self.testing_controller)
        assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #7
0
    def test_remove_app_rules(self):
        """
        Test DPI classifier flows are properly removed

        Assert:
            Initial state has (`facebook` and `instagram`), remove (`facebook`)
            App type matches on (`instagram`)
        """
        flow_match1 = FlowMatch(ip_proto=FlowMatch.IPPROTO_TCP,
                                ipv4_dst='45.10.0.0/24',
                                ipv4_src='1.2.3.0/24',
                                tcp_dst=80,
                                tcp_src=51115,
                                direction=FlowMatch.UPLINK)
        self.dpi_controller.remove_classify_flow(flow_match1)
        hub.sleep(5)
        assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #8
0
    def test_add_app_rules(self):
        """
        Test DPI classifier flows are properly added

        Assert:
            1 App not tracked -> no rule installed(`notanAPP`)
            3 App types are matched on:
                facebook other
                google_docs other
                viber audio
        """
        flow_match1 = FlowMatch(ip_proto=FlowMatch.IPPROTO_TCP,
                                ipv4_dst='45.10.0.0/24',
                                ipv4_src='1.2.3.0/24',
                                tcp_dst=80,
                                tcp_src=51115,
                                direction=FlowMatch.UPLINK)
        flow_match2 = FlowMatch(ip_proto=FlowMatch.IPPROTO_TCP,
                                ipv4_dst='1.10.0.0/24',
                                ipv4_src='6.2.3.0/24',
                                tcp_dst=111,
                                tcp_src=222,
                                direction=FlowMatch.UPLINK)
        flow_match3 = FlowMatch(ip_proto=FlowMatch.IPPROTO_UDP,
                                ipv4_dst='22.2.2.24',
                                ipv4_src='15.22.32.0/24',
                                udp_src=111,
                                udp_dst=222,
                                direction=FlowMatch.UPLINK)
        flow_match_for_no_proto = FlowMatch(ip_proto=FlowMatch.IPPROTO_UDP,
                                            ipv4_dst='1.1.1.1')
        self.dpi_controller.add_classify_flow(flow_match_for_no_proto,
                                              'notanAPP', 'null')
        self.dpi_controller.add_classify_flow(flow_match1,
                                              'base.ip.http.facebook',
                                              'NotReal')
        self.dpi_controller.add_classify_flow(
            flow_match2, 'base.ip.https.google_gen.google_docs', 'MAGMA')
        self.dpi_controller.add_classify_flow(flow_match3, 'base.ip.udp.viber',
                                              'AudioTransfer Receiving')
        hub.sleep(5)
        assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #9
0
    def test_blocking_ip_match(self):
        """
        Inbound ip match test, checks that packets are properly matched when
        the inbound traffic matches an ip in the blocklist.

        Assert:
            Both packets are matched
            Ip match flows are added
        """
        # Set up subscribers
        sub = SubContextConfig(
            'IMSI001010000000013',
            '192.168.128.74',
            default_ambr_config,
            self._tbl_num,
        )

        isolator = RyuDirectTableIsolator(
            RyuForwardFlowArgsBuilder.from_subscriber(sub).build_requests(),
            self.testing_controller,
        )

        # Set up packets
        pkt_sender = ScapyPacketInjector(self.BRIDGE)
        packets = [
            _build_default_ip_packet(self.MAC_DEST, self.OUTBOUND_TEST_IP1,
                                     sub.ip),
            _build_default_ip_packet(self.MAC_DEST, self.OUTBOUND_TEST_IP2,
                                     sub.ip),
            _build_default_ip_packet(self.MAC_DEST, self.BOTH_DIR_TEST_IP,
                                     sub.ip),
        ]

        with isolator:
            for packet in packets:
                pkt_sender.send(packet)

        assert_bridge_snapshot_match(
            self,
            self.BRIDGE,
            self.service_manager,
        )
Beispiel #10
0
    def testFlowSnapshotMatch(self):
        """
        Ensure 2 IMSIs sample flows are installed

        Assert:
            Snapshots match
        """
        imsi1 = 'IMSI010000000088888'
        imsi2 = 'IMSI010000000011111'
        msidn = 'BigTower'
        apn_mac = '08-00-27-cd-32-07'
        apn_name = 'MagmaBox'
        self.ipfix_controller.add_ue_sample_flow(imsi1, msidn, apn_mac,
                                                 apn_name)
        self.ipfix_controller.add_ue_sample_flow(imsi2, msidn, apn_mac,
                                                 apn_name)

        # Big rule wait a bit for it to appear
        hub.sleep(2)
        assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #11
0
    def test_add_three_subscribers(self):
        """
        Add flows for two subscribers
        """
        imsi_1 = 'IMSI010000000088888'
        imsi_2 = 'IMSI010000111111118'
        imsi_3 = 'IMSI010002222222222'
        mac_1 = '5e:cc:cc:b1:49:4b'
        mac_2 = '5e:a:cc:af:aa:fe'
        mac_3 = '5e:bb:cc:aa:aa:fe'

        # Add subscriber with UE MAC address """

        self.check_quota_controller.update_subscriber_quota_state(
            SubscriberQuotaUpdate(sid=SubscriberID(id=imsi_1),
                                  mac_addr=mac_1,
                                  update_type=SubscriberQuotaUpdate.NO_QUOTA))
        self.check_quota_controller.update_subscriber_quota_state(
            SubscriberQuotaUpdate(sid=SubscriberID(id=imsi_2),
                                  mac_addr=mac_2,
                                  update_type=SubscriberQuotaUpdate.NO_QUOTA))
        self.check_quota_controller.update_subscriber_quota_state(
            SubscriberQuotaUpdate(
                sid=SubscriberID(id=imsi_3),
                mac_addr=mac_3,
                update_type=SubscriberQuotaUpdate.VALID_QUOTA))

        wait_after_send(self.testing_controller)

        assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)

        self.check_quota_controller.update_subscriber_quota_state(
            SubscriberQuotaUpdate(sid=SubscriberID(id=imsi_2),
                                  mac_addr=mac_2,
                                  update_type=SubscriberQuotaUpdate.TERMINATE))
        self.check_quota_controller.update_subscriber_quota_state(
            SubscriberQuotaUpdate(sid=SubscriberID(id=imsi_3),
                                  mac_addr=mac_3,
                                  update_type=SubscriberQuotaUpdate.TERMINATE))
Beispiel #12
0
    def test_add_app_rules(self):
        """
        Test DPI classifier flows are properly added

        Assert:
            2 App types are matched on (`facebook` and `instagram`)
        """
        flow_match1 = FlowMatch(ip_proto=FlowMatch.IPPROTO_TCP,
                                ipv4_dst='45.10.0.0/24',
                                ipv4_src='1.2.3.0/24',
                                tcp_dst=80,
                                tcp_src=51115,
                                direction=FlowMatch.UPLINK)
        flow_match2 = FlowMatch(ip_proto=FlowMatch.IPPROTO_UDP,
                                ipv4_dst='45.10.0.0/24',
                                ipv4_src='1.2.3.0/24',
                                udp_src=111,
                                udp_dst=222,
                                direction=FlowMatch.UPLINK)
        self.dpi_controller.add_classify_flow(flow_match1, 'facebook')
        self.dpi_controller.add_classify_flow(flow_match2, 'instagram')
        hub.sleep(5)
        assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #13
0
    def test_blocking_ip_match(self):
        """
        Inbound ip match test, checks that packets are properly matched when
        the inbound traffic matches an ip in the blocklist.

        Assert:
            Both packets are matched
            Ip match flows are added
        """
        sub = self._setup_subscribers_ipv6()

        isolator = RyuDirectTableIsolator(
            RyuForwardFlowArgsBuilder.from_subscriber(sub).build_requests(),
            self.testing_controller,
        )

        # Set up packets
        pkt_sender = ScapyPacketInjector(self.BRIDGE)
        packets = [
            _build_default_ipv6_packet(self.MAC_DEST, self.OUTBOUND_TEST_IP1,
                                       sub.ip),
            _build_default_ipv6_packet(self.MAC_DEST, self.OUTBOUND_TEST_IP2,
                                       sub.ip),
            _build_default_ipv6_packet(self.MAC_DEST, self.OUTBOUND_TEST_IP3,
                                       sub.ip),
        ]

        with isolator:
            for packet in packets:
                pkt_sender.send(packet)

        assert_bridge_snapshot_match(
            self,
            self.BRIDGE,
            self.service_manager,
            ipv6_prefix_only=True,
        )
Beispiel #14
0
 def testFlowSnapshotMatch(self):
     fake_inout_setup(self.inout_controller)
     assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #15
0
    def testFlowSnapshotMatch(self):
        cls = self.__class__

        assert_bridge_snapshot_match(self, self.UPLINK_BRIDGE, self.service_manager)
        self.assertIn(cls.SGi_IP, get_iface_ipv4(cls.BRIDGE_ETH_PORT), "ip not found")
Beispiel #16
0
    def test_url_redirect(self):
        """
        Partial redirection test, checks if flows were added properly for url
        based redirection.

        Assert:
            1 Packet is matched
            Packet bypass flows are added
            Flow learn action is triggered - another flow is added to the table
        """
        fake_controller_setup(self.enforcement_controller)
        redirect_ips = ["185.128.101.5", "185.128.121.4"]
        self.enforcement_controller._redirect_manager._dns_cache.get(
            "about.sha.ddih.org", lambda: redirect_ips, max_age=42)
        imsi = 'IMSI010000000088888'
        sub_ip = '192.168.128.74'
        flow_list = [FlowDescription(match=FlowMatch())]
        policy = PolicyRule(id='redir_test',
                            priority=3,
                            flow_list=flow_list,
                            redirect=RedirectInformation(
                                support=1,
                                address_type=2,
                                server_address="http://about.sha.ddih.org/"))

        # ============================ Subscriber ============================
        sub_context = RyuDirectSubscriberContext(
            imsi, sub_ip, self.enforcement_controller,
            self._tbl_num).add_dynamic_rule(policy)
        isolator = RyuDirectTableIsolator(
            RyuForwardFlowArgsBuilder.from_subscriber(
                sub_context.cfg).build_requests(), self.testing_controller)
        pkt_sender = ScapyPacketInjector(self.IFACE)
        packet = TCPPacketBuilder()\
            .set_tcp_layer(42132, 80, 321)\
            .set_tcp_flags("S")\
            .set_ip_layer('151.42.41.122', sub_ip)\
            .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00")\
            .build()

        # Check if these flows were added (queries should return flows)
        permit_outbound, permit_inbound = [], []
        for ip in redirect_ips:
            permit_outbound.append(
                FlowQuery(self._tbl_num,
                          self.testing_controller,
                          match=flow_match_to_magma_match(
                              FlowMatch(ipv4_dst=ip,
                                        direction=FlowMatch.UPLINK))))
            permit_inbound.append(
                FlowQuery(self._tbl_num,
                          self.testing_controller,
                          match=flow_match_to_magma_match(
                              FlowMatch(ipv4_src=ip,
                                        direction=FlowMatch.DOWNLINK))))

        learn_action_flow = flow_match_to_magma_match(
            FlowMatch(ip_proto=6,
                      direction=FlowMatch.DOWNLINK,
                      ipv4_src=self.BRIDGE_IP_ADDRESS,
                      ipv4_dst=sub_ip))
        learn_action_query = FlowQuery(self._tbl_num, self.testing_controller,
                                       learn_action_flow)

        # =========================== Verification ===========================
        # 1 packet sent, permit rules installed, learn action installed. Since
        # the enforcement table is entered via the DPI table and the scratch
        # enforcement table, the number of packets handled by the table is 2.
        flow_verifier = FlowVerifier(
            [
                FlowTest(FlowQuery(self._tbl_num, self.testing_controller), 2),
                FlowTest(learn_action_query, 0, flow_count=1)
            ] +
            [FlowTest(query, 0, flow_count=1) for query in permit_outbound] +
            [FlowTest(query, 0, flow_count=1) for query in permit_inbound],
            lambda: wait_after_send(self.testing_controller))

        with isolator, sub_context, flow_verifier:
            pkt_sender.send(packet)
            assert_bridge_snapshot_match(self, self.BRIDGE,
                                         self.service_manager)

        flow_verifier.verify()
Beispiel #17
0
 def testFlowSnapshotMatch(self):
     cls = self.__class__
     assert_bridge_snapshot_match(self,
                                  self.UPLINK_BRIDGE,
                                  self.service_manager,
                                  include_stats=False)
Beispiel #18
0
 def testFlowSnapshotMatch(self):
     # time.sleep(100)
     assert_bridge_snapshot_match(self,
                                  self.UPLINK_BRIDGE,
                                  self.service_manager,
                                  include_stats=False)
Beispiel #19
0
    def test_outbound_ip_match(self):
        """
        Outbound ip match test, checks that packets are properly matched when
        the outbound traffic matches an ip in the blocklist.

        Assert:
            Both packets are matched
            Ip match flows are added
        """
        sub = self._setup_subscribers()

        isolator = RyuDirectTableIsolator(
            RyuForwardFlowArgsBuilder.from_subscriber(sub).build_requests(),
            self.testing_controller,
        )

        # Set up packets
        pkt_sender = ScapyPacketInjector(self.BRIDGE)
        packets = [
            _build_default_ip_packet(self.MAC_DEST, sub.ip,
                                     self.OUTBOUND_TEST_IP),
            _build_default_ip_packet(self.MAC_DEST, sub.ip,
                                     self.BOTH_DIR_TEST_IP),
        ]

        # Check if these flows were added (queries should return flows)
        outbound_flow_queries = [
            FlowQuery(
                self._tbl_num,
                self.testing_controller,
                match=MagmaMatch(
                    eth_type=ether_types.ETH_TYPE_IP,
                    direction=Direction.IN,
                    ipv4_src=self.OUTBOUND_TEST_IP,
                ),
            ),
            FlowQuery(
                self._tbl_num,
                self.testing_controller,
                match=MagmaMatch(
                    eth_type=ether_types.ETH_TYPE_IP,
                    direction=Direction.IN,
                    ipv4_src=self.BOTH_DIR_TEST_IP,
                ),
            ),
        ]

        # =========================== Verification ===========================
        # packets matched, ip match flows installed
        flow_verifier = FlowVerifier(
            [
                FlowTest(
                    FlowQuery(self._tbl_num, self.testing_controller),
                    2,
                ),
            ] + [
                FlowTest(query, 1, flow_count=1)
                for query in outbound_flow_queries
            ],
            lambda: wait_after_send(self.testing_controller, ),
        )

        with isolator, flow_verifier:
            for packet in packets:
                pkt_sender.send(packet)

        flow_verifier.verify()
        assert_bridge_snapshot_match(
            self,
            self.BRIDGE,
            self.service_manager,
        )
Beispiel #20
0
 def testFlowSnapshotMatch(self):
     fake_mandatory_controller_setup(self.ingress_controller)
     fake_mandatory_controller_setup(self.middle_controller)
     fake_mandatory_controller_setup(self.egress_controller)
     assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #21
0
    def test_no_match(self):
        """
        No match test, checks that packets are not matched when
        the there is no match to the ip and direction in the blocklist.

        Assert:
            Both packets are not matched
            Ip match flows are added
        """
        # Set up subscribers
        sub = SubContextConfig(
            'IMSI001010000000013', '192.168.128.74',
            default_ambr_config, self._tbl_num,
        )

        isolator = RyuDirectTableIsolator(
            RyuForwardFlowArgsBuilder.from_subscriber(sub).build_requests(),
            self.testing_controller,
        )

        # Set up packets. The directions of the packets are opposite of the
        # installed match flow, so there should not matches.
        pkt_sender = ScapyPacketInjector(self.BRIDGE)
        packets = [
            _build_default_ip_packet(self.MAC_DEST, self.OUTBOUND_TEST_IP, sub.ip),
            _build_default_ip_packet(self.MAC_DEST, sub.ip, self.INBOUND_TEST_IP),
        ]

        # Check if these flows were added (queries should return flows)
        outbound_flow_queries = [
            FlowQuery(
                self._tbl_num, self.testing_controller,
                match=MagmaMatch(
                    eth_type=ether_types.ETH_TYPE_IP,
                    direction=Direction.OUT,
                    ipv4_dst=self.INBOUND_TEST_IP,
                ),
            ),
            FlowQuery(
                self._tbl_num, self.testing_controller,
                match=MagmaMatch(
                    eth_type=ether_types.ETH_TYPE_IP,
                    direction=Direction.IN,
                    ipv4_src=self.OUTBOUND_TEST_IP,
                ),
            ),
        ]

        # =========================== Verification ===========================
        # packets are not matched, ip match flows installed
        flow_verifier = FlowVerifier(
            [
                FlowTest(
                    FlowQuery(self._tbl_num, self.testing_controller), 2,
                ),
            ] + [
                FlowTest(query, 0, flow_count=1) for query in
                outbound_flow_queries
            ],
            lambda: wait_after_send(
                self.testing_controller,
            ),
        )

        with isolator, flow_verifier:
            for packet in packets:
                pkt_sender.send(packet)

        flow_verifier.verify()
        assert_bridge_snapshot_match(
            self,
            self.BRIDGE,
            self.service_manager,
        )
Beispiel #22
0
 def testFlowSnapshotMatch(self):
     assert_bridge_snapshot_match(self, self.UPLINK_BRIDGE,
                                  self.service_manager)
Beispiel #23
0
    def testFlowSnapshotMatch(self):
        cls = self.__class__
        assert_bridge_snapshot_match(self, self.UPLINK_BRIDGE, self.service_manager,
                                     include_stats=False)

        self.assertEqual(get_ovsdb_port_tag(cls.UPLINK_BRIDGE), cls.VLAN_TAG)
Beispiel #24
0
 def testFlowSnapshotMatch(self):
     hub.sleep(2)
     assert_bridge_snapshot_match(self, self.BRIDGE, self.service_manager)
Beispiel #25
0
    def test_ipv4_redirect(self):
        """
        Partial redirection test, checks if flows were added properly for ipv4
        based redirection.

        Assert:
            1 Packet is matched
            Packet bypass flows are added
            Flow learn action is triggered - another flow is added to the table
        """
        fake_controller_setup(self.enforcement_controller)
        redirect_ip = "54.12.31.42"
        imsi = 'IMSI012000000088888'
        sub_ip = '192.168.128.74'
        flow_list = [FlowDescription(match=FlowMatch())]
        policy = VersionedPolicy(
            rule=PolicyRule(
                id='redir_ip_test', priority=3, flow_list=flow_list,
                redirect=RedirectInformation(
                    support=1,
                    address_type=0,
                    server_address=redirect_ip,
                ),
            ),
            version=1,
        )

        # ============================ Subscriber ============================
        sub_context = RyuDirectSubscriberContext(
            imsi, sub_ip, self.enforcement_controller, self._tbl_num,
        ).add_policy(policy)
        isolator = RyuDirectTableIsolator(
            RyuForwardFlowArgsBuilder.from_subscriber(sub_context.cfg)
                                     .build_requests(),
            self.testing_controller,
        )
        pkt_sender = ScapyPacketInjector(self.IFACE)
        packet = TCPPacketBuilder()\
            .set_tcp_layer(42132, 80, 321)\
            .set_tcp_flags("S")\
            .set_ip_layer('151.42.41.122', sub_ip)\
            .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00")\
            .build()

        # Check if these flows were added (queries should return flows)
        permit_outbound = FlowQuery(
            self._tbl_num, self.testing_controller,
            match=flow_match_to_magma_match(
                FlowMatch(
                    ip_dst=convert_ipv4_str_to_ip_proto(redirect_ip),
                    direction=FlowMatch.UPLINK,
                ),
            ),
        )
        permit_inbound = FlowQuery(
            self._tbl_num, self.testing_controller,
            match=flow_match_to_magma_match(
                FlowMatch(
                    ip_src=convert_ipv4_str_to_ip_proto(redirect_ip),
                    direction=FlowMatch.DOWNLINK,
                ),
            ),
        )
        learn_action_flow = flow_match_to_magma_match(
            FlowMatch(
                ip_proto=6, direction=FlowMatch.DOWNLINK,
                ip_src=convert_ipv4_str_to_ip_proto(self.BRIDGE_IP_ADDRESS),
                ip_dst=convert_ipv4_str_to_ip_proto(sub_ip),
            ),
        )
        learn_action_query = FlowQuery(
            self._tbl_num, self.testing_controller,
            learn_action_flow,
        )

        # =========================== Verification ===========================
        # 1 packet sent, permit rules installed, learn action installed. Since
        # the enforcement table is entered via the DPI table and the scratch
        # enforcement table, the number of packets handled by the table is 2.
        flow_verifier = FlowVerifier(
            [
                FlowTest(FlowQuery(self._tbl_num, self.testing_controller), 2),
                FlowTest(permit_outbound, 0, flow_count=1),
                FlowTest(permit_inbound, 0, flow_count=1),
                FlowTest(learn_action_query, 0, flow_count=1),
            ], lambda: wait_after_send(self.testing_controller),
        )

        with isolator, sub_context, flow_verifier:
            pkt_sender.send(packet)
            assert_bridge_snapshot_match(
                self, self.BRIDGE,
                self.service_manager,
            )

        flow_verifier.verify()