def _install_default_flows(self, dp): match = MagmaMatch(in_port=self.config.he_proxy_port) flows.add_drop_flow(dp, self.tbl_num, match, priority=flows.MINIMUM_PRIORITY + 1) match = MagmaMatch() flows.add_resubmit_next_service_flow(dp, self.tbl_num, match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_table)
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 _install_default_flows(self, datapath: Datapath) -> None: """ For each direction set the default flows to just forward to next app. Args: datapath: ryu datapath struct """ parser = self._datapath.ofproto_parser match = MagmaMatch() flows.add_resubmit_next_service_flow( datapath, self.tbl_num, match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_main_table) if not self._service_manager.is_app_enabled(DPIController.APP_NAME): flows.add_resubmit_next_service_flow( self._datapath, self._app_set_tbl_num, MagmaMatch(), priority=flows.MINIMUM_PRIORITY, cookie=self.tbl_num, resubmit_table=self._imsi_set_tbl_num) flows.add_resubmit_next_service_flow( self._datapath, self._imsi_set_tbl_num, MagmaMatch(), priority=flows.MINIMUM_PRIORITY, cookie=self.tbl_num, resubmit_table=self._ipfix_sample_tbl_num) if self.ipfix_config.enabled and (self._dpi_enabled or self._conntrackd_enabled): pdp = 1 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=[0, 0, 0, 0, 0, 0], msisdn="defau".encode('ascii'), apn_name="default".encode('ascii'), pdp_start_epoch=pdp.to_bytes(8, byteorder='little'), sampling_port=self.ipfix_config.sampling_port) ] flows.add_drop_flow(self._datapath, self._ipfix_sample_tbl_num, match, actions, priority=flows.DEFAULT_PRIORITY)
def _install_ip_blocklist_flow(self, datapath): """ Install flows to drop any packets with ip address blocks matching the blocklist. """ for entry in self.config.ip_blocklist: ip_network = ipaddress.IPv4Network(entry['ip']) direction = entry.get('direction', None) if direction is not None and \ direction not in [ self.CONFIG_INBOUND_DIRECTION, self.CONFIG_OUTBOUND_DIRECTION, ]: self.logger.error( 'Invalid direction found in ip blocklist: %s', direction, ) continue # If no direction is specified, both outbound and inbound traffic # will be dropped. if direction is None or direction == self.CONFIG_INBOUND_DIRECTION: match = MagmaMatch( direction=Direction.OUT, eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=( ip_network.network_address, ip_network.netmask, ), ) flows.add_drop_flow( datapath, self.tbl_num, match, [], priority=flows.DEFAULT_PRIORITY, ) if direction is None or \ direction == self.CONFIG_OUTBOUND_DIRECTION: match = MagmaMatch( direction=Direction.IN, eth_type=ether_types.ETH_TYPE_IP, ipv4_src=( ip_network.network_address, ip_network.netmask, ), ) flows.add_drop_flow( datapath, self.tbl_num, match, [], priority=flows.DEFAULT_PRIORITY, )
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 _install_default_arp_drop_flow(self, datapath): """ Install default drop flow for all unmatched arps """ # Drop all other ARPs match = MagmaMatch(eth_type=ether_types.ETH_TYPE_ARP) flows.add_drop_flow( datapath, self.table_num, match, [], priority=flows.DEFAULT_PRIORITY, )
def _install_drop_rule_for_untagged_arps(self, datapath): """ Install default drop flow for all unmatched arps """ # Drop all other ARPs match = MagmaMatch(eth_type=ether_types.ETH_TYPE_ARP, imsi=0) flows.add_drop_flow( datapath, self.table_num, match, [], priority=flows.UE_FLOW_PRIORITY - 1, )
def _install_default_flows(self, datapath): parser = datapath.ofproto_parser match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ct_state=(0x0, self.CT_TRK)) actions = [ parser.NXActionCT(flags=0x0, zone_src=None, zone_ofs_nbits=ofs_nbits(14, 15), recirc_table=self.conntrack_scratch, alg=0, actions=[]) ] flows.add_resubmit_next_service_flow(datapath, self.tbl_num, match, actions, priority=flows.DEFAULT_PRIORITY, resubmit_table=self.next_table) # Match all new connections on scratch table match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ct_zone=ofs_nbits(14, 15), ct_state=(self.CT_NEW | self.CT_TRK, self.CT_NEW | self.CT_TRK)) actions = [ parser.NXActionCT(flags=0x1, zone_src=None, zone_ofs_nbits=ofs_nbits(14, 15), recirc_table=self.connection_event_table, alg=0, actions=[]) ] flows.add_drop_flow(datapath, self.conntrack_scratch, match, actions, priority=flows.DEFAULT_PRIORITY) match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP) flows.add_resubmit_next_service_flow(datapath, self.tbl_num, match, [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_table)
def _packet_in_handler(self, ev): msg = ev.msg pkt = packet.Packet(msg.data) pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) if pkt_ipv4 is None: return None dst = pkt_ipv4.dst if dst is None: return None # For sending notification to SMF using GRPC self._send_message_interface(dst, msg.cookie) # Add flow for paging with hard time. match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ipv4_dst=dst) flows.add_drop_flow(self._datapath, self.tbl_num, match, [], priority = Utils.PAGING_RULE_DROP_PRIORITY, hard_timeout= self.config.paging_timeout)
def _install_default_flows(self, datapath): """ Adds default flows for access control. For normal(ip blocklist table): Forward uplink to next table Forward downlink to scratch table For scratch table: Drop all unmatched traffic """ flows.add_resubmit_next_service_flow( datapath, self.tbl_num, MagmaMatch(direction=Direction.IN), [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_table, ) flows.add_resubmit_next_service_flow( datapath, self.tbl_num, MagmaMatch(direction=Direction.OUT), [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self._tunnel_acl_scratch, ) if self.config.setup_type == 'CWF': flows.add_drop_flow( datapath, self._tunnel_acl_scratch, MagmaMatch(), [], priority=flows.MINIMUM_PRIORITY, ) else: # TODO add LTE WLAN peers flows.add_resubmit_next_service_flow( datapath, self._tunnel_acl_scratch, MagmaMatch(), [], priority=flows.MINIMUM_PRIORITY, resubmit_table=self.next_table, )
def _install_ip_blocking_flow(self, datapath, ip_network, direction, ip_version): """ Install flows to drop any packets with ip address blocks matching the blocklist. """ if direction and direction not in [ self.CONFIG_INBOUND_DIRECTION, self.CONFIG_OUTBOUND_DIRECTION, ]: self.logger.error( 'Invalid direction found in ip blocklist: %s', direction, ) return # If no direction is specified, both outbound and inbound traffic # will be dropped. if direction is None or direction == self.CONFIG_INBOUND_DIRECTION: match = AccessControlController._create_magma_match_blocking_flow_out( ip_version, ip_network, ) flows.add_drop_flow( datapath, self.tbl_num, match, [], priority=flows.DEFAULT_PRIORITY, ) if direction is None or direction == self.CONFIG_OUTBOUND_DIRECTION: match = AccessControlController._create_magma_match_blocking_flow_in( ip_version, ip_network, ) flows.add_drop_flow( datapath, self.tbl_num, match, [], priority=flows.DEFAULT_PRIORITY, )
def setup_cwf_redirect(self, datapath, loop, redirect_request): """ Add flows to forward traffic to the redirection server for cwf networks 1) Intercept tcp traffic to the web to the redirection server, which completes the tcp handshake. Also overwrite UE src ip to match the subnet of the redirection server. This is done by assigning an internal IP per each subscriber. Add an OVS flow with a learn action (flow catches inbound tcp http packets, while learn action creates another flow that rewrites packet back to send to ue) 2) Add flows to allow UDP traffic so DNS queries can go through. Add flows with a higher priority that allow traffic to and from the address provided in redirect rule. TODO we might want to track stats for these rules and report to sessiond """ if not self._cwf_args_set: raise RedirectException("Can't install cwf redirection, missing" "cwf specific args, call set_cwf_args()") imsi = redirect_request.imsi rule = redirect_request.rule rule_num = redirect_request.rule_num rule_version = redirect_request.rule_version priority = redirect_request.priority if rule.redirect.address_type == rule.redirect.URL: self._install_url_bypass_flows(datapath, loop, imsi, rule, rule_num, rule_version, priority) elif rule.redirect.address_type == rule.redirect.IPv4: self._install_ipv4_bypass_flows(datapath, imsi, rule, rule_num, rule_version, priority, [rule.redirect.server_address]) parser = datapath.ofproto_parser # TODO use subscriber ip_addr to generate internal IP and release # internal IP when subscriber disconnects or redirection flow is removed internal_ip = self._internal_ip_allocator.next_ip() self._save_redirect_entry(internal_ip, rule.redirect) #TODO check if we actually need this, dns might already be allowed self._install_dns_flows(datapath, imsi, rule, rule_num, rule_version, priority) match_tcp_80 = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.OUT, tcp_dst=80) match_tcp_8008 = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.OUT, tcp_dst=8080) match_tcp_8080 = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.OUT, tcp_dst=8008) actions = [ parser.NXActionLearn( table_id=self._mac_rewrite_scratch, priority=flows.UE_FLOW_PRIORITY, cookie=rule_num, specs=[ parser.NXFlowSpecMatch(src=ether_types.ETH_TYPE_IP, dst=('eth_type_nxm', 0), n_bits=16), parser.NXFlowSpecMatch(src=IPPROTO_TCP, dst=('ip_proto_nxm', 0), n_bits=8), parser.NXFlowSpecMatch(src=int( ipaddress.IPv4Address(self._bridge_ip)), dst=('ipv4_src_nxm', 0), n_bits=32), parser.NXFlowSpecMatch(src=int(internal_ip), dst=('ipv4_dst_nxm', 0), n_bits=32), parser.NXFlowSpecMatch(src=('tcp_src_nxm', 0), dst=('tcp_dst_nxm', 0), n_bits=16), parser.NXFlowSpecMatch(src=self._redirect_port, dst=('tcp_src_nxm', 0), n_bits=16), parser.NXFlowSpecLoad(src=('eth_src_nxm', 0), dst=('eth_dst_nxm', 0), n_bits=48), parser.NXFlowSpecLoad(src=encode_imsi(imsi), dst=(IMSI_REG, 0), n_bits=64), parser.NXFlowSpecLoad(src=('ipv4_src_nxm', 0), dst=('ipv4_dst_nxm', 0), n_bits=32), parser.NXFlowSpecLoad(src=('ipv4_dst_nxm', 0), dst=('ipv4_src_nxm', 0), n_bits=32), parser.NXFlowSpecLoad(src=('tcp_dst_nxm', 0), dst=('tcp_src_nxm', 0), n_bits=16), ]), parser.OFPActionSetField(ipv4_src=str(internal_ip)), parser.OFPActionSetField(ipv4_dst=self._bridge_ip), parser.OFPActionSetField(eth_dst=self._bridge_mac), parser.OFPActionSetField(tcp_dst=self._redirect_port), ] flows.add_output_flow(datapath, self.main_tbl_num, match_tcp_80, actions, priority=flows.UE_FLOW_PRIORITY, cookie=rule_num, output_port=OFPP_LOCAL) flows.add_output_flow(datapath, self.main_tbl_num, match_tcp_8008, actions, priority=flows.UE_FLOW_PRIORITY, cookie=rule_num, output_port=OFPP_LOCAL) flows.add_output_flow(datapath, self.main_tbl_num, match_tcp_8080, actions, priority=flows.UE_FLOW_PRIORITY, cookie=rule_num, output_port=OFPP_LOCAL) # Add flows for vlan traffic too (we need to pop vlan for flask server) # In ryu vlan_vid=(0x1000, 0x1000) matches all vlans match_tcp_80_vlan = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.OUT, tcp_dst=80, vlan_vid=(0x1000, 0x1000)) match_tcp_8008_vlan = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.OUT, tcp_dst=8080, vlan_vid=(0x1000, 0x1000)) match_tcp_8080_vlan = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.OUT, tcp_dst=8008, vlan_vid=(0x1000, 0x1000)) actions.append(parser.OFPActionPopVlan()) flows.add_output_flow(datapath, self.main_tbl_num, match_tcp_80_vlan, actions, priority=flows.UE_FLOW_PRIORITY + 1, cookie=rule_num, output_port=OFPP_LOCAL) flows.add_output_flow(datapath, self.main_tbl_num, match_tcp_8008_vlan, actions, priority=flows.UE_FLOW_PRIORITY + 1, cookie=rule_num, output_port=OFPP_LOCAL) flows.add_output_flow(datapath, self.main_tbl_num, match_tcp_8080_vlan, actions, priority=flows.UE_FLOW_PRIORITY + 1, cookie=rule_num, output_port=OFPP_LOCAL) # TODO cleanup, make this a default rule in the ue_mac table ue_tbl = 0 ue_next_tbl = 1 # Allows traffic back from the flask server match = MagmaMatch(in_port=OFPP_LOCAL) actions = [ parser.NXActionResubmitTable(table_id=self._mac_rewrite_scratch) ] flows.add_resubmit_next_service_flow(datapath, ue_tbl, match, actions=actions, priority=flows.DEFAULT_PRIORITY, resubmit_table=ue_next_tbl) match = MagmaMatch(imsi=encode_imsi(imsi), eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, direction=Direction.IN, in_port=OFPP_LOCAL) flows.add_resubmit_next_service_flow(datapath, self.main_tbl_num, match, [], priority=flows.DEFAULT_PRIORITY, cookie=rule_num, resubmit_table=self._egress_table) # Mac doesn't matter as we rewrite it anwyays mac_addr = '01:02:03:04:05:06' if self._arp_contoller or self._arpd_controller_fut.done(): if not self._arp_contoller: self._arp_contoller = self._arpd_controller_fut.result() self._arp_contoller.set_incoming_arp_flows(datapath, internal_ip, mac_addr) # Drop all other traffic that doesn't match match = MagmaMatch(imsi=encode_imsi(imsi)) flows.add_drop_flow(datapath, self.main_tbl_num, match, [], priority=flows.MINIMUM_PRIORITY + 1, cookie=rule_num)
def _install_scratch_table_flows(self, datapath, imsi, rule, rule_num, rule_version, priority): """ The flow action for subscribers that need to be redirected does 2 things * Forward requests from subscriber to the internal http server * Instantiate a flow that matches response packets from the server and sends them back to subscriber Match: incoming tcp traffic with port 80, direction out Action: 1) Set reg2 to rule_num 2) Set ip dst to server ip 3) Output to table 20 4) Apply LearnAction: LearnAction(adds new flow for every pkt flow that hits this rule) 1) Match ip packets 2) Match tcp protocol 3) Match packets from LOCAL port 4) Match ip src = server ip 5) Match ip dst = current flow ip src 6) Match tcp src = current flow tcp dst 7) Match tcp dst = current flow tcp src 8) Load ip src = current flow ip dst 9) Output through gtp0 """ parser = datapath.ofproto_parser match_http = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP, ip_proto=IPPROTO_TCP, tcp_dst=80, imsi=encode_imsi(imsi), direction=Direction.OUT) of_note = parser.NXActionNote(list(rule.id.encode())) actions = [ parser.NXActionLearn( table_id=self.main_tbl_num, priority=priority, cookie=rule_num, specs=[ parser.NXFlowSpecMatch(src=ether_types.ETH_TYPE_IP, dst=('eth_type_nxm', 0), n_bits=16), parser.NXFlowSpecMatch(src=IPPROTO_TCP, dst=('ip_proto_nxm', 0), n_bits=8), parser.NXFlowSpecMatch(src=Direction.IN, dst=(DIRECTION_REG, 0), n_bits=32), parser.NXFlowSpecMatch(src=int( ipaddress.IPv4Address(self._bridge_ip)), dst=('ipv4_src_nxm', 0), n_bits=32), parser.NXFlowSpecMatch(src=('ipv4_src_nxm', 0), dst=('ipv4_dst_nxm', 0), n_bits=32), parser.NXFlowSpecMatch(src=('tcp_src_nxm', 0), dst=('tcp_dst_nxm', 0), n_bits=16), parser.NXFlowSpecMatch(src=self._redirect_port, dst=('tcp_src_nxm', 0), n_bits=16), parser.NXFlowSpecMatch(src=encode_imsi(imsi), dst=(IMSI_REG, 0), n_bits=64), parser.NXFlowSpecLoad(src=('ipv4_dst_nxm', 0), dst=('ipv4_src_nxm', 0), n_bits=32), parser.NXFlowSpecLoad(src=80, dst=('tcp_src_nxm', 0), n_bits=16), # Learn doesn't support resubmit to table, so send directly parser.NXFlowSpecOutput(src=('in_port', 0), dst="", n_bits=16), ]), parser.NXActionRegLoad2(dst=SCRATCH_REGS[0], value=self.REDIRECT_PROCESSED), parser.OFPActionSetField(ipv4_dst=self._bridge_ip), parser.OFPActionSetField(tcp_dst=self._redirect_port), of_note, ] actions += self._load_rule_actions(parser, rule_num, rule_version) flows.add_resubmit_current_service_flow( datapath, self._scratch_tbl_num, match_http, actions, priority=priority, cookie=rule_num, hard_timeout=rule.hard_timeout, resubmit_table=self.main_tbl_num) match = MagmaMatch(imsi=encode_imsi(imsi)) action = [] flows.add_drop_flow(datapath, self._scratch_tbl_num, match, action, priority=flows.MINIMUM_PRIORITY + 1, cookie=rule_num)