Skip to content

Commit

Permalink
Parse call forwarding data in UDA
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkneipp committed Oct 24, 2024
1 parent ec43d71 commit 2c8dc88
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 6 deletions.
9 changes: 8 additions & 1 deletion default_sh_user_data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@
<Sh-IMS-Data>
<S-CSCFName>{{ Sh_template_vars['scscf'] }}</S-CSCFName>
<IMSUserState>{{ Sh_template_vars['imsUserState'] }}</IMSUserState>
<!-- The following 2 values are not 3gpp-standardised. Remove if your deployment requires 3gpp-compliant XML. -->
<!-- The following values are not 3gpp-standardised. Remove if your deployment requires 3gpp-compliant XML. -->
<InboundCommunicationBarred>{{ Sh_template_vars['inboundCommunicationBarred'] }}</InboundCommunicationBarred>
<OutboundCommunicationBarred>{{ Sh_template_vars['outboundCommunicationBarred'] }}</OutboundCommunicationBarred>
<CallForwardActive>{{ Sh_template_vars['callForwarding']['enabled'] }}</CallForwardActive>
<CallForwardUnconditional>{{ Sh_template_vars['callForwarding']['unconditional'] }}</CallForwardUnconditional>
<CallForwardNotRegistered>{{ Sh_template_vars['callForwarding']['notRegistered'] }}</CallForwardNotRegistered>
<CallForwardNoAnswer>{{ Sh_template_vars['callForwarding']['noAnswer'] }}</CallForwardNoAnswer>
<CallForwardBusy>{{ Sh_template_vars['callForwarding']['busy'] }}</CallForwardBusy>
<CallForwardNotReachable>{{ Sh_template_vars['callForwarding']['notReachable'] }}</CallForwardNotReachable>
<CallForwardNoReplyTimer>{{ Sh_template_vars['callForwarding']['noReplyTimer'] }}</CallForwardNoReplyTimer>
</Sh-IMS-Data>

