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