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。此次的状态转移将要触发三次回调函数,分别处理DeregisteredStartAuthEventDeregisteredExitEvent、以及AuthenticationEntryEvent

// 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)
		}
    }
}

AuthenticationEntryEvent打印完日志以后,回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]--> ContextSetupContextSetup回调函数的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消息相关的服务:

posted @ 2024-12-09 22:18  zrq96  阅读(14)  评论(0编辑  收藏  举报