def _add_dhcp_passthrough_flows(self, sid, mac_addr): ofproto, parser = self._datapath.ofproto, self._datapath.ofproto_parser # Set so packet skips enforcement controller action = load_passthrough(parser) uplink_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_UDP, udp_src=68, udp_dst=67, eth_src=mac_addr) self._add_resubmit_flow(sid, uplink_match, action, flows.PASSTHROUGH_PRIORITY) downlink_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_UDP, udp_src=67, udp_dst=68, eth_dst=mac_addr) # Set so triggers packetin and we can learn the ip to do arp response self._add_resubmit_flow(sid, downlink_match, action, flows.PASSTHROUGH_PRIORITY, next_table=self._dhcp_learn_scratch) # Install default flow for dhcp learn scratch imsi_match = MagmaMatch(imsi=encode_imsi(sid)) flows.add_output_flow(self._datapath, self._dhcp_learn_scratch, match=imsi_match, actions=[], priority=flows.PASSTHROUGH_PRIORITY, output_port=ofproto.OFPP_CONTROLLER, copy_table=self.next_table, max_len=ofproto.OFPCML_NO_BUFFER)
def _deactivate_flows_for_subscriber(self, imsi): """ Deactivate all rules for a subscriber, ending any enforcement """ match = MagmaMatch(imsi=encode_imsi(imsi)) flows.delete_flow(self._datapath, self.tbl_num, match) self._redirect_manager.deactivate_flows_for_subscriber( self._datapath, imsi) self._qos_map.del_subscriber_queues(imsi)
def _add_resubmit_flow(self, sid, match, action=None, priority=flows.DEFAULT_PRIORITY, next_table=None, tbl_num=None): parser = self._datapath.ofproto_parser if action is None: actions = [] else: actions = [action] if next_table is None: next_table = self.next_table if tbl_num is None: tbl_num = self.tbl_num # Add IMSI metadata actions.append( parser.NXActionRegLoad2(dst=IMSI_REG, value=encode_imsi(sid))) flows.add_resubmit_next_service_flow(self._datapath, tbl_num, match, actions=actions, priority=priority, resubmit_table=next_table)
def add_ue_sample_flow(self, imsi: str, msisdn: str, apn_mac_addr: str, apn_name: str) -> None: """ Install a flow to sample packets for IPFIX for specific imsi Args: imsi (string): subscriber to install rule for msisdn (string): msisdn string apn_mac_addr (string): AP mac address string apn_name (string): AP name """ if self._datapath is None: self.logger.error('Datapath not initialized for adding flows') return imsi_hex = hex(encode_imsi(imsi)) pdp_start_epoch = int(time.time()) action_str = ( 'ovs-ofctl add-flow {} "table={},priority={},metadata={},' 'actions=sample(probability={},collector_set_id={},' 'obs_domain_id={},obs_point_id={},apn_mac_addr={},msisdn={},' 'apn_name=\\\"{}\\\",pdp_start_epoch={},sampling_port={}),' 'resubmit(,{})"' ).format( self._bridge_name, self.tbl_num, flows.UE_FLOW_PRIORITY, imsi_hex, self.ipfix_config.probability, self.ipfix_config.collector_set_id, self.ipfix_config.obs_domain_id, self.ipfix_config.obs_point_id, apn_mac_addr.replace("-", ":"), msisdn, apn_name, pdp_start_epoch, self.ipfix_config.sampling_port, self.next_main_table ) try: subprocess.Popen(action_str, shell=True).wait() except subprocess.CalledProcessError as e: raise Exception('Error: {} failed with: {}'.format(action_str, e))
def _deactivate_flows_for_subscriber(self, imsi, ip_addr): """ Deactivate all rules for specified subscriber session """ ip_match_in = get_ue_ip_match_args(ip_addr, Direction.IN) match = MagmaMatch(eth_type=get_eth_type(ip_addr), imsi=encode_imsi(imsi), **ip_match_in) flows.delete_flow(self._datapath, self.tbl_num, match) ip_match_out = get_ue_ip_match_args(ip_addr, Direction.OUT) match = MagmaMatch(eth_type=get_eth_type(ip_addr), imsi=encode_imsi(imsi), **ip_match_out) flows.delete_flow(self._datapath, self.tbl_num, match) self._redirect_manager.deactivate_flows_for_subscriber( self._datapath, imsi) self._qos_mgr.remove_subscriber_qos(imsi)
def _deactivate_flow_for_rule(self, imsi, ip_addr, rule_id): """ Deactivate a specific rule using the flow cookie for a subscriber Args: imsi (string): subscriber id rule_id (string): policy rule id """ try: num = self._rule_mapper.get_rule_num(rule_id) except KeyError: self.logger.error('Could not find rule id %s', rule_id) return cookie, mask = (num, flows.OVS_COOKIE_MATCH_ALL) match = MagmaMatch(imsi=encode_imsi(imsi)) flows.delete_flow( self._datapath, self.tbl_num, match, cookie=cookie, cookie_mask=mask, ) self._redirect_manager.deactivate_flow_for_rule( self._datapath, imsi, num, ) if self._qos_mgr: self._qos_mgr.remove_subscriber_qos(imsi, num) self._remove_he_flows(ip_addr, rule_id)
def _install_dns_flows(self, datapath, imsi, rule, rule_num, priority): """ Installs flows that allow DNS queries to path thorugh. """ parser = datapath.ofproto_parser of_note = parser.NXActionNote(list(rule.id.encode())) actions = [ parser.NXActionRegLoad2(dst='reg2', value=rule_num), of_note, ] matches = [] # Install UDP flows for DNS matches.append( MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_UDP, udp_src=53, direction=Direction.IN, imsi=encode_imsi(imsi))) matches.append( MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_UDP, udp_dst=53, direction=Direction.OUT, imsi=encode_imsi(imsi))) # Install TCP flows for DNS matches.append( MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, tcp_src=53, direction=Direction.IN, imsi=encode_imsi(imsi))) matches.append( MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, tcp_dst=53, direction=Direction.OUT, imsi=encode_imsi(imsi))) for match in matches: flows.add_resubmit_next_service_flow( datapath, self.main_tbl_num, match, actions, priority=priority, cookie=rule_num, hard_timeout=rule.hard_timeout, resubmit_table=self.next_table)
def _add_subscriber_flow(self, imsi: str, ue_mac: str, has_quota: bool): """ Redirect the UE flow to the dedicated flask server. On return traffic rewrite the IP/port so the redirection is seamless. """ parser = self._datapath.ofproto_parser if has_quota: tcp_dst = self.config.has_quota_port else: tcp_dst = self.config.no_quota_port match = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.OUT, vlan_vid=(0x1000, 0x1000), ipv4_dst=self.config.quota_check_ip) actions = [ parser.OFPActionSetField(ipv4_dst=self.config.bridge_ip), parser.OFPActionSetField(eth_dst=self.config.cwf_bridge_mac), parser.OFPActionSetField(tcp_dst=tcp_dst), parser.OFPActionPopVlan() ] flows.add_output_flow(self._datapath, self.tbl_num, match, actions, priority=flows.UE_FLOW_PRIORITY, output_port=OFPP_LOCAL) # For traffic from the check quota server rewrite src ip and port match = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.IN, ipv4_src=self.config.bridge_ip) actions = [ parser.OFPActionSetField(ipv4_src=self.config.quota_check_ip), parser.OFPActionSetField(eth_dst=ue_mac), parser.OFPActionSetField(tcp_src=80) ] flows.add_resubmit_next_service_flow( self._datapath, self.tbl_num, match, actions, priority=flows.DEFAULT_PRIORITY, resubmit_table=self.next_main_table)
def _deactivate_flow_for_rule(self, imsi, ip_addr, rule_id): """ Deactivate a specific rule using the flow cookie for a subscriber """ try: num = self._rule_mapper.get_rule_num(rule_id) except KeyError: self.logger.error('Could not find rule id %s', rule_id) return if num is None: self.logger.error('Rule num is None for rule %s', rule_id) return cookie, mask = (num, flows.OVS_COOKIE_MATCH_ALL) ip_match_in = get_ue_ip_match_args(ip_addr, Direction.IN) match = MagmaMatch( eth_type=get_eth_type(ip_addr), imsi=encode_imsi(imsi), **ip_match_in, ) flows.delete_flow( self._datapath, self.tbl_num, match, cookie=cookie, cookie_mask=mask, ) ip_match_out = get_ue_ip_match_args(ip_addr, Direction.OUT) match = MagmaMatch( eth_type=get_eth_type(ip_addr), imsi=encode_imsi(imsi), **ip_match_out, ) flows.delete_flow( self._datapath, self.tbl_num, match, cookie=cookie, cookie_mask=mask, ) self._redirect_manager.deactivate_flow_for_rule( self._datapath, imsi, num, ) self._qos_mgr.remove_subscriber_qos(imsi, num) self._remove_he_flows(ip_addr, rule_id, num)
def _get_classify_rule_flow_msgs(self, imsi, flow, rule_num, priority, qos, hard_timeout, rule_id, app_name, app_service_type): """ 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, instructions = self._get_classify_rule_of_actions( flow, rule_num, imsi, qos, rule_id) 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 # Set 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=self._enforcement_stats_scratch)) 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=self._enforcement_stats_scratch)) return msgs
def add_ue_sample_flow( self, imsi: str, msisdn: str, apn_mac_addr: str, apn_name: str, pdp_start_time: int, ) -> None: """ Install a flow to sample packets for IPFIX for specific imsi Args: imsi (string): subscriber to install rule for msisdn (string): msisdn string apn_mac_addr (string): AP mac address string apn_name (string): AP name """ if self._datapath is None: self.logger.error('Datapath not initialized for adding flows') return if not self.ipfix_config.enabled: # TODO logging higher than debug here will provide too much noise # possible fix is making ipfix a dynamic service enabled from orc8r self.logger.debug('IPFIX export dst not setup for adding flows') return parser = self._datapath.ofproto_parser if not apn_mac_addr or '-' not in apn_mac_addr: apn_mac_bytes = [0, 0, 0, 0, 0, 0] else: apn_mac_bytes = [int(a, 16) for a in apn_mac_addr.split('-')] if not msisdn: msisdn = 'no_msisdn' actions = [ parser.NXActionSample2( probability=self.ipfix_config.probability, collector_set_id=self.ipfix_config.collector_set_id, obs_domain_id=self.ipfix_config.obs_domain_id, obs_point_id=self.ipfix_config.obs_point_id, apn_mac_addr=apn_mac_bytes, msisdn=msisdn.encode('ascii'), apn_name=apn_name.encode('ascii'), pdp_start_epoch=pdp_start_time.to_bytes(8, byteorder='little'), sampling_port=self.ipfix_config.sampling_port, ), ] match = MagmaMatch(imsi=encode_imsi(imsi)) if self._dpi_enabled or self._conntrackd_enabled: flows.add_drop_flow( self._datapath, self._ipfix_sample_tbl_num, match, actions, priority=flows.UE_FLOW_PRIORITY, ) else: flows.add_resubmit_next_service_flow( self._datapath, self._ipfix_sample_tbl_num, match, actions, priority=flows.UE_FLOW_PRIORITY, resubmit_table=self.next_main_table, )
def _generate_rule_match(imsi, rule_num, version, direction): """ Return a MagmaMatch that matches on the rule num and the version. """ return MagmaMatch(imsi=encode_imsi(imsi), direction=direction, reg2=rule_num, rule_version=version)
def deactivate_flow_for_rule(self, datapath, imsi, rule_num): """ Deactivate a specific rule using the flow cookie for a subscriber """ cookie, mask = (rule_num, flows.OVS_COOKIE_MATCH_ALL) match = MagmaMatch(imsi=encode_imsi(imsi)) flows.delete_flow(datapath, self._scratch_tbl_num, match, cookie=cookie, cookie_mask=mask)
def _set_subscriber_match(self, sub_info): """ Sets up match/action for subscriber flows """ if sub_info.ip.count(":") >= 2: self._match_kwargs = {"eth_type": ether_types.ETH_TYPE_IPV6} else: self._match_kwargs = {"eth_type": ether_types.ETH_TYPE_IP} return self.set_ip(sub_info.ip) \ .set_reg_value(IMSI_REG, encode_imsi(sub_info.imsi))
def _get_default_flow_msg_for_subscriber(self, imsi): match = MagmaMatch(imsi=encode_imsi(imsi)) actions = [] return flows.get_add_drop_flow_msg(self._datapath, self.tbl_num, match, actions, priority=self.ENFORCE_DROP_PRIORITY)
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 """ 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 _ng_tunnel_update(self, pdr_entry: PDRRuleEntry, subscriber_id: str) -> bool: ret = self._classifier_app.gtp_handler( pdr_entry.pdr_state, pdr_entry.precedence, pdr_entry.local_f_teid, pdr_entry.far_action.o_teid, pdr_entry.ue_ip_addr, pdr_entry.far_action.gnb_ip_addr, encode_imsi(subscriber_id), True) return ret
def _install_mirror_flows(self, imsis): parser = self._datapath.ofproto_parser for imsi in imsis: self.logger.debug("Enabling LI tracking for IMSI %s", imsi) match = MagmaMatch(imsi=encode_imsi(imsi)) actions = [parser.OFPActionOutput(self._li_dst_port_num)] flows.add_resubmit_next_service_flow(self._datapath, self.tbl_num, match, actions, priority=flows.DEFAULT_PRIORITY, resubmit_table=self.next_table)
def _save_version_unsafe( self, imsi: str, ip_addr: str, rule_id: str, version, ): key = self._get_json_key(encode_imsi(imsi), ip_addr, rule_id) self._version_by_imsi_and_rule[key] = version
def deactivate_flows_for_subscriber(self, datapath, imsi): """ Deactivate all rules for a subscriber """ flows.delete_flow( datapath, self._scratch_tbl_num, MagmaMatch(imsi=encode_imsi(imsi)), )
def create_ue_session_entry(tunnel_msg: UESessionSet) -> UESessionSet: """Do create named tuple from input message Args: tunnel_msg: Message from MME Returns: UESessionSet """ sid = 0 ue_ipv4_address = None ue_ipv6_address = None enb_ipv4_address = None apn = None if tunnel_msg.subscriber_id.id: sid = encode_imsi(tunnel_msg.subscriber_id.id) if tunnel_msg.ue_ipv4_address.address: addr_str = socket.inet_ntop( socket.AF_INET, tunnel_msg.ue_ipv4_address.address, ) ue_ipv4_address = IPAddress( version=tunnel_msg.ue_ipv4_address.version, address=addr_str.encode('utf8'), ) if tunnel_msg.ue_ipv6_address.address: addr_str = socket.inet_ntop( socket.AF_INET6, tunnel_msg.ue_ipv6_address.address, ) ue_ipv6_address = IPAddress( version=tunnel_msg.ue_ipv6_address.version, address=addr_str.encode('utf8'), ) if tunnel_msg.enb_ip_address.address: enb_ipv4_address = ipaddress.ip_address( tunnel_msg.enb_ip_address.address, ) if tunnel_msg.apn: apn = tunnel_msg.apn return ( UESessionEntry( sid=sid, precedence=tunnel_msg.precedence, ue_ipv4_address=ue_ipv4_address, ue_ipv6_address=ue_ipv6_address, enb_ip_address=enb_ipv4_address, apn=apn, vlan=tunnel_msg.vlan, in_teid=tunnel_msg.in_teid, out_teid=tunnel_msg.out_teid, ue_session_state=tunnel_msg.ue_session_state.ue_config_state, ip_flow_dl=tunnel_msg.ip_flow_dl, ) )
def _generate_rule_match(imsi, ip_addr, rule_num, version, direction): """ Return a MagmaMatch that matches on the rule num and the version. """ ip_match = get_ue_ip_match_args(ip_addr, direction) return MagmaMatch(imsi=encode_imsi(imsi), eth_type=get_eth_type(ip_addr), direction=direction, rule_num=rule_num, rule_version=version, **ip_match)
def _delete_resubmit_flow(self, sid, match): parser = self._datapath.ofproto_parser tbl_num = self._service_manager.get_table_num(self.APP_NAME) # Add IMSI metadata actions = [ parser.NXActionRegLoad2(dst=IMSI_REG, value=encode_imsi(sid)) ] flows.delete_flow(self._datapath, tbl_num, match, actions=actions)
def get_version(self, imsi: str, rule_id: str) -> int: """ Returns the version number given a subscriber and a rule. """ key = self._get_json_key(encode_imsi(imsi), rule_id) with self._lock: version = self._version_by_imsi_and_rule.get(key) if version is None: version = 0 return version
def _install_not_processed_flows(self, datapath, imsi, ip_addr, rule, rule_num, priority): """ Redirect all traffic to the scratch table to only allow redirected http traffic to go through, the rest will be dropped. reg0 is used as a boolean to know whether the drop rule was processed. """ parser = datapath.ofproto_parser of_note = parser.NXActionNote(list(rule.id.encode())) match = MagmaMatch(imsi=encode_imsi(imsi), direction=Direction.OUT, reg0=self.REDIRECT_NOT_PROCESSED, eth_type=ether_types.ETH_TYPE_IP, ipv4_src=ip_addr) action = [of_note] flows.add_resubmit_current_service_flow( datapath, self.main_tbl_num, match, action, priority=priority, cookie=rule_num, hard_timeout=rule.hard_timeout, resubmit_table=self._scratch_tbl_num) match = MagmaMatch(imsi=encode_imsi(imsi), direction=Direction.OUT, reg0=self.REDIRECT_PROCESSED, eth_type=ether_types.ETH_TYPE_IP, ipv4_src=ip_addr) action = [of_note] flows.add_resubmit_next_service_flow(datapath, self.main_tbl_num, match, action, priority=priority, cookie=rule_num, hard_timeout=rule.hard_timeout, copy_table=self.stats_table, resubmit_table=self.next_table)
def _deactivate_flows_for_subscriber(self, imsi, _): """ Deactivate all rules for a subscriber, ending any enforcement Args: imsi (string): subscriber id """ match = MagmaMatch(imsi=encode_imsi(imsi)) flows.delete_flow(self._datapath, self.tbl_num, match) self._redirect_manager.deactivate_flows_for_subscriber(self._datapath, imsi) self._qos_mgr.remove_subscriber_qos(imsi)
def update_version(self, imsi: str, rule_id: Optional[int] = None): """ Increment the version number for a given subscriber and rule. If the rule id is not specified, then all rules for the subscriber will be incremented. """ with self._lock: if rule_id is None: for rule in self._version_by_imsi_and_rule[encode_imsi(imsi)]: self._update_version_unsafe(imsi, rule) else: self._update_version_unsafe(imsi, rule_id)
def _install_default_flow_for_subscriber(self, imsi): """ Add a low priority flow to drop a subscriber's traffic in the event that all rules have been deactivated. Args: imsi (string): subscriber id """ match = MagmaMatch(imsi=encode_imsi(imsi)) actions = [] # empty options == drop flows.add_drop_flow(self._datapath, self.tbl_num, match, actions, priority=self.ENFORCE_DROP_PRIORITY)
def _delete_resubmit_flow(self, sid, match, action=None): parser = self._datapath.ofproto_parser if action is None: actions = [] else: actions = [action] # Add IMSI metadata actions.append( parser.NXActionRegLoad2(dst=IMSI_REG, value=encode_imsi(sid))) flows.delete_flow(self._datapath, self.tbl_num, match, actions=actions)
def _ng_tunnel_update(self, pdr_entry: PDRRuleEntry, subscriber_id: str) -> bool: if pdr_entry.pdr_state == PdrState.Value('INSTALL'): ret = self._classifier_app.add_tunnel_flows(\ pdr_entry.precedence, pdr_entry.local_f_teid,\ pdr_entry.far_action.o_teid, pdr_entry.ue_ip_addr,\ pdr_entry.far_action.gnb_ip_addr, encode_imsi(subscriber_id)) elif pdr_entry.pdr_state in \ [PdrState.Value('REMOVE'), PdrState.Value('IDLE')]: ret = self._classifier_app.delete_tunnel_flows(\ pdr_entry.local_f_teid, pdr_entry.ue_ip_addr) return ret