Skip to content

IMS Support#532

Open
anaswarac-dac wants to merge 2 commits intoomec-project:mainfrom
5GC-DEV:ims-support
Open

IMS Support#532
anaswarac-dac wants to merge 2 commits intoomec-project:mainfrom
5GC-DEV:ims-support

Conversation

@anaswarac-dac
Copy link
Copy Markdown
Contributor

This PR adds IMS call support for UE in SMF.

Note: - During testing with COTS UEs, IMS PDU session release behavior was observed and needs further analysis.

Create cdacmaster.yml

Define CI pipeline with multiple jobs and permissions

This workflow defines a CI pipeline with multiple jobs for building, testing, and analyzing the codebase. Each job has specific permissions and uses reusable workflows from the omec-project repository.

Update cdacmaster.yml
@anaswarac-dac anaswarac-dac requested a review from a team March 23, 2026 06:56
.
Signed-off-by: anaswara <anaswara.n@cdac.in>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces IMS-related PDU session modification support in the SMF, including P-CSCF address handling and policy-driven QoS updates that trigger PFCP session modification plus NAS/NGAP modification messaging.

Changes:

  • Add UE-facing QoS update handling (QoS rules/flows + N1N2 PDU Session Resource Modify) for SM Policy update notifications.
  • Extend PFCP Session Modification to include rule removal (Remove PDR/FAR/QER) and update call sites/tests accordingly.
  • Add configurable P-CSCF IPv4/IPv6 info and attempt to include P-CSCF IPv4 in PCO when requested.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
