Free5GC源码研究(15) - AMF研究(四)
前文再研究AMF的sbi接口后,又研究了其nas和ngap接口,本文折返回去继续研究sbi接口剩下的几个功能
AMF的sbi接口模块,虽然看起来代码和函数很多,但其基本功能也就两个,一是提供对UE Context的操作,这功能还基本上旨在新旧AMF之间切换时才会用到;第二个功能是作为UE/RAN与核心网其他NF之间的通信中继,毕竟只有核心网控制面中只有AMF能够与UE/RAN之间通信。N1N2MessageTransferProcedure
就是这个功能的核心函数:
概括来说,这个函数首先会把消息目标的UeContext
找出来,把要发送的N1、N2消息打包做好准备。
// https://github.com/free5gc/amf/blob/v1.2.5/internal/sbi/processor/n1n2message.go#L69 func (p *Processor) N1N2MessageTransferProcedure(ueContextID string, reqUri string, n1n2MessageTransferRequest models.N1N2MessageTransferRequest) ( n1n2MessageTransferRspData *models.N1N2MessageTransferRspData, locationHeader string, problemDetails *models.ProblemDetails, transferErr *models.N1N2MessageTransferError, ) { amfSelf := context.GetSelf() ue, ok = amfSelf.AmfUeFindByUeContextID(ueContextID) if requestData.N1MessageContainer != nil { switch requestData.N1MessageContainer.N1MessageClass { case models.N1MessageClass_SM: n1MsgType = nasMessage.PayloadContainerTypeN1SMInfo smContext, ok = ue.SmContextFindByPDUSessionID(requestData.PduSessionId) anType = smContext.AccessType() case models.N1MessageClass_SMS: n1MsgType = nasMessage.PayloadContainerTypeSMS case models.N1MessageClass_LPP: n1MsgType = nasMessage.PayloadContainerTypeLPP case models.N1MessageClass_UPDP: n1MsgType = nasMessage.PayloadContainerTypeUEPolicy } } if requestData.N2InfoContainer != nil { switch requestData.N2InfoContainer.N2InformationClass { case models.N2InformationClass_SM: if smContext == nil { smContext, ok = ue.SmContextFindByPDUSessionID(requestData.PduSessionId) anType = smContext.AccessType() } } }
如果不巧这时的UE正处于注册或者移动的流程中,消息旧不能发送需要下次再来。
onGoing := ue.OnGoing(anType) // 4xx response cases // TODO: Error Status 307, 403 in TS29.518 Table 6.1.3.5.3.1-3 switch onGoing.Procedure { case context.OnGoingProcedurePaging: ue.StopT3513() callback.SendN1N2TransferFailureNotification(ue, models.N1N2MessageTransferCause_UE_NOT_RESPONDING) case context.OnGoingProcedureRegistration: transferErr = new(models.N1N2MessageTransferError) // "TEMPORARY_REJECT_REGISTRATION_ONGOING" return nil, "", nil, transferErr case context.OnGoingProcedureN2Handover: transferErr = new(models.N1N2MessageTransferError) // "TEMPORARY_REJECT_HANDOVER_ONGOING" return nil, "", nil, transferErr }
如果此时UE与RAN之间已经建立了活跃连接,AMF知道UE具体连接在哪个基站上,那就可以用直接向UE发送下行数据的方式传输N1消息ngap_message.SendDownlinkNasTransport
。对于向基站发送的N2消息,目前free5gc只支持替SMF发送绘画管理相关数据。
// UE is CM-Connected if ue.CmConnect(anType) { if n1Msg != nil { nasPdu, err = gmm_message. BuildDLNASTransport(ue, anType, n1MsgType, n1Msg, uint8(requestData.PduSessionId), nil, nil, 0) if n2Info == nil { ue.ProducerLog.Debug("Forward N1 Message to UE") ngap_message.SendDownlinkNasTransport(ue.RanUe[anType], nasPdu, nil) n1n2MessageTransferRspData = new(models.N1N2MessageTransferRspData) n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_N1_N2_TRANSFER_INITIATED return n1n2MessageTransferRspData, "", nil, nil } } // TODO: only support transfer N2 SM information now if n2Info != nil { smInfo := requestData.N2InfoContainer.SmInfo switch smInfo.N2InfoContent.NgapIeType { case models.NgapIeType_PDU_RES_SETUP_REQ: ue.ProducerLog.Debugln("AMF Transfer NGAP PDU Session Resource Setup Request from SMF") if ue.RanUe[anType].InitialContextSetup { list := ngapType.PDUSessionResourceSetupListSUReq{} ngap_message.AppendPDUSessionResourceSetupListSUReq(&list, smInfo.PduSessionId, *smInfo.SNssai, nasPdu, n2Info) ngap_message.SendPDUSessionResourceSetupRequest(ue.RanUe[anType], nil, &list) } else { list := ngapType.PDUSessionResourceSetupListCxtReq{} ngap_message.AppendPDUSessionResourceSetupListCxtReq(&list, smInfo.PduSessionId, *smInfo.SNssai, nasPdu, n2Info) ngap_message.SendInitialContextSetupRequest(ue, anType, nil, &list, nil, nil, nil) ue.RanUe[anType].InitialContextSetup = true } n1n2MessageTransferRspData = new(models.N1N2MessageTransferRspData) n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_N1_N2_TRANSFER_INITIATED return n1n2MessageTransferRspData, "", nil, nil case models.NgapIeType_PDU_RES_MOD_REQ: ue.ProducerLog.Debugln("AMF Transfer NGAP PDU Session Resource Modify Request from SMF") list := ngapType.PDUSessionResourceModifyListModReq{} ngap_message.AppendPDUSessionResourceModifyListModReq(&list, smInfo.PduSessionId, nasPdu, n2Info) ngap_message.SendPDUSessionResourceModifyRequest(ue.RanUe[anType], list) n1n2MessageTransferRspData = new(models.N1N2MessageTransferRspData) n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_N1_N2_TRANSFER_INITIATED return n1n2MessageTransferRspData, "", nil, nil case models.NgapIeType_PDU_RES_REL_CMD: ue.ProducerLog.Debugln("AMF Transfer NGAP PDU Session Resource Release Command from SMF") list := ngapType.PDUSessionResourceToReleaseListRelCmd{} ngap_message.AppendPDUSessionResourceToReleaseListRelCmd(&list, smInfo.PduSessionId, n2Info) ngap_message.SendPDUSessionResourceReleaseCommand(ue.RanUe[anType], nasPdu, list) n1n2MessageTransferRspData = new(models.N1N2MessageTransferRspData) n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_N1_N2_TRANSFER_INITIATED return n1n2MessageTransferRspData, "", nil, nil default: ue.ProducerLog.Errorf("NGAP IE Type[%s] is not supported for SmInfo", smInfo.N2InfoContent.NgapIeType) problemDetails = &models.ProblemDetails{ Status: http.StatusForbidden, Cause: "UNSPECIFIED", } return nil, "", problemDetails, nil } } }
而如果UE并没有与RAN之间没有活跃的连接,AMF也只知道UE在哪个区域(Tracking Area),但不知道具体连接哪个基站,则需要先通过寻呼(Paging)唤醒UE,等UE回应后才能发送实际消息
// UE is CM-IDLE // 409: transfer a N2 PDU Session Resource Release Command to a 5G-AN and if the UE is in CM-IDLE if n2Info != nil && requestData.N2InfoContainer.SmInfo.N2InfoContent.NgapIeType == models.NgapIeType_PDU_RES_REL_CMD { transferErr = new(models.N1N2MessageTransferError) transferErr.Error = &models.ProblemDetails{ Status: http.StatusConflict, Cause: "UE_IN_CM_IDLE_STATE", } return nil, "", nil, transferErr } // 504: the UE in MICO mode or the UE is only registered over Non-3GPP access and its state is CM-IDLE if !ue.State[models.AccessType__3_GPP_ACCESS].Is(context.Registered) { transferErr = new(models.N1N2MessageTransferError) transferErr.Error = &models.ProblemDetails{ Status: http.StatusGatewayTimeout, Cause: "UE_NOT_REACHABLE", } return nil, "", nil, transferErr } n1n2MessageTransferRspData = new(models.N1N2MessageTransferRspData) var pagingPriority *ngapType.PagingPriority var n1n2MessageID int64 n1n2MessageIDTmp, err := ue.N1N2MessageIDGenerator.Allocate() n1n2MessageID = n1n2MessageIDTmp locationHeader = context.GetSelf().GetIPv4Uri() + reqUri + "/" + strconv.Itoa(int(n1n2MessageID)) // Case A (UE is CM-IDLE in 3GPP access and the associated access type is 3GPP access) // in subclause 5.2.2.3.1.2 of TS29518 if anType == models.AccessType__3_GPP_ACCESS { if requestData.SkipInd && n2Info == nil { n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_N1_MSG_NOT_TRANSFERRED } else { n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_ATTEMPTING_TO_REACH_UE message := context.N1N2Message{ Request: n1n2MessageTransferRequest, Status: n1n2MessageTransferRspData.Cause, ResourceUri: locationHeader, } ue.N1N2Message = &message ue.SetOnGoing(anType, &context.OnGoing{ Procedure: context.OnGoingProcedurePaging, Ppi: requestData.Ppi, }) if onGoing.Ppi != 0 { pagingPriority = new(ngapType.PagingPriority) pagingPriority.Value = aper.Enumerated(onGoing.Ppi) } pkg, err := ngap_message.BuildPaging(ue, pagingPriority, false) if err != nil { logger.NgapLog.Errorf("Build Paging failed : %s", err.Error()) return n1n2MessageTransferRspData, locationHeader, problemDetails, transferErr } ngap_message.SendPaging(ue, pkg) } // TODO: WAITING_FOR_ASYNCHRONOUS_TRANSFER return n1n2MessageTransferRspData, locationHeader, nil, nil } else { // Case B (UE is CM-IDLE in Non-3GPP access but CM-CONNECTED in 3GPP access and the associated // access type is Non-3GPP access)in subclause 5.2.2.3.1.2 of TS29518 if ue.CmConnect(models.AccessType__3_GPP_ACCESS) { if n2Info == nil { n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_N1_N2_TRANSFER_INITIATED gmm_message.SendDLNASTransport(ue.RanUe[models.AccessType__3_GPP_ACCESS], nasMessage.PayloadContainerTypeN1SMInfo, n1Msg, requestData.PduSessionId, 0, nil, 0) } else { n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_ATTEMPTING_TO_REACH_UE message := context.N1N2Message{ Request: n1n2MessageTransferRequest, Status: n1n2MessageTransferRspData.Cause, ResourceUri: locationHeader, } ue.N1N2Message = &message nasMsg, err := gmm_message.BuildNotification(ue, models.AccessType_NON_3_GPP_ACCESS) if err != nil { logger.GmmLog.Errorf("Build Notification failed : %s", err.Error()) return n1n2MessageTransferRspData, locationHeader, problemDetails, transferErr } gmm_message.SendNotification(ue.RanUe[models.AccessType__3_GPP_ACCESS], nasMsg) } return n1n2MessageTransferRspData, locationHeader, nil, nil } else { // Case C ( UE is CM-IDLE in both Non-3GPP access and 3GPP access and the associated access ype is Non-3GPP access) // in subclause 5.2.2.3.1.2 of TS29518 n1n2MessageTransferRspData.Cause = models.N1N2MessageTransferCause_ATTEMPTING_TO_REACH_UE message := context.N1N2Message{ Request: n1n2MessageTransferRequest, Status: n1n2MessageTransferRspData.Cause, ResourceUri: locationHeader, } ue.N1N2Message = &message ue.SetOnGoing(anType, &context.OnGoing{ Procedure: context.OnGoingProcedurePaging, Ppi: requestData.Ppi, }) if onGoing.Ppi != 0 { pagingPriority = new(ngapType.PagingPriority) pagingPriority.Value = aper.Enumerated(onGoing.Ppi) } pkg, err := ngap_message.BuildPaging(ue, pagingPriority, true) if err != nil { logger.NgapLog.Errorf("Build Paging failed : %s", err.Error()) } ngap_message.SendPaging(ue, pkg) return n1n2MessageTransferRspData, locationHeader, nil, nil } } }
剩下的几个N1N2Message**
函数都是作为上面N1N2MessageTransferProcedure
函数的辅助函数,比如告知目前整个消息转发过程的状态,是刚开始呢,还是在等待发送,还是没有回应之类的。
// https://github.com/free5gc/amf/blob/v1.2.5/internal/sbi/processor/n1n2message.go#L394C1-L424C2 func (p *Processor) N1N2MessageTransferStatusProcedure(ueContextID string, reqUri string) (models.N1N2MessageTransferCause, *models.ProblemDetails, ) { amfSelf := context.GetSelf() ue, ok := amfSelf.AmfUeFindByUeContextID(ueContextID) return n1n2Message.Status, nil }
也会为消息转发相关事件提供订阅机制:
// https://github.com/free5gc/amf/blob/v1.2.5/internal/sbi/processor/n1n2message.go#L441 func (p *Processor) N1N2MessageSubscribeProcedure(ueContextID string, ueN1N2InfoSubscriptionCreateData models.UeN1N2InfoSubscriptionCreateData) ( *models.UeN1N2InfoSubscriptionCreatedData, *models.ProblemDetails, ) { amfSelf := context.GetSelf() ue, ok := amfSelf.AmfUeFindByUeContextID(ueContextID) ueN1N2InfoSubscriptionCreatedData := new(models.UeN1N2InfoSubscriptionCreatedData) newSubscriptionID, err := ue.N1N2MessageSubscribeIDGenerator.Allocate() ueN1N2InfoSubscriptionCreatedData.N1n2NotifySubscriptionId = strconv.Itoa(int(newSubscriptionID)) ue.N1N2MessageSubscription.Store(newSubscriptionID, ueN1N2InfoSubscriptionCreateData) return ueN1N2InfoSubscriptionCreatedData, nil } // https://github.com/free5gc/amf/blob/v1.2.5/internal/sbi/processor/n1n2message.go#L490C1-L508C2 func (p *Processor) N1N2MessageUnSubscribeProcedure(ueContextID string, subscriptionID string) *models.ProblemDetails { amfSelf := context.GetSelf() ue, ok := amfSelf.AmfUeFindByUeContextID(ueContextID) ue.N1N2MessageSubscription.Delete(subscriptionID) return nil }
当相应事件发生时通知订阅者:
func SendN1MessageNotify(ue *amf_context.AmfUe, n1class models.N1MessageClass, n1Msg []byte, registerContext *models.RegistrationContextContainer, ) { ue.N1N2MessageSubscription.Range(func(key, value interface{}) bool { subscriptionID := key.(int64) subscription := value.(models.UeN1N2InfoSubscriptionCreateData) if subscription.N1NotifyCallbackUri != "" && subscription.N1MessageClass == n1class { configuration := Namf_Communication.NewConfiguration() client := Namf_Communication.NewAPIClient(configuration) n1MessageNotify := models.N1MessageNotify{ JsonData: &models.N1MessageNotification{ N1NotifySubscriptionId: strconv.Itoa(int(subscriptionID)), N1MessageContainer: &models.N1MessageContainer{ N1MessageClass: subscription.N1MessageClass, N1MessageContent: &models.RefToBinaryData{ ContentId: "n1Msg", }, }, RegistrationCtxtContainer: registerContext, }, BinaryDataN1Message: n1Msg, } httpResponse, err := client.N1MessageNotifyCallbackDocumentApiServiceCallbackDocumentApi. N1MessageNotify(context.Background(), subscription.N1NotifyCallbackUri, n1MessageNotify) } return true }) }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!