本文分析 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,类型为 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
}
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 设置为 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
HandleNAS
--> Dispatch
--> SendEvent GmmMessageEvent
--> Registered
--> 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.SMContextState = smf_context.ActivePending
logger.CtxLog.Traceln("SMContextState Change State: ", smContext.SMContextState.String())
smContext.SetCreateData(createData)
smContext.SmStatusNotifyUri = createData.SmContextStatusUri
3.1 SendNFDiscoveryUDM
服务发现 UDM,向 NRF 查找 Nnrf_NFDiscovery /nf-instances
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"
}
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)
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
func ApplySmPolicyFromDecision(smContext *smf_context.SMContext, decision *models.SmPolicyDecision) error {
logger.PduSessLog.Traceln("In ApplySmPolicyFromDecision")
smContext.SMContextState = smf_context.ModificationPending
selectedSessionRule := smContext.SelectedSessionRule()
if selectedSessionRule == nil { //No active session rule
//Update session rules from decision
for id, sessRuleModel := range decision.SessRules {
handleSessionRule(smContext, id, &sessRuleModel)
}
for id := range smContext.SessionRules {
// Randomly choose a session rule to activate
smf_context.SetSessionRuleActivateState(smContext.SessionRules[id], true)
break
}
}
3.6.1 handleSessionRule 函数
smContext 加入会话规则,包括 AuthSessAmbr AuthDefQos SessRuleId
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
在这里区分预先配置和未预先配置 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)
logger.PfcpLog.Infoln("In HandlePfcpSessionEstablishmentResponse")
SEID := msg.PfcpMessage.Header.SEID
smContext := smf_context.GetSMContextBySEID(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
// TS23502 4.2.3.3, 4.2.4.3, 4.3.2.2, 4.3.2.3, 4.3.3.2, 4.3.7
func HandleN1N2MessageTransferRequest(request *http_wrapper.Request) *http_wrapper.Response {
logger.ProducerLog.Infof("Handle N1N2 Message Transfer Request")
n1n2MessageTransferRequest := request.Body.(models.N1N2MessageTransferRequest)
ueContextID := request.Params["ueContextId"]
reqUri := request.Params["reqUri"]
8.1 N1N2MessageTransferProcedure 函数
根据 N1 N2 消息进行处理
// There are 4 possible return value for this function:
// - n1n2MessageTransferRspData: if AMF handle N1N2MessageTransfer Request successfully.
// - locationHeader: if response status code is 202, then it will return a non-empty string location header for
// response
// - problemDetails: if AMF reject the request due to application error, e.g. UE context not found.
// - TransferErr: if AMF reject the request due to procedure error, e.g. UE has an ongoing procedure.
// see TS 29.518 6.1.3.5.3.1 for more details.
func N1N2MessageTransferProcedure(ueContextID string, reqUri string,
n1n2MessageTransferRequest models.N1N2MessageTransferRequest) (
n1n2MessageTransferRspData *models.N1N2MessageTransferRspData,
locationHeader string, problemDetails *models.ProblemDetails,
transferErr *models.N1N2MessageTransferError)
8.2 N2 信息类型为 NgapIeType_PDU_RES_SETUP_REQ
AMF 将透传 N1 N2 消息,如果存在 N1 消息,BuildDLNASTransport 函数将创建 gmm 消息,类型为 MsgTypeDLNASTransport,SendPDUSessionResourceSetupRequest 函数构建 NGAP 消息
case models.NgapIeType_PDU_RES_SETUP_REQ:
logger.ProducerLog.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())
}
}
if ue.RanUe[anType].SentInitialContextSetupRequest {
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, nil, n2Info)
ngap_message.SendInitialContextSetupRequest(ue, anType, nasPdu, &list, nil, nil, nil)
ue.RanUe[anType].SentInitialContextSetupRequest = true
}
8.2.1 AppendPDUSessionResourceSetupListSUReq 函数
PDUSessionResourceSetupListSUReq 结构填充 PDU 会话 ID,SNSSAI,nasPDU
8.2.2 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)
参考:
github free5gc 3.0.4