free5gc AMF 源码分析
1. AMF 程序初始化以及启动
1.1 启动 httpcallback 服务
httpcallback.AddService(router),实现在 afm/httpcallback 中,URL 以及 handler 如下所示:
Name |
Pattern |
HandlerFunc |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1.2 根据配置文件中的服务列表启动服务
serviceNameList: - namf-comm - namf-evts - namf-mt - namf-loc - namf-oam
for _, serviceName := range factory.AmfConfig.Configuration.ServiceNameList {
switch models.ServiceName(serviceName) {
case models.ServiceName_NAMF_COMM:
communication.AddService(router)
case models.ServiceName_NAMF_EVTS:
eventexposure.AddService(router)
case models.ServiceName_NAMF_MT:
mt.AddService(router)
case models.ServiceName_NAMF_LOC:
location.AddService(router)
}
}
1.3 初始化 amf 上下文
通过配置文件,较简单,amf 监听端口为 29518
self := context.AMF_Self()
util.InitAmfContext(self)
addr := fmt.Sprintf("%s:%d", self.HttpIPv4Address, self.HttpIpv4Port)
1.4 根据 ngap IP 列表建立 SCTP 服务
TCP是以字节为单位传输的,SCTP是以数据块为单位传输的
TCP通常是单路径传输,SCTP可以多路径传输
for _, ngapAddr := range self.NgapIpList {
sctpListener = sctp.Server(ngapAddr)
}
1.5 核心处理流程
实现在 amf/handler/hander.go
go handler.Handle()
1.6 注册到 NRF
amf id 组成 regionId: 16bits, setId: 10bits, ptrId: 6bits <AMF Identifier> = <AMF Region ID><AMF Set ID><AMF Pointer>
调用 NRF Nnrf_NFManagement,注册
// Register to NRF
profile, err := consumer.BuildNFInstance(self)
if err != nil {
initLog.Error("Build AMF Profile Error")
}
_, self.NfId, _ = consumer.SendRegisterNFInstance(self.NrfUri, self.NfId, profile)
2. RAN 发起的 NGSetupRequest 消息
NG Setup 流程用来交换 NG-RAN 节点和 AMF 在 NG-C 接口上正确互操作所需的应用程序数据,该程序应是 TNL 关联开始运行后触发的第一个 NGAP 程序。该过程使用 非UE 相关的信令。
2.1 NGAP 结构体
type NGAPPDU struct {
Present int
InitiatingMessage *InitiatingMessage
SuccessfulOutcome *SuccessfulOutcome
UnsuccessfulOutcome *UnsuccessfulOutcome
}
Present 设置为 NGAPPDUPresentInitiatingMessage
2.2 InitiatingMessage 结构体
type InitiatingMessage struct {
ProcedureCode ProcedureCode
Criticality Criticality
Value InitiatingMessageValue `aper:"openType,referenceFieldName:ProcedureCode"`
}
initiatingMessage ProcedureCode 设置为 ProcedureCodeNGSetup InitiatingMessagePresentNGSetupRequest
当使用 NG-RAN 时,N2 参数包括所选的 PLMN ID、位置信息和与 Ue 所在小区相关的身份、Ue 上下文请求,该请求指明需要在 NG-RAN 中设置一个包含安全信息的 Ue 上下文 。也包括建立的原因,
如果可用的话,才提供请求的 NSSAI 映射
如果 UE 注册类型指明是定期注册更新,则省略 4 - 19 步骤
如果 UE 包含首选的网络行为
2.3 AMF 接收 NGSetupRequest 消息
根据 NGAPPDUPresentInitiatingMessage 和 ProcedureCodeNGSetup 定位到 HandleNGSetupRequest
func HandleNGSetupRequest(ran *context.AmfRan, message *ngapType.NGAPPDU) {
var globalRANNodeID *ngapType.GlobalRANNodeID
var rANNodeName *ngapType.RANNodeName
var supportedTAList *ngapType.SupportedTAList
var pagingDRX *ngapType.PagingDRX
var cause ngapType.Cause
主要是验证信息,如果验证通过则 SendNGSetupResponse,失败则调用 SendNGSetupFailure
如果成功则发送 NGAPPDUPresentSuccessfulOutcome,类型为 SuccessfulOutcomePresentNGSetupResponse,包括 IE:AMFName,ServedGUAMIList,relativeAMFCapacity,pLMNSupportList
4. PDU 会话建立流程
4.1 UE 发起的 PDU 会话建立请求 (RAN -> AMF)
GetPduSessionEstablishmentRequest 创建 NAS-PDU,填充 NAS 消息中的 SM 消息,设置 SM 头消息类型为 MsgTypePDUSessionEstablishmentRequest,SM 头格式为:
type GsmHeader struct {
Octet [4]uint8
}
PDUSessionEstablishmentRequest 结构体
type PDUSessionEstablishmentRequest struct {
nasType.ExtendedProtocolDiscriminator
nasType.PDUSessionID
nasType.PTI
nasType.PDUSESSIONESTABLISHMENTREQUESTMessageIdentity
nasType.IntegrityProtectionMaximumDataRate
*nasType.PDUSessionType
*nasType.SSCMode
*nasType.Capability5GSM
*nasType.MaximumNumberOfSupportedPacketFilters
*nasType.AlwaysonPDUSessionRequested
*nasType.SMPDUDNRequestContainer
*nasType.ExtendedProtocolConfigurationOptions
}
NAS 消息中的 MM 消息头部类型为 MsgTypeULNASTransport,设置的内容 ULNASTransport,类型为 MsgTypeULNASTransport,PayloadContainerTypeN1SMInfo
添加 DNN,SNSSAI,PayloadContainer(上面创建的 NAS 携带 SM 消息)
type ULNASTransport struct {
nasType.ExtendedProtocolDiscriminator
nasType.SpareHalfOctetAndSecurityHeaderType
nasType.ULNASTRANSPORTMessageIdentity
nasType.SpareHalfOctetAndPayloadContainerType
nasType.PayloadContainer
*nasType.PduSessionID2Value
*nasType.OldPDUSessionID
*nasType.RequestType
*nasType.SNSSAI
*nasType.DNN
*nasType.AdditionalInformation
}
4.1.1 BuildUplinkNasTransport 函数创建 NGAP 消息
NGAP 消息了类型为 NGAPPDUPresentInitiatingMessage,数据结构体为 InitiatingMessage,ProcedureCode 设置为 ProcedureCodeUplinkNASTransport,携带 IE 有
// AMF UE NGAP ID
ProtocolIEIDAMFUENGAPID UplinkNASTransportIEsPresentAMFUENGAPID
// RAN UE NGAP ID
ProtocolIEIDRANUENGAPID UplinkNASTransportIEsPresentRANUENGAPID
// NAS-PDU
ProtocolIEIDNASPDU UplinkNASTransportIEsPresentNASPDU
// User Location Information
ProtocolIEIDUserLocationInformation UplinkNASTransportIEsPresentUserLocationInformation
UPLINK NAS TRANSPORT
IE/Group Name |
Presence |
IE type and reference |
Criticality |
Assigned Criticality |
Message Type |
M |
9.3.1.1 |
YES |
ignore |
AMF UE NGAP ID |
M |
9.3.3.1 |
YES |
reject |
RAN UE NGAP ID |
M |
9.3.3.2 |
YES |
reject |
NAS-PDU |
M |
9.3.3.4 |
YES |
reject |
User Location Information |
M |
9.3.1.16 |
YES |
ignore |
4.2 AMF 收到 RAN 发送的 NGAP UPLINK NAS TRANSPORT 消息
根据 NGAP 消息类型为 NGAPPDUPresentInitiatingMessage,ProcedureCode 设置为 ProcedureCodeUplinkNASTransport,InitiatingMessagePresentUplinkNASTransport,定位到 HandleUplinkNasTransport
提取出 IE 包括 AMFUENGAPID RANUENGAPID NASPDU UserLocationInformation
func HandleUplinkNasTransport(ran *context.AmfRan, message *ngapType.NGAPPDU) {
var aMFUENGAPID *ngapType.AMFUENGAPID
var rANUENGAPID *ngapType.RANUENGAPID
var nASPDU *ngapType.NASPDU
var userLocationInformation *ngapType.UserLocationInformation
根据 3GPP 接入类型,以及 MM 消息类 MsgTypeULNASTransport,定位到函数 MsgTypeULNASTransport
提取出 PDU session ID,Snssai,DNN,requestType(ULNASTransportRequestTypeInitialRequest)设置为 RequestType_INITIAL_REQUEST
func HandleULNASTransport(ue *context.AmfUe, anType models.AccessType, procedureCode int64, ulNasTransport *nasMessage.ULNASTransport, securityHeaderType uint8) error {
logger.GmmLog.Infoln("Handle UL NAS Transport")
if ue.MacFailed {
return fmt.Errorf("NAS message integrity check failed")
}
switch ulNasTransport.GetPayloadContainerType() {
case nasMessage.PayloadContainerTypeN1SMInfo:
在处理 SM 消息根据类型为 MsgTypePDUSessionEstablishmentRequest 根据初始请求设置为:RequestType_INITIAL_REQUEST
func HandlePDUSessionEstablishmentRequest(ue *context.AmfUe, anType models.AccessType, payload []byte, pduSessionID int32, requestType models.RequestType, sNssai *models.Snssai, dnn string) error {
// TODO Request Type Emergency requset
var pduSession models.PduSessionContext
pduSession.PduSessionId = pduSessionID
pduSession.AccessType = anType
amfSelf := context.AMF_Self()
if requestType == models.RequestType_INITIAL_REQUEST {
如果 UE 未提供 Snssai,则根据 UE 订阅的切片信息选择,如果 DNN 未提供,选择切片默认 DNN
如果以上情况没有,则使用 UE Allow 切片信息
if sNssai == nil {
if ue.SmfSelectionData != nil {
for snssai, sNssaiInfo := range ue.SmfSelectionData.SubscribedSnssaiInfos {
var err error
sNssai, err = util.SnssaiHexToModels(snssai)
if err != nil {
return err
}
if dnn == "" {
for _, dnnInfo := range sNssaiInfo.DnnInfos {
if dnnInfo.DefaultDnnIndicator {
dnn = dnnInfo.Dnn
break
}
}
}
}
}
if sNssai == nil {
allowedNssai := ue.AllowedNssai[anType]
if len(allowedNssai) > 0 {
sNssai = allowedNssai[0].AllowedSnssai
} else {
err := fmt.Errorf("Ue[%s] doesn't have allowedNssai\n", ue.Supi)
logger.GmmLog.Errorf(err.Error())
return err
}
}
}
4.2.1 selectSmf 函数
AMF 根据切片信息,DNN 等为 PDU 会话选择 SMF
AMF->SMF: Nnssf_NSSelection_Get 返回了网络切片 ID,AMF 根据 NSI 找到 S-NAASI 选择 SMF
4.2.2 如果 UE 的会话上下文存在
则调用 SendUpdateSmContextRequest 向 SMF 发送 Nsmf_PDUSession /sm-contexts/{smContextRef}/modify 请求
// Store PduSessionContext For duplicated PDU Session Id
if smContext, ok := ue.SmContextList[pduSessionID]; ok {
ue.StoredSmContext[pduSessionID] = &context.StoredSmContext{
SmfId: smfID,
SmfUri: smfUri,
PduSessionContext: &pduSession,
AnType: anType,
Payload: payload,
}
4.3 如果 UE 的会话上下文不存在
BuildCreateSmContextRequest 创建会话上下文请求,其包括:
- supi
- UnauthenticatedSupi
- pei
- gpsi
- pdusessionId
- sNssai
- DNN
- servingNfId
- guami
- servingNetwork
- requestType
- N1Smmsg
- Antype
- RatType
- [ UeLocation ]
- SmContextStatusUri
smContextCreateData.Supi = ue.Supi
smContextCreateData.UnauthenticatedSupi = ue.UnauthenticatedSupi
smContextCreateData.Pei = ue.Pei
smContextCreateData.Gpsi = ue.Gpsi
smContextCreateData.PduSessionId = pduSessionContext.PduSessionId
smContextCreateData.SNssai = pduSessionContext.SNssai
smContextCreateData.Dnn = pduSessionContext.Dnn
smContextCreateData.ServingNfId = context.NfId
smContextCreateData.Guami = &context.ServedGuamiList[0]
smContextCreateData.ServingNetwork = context.ServedGuamiList[0].PlmnId
4.3.1 SendCreateSmContextRequest
向 SMF 发送 Nsmf_PDUSession /sm-contexts 请求
4.3.2 SMF 回复 AMF response
5. N1N2MessageTransfer(SMF->AMF)步骤 11
包裹 N1N2MessageTransfer,设置类型为 EventN1N2MessageTransfer,丢进 channel 进行处理
HandleN1N2MessageTransferRequest 函数
由 SMF 向 AMF 发送的 N2 信息 ngap 类型为 NgapIeType_PDU_RES_SETUP_REQ
5.1 N1 信息类型为 NgapIeType_PDU_RES_SETUP_REQ
case models.NgapIeType_PDU_RES_SETUP_REQ:
HttpLog.Debugln("AMF Transfer NGAP PDU Resource Setup Req from SMF")
var nasPdu []byte
var err error
if n1Msg != nil {
pduSessionId := uint8(smInfo.PduSessionId)
nasPdu, err = gmm_message.BuildDLNASTransport(ue, nasMessage.PayloadContainerTypeN1SMInfo, n1Msg, &pduSessionId, nil, nil, 0)
if err != nil {
logger.HttpLog.Errorln(err.Error())
}
}
5.1.1 BuildDLNASTransport 函数
创建 NAS MM 消息,类型设置为 MsgTypeDLNASTransport,
5.1.2 AppendPDUSessionResourceSetupListSUReq 函数
PDUSessionResourceSetupListSUReq 结构填充 PDU 会话 ID,SNSSAI,nasPDU
5.1.3 SendPDUSessionResourceSetupRequest 函数 N2 PDU Session Request(AMF->RAN) 步骤 12
为多个 PDU 会话和对应的 Qos 流在 Uu 和 NG-U 分配资源,来给 UE 建立相应的 DRB,这个流程使用 UE 相关的信令,由 AMF 发起 PDU SESSION RESOURCE SETUP REQUEST 消息到 NG-RAN 节点
BuildPDUSessionResourceSetupRequest 函数实例化 NGAPPDU 对象
Present 设置为 NGAPPDUPresentInitiatingMessage,填充的为 initiatingMessage,ProcedureCode 设置为 ProcedureCodePDUSessionResourceSetup InitiatingMessagePresentPDUSessionResourceSetupRequest
type NGAPPDU struct {
Present int
InitiatingMessage *InitiatingMessage
SuccessfulOutcome *SuccessfulOutcome
UnsuccessfulOutcome *UnsuccessfulOutcome
}
5.1.3.1 (IE)AMF UE NGAP ID
ProtocolIEIDAMFUENGAPID --> PDUSessionResourceSetupRequestIEsPresentAMFUENGAPID
5.1.3.2 (IE)RAN UE NGAP ID
ProtocolIEIDRANUENGAPID --> PDUSessionResourceSetupRequestIEsPresentRANUENGAPID
5.1.3.3(IE)Ran Paging Priority (optional)
5.1.3.4(IE)NAS-PDU (optional)
ProtocolIEIDNASPDU --> PDUSessionResourceSetupRequestIEsPresentNASPDU
5.1.3.5(IE)PDU Session Resource Setup Request list
ProtocolIEIDPDUSessionResourceSetupListSUReq --> PDUSessionResourceSetupRequestIEsPresentPDUSessionResourceSetupListSUReq
5.2 N2 PDU Session Response (RAN -> AMF)步骤 14
NGAP 消息类型为 NGAPPDUPresentSuccessfulOutcome,填充数据结构为 SuccessfulOutcome,设置为 ProcedureCodePDUSessionResourceSetup,SuccessfulOutcomePresentPDUSessionResourceSetupResponse
AMF 收到 N2 PDU Session Response 进行处理,定位函数为 HandlePDUSessionResourceSetupResponse,包括更新到 SMF (步骤 15) Nsmf_PDUSession_UpdateSMContextRequest
NRF 中注册的 AMF 信息:
{
"_id" : ObjectId("5f1a9c2564d2e538cb1309d7"),
"nfInstanceId" : "d5a9f0d5-a065-43cb-ad91-65a123351ee4",
"nfType" : "AMF",
"nfStatus" : "REGISTERED",
"plmnList" : [
{
"mcc" : "208",
"mnc" : "93"
} ],
"sNssais" : [
{
"sst" : 1,
"sd" : "010203"
},
{
"sst" : 1,
"sd" : "112233"
} ],
"ipv4Addresses" : [
"amf" ],
"amfInfo" : {
"taiList" : [
{
"plmnId" : {
"mcc" : "208",
"mnc" : "93"
},
"tac" : "000001"
} ],
"amfSetId" : "3f8",
"amfRegionId" : "ca",
"guamiList" : [
{
"plmnId" : {
"mcc" : "208",
"mnc" : "93"
},
"amfId" : "cafe00"
} ]
},
"nfServices" : [
{
"ipEndPoints" : [
{
"transport" : "TCP",
"port" : 29518,
"ipv4Address" : "amf"
} ],
"apiPrefix" : "https://amf:29518",
"serviceInstanceId" : "0",
"serviceName" : "namf-comm",
"versions" : [
{
"apiVersionInUri" : "v1",
"apiFullVersion" : "1.0.0"
} ],
"scheme" : "https",
"nfServiceStatus" : "REGISTERED"
},
{
"versions" : [
{
"apiVersionInUri" : "v1",
"apiFullVersion" : "1.0.0"
} ],
"scheme" : "https",
"nfServiceStatus" : "REGISTERED",
"ipEndPoints" : [
{
"ipv4Address" : "amf",
"transport" : "TCP",
"port" : 29518
} ],
"apiPrefix" : "https://amf:29518",
"serviceInstanceId" : "1",
"serviceName" : "namf-evts"
},
{
"scheme" : "https",
"nfServiceStatus" : "REGISTERED",
"ipEndPoints" : [
{
"ipv4Address" : "amf",
"transport" : "TCP",
"port" : 29518
} ],
"apiPrefix" : "https://amf:29518",
"serviceInstanceId" : "2",
"serviceName" : "namf-mt",
"versions" : [
{
"apiVersionInUri" : "v1",
"apiFullVersion" : "1.0.0"
} ]
},
{
"ipEndPoints" : [
{
"ipv4Address" : "amf",
"transport" : "TCP",
"port" : 29518
} ],
"apiPrefix" : "https://amf:29518",
"serviceInstanceId" : "3",
"serviceName" : "namf-loc",
"versions" : [
{
"apiVersionInUri" : "v1",
"apiFullVersion" : "1.0.0"
} ],
"scheme" : "https",
"nfServiceStatus" : "REGISTERED"
} ]
}