From 05c55bbb9c052e670a27fda03335da660bc5636f Mon Sep 17 00:00:00 2001 From: saileshvvr Date: Wed, 17 Jan 2024 07:27:30 +0530 Subject: [PATCH] Add support for UECM Registration and Deregistration for a PDU Session (#92) * Add support for UECM Registration and Deregistration for a PDU Session * Addressed the suggested comments to change the state in UeCmDeregistration() * fix: ci linter error --------- Co-authored-by: Ian Chen --- internal/context/sm_context.go | 2 + .../sbi/consumer/ue_context_management.go | 103 ++++++++++++++++++ internal/sbi/producer/pdu_session.go | 56 +++++++++- internal/util/search_nf_service.go | 50 +++++++++ 4 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 internal/sbi/consumer/ue_context_management.go create mode 100644 internal/util/search_nf_service.go diff --git a/internal/context/sm_context.go b/internal/context/sm_context.go index 308337d0..714ab250 100644 --- a/internal/context/sm_context.go +++ b/internal/context/sm_context.go @@ -208,6 +208,8 @@ type SMContext struct { // State state SMContextState + UeCmRegistered bool + // Loggers Log *logrus.Entry diff --git a/internal/sbi/consumer/ue_context_management.go b/internal/sbi/consumer/ue_context_management.go new file mode 100644 index 00000000..a9263fa6 --- /dev/null +++ b/internal/sbi/consumer/ue_context_management.go @@ -0,0 +1,103 @@ +package consumer + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/Nudm_UEContextManagement" + "github.com/free5gc/openapi/models" + smf_context "github.com/free5gc/smf/internal/context" + "github.com/free5gc/smf/internal/logger" + "github.com/free5gc/smf/internal/util" +) + +func UeCmRegistration(smCtx *smf_context.SMContext) ( + *models.ProblemDetails, error, +) { + uecmUri := util.SearchNFServiceUri(smf_context.GetSelf().UDMProfile, models.ServiceName_NUDM_UECM, + models.NfServiceStatus_REGISTERED) + if uecmUri == "" { + return nil, errors.Errorf("SMF can not select an UDM by NRF: SearchNFServiceUri failed") + } + + configuration := Nudm_UEContextManagement.NewConfiguration() + configuration.SetBasePath(uecmUri) + client := Nudm_UEContextManagement.NewAPIClient(configuration) + + registrationData := models.SmfRegistration{ + SmfInstanceId: smf_context.GetSelf().NfInstanceID, + SupportedFeatures: "", + PduSessionId: smCtx.PduSessionId, + SingleNssai: smCtx.SNssai, + Dnn: smCtx.Dnn, + EmergencyServices: false, + PcscfRestorationCallbackUri: "", + PlmnId: smCtx.Guami.PlmnId, + PgwFqdn: "", + } + + logger.PduSessLog.Infoln("UECM Registration SmfInstanceId:", registrationData.SmfInstanceId, + " PduSessionId:", registrationData.PduSessionId, " SNssai:", registrationData.SingleNssai, + " Dnn:", registrationData.Dnn, " PlmnId:", registrationData.PlmnId) + + _, httpResp, localErr := client.SMFRegistrationApi.SmfRegistrationsPduSessionId(context.Background(), + smCtx.Supi, smCtx.PduSessionId, registrationData) + defer func() { + if httpResp != nil { + if rspCloseErr := httpResp.Body.Close(); rspCloseErr != nil { + logger.PduSessLog.Errorf("UeCmRegistration response body cannot close: %+v", + rspCloseErr) + } + } + }() + + if localErr == nil { + smCtx.UeCmRegistered = true + return nil, nil + } else if httpResp != nil { + if httpResp.Status != localErr.Error() { + return nil, localErr + } + problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + return &problem, nil + } else { + return nil, openapi.ReportError("server no response") + } +} + +func UeCmDeregistration(smCtx *smf_context.SMContext) (*models.ProblemDetails, error) { + uecmUri := util.SearchNFServiceUri(smf_context.GetSelf().UDMProfile, models.ServiceName_NUDM_UECM, + models.NfServiceStatus_REGISTERED) + if uecmUri == "" { + return nil, errors.Errorf("SMF can not select an UDM by NRF: SearchNFServiceUri failed") + } + + configuration := Nudm_UEContextManagement.NewConfiguration() + configuration.SetBasePath(uecmUri) + client := Nudm_UEContextManagement.NewAPIClient(configuration) + + httpResp, localErr := client.SMFDeregistrationApi.Deregistration(context.Background(), + smCtx.Supi, smCtx.PduSessionId) + defer func() { + if httpResp != nil { + if rspCloseErr := httpResp.Body.Close(); rspCloseErr != nil { + logger.ConsumerLog.Errorf("UeCmDeregistration response body cannot close: %+v", + rspCloseErr) + } + } + }() + if localErr == nil { + smCtx.UeCmRegistered = false + return nil, nil + } else if httpResp != nil { + if httpResp.Status != localErr.Error() { + return nil, localErr + } + problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + return &problem, nil + } else { + return nil, openapi.ReportError("server no response") + } +} diff --git a/internal/sbi/producer/pdu_session.go b/internal/sbi/producer/pdu_session.go index 086b097c..05610f60 100644 --- a/internal/sbi/producer/pdu_session.go +++ b/internal/sbi/producer/pdu_session.go @@ -184,14 +184,14 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, smContext.SMPolicyID = smPolicyID // Update SessionRule from decision - if err := smContext.ApplySessionRules(smPolicyDecision); err != nil { + if err = smContext.ApplySessionRules(smPolicyDecision); err != nil { smContext.Log.Errorf("PDUSessionSMContextCreate err: %v", err) return makeEstRejectResAndReleaseSMContext(smContext, nasMessage.Cause5GSMRequestRejectedUnspecified, &Nsmf_PDUSession.SubscriptionDenied) } - if err := smContext.SelectDefaultDataPath(); err != nil { + if err = smContext.SelectDefaultDataPath(); err != nil { smContext.SetState(smf_context.InActive) smContext.Log.Errorf("PDUSessionSMContextCreate err: %v", err) return makeEstRejectResAndReleaseSMContext(smContext, @@ -199,10 +199,20 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, &Nsmf_PDUSession.InsufficientResourceSliceDnn) } - if err := smContext.ApplyPccRules(smPolicyDecision); err != nil { + if err = smContext.ApplyPccRules(smPolicyDecision); err != nil { smContext.Log.Errorf("apply sm policy decision error: %+v", err) } + // UECM registration + problemDetails, err := consumer.UeCmRegistration(smContext) + if problemDetails != nil { + smContext.Log.Errorf("UECM_Registration Error: %+v", problemDetails) + } else if err != nil { + smContext.Log.Errorf("UECM_Registration Error: %+v", err) + } else { + smContext.Log.Traceln("UECM Registration Successful") + } + // generate goroutine to handle PFCP and // reply PDUSessionSMContextCreate rsp immediately needUnlock = false @@ -230,7 +240,6 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, Status: http.StatusCreated, Body: response, } - // TODO: UECM registration } func HandlePDUSessionSMContextUpdate(smContextRef string, body models.UpdateSmContextRequest) *httpwrapper.Response { @@ -314,6 +323,19 @@ func HandlePDUSessionSMContextUpdate(smContextRef string, body models.UpdateSmCo } } + if smContext.UeCmRegistered { + problemDetails, err := consumer.UeCmDeregistration(smContext) + if problemDetails != nil { + if problemDetails.Cause != "CONTEXT_NOT_FOUND" { + logger.PduSessLog.Errorf("UECM_DeRegistration Failed Problem[%+v]", problemDetails) + } + } else if err != nil { + logger.PduSessLog.Errorf("UECM_DeRegistration Error[%+v]", err) + } else { + logger.PduSessLog.Traceln("UECM_DeRegistration successful") + } + } + cause := nasMessage.Cause5GSMRegularDeactivation if m.PDUSessionReleaseRequest.Cause5GSM != nil { cause = m.PDUSessionReleaseRequest.Cause5GSM.GetCauseValue() @@ -859,6 +881,19 @@ func HandlePDUSessionSMContextRelease(smContextRef string, body models.ReleaseSm } } + if smContext.UeCmRegistered { + problemDetails, err := consumer.UeCmDeregistration(smContext) + if problemDetails != nil { + if problemDetails.Cause != "CONTEXT_NOT_FOUND" { + logger.PduSessLog.Errorf("UECM_DeRegistration Failed Problem[%+v]", problemDetails) + } + } else if err != nil { + logger.PduSessLog.Errorf("UECM_DeRegistration Error[%+v]", err) + } else { + logger.PduSessLog.Traceln("UECM_DeRegistration successful") + } + } + if !smContext.CheckState(smf_context.InActive) { smContext.SetState(smf_context.PFCPModification) } @@ -945,6 +980,19 @@ func HandlePDUSessionSMContextLocalRelease(smContext *smf_context.SMContext, cre } } + if smContext.UeCmRegistered { + problemDetails, err := consumer.UeCmDeregistration(smContext) + if problemDetails != nil { + if problemDetails.Cause != "CONTEXT_NOT_FOUND" { + logger.PduSessLog.Errorf("UECM_DeRegistration Failed Problem[%+v]", problemDetails) + } + } else if err != nil { + logger.PduSessLog.Errorf("UECM_DeRegistration Error[%+v]", err) + } else { + logger.PduSessLog.Traceln("UECM_DeRegistration successful") + } + } + smContext.SetState(smf_context.PFCPModification) pfcpResponseStatus := releaseSession(smContext) diff --git a/internal/util/search_nf_service.go b/internal/util/search_nf_service.go new file mode 100644 index 00000000..32d5e79c --- /dev/null +++ b/internal/util/search_nf_service.go @@ -0,0 +1,50 @@ +package util + +import ( + "fmt" + + "github.com/free5gc/openapi/models" +) + +func SearchNFServiceUri(nfProfile models.NfProfile, serviceName models.ServiceName, + nfServiceStatus models.NfServiceStatus, +) (nfUri string) { + if nfProfile.NfServices != nil { + for _, service := range *nfProfile.NfServices { + if service.ServiceName == serviceName && service.NfServiceStatus == nfServiceStatus { + if nfProfile.Fqdn != "" { + nfUri = nfProfile.Fqdn + } else if service.Fqdn != "" { + nfUri = service.Fqdn + } else if service.ApiPrefix != "" { + nfUri = service.ApiPrefix + } else if service.IpEndPoints != nil { + point := (*service.IpEndPoints)[0] + if point.Ipv4Address != "" { + nfUri = getSbiUri(service.Scheme, point.Ipv4Address, point.Port) + } else if len(nfProfile.Ipv4Addresses) != 0 { + nfUri = getSbiUri(service.Scheme, nfProfile.Ipv4Addresses[0], point.Port) + } + } + } + if nfUri != "" { + break + } + } + } + return +} + +func getSbiUri(scheme models.UriScheme, ipv4Address string, port int32) (uri string) { + if port != 0 { + uri = fmt.Sprintf("%s://%s:%d", scheme, ipv4Address, port) + } else { + switch scheme { + case models.UriScheme_HTTP: + uri = fmt.Sprintf("%s://%s:80", scheme, ipv4Address) + case models.UriScheme_HTTPS: + uri = fmt.Sprintf("%s://%s:443", scheme, ipv4Address) + } + } + return +}