diff --git a/internal/context/context.go b/internal/context/context.go index 154b997a..b63d5149 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -82,6 +82,8 @@ type SMFContext struct { UEDefaultPathPool map[string]*UEDefaultPaths LocalSEIDCount uint64 + Ues *Ues + // Each pdu session should have a unique charging id ChargingIDGenerator *idgenerator.IDGenerator } @@ -261,6 +263,9 @@ func InitSmfContext(config *factory.Config) { smfContext.Locality = configuration.Locality + + smfContext.Ues = InitSmfUeData() + TeidGenerator = idgenerator.NewGenerator(1, math.MaxUint32) } diff --git a/internal/context/sm_ue.go b/internal/context/sm_ue.go new file mode 100644 index 00000000..364769a9 --- /dev/null +++ b/internal/context/sm_ue.go @@ -0,0 +1,100 @@ +package context + +import "sync" + +type UeData struct { + PduSessionCount int // store number of PDU Sessions for each UE + SdmSubscriptionId string // store SDM Subscription ID per UE +} + +type Ues struct { + ues map[string]UeData // map to store UE data with SUPI as key + mu sync.Mutex // mutex for concurrent access +} + +func InitSmfUeData() *Ues { + return &Ues{ + ues: make(map[string]UeData), + } +} + +// IncrementPduSessionCount increments the PDU session count for a given UE. +func (u *Ues) IncrementPduSessionCount(ueId string) { + u.mu.Lock() + defer u.mu.Unlock() + + ueData := u.ues[ueId] + ueData.PduSessionCount++ + u.ues[ueId] = ueData +} + +// DecrementPduSessionCount decrements the PDU session count for a given UE. +func (u *Ues) DecrementPduSessionCount(ueId string) { + u.mu.Lock() + defer u.mu.Unlock() + + ueData := u.ues[ueId] + if ueData.PduSessionCount > 0 { + ueData.PduSessionCount-- + u.ues[ueId] = ueData + } +} + +// SetSubscriptionId sets the SDM subscription ID for a given UE. +func (u *Ues) SetSubscriptionId(ueId, subscriptionId string) { + u.mu.Lock() + defer u.mu.Unlock() + + ueData := u.ues[ueId] + ueData.SdmSubscriptionId = subscriptionId + u.ues[ueId] = ueData +} + +// GetSubscriptionId returns the SDM subscription ID for a given UE. +func (u *Ues) GetSubscriptionId(ueId string) string { + u.mu.Lock() + defer u.mu.Unlock() + + return u.ues[ueId].SdmSubscriptionId +} + +// GetUeData returns the data for a given UE. +func (u *Ues) GetUeData(ueId string) UeData { + u.mu.Lock() + defer u.mu.Unlock() + + return u.ues[ueId] +} + +// DeleteUe deletes a UE. +func (u *Ues) DeleteUe(ueId string) { + u.mu.Lock() + defer u.mu.Unlock() + + delete(u.ues, ueId) +} + +// UeExists checks if a UE already exists. +func (u *Ues) UeExists(ueId string) bool { + u.mu.Lock() + defer u.mu.Unlock() + + _, exists := u.ues[ueId] + return exists +} + +// IsLastPduSession checks if it is the last PDU session for a given UE. +func (u *Ues) IsLastPduSession(ueID string) bool { + u.mu.Lock() + defer u.mu.Unlock() + + return u.ues[ueID].PduSessionCount == 1 +} + +// GetPduSessionCount returns the number of sessions for a given UE. +func (u *Ues) GetPduSessionCount(ueId string) int { + u.mu.Lock() + defer u.mu.Unlock() + + return u.ues[ueId].PduSessionCount +} diff --git a/internal/sbi/consumer/subscriber_data_management.go b/internal/sbi/consumer/subscriber_data_management.go new file mode 100644 index 00000000..a793b955 --- /dev/null +++ b/internal/sbi/consumer/subscriber_data_management.go @@ -0,0 +1,178 @@ +package consumer + +import ( + "github.com/antihax/optional" + "github.com/pkg/errors" + + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/Nudm_SubscriberDataManagement" + "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 SDMGetSmData(smCtx *smf_context.SMContext, + smPlmnID *models.PlmnId, +) (problemDetails *models.ProblemDetails, err error) { + // Query UDM + if problemDetails, err = SendNFDiscoveryUDM(); err != nil { + smCtx.Log.Warnf("Send NF Discovery Serving UDM Error[%v]", err) + } else if problemDetails != nil { + smCtx.Log.Warnf("Send NF Discovery Serving UDM Problem[%+v]", problemDetails) + } else { + smCtx.Log.Infoln("Send NF Discovery Serving UDM Successfully") + } + + smDataParams := &Nudm_SubscriberDataManagement.GetSmDataParamOpts{ + Dnn: optional.NewString(smCtx.Dnn), + PlmnId: optional.NewInterface(openapi.MarshToJsonString(smPlmnID)), + SingleNssai: optional.NewInterface(openapi.MarshToJsonString(smCtx.SNssai)), + } + + SubscriberDataManagementClient := smf_context.GetSelf().SubscriberDataManagementClient + + ctx, pd, oauthErr := smf_context.GetSelf().GetTokenCtx(models.ServiceName_NUDM_SDM, models.NfType_UDM) + if oauthErr != nil { + return pd, oauthErr + } + + sessSubData, rsp, localErr := SubscriberDataManagementClient. + SessionManagementSubscriptionDataRetrievalApi. + GetSmData(ctx, smCtx.Supi, smDataParams) + if localErr == nil { + defer func() { + if rspCloseErr := rsp.Body.Close(); rspCloseErr != nil { + logger.ConsumerLog.Errorf("GetSmData response body cannot close: %+v", rspCloseErr) + } + }() + if len(sessSubData) > 0 { + smCtx.DnnConfiguration = sessSubData[0].DnnConfigurations[smCtx.Dnn] + // UP Security info present in session management subscription data + if smCtx.DnnConfiguration.UpSecurity != nil { + smCtx.UpSecurity = smCtx.DnnConfiguration.UpSecurity + } + } else { + logger.ConsumerLog.Errorln("SessionManagementSubscriptionData from UDM is nil") + err = openapi.ReportError("SmData is nil") + } + } else if rsp != nil { + if rsp.Status != localErr.Error() { + err = localErr + return nil, err + } + problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + problemDetails = &problem + } else { + logger.ConsumerLog.Errorln("Get SessionManagementSubscriptionData error:", localErr) + err = localErr + } + + return problemDetails, err +} + +func SDMSubscribe(smCtx *smf_context.SMContext, smPlmnID *models.PlmnId) ( + problemDetails *models.ProblemDetails, err error, +) { + if !smf_context.GetSelf().Ues.UeExists(smCtx.Supi) { + sdmUri := util.SearchNFServiceUri(smf_context.GetSelf().UDMProfile, models.ServiceName_NUDM_SDM, + models.NfServiceStatus_REGISTERED) + if sdmUri == "" { + return nil, errors.Errorf("SMF can not select an UDM by NRF: SearchNFServiceUri failed") + } + + configuration := Nudm_SubscriberDataManagement.NewConfiguration() + configuration.SetBasePath(sdmUri) + client := Nudm_SubscriberDataManagement.NewAPIClient(configuration) + + sdmSubscription := models.SdmSubscription{ + NfInstanceId: smf_context.GetSelf().NfInstanceID, + PlmnId: smPlmnID, + } + + ctx, pd, oauthErr := smf_context.GetSelf().GetTokenCtx(models.ServiceName_NUDM_SDM, models.NfType_UDM) + if oauthErr != nil { + return pd, oauthErr + } + + resSubscription, httpResp, localErr := client.SubscriptionCreationApi.Subscribe( + ctx, smCtx.Supi, sdmSubscription) + defer func() { + if httpResp != nil { + if rspCloseErr := httpResp.Body.Close(); rspCloseErr != nil { + logger.ConsumerLog.Errorf("Subscribe response body cannot close: %+v", + rspCloseErr) + } + } + }() + + if localErr == nil { + smf_context.GetSelf().Ues.SetSubscriptionId(smCtx.Supi, resSubscription.SubscriptionId) + logger.ConsumerLog.Infoln("SDM Subscription Successful UE:", smCtx.Supi, "SubscriptionId:", + resSubscription.SubscriptionId) + } else if httpResp != nil { + if httpResp.Status != localErr.Error() { + err = localErr + return problemDetails, err + } + problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + problemDetails = &problem + } else { + err = openapi.ReportError("server no response") + } + } + + smf_context.GetSelf().Ues.IncrementPduSessionCount(smCtx.Supi) + return problemDetails, err +} + +func SDMUnSubscribe(smCtx *smf_context.SMContext) (problemDetails *models.ProblemDetails, + err error, +) { + if smf_context.GetSelf().Ues.UeExists(smCtx.Supi) { + if smf_context.GetSelf().Ues.IsLastPduSession(smCtx.Supi) { + sdmUri := util.SearchNFServiceUri(smf_context.GetSelf().UDMProfile, models.ServiceName_NUDM_SDM, + models.NfServiceStatus_REGISTERED) + if sdmUri == "" { + return nil, errors.Errorf("SMF can not select an UDM by NRF: SearchNFServiceUri failed") + } + configuration := Nudm_SubscriberDataManagement.NewConfiguration() + configuration.SetBasePath(sdmUri) + + client := Nudm_SubscriberDataManagement.NewAPIClient(configuration) + subscriptionId := smf_context.GetSelf().Ues.GetSubscriptionId(smCtx.Supi) + + ctx, pd, oauthErr := smf_context.GetSelf().GetTokenCtx(models.ServiceName_NUDM_SDM, models.NfType_UDM) + if oauthErr != nil { + return pd, oauthErr + } + + httpResp, localErr := client.SubscriptionDeletionApi.Unsubscribe(ctx, smCtx.Supi, subscriptionId) + defer func() { + if httpResp != nil { + if rspCloseErr := httpResp.Body.Close(); rspCloseErr != nil { + logger.ConsumerLog.Errorf("Unsubscribe response body cannot close: %+v", + rspCloseErr) + } + } + }() + if localErr == nil { + logger.ConsumerLog.Infoln("SDM UnSubscription Successful UE:", smCtx.Supi, "SubscriptionId:", + subscriptionId) + } else if httpResp != nil { + if httpResp.Status != localErr.Error() { + err = localErr + return problemDetails, err + } + problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + problemDetails = &problem + } else { + err = openapi.ReportError("server no response") + } + smf_context.GetSelf().Ues.DeleteUe(smCtx.Supi) + } else { + smf_context.GetSelf().Ues.DecrementPduSessionCount(smCtx.Supi) + } + } + return problemDetails, err +} diff --git a/internal/sbi/producer/pdu_session.go b/internal/sbi/producer/pdu_session.go index 80aa5cc2..7df0019c 100644 --- a/internal/sbi/producer/pdu_session.go +++ b/internal/sbi/producer/pdu_session.go @@ -6,14 +6,11 @@ import ( "net" "net/http" - "github.com/antihax/optional" - "github.com/free5gc/nas" "github.com/free5gc/nas/nasMessage" "github.com/free5gc/openapi" "github.com/free5gc/openapi/Namf_Communication" "github.com/free5gc/openapi/Nsmf_PDUSession" - "github.com/free5gc/openapi/Nudm_SubscriberDataManagement" "github.com/free5gc/openapi/models" "github.com/free5gc/pfcp/pfcpType" smf_context "github.com/free5gc/smf/internal/context" @@ -81,54 +78,24 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, smContext.Log.Debugf("S-NSSAI[sst: %d, sd: %s] DNN[%s]", smContext.SNssai.Sst, smContext.SNssai.Sd, smContext.Dnn) - // Query UDM - if problemDetails, err := consumer.SendNFDiscoveryUDM(); err != nil { - smContext.Log.Warnf("Send NF Discovery Serving UDM Error[%v]", err) - } else if problemDetails != nil { - smContext.Log.Warnf("Send NF Discovery Serving UDM Problem[%+v]", problemDetails) - } else { - smContext.Log.Infoln("Send NF Discovery Serving UDM Successfully") - } - smPlmnID := createData.Guami.PlmnId - smDataParams := &Nudm_SubscriberDataManagement.GetSmDataParamOpts{ - Dnn: optional.NewString(createData.Dnn), - PlmnId: optional.NewInterface(openapi.MarshToJsonString(smPlmnID)), - SingleNssai: optional.NewInterface(openapi.MarshToJsonString(smContext.SNssai)), + problemDetails, err := consumer.SDMGetSmData(smContext, smPlmnID) + if problemDetails != nil { + smContext.Log.Errorf("SDM_Get SmData Failed Problem[%+v]", problemDetails) + } else if err != nil { + smContext.Log.Errorf("SDM_Get SmData Error[%+v]", err) } - SubscriberDataManagementClient := smf_context.GetSelf().SubscriberDataManagementClient - - ctx, _, oauthErr := smf_context.GetSelf().GetTokenCtx(models.ServiceName_NUDM_SDM, models.NfType_UDM) - if oauthErr != nil { - smContext.Log.Errorf("Get Token Context Error[%v]", oauthErr) - return nil - } - - if sessSubData, rsp, err := SubscriberDataManagementClient. - SessionManagementSubscriptionDataRetrievalApi. - GetSmData(ctx, smContext.Supi, smDataParams); err != nil { - smContext.Log.Errorln("Get SessionManagementSubscriptionData error:", err) - } else { - defer func() { - if rspCloseErr := rsp.Body.Close(); rspCloseErr != nil { - smContext.Log.Errorf("GetSmData response body cannot close: %+v", rspCloseErr) - } - }() - if len(sessSubData) > 0 { - smContext.DnnConfiguration = sessSubData[0].DnnConfigurations[smContext.Dnn] - // UP Security info present in session management subscription data - if smContext.DnnConfiguration.UpSecurity != nil { - smContext.UpSecurity = smContext.DnnConfiguration.UpSecurity - } - } else { - smContext.Log.Errorln("SessionManagementSubscriptionData from UDM is nil") - } + problemDetails, err = consumer.SDMSubscribe(smContext, smPlmnID) + if problemDetails != nil { + smContext.Log.Errorf("SDM Subscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + smContext.Log.Errorf("SDM Subscription Error[%+v]", err) } establishmentRequest := m.PDUSessionEstablishmentRequest - if err := HandlePDUSessionEstablishmentRequest(smContext, establishmentRequest); err != nil { + if err = HandlePDUSessionEstablishmentRequest(smContext, establishmentRequest); err != nil { smContext.Log.Errorf("PDU Session Establishment fail by %s", err) gsmError := &GSMError{} if errors.As(err, &gsmError) { @@ -142,7 +109,7 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, } // Discover and new Namf_Comm client for use later - if problemDetails, err := consumer.SendNFDiscoveryServingAMF(smContext); err != nil { + if problemDetails, err = consumer.SendNFDiscoveryServingAMF(smContext); err != nil { smContext.Log.Warnf("Send NF Discovery Serving AMF Error[%v]", err) } else if problemDetails != nil { smContext.Log.Warnf("Send NF Discovery Serving AMF Problem[%+v]", problemDetails) @@ -158,7 +125,7 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, } } - if err := smContext.AllocUeIP(); err != nil { + if err = smContext.AllocUeIP(); err != nil { smContext.SetState(smf_context.InActive) smContext.Log.Errorf("PDUSessionSMContextCreate err: %v", err) return makeEstRejectResAndReleaseSMContext(smContext, @@ -166,7 +133,7 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, &Nsmf_PDUSession.InsufficientResourceSliceDnn) } - if err := smContext.PCFSelection(); err != nil { + if err = smContext.PCFSelection(); err != nil { smContext.Log.Errorln("pcf selection error:", err) } @@ -206,12 +173,12 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, } // If PCF prepares default Pcc Rule, SMF do not need to create defaultDataPath. - if err := smContext.ApplyPccRules(smPolicyDecision); err != nil { + if err = smContext.ApplyPccRules(smPolicyDecision); err != nil { smContext.Log.Errorf("apply sm policy decision error: %+v", err) } // SelectDefaultDataPath() will create a default data path if default data path is not found. - 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, @@ -219,6 +186,16 @@ func HandlePDUSessionSMContextCreate(isDone <-chan struct{}, &Nsmf_PDUSession.InsufficientResourceSliceDnn) } + // 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 @@ -329,6 +306,13 @@ func HandlePDUSessionSMContextUpdate(smContextRef string, body models.UpdateSmCo } } + problemDetails, err := consumer.SDMUnSubscribe(smContext) + if problemDetails != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.PduSessLog.Errorf("SDM UnSubscriptio Error[%+v]", err) + } + if smContext.UeCmRegistered { problemDetails, err := consumer.UeCmDeregistration(smContext) if problemDetails != nil { @@ -907,6 +891,13 @@ func HandlePDUSessionSMContextRelease(smContextRef string, body models.ReleaseSm } } + problemDetails, err := consumer.SDMUnSubscribe(smContext) + if problemDetails != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.PduSessLog.Errorf("SDM UnSubscriptio Error[%+v]", err) + } + if smContext.UeCmRegistered { problemDetails, err := consumer.UeCmDeregistration(smContext) if problemDetails != nil { @@ -1008,6 +999,13 @@ func HandlePDUSessionSMContextLocalRelease(smContext *smf_context.SMContext, cre } } + problemDetails, err := consumer.SDMUnSubscribe(smContext) + if problemDetails != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.PduSessLog.Errorf("SDM UnSubscriptio Error[%+v]", err) + } + if smContext.UeCmRegistered { problemDetails, err := consumer.UeCmDeregistration(smContext) if problemDetails != nil { diff --git a/internal/sbi/producer/sm_common.go b/internal/sbi/producer/sm_common.go index 1e246a70..ff20ec55 100644 --- a/internal/sbi/producer/sm_common.go +++ b/internal/sbi/producer/sm_common.go @@ -16,6 +16,26 @@ func RemoveSMContextFromAllNF(smContext *smf_context.SMContext, sendNotification } } + problemDetails, err := consumer.SDMUnSubscribe(smContext) + if problemDetails != nil { + smContext.Log.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + smContext.Log.Errorf("SDM UnSubscriptio Error[%+v]", err) + } + + if smContext.UeCmRegistered { + problemDetails, err := consumer.UeCmDeregistration(smContext) + if problemDetails != nil { + if problemDetails.Cause != "CONTEXT_NOT_FOUND" { + smContext.Log.Errorf("UECM_DeRegistration Failed Problem[%+v]", problemDetails) + } + } else if err != nil { + smContext.Log.Errorf("UECM_DeRegistration Error[%+v]", err) + } else { + smContext.Log.Traceln("UECM_DeRegistration successful") + } + } + // Because the amfUE who called this SMF API is being locked until the API Handler returns, // sending SMContext Status Notification should run asynchronously // so that this function returns immediately.