util/qos_convert.go Adds bitrate normalization helper used when building QER rates.
qos/traffic_control_data.go Implements concrete change detection for TrafficControlData.
qos/qos_rule.go Adds QoS rule add/modify/delete building helpers and updates marshaling for delete rules.
qos/qos_flow.go Adds QoS flow add/modify/delete description building and QoS data change detection.
qos/pcc_rule.go Implements PCC rule diffing helper logic.
producer/ulcl_procedure.go Updates PFCP session modification calls to new signature.
producer/sm_pfcp_handling.go Updates PFCP session modification call to new signature.
producer/pdu_session.go Updates PFCP session modification call to new signature.
producer/n1n2_data_handler.go Extends PFCP param struct with removal lists.
producer/datapath.go Updates PFCP session modification call to new signature.
producer/callback.go Implements SM Policy Update Notify handling to drive PFCP modify + N1N2 QoS modification.
pfcp/message/send.go Adds remove PDR/FAR/QER parameters to PFCP session modification send path.
pfcp/message/build.go Adds Remove PDR/FAR/QER IEs to PFCP Session Modification Request.
pfcp/message/send_test.go Updates PFCP send test for new signature.
pfcp/message/build_test.go Updates PFCP build tests for new signature.
factory/config.go Adds PCSCFInfo configuration struct + YAML field.
config/smfcfg.yaml Adds example pcscfInfos config values.
context/context.go Plumbs configured PCSCFInfo into SMF context.
context/pcscf_info.go Adds context-level PCSCFInfo type.
context/pco.go Adds protocol option flag for PCSCF IPv4 request.
context/gsm_handler.go Attempts to detect UE PCSCF request from PCO container list.
context/gsm_build.go Attempts to add PCSCF IPv4 address into EPCO and builds PDU Session Modification Command.
context/ngap_build.go Adds PDUSessionResourceModifyRequestTransfer builder + bitrate parsing helper.
context/sm_context.go Adds additional logging and null-guards in SelectedSessionRule.
context/datapath.go Adds dedicated QER creation (GBR/MBR) for non-default QoS flows.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +351 to 362
func GetPfId(pfID string) uint8 {
if pfID == "" {
fmt.Println("Warning: PackFiltId is empty, defaulting to 0")
return 0
}
id, err := strconv.Atoi(pfID)
if err != nil {
fmt.Printf("Error converting PackFiltId [%s] to int: %v. Defaulting to 0\n", pfID, err)
return 0
} else {
return (uint8(id) & PacketFilterIdBitmask)
}
return uint8(id)
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetPfId now prints warnings/errors to stdout via fmt and returns uint8(id) without applying PacketFilterIdBitmask (IDs are limited to 0-15 per the struct comment). This can both spam stdout and produce invalid packet filter identifiers. Prefer logger.QosLog and return (uint8(id) & PacketFilterIdBitmask).

Copilot uses AI. Check for mistakes.
Comment on lines 103 to 141
qosFlowUpdate := smPolicyUpdates.QosFlowUpdate
hasUpdates := false // Track if any QoS flows were processed

// ===============================
// Handle PCC rule deletions if QoS flow updates are nil
// ===============================
if smPolicyUpdates == nil || smPolicyUpdates.QosFlowUpdate == nil {
logger.QosLog.Warn("smPolicyUpdates or QosFlowUpdate is nil, processing PCC rule deletions only")

for pccRuleID := range smPolicyUpdates.PccRuleUpdate.del {
logger.QosLog.Infof("Processing deletion for PCC rule ID: %s", pccRuleID)

qfiVal, err := strconv.Atoi(pccRuleID)
if err != nil {
logger.QosLog.Errorf("Invalid QFI string for PCC rule ID '%s': %v", pccRuleID, err)
continue
}
qfi := uint8(qfiVal)

logger.QosLog.Infof("Deleting QoS Flow Description for QFI=%d (from PCC rule %s)", qfi, pccRuleID)

// Skip if QFI is zero
if qfi == 0 {
logger.QosLog.Warnf("Skipping QoS Flow deletion because QFI=0 for PCC rule ID='%s'", pccRuleID)
continue
}

// Build delete QoS Flow Description
QFDescriptions.BuildDelQosFlowDescFromQoSDesc(qfi)
hasUpdates = true
}

if hasUpdates {
logger.QosLog.Infof("Completed building delete QoS flow descriptions for %d PCC rules", len(smPolicyUpdates.PccRuleUpdate.del))
} else {
logger.QosLog.Warn("No QoS flow deletions were processed")
}
}

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuildAuthorizedQosFlowDescriptions assigns qosFlowUpdate := smPolicyUpdates.QosFlowUpdate before checking smPolicyUpdates for nil, and then inside the nil-check block it still dereferences smPolicyUpdates (e.g., smPolicyUpdates.PccRuleUpdate.del). If smPolicyUpdates is nil this will panic. The function should return early when smPolicyUpdates is nil, and separately guard PccRuleUpdate/del from nil before ranging.

Suggested change
qosFlowUpdate := smPolicyUpdates.QosFlowUpdate
hasUpdates := false // Track if any QoS flows were processed
// ===============================
// Handle PCC rule deletions if QoS flow updates are nil
// ===============================
if smPolicyUpdates == nil || smPolicyUpdates.QosFlowUpdate == nil {
logger.QosLog.Warn("smPolicyUpdates or QosFlowUpdate is nil, processing PCC rule deletions only")
for pccRuleID := range smPolicyUpdates.PccRuleUpdate.del {
logger.QosLog.Infof("Processing deletion for PCC rule ID: %s", pccRuleID)
qfiVal, err := strconv.Atoi(pccRuleID)
if err != nil {
logger.QosLog.Errorf("Invalid QFI string for PCC rule ID '%s': %v", pccRuleID, err)
continue
}
qfi := uint8(qfiVal)
logger.QosLog.Infof("Deleting QoS Flow Description for QFI=%d (from PCC rule %s)", qfi, pccRuleID)
// Skip if QFI is zero
if qfi == 0 {
logger.QosLog.Warnf("Skipping QoS Flow deletion because QFI=0 for PCC rule ID='%s'", pccRuleID)
continue
}
// Build delete QoS Flow Description
QFDescriptions.BuildDelQosFlowDescFromQoSDesc(qfi)
hasUpdates = true
}
if hasUpdates {
logger.QosLog.Infof("Completed building delete QoS flow descriptions for %d PCC rules", len(smPolicyUpdates.PccRuleUpdate.del))
} else {
logger.QosLog.Warn("No QoS flow deletions were processed")
}
}
hasUpdates := false // Track if any QoS flows were processed
// ===============================
// Handle nil smPolicyUpdates early
// ===============================
if smPolicyUpdates == nil {
logger.QosLog.Warn("smPolicyUpdates is nil, nothing to process")
return &QFDescriptions
}
// ===============================
// Handle PCC rule deletions if QoS flow updates are nil
// ===============================
if smPolicyUpdates.QosFlowUpdate == nil {
logger.QosLog.Warn("QosFlowUpdate is nil, processing PCC rule deletions only")
if smPolicyUpdates.PccRuleUpdate != nil && smPolicyUpdates.PccRuleUpdate.del != nil {
for pccRuleID := range smPolicyUpdates.PccRuleUpdate.del {
logger.QosLog.Infof("Processing deletion for PCC rule ID: %s", pccRuleID)
qfiVal, err := strconv.Atoi(pccRuleID)
if err != nil {
logger.QosLog.Errorf("Invalid QFI string for PCC rule ID '%s': %v", pccRuleID, err)
continue
}
qfi := uint8(qfiVal)
logger.QosLog.Infof("Deleting QoS Flow Description for QFI=%d (from PCC rule %s)", qfi, pccRuleID)
// Skip if QFI is zero
if qfi == 0 {
logger.QosLog.Warnf("Skipping QoS Flow deletion because QFI=0 for PCC rule ID='%s'", pccRuleID)
continue
}
// Build delete QoS Flow Description
QFDescriptions.BuildDelQosFlowDescFromQoSDesc(qfi)
hasUpdates = true
}
if hasUpdates {
logger.QosLog.Infof("Completed building delete QoS flow descriptions for %d PCC rules", len(smPolicyUpdates.PccRuleUpdate.del))
} else {
logger.QosLog.Warn("No QoS flow deletions were processed")
}
} else {
logger.QosLog.Warn("PccRuleUpdate or its delete map is nil, no QoS flow deletions to process")
}
// No QoS flow updates to process beyond deletions
return &QFDescriptions
}
qosFlowUpdate := smPolicyUpdates.QosFlowUpdate

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +63
smContext.SMLock.Lock()
defer smContext.SMLock.Unlock()

logger.PduSessLog.Infoln("In HandleSMPolicyUpdateNotify")
pcfPolicyDecision := request.SmPolicyDecision

if smContext.SMContextState != smfContext.SmStateActive {
// Wait till the state becomes SmStateActive again
// TODO: implement waiting in concurrent architecture
logger.PduSessLog.Warnf("SMContext[%s-%02d] should be SmStateActive, but actual %s",
smContext.Supi, smContext.PDUSessionID, smContext.SMContextState.String())
}

//TODO: Response data type -
//[200 OK] UeCampingRep
//[200 OK] array(PartialSuccessReport)
//[400 Bad Request] ErrorReport
// Derive QoS change
logger.PduSessLog.Infof("Building SM Policy Update for UE [%s], PDU Session ID [%d]",
smContext.Supi, smContext.PDUSessionID)

// Derive QoS change(compare existing vs received Policy Decision)
policyUpdates := qos.BuildSmPolicyUpdate(&smContext.SmPolicyData, pcfPolicyDecision)
smContext.SmPolicyUpdates = append(smContext.SmPolicyUpdates, policyUpdates)

// Update UPF
// TODO
logger.PduSessLog.Infof("SM Policy Update built: %+v", policyUpdates)

httpResponse := httpwrapper.NewResponse(http.StatusNoContent, nil, nil)
txn.Rsp = httpResponse
smContext.SmPolicyUpdates = append(smContext.SmPolicyUpdates[:0], policyUpdates)
logger.PduSessLog.Infof("Appended SM Policy Update, total updates count: %d",
len(smContext.SmPolicyUpdates))
logger.PduSessLog.Infof("SmPolicyUpdates: %v", smContext.SmPolicyUpdates)

// Build PFCP parameters
pfcpParam := BuildPfcpParam(smContext)
// Set state to PFCP Modify before sending PFCP request
smContext.ChangeState(smfContext.SmStatePfcpModify)

if err := SendPfcpSessionModifyReq(smContext, pfcpParam); err != nil {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HandleSMPolicyUpdateNotify holds smContext.SMLock across PFCP signaling and the AMF N1N2 call (network I/O + waiting on SBIPFCPCommunicationChan). This can block unrelated SM context operations and may deadlock in the UPF-adapter path where PFCP response handlers also take SMLock. Consider limiting the lock scope to only the shared-state mutations, releasing it before sending PFCP/HTTP requests and waiting for responses.

Copilot uses AI. Check for mistakes.
// ----------------------
// Handle Downlink PDRs
// ----------------------
if dlPDR, ok := ANUPF.DownLinkTunnel.PDR[ruleid]; ok {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuildPfcpParam selects a single PCC rule key (ruleid) from the SmPolicyDecision.PccRules map and only processes PDRs with that key. If multiple PCC rules/PDRs exist, the rest will be ignored (and in release-only case, only one rule would be removed). This should iterate over all relevant PDR entries (or all keys in the PDR maps) rather than picking one arbitrary map key.

Suggested change
if dlPDR, ok := ANUPF.DownLinkTunnel.PDR[ruleid]; ok {
for ruleid, dlPDR := range ANUPF.DownLinkTunnel.PDR {

Copilot uses AI. Check for mistakes.
for pccRuleName, pccRuleVal := range pccRulesUpdate.mod {
logger.QosLog.Infof("building QoS Rule from modified PCC rule [%s]", pccRuleName)
refQosData := GetQoSDataFromPolicyDecision(smPolicyDecision, pccRuleVal.RefQosData[0])
qosRule := BuildAddQoSRuleFromPccRule(pccRuleVal, refQosData, OperationCodeCreateNewQoSRule)
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuildQosRules treats PCC rule modifications as creates (OperationCodeCreateNewQoSRule). For existing rule updates this should use a modify operation code (and likely BuildModifyQosRuleFromPccRule) so the UE applies it as a modification rather than a new rule.

Suggested change
qosRule := BuildAddQoSRuleFromPccRule(pccRuleVal, refQosData, OperationCodeCreateNewQoSRule)
qosRule := BuildAddQoSRuleFromPccRule(pccRuleVal, refQosData, OperationCodeModifyExistingQoSRuleAndReplaceAllPacketFilters)

Copilot uses AI. Check for mistakes.
ANUPF := defaultPath.FirstDPNode
err := pfcp_message.SendPfcpSessionModificationRequest(ANUPF.UPF.NodeID, smContext,
pfcpParam.pdrList, pfcpParam.farList, pfcpParam.barList, pfcpParam.qerList, ANUPF.UPF.Port)
pfcpParam.pdrList, pfcpParam.farList, pfcpParam.barList, pfcpParam.qerList, nil, nil, nil, ANUPF.UPF.Port)
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SendPfcpSessionModifyReq always passes nil removePDR/removeFAR/removeQER into SendPfcpSessionModificationRequest. This makes the new teardown/removal lists in pfcpParam ineffective (e.g., BuildPfcpParam's release-only path). Pass pfcpParam.removePDR/removeFAR/removeQER through so PFCP removal IEs are actually sent.

Suggested change
pfcpParam.pdrList, pfcpParam.farList, pfcpParam.barList, pfcpParam.qerList, nil, nil, nil, ANUPF.UPF.Port)
pfcpParam.pdrList, pfcpParam.farList, pfcpParam.barList, pfcpParam.qerList, pfcpParam.removePDR, pfcpParam.removeFAR, pfcpParam.removeQER, ANUPF.UPF.Port)

Copilot uses AI. Check for mistakes.
Comment on lines 82 to +94
case nasMessage.PCSCFIPv4AddressRequestUL:
smContext.SubGsmLog.Infoln("Didn't Implement container type PCSCFIPv4AddressRequestUL")
case nasMessage.DNSServerIPv4AddressRequestUL:
smContext.ProtocolConfigurationOptions.DNSIPv4Request = true
case nasMessage.MSISDNRequestUL:
smContext.SubGsmLog.Infoln("Didn't Implement container type MSISDNRequestUL")
case nasMessage.IFOMSupportRequestUL:
smContext.SubGsmLog.Infoln("Didn't Implement container type IFOMSupportRequestUL")
case nasMessage.MSSupportOfLocalAddressInTFTIndicatorUL:
smContext.SubGsmLog.Infoln("Didn't Implement container type MSSupportOfLocalAddressInTFTIndicatorUL")
case nasMessage.PCSCFReSelectionSupportUL:
smContext.SubGsmLog.Infoln("Didn't Implement container type PCSCFReSelectionSupportUL")
smContext.ProtocolConfigurationOptions.PCSCFIPv4Request = true
smContext.SubGsmLog.Infoln("PCSCFIPv4AddressRequestUL has been set true")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PCSCF IPv4 request flag is set when encountering PCSCFReSelectionSupportUL, but the actual container ID for requesting a P-CSCF IPv4 address is PCSCFIPv4AddressRequestUL. As written, a UE that sends PCSCFIPv4AddressRequestUL will still hit the "Didn't Implement" case and not set PCSCFIPv4Request.

Copilot uses AI. Check for mistakes.
Comment on lines +513 to +526
val, err := strconv.ParseUint(strings.TrimSuffix(s, "kbps"), 10, 64)
if err != nil {
return 0, err
}
return val * 1000, nil
}
if strings.HasSuffix(s, "mbps") {
val, err := strconv.ParseUint(strings.TrimSuffix(s, "mbps"), 10, 64)
if err != nil {
return 0, err
}
return val * 1000 * 1000, nil
}
return 0, nil
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StringToBitRate returns (0, nil) for unsupported/invalid strings and does not trim whitespace after stripping the unit (e.g., "101 Mbps" -> trimSuffix("mbps") yields "101 " which ParseUint fails). This can silently convert rates to 0 and mask errors. Consider trimming spaces after TrimSuffix and returning a non-nil error for unknown/invalid units so callers can handle it explicitly.

Suggested change
val, err := strconv.ParseUint(strings.TrimSuffix(s, "kbps"), 10, 64)
if err != nil {
return 0, err
}
return val * 1000, nil
}
if strings.HasSuffix(s, "mbps") {
val, err := strconv.ParseUint(strings.TrimSuffix(s, "mbps"), 10, 64)
if err != nil {
return 0, err
}
return val * 1000 * 1000, nil
}
return 0, nil
numPart := strings.TrimSpace(strings.TrimSuffix(s, "kbps"))
val, err := strconv.ParseUint(numPart, 10, 64)
if err != nil {
return 0, err
}
return val * 1000, nil
}
if strings.HasSuffix(s, "mbps") {
numPart := strings.TrimSpace(strings.TrimSuffix(s, "mbps"))
val, err := strconv.ParseUint(numPart, 10, 64)
if err != nil {
return 0, err
}
return val * 1000 * 1000, nil
}
return 0, fmt.Errorf("unsupported bitrate format: %q", s)

Copilot uses AI. Check for mistakes.
Comment on lines +504 to +521
// Set Guaranteed Bit Rate (GBR) if configured
if qosData.GbrUl != "" && qosData.GbrDl != "" {
newQER.GBR = &GBR{
ULGBR: util.BitRateTokbps(util.NormalizeBitRate(qosData.GbrUl)),
DLGBR: util.BitRateTokbps(util.NormalizeBitRate(qosData.GbrDl)),
}
logger.PduSessLog.Infof("CreateDedicatedQosQer: GBR set [UL=%d kbps, DL=%d kbps]",
newQER.GBR.ULGBR, newQER.GBR.DLGBR)
} else {
logger.PduSessLog.Infof("CreateDedicatedQosQer: no GBR configured for QoSId [%s]", qosData.QosId)
}

// Set Maximum Bit Rate (MBR) if configured
if qosData.MaxbrUl != "" && qosData.MaxbrDl != "" {
newQER.MBR = &MBR{
ULMBR: util.BitRateTokbps(util.NormalizeBitRate(qosData.MaxbrUl)),
DLMBR: util.BitRateTokbps(util.NormalizeBitRate(qosData.MaxbrDl)),
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateDedicatedQosQer normalizes rates via util.NormalizeBitRate before BitRateTokbps. NormalizeBitRate currently hardcodes the unit to Kbps when a decimal is present, which will mis-handle values like "101.5 Mbps" and can drastically under-state configured GBR/MBR. Consider normalizing by trimming fractional zeros while preserving the original unit token, or updating BitRateTokbps to parse floats directly.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +44
parts := strings.Split(br, ".")
if len(parts) > 1 {
br = parts[0] + " Kbps"
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NormalizeBitRate rewrites any value containing a decimal point to " Kbps", which discards the original unit (e.g., "1.5 Mbps" becomes "1 Kbps") and can under/over-state rates. Consider parsing the numeric part (float) and preserving the original unit token (bps/Kbps/Mbps/...) when normalizing, or normalizing by trimming trailing zeros without hardcoding a unit.

Suggested change
parts := strings.Split(br, ".")
if len(parts) > 1 {
br = parts[0] + " Kbps"
}
// Normalize only the numeric part and preserve the original unit token(s).
fields := strings.Fields(br)
if len(fields) == 0 {
return strings.TrimSpace(br)
}
numeric := fields[0]
unit := ""
if len(fields) > 1 {
unit = strings.Join(fields[1:], " ")
}
if strings.Contains(numeric, ".") {
numeric = strings.TrimRight(strings.TrimRight(numeric, "0"), ".")
if numeric == "" {
numeric = "0"
}
}
if unit != "" {
br = numeric + " " + unit
} else {
br = numeric
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants