def add_classify_flow(self, flow_match, flow_state, app: str, service_type: str): """ Parse DPI output and set the register for future packets matching this flow. APP is split into tokens as the top level app is not supported, but the parent protocol might be. Example we care about google traffic, but don't neccessarily want to classify every specific google service. """ # TODO add error return if self._datapath is None: return parser = self._datapath.ofproto_parser app_id = get_app_id(app, service_type) try: ul_match = flow_match_to_magma_match(flow_match) ul_match.direction = None dl_match = flow_match_to_magma_match(flip_flow_match(flow_match)) dl_match.direction = None except FlowMatchError as e: self.logger.error(e) return actions = [parser.NXActionRegLoad2(dst=DPI_REG, value=app_id)] # No reason to create a flow here if flow_state != FlowRequest.FLOW_CREATED: flows.add_flow(self._datapath, self._classify_app_tbl_num, ul_match, actions, priority=flows.DEFAULT_PRIORITY, idle_timeout=self._idle_timeout) flows.add_flow(self._datapath, self._classify_app_tbl_num, dl_match, actions, priority=flows.DEFAULT_PRIORITY, idle_timeout=self._idle_timeout)
def add_classify_flow(self, flow_match, app: str, service_type: str, src_mac: str, dst_mac: str): """ Parse DPI output and set the register for future packets matching this flow. APP is split into tokens as the top level app is not supported, but the parent protocol might be. Example we care about google traffic, but don't neccessarily want to classify every specific google service. """ parser = self._datapath.ofproto_parser app_id = self._get_app_id(app, service_type) try: ul_match = flow_match_to_magma_match(flow_match) ul_match.direction = None dl_match = flow_match_to_magma_match(flip_flow_match(flow_match)) dl_match.direction = None except FlowMatchError as e: self.logger.error(e) return actions = [parser.NXActionRegLoad2(dst=DPI_REG, value=app_id)] actions_w_mirror = \ [parser.OFPActionOutput(self._mon_port_number)] + actions flows.add_resubmit_next_service_flow(self._datapath, self.tbl_num, ul_match, actions_w_mirror, priority=flows.DEFAULT_PRIORITY, resubmit_table=self.next_table, idle_timeout=self._idle_timeout) flows.add_resubmit_next_service_flow(self._datapath, self.tbl_num, dl_match, actions_w_mirror, priority=flows.DEFAULT_PRIORITY, resubmit_table=self.next_table, idle_timeout=self._idle_timeout) if self._service_manager.is_app_enabled(IPFIXController.APP_NAME): self._generate_ipfix_sampling_pkt(flow_match, src_mac, dst_mac) flows.add_resubmit_next_service_flow( self._datapath, self._app_set_tbl_num, ul_match, actions, priority=flows.DEFAULT_PRIORITY, resubmit_table=self._imsi_set_tbl_num, idle_timeout=self._idle_timeout) flows.add_resubmit_next_service_flow( self._datapath, self._app_set_tbl_num, dl_match, actions, priority=flows.DEFAULT_PRIORITY, resubmit_table=self._imsi_set_tbl_num, idle_timeout=self._idle_timeout)
def remove_classify_flow(self, flow_match): try: ul_match = flow_match_to_magma_match(flow_match) ul_match.direction = None dl_match = flow_match_to_magma_match(flip_flow_match(flow_match)) dl_match.direction = None except FlowMatchError as e: self.logger.error(e) return False flows.delete_flow(self._datapath, self.tbl_num, ul_match) flows.delete_flow(self._datapath, self.tbl_num, dl_match) return True
def remove_classify_flow(self, flow_match, src_mac: str, dst_mac: str): try: ul_match = flow_match_to_magma_match(flow_match) ul_match.direction = None dl_match = flow_match_to_magma_match(flip_flow_match(flow_match)) dl_match.direction = None except FlowMatchError as e: self.logger.error(e) return False flows.delete_flow(self._datapath, self.tbl_num, ul_match) flows.delete_flow(self._datapath, self.tbl_num, dl_match) if self._service_manager.is_app_enabled(IPFIXController.APP_NAME): self._generate_ipfix_sampling_pkt(flow_match, src_mac, dst_mac) return True
def test_rule_install(self): """ Adds a policy to a subscriber. Verifies that flows are properly installed in enforcement and enforcement stats. Assert: Policy classification flows installed in enforcement Policy match flows installed in enforcement_stats """ fake_controller_setup(self.enforcement_controller, self.enforcement_stats_controller) imsi = 'IMSI001010000000013' sub_ip = '192.168.128.74' flow_list = [ FlowDescription(match=FlowMatch(ipv4_dst='45.10.0.0/25', direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT) ] policy = PolicyRule(id='rule1', priority=3, flow_list=flow_list) self.service_manager.session_rule_version_mapper.update_version( imsi, 'rule1') version = \ self.service_manager.session_rule_version_mapper.get_version( imsi, 'rule1') """ Setup subscriber, setup table_isolation to fwd pkts """ self._static_rule_dict[policy.id] = policy sub_context = RyuDirectSubscriberContext( imsi, sub_ip, self.enforcement_controller, self._main_tbl_num, self.enforcement_stats_controller).add_static_rule(policy.id) # =========================== Verification =========================== rule_num = self.enforcement_stats_controller._rule_mapper \ .get_or_create_rule_num(policy.id) enf_query = FlowQuery(self._main_tbl_num, self.testing_controller, match=flow_match_to_magma_match( FlowMatch(ipv4_dst='45.10.0.0/25', direction=FlowMatch.UPLINK)), cookie=rule_num) es_query = FlowQuery(self._scratch_tbl_num, self.testing_controller, match=MagmaMatch(imsi=encode_imsi(imsi), reg2=rule_num, rule_version=version), cookie=rule_num) # Verifies that 1 flow is installed in enforcement and 2 flows are # installed in enforcement stats, one for uplink and one for downlink. flow_verifier = FlowVerifier([ FlowTest(enf_query, 0, flow_count=1), FlowTest(es_query, 0, flow_count=2), ], lambda: None) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager) with sub_context, flow_verifier, snapshot_verifier: pass flow_verifier.verify()
def _get_classify_rule_flow_msgs(self, imsi, ip_addr, apn_ambr, flow, rule_num, priority, qos, hard_timeout, rule_id, app_name, app_service_type, next_table, version, qos_mgr): """ Install a flow from a rule. If the flow action is DENY, then the flow will drop the packet. Otherwise, the flow classifies the packet with its matched rule and injects the rule num into the packet's register. """ flow_match = flow_match_to_magma_match(flow.match, ip_addr) flow_match.imsi = encode_imsi(imsi) flow_match_actions, instructions = self._get_action_for_rule( flow, rule_num, imsi, ip_addr, apn_ambr, qos, rule_id, version, qos_mgr) msgs = [] if app_name: # We have to allow initial traffic to pass through, before it gets # classified by DPI, flow match set app_id to unclassified flow_match.app_id = UNCLASSIFIED_PROTO_ID parser = self._datapath.ofproto_parser passthrough_actions = flow_match_actions + \ [parser.NXActionRegLoad2(dst=SCRATCH_REGS[1], value=IGNORE_STATS)] msgs.append( flows.get_add_resubmit_current_service_flow_msg( self._datapath, self.tbl_num, flow_match, passthrough_actions, hard_timeout=hard_timeout, priority=self.UNCLASSIFIED_ALLOW_PRIORITY, cookie=rule_num, resubmit_table=next_table)) flow_match.app_id = get_app_id( PolicyRule.AppName.Name(app_name), PolicyRule.AppServiceType.Name(app_service_type), ) if flow.action == flow.DENY: msgs.append( flows.get_add_drop_flow_msg(self._datapath, self.tbl_num, flow_match, flow_match_actions, hard_timeout=hard_timeout, priority=priority, cookie=rule_num)) else: msgs.append( flows.get_add_resubmit_current_service_flow_msg( self._datapath, self.tbl_num, flow_match, flow_match_actions, instructions=instructions, hard_timeout=hard_timeout, priority=priority, cookie=rule_num, resubmit_table=next_table)) return msgs
def test_subscriber_restrict_policy(self): """ Add restrict policy to subscriber, send 4096 packets Assert: Packets are properly matched with the 'restrict_match' policy Send /20 (4096) packets, match /16 (256) packets """ fake_controller_setup(self.gy_controller) imsi = 'IMSI010000000088888' sub_ip = '192.168.128.74' flow_list1 = [ FlowDescription(match=FlowMatch( ip_dst=convert_ipv4_str_to_ip_proto('8.8.8.0/24'), direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT) ] policies = [ VersionedPolicy( rule=PolicyRule(id='restrict_match', priority=2, flow_list=flow_list1), version=1, ) ] pkts_matched = 256 pkts_sent = 4096 # ============================ Subscriber ============================ sub_context = RyuDirectSubscriberContext( imsi, sub_ip, self.gy_controller, self._tbl_num).add_policy(policies[0]) isolator = RyuDirectTableIsolator( RyuForwardFlowArgsBuilder.from_subscriber( sub_context.cfg).build_requests(), self.testing_controller) pkt_sender = ScapyPacketInjector(self.IFACE) packet = IPPacketBuilder()\ .set_ip_layer('8.8.8.8', sub_ip)\ .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00")\ .build() flow_query = FlowQuery(self._tbl_num, self.testing_controller, match=flow_match_to_magma_match( flow_list1[0].match)) # =========================== Verification =========================== # Verify aggregate table stats, subscriber 1 'simple_match' pkt count flow_verifier = FlowVerifier([ FlowTest(FlowQuery(self._tbl_num, self.testing_controller), pkts_sent), FlowTest(flow_query, pkts_matched) ], lambda: wait_after_send(self.testing_controller)) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager, include_stats=False) with isolator, sub_context, flow_verifier, snapshot_verifier: pkt_sender.send(packet)
def remove_classify_flow(self, match): try: match = flow_match_to_magma_match(match) except FlowMatchError as e: self.logger.error(e) return False flows.delete_flow(self._datapath, self.tbl_num, match) return True
def test_subscriber_policy(self): """ Add policy to subscriber, send 4096 packets Assert: Packets are properly matched with the 'simple_match' policy Send /20 (4096) packets, match /16 (256) packets """ imsi = 'IMSI010000000088888' sub_ip = '192.168.128.74' flow_list1 = [FlowDescription( match=FlowMatch( ipv4_dst='45.10.0.0/24', direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT) ] policies = [ PolicyRule(id='simple_match', priority=2, flow_list=flow_list1) ] pkts_matched = 256 pkts_sent = 4096 self._static_rule_dict[policies[0].id] = policies[0] # ============================ Subscriber ============================ sub_context = RyuDirectSubscriberContext( imsi, sub_ip, self.enforcement_controller, self._tbl_num ).add_static_rule(policies[0].id) isolator = RyuDirectTableIsolator( RyuForwardFlowArgsBuilder.from_subscriber(sub_context.cfg) .build_requests(), self.testing_controller ) pkt_sender = ScapyPacketInjector(self.IFACE) packet = IPPacketBuilder()\ .set_ip_layer('45.10.0.0/20', sub_ip)\ .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00")\ .build() flow_query = FlowQuery( self._tbl_num, self.testing_controller, match=flow_match_to_magma_match(flow_list1[0].match) ) # =========================== Verification =========================== # Verify aggregate table stats, subscriber 1 'simple_match' pkt count flow_verifier = FlowVerifier([ FlowTest(FlowQuery(self._tbl_num, self.testing_controller), pkts_sent), FlowTest(flow_query, pkts_matched) ], lambda: wait_after_send(self.testing_controller)) with isolator, sub_context, flow_verifier: pkt_sender.send(packet) flow_verifier.verify()
def _get_add_flow_msg(self, imsi, flow, rule_num, priority, ul_qos, hard_timeout, rule_id): match = flow.match ryu_match = flow_match_to_magma_match(match) ryu_match.imsi = encode_imsi(imsi) actions = self._get_of_actions_for_flow(flow, rule_num, imsi, ul_qos, rule_id) resubmit_table = self.next_table if flow.action != flow.DENY else None return flows.get_add_flow_msg(self._datapath, self.tbl_num, ryu_match, actions, hard_timeout=hard_timeout, priority=priority, cookie=rule_num, resubmit_table=resubmit_table)
def _get_classify_rule_flow_msg(self, imsi, flow, rule_num, priority, ul_qos, dl_qos, hard_timeout, rule_id): """ Install a flow from a rule. If the flow action is DENY, then the flow will drop the packet. Otherwise, the flow classifies the packet with its matched rule and injects the rule num into the packet's register. """ flow_match = flow_match_to_magma_match(flow.match) flow_match.imsi = encode_imsi(imsi) flow_match_actions = self._get_classify_rule_of_actions( flow, rule_num, imsi, ul_qos, dl_qos, rule_id) if flow.action == flow.DENY: return flows.get_add_drop_flow_msg(self._datapath, self.tbl_num, flow_match, flow_match_actions, hard_timeout=hard_timeout, priority=priority, cookie=rule_num) if self._enforcement_stats_scratch: return flows.get_add_resubmit_current_service_flow_msg( self._datapath, self.tbl_num, flow_match, flow_match_actions, hard_timeout=hard_timeout, priority=priority, cookie=rule_num, resubmit_table=self._enforcement_stats_scratch) # If enforcement stats has not claimed a scratch table, resubmit # directly to the next app. return flows.get_add_resubmit_next_service_flow_msg( self._datapath, self.tbl_num, flow_match, flow_match_actions, hard_timeout=hard_timeout, priority=priority, cookie=rule_num, resubmit_table=self.next_main_table)
def add_classify_flow(self, match, app): try: match = flow_match_to_magma_match(match) except FlowMatchError as e: self.logger.error(e) return False parser = self._datapath.ofproto_parser app_id = appMap.get(app, 0) if app_id != 0: actions = [parser.NXActionRegLoad2(dst=DPI_REG, value=app_id)] flows.add_resubmit_next_service_flow( self._datapath, self.tbl_num, match, actions, priority=flows.DEFAULT_PRIORITY, resubmit_table=self.next_table) else: self.logger.error("Unrecognized app name %s", app) return True
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()
def test_enforcement_restart(self): """ Adds rules using the setup feature 1) Empty SetupFlowsRequest - assert default flows 2) Add 2 imsis, add 2 policies(sub1_rule_temp, sub2_rule_keep), - assert everything is properly added 3) Same imsis 1 new policy, 1 old (sub2_new_rule, sub2_rule_keep) - assert everything is properly added 4) Empty SetupFlowsRequest - assert default flows """ fake_controller_setup( enf_controller=self.enforcement_controller, enf_stats_controller=self.enforcement_stats_controller, startup_flow_controller=self.startup_flows_contoller) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager, 'default_flows') with snapshot_verifier: pass imsi1 = 'IMSI010000000088888' imsi2 = 'IMSI010000000012345' sub2_ip = '192.168.128.74' flow_list1 = [ FlowDescription(match=FlowMatch(ipv4_dst='45.10.0.0/24', direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT), FlowDescription(match=FlowMatch(ipv4_dst='45.11.0.0/24', direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT) ] flow_list2 = [ FlowDescription(match=FlowMatch(ipv4_dst='10.10.1.0/24', direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT) ] policies1 = [ PolicyRule(id='sub1_rule_temp', priority=2, flow_list=flow_list1), ] policies2 = [ PolicyRule(id='sub2_rule_keep', priority=3, flow_list=flow_list2) ] enf_stat_name = [imsi1 + '|sub1_rule_temp', imsi2 + '|sub2_rule_keep'] self.service_manager.session_rule_version_mapper.update_version( imsi1, 'sub1_rule_temp') self.service_manager.session_rule_version_mapper.update_version( imsi2, 'sub2_rule_keep') setup_flows_request = SetupFlowsRequest(requests=[ ActivateFlowsRequest(sid=SIDUtils.to_pb(imsi1), dynamic_rules=policies1), ActivateFlowsRequest(sid=SIDUtils.to_pb(imsi2), dynamic_rules=policies2), ], epoch=global_epoch) fake_controller_setup( enf_controller=self.enforcement_controller, enf_stats_controller=self.enforcement_stats_controller, startup_flow_controller=self.startup_flows_contoller, setup_flows_request=setup_flows_request) sub_context = RyuDirectSubscriberContext(imsi2, sub2_ip, self.enforcement_controller, self._enforcement_tbl_num) isolator = RyuDirectTableIsolator( RyuForwardFlowArgsBuilder.from_subscriber( sub_context.cfg).build_requests(), self.testing_controller) pkt_sender = ScapyPacketInjector(self.IFACE) packets = IPPacketBuilder()\ .set_ip_layer('10.10.1.8/20', sub2_ip)\ .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00")\ .build() pkts_sent = 4096 pkts_matched = 256 flow_query = FlowQuery(self._enforcement_tbl_num, self.testing_controller, match=flow_match_to_magma_match( flow_list2[0].match)) flow_verifier = FlowVerifier([ FlowTest( FlowQuery(self._enforcement_tbl_num, self.testing_controller), pkts_sent), FlowTest(flow_query, pkts_matched) ], self._wait_func(enf_stat_name)) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager, 'before_restart') with isolator, flow_verifier, snapshot_verifier: pkt_sender.send(packets) flow_verifier.verify() flow_list1 = [ FlowDescription(match=FlowMatch(ipv4_dst='24.10.0.0/24', direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT) ] policies = [ PolicyRule(id='sub2_new_rule', priority=2, flow_list=flow_list1), PolicyRule(id='sub2_rule_keep', priority=3, flow_list=flow_list2) ] self.service_manager.session_rule_version_mapper.update_version( imsi2, 'sub2_new_rule') enf_stat_name = [imsi2 + '|sub2_new_rule', imsi2 + '|sub2_rule_keep'] setup_flows_request = SetupFlowsRequest(requests=[ ActivateFlowsRequest(sid=SIDUtils.to_pb(imsi2), dynamic_rules=policies) ], epoch=global_epoch) fake_controller_setup( enf_controller=self.enforcement_controller, enf_stats_controller=self.enforcement_stats_controller, startup_flow_controller=self.startup_flows_contoller, setup_flows_request=setup_flows_request) flow_verifier = FlowVerifier([FlowTest(flow_query, pkts_matched)], self._wait_func(enf_stat_name)) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager, 'after_restart') with flow_verifier, snapshot_verifier: pass fake_controller_setup( enf_controller=self.enforcement_controller, enf_stats_controller=self.enforcement_stats_controller, startup_flow_controller=self.startup_flows_contoller) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager, 'default_flows') with snapshot_verifier: pass
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()
def test_subscriber_two_policies(self): """ Add 2 policies to subscriber Assert: Packets are properly matched with the 'match' policy The total packet delta in the table is from the above match """ fake_controller_setup(self.enforcement_controller) imsi = 'IMSI208950000000001' sub_ip = '192.168.128.74' flow_list1 = [ FlowDescription(match=FlowMatch( ip_src=convert_ipv4_str_to_ip_proto('15.0.0.0/24'), direction=FlowMatch.DOWNLINK), action=FlowDescription.DENY) ] flow_list2 = [ FlowDescription(match=FlowMatch(ip_proto=6, direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT) ] policies = [ PolicyRule(id='match', priority=2, flow_list=flow_list1), PolicyRule(id='no_match', priority=2, flow_list=flow_list2) ] pkts_sent = 42 self._static_rule_dict[policies[0].id] = policies[0] self._static_rule_dict[policies[1].id] = policies[1] # ============================ Subscriber ============================ sub_context = RyuDirectSubscriberContext(imsi, sub_ip, self.enforcement_controller, self._tbl_num) \ .add_static_rule(policies[0].id)\ .add_static_rule(policies[1].id) isolator = RyuDirectTableIsolator( RyuForwardFlowArgsBuilder.from_subscriber( sub_context.cfg).build_requests(), self.testing_controller) pkt_sender = ScapyPacketInjector(self.IFACE) packet = IPPacketBuilder()\ .set_ip_layer(sub_ip, '15.0.0.8')\ .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00")\ .build() flow_query = FlowQuery(self._tbl_num, self.testing_controller, match=flow_match_to_magma_match( flow_list1[0].match)) # =========================== Verification =========================== # Verify aggregate table stats, subscriber 1 'match' rule pkt count flow_verifier = FlowVerifier([ FlowTest(FlowQuery(self._tbl_num, self.testing_controller), pkts_sent), FlowTest(flow_query, pkts_sent) ], lambda: wait_after_send(self.testing_controller)) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager) with isolator, sub_context, flow_verifier, snapshot_verifier: pkt_sender.send(packet, pkts_sent) flow_verifier.verify()
def test_two_subscribers(self): """ Add 2 subscribers at the same time Assert: For subcriber1 the packets are matched to the proper policy For subcriber2 the packets are matched to the proper policy The total packet delta in the table is from the above matches """ fake_controller_setup(self.enforcement_controller) pkt_sender = ScapyPacketInjector(self.IFACE) ip_match = [ FlowDescription(match=FlowMatch( ip_src=convert_ipv4_str_to_ip_proto('8.8.8.0/24'), direction=1), action=1) ] tcp_match = [ FlowDescription(match=FlowMatch(ip_proto=6, direction=FlowMatch.DOWNLINK), action=FlowDescription.DENY) ] self._static_rule_dict['t'] = PolicyRule(id='t', priority=2, flow_list=ip_match) # =========================== Subscriber 1 =========================== sub_context1 = RyuDirectSubscriberContext( 'IMSI208950001111111', '192.168.128.5', self.enforcement_controller, self._tbl_num).add_static_rule('t') isolator1 = RyuDirectTableIsolator( RyuForwardFlowArgsBuilder.from_subscriber( sub_context1.cfg).build_requests(), self.testing_controller) packet_ip = IPPacketBuilder()\ .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00")\ .set_ip_layer(sub_context1.cfg.ip, '8.8.8.8')\ .build() s1_pkts_sent = 29 pkts_to_send = [PktsToSend(packet_ip, s1_pkts_sent)] flow_query1 = FlowQuery(self._tbl_num, self.testing_controller, match=flow_match_to_magma_match( ip_match[0].match)) s1 = SubTest(sub_context1, isolator1, FlowTest(flow_query1, s1_pkts_sent)) # =========================== Subscriber 2 =========================== sub_context2 = RyuDirectSubscriberContext( 'IMSI911500451242001', '192.168.128.100', self.enforcement_controller, self._tbl_num).add_dynamic_rule( PolicyRule(id='qqq', priority=2, flow_list=tcp_match)) isolator2 = RyuDirectTableIsolator( RyuForwardFlowArgsBuilder.from_subscriber( sub_context2.cfg).build_requests(), self.testing_controller) packet_tcp = TCPPacketBuilder()\ .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00")\ .set_ip_layer(sub_context2.cfg.ip, '15.0.0.8')\ .build() s2_pkts_sent = 18 pkts_to_send.append(PktsToSend(packet_tcp, s2_pkts_sent)) flow_query2 = FlowQuery(self._tbl_num, self.testing_controller, match=flow_match_to_magma_match( tcp_match[0].match)) s2 = SubTest(sub_context2, isolator2, FlowTest(flow_query2, s2_pkts_sent)) # =========================== Verification =========================== # Verify aggregate table stats, subscriber 1 & 2 flows packet matches pkts = s1_pkts_sent + s2_pkts_sent flow_verifier = FlowVerifier([ FlowTest(FlowQuery(self._tbl_num, self.testing_controller), pkts), s1.flowtest_list, s2.flowtest_list ], lambda: wait_after_send(self.testing_controller)) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager) with s1.isolator, s1.context, s2.isolator, s2.context, flow_verifier, \ snapshot_verifier: for pkt in pkts_to_send: pkt_sender.send(pkt.pkt, pkt.num) flow_verifier.verify()
def add_classify_flow(self, flow_match, app: str, service_type: str): """ Parse DPI output and set the register for future packets matching this flow. APP is split into tokens as the top level app is not supported, but the parent protocol might be. Example we care about google traffic, but don't neccessarily want to classify every specific google service. """ try: match = flow_match_to_magma_match(flow_match) except FlowMatchError as e: self.logger.error(e) return parser = self._datapath.ofproto_parser tokens = app.split('.') # We could be sent an app that we don't care about, just ignore it if not any(app for app in tokens if app in PARENT_PROTOS or app in APP_PROTOS): self.logger.debug("Unrecognized app name %s", app) return app_match = [app for app in tokens if app in APP_PROTOS] if len(app_match) > 1: self.logger.warning("Found more than 1 app match in %s", app) return if (len(app_match) == 1): app_id = APP_PROTOS[app_match[0]] self.logger.debug("Classified %s-%s as %d", app, service_type, app_id) else: parent_match = [app for app in tokens if app in PARENT_PROTOS] # This shoudn't happen as we confirmed the match exists if len(parent_match) == 0: self.logger.warning("Didn't find a match for app name %s", app) return if len(parent_match) > 1: self.logger.warning("Found more than 1 parent app match in %s", app) app_id = PARENT_PROTOS[parent_match[0]] service_id = SERVICE_IDS['other'] for serv in SERVICE_IDS: if serv in service_type.lower(): service_id = SERVICE_IDS[serv] break app_id += service_id self.logger.error("Classified %s-%s as %d", app, service_type, app_id) actions = [ parser.OFPActionOutput(self._mon_port_number), parser.NXActionRegLoad2(dst=DPI_REG, value=app_id) ] flows.add_resubmit_next_service_flow(self._datapath, self.tbl_num, match, actions, priority=flows.DEFAULT_PRIORITY, resubmit_table=self.next_table, idle_timeout=self._idle_timeout)
def test_rule_reactivation(self): """ Adds a policy to a subscriber, deletes it by incrementing the version, and add it back. Verifies that the usage stats is correctly reported, the old flows are deleted, and the new flows are installed. Assert: UPLINK policy matches 128 packets (*34 = 4352 bytes) Old flows are deleted New flows are installed No other stats are reported """ fake_controller_setup(self.enforcement_controller, self.enforcement_stats_controller) imsi = 'IMSI001010000000013' sub_ip = '192.168.128.74' num_pkts_tx_match = 128 flow_list = [ FlowDescription(match=FlowMatch(ipv4_dst='45.10.0.0/25', direction=FlowMatch.UPLINK), action=FlowDescription.PERMIT) ] policy = PolicyRule(id='rule1', priority=3, flow_list=flow_list) enf_stat_name = imsi + '|rule1' self.service_manager.session_rule_version_mapper.update_version( imsi, 'rule1') version = \ self.service_manager.session_rule_version_mapper.get_version( imsi, 'rule1') """ Setup subscriber, setup table_isolation to fwd pkts """ self._static_rule_dict[policy.id] = policy sub_context = RyuDirectSubscriberContext( imsi, sub_ip, self.enforcement_controller, self._main_tbl_num, self.enforcement_stats_controller).add_static_rule(policy.id) isolator = RyuDirectTableIsolator( RyuForwardFlowArgsBuilder.from_subscriber( sub_context.cfg).build_requests(), self.testing_controller) """ Create a packet """ pkt_sender = ScapyPacketInjector(self.IFACE) packet = IPPacketBuilder() \ .set_ip_layer('45.10.0.0/20', sub_ip) \ .set_ether_layer(self.MAC_DEST, "00:00:00:00:00:00") \ .build() # =========================== Verification =========================== rule_num = self.enforcement_stats_controller._rule_mapper \ .get_or_create_rule_num(policy.id) enf_query = FlowQuery(self._main_tbl_num, self.testing_controller, match=flow_match_to_magma_match( FlowMatch(ipv4_dst='45.10.0.0/25', direction=FlowMatch.UPLINK)), cookie=rule_num) es_old_version_query = FlowQuery(self._scratch_tbl_num, self.testing_controller, match=MagmaMatch( imsi=encode_imsi(imsi), reg2=rule_num, rule_version=version), cookie=rule_num) es_new_version_query = FlowQuery(self._scratch_tbl_num, self.testing_controller, match=MagmaMatch( imsi=encode_imsi(imsi), reg2=rule_num, rule_version=version + 1), cookie=rule_num) packet_wait = FlowVerifier([], self._wait_func([enf_stat_name])) """ Verify that flows are properly deleted """ verifier = FlowVerifier([ FlowTest(es_old_version_query, 0, flow_count=0), FlowTest(es_new_version_query, num_pkts_tx_match, flow_count=2), FlowTest(enf_query, num_pkts_tx_match, flow_count=1), ], self._wait_func([enf_stat_name])) snapshot_verifier = SnapshotVerifier(self, self.BRIDGE, self.service_manager) """ Send a packet, then deactivate and reactivate the same rule and send a packet. Wait until it is received by ovs and enf stats. """ with isolator, sub_context, verifier, snapshot_verifier: with packet_wait: self.enforcement_stats_controller._report_usage.reset_mock() pkt_sender.send(packet) self.enforcement_stats_controller._report_usage.reset_mock() self.service_manager.session_rule_version_mapper. \ update_version(imsi, 'rule1') self.enforcement_controller.deactivate_rules(imsi, [policy.id]) self.enforcement_controller.activate_rules(imsi, sub_ip, [policy.id], []) self.enforcement_stats_controller.activate_rules( imsi, sub_ip, [policy.id], []) pkt_sender.send(packet) verifier.verify() stats = get_enforcement_stats( self.enforcement_stats_controller._report_usage.call_args_list) """ Verify both packets are reported after reactivation. """ self.assertEqual(stats[enf_stat_name].sid, imsi) self.assertEqual(stats[enf_stat_name].rule_id, "rule1") self.assertEqual(stats[enf_stat_name].bytes_rx, 0) # TODO Figure out why this one fails. #self.assertEqual(stats[enf_stat_name].bytes_tx, # num_pkts_tx_match * len(packet)) self.assertEqual(len(stats), 1)
def _get_classify_rule_flow_msgs(self, imsi, msisdn: bytes, uplink_tunnel: int, ip_addr, apn_ambr, flow, rule_num, priority, qos, hard_timeout, rule_id, app_name, app_service_type, next_table, version, qos_mgr, copy_table, urls: List[str] = None): """ Install a flow from a rule. If the flow action is DENY, then the flow will drop the packet. Otherwise, the flow classifies the packet with its matched rule and injects the rule num into the packet's register. """ parser = self._datapath.ofproto_parser flow_match = flow_match_to_magma_match(flow.match, ip_addr) flow_match.imsi = encode_imsi(imsi) flow_match_actions, instructions = self._get_action_for_rule( flow, rule_num, imsi, ip_addr, apn_ambr, qos, rule_id, version, qos_mgr) msgs = [] if app_name: # We have to allow initial traffic to pass through, before it gets # classified by DPI, flow match set app_id to unclassified flow_match.app_id = UNCLASSIFIED_PROTO_ID passthrough_actions = flow_match_actions + \ [parser.NXActionRegLoad2(dst=SCRATCH_REGS[1], value=IGNORE_STATS)] msgs.append( flows.get_add_resubmit_current_service_flow_msg( self._datapath, self.tbl_num, flow_match, passthrough_actions, hard_timeout=hard_timeout, priority=Utils.UNCLASSIFIED_ALLOW_PRIORITY, cookie=rule_num, copy_table=copy_table, resubmit_table=next_table)) flow_match.app_id = get_app_id( PolicyRule.AppName.Name(app_name), PolicyRule.AppServiceType.Name(app_service_type), ) # For DROP flow just send to stats table, it'll get dropped there if flow.action == flow.DENY: flow_match_actions = flow_match_actions + \ [parser.NXActionRegLoad2(dst=SCRATCH_REGS[1], value=DROP_FLOW_STATS)] msgs.append( flows.get_add_resubmit_current_service_flow_msg( self._datapath, self.tbl_num, flow_match, flow_match_actions, hard_timeout=hard_timeout, priority=priority, cookie=rule_num, resubmit_table=copy_table)) else: msgs.append( flows.get_add_resubmit_current_service_flow_msg( self._datapath, self.tbl_num, flow_match, flow_match_actions, instructions=instructions, hard_timeout=hard_timeout, priority=priority, cookie=rule_num, copy_table=copy_table, resubmit_table=next_table)) if self.proxy_controller: ue_ip = ipv4_address_to_str(ip_addr) ip_dst = get_flow_ip_dst(flow.match) direction = get_direction_for_match(flow.match) proxy_msgs = self.proxy_controller.get_subscriber_he_flows( rule_id, direction, ue_ip, uplink_tunnel, ip_dst, rule_num, urls, imsi, msisdn) msgs.extend(proxy_msgs) return msgs