Free5GC源码研究(15) - AMF研究(四)

前文再研究AMF的sbi接口后,又研究了其nas和ngap接口,本文折返回去继续研究sbi接口剩下的几个功能

AMF的sbi接口模块,虽然看起来代码和函数很多,但其基本功能也就两个,一是提供对UE Context的操作,这功能还基本上旨在新旧AMF之间切换时才会用到;第二个功能是作为UE/RAN与核心网其他NF之间的通信中继,毕竟只有核心网控制面中只有AMF能够与UE/RAN之间通信。N1N2MessageTransferProcedure就是这个功能的核心函数:

img

概括来说,这个函数首先会把消息目标的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
	})
}
posted @ 2024-12-11 17:26  zrq96  阅读(10)  评论(0编辑  收藏  举报