【5G核心网】free5GC 会话建立流程源码分析

本文分析 Free5GC PDU Session Establishment procedure 会话建立流程

1. UE 发起的 PDU 会话建立请求 (RAN -> AMF) 

    GetPduSessionEstablishmentRequest 创建 NAS-PDU,填充 NAS 消息中的 SM 消息,设置 SM 头消息类型为 MsgTypePDUSessionEstablishmentRequest PDUSessionEstablishmentRequestPDUSessionTypeType,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,类型为 MsgTypeULNASTransportPayloadContainerTypeN1SMInfo

    添加 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
}

   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

 

2. AMF 收到 RAN 发送的 NGAP UPLINK NAS TRANSPORT 消息

    根据 NGAP 消息类型为 NGAPPDUPresentInitiatingMessage,ProcedureCode 设置为 ProcedureCodeUplinkNASTransportInitiatingMessagePresentUplinkNASTransport,定位到 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

    HandleNAS

        -->  Dispatch

               -->  ue.Sm[anType].SendEvent(gmm.EVENT_GMM_MESSAGE, args)

                     -->  Registered_3gpp

                            -->  HandleULNASTransport

    根据 3GPP 接入类型,以及 MM 消息类 MsgTypeULNASTransport,目前状态已经未 REGISTED,定位到函数  HandleULNASTransport

    提取出 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 根据初始请求 ULNASTransportRequestTypeInitialRequest 设置为: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
		}
	}

}

   2.1 selectSmf 函数

    AMF 根据切片信息,DNN 等为 PDU 会话选择 SMF

    AMF->SMF:  Nnssf_NSSelection_Get 返回了网络切片 ID,AMF 根据 NSI 找到 S-NAASI 选择 SMF

    GetNsiInformationFromSnssai 函数根据请求的 SNssai 是否存在允许的列表中,如果不存在则选择一个,NSSelectionGetForPduSession 则选择一个切片信息

     通过 NRF 的 Nnrf_NFDiscovery 服务发现 /nf-instances。AMF 根据切片信息,DNN 等为 PDU 会话选择 SMF

param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{
	ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}),
	Dnn:          optional.NewString(pduSession.Dnn),
	Snssais:      optional.NewInterface(util.MarshToJsonString([]models.Snssai{*pduSession.SNssai})),
}

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

   2.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

      2.3.1 SendCreateSmContextRequest

      向 SMF 发送 Nsmf_PDUSession /sm-contexts 请求

 

3. SMF Nsmf_PDUSession_CreateSMContext

    SMF 从 AMF 接收 Nsmf_PDUSession_CreateSMContext 请求,/sm-contexts,则进入 PostSmContexts 函数处理,设置为 PDUSessionSMContextCreate 类型消息,丢进 channel 进行集中处理

    HandlePDUSessionSMContextCreate 函数

      SMF 创建会话管理上下文,实例化 SMContext

    - supi

    - pduSessionID

    - gpsi

    - DNn

    - SNssai

    - HplmnSnssai

    - ServingNetwork

    - AnType

    - RatType

    - UeLocation

    - OldPduSessionId

    - ServingNfId

    - SmContextStatusUri

createData := request.JsonData
smContext := smf_context.NewSMContext(createData.Supi, createData.PduSessionId)
smContext.SetCreateData(createData)
smContext.SmStatusNotifyUri = createData.SmContextStatusUri

   3.1 SendNFDiscoveryUDM

    服务发现 UDM,向 NRF 查找 Nnrf_NFDiscovery /nf-instances

{
    "_id": ObjectId("5f168fe7480a2ae81dcac123"),
    "nfInstanceId": "400346f4-087e-40b1-a4cd-00566953999d",
    "nfType": "UDM",
    "nfStatus": "REGISTERED",
    "plmnList": [{
        "mcc": "208",
        "mnc": "93"
    }],
    "ipv4Addresses": ["udm"],
    "udmInfo": {},
    "nfServices": [{
        "serviceName": "nudm-sdm",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "transport": "TCP",
            "port": 29503,
            "ipv4Address": "udm"
        }],
        "apiPrefix": "https://udm:29503",
        "serviceInstanceId": "0"
    }, {
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "ipv4Address": "udm",
            "transport": "TCP",
            "port": 29503
        }],
        "apiPrefix": "https://udm:29503",
        "serviceInstanceId": "1",
        "serviceName": "nudm-uecm"
    }, {
        "serviceName": "nudm-ueau",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "transport": "TCP",
            "port": 29503,
            "ipv4Address": "udm"
        }],
        "apiPrefix": "https://udm:29503",
        "serviceInstanceId": "2"
    }, {
        "apiPrefix": "https://udm:29503",
        "serviceInstanceId": "3",
        "serviceName": "nudm-ee",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "port": 29503,
            "ipv4Address": "udm",
            "transport": "TCP"
        }]
    }, {
        "ipEndPoints": [{
            "ipv4Address": "udm",
            "transport": "TCP",
            "port": 29503
        }],
        "apiPrefix": "https://udm:29503",
        "serviceInstanceId": "4",
        "serviceName": "nudm-pp",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED"
    }]
}

   3.2 向 UDM 发起 Nudm_SubscriberDataManagement.请求 /{supi}/sm-data 步骤 4

smPlmnID := createData.Guami.PlmnId

smDataParams := &Nudm_SubscriberDataManagement.GetSmDataParamOpts{
	Dnn:         optional.NewString(createData.Dnn),
	PlmnId:      optional.NewInterface(smPlmnID.Mcc + smPlmnID.Mnc),
	SingleNssai: optional.NewInterface(openapi.MarshToJsonString(smContext.Snssai)),
}

SubscriberDataManagementClient := smf_context.SMF_Self().SubscriberDataManagementClient

sessSubData, _, err := SubscriberDataManagementClient.SessionManagementSubscriptionDataRetrievalApi.GetSmData(context.Background(), smContext.Supi, smDataParams)

{
    "_id": ObjectId("5ef0907d29ad99a43c018d2d"),
    "smPolicySnssaiData": {
        "01010203": {
            "snssai": {
                "sst": 1,
                "sd": "010203"
            },
            "smPolicyDnnData": {
                "internet": {
                    "dnn": "internet"
                }
            }
        },
        "01112233": {
            "snssai": {
                "sst": 1,
                "sd": "112233"
            },
            "smPolicyDnnData": {
                "internet": {
                    "dnn": "internet"
                }
            }
        }
    },
    "ueId": "imsi-2089300007487"
} {
    "_id": ObjectId("5ef0907d29ad99a43c018d2d"),
    "smPolicySnssaiData": {
        "01010203": {
            "snssai": {
                "sst": 1,
                "sd": "010203"
            },
            "smPolicyDnnData": {
                "internet": {
                    "dnn": "internet"
                }
            }
        },
        "01112233": {
            "snssai": {
                "sst": 1,
                "sd": "112233"
            },
            "smPolicyDnnData": {
                "internet": {
                    "dnn": "internet"
                }
            }
        }
    },
    "ueId": "imsi-2089300007487"
}

   3.3 为 UE 分配 IP 地址

func (smContext *SMContext) HandlePDUSessionEstablishmentRequest(req *nasMessage.PDUSessionEstablishmentRequest) {
	// Retrieve PDUSessionID
	smContext.PDUSessionID = int32(req.PDUSessionID.GetPDUSessionID())
	logger.GsmLog.Infoln("In HandlePDUSessionEstablishmentRequest")
	// Handle PDUSessionType

	smContext.PDUAddress = AllocUEIP()
}

   3.4 PCF Selection (步骤 7a)

{
    "_id": ObjectId("5f1a9c2464d2e538cb1309d5"),
    "pcfInfo": {
        "dnnList": ["free5gc", "internet"]
    },
    "nfServices": [{
        "serviceName": "npcf-am-policy-control",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "ipv4Address": "pcf",
            "transport": "TCP",
            "port": 29507
        }],
        "apiPrefix": "https://pcf:29507",
        "serviceInstanceId": "0"
    }, {
        "serviceName": "npcf-smpolicycontrol",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "ipv4Address": "pcf",
            "transport": "TCP",
            "port": 29507
        }],
        "apiPrefix": "https://pcf:29507",
        "supportedFeatures": "3fff",
        "serviceInstanceId": "1"
    }, {
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "ipv4Address": "pcf",
            "transport": "TCP",
            "port": 29507
        }],
        "apiPrefix": "https://pcf:29507",
        "serviceInstanceId": "2",
        "serviceName": "npcf-bdtpolicycontrol",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https"
    }, {
        "ipEndPoints": [{
            "ipv4Address": "pcf",
            "transport": "TCP",
            "port": 29507
        }],
        "apiPrefix": "https://pcf:29507",
        "supportedFeatures": "3",
        "serviceInstanceId": "3",
        "serviceName": "npcf-policyauthorization",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED"
    }, {
        "versions": [{
            "apiFullVersion": "1.0.0",
            "apiVersionInUri": "v1"
        }],
        "scheme": "https",
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "ipv4Address": "pcf",
            "transport": "TCP",
            "port": 29507
        }],
        "apiPrefix": "https://pcf:29507",
        "serviceInstanceId": "4",
        "serviceName": "npcf-eventexposure"
    }, {
        "nfServiceStatus": "REGISTERED",
        "ipEndPoints": [{
            "ipv4Address": "pcf",
            "transport": "TCP",
            "port": 29507
        }],
        "apiPrefix": "https://pcf:29507",
        "serviceInstanceId": "5",
        "serviceName": "npcf-ue-policy-control",
        "versions": [{
            "apiVersionInUri": "v1",
            "apiFullVersion": "1.0.0"
        }],
        "scheme": "https"
    }],
    "nfInstanceId": "47c4dfb8-840e-4be5-9bfa-b0ef9dae4672",
    "nfType": "PCF",
    "nfStatus": "REGISTERED",
    "plmnList": [{
        "mcc": "208",
        "mnc": "93"
    }],
    "ipv4Addresses": ["pcf"]
}

   3.5 SM Policy Association Establisment (步骤 7b)

    实例化 SmPolicyContextData 并填充 Supi,PDUSessionID,Dnn,Snssai 等等。调用 /sm-policies 更新 PCF 策略配置

smPolicyData := models.SmPolicyContextData{}

smPolicyData.Supi = smContext.Supi
smPolicyData.PduSessionId = smContext.PDUSessionID
smPolicyData.NotificationUri = fmt.Sprintf("%s://%s:%d/nsmf-callback/sm-policies/%s", smf_context.SMF_Self().URIScheme, smf_context.SMF_Self().HTTPAddress, smf_context.SMF_Self().HTTPPort, smContext.Ref)
smPolicyData.Dnn = smContext.Dnn
smPolicyData.PduSessionType = nasConvert.PDUSessionTypeToModels(smContext.SelectedPDUSessionType)
smPolicyData.AccessType = smContext.AnType
smPolicyData.RatType = smContext.RatType
smPolicyData.Ipv4Address = smContext.PDUAddress.To4().String()
smPolicyData.SubsSessAmbr = smContext.DnnConfiguration.SessionAmbr
smPolicyData.SubsDefQos = smContext.DnnConfiguration.Var5gQosProfile
smPolicyData.SliceInfo = smContext.Snssai
smPolicyData.ServingNetwork = &models.NetworkId{
	Mcc: smContext.ServingNetwork.Mcc,
	Mnc: smContext.ServingNetwork.Mnc,
}
smPolicyData.SuppFeat = "F"

smPolicyDecision, _, err := smContext.SMPolicyClient.DefaultApi.SmPoliciesPost(context.Background(), smPolicyData)

{
    "_id": ObjectId("5f0290d83fbd30af88df2945"),
    "singleNssai": {
        "sst": 1,
        "sd": "010203"
    },
    "dnnConfigurations": {
        "internet": {
            "pduSessionTypes": {
                "defaultSessionType": "IPV4",
                "allowedSessionTypes": ["IPV4"]
            },
            "sscModes": {
                "defaultSscMode": "SSC_MODE_1",
                "allowedSscModes": ["SSC_MODE_1", "SSC_MODE_2", "SSC_MODE_3"]
            },
            "5gQosProfile": {
                "5qi": 0,
                "arp": {
                    "priorityLevel": 8,
                    "preemptCap": "",
                    "preemptVuln": ""
                },
                "priorityLevel": 8
            },
            "sessionAmbr": {
                "uplink": "1000 Kbps",
                "downlink": "1000 Kbps"
            }
        }
    },
    "ueId": "imsi-208930000000003",
    "servingPlmnId": "20893"
}

   3.6 ApplySmPolicyFromDecision 函数(新增)

      应用 session rule

 

4. UPF Selection(步骤 8)

    UPTunnel 结构体

type UPTunnel struct {
	PathIDGenerator *idgenerator.IDGenerator
	DataPathPool    DataPathPool
}

    DataPath 结构体

type DataPath struct {
	//meta data
	Activated         bool
	IsDefaultPath     bool
	Destination       Destination
	HasBranchingPoint bool
	//Data Path Double Link List
	FirstDPNode *DataPathNode
}

    DataPathNode 结构体

type DataPathNode struct {
	UPF *UPF
	//DataPathToAN *DataPathDownLink
	//DataPathToDN map[string]*DataPathUpLink //uuid to DataPathLink

	UpLinkTunnel   *GTPTunnel
	DownLinkTunnel *GTPTunnel
	//for UE Routing Topology
	//for special case:
	//branching & leafnode

	//InUse                bool
	IsBranchingPoint bool
	//DLDataPathLinkForPSA *DataPathUpLink
	//BPUpLinkPDRs         map[string]*DataPathDownLink // uuid to UpLink

	HaveSession bool
}

     例如官方例子:

userplane_information:

  • up_nodes:
    • gNB1:
      • type: AN
      • an_ip: 192.188.2.3
    • BranchingUPF:
      • type: UPF
      • node_id: 10.200.200.102
      • up_resource_ip: 192.188.2.2
    • AnchorUPF1:
      • type: UPF
      • node_id: 10.200.200.101
      • up_resource_ip: 192.188.2.23
    • AnchorUPF2:
      • type: UPF
      • node_id: 10.200.200.103
      • up_resource_ip: 192.188.2.24
    • links:
      • A: gNB1 B: BranchingUPF
      • A: BranchingUPF B: AnchorUPF1
      • A: BranchingUPF B: AnchorUPF2

    GenerateDataPath 终于更改了,原来写的不知道要干什么

    在这里区分预先配置和未预先配置 UE SUPI 路径

   4.1 支持 ULCL 且预先配置路由情况

if smf_context.SMF_Self().ULCLSupport && smf_context.CheckUEHasPreConfig(createData.Supi) {
	//TODO: change UPFRoot => ULCL UserPlane Refactor
	logger.PduSessLog.Infof("SUPI[%s] has pre-config route", createData.Supi)
	uePreConfigPaths := smf_context.GetUEPreConfigPaths(createData.Supi)
	smContext.Tunnel.DataPathPool = uePreConfigPaths.DataPathPool
	smContext.Tunnel.PathIDGenerator = uePreConfigPaths.PathIDGenerator
	defaultPath = smContext.Tunnel.DataPathPool.GetDefaultPath()
	smContext.AllocateLocalSEIDForDataPath(defaultPath)
	defaultPath.ActivateTunnelAndPDR(smContext)
	// TODO: Maybe we don't need this
	smContext.BPManager = smf_context.NewBPManager(createData.Supi)
} 

   4.2 没有预先配置路由情况

} else {
	logger.PduSessLog.Infof("SUPI[%s] has no pre-config route", createData.Supi)
	defaultUPPath := smf_context.GetUserPlaneInformation().GetDefaultUserPlanePathByDNN(createData.Dnn)
	smContext.AllocateLocalSEIDForUPPath(defaultUPPath)
	defaultPath = smf_context.GenerateDataPath(defaultUPPath, smContext)
	defaultPath.IsDefaultPath = true
	smContext.Tunnel.AddDataPath(defaultPath)
	defaultPath.ActivateTunnelAndPDR(smContext)
}

    GetDefaultUserPlanePathByDNN 返回 AN 和 DNN 的路径

    AllocateLocalSEIDForUPPath 为 path 分配 Local Seid 

    GenerateDataPath 函数建立一个链表

   4.3 ActivateTunnelAndPDR 函数

func (dataPath *DataPath) ActivateTunnelAndPDR(smContext *SMContext) {

	firstDPNode := dataPath.FirstDPNode
	logger.PduSessLog.Traceln("In ActivateTunnelAndPDR")
	logger.PduSessLog.Traceln(dataPath.ToString())

    Activate Tunnels

    Activate PDR

5. SendPFCPRule

    对于首次没有 session 的情况,上行链路和下行链路 PDR (包检测规则)和 FAR (转发行为规则)

func SendPFCPRule(smContext *smf_context.SMContext, dataPath *smf_context.DataPath) {

	logger.PduSessLog.Infof("Send PFCP Rule")
	logger.PduSessLog.Infof("DataPath: ", dataPath)
	for curDataPathNode := dataPath.FirstDPNode; curDataPathNode != nil; curDataPathNode = curDataPathNode.Next() {
		pdrList := make([]*smf_context.PDR, 0, 2)
		farList := make([]*smf_context.FAR, 0, 2)
		if !curDataPathNode.HaveSession {
			if curDataPathNode.UpLinkTunnel != nil && curDataPathNode.UpLinkTunnel.PDR != nil {
				pdrList = append(pdrList, curDataPathNode.UpLinkTunnel.PDR)
				farList = append(farList, curDataPathNode.UpLinkTunnel.PDR.FAR)
			}
			if curDataPathNode.DownLinkTunnel != nil && curDataPathNode.DownLinkTunnel.PDR != nil {
				pdrList = append(pdrList, curDataPathNode.DownLinkTunnel.PDR)
				farList = append(farList, curDataPathNode.DownLinkTunnel.PDR.FAR)
			}

			pfcp_message.SendPfcpSessionEstablishmentRequest(curDataPathNode.UPF.NodeID, smContext, pdrList, farList, nil)
			curDataPathNode.HaveSession = true

   5.1 SendPfcpSessionEstablishmentRequest 步骤 10a

    建立 PFCP 会话建立请求到 UPF,包括 SM 上下文,PDR,FAR,BAR(目前空)

    BuildPfcpSessionEstablishmentRequestForULCL 函数主要用来实例化 PFCPSessionEstablishmentRequest,并填充 CP NodeID,CreatePDR,CreateFAR 等,

    创建的 PFCP 消息头,消息类型为 PFCP_SESSION_ESTABLISHMENT_REQUEST

func SendPfcpSessionEstablishmentRequest(upNodeID pfcpType.NodeID, ctx *context.SMContext, pdrList []*context.PDR, farList []*context.FAR, barList []*context.BAR) {
	pfcpMsg, err := BuildPfcpSessionEstablishmentRequest(upNodeID, ctx, pdrList, farList, barList)
	if err != nil {
		logger.PfcpLog.Errorf("Build PFCP Session Establishment Request failed: %v", err)
		return
	}

	message := pfcp.Message{
		Header: pfcp.Header{
			Version:         pfcp.PfcpVersion,
			MP:              1,
			S:               pfcp.SEID_PRESENT,
			MessageType:     pfcp.PFCP_SESSION_ESTABLISHMENT_REQUEST,
			SEID:            0,
			SequenceNumber:  getSeqNumber(),
			MessagePriority: 0,
		},
		Body: pfcpMsg,
	}

	upaddr := &net.UDPAddr{
		IP:   upNodeID.ResolveNodeIdToIp(),
		Port: pfcpUdp.PFCP_PORT,
	}

6.  UPF 处理 PFCP 建立请求

    消息类型为: PFCP_SESSION_ESTABLISHMENT_REQUEST,body 结构为: PFCPSessionEstablishmentRequest,处理函数为 UpfN4HandleSessionEstablishmentRequest

Status UpfN4HandleSessionEstablishmentRequest(UpfSession *session, PfcpXact *pfcpXact,
                                              PFCPSessionEstablishmentRequest *request) {
    Status status;
    uint8_t cause = PFCP_CAUSE_REQUEST_ACCEPTED;

    UTLT_Assert(session, return STATUS_ERROR, "Upf Session error");
    UTLT_Assert(pfcpXact, return STATUS_ERROR, "pfcpXact error");

   6.1 UpfN4HandleCreateFar 处理 FAR 转发行为规则

    gtp5g_far_alloc 实例化 gtp5g_far 对象

struct gtp5g_far {
    struct hlist_node    hlist_id;

    u32 id;

//    u8 dest_iface;
    u8 action;                              // apply action

    struct forwarding_parameter *fwd_param;

    struct net_device   *dev;
    struct rcu_head     rcu_head;
};

    CreateFAR 结构体

typedef struct _CreateFAR {
    unsigned long presence;
    FARID fARID;
    ApplyAction applyAction;
    ForwardingParameters forwardingParameters;
    DuplicatingParameters duplicatingParameters;
    BARID bARID;
} __attribute__((packed)) CreateFAR;

   根据 3GPP 定义的 Create FAR IE within PFCP Session Establishment Request

FAR ID

M

This IE shall uniquely identify the FAR among all the FARs configured for that PFCP session.

Apply Action

M

This IE shall indicate the action to apply to the packets, See clauses 5.2.1 and 5.2.3.

Forwarding Parameters

C

This IE shall be present when the Apply Action requests the packets to be forwarded. It may be present otherwise.

When present, this IE shall contain the forwarding instructions to be applied by the UP function when the Apply Action requests the packets to be forwarded.

See table 7.5.2.3-2.

Duplicating Parameters

C

This IE shall be present when the Apply Action requests the packets to be duplicated. It may be present otherwise.

When present, this IE shall contain the forwarding instructions to be applied by the UP function for the traffic to be duplicated, when the Apply Action requests the packets to be duplicated.

Several IEs with the same IE type may be present to represent to duplicate the packets to different destinations. See NOTE 1.

See table 7.5.2.3-3.

BAR ID

O

When present, this IE shall contain the BAR ID of the BAR defining the buffering instructions to be applied by the UP function when the Apply Action requests the packets to be buffered.

     6.1.1  _pushFarToKernel

          -->  GtpTunnelAddFar

                 -->  NetlinkSockOpen

                        -->  gtp5g_add_far

                               -->  genl_nlmsg_build_hdr

                               -->  gtp5g_build_far_payload

                               -->  genl_socket_talk

   struct nlmsghdr

    Netlink 的报文由消息头和消息体构成,struct nlmsghdr 为消息头:

struct nlmsghdr

{

    __u32 nlmsg_len; /* Length of message including header */

    __u16 nlmsg_type; /* Message content */

    __u16 nlmsg_flags; /* Additional flags */

    __u32 nlmsg_seq; /* Sequence number */

    __u32 nlmsg_pid; /* Sending process PID */

};

   -  nlmsg_len:消息的长度,按字节计算。包括了Netlink消息头

   -  nlmsg_type:消息类型,数据/控制消息。如下:

        a) NLMSG_NOOP,空消息

        b) NLMSG_ERROR,指明该消息中包含一个错误

        c) NLMSG_DONE,如果内核通过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,其余所有消息的 nlmsg_flags 属性都被设置NLM_F_MULTI位有效。

        d) NLMSG_OVERRUN,暂时没用到

   -  nlmsg_flags:附加在消息上的额外说明信息,如上面提到的 NLM_F_MULTI

   6.2 UpfN4HandleCreatePdr 处理 PDR 包检测规则

    gtp5g_pdr_alloc 实例化 gtp5g_pdr 对象,

struct gtp5g_pdr {
    uint16_t id;
    uint32_t *precedence;
    struct gtp5g_pdi *pdi;

    uint8_t *outer_hdr_removal;
    uint32_t *far_id;

    /* Not in 3GPP spec, just used for routing */
    struct in_addr *role_addr_ipv4;

    /* Not in 3GPP spec, just used for buffering */
    char *unix_sock_path;
};

    _CreatePDR 结构体

typedef struct _CreatePDR {
    unsigned long presence;
    PacketDetectionRuleID pDRID;
    Precedence precedence;
    PDI pDI;
    OuterHeaderRemoval outerHeaderRemoval;
    FARID fARID;
    URRID uRRID;
    QERID qERID;
    ActivatePredefinedRules activatePredefinedRules;
} __attribute__((packed)) CreatePDR;

    PDI SDF 过滤器 很多未实现

   6.3 UpfN4BuildSessionEstablishmentResponse 函数

    响应请求设置类型为 PFCP_SESSION_ESTABLISHMENT_RESPONSE,设置 seid。body 结构体为 pFCPSessionEstablishmentResponse

    6.3.2 SendPfcpSessionModificationRequest 函数

    对于已经存在的会话,修改的情况

7. HandlePfcpSessionEstablishmentResponse 函数

    从 UPF 收到回复请求,类型为 PFCP_SESSION_ESTABLISHMENT_RESPONSE

func HandlePfcpSessionEstablishmentResponse(msg *pfcpUdp.Message) {
	rsp := msg.PfcpMessage.Body.(pfcp.PFCPSessionEstablishmentResponse)

	SEID := msg.PfcpMessage.Header.SEID
	smContext := smf_context.GetSMContextBySEID(SEID)

	if rsp.UPFSEID != nil {
		UPFSEID := rsp.UPFSEID
		smContext.RemoteSEID = UPFSEID.Seid
	}

   7.1 BuildGSMPDUSessionEstablishmentAccept 函数

    实例化 NAS 消息,填充 GSM 消息,设置类型为 MsgTypePDUSessionEstablishmentAccept,PDUSessionEstablishmentAccept 结构体如下

type PDUSessionEstablishmentAccept struct {
	nasType.ExtendedProtocolDiscriminator
	nasType.PDUSessionID
	nasType.PTI
	nasType.PDUSESSIONESTABLISHMENTACCEPTMessageIdentity
	nasType.SelectedSSCModeAndSelectedPDUSessionType
	nasType.AuthorizedQosRules
	nasType.SessionAMBR
	*nasType.Cause5GSM
	*nasType.PDUAddress
	*nasType.RQTimerValue
	*nasType.SNSSAI
	*nasType.AlwaysonPDUSessionIndication
	*nasType.MappedEPSBearerContexts
	*nasType.EAPMessage
	*nasType.AuthorizedQosFlowDescriptions
	*nasType.ExtendedProtocolConfigurationOptions
	*nasType.DNN
}

     7.1.1 填充 PDUSessionEstablishmentAccept 结构体

  •      PDUSessionID
  •      消息类型 MsgTypePDUSessionEstablishmentAccept
  •      PTI:0x00
  •      PDU 会话类型:这里实现为 IPv4
  •      SSC 模式,这里设置为 1
  •      SessionAMBR
  •      QOS 规则
  •     【PDU Address】
pDUSessionEstablishmentAccept.SetPDUSessionID(uint8(smContext.PDUSessionID))
pDUSessionEstablishmentAccept.SetMessageType(nas.MsgTypePDUSessionEstablishmentAccept)
pDUSessionEstablishmentAccept.SetExtendedProtocolDiscriminator(nasMessage.Epd5GSSessionManagementMessage)
pDUSessionEstablishmentAccept.SetPTI(0x00)
pDUSessionEstablishmentAccept.SetPDUSessionType(smContext.SelectedPDUSessionType)
pDUSessionEstablishmentAccept.SetSSCMode(1)
pDUSessionEstablishmentAccept.SessionAMBR = nasConvert.ModelsToSessionAMBR(smContext.SessionRule.AuthSessAmbr)
pDUSessionEstablishmentAccept.SessionAMBR.SetLen(uint8(len(pDUSessionEstablishmentAccept.SessionAMBR.Octet)))

   7.2 BuildPDUSessionResourceSetupRequestTransfer 函数

    实例化 PDUSessionResourceSetupRequestTransfer 对象,主要包含的是 IE 信息元素

func BuildPDUSessionResourceSetupRequestTransfer(ctx *SMContext) (buf []byte, err error) {
	var UpNode = ctx.Tunnel.UpfRoot.UPF
	var teidOct = make([]byte, 4)
	binary.BigEndian.PutUint32(teidOct, ctx.Tunnel.UpfRoot.UpLinkTunnel.TEID)

	resourceSetupRequestTransfer := ngapType.PDUSessionResourceSetupRequestTransfer{}

    PDUSessionResourceSetupRequestTransferIEs 结构定义如下:

type PDUSessionResourceSetupRequestTransferIEs struct {
   Id          ProtocolIEID
   Criticality Criticality
   Value       PDUSessionResourceSetupRequestTransferIEsValue `aper:"openType,referenceFieldName:Id"`
}

    -  UL NG-U UP TNL Information

    -   PDU Session Type

    -   QoS Flow Setup Request List,use Default 5qi, arp

   7.3 实例化 N1N2MessageTransferRequest

     官方 3GPP 提供的 openapi 生成,包括 PDU 会话 ID,包括 N1,N2 消息,N2 信息包括 Snssai

n1n2Request := models.N1N2MessageTransferRequest{}
n1n2Request.JsonData = &models.N1N2MessageTransferReqData{
	PduSessionId: smContext.PDUSessionID,
	N1MessageContainer: &models.N1MessageContainer{
		N1MessageClass:   "SM",
		N1MessageContent: &models.RefToBinaryData{ContentId: "GSM_NAS"},
	},
	N2InfoContainer: &models.N2InfoContainer{
		N2InformationClass: models.N2InformationClass_SM,
		SmInfo: &models.N2SmInformation{
			PduSessionId: smContext.PDUSessionID,
			N2InfoContent: &models.N2InfoContent{
				NgapIeType: models.NgapIeType_PDU_RES_SETUP_REQ,
				NgapData: &models.RefToBinaryData{
					ContentId: "N2SmInformation",
				},
			},
			SNssai: smContext.Snssai,
		},
	},
}
n1n2Request.BinaryDataN1Message = smNasBuf
n1n2Request.BinaryDataN2Information = n2Pdu

    7.4 SMF 发送消息到 AMF

     /ue-contexts/{ueContextId}/n1-n2-messages

8. N1N2MessageTransfer(SMF->AMF)步骤 11

    包裹 N1N2MessageTransfer,设置类型为 EventN1N2MessageTransfer,丢进 channel 进行处理

   HandleN1N2MessageTransferRequest 函数

    由 SMF 向 AMF 发送的 N2 信息 ngap 类型为 NgapIeType_PDU_RES_SETUP_REQ

   8.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())
		}
	}

    8.1.1 BuildDLNASTransport 函数

    创建 NAS MM 消息,类型设置为 MsgTypeDLNASTransport

    8.1.2 AppendPDUSessionResourceSetupListSUReq 函数

    PDUSessionResourceSetupListSUReq 结构填充 PDU 会话 ID,SNSSAI,nasPDU

    8.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
}

    -  (IE)AMF UE NGAP ID

         ProtocolIEIDAMFUENGAPID -->  PDUSessionResourceSetupRequestIEsPresentAMFUENGAPID

    -   (IE)RAN UE NGAP ID

          ProtocolIEIDRANUENGAPID -->  PDUSessionResourceSetupRequestIEsPresentRANUENGAPID

    -   (IE)Ran Paging Priority (optional)

    -   (IE)NAS-PDU (optional)

          ProtocolIEIDNASPDU -->  PDUSessionResourceSetupRequestIEsPresentNASPDU

    -   (IE)PDU Session Resource Setup Request list

         ProtocolIEIDPDUSessionResourceSetupListSUReq -->  PDUSessionResourceSetupRequestIEsPresentPDUSessionResourceSetupListSUReq

   

