def __init__(self, *args, **kwargs): super(EnforcementController, self).__init__(*args, **kwargs) self._config = kwargs['config'] self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_main_table = self._service_manager.get_next_table_num( EnforcementStatsController.APP_NAME) self._enforcement_stats_tbl = self._service_manager.get_table_num( EnforcementStatsController.APP_NAME) self.loop = kwargs['loop'] self._msg_hub = MessageHub(self.logger) self._redirect_scratch = \ self._service_manager.allocate_scratch_tables(self.APP_NAME, 1)[0] self._bridge_ip_address = kwargs['config']['bridge_ip_address'] self._redirect_manager = None self._qos_mgr = None self._clean_restart = kwargs['config']['clean_restart'] self._redirect_manager = RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._enforcement_stats_tbl, self.next_main_table, self._redirect_scratch, self._session_rule_version_mapper)
def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._delete_all_flows(datapath) self._install_default_flows(datapath) self._datapath = datapath # The next table is dependent on whether enforcement_stats claims a # scratch table, which happens during its initialization. Since # initialization order is non-deterministic, redirect manager is # initialized here instead of in __init__. if self._enforcement_stats_scratch is not None: redirect_next_table = self._enforcement_stats_scratch else: redirect_next_table = self.next_main_table self._redirect_manager = RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, redirect_next_table, self._redirect_scratch, self._session_rule_version_mapper)
def __init__(self, *args, **kwargs): super(GYController, self).__init__(*args, **kwargs) self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_main_table = self._service_manager.get_next_table_num( self.APP_NAME) self.loop = kwargs['loop'] self._msg_hub = MessageHub(self.logger) self._internal_ip_allocator = kwargs['internal_ip_allocator'] tbls = \ self._service_manager.allocate_scratch_tables(self.APP_NAME, 2) self._redirect_scratch = tbls[0] self._mac_rewr = \ self._service_manager.INTERNAL_MAC_IP_REWRITE_TBL_NUM self._bridge_ip_address = kwargs['config']['bridge_ip_address'] self._clean_restart = kwargs['config']['clean_restart'] self._redirect_manager = \ RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._service_manager.get_table_num(EGRESS), self._redirect_scratch, self._session_rule_version_mapper ).set_cwf_args( internal_ip_allocator=kwargs['internal_ip_allocator'], arp=kwargs['app_futures']['arpd'], mac_rewrite=self._mac_rewr, bridge_name=kwargs['config']['bridge_name'], egress_table=self._service_manager.get_table_num(EGRESS) )
def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._datapath = datapath self._qos_mgr = QosManager(datapath, self.loop, self._config) self._qos_mgr.setup() self._redirect_manager = RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._enforcement_stats_scratch, self._redirect_scratch, self._session_rule_version_mapper)
def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._datapath = datapath if not self._relay_enabled: self._install_default_flows_if_not_installed(datapath, []) self._redirect_manager = RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._enforcement_stats_scratch, self._redirect_scratch, self._session_rule_version_mapper)
def _install_redirect_flow(self, imsi, ip_addr, rule, version): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = Utils.get_of_priority(rule.priority) redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr.address.decode('utf-8'), rule=rule, rule_num=rule_num, rule_version=version, priority=priority, ) try: self._redirect_manager.setup_lte_redirect( self._datapath, self.loop, redirect_request, ) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err, ) return RuleModResult.FAILURE
def _install_redirect_flow(self, imsi, ip_addr, rule): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) rule_version = self._session_rule_version_mapper.get_version(imsi, ip_addr, rule.id) priority = rule.priority # TODO currently if redirection is enabled we ignore other flows # from rule.flow_list, confirm that this is the expected behaviour redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr.address.decode('utf-8'), rule=rule, rule_num=rule_num, rule_version=rule_version, priority=priority) try: if self._setup_type == 'CWF': self._redirect_manager.setup_cwf_redirect( self._datapath, self.loop, redirect_request) else: self._redirect_manager.setup_lte_redirect( self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err ) return RuleModResult.FAILURE
def _install_redirect_flow(self, imsi, ip_addr, rule, version): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) # CWF generates an internal IP for redirection so ip_addr is not needed if self._setup_type == 'CWF': ip_addr_str = None elif ip_addr and ip_addr.address: ip_addr_str = ip_addr.address.decode('utf-8') priority = rule.priority # TODO currently if redirection is enabled we ignore other flows # from rule.flow_list, confirm that this is the expected behaviour redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr_str, rule=rule, rule_num=rule_num, rule_version=version, priority=priority, ) try: if self._setup_type == 'CWF': self._redirect_manager.setup_cwf_redirect( self._datapath, self.loop, redirect_request, ) else: self._redirect_manager.setup_lte_redirect( self._datapath, self.loop, redirect_request, ) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err, ) return RuleModResult.FAILURE
def __init__(self, *args, **kwargs): super(EnforcementController, self).__init__(*args, **kwargs) self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_table = self._service_manager.get_next_table_num( self.APP_NAME) self._datapath = None self._rule_mapper = kwargs['rule_id_mapper'] self.loop = kwargs['loop'] self._policy_dict = PolicyRuleDict() self._qos_map = QosQueueMap(kwargs['config']['nat_iface'], kwargs['config']['enodeb_iface'], kwargs['config']['enable_queue_pgm']) self._msg_hub = MessageHub(self.logger) self._redirect_manager = RedirectionManager( kwargs['config']['bridge_ip_address'], self.logger, self.tbl_num, self.next_table)
def _install_flow_for_rule(self, imsi, ip_addr, rule): """ Install a flow to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = self.get_of_priority(rule.priority) ul_qos = rule.qos.max_req_bw_ul dl_qos = rule.qos.max_req_bw_dl if rule.redirect.support == rule.redirect.ENABLED: # TODO currently if redirection is enabled we ignore other flows # from rule.flow_list, confirm that this is the expected behaviour redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr, rule=rule, rule_num=rule_num, priority=priority) try: self._redirect_manager.handle_redirection( self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err ) return RuleModResult.FAILURE if not rule.flow_list: self.logger.error('The flow list for imsi %s, rule.id - %s' 'is empty, this shoudn\'t happen', imsi, rule.id) return RuleModResult.FAILURE flow_adds = [] for flow in rule.flow_list: try: flow_adds.append(self._get_classify_rule_flow_msg( imsi, flow, rule_num, priority, ul_qos, dl_qos, rule.hard_timeout, rule.id)) except FlowMatchError as err: # invalid match self.logger.error( "Failed to install rule %s for subscriber %s: %s", rule.id, imsi, err) return RuleModResult.FAILURE chan = self._msg_hub.send(flow_adds, self._datapath) return self._wait_for_responses(imsi, rule, chan)
def _install_redirect_flow(self, imsi, ip_addr, rule): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = self.get_of_priority(rule.priority) redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr, rule=rule, rule_num=rule_num, priority=priority) try: self._redirect_manager.handle_redirection(self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err) return RuleModResult.FAILURE
class EnforcementController(PolicyMixin, RestartMixin, MagmaController): """ EnforcementController The enforcement controller installs flows for policy enforcement and classification. Each flow installed matches on a rule and an IMSI and then classifies the packet with the rule. The flow also redirects and drops the packet as specified in the policy. NOTE: Enforcement currently relies on the fact that policies do not overlap. In this implementation, there is the idea of a 'default rule' which is the catch-all. This rule is treated specially and tagged with a specific priority. """ APP_NAME = "enforcement" APP_TYPE = ControllerType.LOGICAL DEFAULT_FLOW_COOKIE = 0xfffffffffffffffe def __init__(self, *args, **kwargs): super(EnforcementController, self).__init__(*args, **kwargs) self._config = kwargs['config'] self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_main_table = self._service_manager.get_next_table_num( EnforcementStatsController.APP_NAME) self._enforcement_stats_tbl = self._service_manager.get_table_num( EnforcementStatsController.APP_NAME) self.loop = kwargs['loop'] self._msg_hub = MessageHub(self.logger) self._redirect_scratch = \ self._service_manager.allocate_scratch_tables(self.APP_NAME, 1)[0] self._bridge_ip_address = kwargs['config']['bridge_ip_address'] self._redirect_manager = None self._qos_mgr = None self._clean_restart = kwargs['config']['clean_restart'] self._redirect_manager = RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._enforcement_stats_tbl, self.next_main_table, self._redirect_scratch, self._session_rule_version_mapper) def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._datapath = datapath self._qos_mgr = QosManager.get_qos_manager(datapath, self.loop, self._config) def cleanup_on_disconnect(self, datapath): """ Cleanup flows on datapath disconnect event. Args: datapath: ryu datapath struct """ if self._clean_restart: self.delete_all_flows(datapath) def delete_all_flows(self, datapath): flows.delete_all_flows_from_table(datapath, self.tbl_num) flows.delete_all_flows_from_table(datapath, self._redirect_scratch) def cleanup_state(self): pass @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER) def _handle_barrier(self, ev): self._msg_hub.handle_barrier(ev) @set_ev_cls(ofp_event.EventOFPErrorMsg, MAIN_DISPATCHER) def _handle_error(self, ev): self._msg_hub.handle_error(ev) @set_ev_cls(ofp_event.EventOFPMeterConfigStatsReply, MAIN_DISPATCHER) def meter_config_stats_reply_handler(self, ev): if not self._qos_mgr: return qos_impl = self._qos_mgr.impl if qos_impl and isinstance(qos_impl, MeterManager): qos_impl.handle_meter_config_stats(ev.msg.body) @set_ev_cls(ofp_event.EventOFPMeterFeaturesStatsReply, MAIN_DISPATCHER) def meter_features_stats_reply_handler(self, ev): if not self._qos_mgr: return qos_impl = self._qos_mgr.impl if qos_impl and isinstance(qos_impl, MeterManager): qos_impl.handle_meter_feature_stats(ev.msg.body) def _get_default_flow_msgs(self, datapath) -> DefaultMsgsMap: """ Gets the default flow msg that forward to stats table(traffic will be dropped because stats table doesn't forward anything) Args: datapath: ryu datapath struct Returns: The list of default msgs to add """ match = MagmaMatch() msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self._enforcement_stats_tbl, cookie=self.DEFAULT_FLOW_COOKIE) return {self.tbl_num: [msg]} def _get_rule_match_flow_msgs(self, imsi, msisdn: bytes, uplink_tunnel: int, ip_addr, apn_ambr, rule): """ Get flow msgs to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for msisdn (bytes): subscriber MSISDN uplink_tunnel (int): tunnel ID of the subscriber. ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = Utils.get_of_priority(rule.priority) flow_adds = [] for flow in rule.flow_list: try: version = self._session_rule_version_mapper.get_version( imsi, ip_addr, rule.id) flow_adds.extend( self._get_classify_rule_flow_msgs( imsi, msisdn, uplink_tunnel, ip_addr, apn_ambr, flow, rule_num, priority, rule.qos, rule.hard_timeout, rule.id, rule.app_name, rule.app_service_type, self.next_main_table, version, self._qos_mgr, self._enforcement_stats_tbl, rule.he.urls)) except FlowMatchError as err: # invalid match self.logger.error( "Failed to get flow msg '%s' for subscriber %s: %s", rule.id, imsi, err) raise err return flow_adds def _install_flow_for_rule(self, imsi, msisdn: bytes, uplink_tunnel: int, ip_addr, apn_ambr, rule): """ Install a flow to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for msisdn (bytes): subscriber MSISDN uplink_tunnel (int): tunnel ID of the subscriber. ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ if rule.redirect.support == rule.redirect.ENABLED: return self._install_redirect_flow(imsi, ip_addr, rule) if not rule.flow_list: self.logger.error( 'The flow list for imsi %s, rule.id - %s' 'is empty, this shoudn\'t happen', imsi, rule.id) return RuleModResult.FAILURE flow_adds = [] try: flow_adds = self._get_rule_match_flow_msgs(imsi, msisdn, uplink_tunnel, ip_addr, apn_ambr, rule) except FlowMatchError: return RuleModResult.FAILURE try: chan = self._msg_hub.send(flow_adds, self._datapath) except MagmaDPDisconnectedError: self.logger.error( "Datapath disconnected, failed to install rule %s" "for imsi %s", rule, imsi) return RuleModResult.FAILURE return self._wait_for_rule_responses(imsi, ip_addr, rule, chan) def _install_redirect_flow(self, imsi, ip_addr, rule): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) rule_version = self._session_rule_version_mapper.get_version( imsi, ip_addr, rule.id) priority = Utils.get_of_priority(rule.priority) redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr.address.decode('utf-8'), rule=rule, rule_num=rule_num, rule_version=rule_version, priority=priority) try: self._redirect_manager.setup_lte_redirect(self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err) return RuleModResult.FAILURE def _get_default_flow_msgs_for_subscriber(self, *_): pass def _install_default_flow_for_subscriber(self, *_): pass 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 _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) self._remove_he_flows(ip_addr) def deactivate_rules(self, imsi, ip_addr, rule_ids): """ Deactivate flows for a subscriber. Only imsi -> remove all rules for imsi imsi+ipv4 -> remove all rules for imsi session imsi+rule_ids -> remove specific rules for imsi (for all sessions) imsi+ipv4+rule_ids -> remove rules for specific imsi session Args: imsi (string): subscriber id ip_addr (string): subscriber ip address rule_ids (list of strings): policy rule ids """ if not self.init_finished: self.logger.error('Pipelined is not initialized') return RuleModResult.FAILURE if self._datapath is None: self.logger.error('Datapath not initialized') return if not imsi: self.logger.error('No subscriber specified') return if not rule_ids: self._deactivate_flows_for_subscriber(imsi, ip_addr) else: for rule_id in rule_ids: self._deactivate_flow_for_rule(imsi, ip_addr, rule_id) def recover_state(self, _): pass
class EnforcementController(PolicyMixin, MagmaController): """ EnforcementController The enforcement controller installs flows for policy enforcement and classification. Each flow installed matches on a rule and an IMSI and then classifies the packet with the rule. The flow also redirects and drops the packet as specified in the policy. NOTE: Enforcement currently relies on the fact that policies do not overlap. In this implementation, there is the idea of a 'default rule' which is the catch-all. This rule is treated specially and tagged with a specific priority. """ APP_NAME = "enforcement" APP_TYPE = ControllerType.LOGICAL ENFORCE_DROP_PRIORITY = flows.MINIMUM_PRIORITY + 1 # Should not overlap with the drop flow as drop matches all packets. MIN_ENFORCE_PROGRAMMED_FLOW = ENFORCE_DROP_PRIORITY + 1 MAX_ENFORCE_PRIORITY = flows.MAXIMUM_PRIORITY # Effectively range is 2 -> 65535 ENFORCE_PRIORITY_RANGE = MAX_ENFORCE_PRIORITY - MIN_ENFORCE_PROGRAMMED_FLOW def __init__(self, *args, **kwargs): super(EnforcementController, self).__init__(*args, **kwargs) self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_main_table = self._service_manager.get_next_table_num( self.APP_NAME) self._enforcement_stats_scratch = self._service_manager.get_table_num( EnforcementStatsController.APP_NAME) self.loop = kwargs['loop'] self._relay_enabled = kwargs['mconfig'].relay_enabled self._qos_map = QosQueueMap(kwargs['config']['nat_iface'], kwargs['config']['enodeb_iface'], kwargs['config']['enable_queue_pgm']) self._msg_hub = MessageHub(self.logger) self._redirect_scratch = \ self._service_manager.allocate_scratch_tables(self.APP_NAME, 1)[0] self._bridge_ip_address = kwargs['config']['bridge_ip_address'] self._redirect_manager = None self._clean_restart = kwargs['config']['clean_restart'] self._relay_enabled = kwargs['mconfig'].relay_enabled if not self._relay_enabled: self.logger.info('Relay mode is not enabled, enforcement will not' ' wait for sessiond to push flows.') def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._datapath = datapath if not self._relay_enabled: self._install_default_flows_if_not_installed(datapath, []) self._redirect_manager = RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._enforcement_stats_scratch, self._redirect_scratch, self._session_rule_version_mapper) def cleanup_on_disconnect(self, datapath): """ Cleanup flows on datapath disconnect event. Args: datapath: ryu datapath struct """ if self._clean_restart: self.delete_all_flows(datapath) def delete_all_flows(self, datapath): flows.delete_all_flows_from_table(datapath, self.tbl_num) flows.delete_all_flows_from_table(datapath, self._redirect_scratch) @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER) def _handle_barrier(self, ev): self._msg_hub.handle_barrier(ev) @set_ev_cls(ofp_event.EventOFPErrorMsg, MAIN_DISPATCHER) def _handle_error(self, ev): self._msg_hub.handle_error(ev) def _install_default_flows_if_not_installed( self, datapath, existing_flows: List[OFPFlowStats]) -> List[OFPFlowStats]: """ For each direction set the default flows to just forward to next app. The enforcement flows for each subscriber would be added when the IP session is created, by reaching out to the controller/PCRF. If default flows are already installed, do nothing. Args: datapath: ryu datapath struct Returns: The list of flows that remain after inserting default flows """ inbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.IN) outbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.OUT) inbound_msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, inbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) outbound_msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, outbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) msgs, remaining_flows = self._msg_hub \ .filter_msgs_if_not_in_flow_list([inbound_msg, outbound_msg], existing_flows) if msgs: chan = self._msg_hub.send(msgs, datapath) self._wait_for_responses(chan, len(msgs)) return remaining_flows def get_of_priority(self, precedence): """ Lower the precedence higher the importance of the flow in 3GPP. Higher the priority higher the importance of the flow in openflow. Convert precedence to priority: 1 - Flows with precedence > 65534 will have min priority which is the min priority for a programmed flow = (default drop + 1) 2 - Flows in the precedence range 0-65534 will have priority 65535 - Precedence :param precedence: :return: """ if precedence >= self.ENFORCE_PRIORITY_RANGE: self.logger.warning( "Flow precedence is higher than OF range using min priority %d", self.MIN_ENFORCE_PROGRAMMED_FLOW) return self.MIN_ENFORCE_PROGRAMMED_FLOW return self.MAX_ENFORCE_PRIORITY - precedence def _get_rule_match_flow_msgs(self, imsi, rule): """ Get a flow msg to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = self.get_of_priority(rule.priority) ul_qos = rule.qos.max_req_bw_ul dl_qos = rule.qos.max_req_bw_dl flow_adds = [] for flow in rule.flow_list: try: flow_adds.append( self._get_classify_rule_flow_msg(imsi, flow, rule_num, priority, ul_qos, dl_qos, rule.hard_timeout, rule.id)) except FlowMatchError as err: # invalid match self.logger.error( "Failed to get flow msg '%s' for subscriber %s: %s", rule.id, imsi, err) raise err return flow_adds def _install_flow_for_rule(self, imsi, ip_addr, rule): """ Install a flow to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ if rule.redirect.support == rule.redirect.ENABLED: return self._install_redirect_flow(imsi, ip_addr, rule) if not rule.flow_list: self.logger.error( 'The flow list for imsi %s, rule.id - %s' 'is empty, this shoudn\'t happen', imsi, rule.id) return RuleModResult.FAILURE flow_adds = [] try: flow_adds = self._get_rule_match_flow_msgs(imsi, rule) except FlowMatchError: return RuleModResult.FAILURE chan = self._msg_hub.send(flow_adds, self._datapath) return self._wait_for_rule_responses(imsi, rule, chan) def _wait_for_rule_responses(self, imsi, rule, chan): def fail(err): self.logger.error( "Failed to install rule %s for subscriber %s: %s", rule.id, imsi, err) self._deactivate_flow_for_rule(imsi, rule.id) return RuleModResult.FAILURE for _ in range(len(rule.flow_list)): try: result = chan.get() except MsgChannel.Timeout: return fail("No response from OVS") if not result.ok(): return fail(result.exception()) return RuleModResult.SUCCESS 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 _install_redirect_flow(self, imsi, ip_addr, rule): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = self.get_of_priority(rule.priority) redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr, rule=rule, rule_num=rule_num, priority=priority) try: self._redirect_manager.handle_redirection(self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err) return RuleModResult.FAILURE def _get_classify_rule_of_actions(self, flow, rule_num, imsi, ul_qos, dl_qos, rule_id): parser = self._datapath.ofproto_parser # encode the rule id in hex of_note = parser.NXActionNote(list(rule_id.encode())) actions = [of_note] if flow.action == flow.DENY: return actions # QoS Rate-Limiting is currently supported for uplink traffic qid = 0 if ul_qos != 0 and flow.match.direction == flow.match.UPLINK: qid = self._qos_map.map_flow_to_queue(imsi, rule_num, ul_qos, True) elif dl_qos != 0 and flow.match.direction == flow.match.DOWNLINK: qid = self._qos_map.map_flow_to_queue(imsi, rule_num, dl_qos, False) if qid != 0: actions.append(parser.OFPActionSetField(pkt_mark=qid)) version = self._session_rule_version_mapper.get_version(imsi, rule_id) actions.extend([ parser.NXActionRegLoad2(dst='reg2', value=rule_num), parser.NXActionRegLoad2(dst=RULE_VERSION_REG, value=version) ]) return actions 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 _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 _deactivate_flow_for_rule(self, imsi, 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 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) self._qos_map.del_queue_for_flow(imsi, num) 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 deactivate_rules(self, imsi, rule_ids): """ Deactivate flows for a subscriber. If only imsi is present, delete all rule flows for a subscriber (i.e. end its session). If rule_ids are present, delete the rule flows for that subscriber. Args: imsi (string): subscriber id rule_ids (list of strings): policy rule ids """ if not self.init_finished: self.logger.error('Pipelined is not initialized') return RuleModResult.FAILURE if self._datapath is None: self.logger.error('Datapath not initialized') return if not imsi: self.logger.error('No subscriber specified') return if not rule_ids: self._deactivate_flows_for_subscriber(imsi) else: for rule_id in rule_ids: self._deactivate_flow_for_rule(imsi, rule_id)
class EnforcementController(MagmaController): """ EnforcementController The enforcement controller installs flows for tracking subscriber usage per rule and enforcing the usage. Each flow installed matches on a rule and an IMSI and then the statistics are sent to sessiond for tracking. NOTE: Enforcement currently relies on the fact that policies do not overlap. In this implementation, there is the idea of a 'default rule' which is the catch-all. This rule is treated specially and tagged with a specific priority. """ APP_NAME = "enforcement" ENFORCE_DROP_PRIORITY = flows.MINIMUM_PRIORITY + 1 # Should not overlap with the drop flow as drop matches all packets. MIN_ENFORCE_PROGRAMMED_FLOW = ENFORCE_DROP_PRIORITY + 1 MAX_ENFORCE_PRIORITY = flows.MAXIMUM_PRIORITY # Effectively range is 2 -> 65535 ENFORCE_PRIORITY_RANGE = MAX_ENFORCE_PRIORITY - MIN_ENFORCE_PROGRAMMED_FLOW def __init__(self, *args, **kwargs): super(EnforcementController, self).__init__(*args, **kwargs) self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_table = self._service_manager.get_next_table_num( self.APP_NAME) self._datapath = None self._rule_mapper = kwargs['rule_id_mapper'] self.loop = kwargs['loop'] self._policy_dict = PolicyRuleDict() self._qos_map = QosQueueMap(kwargs['config']['nat_iface'], kwargs['config']['enodeb_iface'], kwargs['config']['enable_queue_pgm']) self._msg_hub = MessageHub(self.logger) self._redirect_manager = RedirectionManager( kwargs['config']['bridge_ip_address'], self.logger, self.tbl_num, self.next_table) def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ flows.delete_all_flows_from_table(datapath, self.tbl_num) self._install_default_flows(datapath) self._datapath = datapath def cleanup_on_disconnect(self, datapath): """ Cleanup flows on datapath disconnect event. Args: datapath: ryu datapath struct """ flows.delete_all_flows_from_table(datapath, self.tbl_num) @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER) def _handle_barrier(self, ev): self._msg_hub.handle_barrier(ev) @set_ev_cls(ofp_event.EventOFPErrorMsg, MAIN_DISPATCHER) def _handle_error(self, ev): self._msg_hub.handle_error(ev) def _install_default_flows(self, datapath): """ For each direction set the default flows to just forward to next table. The enforcement flows for each subscriber would be added when the IP session is created, by reaching out to the controller/PCRF. Args: datapath: ryu datapath struct """ inbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.IN) outbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.OUT) flows.add_flow(datapath, self.tbl_num, inbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_table) flows.add_flow(datapath, self.tbl_num, outbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_table) def _install_flow_for_static_rule(self, imsi, ip_addr, rule_id): """ Install a flow to get stats for a particular static rule id. The rule will be loaded from Redis and installed Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule_id (string): policy rule id """ rule = self._policy_dict[rule_id] if rule is None: self.logger.error("Could not find rule for rule_id: %s", rule_id) return RuleModResult.FAILURE return self._install_flow_for_rule(imsi, ip_addr, rule) def get_of_priority(self, precedence): """ Lower the precedence higher the importance of the flow in 3GPP. Higher the priority higher the importance of the flow in openflow. Convert precedence to priority: 1 - Flows with precedence > 65534 will have min priority which is the min priority for a programmed flow = (default drop + 1) 2 - Flows in the precedence range 0-65534 will have priority 65535 - Precedence :param precedence: :return: """ if precedence >= self.ENFORCE_PRIORITY_RANGE: self.logger.warning( "Flow precedence is higher than OF range using " "min priority %d", self.MIN_ENFORCE_PROGRAMMED_FLOW) return self.MIN_ENFORCE_PROGRAMMED_FLOW return self.MAX_ENFORCE_PRIORITY - precedence def _install_flow_for_rule(self, imsi, ip_addr, rule): """ Install a flow to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = self.get_of_priority(rule.priority) ul_qos = rule.qos.max_req_bw_ul if rule.redirect.support == rule.redirect.ENABLED: # TODO currently if redirection is enabled we ignore other flows # from rule.flow_list, confirm that this is the expected behaviour redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr, rule=rule, rule_num=rule_num, priority=priority) try: self._redirect_manager.handle_redirection( self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err) return RuleModResult.FAILURE flow_adds = [] for flow in rule.flow_list: try: flow_adds.append( self._get_add_flow_msg(imsi, flow, rule_num, priority, ul_qos, rule.hard_timeout, rule.id)) except FlowMatchError as err: # invalid match self.logger.error( "Failed to install rule %s for subscriber %s: %s", rule.id, imsi, err) return RuleModResult.FAILURE chan = self._msg_hub.send(flow_adds, self._datapath) return self._wait_for_responses(imsi, rule, chan) def _wait_for_responses(self, imsi, rule, chan): def fail(err): self.logger.error( "Failed to install rule %s for subscriber %s: %s", rule.id, imsi, err) self._deactivate_flow_for_rule(imsi, rule.id) return RuleModResult.FAILURE for _ in range(len(rule.flow_list)): try: result = chan.get() except MsgChannel.Timeout: return fail("No response from OVS") if not result.ok(): return fail(result.exception()) return RuleModResult.SUCCESS 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_of_actions_for_flow(self, flow, rule_num, imsi, ul_qos, rule_id): parser = self._datapath.ofproto_parser # encode the rule id in hex of_note = parser.NXActionNote(list(rule_id.encode())) if flow.action == flow.DENY: return [of_note] # QoS Rate-Limiting is currently supported for uplink traffic if ul_qos != 0 and flow.match.direction == flow.match.UPLINK: qid = self._qos_map.map_flow_to_queue(imsi, rule_num, ul_qos, True) if qid != 0: return [ parser.OFPActionSetField(pkt_mark=qid), parser.NXActionRegLoad2(dst='reg2', value=rule_num), of_note ] return [parser.NXActionRegLoad2(dst='reg2', value=rule_num), of_note] def _install_drop_flow(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_flow(self._datapath, self.tbl_num, match, actions, priority=self.ENFORCE_DROP_PRIORITY) def activate_flows(self, imsi, ip_addr, static_rule_ids, dynamic_rules, fut): """ Activate the flows for a subscriber based on the rules stored in Redis. During activation, another low priority flow is installed for the subscriber in the event that all rules are out of credit. Args: imsi (string): subscriber id ip_addr (string): subscriber session ipv4 address static_rule_ids (string []): list of static rules to activate dynamic_rules (PolicyRule []): list of dynamic rules to activate fut (Future): future to wait on the results of flow activations """ if self._datapath is None: self.logger.error('Datapath not initialized for adding flows') fut.set_result( ActivateFlowsResult( static_rule_results=[ RuleModResult( rule_id=rule_id, result=RuleModResult.FAILURE, ) for rule_id in static_rule_ids ], dynamic_rule_results=[ RuleModResult( rule_id=rule.id, result=RuleModResult.FAILURE, ) for rule in dynamic_rules ], )) return static_results = [] for rule_id in static_rule_ids: res = self._install_flow_for_static_rule(imsi, ip_addr, rule_id) static_results.append(RuleModResult(rule_id=rule_id, result=res)) dyn_results = [] for rule in dynamic_rules: res = self._install_flow_for_rule(imsi, ip_addr, rule) dyn_results.append(RuleModResult(rule_id=rule.id, result=res)) # No matter what, install base flow to drop packets when all other # flows have been deactivated self._install_drop_flow(imsi) fut.set_result( ActivateFlowsResult( static_rule_results=static_results, dynamic_rule_results=dyn_results, )) def _deactivate_flow_for_rule(self, imsi, 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 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) self._qos_map.del_queue_for_flow(imsi, num) def _deactivate_flows_for_subscriber(self, imsi): """ Deactivate all rules for a subscriber, ending any enforcement """ flows.delete_flow(self._datapath, self.tbl_num, MagmaMatch(imsi=encode_imsi(imsi))) self._redirect_manager.deactivate_flows_for_subscriber( self._datapath, imsi) self._qos_map.del_subscriber_queues(imsi) def deactivate_flows(self, imsi, rule_ids): """ Deactivate flows for a subscriber. If only imsi is present, delete all rule flows for a subscriber (i.e. end its session). If rule_ids are present, delete the rule flows for that subscriber. Args: imsi (string): subscriber id rule_ids (list of strings): policy rule ids """ if self._datapath is None: self.logger.error('Datapath not initialized') return if not imsi: self.logger.error('No subscriber specified') return if not rule_ids: self._deactivate_flows_for_subscriber(imsi) else: for rule_id in rule_ids: self._deactivate_flow_for_rule(imsi, rule_id)
class GYController(PolicyMixin, MagmaController): """ GYController The GY controller installs flows for enforcement of GY final actions, this includes redirection and QoS(currently not supported) """ APP_NAME = "gy" APP_TYPE = ControllerType.LOGICAL def __init__(self, *args, **kwargs): super(GYController, self).__init__(*args, **kwargs) self._config = kwargs['config'] self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_main_table = self._service_manager.get_next_table_num( self.APP_NAME) self.next_service_table = self._service_manager.get_next_table_num( EnforcementStatsController.APP_NAME) self._enforcement_stats_tbl = self._service_manager.get_table_num( EnforcementStatsController.APP_NAME) self.loop = kwargs['loop'] self._msg_hub = MessageHub(self.logger) self._internal_ip_allocator = kwargs['internal_ip_allocator'] self._redirect_scratch = \ self._service_manager.allocate_scratch_tables(self.APP_NAME, 2)[0] self._mac_rewr = \ self._service_manager.INTERNAL_MAC_IP_REWRITE_TBL_NUM self._bridge_ip_address = kwargs['config']['bridge_ip_address'] self._clean_restart = kwargs['config']['clean_restart'] self._qos_mgr = None self._setup_type = self._config['setup_type'] self._redirect_manager = \ RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._service_manager.get_table_num(EGRESS), self._redirect_scratch, self._session_rule_version_mapper ) if self._setup_type == 'CWF': self._redirect_manager.set_cwf_args( internal_ip_allocator=kwargs['internal_ip_allocator'], arp=kwargs['app_futures']['arpd'], mac_rewrite=self._mac_rewr, bridge_name=kwargs['config']['bridge_name'], egress_table=self._service_manager.get_table_num(EGRESS) ) def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._datapath = datapath self._qos_mgr = QosManager(datapath, self.loop, self._config) self._qos_mgr.setup() self._delete_all_flows(datapath) self._install_default_flows(datapath) def deactivate_rules(self, imsi, ip_addr, rule_ids): """ Deactivate flows for a subscriber. If only imsi is present, delete all rule flows for a subscriber (i.e. end its session). If rule_ids are present, delete the rule flows for that subscriber. Args: imsi (string): subscriber id rule_ids (list of strings): policy rule ids """ if not self.init_finished: self.logger.error('Pipelined is not initialized') return RuleModResult.FAILURE if self._datapath is None: self.logger.error('Datapath not initialized') return if not imsi: self.logger.error('No subscriber specified') return if not rule_ids: self._deactivate_flows_for_subscriber(imsi, ip_addr) else: for rule_id in rule_ids: self._deactivate_flow_for_rule(imsi, ip_addr, rule_id) def cleanup_state(self): pass def _deactivate_flows_for_subscriber(self, imsi, ip_addr): """ Deactivate all rules for a subscriber, ending any enforcement Args: imsi (string): subscriber id ip_addr(IPAddress): session IP address """ 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) self._remove_he_flows(ip_addr, None) 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) self._qos_mgr.remove_subscriber_qos(imsi, num) self._remove_he_flows(ip_addr, rule_id) def _install_flow_for_rule(self, imsi, msisdn:bytes, uplink_tunnel: int, ip_addr, apn_ambr, rule): """ Install a flow to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address apn_ambr (integer): maximum bandwidth for non-GBR EPS bearers rule (PolicyRule): policy rule proto """ if rule.redirect.support == rule.redirect.ENABLED: self._install_redirect_flow(imsi, ip_addr, rule) return RuleModResult.SUCCESS if not rule.flow_list: self.logger.error('The flow list for imsi %s, rule.id - %s' 'is empty, this shoudn\'t happen', imsi, rule.id) return RuleModResult.FAILURE flow_adds = [] try: flow_adds = self._get_rule_match_flow_msgs(imsi, msisdn, uplink_tunnel, ip_addr, apn_ambr, rule) except FlowMatchError: return RuleModResult.FAILURE chan = self._msg_hub.send(flow_adds, self._datapath) return self._wait_for_rule_responses(imsi, ip_addr, rule, chan) def _get_default_flow_msgs_for_subscriber(self, *_): return None def _install_default_flow_for_subscriber(self, *_): pass def _delete_all_flows(self, datapath): flows.delete_all_flows_from_table(datapath, self.tbl_num) flows.delete_all_flows_from_table(datapath, self._redirect_scratch) flows.delete_all_flows_from_table(datapath, self._mac_rewr) def _install_default_flows(self, datapath): """ For each direction set the default flows to just forward to next app. The enforcement flows for each subscriber would be added when the IP session is created, by reaching out to the controller/PCRF. Args: datapath: ryu datapath struct """ match = MagmaMatch() flows.add_resubmit_next_service_flow( datapath, self.tbl_num, match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) def _install_redirect_flow(self, imsi, ip_addr, rule): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) rule_version = self._session_rule_version_mapper.get_version(imsi, ip_addr, rule.id) priority = rule.priority # TODO currently if redirection is enabled we ignore other flows # from rule.flow_list, confirm that this is the expected behaviour redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr.address.decode('utf-8'), rule=rule, rule_num=rule_num, rule_version=rule_version, priority=priority) try: if self._setup_type == 'CWF': self._redirect_manager.setup_cwf_redirect( self._datapath, self.loop, redirect_request) else: self._redirect_manager.setup_lte_redirect( self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err ) return RuleModResult.FAILURE def _install_default_flows_if_not_installed(self, datapath, existing_flows: List[OFPFlowStats]) -> List[OFPFlowStats]: """ For each direction set the default flows to just forward to next app. The enforcement flows for each subscriber would be added when the IP session is created, by reaching out to the controller/PCRF. If default flows are already installed, do nothing. Args: datapath: ryu datapath struct Returns: The list of flows that remain after inserting default flows """ match = MagmaMatch() msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) msgs, remaining_flows = self._msg_hub \ .filter_msgs_if_not_in_flow_list([msg], existing_flows) if msgs: chan = self._msg_hub.send(msgs, datapath) self._wait_for_responses(chan, len(msgs)) return remaining_flows def _get_rule_match_flow_msgs(self, imsi, msisdn: bytes, uplink_tunnel: int, ip_addr, apn_ambr, rule): """ Get flow msgs to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for msisdn (bytes): subscriber ISDN ip_addr (string): subscriber session ipv4 address apn_ambr (integer): maximum bandwidth for non-GBR EPS bearers rule (PolicyRule): policy rule proto """ rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = Utils.get_of_priority(rule.priority) flow_adds = [] for flow in rule.flow_list: try: version = self._session_rule_version_mapper.get_version(imsi, ip_addr, rule.id) flow_adds.extend(self._get_classify_rule_flow_msgs( imsi, msisdn, uplink_tunnel, ip_addr, apn_ambr, flow, rule_num, priority, rule.qos, rule.hard_timeout, rule.id, rule.app_name, rule.app_service_type, self.next_service_table, version, self._qos_mgr, self._enforcement_stats_tbl)) except FlowMatchError as err: # invalid match self.logger.error( "Failed to get flow msg '%s' for subscriber %s: %s", rule.id, imsi, err) raise err return flow_adds @set_ev_cls(ofp_event.EventOFPMeterConfigStatsReply, MAIN_DISPATCHER) def meter_config_stats_reply_handler(self, ev): if not self._qos_mgr: return qos_impl = self._qos_mgr.impl if qos_impl and isinstance(qos_impl, MeterManager): qos_impl.handle_meter_config_stats(ev.msg.body) @set_ev_cls(ofp_event.EventOFPMeterFeaturesStatsReply, MAIN_DISPATCHER) def meter_features_stats_reply_handler(self, ev): if not self._qos_mgr: return qos_impl = self._qos_mgr.impl if qos_impl and isinstance(qos_impl, MeterManager): qos_impl.handle_meter_feature_stats(ev.msg.body) @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER) def _handle_barrier(self, ev): self._msg_hub.handle_barrier(ev) @set_ev_cls(ofp_event.EventOFPErrorMsg, MAIN_DISPATCHER) def _handle_error(self, ev): self._msg_hub.handle_error(ev)
class EnforcementController(PolicyMixin, MagmaController): """ EnforcementController The enforcement controller installs flows for policy enforcement and classification. Each flow installed matches on a rule and an IMSI and then classifies the packet with the rule. The flow also redirects and drops the packet as specified in the policy. NOTE: Enforcement currently relies on the fact that policies do not overlap. In this implementation, there is the idea of a 'default rule' which is the catch-all. This rule is treated specially and tagged with a specific priority. """ APP_NAME = "enforcement" APP_TYPE = ControllerType.LOGICAL def __init__(self, *args, **kwargs): super(EnforcementController, self).__init__(*args, **kwargs) self._config = kwargs['config'] self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_main_table = self._service_manager.get_next_table_num( self.APP_NAME) self._enforcement_stats_tbl = self._service_manager.get_table_num( EnforcementStatsController.APP_NAME) self.loop = kwargs['loop'] self._msg_hub = MessageHub(self.logger) self._redirect_scratch = \ self._service_manager.allocate_scratch_tables(self.APP_NAME, 1)[0] self._bridge_ip_address = kwargs['config']['bridge_ip_address'] self._redirect_manager = None self._qos_mgr = None self._clean_restart = kwargs['config']['clean_restart'] self._redirect_manager = RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._enforcement_stats_tbl, self._redirect_scratch, self._session_rule_version_mapper) def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._datapath = datapath self._qos_mgr = QosManager(datapath, self.loop, self._config) self._qos_mgr.setup() def cleanup_on_disconnect(self, datapath): """ Cleanup flows on datapath disconnect event. Args: datapath: ryu datapath struct """ if self._clean_restart: self.delete_all_flows(datapath) def delete_all_flows(self, datapath): flows.delete_all_flows_from_table(datapath, self.tbl_num) flows.delete_all_flows_from_table(datapath, self._redirect_scratch) def cleanup_state(self): pass @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER) def _handle_barrier(self, ev): self._msg_hub.handle_barrier(ev) @set_ev_cls(ofp_event.EventOFPErrorMsg, MAIN_DISPATCHER) def _handle_error(self, ev): self._msg_hub.handle_error(ev) @set_ev_cls(ofp_event.EventOFPMeterConfigStatsReply, MAIN_DISPATCHER) def meter_config_stats_reply_handler(self, ev): if not self._qos_mgr: return qos_impl = self._qos_mgr.impl if qos_impl and isinstance(qos_impl, MeterManager): qos_impl.handle_meter_config_stats(ev.msg.body) @set_ev_cls(ofp_event.EventOFPMeterFeaturesStatsReply, MAIN_DISPATCHER) def meter_features_stats_reply_handler(self, ev): if not self._qos_mgr: return qos_impl = self._qos_mgr.impl if qos_impl and isinstance(qos_impl, MeterManager): qos_impl.handle_meter_feature_stats(ev.msg.body) def _install_default_flows_if_not_installed(self, datapath, existing_flows: List[OFPFlowStats]) -> List[OFPFlowStats]: """ For each direction set the default flows to just forward to next app. The enforcement flows for each subscriber would be added when the IP session is created, by reaching out to the controller/PCRF. If default flows are already installed, do nothing. Args: datapath: ryu datapath struct Returns: The list of flows that remain after inserting default flows """ inbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.IN) outbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.OUT) inbound_msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, inbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) outbound_msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, outbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) msgs, remaining_flows = self._msg_hub \ .filter_msgs_if_not_in_flow_list([inbound_msg, outbound_msg], existing_flows) if msgs: chan = self._msg_hub.send(msgs, datapath) self._wait_for_responses(chan, len(msgs)) return remaining_flows def _get_rule_match_flow_msgs(self, imsi, ip_addr, apn_ambr, rule): """ Get flow msgs to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = Utils.get_of_priority(rule.priority) flow_adds = [] for flow in rule.flow_list: try: version = self._session_rule_version_mapper.get_version(imsi, ip_addr, rule.id) flow_adds.extend(self._get_classify_rule_flow_msgs( imsi, ip_addr, apn_ambr, flow, rule_num, priority, rule.qos, rule.hard_timeout, rule.id, rule.app_name, rule.app_service_type, self._enforcement_stats_tbl, version, self._qos_mgr)) except FlowMatchError as err: # invalid match self.logger.error( "Failed to get flow msg '%s' for subscriber %s: %s", rule.id, imsi, err) raise err return flow_adds def _install_flow_for_rule(self, imsi, ip_addr, apn_ambr, rule): """ Install a flow to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ if rule.redirect.support == rule.redirect.ENABLED: return self._install_redirect_flow(imsi, ip_addr, rule) if not rule.flow_list: self.logger.error('The flow list for imsi %s, rule.id - %s' 'is empty, this shoudn\'t happen', imsi, rule.id) return RuleModResult.FAILURE flow_adds = [] try: flow_adds = self._get_rule_match_flow_msgs(imsi, ip_addr, apn_ambr, rule) except FlowMatchError: return RuleModResult.FAILURE chan = self._msg_hub.send(flow_adds, self._datapath) return self._wait_for_rule_responses(imsi, rule, chan) def _install_redirect_flow(self, imsi, ip_addr, rule): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) rule_version = self._session_rule_version_mapper.get_version(imsi, ip_addr, rule.id) priority = Utils.get_of_priority(rule.priority) redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr.address.decode('utf-8'), rule=rule, rule_num=rule_num, rule_version=rule_version, priority=priority) try: self._redirect_manager.setup_lte_redirect( self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err ) return RuleModResult.FAILURE def _get_default_flow_msgs_for_subscriber(self, imsi, ip_addr): ip_match_in = get_ue_ip_match_args(ip_addr, Direction.IN) match_in = MagmaMatch(eth_type=get_eth_type(ip_addr), imsi=encode_imsi(imsi), **ip_match_in) ip_match_out = get_ue_ip_match_args(ip_addr, Direction.OUT) match_out = MagmaMatch(eth_type=get_eth_type(ip_addr), imsi=encode_imsi(imsi), **ip_match_out) actions = [] return [ flows.get_add_drop_flow_msg( self._datapath, self.tbl_num, match_in, actions, priority=Utils.DROP_PRIORITY), flows.get_add_drop_flow_msg( self._datapath, self.tbl_num, match_out, actions, priority=Utils.DROP_PRIORITY)] def _install_default_flow_for_subscriber(self, imsi, ip_addr): """ 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 """ msgs = self._get_default_flow_msgs_for_subscriber(imsi, ip_addr) if msgs: chan = self._msg_hub.send(msgs, self._datapath) self._wait_for_responses(chan, len(msgs)) 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 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) 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_rules(self, imsi, ip_addr, rule_ids): """ Deactivate flows for a subscriber. Only imsi -> remove all rules for imsi imsi+ipv4 -> remove all rules for imsi session imsi+rule_ids -> remove specific rules for imsi (for all sessions) imsi+ipv4+rule_ids -> remove rules for specific imsi session Args: imsi (string): subscriber id ip_addr (string): subscriber ip address rule_ids (list of strings): policy rule ids """ if not self.init_finished: self.logger.error('Pipelined is not initialized') return RuleModResult.FAILURE if self._datapath is None: self.logger.error('Datapath not initialized') return if not imsi: self.logger.error('No subscriber specified') return if not rule_ids: self._deactivate_flows_for_subscriber(imsi, ip_addr) else: for rule_id in rule_ids: self._deactivate_flow_for_rule(imsi, ip_addr, rule_id)
class GYController(PolicyMixin, MagmaController): """ GYController The GY controller installs flows for enforcement of GY final actions, this includes redirection and QoS(currently not supported) """ APP_NAME = "gy" APP_TYPE = ControllerType.LOGICAL def __init__(self, *args, **kwargs): super(GYController, self).__init__(*args, **kwargs) self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_main_table = self._service_manager.get_next_table_num( self.APP_NAME) self.loop = kwargs['loop'] self._msg_hub = MessageHub(self.logger) self._internal_ip_allocator = kwargs['internal_ip_allocator'] tbls = \ self._service_manager.allocate_scratch_tables(self.APP_NAME, 2) self._redirect_scratch = tbls[0] self._mac_rewr = \ self._service_manager.INTERNAL_MAC_IP_REWRITE_TBL_NUM self._bridge_ip_address = kwargs['config']['bridge_ip_address'] self._clean_restart = kwargs['config']['clean_restart'] self._redirect_manager = \ RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._service_manager.get_table_num(EGRESS), self._redirect_scratch, self._session_rule_version_mapper ).set_cwf_args( internal_ip_allocator=kwargs['internal_ip_allocator'], arp=kwargs['app_futures']['arpd'], mac_rewrite=self._mac_rewr, bridge_name=kwargs['config']['bridge_name'], egress_table=self._service_manager.get_table_num(EGRESS) ) def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._datapath = datapath self._delete_all_flows(datapath) self._install_default_flows(datapath) def deactivate_rules(self, imsi, rule_ids): """ Deactivate flows for a subscriber. If only imsi is present, delete all rule flows for a subscriber (i.e. end its session). If rule_ids are present, delete the rule flows for that subscriber. Args: imsi (string): subscriber id rule_ids (list of strings): policy rule ids """ if not self.init_finished: self.logger.error('Pipelined is not initialized') return RuleModResult.FAILURE if self._datapath is None: self.logger.error('Datapath not initialized') return if not imsi: self.logger.error('No subscriber specified') return if not rule_ids: self._deactivate_flows_for_subscriber(imsi) else: for rule_id in rule_ids: self._deactivate_flow_for_rule(imsi, rule_id) def cleanup_state(self): pass 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) def _deactivate_flow_for_rule(self, imsi, 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 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) def _install_flow_for_rule(self, imsi, ip_addr, rule): if rule.redirect.support == rule.redirect.ENABLED: self._install_redirect_flow(imsi, ip_addr, rule) return RuleModResult.SUCCESS else: # TODO: Add support once sessiond implements restrict access QOS self.logger.error('GY only supports FINAL action redirect, other' 'final actions are not supported') return RuleModResult.FAILURE def _install_default_flow_for_subscriber(self, imsi): pass def _delete_all_flows(self, datapath): flows.delete_all_flows_from_table(datapath, self.tbl_num) flows.delete_all_flows_from_table(datapath, self._redirect_scratch) flows.delete_all_flows_from_table(datapath, self._mac_rewr) def _install_default_flows(self, datapath): """ For each direction set the default flows to just forward to next app. The enforcement flows for each subscriber would be added when the IP session is created, by reaching out to the controller/PCRF. Args: datapath: ryu datapath struct """ inbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.IN) outbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.OUT) flows.add_resubmit_next_service_flow( datapath, self.tbl_num, inbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) flows.add_resubmit_next_service_flow( datapath, self.tbl_num, outbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) def _install_redirect_flow(self, imsi, ip_addr, rule): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = rule.priority # TODO currently if redirection is enabled we ignore other flows # from rule.flow_list, confirm that this is the expected behaviour redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr, rule=rule, rule_num=rule_num, priority=priority) try: self._redirect_manager.setup_cwf_redirect(self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err) return RuleModResult.FAILURE def _install_default_flows_if_not_installed( self, datapath, existing_flows: List[OFPFlowStats]) -> List[OFPFlowStats]: inbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.IN) outbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.OUT) inbound_msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, inbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) outbound_msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, outbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) msgs, remaining_flows = self._msg_hub \ .filter_msgs_if_not_in_flow_list([inbound_msg, outbound_msg], existing_flows) if msgs: chan = self._msg_hub.send(msgs, datapath) self._wait_for_responses(chan, len(msgs)) return remaining_flows @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER) def _handle_barrier(self, ev): self._msg_hub.handle_barrier(ev) @set_ev_cls(ofp_event.EventOFPErrorMsg, MAIN_DISPATCHER) def _handle_error(self, ev): self._msg_hub.handle_error(ev)
class EnforcementController(PolicyMixin, MagmaController): """ EnforcementController The enforcement controller installs flows for policy enforcement and classification. Each flow installed matches on a rule and an IMSI and then classifies the packet with the rule. The flow also redirects and drops the packet as specified in the policy. NOTE: Enforcement currently relies on the fact that policies do not overlap. In this implementation, there is the idea of a 'default rule' which is the catch-all. This rule is treated specially and tagged with a specific priority. """ APP_NAME = "enforcement" APP_TYPE = ControllerType.LOGICAL ENFORCE_DROP_PRIORITY = flows.MINIMUM_PRIORITY + 1 # For allowing unlcassified flows for app/service type rules. UNCLASSIFIED_ALLOW_PRIORITY = ENFORCE_DROP_PRIORITY + 1 # Should not overlap with the drop flow as drop matches all packets. MIN_ENFORCE_PROGRAMMED_FLOW = UNCLASSIFIED_ALLOW_PRIORITY + 1 MAX_ENFORCE_PRIORITY = flows.MAXIMUM_PRIORITY # Effectively range is 3 -> 65535 ENFORCE_PRIORITY_RANGE = MAX_ENFORCE_PRIORITY - MIN_ENFORCE_PROGRAMMED_FLOW def __init__(self, *args, **kwargs): super(EnforcementController, self).__init__(*args, **kwargs) self._config = kwargs['config'] self.tbl_num = self._service_manager.get_table_num(self.APP_NAME) self.next_main_table = self._service_manager.get_next_table_num( self.APP_NAME) self._enforcement_stats_scratch = self._service_manager.get_table_num( EnforcementStatsController.APP_NAME) self.loop = kwargs['loop'] self._msg_hub = MessageHub(self.logger) self._redirect_scratch = \ self._service_manager.allocate_scratch_tables(self.APP_NAME, 1)[0] self._bridge_ip_address = kwargs['config']['bridge_ip_address'] self._redirect_manager = None self._qos_mgr = None self._clean_restart = kwargs['config']['clean_restart'] def initialize_on_connect(self, datapath): """ Install the default flows on datapath connect event. Args: datapath: ryu datapath struct """ self._datapath = datapath self._qos_mgr = QosManager(datapath, self.loop, self._config) self._qos_mgr.setup() self._redirect_manager = RedirectionManager( self._bridge_ip_address, self.logger, self.tbl_num, self._enforcement_stats_scratch, self._redirect_scratch, self._session_rule_version_mapper) def cleanup_on_disconnect(self, datapath): """ Cleanup flows on datapath disconnect event. Args: datapath: ryu datapath struct """ if self._clean_restart: self.delete_all_flows(datapath) def delete_all_flows(self, datapath): flows.delete_all_flows_from_table(datapath, self.tbl_num) flows.delete_all_flows_from_table(datapath, self._redirect_scratch) def cleanup_state(self): pass @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER) def _handle_barrier(self, ev): self._msg_hub.handle_barrier(ev) @set_ev_cls(ofp_event.EventOFPErrorMsg, MAIN_DISPATCHER) def _handle_error(self, ev): self._msg_hub.handle_error(ev) @set_ev_cls(ofp_event.EventOFPMeterConfigStatsReply, MAIN_DISPATCHER) def meter_config_stats_reply_handler(self, ev): if not self._qos_mgr: return qos_impl = self._qos_mgr.impl if qos_impl and isinstance(qos_impl, MeterManager): qos_impl.handle_meter_config_stats(ev.msg.body) @set_ev_cls(ofp_event.EventOFPMeterFeaturesStatsReply, MAIN_DISPATCHER) def meter_features_stats_reply_handler(self, ev): if not self._qos_mgr: return qos_impl = self._qos_mgr.impl if qos_impl and isinstance(qos_impl, MeterManager): qos_impl.handle_meter_feature_stats(ev.msg.body) def _install_default_flows_if_not_installed(self, datapath, existing_flows: List[OFPFlowStats]) -> List[OFPFlowStats]: """ For each direction set the default flows to just forward to next app. The enforcement flows for each subscriber would be added when the IP session is created, by reaching out to the controller/PCRF. If default flows are already installed, do nothing. Args: datapath: ryu datapath struct Returns: The list of flows that remain after inserting default flows """ inbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.IN) outbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, direction=Direction.OUT) inbound_msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, inbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) outbound_msg = flows.get_add_resubmit_next_service_flow_msg( datapath, self.tbl_num, outbound_match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) msgs, remaining_flows = self._msg_hub \ .filter_msgs_if_not_in_flow_list([inbound_msg, outbound_msg], existing_flows) if msgs: chan = self._msg_hub.send(msgs, datapath) self._wait_for_responses(chan, len(msgs)) return remaining_flows def get_of_priority(self, precedence): """ Lower the precedence higher the importance of the flow in 3GPP. Higher the priority higher the importance of the flow in openflow. Convert precedence to priority: 1 - Flows with precedence > 65534 will have min priority which is the min priority for a programmed flow = (default drop + 1) 2 - Flows in the precedence range 0-65534 will have priority 65535 - Precedence :param precedence: :return: """ if precedence >= self.ENFORCE_PRIORITY_RANGE: self.logger.warning( "Flow precedence is higher than OF range using min priority %d", self.MIN_ENFORCE_PROGRAMMED_FLOW) return self.MIN_ENFORCE_PROGRAMMED_FLOW return self.MAX_ENFORCE_PRIORITY - precedence def _get_rule_match_flow_msgs(self, imsi, ip_addr, apn_ambr, rule): """ Get flow msgs to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = self.get_of_priority(rule.priority) flow_adds = [] for flow in rule.flow_list: try: flow_adds.extend(self._get_classify_rule_flow_msgs( imsi, ip_addr, apn_ambr, flow, rule_num, priority, rule.qos, rule.hard_timeout, rule.id, rule.app_name, rule.app_service_type)) except FlowMatchError as err: # invalid match self.logger.error( "Failed to get flow msg '%s' for subscriber %s: %s", rule.id, imsi, err) raise err return flow_adds def _install_flow_for_rule(self, imsi, ip_addr, apn_ambr, rule): """ Install a flow to get stats for a particular rule. Flows will match on IMSI, cookie (the rule num), in/out direction Args: imsi (string): subscriber to install rule for ip_addr (string): subscriber session ipv4 address rule (PolicyRule): policy rule proto """ if rule.redirect.support == rule.redirect.ENABLED: return self._install_redirect_flow(imsi, ip_addr, rule) if not rule.flow_list: self.logger.error('The flow list for imsi %s, rule.id - %s' 'is empty, this shoudn\'t happen', imsi, rule.id) return RuleModResult.FAILURE flow_adds = [] try: flow_adds = self._get_rule_match_flow_msgs(imsi, ip_addr, apn_ambr, rule) except FlowMatchError: return RuleModResult.FAILURE chan = self._msg_hub.send(flow_adds, self._datapath) return self._wait_for_rule_responses(imsi, rule, chan) def _wait_for_rule_responses(self, imsi, rule, chan): def fail(err): self.logger.error( "Failed to install rule %s for subscriber %s: %s", rule.id, imsi, err) self._deactivate_flow_for_rule(imsi, rule.id) return RuleModResult.FAILURE for _ in range(len(rule.flow_list)): try: result = chan.get() except MsgChannel.Timeout: return fail("No response from OVS") if not result.ok(): return fail(result.exception()) return RuleModResult.SUCCESS 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): """ 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, ip_addr, apn_ambr, 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 _install_redirect_flow(self, imsi, ip_addr, rule): rule_num = self._rule_mapper.get_or_create_rule_num(rule.id) priority = self.get_of_priority(rule.priority) redirect_request = RedirectionManager.RedirectRequest( imsi=imsi, ip_addr=ip_addr, rule=rule, rule_num=rule_num, priority=priority) try: self._redirect_manager.handle_redirection( self._datapath, self.loop, redirect_request) return RuleModResult.SUCCESS except RedirectException as err: self.logger.error( 'Redirect Exception for imsi %s, rule.id - %s : %s', imsi, rule.id, err ) return RuleModResult.FAILURE def _get_classify_rule_of_actions(self, flow, rule_num, imsi, ip_addr, apn_ambr, qos, rule_id): parser = self._datapath.ofproto_parser instructions = [] # encode the rule id in hex of_note = parser.NXActionNote(list(rule_id.encode())) actions = [of_note] if flow.action == flow.DENY: return actions, instructions mbr_ul = qos.max_req_bw_ul mbr_dl = qos.max_req_bw_dl qos_info = None ambr = None d = flow.match.direction if d == flow.match.UPLINK: if apn_ambr: ambr = apn_ambr.max_bandwidth_ul if mbr_ul != 0: qos_info = QosInfo(gbr=qos.gbr_ul, mbr=mbr_ul) if d == flow.match.DOWNLINK: if apn_ambr: ambr = apn_ambr.max_bandwidth_dl if mbr_dl != 0: qos_info = QosInfo(gbr=qos.gbr_dl, mbr=mbr_dl) if qos_info or ambr: action, inst = self._qos_mgr.add_subscriber_qos( imsi, ip_addr, ambr, rule_num, d, qos_info) self.logger.debug("adding Actions %s instruction %s ", action, inst) if action: actions.append(action) if inst: instructions.append(inst) version = self._session_rule_version_mapper.get_version(imsi, rule_id) actions.extend( [parser.NXActionRegLoad2(dst='reg2', value=rule_num), parser.NXActionRegLoad2(dst=RULE_VERSION_REG, value=version) ]) return actions, instructions 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 _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 _deactivate_flow_for_rule(self, imsi, 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 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) self._qos_mgr.remove_subscriber_qos(imsi, num) 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_mgr.remove_subscriber_qos(imsi) def deactivate_rules(self, imsi, rule_ids): """ Deactivate flows for a subscriber. If only imsi is present, delete all rule flows for a subscriber (i.e. end its session). If rule_ids are present, delete the rule flows for that subscriber. Args: imsi (string): subscriber id rule_ids (list of strings): policy rule ids """ if not self.init_finished: self.logger.error('Pipelined is not initialized') return RuleModResult.FAILURE if self._datapath is None: self.logger.error('Datapath not initialized') return if not imsi: self.logger.error('No subscriber specified') return if not rule_ids: self._deactivate_flows_for_subscriber(imsi) else: for rule_id in rule_ids: self._deactivate_flow_for_rule(imsi, rule_id)