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消息相关的服务: