Skip to content

Commit

Permalink
Support for third party vendor PCSCFs on AAA
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkneipp committed Jan 30, 2024
1 parent 897a9df commit 6111373
Showing 1 changed file with 119 additions and 77 deletions.
196 changes: 119 additions & 77 deletions lib/diameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -3232,18 +3274,18 @@ 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)
raaResultCode = int(self.get_avp_data(raaAvps, 268)[0], 16)

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)
Expand Down

0 comments on commit 6111373

Please sign in to comment.