9. N2 PDU Session Response (RAN -> AMF)步骤 14    

      NGAP 消息类型为 NGAPPDUPresentSuccessfulOutcome,填充数据结构为 SuccessfulOutcome,设置为 ProcedureCodePDUSessionResourceSetup,SuccessfulOutcomePresentPDUSessionResourceSetupResponse

      AMF 收到 N2 PDU Session Response 进行处理,定位函数为 HandlePDUSessionResourceSetupResponse,包括更新到 SMF (步骤 15) Nsmf_PDUSession_UpdateSMContextRequest  /sm-contexts/{smContextRef}/modify,类型为 N2SmInfoType_PDU_RES_SETUP_RSP

if pDUSessionResourceSetupResponseList != nil {
	Ngaplog.Trace("[NGAP] Send PDUSessionResourceSetupResponseTransfer to SMF")

	for _, item := range pDUSessionResourceSetupResponseList.List {
		pduSessionID := int32(item.PDUSessionID.Value)
		transfer := item.PDUSessionResourceSetupResponseTransfer

		response, _, _, err := consumer.SendUpdateSmContextN2Info(amfUe, pduSessionID, models.N2SmInfoType_PDU_RES_SETUP_RSP, transfer)
		if err != nil {
			Ngaplog.Errorf("SendUpdateSmContextN2Info[PDUSessionResourceSetupResponseTransfer] Error:\n%s", err.Error())
		}
		// RAN initiated QoS Flow Mobility in subclause 5.2.2.3.7
		if response != nil && response.BinaryDataN2SmInformation != nil {
			// TODO: n2SmInfo send to RAN
		} else if response == nil {
			// TODO: error handling
		}
	}
}

   9.1 BuildUpdateSmContextRequset 函数

     实例化 SmContextUpdateData