</Sh-Data>
126 changes: 122 additions & 4 deletions lib/diameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import yaml
import json
import time
import xmltodict
import socket
import requests
import traceback
Expand Down Expand Up @@ -341,7 +342,7 @@ def generate_diameter_packet(self, packet_version, packet_flags, packet_command_
except Exception as e:
self.logTool.log(service='HSS', level='error', message=f"[diameter.py] [generate_diameter_packet] Exception: {e}", redisClient=self.redisMessaging)

def get_sh_profile_rules(self, serviceName, xmlRoot, xmlNamespace):
def get_sh_profile_call_barring_rules(self, serviceName, xmlRoot, xmlNamespace):
service = xmlRoot.find(f'default:{serviceName}', xmlNamespace)
if service is not None:
active = service.get('active')
Expand All @@ -350,6 +351,78 @@ def get_sh_profile_rules(self, serviceName, xmlRoot, xmlNamespace):
return active, allowValue
return None, None

def get_sh_profile_call_forwarding_rules(self, serviceName, xmlRoot, xmlNamespace):
"""
Parse call forwarding rules from XCAP XML.
Returns a tuple of (active_status, rules_dict) where rules_dict contains the forwarding rules
for different conditions including unconditional forwarding.
"""
service = xmlRoot.find(f'default:{serviceName}', xmlNamespace)
if service is None:
self.logTool.log(service='HSS', level='debug', message=f"Service {serviceName} not found", redisClient=self.redisMessaging)
return None, None

active = service.get('active') == 'true'

forwarding_rules = {}

ruleset = service.find('.//cp:ruleset', xmlNamespace)
self.logTool.log(service='HSS', level='debug', message=f"Found ruleset: {ruleset is not None}", redisClient=self.redisMessaging)

if ruleset is not None:
rules = ruleset.findall('cp:rule', xmlNamespace)
self.logTool.log(service='HSS', level='debug', message=f"Number of rules found: {len(rules)}", redisClient=self.redisMessaging)

for rule in rules:
rule_id = rule.get('id')
if rule_id is None:
continue

self.logTool.log(service='HSS', level='debug', message=f"Processing rule ID: {rule_id}", redisClient=self.redisMessaging)

conditions = rule.find('.//cp:conditions', xmlNamespace)
condition = None

if conditions is not None:
condition_children = list(conditions)
if len(condition_children) == 0:
condition = "unconditional"
self.logTool.log(service='HSS', level='debug', message="Found unconditional forwarding rule (empty conditions)", redisClient=self.redisMessaging)
elif len(condition_children) == 1:
first_condition = condition_children[0]
condition_tag = first_condition.tag.split('}')[-1]
if condition_tag == 'rule-deactivated':
condition = "unconditional"
self.logTool.log(service='HSS', level='debug', message="Found unconditional forwarding rule (rule-deactivated)", redisClient=self.redisMessaging)
else:
condition = condition_tag
self.logTool.log(service='HSS', level='debug', message=f"Found condition: {condition}", redisClient=self.redisMessaging)
else:
condition = "unconditional"
self.logTool.log(service='HSS', level='debug', message="Found unconditional forwarding rule (no conditions element)", redisClient=self.redisMessaging)

actions = rule.find('.//cp:actions', xmlNamespace)
if actions is not None:
forward_to = actions.find('.//default:forward-to', xmlNamespace)
if forward_to is not None:
target = forward_to.find('.//default:target', xmlNamespace)
if target is not None:
self.logTool.log(service='HSS', level='debug', message=f"Found target: {target.text}", redisClient=self.redisMessaging)
forwarding_rules[rule_id] = {
'condition': condition,
'target': target.text
}
else:
self.logTool.log(service='HSS', level='debug', message="No target element found", redisClient=self.redisMessaging)
else:
self.logTool.log(service='HSS', level='debug', message="No forward-to element found", redisClient=self.redisMessaging)

no_reply_timer = service.find('.//default:NoReplyTimer', xmlNamespace)
if no_reply_timer is not None:
forwarding_rules['NoReplyTimer'] = no_reply_timer.text

return active, forwarding_rules

def roundUpToMultiple(self, n, multiple):
return ((n + multiple - 1) // multiple) * multiple

Expand Down Expand Up @@ -2896,18 +2969,23 @@ def Answer_16777217_306(self, packet_vars, avps):
subscriberShProfile = subscriber_details.get('sh_profile', '')
if not subscriberShProfile:
subscriberShProfile = subscriber_details.get('xcap_profile', '')

subscriber_details['inboundCommunicationBarred'] = False
subscriber_details['outboundCommunicationBarred'] = False
subscriber_details['callForwarding'] = {'enabled': True, 'unconditional': False, 'notRegistered': False, 'noAnswer': False, 'busy': False, 'notReachable': False, 'noReplyTimer': 20}

try:
subscriberShXml = ET.fromstring(subscriberShProfile)
namespaces = {
'default': 'http://uri.etsi.org/ngn/params/xml/simservs/xcap',
'cp': 'urn:ietf:params:xml:ns:common-policy'
}
incomingCommunicationBarringRuleActive, incomingCommunicationBarringAllowed = self.get_sh_profile_rules('incoming-communication-barring')
outgoingCommunicationBarringRuleActive, outgoingCommunicationBarringAllowed = self.get_sh_profile_rules('outgoing-communication-barring')
incomingCommunicationBarringRuleActive, incomingCommunicationBarringAllowed = self.get_sh_profile_call_barring_rules('incoming-communication-barring', subscriberShXml, namespaces)
outgoingCommunicationBarringRuleActive, outgoingCommunicationBarringAllowed = self.get_sh_profile_call_barring_rules('outgoing-communication-barring', subscriberShXml, namespaces)

call_forwarding_active, call_forwarding_rules = self.get_sh_profile_call_forwarding_rules('communication-diversion', subscriberShXml, namespaces)
self.logTool.log(service='HSS', level='debug', message=f"Call forwarding rules enabled: {call_forwarding_active}", redisClient=self.redisMessaging)
self.logTool.log(service='HSS', level='debug', message=f"Call forwarding rules: {call_forwarding_rules}", redisClient=self.redisMessaging)

if incomingCommunicationBarringRuleActive:
if not incomingCommunicationBarringAllowed:
Expand All @@ -2916,13 +2994,53 @@ def Answer_16777217_306(self, packet_vars, avps):
if outgoingCommunicationBarringRuleActive:
if not outgoingCommunicationBarringAllowed:
subscriber_details['outboundCommunicationBarred'] = True

try:
if call_forwarding_active:
subscriber_details['callForwarding']['notRegistered'] = call_forwarding_rules['not-registered']['target']
except:
pass

try:
if call_forwarding_active:
subscriber_details['callForwarding']['noAnswer'] = call_forwarding_rules['no-answer']['target']
except:
pass

try:
if call_forwarding_active:
subscriber_details['callForwarding']['busy'] = call_forwarding_rules['busy']['target']
except:
pass

try:
if call_forwarding_active:
subscriber_details['callForwarding']['notReachable'] = call_forwarding_rules['not-reachable']['target']
except:
pass

try:
if call_forwarding_active:
subscriber_details['callForwarding']['unconditional'] = call_forwarding_rules['forward-unconditional']['target']
except:
pass

try:
if call_forwarding_active:
subscriber_details['callForwarding']['noReplyTimer'] = int(call_forwarding_rules['NoReplyTimer'])
except:
pass


except Exception as e:
self.logTool.log(service='HSS', level='debug', message="Unable to parse Sh Profile XML for subscriber: " + str(subscriber_details), redisClient=self.redisMessaging)
self.logTool.log(service='HSS', level='debug', message=f"{traceback.format_exc()}", redisClient=self.redisMessaging)

self.logTool.log(service='HSS', level='debug', message="Rendering template with values: " + str(subscriber_details), redisClient=self.redisMessaging)
xmlbody = template.render(Sh_template_vars=subscriber_details)

self.logTool.log(service='HSS', level='debug', message=f"Template: {template}", redisClient=self.redisMessaging)

avp += self.generate_vendor_avp(702, "c0", 10415, str(binascii.hexlify(str.encode(xmlbody)),'ascii'))

avp += self.generate_avp(268, 40, "000007d1") #DIAMETER_SUCCESS
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ prometheus_flask_exporter
pydantic==2.8.2
pydantic_core==2.20.1
tzlocal==4.3
influxdb==5.3.1
influxdb==5.3.1
xmltodict==0.14.2

0 comments on commit 2c8dc88

Please sign in to comment.