Free5GC源码研究(14) - AMF研究(三)
前文分别研究了AMF的sbi模块、nas模块、和ngap模块。本文深入研究与AMF强相关的用户设备注册流程。
“注册”,顾名思义,就是“注释名字于簿册之中”。从free5gc的程序逻辑来看,用户设备注册的本质工作是在AMFContext.UePool中正确地创建该设备的AmfUe结构,并设置好相应的属性(其实我觉得这个更像“登陆”,但5G标准说是注册那就注册吧)。5G网络的注册流程包括初始注册、移动注册、周期注册、和紧急注册四种,其中初始注册是我最感兴趣的流程。下面我会尽可能详细揭示初始注册的各个步骤。
初始注册流程
当用户设备开机,尝试连接网络时,会经由接入网向核心网发送初始注册请求。核心网AMF的ngap服务器经过前文所述流程读取来自RAN的消息后,开始把消息分发到相应的处理器函数handlerInitialUEMessage
。
/* https://github.com/free5gc/amf/blob/v1.2.5/internal/ngap/dispatcher.go */ func Dispatch(conn net.Conn, msg []byte) { // ...... ran, ok := amfSelf.AmfRanFindByConn(conn) pdu, err := ngap.Decoder(msg) dispatchMain(ran, pdu) } /* https://github.com/free5gc/amf/blob/v1.2.5/internal/ngap/dispatcher_generated.go */ func dispatchMain(ran *context.AmfRan, message *ngapType.NGAPPDU) { switch message.Present { case ngapType.NGAPPDUPresentInitiatingMessage: initiatingMessage := message.InitiatingMessage switch initiatingMessage.ProcedureCode.Value { // https://github.com/free5gc/amf/blob/v1.2.5/internal/ngap/dispatcher_generated.go#L49 case ngapType.ProcedureCodeInitialUEMessage: handlerInitialUEMessage(ran, message, initiatingMessage) } } /* https://github.com/free5gc/amf/blob/v1.2.5/internal/ngap/handler_generated.go#L4148 */ func handlerInitialUEMessage(ran *context.AmfRan, message *ngapType.NGAPPDU, initiatingMessage *ngapType.InitiatingMessage) { // ...... // https://github.com/free5gc/amf/blob/v1.2.5/internal/ngap/handler_generated.go#L4369 handleInitialUEMessageMain(ran, message, rANUENGAPID, nASPDU, userLocationInformation, rRCEstablishmentCause /* may be nil */, fiveGSTMSI /* may be nil */, uEContextRequest /* may be nil */) }
因为注册管理是nas协议负责的流程,ngap把消息转发给nas让它来处理
// https://github.com/free5gc/amf/blob/v1.2.5/internal/ngap/handler.go#L411 import ( amf_nas "github.com/free5gc/amf/internal/nas" ) func handleInitialUEMessageMain(ran *context.AmfRan, message *ngapType.NGAPPDU, rANUENGAPID *ngapType.RANUENGAPID, nASPDU *ngapType.NASPDU, userLocationInformation *ngapType.UserLocationInformation, rRCEstablishmentCause *ngapType.RRCEstablishmentCause, fiveGSTMSI *ngapType.FiveGSTMSI, uEContextRequest *ngapType.UEContextRequest, ) { // https://github.com/free5gc/amf/blob/v1.2.5/internal/ngap/handler.go#L553C2-L553C9 amf_nas.HandleNAS(ranUe, ngapType.ProcedureCodeInitialUEMessage, nASPDU.Value, true) }
nas协议模块遵循同样的思路,把消息分发给gmm状态机。注意此过程中,nas创建了一个AmfUe
,但却是在ranUe
结构中创建的,而且还是个没有id标记的临时AmfUe
结构
/* https://github.com/free5gc/amf/blob/v1.2.5/internal/nas/handler.go */ func HandleNAS(ranUe *amf_context.RanUe, procedureCode int64, nasPdu []byte, initialMessage bool) { // ...... msg, integrityProtected, err := nas_security.Decode(ranUe.AmfUe, ranUe.Ran.AnType, nasPdu, initialMessage) if ranUe.AmfUe == nil { // Only the New created RanUE will have no AmfUe in it if ranUe.HoldingAmfUe != nil && !ranUe.HoldingAmfUe.CmConnect(ranUe.Ran.AnType) { // If the UE is CM-IDLE, there is no RanUE in AmfUe, so here we attach new RanUe to AmfUe. gmm_common.AttachRanUeToAmfUeAndReleaseOldIfAny(ranUe.HoldingAmfUe, ranUe) ranUe.HoldingAmfUe = nil } else { // Assume we have an existing UE context in CM-CONNECTED state. (RanUe <-> AmfUe) // We will release it if the new UE context has a valid security context(Authenticated) in line 50. ranUe.AmfUe = amfSelf.NewAmfUe("") gmm_common.AttachRanUeToAmfUeAndReleaseOldIfAny(ranUe.AmfUe, ranUe) } } Dispatch(ranUe.AmfUe, ranUe.Ran.AnType, procedureCode, msg) } /* https://github.com/free5gc/amf/blob/v1.2.5/internal/nas/dispatch.go */ func Dispatch(ue *context.AmfUe, accessType models.AccessType, procedureCode int64, msg *nas.Message) error { // ...... return gmm.GmmFSM.SendEvent(ue.State[accessType], gmm.GmmMessageEvent, fsm.ArgsType{ gmm.ArgAmfUe: ue, gmm.ArgAccessType: accessType, gmm.ArgNASMessage: msg.GmmMessage, gmm.ArgProcedureCode: procedureCode, }, logger.GmmLog) }
现在,nas协议给GmmFSM激发了一个事件,事件类型是GmmMessageEvent
,而此时状态机的状态为ue.State[accessType]
,亦即Deregistered
,因为当前ue是个新初始化好的结构:
// https://github.com/free5gc/amf/blob/v1.2.5/internal/context/amf_ue.go#L267 func (ue *AmfUe) init() { ue.State = make(map[models.AccessType]*fsm.State) ue.State[models.AccessType__3_GPP_ACCESS] = fsm.NewState(Deregistered) ue.State[models.AccessType_NON_3_GPP_ACCESS] = fsm.NewState(Deregistered) }
由此,gmm状态机开始进行状态转移,进入初始注册流程,并执行相应回调函数Deregistered --[GmmMessageEvent]--> Deregistered
(实际上此时并没有状态转移)。回顾前文,此时回调函数DeRegistered
只会处理一次传入的事件,亦即HandleRegistrationRequest
并在此激发事件StartAuthEvent
。
// https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/sm.go#L17C1-L61C1 func DeRegistered(state *fsm.State, event fsm.EventType, args fsm.ArgsType) { switch event { case fsm.EntryEvent: amfUe.ClearRegistrationRequestData(accessType) case GmmMessageEvent: gmmMessage := args[ArgNASMessage].(*nas.GmmMessage) switch gmmMessage.GetMessageType() { case nas.MsgTypeRegistrationRequest: HandleRegistrationRequest(amfUe, accessType, procedureCode, gmmMessage.RegistrationRequest) GmmFSM.SendEvent(state, StartAuthEvent, fsm.ArgsType{...}) case StartAuthEvent: logger.GmmLog.Debugln(event) case fsm.ExitEvent: logger.GmmLog.Debugln(event) } } } // https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/handler.go#L378 // Handle cleartext IEs of Registration Request, which cleattext IEs defined in TS 24.501 4.4.6 func HandleRegistrationRequest(ue *context.AmfUe, anType models.AccessType, procedureCode int64, registrationRequest *nasMessage.RegistrationRequest, ) error { var guamiFromUeGuti models.Guami amfSelf := context.GetSelf() ue.SetOnGoing(anType, &context.OnGoing{ Procedure: context.OnGoingProcedureRegistration, }) ue.StopT3513() ue.StopT3565() // TS 24.501 8.2.6.21: if the UE is sending a REGISTRATION REQUEST message as an initial NAS message, // the UE has a valid 5G NAS security context and the UE needs to send non-cleartext IEs // TS 24.501 4.4.6: When the UE sends a REGISTRATION REQUEST or SERVICE REQUEST message that includes a NAS message // container IE, the UE shall set the security header type of the initial NAS message to "integrity protected" if registrationRequest.NASMessageContainer != nil && !ue.MacFailed { contents := registrationRequest.NASMessageContainer.GetNASMessageContainerContents() // TS 24.501 4.4.6: When the UE sends a REGISTRATION REQUEST or SERVICE REQUEST message that includes a NAS // message container IE, the UE shall set the security header type of the initial NAS message to // "integrity protected"; then the AMF shall decipher the value part of the NAS message container IE security.NASEncrypt(ue.CipheringAlg, ue.KnasEnc, ue.ULCount.Get(), security.Bearer3GPP, security.DirectionUplink, contents) m := nas.NewMessage() m.GmmMessageDecode(&contents) messageType := m.GmmMessage.GmmHeader.GetMessageType() // TS 24.501 4.4.6: The AMF shall consider the NAS message that is obtained from the NAS message container // IE as the initial NAS message that triggered the procedure registrationRequest = m.RegistrationRequest } // TS 33.501 6.4.6 step 3: if the initial NAS message was protected but did not pass the integrity check ue.RetransmissionOfInitialNASMsg = ue.MacFailed ue.RegistrationRequest = registrationRequest ue.RegistrationType5GS = registrationRequest.NgksiAndRegistrationType5GS.GetRegistrationType5GS() ue.RegistrationType5GS = nasMessage.RegistrationType5GSInitialRegistration mobileIdentity5GSContents := registrationRequest.MobileIdentity5GS.GetMobileIdentity5GSContents() ue.IdentityTypeUsedForRegistration = nasConvert.GetTypeOfIdentity(mobileIdentity5GSContents[0]) switch ue.IdentityTypeUsedForRegistration { // get type of identity case nasMessage.MobileIdentity5GSTypeNoIdentity: ue.GmmLog.Infof("MobileIdentity5GS: No Identity") case nasMessage.MobileIdentity5GSTypeSuci: suci, plmnId, err := nasConvert.SuciToStringWithError(mobileIdentity5GSContents) ue.Suci = suci ue.PlmnId = util.PlmnIdStringToModels(plmnId) case nasMessage.MobileIdentity5GSType5gGuti: guamiFromUeGutiTmp, guti, err := nasConvert.GutiToStringWithError(mobileIdentity5GSContents) guamiFromUeGuti = guamiFromUeGutiTmp ue.PlmnId = *guamiFromUeGuti.PlmnId servedGuami := amfSelf.ServedGuamiList[0] if reflect.DeepEqual(guamiFromUeGuti, servedGuami) { ue.ServingAmfChanged = false // refresh 5G-GUTI according to 6.12.3 Subscription temporary identifier, TS33.501 if ue.SecurityContextAvailable { context.GetSelf().FreeTmsi(int64(ue.Tmsi)) context.GetSelf().AllocateGutiToUe(ue) } } else { ue.ServingAmfChanged = true context.GetSelf().FreeTmsi(int64(ue.Tmsi)) ue.Guti = guti } case nasMessage.MobileIdentity5GSTypeImei: imei, err := nasConvert.PeiToStringWithError(mobileIdentity5GSContents) ue.Pei = imei case nasMessage.MobileIdentity5GSTypeImeisv: imeisv, err := nasConvert.PeiToStringWithError(mobileIdentity5GSContents) ue.Pei = imeisv } // NgKsi: TS 24.501 9.11.3.32 switch registrationRequest.NgksiAndRegistrationType5GS.GetTSC() { case nasMessage.TypeOfSecurityContextFlagNative: ue.NgKsi.Tsc = models.ScType_NATIVE case nasMessage.TypeOfSecurityContextFlagMapped: ue.NgKsi.Tsc = models.ScType_MAPPED } ue.NgKsi.Ksi = int32(registrationRequest.NgksiAndRegistrationType5GS.GetNasKeySetIdentifiler()) if ue.NgKsi.Tsc == models.ScType_NATIVE && ue.NgKsi.Ksi != 7 { } else { ue.NgKsi.Tsc = models.ScType_NATIVE ue.NgKsi.Ksi = 0 } // Copy UserLocation from ranUe if ue.RanUe[anType] != nil { ue.Location = ue.RanUe[anType].Location ue.Tai = ue.RanUe[anType].Tai } ue.UESecurityCapability = *registrationRequest.UESecurityCapability // TODO (TS 23.502 4.2.2.2 step 4): if UE's 5g-GUTI is included & serving AMF has changed // since last registration procedure, new AMF may invoke Namf_Communication_UEContextTransfer // to old AMF, including the complete registration request nas msg, to request UE's SUPI & UE Context if ue.ServingAmfChanged { contextTransferFromOldAmf(ue, anType, guamiFromUeGuti) } return nil }
查阅状态转移图,发现这次的转移路径是:Deregistered --[StartAuthEvent]--> Authentication
。此次的状态转移将要触发三次回调函数,分别处理Deregistered
的StartAuthEvent
、Deregistered
的ExitEvent
、以及Authentication
的EntryEvent
// https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/sm.go#L133C1-L239C1 func Authentication(state *fsm.State, event fsm.EventType, args fsm.ArgsType) { switch event { case fsm.EntryEvent: amfUe = args[ArgAmfUe].(*context.AmfUe) amfUe.GmmLog.Debugln("EntryEvent at GMM State[Authentication]") fallthrough case AuthRestartEvent: amfUe = args[ArgAmfUe].(*context.AmfUe) accessType := args[ArgAccessType].(models.AccessType) pass, err := AuthenticationProcedure(amfUe, accessType) if err != nil { GmmFSM.SendEvent(state, AuthErrorEvent, fsm.ArgsType{...}, logger.GmmLog) } if pass { GmmFSM.SendEvent(state, AuthSuccessEvent, fsm.ArgsType{...}, logger.GmmLog) } case GmmMessageEvent: switch gmmMessage.GetMessageType() { case nas.MsgTypeAuthenticationResponse: HandleAuthenticationResponse(amfUe, accessType, gmmMessage.AuthenticationResponse) case nas.MsgTypeAuthenticationFailure: HandleAuthenticationFailure(amfUe, accessType, gmmMessage.AuthenticationFailure) case nas.MsgTypeStatus5GMM: HandleStatus5GMM(amfUe, accessType, gmmMessage.Status5GMM) } } }
Authentication
的EntryEvent
打印完日志以后,回fallthrough到AuthRestartEvent
,调用AuthenticationProcedure
函数对设备鉴权,成功的话则进一步状态转移-- [AuthSuccessEvent] --> SecurityMode
,而鉴权失败的话则状态回退 -- [AuthErrorEvent] --> Deregistered
。设备鉴权会涉及到AUSF,回顾前文
整个鉴权过程包含了而至少两次相互通信,第一次通信是设备向网络提供自己的身份信息(就像输入用户名),网络端在数据库里找到设备的相关数据后返回一个aka-challenge;第二次通信是设备向网络发送自己对aka-challenge解出来的RES,网络端判断这个RES是否正确,如果正确则返回鉴权成功信息,否则返回鉴权失败信息。
所以AuthenticationProcedure
会先向AUSFSendUEAuthenticationAuthenticateRequest
提供用户设备的身份信息,然后AUSF会向UDM要一个aka-challenge(设置在ue.AuthenticationCtx
中),然后向用户设备SendAuthenticationRequest
,并开始计时,要求UE在限定时间内返回对aka-challenge的解答。
// https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/handler.go#L1627 func AuthenticationProcedure(ue *context.AmfUe, accessType models.AccessType) (bool, error) { // Check whether UE has SUCI and SUPI, if not then Request UE's SUCI by sending identity request amfSelf := context.GetSelf() response, problemDetails, err := consumer.GetConsumer().SendUEAuthenticationAuthenticateRequest(ue, nil) if err != nil { gmm_message.SendRegistrationReject(ue.RanUe[accessType], nasMessage.Cause5GMMCongestion, "") return false, err } ue.AuthenticationCtx = response ue.ABBA = []uint8{0x00, 0x00} // set ABBA value as described at TS 33.501 Annex A.7.1 gmm_message.SendAuthenticationRequest(ue.RanUe[accessType]) return false, nil } // https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/message/send.go#L106 func SendAuthenticationRequest(ue *context.RanUe) { nasMsg, err := BuildAuthenticationRequest(amfUe, ran.AnType) ngap_message.SendDownlinkNasTransport(ue, nasMsg, nil) if context.GetSelf().T3560Cfg.Enable { cfg := context.GetSelf().T3560Cfg amfUe.T3560 = context.NewTimer(cfg.ExpireTime, cfg.MaxRetryTimes, func(expireTimes int32) { amfUe.GmmLog.Warnf("T3560 expires, retransmit Authentication Request (retry: %d)", expireTimes) ngap_message.SendDownlinkNasTransport(ue, nasMsg, nil) }, func() { amfUe.T3560 = nil gmm_common.RemoveAmfUe(amfUe, false) }) } }
进行到这一步,gmm状态机处于Authentication
状态,并等待着来自UE/RAN的的第二次消息。当UE解除aka-challenge后,会把结果在此发送给AMF。此时ngap服务器再次收到消息,做相同的识别和分发处理后,把消息交给nas,然后nas激发事件给gmm状态机,亦即Authentication --[GmmMessageEvent]--> Authentication
。如果一切顺利,UE给AMF发送的消息应该是应当是nas.MsgTypeAuthenticationResponse
,对应的处理函数是HandleAuthenticationResponse
// TS 24.501 5.4.1 func HandleAuthenticationResponse(ue *context.AmfUe, accessType models.AccessType, authenticationResponse *nasMessage.AuthenticationResponse, ) error { ue.StopT3560() switch ue.AuthenticationCtx.AuthType { case models.AuthType__5_G_AKA: var av5gAka models.Av5gAka mapstructure.Decode(ue.AuthenticationCtx.Var5gAuthData, &av5gAka) resStar := authenticationResponse.AuthenticationResponseParameter.GetRES() // Calculate HRES* (TS 33.501 Annex A.5) ...... hResStar := hex.EncodeToString(hResStarBytes[16:]) response, problemDetails, err := consumer.GetConsumer().SendAuth5gAkaConfirmRequest( ue, hex.EncodeToString(resStar[:])) switch response.AuthResult { case models.AuthResult_SUCCESS: ue.UnauthenticatedSupi = false ue.Kseaf = response.Kseaf ue.Supi = response.Supi ue.DerivateKamf() return GmmFSM.SendEvent(ue.State[accessType], AuthSuccessEvent, fsm.ArgsType{...}, logger.GmmLog) case models.AuthResult_FAILURE: gmm_message.SendAuthenticationReject(ue.RanUe[accessType], "") return GmmFSM.SendEvent(ue.State[accessType], AuthFailEvent, fsm.ArgsType{...}, logger.GmmLog) } case models.AuthType_EAP_AKA_PRIME: // ...... } return nil }
假如一切顺利,进入SecurityMode
状态,检查UE和网络之间的通信安全状态。
// https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/sm.go#L240C1-L321C1 func SecurityMode(state *fsm.State, event fsm.EventType, args fsm.ArgsType) { switch event { case fsm.EntryEvent: if amfUe.SecurityContextIsValid() { GmmFSM.SendEvent(state, SecurityModeSuccessEvent, fsm.ArgsType{...}, logger.GmmLog) } else { if err := amfUe.SelectSecurityAlg(amfSelf.SecurityAlgorithm.IntegrityOrder, amfSelf.SecurityAlgorithm.CipheringOrder); err != nil { gmm_message.SendRegistrationReject(amfUe.RanUe[accessType], nasMessage.Cause5GMMUESecurityCapabilitiesMismatch, "") err = GmmFSM.SendEvent(state, SecurityModeFailEvent, fsm.ArgsType{...}, logger.GmmLog) return } // Generate KnasEnc, KnasInt amfUe.DerivateAlgKey() gmm_message.SendSecurityModeCommand(amfUe.RanUe[accessType], accessType, eapSuccess, eapMessage) } case GmmMessageEvent: switch gmmMessage.GetMessageType() { case nas.MsgTypeSecurityModeComplete: HandleSecurityModeComplete(amfUe, accessType, procedureCode, gmmMessage.SecurityModeComplete) case nas.MsgTypeSecurityModeReject: HandleSecurityModeReject(amfUe, accessType, gmmMessage.SecurityModeReject) GmmFSM.SendEvent(state, SecurityModeFailEvent, fsm.ArgsType{...}, logger.GmmLog) case nas.MsgTypeStatus5GMM: HandleStatus5GMM(amfUe, accessType, gmmMessage.Status5GMM) } // more cases: logging } }
如果UE和网络之间的通信不安全的话,则要选择一个安全算法给信道加密,若已经足够安全,则激发事件,继续状态转移SecurityMode --[SecurityModeSuccessEvent]--> ContextSetup
。ContextSetup
回调函数的EntryEvent
中,状态机判断当前处理的nas消息是什么类型。我们现在研究的是初始注册流程,所以是RegistrationType5GSInitialRegistration
类型,对应的处理函数是HandleInitialRegistration
。
// https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/sm.go#L322C1-L437C1 func ContextSetup(state *fsm.State, event fsm.EventType, args fsm.ArgsType) { switch event { case fsm.EntryEvent: switch message := gmmMessage.(type) { case *nasMessage.RegistrationRequest: amfUe.RegistrationRequest = message switch amfUe.RegistrationType5GS { case nasMessage.RegistrationType5GSInitialRegistration: HandleInitialRegistration(amfUe, accessType) case nasMessage.RegistrationType5GSMobilityRegistrationUpdating: fallthrough case nasMessage.RegistrationType5GSPeriodicRegistrationUpdating: HandleMobilityAndPeriodicRegistrationUpdating(amfUe, accessType) } case *nasMessage.ServiceRequest: HandleServiceRequest(amfUe, accessType, message) } case GmmMessageEvent: switch gmmMessage.GetMessageType() { case nas.MsgTypeRegistrationComplete: HandleRegistrationComplete(amfUe, accessType, gmmMessage.RegistrationComplete) case nas.MsgTypeStatus5GMM: HandleStatus5GMM(amfUe, accessType, gmmMessage.Status5GMM) } case ContextSetupFailEvent: consumer.GetConsumer().UeCmDeregistration(amfUe, accessType) // case ContextSetupSuccessEvent, case fsm.ExitEvent, default: logging ... } } // https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/handler.go#L620 func HandleInitialRegistration(ue *context.AmfUe, anType models.AccessType) error { amfSelf := context.GetSelf() ue.UpdateSecurityContext(anType) getSubscribedNssai(ue) handleRequestedNssai(ue, anType) storeLastVisitedRegisteredTAI(ue, ue.RegistrationRequest.LastVisitedRegisteredTAI) negotiateDRXParameters(ue, ue.RegistrationRequest.RequestedDRXParameters) if ue.ServingAmfChanged { // If the AMF has changed the new AMF notifies the old AMF that the registration of the UE in the new AMF is completed req := models.UeRegStatusUpdateReqData{TransferStatus: models.UeContextTransferStatus_TRANSFERRED,} regStatusTransferComplete, problemDetails, err := consumer.GetConsumer().RegistrationStatusUpdate(ue, req) } consumer.GetConsumer().AMPolicyControlCreate(ue, anType) amfSelf.AllocateRegistrationArea(ue, anType) assignLadnInfo(ue, anType) amfSelf.AddAmfUeToUePool(ue, ue.Supi) gmm_message.SendRegistrationAccept(ue, anType, nil, nil, nil, nil, nil) return nil }
HandleInitialRegistration
函数为设备做了些准备工作,包括更新安全策略、请NSSF选择网络切片、更新AMF服务状态、向PCF获取相应的amPolicy等等。我们也终于看到amfSelf.AddAmfUeToUePool(ue, ue.Supi)
,也就是在AMFContext
中创建了该设备的AmfUe
结构,这一步意味着设备已经成功在网络中注册。剩下来的收尾工作也很重要,比如向UE发送消息,告知其注册成功。UE收到来自核心网的注册成功通知后,回复RegistrationComplete
,确认UE侧的注册流程已完成。ngap服务器收到了来自UE消息后再次识别和分发给nas协议,nas再给gmm状态机激发GmmMessageEvent
。此时状态机还处于ContextSetup
状态,所以这次的状态转移还是ContextSetup --[GmmMessageEvent]--> ContextSetup
,hanlde来自UE的gmmMessageHandleRegistrationComplete
,主要跟向SMF更新smPolicy,同步设备和网络的时间信息NITZ(Network Identity and Time Zone),最后激发一个ContextSetupSuccessEvent
事件
func HandleRegistrationComplete(ue *context.AmfUe, accessType models.AccessType, registrationComplete *nasMessage.RegistrationComplete, ) error { ue.StopT3550() // Release existed old SmContext when Initial Registration completed if ue.RegistrationType5GS == nasMessage.RegistrationType5GSInitialRegistration { ue.SmContextList.Range(func(key, value interface{}) bool { smContext := value.(*context.SmContext) if smContext.AccessType() == accessType { problemDetail, err := consumer.GetConsumer().SendReleaseSmContextRequest(ue, smContext, nil, "", nil) } return true }) } // Send NITZ information to UE configurationUpdateCommandFlags := &context.ConfigurationUpdateCommandFlags{NeedNITZ: true,} gmm_message.SendConfigurationUpdateCommand(ue, accessType, configurationUpdateCommandFlags) if ue.RegistrationRequest.UplinkDataStatus == nil && ue.RegistrationRequest.GetFOR() == nasMessage.FollowOnRequestNoPending { ngap_message.SendUEContextReleaseCommand(ue.RanUe[accessType], context.UeContextN2NormalRelease, ngapType.CausePresentNas, ngapType.CauseNasPresentNormalRelease) } return GmmFSM.SendEvent(ue.State[accessType], ContextSetupSuccessEvent, fsm.ArgsType{...}, logger.GmmLog) }
现在gmm状态机将进行初始注册流程最后的状态转移ContextSetup --[ContextSetupSuccessEvent]--> Registered
,清理掉ue
结构里临时数据:
// https://github.com/free5gc/amf/blob/v1.2.5/internal/gmm/sm.go#L62C1-L131C2 func Registered(state *fsm.State, event fsm.EventType, args fsm.ArgsType) { switch event { case fsm.EntryEvent: amfUe.ClearRegistrationRequestData(accessType) case GmmMessageEvent: // case StartAuthEvent, InitDeregistrationEvent, fsm.ExitEvent, default: logging ... } // ...... } // https://github.com/free5gc/amf/blob/v1.2.5/internal/context/amf_ue.go#L628 func (ue *AmfUe) ClearRegistrationRequestData(accessType models.AccessType) { ue.RegistrationRequest = nil ue.RegistrationType5GS = 0 ue.IdentityTypeUsedForRegistration = 0 ue.AuthFailureCauseSynchFailureTimes = 0 ue.IdentityRequestSendTimes = 0 ue.ServingAmfChanged = false ue.RegistrationAcceptForNon3GPPAccess = nil if ranUe := ue.RanUe[accessType]; ranUe != nil { ranUe.UeContextRequest = factory.AmfConfig.Configuration.DefaultUECtxReq } ue.RetransmissionOfInitialNASMsg = false if onGoing := ue.onGoing[accessType]; onGoing != nil { onGoing.Procedure = OnGoingProcedureNothing } }
至此,用户设备完成其初始注册流程。
因为只有AMF能与UE和RAN通信,所以核心网中其他NF想发消息给UE和RAN,必须以AMF为中继。这就引出了Namf_Communication
中的其他几个与N1/N2消息相关的服务:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!