diff --git a/internal/context/amf_ue.go b/internal/context/amf_ue.go index f293ddfc..52615fd5 100644 --- a/internal/context/amf_ue.go +++ b/internal/context/amf_ue.go @@ -104,6 +104,7 @@ type AmfUe struct { UdmGroupId string SubscribedNssai []models.SubscribedSnssai AccessAndMobilitySubscriptionData *models.AccessAndMobilitySubscriptionData + BackupAmfInfo []models.BackupAmfInfo /* contex abut ausf */ AusfGroupId string AusfId string @@ -850,7 +851,6 @@ func (ue *AmfUe) CopyDataFromUeContextModel(ueContext models.UeContext) { } // SM Context realted function - func (ue *AmfUe) StoreSmContext(pduSessionID int32, smContext *SmContext) { ue.SmContextList.Store(pduSessionID, smContext) } @@ -862,6 +862,19 @@ func (ue *AmfUe) SmContextFindByPDUSessionID(pduSessionID int32) (*SmContext, bo return nil, false } +func (ue *AmfUe) UpdateBackupAmfInfo(backupAmfInfo models.BackupAmfInfo) { + isExist := false + for _, amfInfo := range ue.BackupAmfInfo { + if amfInfo.BackupAmf == backupAmfInfo.BackupAmf { + isExist = true + break + } + } + if !isExist { + ue.BackupAmfInfo = append(ue.BackupAmfInfo, backupAmfInfo) + } +} + func (ue *AmfUe) StopT3513() { if ue.T3513 == nil { return diff --git a/internal/gmm/common/user_profile.go b/internal/gmm/common/user_profile.go index 2556e41d..5a9caa1b 100644 --- a/internal/gmm/common/user_profile.go +++ b/internal/gmm/common/user_profile.go @@ -41,13 +41,13 @@ func RemoveAmfUe(ue *context.AmfUe, notifyNF bool) { func PurgeAmfUeSubscriberData(ue *context.AmfUe) { if ue.RanUe[models.AccessType__3_GPP_ACCESS] != nil { - err := purgeSubscriberData(ue, models.AccessType__3_GPP_ACCESS) + err := PurgeSubscriberData(ue, models.AccessType__3_GPP_ACCESS) if err != nil { logger.GmmLog.Errorf("Purge subscriber data Error[%v]", err.Error()) } } if ue.RanUe[models.AccessType_NON_3_GPP_ACCESS] != nil { - err := purgeSubscriberData(ue, models.AccessType_NON_3_GPP_ACCESS) + err := PurgeSubscriberData(ue, models.AccessType_NON_3_GPP_ACCESS) if err != nil { logger.GmmLog.Errorf("Purge subscriber data Error[%v]", err.Error()) } @@ -69,8 +69,8 @@ func AttachRanUeToAmfUeAndReleaseOldIfAny(ue *context.AmfUe, ranUe *context.RanU ue.AttachRanUe(ranUe) } -func purgeSubscriberData(ue *context.AmfUe, accessType models.AccessType) error { - logger.GmmLog.Debugln("purgeSubscriberData") +func PurgeSubscriberData(ue *context.AmfUe, accessType models.AccessType) error { + logger.GmmLog.Debugln("PurgeSubscriberData") if !ue.ContextValid { return nil @@ -89,9 +89,9 @@ func purgeSubscriberData(ue *context.AmfUe, accessType models.AccessType) error if ue.UeCmRegistered[accessType] { problemDetails, err := consumer.UeCmDeregistration(ue, accessType) if problemDetails != nil { - logger.GmmLog.Errorf("UECM_Registration Failed Problem[%+v]", problemDetails) + logger.GmmLog.Errorf("UECM Deregistration Failed Problem[%+v]", problemDetails) } else if err != nil { - logger.GmmLog.Errorf("UECM_Registration Error[%+v]", err) + logger.GmmLog.Errorf("UECM Deregistration Error[%+v]", err) } ue.UeCmRegistered[accessType] = false } diff --git a/internal/gmm/handler.go b/internal/gmm/handler.go index 6be75112..2c0fa29c 100644 --- a/internal/gmm/handler.go +++ b/internal/gmm/handler.go @@ -695,6 +695,11 @@ func HandleInitialRegistration(ue *context.AmfUe, anType models.AccessType) erro param.PreferredLocality = optional.NewString(amfSelf.Locality) } + // TODO: (step 15) Should use PCF ID to select PCF + // Retrieve PCF ID from old AMF + // if ue.PcfId != "" { + + // } for { resp, err := consumer.SendSearchNFInstances(amfSelf.NrfUri, models.NfType_PCF, models.NfType_AMF, ¶m) if err != nil { diff --git a/internal/sbi/consumer/ue_context_management.go b/internal/sbi/consumer/ue_context_management.go index de7475ad..d0f58ca4 100644 --- a/internal/sbi/consumer/ue_context_management.go +++ b/internal/sbi/consumer/ue_context_management.go @@ -2,9 +2,11 @@ package consumer import ( "context" + "fmt" amf_context "github.com/free5gc/amf/internal/context" "github.com/free5gc/amf/internal/logger" + "github.com/free5gc/amf/pkg/factory" "github.com/free5gc/openapi" "github.com/free5gc/openapi/Nudm_UEContextManagement" "github.com/free5gc/openapi/models" @@ -21,11 +23,18 @@ func UeCmRegistration(ue *amf_context.AmfUe, accessType models.AccessType, initi switch accessType { case models.AccessType__3_GPP_ACCESS: + deregCallbackUri := fmt.Sprintf("%s%s/deregistration/%s", + amfSelf.GetIPv4Uri(), + factory.AmfCallbackResUriPrefix, + ue.Supi, + ) + registrationData := models.Amf3GppAccessRegistration{ AmfInstanceId: amfSelf.NfId, InitialRegistrationInd: initialRegistrationInd, Guami: &amfSelf.ServedGuamiList[0], RatType: ue.RatType, + DeregCallbackUri: deregCallbackUri, // TODO: not support Homogenous Support of IMS Voice over PS Sessions this stage ImsVoPs: models.ImsVoPs_HOMOGENEOUS_NON_SUPPORT, } diff --git a/internal/sbi/httpcallback/api_handle_dereg_notification.go b/internal/sbi/httpcallback/api_handle_dereg_notification.go new file mode 100644 index 00000000..835beccb --- /dev/null +++ b/internal/sbi/httpcallback/api_handle_dereg_notification.go @@ -0,0 +1,126 @@ +package httpcallback + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + amf_context "github.com/free5gc/amf/internal/context" + "github.com/free5gc/amf/internal/logger" + "github.com/free5gc/amf/internal/sbi/consumer" + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/models" +) + +func HTTPHandleDeregistrationNotification(c *gin.Context) { + // TS 23.502 - 4.2.2.2.2 - step 14d + logger.CallbackLog.Infoln("Handle Deregistration Notification") + + var deregData models.DeregistrationData + + requestBody, err := c.GetRawData() + if err != nil { + logger.CallbackLog.Errorf("Get Request Body error: %+v", err) + problemDetails := models.ProblemDetails{ + Title: "System failure", + Status: http.StatusInternalServerError, + Detail: err.Error(), + Cause: "SYSTEM_FAILURE", + } + c.JSON(http.StatusInternalServerError, problemDetails) + return + } + + err = openapi.Deserialize(&deregData, requestBody, "application/json") + if err != nil { + problemDetails := models.ProblemDetails{ + Title: "Malformed request syntax", + Status: http.StatusBadRequest, + Detail: "[Request Body] " + err.Error(), + } + logger.CallbackLog.Errorln(problemDetails.Detail) + c.JSON(http.StatusBadRequest, problemDetails) + return + } + + ueid := c.Param("ueid") + ue, ok := amf_context.GetSelf().AmfUeFindByUeContextID(ueid) + if !ok { + logger.CallbackLog.Errorf("AmfUe Context[%s] not found", ueid) + problemDetails := models.ProblemDetails{ + Status: http.StatusNotFound, + Cause: "CONTEXT_NOT_FOUND", + } + c.JSON(http.StatusNotFound, problemDetails) + return + } + + problemDetails, err := DeregistrationNotificationProcedure(ue, deregData) + if problemDetails != nil { + ue.GmmLog.Errorf("Deregistration Notification Procedure Failed Problem[%+v]", problemDetails) + } else if err != nil { + ue.GmmLog.Errorf("Deregistration Notification Procedure Error[%v]", err.Error()) + } + // TS 23.503 - 5.3.2.3.2 UDM initiated NF Deregistration + // The AMF acknowledges the Nudm_UECM_DeRegistrationNotification to the UDM. + c.JSON(http.StatusNoContent, nil) +} + +// TS 23.502 - 4.2.2.3.3 Network-initiated Deregistration +// The AMF can initiate this procedure for either explicit (e.g. by O&M intervention) or +// implicit (e.g. expiring of Implicit Deregistration timer) +func DeregistrationNotificationProcedure(ue *amf_context.AmfUe, deregData models.DeregistrationData) ( + problemDetails *models.ProblemDetails, err error, +) { + // The AMF does not send the Deregistration Request message to the UE for Implicit Deregistration. + switch deregData.DeregReason { + case models.DeregistrationReason_UE_INITIAL_REGISTRATION: + // TS 23.502 - 4.2.2.2.2 General Registration + // Invokes the Nsmf_PDUSession_ReleaseSMContext for the corresponding access type + ue.SmContextList.Range(func(key, value interface{}) bool { + smContext := value.(*amf_context.SmContext) + if smContext.AccessType() == deregData.AccessType { + problemDetails, err = consumer.SendReleaseSmContextRequest(ue, smContext, nil, "", nil) + if problemDetails != nil { + ue.GmmLog.Errorf("Release SmContext Failed Problem[%+v]", problemDetails) + } else if err != nil { + ue.GmmLog.Errorf("Release SmContext Error[%v]", err.Error()) + } + } + return true + }) + } + // TS 23.502 - 4.2.2.2.2 General Registration - 14e + // TODO: (R16) If old AMF does not have UE context for another access type (i.e. non-3GPP access), + // the Old AMF unsubscribes with the UDM for subscription data using Nudm_SDM_unsubscribe + if ue.SdmSubscriptionId != "" { + problemDetails, err = consumer.SDMUnsubscribe(ue) + if problemDetails != nil { + logger.GmmLog.Errorf("SDM Unubscribe Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.GmmLog.Errorf("SDM Unubscribe Error[%+v]", err) + } + ue.SdmSubscriptionId = "" + } + + // TS 23.502 - 4.2.2.2.2 General Registration - 20 AMF-Initiated Policy Association Termination + // For UE_INITIAL_REGISTRATION and SUBSCRIPTION_WITHDRAW, do AMF-Initiated Policy Association Termination directly. + if ue.PolicyAssociationId != "" { + // TODO: For REGISTRATION_AREA_CHANGE, old AMF performs an AMF-initiated Policy Association Termination + // procedure if the old AMF has established an AM Policy Association and a UE Policy Association with the PCF(s) + // and the old AMF did not transfer the PCF ID(s) to the new AMF. (Ref: TS 23.502 - 4.2.2.2.2) + // Currently, old AMF will transfer the PCF ID but new AMF will not utilize the PCF ID + problemDetails, err := consumer.AMPolicyControlDelete(ue) + if problemDetails != nil { + logger.GmmLog.Errorf("Delete AM policy Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.GmmLog.Errorf("Delete AM policy Error[%+v]", err) + } + } + + // The old AMF should clean the UE context + // TODO: (R16) Only remove the target access UE context + ue.Remove() + + return nil, nil +} diff --git a/internal/sbi/httpcallback/router.go b/internal/sbi/httpcallback/routers.go similarity index 93% rename from internal/sbi/httpcallback/router.go rename to internal/sbi/httpcallback/routers.go index a8a2dea1..80ba64ff 100644 --- a/internal/sbi/httpcallback/router.go +++ b/internal/sbi/httpcallback/routers.go @@ -100,4 +100,11 @@ var routes = Routes{ "/n1-message-notify", HTTPN1MessageNotify, }, + + { + "HandleDeregistrationNotification", + strings.ToUpper("Post"), + "/deregistration/:ueid", + HTTPHandleDeregistrationNotification, + }, } diff --git a/internal/sbi/producer/ue_context.go b/internal/sbi/producer/ue_context.go index b0b1638c..fb611824 100644 --- a/internal/sbi/producer/ue_context.go +++ b/internal/sbi/producer/ue_context.go @@ -260,8 +260,9 @@ func UEContextTransferProcedure(ueContextID string, ueContextTransferRequest mod ue.Lock.Lock() defer ue.Lock.Unlock() - ueContextTransferResponse := new(models.UeContextTransferResponse) - ueContextTransferResponse.JsonData = new(models.UeContextTransferRspData) + ueContextTransferResponse := &models.UeContextTransferResponse{ + JsonData: new(models.UeContextTransferRspData), + } ueContextTransferRspData := ueContextTransferResponse.JsonData //if ue.GetAnType() != UeContextTransferReqData.AccessType { @@ -603,8 +604,10 @@ func RegistrationStatusUpdateProcedure(ueContextID string, ueRegStatusUpdateReqD logger.GmmLog.Errorf("AM Policy Control Delete Error[%v]", err.Error()) } } - - gmm_common.RemoveAmfUe(ue, false) + // TODO: Currently only consider the 3GPP access type + if !ue.UeCmRegistered[models.AccessType__3_GPP_ACCESS] { + gmm_common.RemoveAmfUe(ue, false) + } } else { // NOT_TRANSFERRED logger.CommLog.Debug("[AMF] RegistrationStatusUpdate: NOT_TRANSFERRED")