diff --git a/lib/diameter.py b/lib/diameter.py index b8af15b..e5f4819 100755 --- a/lib/diameter.py +++ b/lib/diameter.py @@ -426,6 +426,7 @@ def decodeAvpPacket(self, data): failsafeCounter += 1 if failsafeCounter > 100: + self.logTool.log(service='HSS', level='warning', message=f"[diameter.py] [decodeAvpPacket] Diameter AVP Decoder Failsafe activated: {data}", redisClient=self.redisMessaging) break avp_vars = {} # The first 4 bytes contains the AVP code @@ -466,6 +467,7 @@ def decodeAvpPacket(self, data): failsafeCounter += 1 if failsafeCounter > 100: + self.logTool.log(service='HSS', level='warning', message=f"[diameter.py] [decodeAvpPacket] Diameter Sub-AVP Decoder Failsafe activated: {data}", redisClient=self.redisMessaging) break # Pop the sub avp data from the list (remove from the end) @@ -505,7 +507,8 @@ def decodeAvpPacket(self, data): if containsSubAvps or containsNestedSubAvps: payloadContainsSubAvps = True else: - payloadContainsSubAvps = False + if not subAvpUnprocessedStack: + payloadContainsSubAvps = False if avpPaddingLength > 0: processed_avps.append(avp_vars) @@ -519,7 +522,8 @@ def decodeAvpPacket(self, data): return processed_avps - def get_avp_data(self, avps, avp_code): #Loops through list of dicts generated by the packet decoder, and returns the data for a specific AVP code in list (May be more than one AVP with same code but different data) + def get_avp_data(self, avps, avp_code): + #Loops through list of dicts generated by the packet decoder, and returns the data for a specific AVP code in list (May be more than one AVP with same code but different data) misc_data = [] for avpObject in avps: if int(avpObject['avp_code']) == int(avp_code): @@ -2941,63 +2945,114 @@ def Answer_16777236_265(self, packet_vars, avps): except Exception as e: pass - # Extract the SDP for both Uplink and Downlink, to create TFTs. - try: - sdpOffer = self.get_avp_data(avps, 524)[0] - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got SDP Offer raw: {sdpOffer}", redisClient=self.redisMessaging) - sdpOffer = binascii.unhexlify(sdpOffer).decode('utf-8') - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got SDP Offer decoded: {sdpOffer}", redisClient=self.redisMessaging) - sdpAnswer = self.get_avp_data(avps, 524)[1] - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got SDP Answer raw: {sdpAnswer}", redisClient=self.redisMessaging) - sdpAnswer = binascii.unhexlify(sdpAnswer).decode('utf-8') - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got SDP Answer decoded: {sdpAnswer}", redisClient=self.redisMessaging) - - regexIpv4 = r"IN IP4 (\d*\.\d*\.\d*\.\d*)" - regexIpv6 = r"IN IP6 ([0-9a-fA-F:]{3,39})" - regexRtp = r"m=audio (\d*)" - regexRtcp = r"a=rtcp:(\d+)" - - sdpDownlink = None - sdpUplink = None - sdpDownlinkIpv4 = '' - sdpDownlinkRtpPort = '' - sdpUplinkRtpPort = '' - - # First, work out which side the SDP Downlink is, then do the same for the SDP Uplink. - if 'downlink' in sdpOffer.lower(): - sdpDownlink = sdpOffer - elif 'downlink' in sdpAnswer.lower(): - sdpDownlink = sdpAnswer - - if 'uplink' in sdpOffer.lower(): - sdpUplink = sdpOffer - elif 'uplink' in sdpAnswer.lower(): - sdpUplink = sdpAnswer - - # Grab the SDP Downlink IP - sdpDownlinkIpv4 = self.Match_SDP(regexPattern=regexIpv4, sdpBody=sdpDownlink) - sdpDownlinkIpv6 = self.Match_SDP(regexPattern=regexIpv6, sdpBody=sdpDownlink) - - # Get the RTP ports - sdpDownlinkRtpPort = self.Match_SDP(regexPattern=regexRtp, sdpBody=sdpDownlink) - sdpUplinkRtpPort = self.Match_SDP(regexPattern=regexRtp, sdpBody=sdpUplink) - - # The RTCP Port is always the RTP port + 1. Comma separated ports arent used due to lack of support in open source PGWs. - # We take a blind approach by setting a range of +1 on both sides. - sdpDownlinkRtpPorts = f"{sdpDownlinkRtpPort}-{int(sdpDownlinkRtpPort)+1}" - sdpUplinkRtpPorts = f"{sdpUplinkRtpPort}-{int(sdpUplinkRtpPort)+1}" - - # If we've got a UE that's sending a malformed request, use a fallback rule. - # Else, if all necessary variables are defined, use the correct SDP rule. - # The fallback rule will fail on some cheap handsets. - if not sdpDownlinkIpv4 or not sdpDownlinkRtpPort or not sdpUplinkRtpPort: - tftString = f"permit out 17 from {ueIp}/32 1-65535 to any 1-65535" - else: - tftString = f"permit out 17 from {sdpDownlinkIpv4}/32 {sdpDownlinkRtpPorts} to {ueIp}/32 {sdpUplinkRtpPorts}" + """ + If the PCSCF supplies us TFT's ready to go, use those. + If not, compile our own. + """ + suppliedTfts = None + completedTftList = [] + try: + suppliedTfts = self.get_avp_data(avps, 507) + if suppliedTfts: + if isinstance(suppliedTfts, list): + tftId = 1 + for suppliedTft in suppliedTfts: + tftDirection = None + decodedTft = bytes.fromhex(suppliedTft).decode('ascii') + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got TFT from PCSCF: {decodedTft}", redisClient=self.redisMessaging) + if 'permit out' in decodedTft.lower(): + tftDirection = 1 + if 'permit in' in decodedTft.lower(): + #@@ Ugly hack, but open5gs has no support for 'permit in' currently. + # Assuming standard syntax, we need to flip the src srcprt and dst dstport, then change permit in to permit out. + # permit out 17 from 1.1.1.1 54939 to 2.2.2.2 50021 + # permit in 17 from 2.2.2.2 50021 to 2.2.2.2 54939 + decodedTftSplit = decodedTft.split(' ') + decodedTft = f"permit out {decodedTftSplit[2]} from {decodedTftSplit[7]} {decodedTftSplit[8]} to {decodedTftSplit[4]} {decodedTftSplit[5]}" + tftDirection = 2 + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Recompiled 'permit in' TFT to: {decodedTft}", redisClient=self.redisMessaging) + + completedTftList.append({ + "tft_group_id": 1, + "direction": tftDirection, + "tft_id": tftId, + "tft_string": decodedTft + } + ) + tftId += 1 except Exception as e: - self.logTool.log(service='HSS', level='error', message=f"[diameter.py] [Answer_16777236_265] [AAA] Failed to extract SDP due to error: {traceback.format_exc()}", redisClient=self.redisMessaging) - + self.logTool.log(service='HSS', level='error', message=f"[diameter.py] [Answer_16777236_265] [AAA] Error using TFTs from PCSCF: {traceback.format_exc()}", redisClient=self.redisMessaging) + if not suppliedTfts: + try: + sdpOffer = self.get_avp_data(avps, 524)[0] + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got SDP Offer raw: {sdpOffer}", redisClient=self.redisMessaging) + sdpOffer = binascii.unhexlify(sdpOffer).decode('utf-8') + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got SDP Offer decoded: {sdpOffer}", redisClient=self.redisMessaging) + sdpAnswer = self.get_avp_data(avps, 524)[1] + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got SDP Answer raw: {sdpAnswer}", redisClient=self.redisMessaging) + sdpAnswer = binascii.unhexlify(sdpAnswer).decode('utf-8') + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [AAA] Got SDP Answer decoded: {sdpAnswer}", redisClient=self.redisMessaging) + + regexIpv4 = r"IN IP4 (\d*\.\d*\.\d*\.\d*)" + regexIpv6 = r"IN IP6 ([0-9a-fA-F:]{3,39})" + regexRtp = r"m=audio (\d*)" + regexRtcp = r"a=rtcp:(\d+)" + + sdpDownlink = None + sdpUplink = None + sdpDownlinkIpv4 = '' + sdpDownlinkRtpPort = '' + sdpUplinkRtpPort = '' + + # First, work out which side the SDP Downlink is, then do the same for the SDP Uplink. + if 'downlink' in sdpOffer.lower(): + sdpDownlink = sdpOffer + elif 'downlink' in sdpAnswer.lower(): + sdpDownlink = sdpAnswer + + if 'uplink' in sdpOffer.lower(): + sdpUplink = sdpOffer + elif 'uplink' in sdpAnswer.lower(): + sdpUplink = sdpAnswer + + # Grab the SDP Downlink IP + sdpDownlinkIpv4 = self.Match_SDP(regexPattern=regexIpv4, sdpBody=sdpDownlink) + sdpDownlinkIpv6 = self.Match_SDP(regexPattern=regexIpv6, sdpBody=sdpDownlink) + + # Get the RTP ports + sdpDownlinkRtpPort = self.Match_SDP(regexPattern=regexRtp, sdpBody=sdpDownlink) + sdpUplinkRtpPort = self.Match_SDP(regexPattern=regexRtp, sdpBody=sdpUplink) + + # The RTCP Port is always the RTP port + 1. Comma separated ports arent used due to lack of support in open source PGWs. + # We take a blind approach by setting a range of +1 on both sides. + sdpDownlinkRtpPorts = f"{sdpDownlinkRtpPort}-{int(sdpDownlinkRtpPort)+1}" + sdpUplinkRtpPorts = f"{sdpUplinkRtpPort}-{int(sdpUplinkRtpPort)+1}" + + # If we've got a UE that's sending a malformed request, use a fallback rule. + # Else, if all necessary variables are defined, use the correct SDP rule. + # The fallback rule will fail on some cheap handsets. + if not sdpDownlinkIpv4 or not sdpDownlinkRtpPort or not sdpUplinkRtpPort: + tftString = f"permit out 17 from {ueIp}/32 1-65535 to any 1-65535" + else: + tftString = f"permit out 17 from {sdpDownlinkIpv4}/32 {sdpDownlinkRtpPorts} to {ueIp}/32 {sdpUplinkRtpPorts}" + + completedTftList.append({ + "tft_group_id": 1, + "direction": 1, + "tft_id": 1, + "tft_string": tftString + }) + completedTftList.append({ + "tft_group_id": 1, + "direction": 2, + "tft_id": 2, + "tft_string": tftString + }) + + except Exception as e: + self.logTool.log(service='HSS', level='error', message=f"[diameter.py] [Answer_16777236_265] [AAA] Failed to extract SDP due to error: {traceback.format_exc()}", redisClient=self.redisMessaging) + """ The below logic is applied: 1. Grab the Flow Rules and bitrates from the PCSCF in the AAR, @@ -3021,20 +3076,7 @@ def Answer_16777236_265(self, packet_vars, avps): "gbr_dl": dlBandwidth, "tft_group_id": 1, "rating_group": None, - "tft": [ - { - "tft_group_id": 1, - "direction": 1, - "tft_id": 1, - "tft_string": tftString - }, - { - "tft_group_id": 1, - "direction": 2, - "tft_id": 2, - "tft_string": tftString - } - ] + "tft": completedTftList } if not emergencySubscriber: @@ -3197,7 +3239,7 @@ def Answer_16777236_275(self, packet_vars, avps): servingPgwPeer = servingApn.get('serving_pgw_peer', '').split(';')[0] pcrfSessionId = servingApn.get('pcrf_session_id', None) else: - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [STA] No servingApn defined for IMS Subscriber", redisClient=self.redisMessaging) + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_275] [STA] No servingApn defined for IMS Subscriber", redisClient=self.redisMessaging) self.database.Update_Proxy_CSCF(imsi=imsi, proxy_cscf=pcscf, pcscf_realm=pcscf_realm, pcscf_peer=pcscf_peer, pcscf_active_session=None) except Exception as e: pass @@ -3209,9 +3251,9 @@ def Answer_16777236_275(self, packet_vars, avps): emergencySubscriberData = self.database.Get_Emergency_Subscriber(rxSessionId=sessionId) if emergencySubscriberData: emergencySubscriber = True - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [STA] Found emergency subscriber with Rx Session: {sessionId}", redisClient=self.redisMessaging) + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_275] [STA] Found emergency subscriber with Rx Session: {sessionId}", redisClient=self.redisMessaging) except Exception as e: - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [STA] Error getting Emergency Subscriber Data: {traceback.format_exc()}", redisClient=self.redisMessaging) + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_275] [STA] Error getting Emergency Subscriber Data: {traceback.format_exc()}", redisClient=self.redisMessaging) emergencySubscriberData = None if emergencySubscriberData: @@ -3232,7 +3274,7 @@ def Answer_16777236_275(self, packet_vars, avps): ) if not len(reAuthAnswer) > 0: - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [STA] RAA Timeout: {reAuthAnswer}", redisClient=self.redisMessaging) + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_275] [STA] RAA Timeout: {reAuthAnswer}", redisClient=self.redisMessaging) assert() raaPacketVars, raaAvps = self.decode_diameter_packet(reAuthAnswer) @@ -3240,10 +3282,10 @@ def Answer_16777236_275(self, packet_vars, avps): if raaResultCode == 2001: avp += self.generate_avp(268, 40, self.int_to_hex(2001, 4)) - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [STA] RAA returned Successfully, authorizing request", redisClient=self.redisMessaging) + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_275] [STA] RAA returned Successfully, authorizing request", redisClient=self.redisMessaging) else: avp += self.generate_avp(268, 40, self.int_to_hex(5001, 4)) - self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_265] [STA] RAA returned Unauthorized, returning Result-Code 5001", redisClient=self.redisMessaging) + self.logTool.log(service='HSS', level='debug', message=f"[diameter.py] [Answer_16777236_275] [STA] RAA returned Unauthorized, returning Result-Code 5001", redisClient=self.redisMessaging) else: self.logTool.log(service='HSS', level='info', message=f"[diameter.py] [Answer_16777236_275] [STA] Unable to find serving APN for RAR, returning Result-Code 2001", redisClient=self.redisMessaging)