func BuildUpdateSmContextRequset(ue *amf_context.AmfUe, present UpdateSmContextPresent, pduSessionId int32, param updateSmContextRequsetParam) (updateData models.SmContextUpdateData) {
	smContext := ue.SmContextList[pduSessionId]
	context := amf_context.AMF_Self()
	switch present {

	case UpdateSmContextPresentOnlyN2SmInfo:
		updateData.N2SmInfoType = param.n2SmType
		updateData.N2SmInfo = new(models.RefToBinaryData)
		updateData.N2SmInfo.ContentId = "N2SmInfo"
		updateData.UeLocation = &ue.Location
	}
	return
}

10. Nsmf_PDUSession_UpdateSMContext request (步骤 15)

    UpdateSmContext 函数设置消息类型为 PDUSessionSMContextUpdate,丢进 channel 处理,定位到函数 HandlePDUSessionSMContextUpdate

switch smContextUpdateData.N2SmInfoType {
case models.N2SmInfoType_PDU_RES_SETUP_RSP:
	pdrList = []*smf_context.PDR{}
	farList = []*smf_context.FAR{}

	for _, dataPath := range tunnel.DataPathPool {

		if dataPath.Activated {
			ANUPF := dataPath.FirstDPNode
			DLPDR := ANUPF.DownLinkTunnel.PDR

			DLPDR.FAR.ApplyAction = pfcpType.ApplyAction{Buff: false, Drop: false, Dupl: false, Forw: true, Nocp: false}
			DLPDR.FAR.ForwardingParameters = &smf_context.ForwardingParameters{
				DestinationInterface: pfcpType.DestinationInterface{
					InterfaceValue: pfcpType.DestinationInterfaceAccess,
				},
				NetworkInstance: []byte(smContext.Dnn),
			}

			DLPDR.State = smf_context.RULE_UPDATE
			DLPDR.FAR.State = smf_context.RULE_UPDATE

			pdrList = append(pdrList, DLPDR)
			farList = append(farList, DLPDR.FAR)
		}

	}

	err = smf_context.HandlePDUSessionResourceSetupResponseTransfer(body.BinaryDataN2SmInformation, smContext)

猜你喜欢

转载自blog.csdn.net/zhonglinzhang/article/details/107834587