def _gen_response(self, state_id, msg, result_code, body_avps=None): """ Generates response message headers to an incoming request and appends the response AVPs in the expected order Args: state_id: the server state id msg: the message to respond to result_code: the Result-Code of the response body_avps: (optional) the AVPs to include in the response body Returns: a message instance containing the response """ # Generate response message headers if body_avps is None: body_avps = [] resp_msg = message.Message.create_response_msg(msg) # Session AVPs must come immediately after header RFC3588 8.8 resp_msg.append_avp(msg.find_avp(*avp.resolve('Session-Id'))) for body_avp in body_avps: resp_msg.append_avp(body_avp) # Auth-Session-State is NO_STATE_MAINTAINED (1) resp_msg.append_avp(avp.AVP('Auth-Session-State', 1)) # Host identifiers resp_msg.append_avp(avp.AVP('Origin-Host', self.host)) resp_msg.append_avp(avp.AVP('Origin-Realm', self.realm)) resp_msg.append_avp(avp.AVP('Origin-State-Id', state_id)) # Response result resp_msg.append_avp(avp.AVP('Result-Code', result_code)) return resp_msg
def test_get_avp(self): """We can get the first occurance of an AVP by name or code""" self.assertEqual(self.msg.find_avp(*avp.resolve('User-Name')).value, 'hello') self.assertEqual(self.msg.find_avp(0, 257).value, '127.0.0.1') # Doesn't exist so returns None self.assertEqual(self.msg.find_avp(0, 1337), None)
def _send_location_request_with_retries(self, state_id, msg, retries_left): user_name = msg.find_avp(*avp.resolve('User-Name')).value visited_plmn = msg.find_avp(*avp.resolve('Visited-PLMN-Id')).value ulr_flags = msg.find_avp(*avp.resolve('ULR-Flags')).value request = UpdateLocationRequest( user_name=user_name, visited_plmn=visited_plmn, skip_subscriber_data=ulr_flags & 1 << 2, initial_attach=ulr_flags & 1 << 5, ) future = self._client.UpdateLocation.future(request, self.grpc_timeout) future.add_done_callback( lambda answer: self._loop.call_soon_threadsafe( self._relay_update_location_answer, state_id, msg, answer, retries_left))
def test_message_filter_avp(self): """We can get a list of AVPs that matches a code and vendor""" # Filter for code 1 (User-Name AVP) filtered_avps = list(self.msg.filter_avps(avp.VendorId.DEFAULT, 1)) self.assertEqual(len(filtered_avps), 2) self.assertEqual(filtered_avps[0].value, 'hello') self.assertEqual(filtered_avps[1].value, 'world') # Filter for non-existent self.assertEqual(len(list(self.msg.filter_avps(*avp.resolve('Session-Id')))), 0)
def _send_auth_with_retries(self, state_id, msg, retries_left): user_name = msg.find_avp(*avp.resolve('User-Name')).value visited_plmn = msg.find_avp(*avp.resolve('Visited-PLMN-Id')).value request_eutran_info = msg.find_avp( *avp.resolve('Requested-EUTRAN-Authentication-Info'), ) num_requested_eutran_vectors = request_eutran_info.find_avp( *avp.resolve('Number-Of-Requested-Vectors'), ).value immediate_response_preferred = request_eutran_info.find_avp( *avp.resolve('Immediate-Response-Preferred'), ).value resync_info = request_eutran_info.find_avp( *avp.resolve('Re-Synchronization-Info'), ) request = AuthenticationInformationRequest( user_name=user_name, visited_plmn=visited_plmn, num_requested_eutran_vectors=num_requested_eutran_vectors, immediate_response_preferred=immediate_response_preferred, resync_info=resync_info.value if resync_info else None, ) future = self._client.AuthenticationInformation.future( request, self.grpc_timeout, ) future.add_done_callback( lambda answer: self._loop.call_soon_threadsafe( self._relay_auth_answer, state_id, msg, answer, retries_left, ), )
def _relay_auth_answer(self, state_id, msg, answer_future, retries_left): user_name = msg.find_avp(*avp.resolve('User-Name')).value err = answer_future.exception() if err and retries_left > 0: # TODO: retry only on network failure and not application failures logging.info("Auth %s Error! [%s] %s, retrying...", user_name, err.code(), err.details()) self._send_auth_with_retries( state_id, msg, retries_left - 1, ) return elif err: logging.warning("Auth %s Error! [%s] %s", user_name, err.code(), err.details()) resp = self._gen_response(state_id, msg, avp.ResultCode.DIAMETER_UNABLE_TO_COMPLY) S6A_AUTH_FAILURE_TOTAL.labels( code=avp.ResultCode.DIAMETER_UNABLE_TO_COMPLY).inc() else: answer = answer_future.result() error_code = answer.error_code if answer.error_code: result_info = avp.AVP('Experimental-Result', [ avp.AVP('Vendor-Id', 10415), avp.AVP('Experimental-Result-Code', error_code) ]) resp = self._gen_response(state_id, msg, error_code, [result_info]) logging.warning("Auth S6a %s Error! [%s]", user_name, error_code) S6A_AUTH_FAILURE_TOTAL.labels(code=error_code).inc() else: auth_info = avp.AVP('Authentication-Info', [ avp.AVP('E-UTRAN-Vector', [ avp.AVP('RAND', vector.rand), avp.AVP('XRES', vector.xres), avp.AVP('AUTN', vector.autn), avp.AVP('KASME', vector.kasme) ]) for vector in answer.eutran_vectors ]) resp = self._gen_response(state_id, msg, avp.ResultCode.DIAMETER_SUCCESS, [auth_info]) S6A_AUTH_SUCCESS_TOTAL.inc() self.writer.send_msg(resp)
def _relay_update_location_answer( self, state_id, msg, answer_future, retries_left, ): err = answer_future.exception() if err and retries_left > 0: # TODO: retry only on network failure and not application failures user_name = msg.find_avp(*avp.resolve('User-Name')).value logging.info( "Location Update %s Error! [%s] %s, retrying...", user_name, err.code(), err.details(), ) self._send_location_request_with_retries( state_id, msg, retries_left - 1, ) return elif err: user_name = msg.find_avp(*avp.resolve('User-Name')).value logging.warning( "Location Update %s Error! [%s] %s", user_name, err.code(), err.details(), ) resp = self._gen_response( state_id, msg, avp.ResultCode.DIAMETER_UNABLE_TO_COMPLY, ) else: answer = answer_future.result() error_code = answer.error_code if error_code: result_info = avp.AVP( 'Experimental-Result', [ avp.AVP('Vendor-Id', 10415), avp.AVP('Experimental-Result-Code', error_code), ], ) resp = self._gen_response( state_id, msg, error_code, [result_info], ) else: # Stubbed out Subscription Data from OAI subscription_data = avp.AVP( 'Subscription-Data', [ avp.AVP('MSISDN', answer.msisdn), avp.AVP('Access-Restriction-Data', 47), avp.AVP('Subscriber-Status', 0), avp.AVP('Network-Access-Mode', 2), avp.AVP( 'AMBR', [ avp.AVP( 'Max-Requested-Bandwidth-UL', answer.total_ambr.max_bandwidth_ul, ), avp.AVP( 'Max-Requested-Bandwidth-DL', answer.total_ambr.max_bandwidth_dl, ), ], ), avp.AVP( 'APN-Configuration-Profile', [ avp.AVP( 'Context-Identifier', answer.default_context_id, ), avp.AVP( 'All-APN-Configurations-Included-Indicator', 1 if answer.all_apns_included else 0, ), *[ avp.AVP( 'APN-Configuration', [ avp.AVP('Context-Identifier', apn.context_id), avp.AVP('PDN-Type', apn.pdn), avp.AVP( 'Service-Selection', apn.service_selection, ), avp.AVP( 'EPS-Subscribed-QoS-Profile', [ avp.AVP( 'QoS-Class-Identifier', apn.qos_profile. class_id, ), avp.AVP( 'Allocation-Retention-Priority', [ avp.AVP( 'Priority-Level', apn.qos_profile. priority_level, ), avp.AVP( 'Pre-emption-Capability', 1 if apn. qos_profile. preemption_capability else 0, ), avp.AVP( 'Pre-emption-Vulnerability', 1 if apn. qos_profile. preemption_vulnerability else 0, ), ], ), ], ), avp.AVP( 'AMBR', [ avp.AVP( 'Max-Requested-Bandwidth-UL', apn.ambr. max_bandwidth_ul, ), avp.AVP( 'Max-Requested-Bandwidth-DL', apn.ambr. max_bandwidth_dl, ), ], ), ], ) for apn in answer.apn ], ], ), ], ) ula_flags = avp.AVP('ULA-Flags', 1) resp = self._gen_response( state_id, msg, avp.ResultCode.DIAMETER_SUCCESS, [ula_flags, subscription_data], ) self.writer.send_msg(resp) S6A_LUR_TOTAL.inc()
def _send_location_request(self, state_id, msg): """ Handles an incoming 3GPP-Update-Location-Request request and writes a 3GPP-Update-Location-Answer Args: state_id: the server state id msg: an update location request message Returns: None """ # Validate the message if not self.validate_message(state_id, msg): return ula_flags = avp.AVP('ULA-Flags', 1) try: imsi = msg.find_avp(*avp.resolve('User-Name')).value profile = self.lte_processor.get_sub_profile(imsi) except SubscriberNotFoundError as e: resp = self._gen_response( state_id, msg, avp.ResultCode.DIAMETER_ERROR_USER_UNKNOWN, ) logging.warning('Subscriber not found for ULR: %s', e) return # Stubbed out Subscription Data from OAI subscription_data = avp.AVP( 'Subscription-Data', [ avp.AVP('MSISDN', b'333608050011'), avp.AVP('Access-Restriction-Data', 47), avp.AVP('Subscriber-Status', 0), avp.AVP('Network-Access-Mode', 2), avp.AVP( 'AMBR', [ avp.AVP('Max-Requested-Bandwidth-UL', profile.max_ul_bit_rate), avp.AVP('Max-Requested-Bandwidth-DL', profile.max_dl_bit_rate), ], ), avp.AVP( 'APN-Configuration-Profile', [ avp.AVP('Context-Identifier', 0), avp.AVP('All-APN-Configurations-Included-Indicator', 0), avp.AVP( 'APN-Configuration', [ avp.AVP('Context-Identifier', 0), avp.AVP('PDN-Type', 0), avp.AVP('Service-Selection', 'oai.ipv4'), avp.AVP( 'EPS-Subscribed-QoS-Profile', [ avp.AVP('QoS-Class-Identifier', 9), avp.AVP( 'Allocation-Retention-Priority', [ avp.AVP('Priority-Level', 15), avp.AVP( 'Pre-emption-Capability', 1), avp.AVP( 'Pre-emption-Vulnerability', 0), ], ), ], ), avp.AVP( 'AMBR', [ avp.AVP( 'Max-Requested-Bandwidth-UL', profile.max_ul_bit_rate, ), avp.AVP( 'Max-Requested-Bandwidth-DL', profile.max_dl_bit_rate, ), ], ), ], ), ], ), ], ) S6A_LUR_TOTAL.inc() resp = self._gen_response( state_id, msg, avp.ResultCode.DIAMETER_SUCCESS, [ula_flags, subscription_data], ) self.writer.send_msg(resp)
def _send_auth(self, state_id, msg): """ Handles an incoming 3GPP-Authentication-Information-Request and writes a 3GPP-Authentication-Information-Answer Args: state_id: the server state id msg: an auth request message Returns: None """ # Validate the message if not self.validate_message(state_id, msg): return imsi = "" try: imsi = msg.find_avp(*avp.resolve('User-Name')).value plmn = msg.find_avp(*avp.resolve('Visited-PLMN-Id')).value request_eutran_info = msg.find_avp( *avp.resolve('Requested-EUTRAN-Authentication-Info'), ) re_sync_info = request_eutran_info.find_avp( *avp.resolve('Re-Synchronization-Info'), ) if re_sync_info: # According to 29.272 7.3.15 this should be concatenation of # RAND and AUTS but OAI only sends AUTS so hardcode till fixed rand = re_sync_info.value[:16] auts = re_sync_info.value[16:] self.lte_processor.resync_lte_auth_seq(imsi, rand, auts) rand, xres, autn, kasme = \ self.lte_processor.generate_lte_auth_vector(imsi, plmn) auth_info = avp.AVP( 'Authentication-Info', [ avp.AVP( 'E-UTRAN-Vector', [ avp.AVP('RAND', rand), avp.AVP('XRES', xres), avp.AVP('AUTN', autn), avp.AVP('KASME', kasme), ], ), ], ) S6A_AUTH_SUCCESS_TOTAL.inc() resp = self._gen_response( state_id, msg, avp.ResultCode.DIAMETER_SUCCESS, [auth_info], ) logging.info("Auth success: %s", imsi) except CryptoError as e: S6A_AUTH_FAILURE_TOTAL.labels( code=avp.ResultCode.DIAMETER_AUTHENTICATION_REJECTED, ).inc() resp = self._gen_response( state_id, msg, avp.ResultCode.DIAMETER_AUTHENTICATION_REJECTED, ) logging.error("Auth error for %s: %s", imsi, e) except SubscriberNotFoundError as e: S6A_AUTH_FAILURE_TOTAL.labels( code=avp.ResultCode.DIAMETER_ERROR_USER_UNKNOWN, ).inc() resp = self._gen_response( state_id, msg, avp.ResultCode.DIAMETER_ERROR_USER_UNKNOWN, ) logging.warning("Subscriber not found: %s", e) except ServiceNotActive as e: S6A_AUTH_FAILURE_TOTAL.labels( code=avp.ResultCode.DIAMETER_ERROR_UNAUTHORIZED_SERVICE, ).inc() resp = self._gen_response( state_id, msg, avp.ResultCode.DIAMETER_ERROR_UNAUTHORIZED_SERVICE, ) logging.error("Service not active for %s: %s", imsi, e) self.writer.send_msg(resp)