Free5GC源码研究(10) - SMF研究(上)
本文研究Session Management Function (SMF)的功能
SMF的概念
对于free5gc各NF的研究来到了最终阶段,只剩SMF和AMF两个功能,是时候回顾一下TS23.501中的这几张网络架构图。首先是这一张经典的非漫游情境下各NF的交互架构:
这张图里,核心网所有的NF通过SBI总线相连,本质上就是说所有NF都可以通过HTTP协议调用其他NF提供的服务。核心网中的SMF和AMF两个功能比较特殊,因为它们除了与核心网中其他NF交互,还与核心网外的其他实体交互:AMF要与用户设备和接入网通信,而SMF则要与用户面通信。下面这张图也展示了5G的系统架构,不过把各NF之间的存在的交互全部显式的展示了出来,所以看起来会有点杂乱。而且留意图中两个蓝色的SMF和两个绿色的UPF,这是说明一个网络中每一个NF可以有多个实例。这张图描述的场景是用户设备与网络建立多个PDU session进行通信,每个PDU session由一个SMF和一个UPF来维护。

下面这张图则更有利于我们全面地理解SMF的功能:漫游情境下两个网络间各NF地交互。

这张图除了看起来更杂乱以外,还多了几个新名词。首先是PLMN (Public Land Mobile Network,公共陆地移动网络),这是指为公众提供移动通信服务的网络,它包括移动网络运营商的无线接入网络(RAN)和核心网(Core Network),国内的比如中国移动和中国联通、国外的包括橙子(前法国电信)和沃达丰等。区别一下DN和PLMN,DN(Data Network 数据网络)指的是用户设备(UE)希望通过5G网络访问的外部网络或服务提供者网络。这些网络可以是互联网、企业内部网络、特定的内容提供网络或其他服务网络。
假如我在国内用的是中国联通的服务,出国以后不在联通的服务范围,也可以通过先连接国外的PLMN,比如沃达丰,然后委托沃达丰帮忙连接国内的联通网络,这样我就能在国外也能使用联通的服务了。这种情境就叫漫游(Roaming),联通就是我们家网络(Home PLMN,H-PLMN),沃达丰是我的访问网络(Visited PLMN,V-PLMN)。
从后两张架构图我们可以看出SMF的重要地位,尤其是在管理会话时的作用。Session Management Function中的Session指的是PDU Session,因而SMF的核心任务是管理PDU Session,也就是其建立、变更、和释放。PDU Session的概念我们在前文PCF研究里已经有所提及,是用户设备与数据网络之间的一种逻辑连接,用于承载QoS流;而QoS流指的就是服务质量得到保证的传输数据流,本质上是由同一个QoS ID标记的所有数据包,他们在网络传输中会得到相同的对待(是否优先处理,是否使用稀有网络资源等)。这个QoS流也是5G网络中处理颗粒度最小(Finest granurity)的传输对象。一方面,由于PDU和QoS体系有诸多参数可以设置,另一方面,SMF要控制UPF的行为,甚至还要处理有AMF转发过来的UE和RAN的请求,所以SMF的程序逻辑也是一等一的复杂——仅次于AMF的复杂。
在TS29.502中,SMF的功能被划分为3组,其中最重要的自然是Nsmf_PDUSession
,负责管理PDU Session的建立、维护、和删除。而其他两组服务都是补充性的功能:Nsmf_EventExposure
是定义了PDU Session中一些重要事件,其他NF可以订阅这些事件,当事件发生时由SMF去通知它们;而Nsmf_NIDD
指的是不通过IP协议栈的数据送达服务(Non-IP Data Delivery),主要功能就是支持从网络端向设备端发送短信,比如最常用的手机验证码。然而这个NIDD服务在free5gc中并未实现,至少我没有找到/pdu-sessions/{pduSessionRef}/deliver
这类路由。
SMF的实现
SMF的代码结构与前面研究过的NF略有不同,其一是internal/
目录下多了一个pfcp/
目录,一个看起来很重要但又不知道是个什么东西的子目录;其二是context/
目录下定义了非常多东西,预示中SMF的全生命周期中将会需要使用和管理异常多的重要数据,以及理解SMF需要下异常多功夫。
$ ls NFs/smf/internal/ context logger pfcp sbi util $ ls NFs/smf/internal/context/ bp_manager.go nf_profile.go pfcp_rules.go sm_context_policy.go traffic_control_data.go ulcl_group.go charging.go ngap_build.go pfcp_session_context.go sm_context_policy_test.go ue_datapath.go upf.go config.go ngap_handler.go pool snssai.go ue_datapath_test.go upf_test.go context.go pcc_rule.go qos_flow.go snssai_dnn_smf_info.go ue_defaultPath.go user_plane_information.go datapath.go pco.go session_rules.go timer.go ue_ip_pool.go user_plane_information_test.go gsm_build.go pfcp_reports.go sm_context.go timer_test.go ue_ip_pool_test.go
PFCP
PFCP全称Packet Forwarding Control Protocol,在TS29.244中详细定义,是SMF和UPF之间的控制协议,SMF通过PFCP协议配置各种规则(PDR、FAR、QER、URR、BAR),以控制和管理UPF的数据面转发行为。

为什么之前研究的NF之间交互不需要专门的通信协议,而SMF与UPF之间却需要?这自然是因为UPF不在核心网控制面之内,不对外暴露HTTP服务接口。free5gc/upf/internal/内部甚至都找不到一个sbi/
子目录。控制面与用户面之间的交互只能通过SMF与UPF之间专门的接口,PFCP定义的通信协议就是这其中之一,free5gc/pfcp是free5gc对此协议的实现,而smf/internal/pfcp与upf/internal/pfcp则是SMF与UPF对该协议的应用。Free5GC官方团队写了一篇讨论PFCP如何在SMF处理消息的笔记,感兴趣的可以过去阅读。
$ tree NFs/smf/internal/pfcp/ NFs/smf/internal/pfcp/ ├── handler │ ├── handler.go │ └── handler_test.go ├── message │ ├── build.go │ ├── send.go │ └── send_test.go ├── udp │ ├── udp.go │ └── udp_test.go ├── reliable_pfcp_request_test.go └── dispatcher.go
smf/internal/pfcp/
包最外层的文件dispatcher.go
就只是个请求的分发器,把smf对upf的PFCP控制消息分发发给Handler
去处理:
// https://github.com/free5gc/smf/blob/v1.2.5/internal/pfcp/dispatcher.go func Dispatch(msg *pfcpUdp.Message) { switch msg.PfcpMessage.Header.MessageType { case pfcp.PFCP_HEARTBEAT_REQUEST: handler.HandlePfcpHeartbeatRequest(msg) case pfcp.PFCP_ASSOCIATION_SETUP_REQUEST: handler.HandlePfcpAssociationSetupRequest(msg) // 。。。。。。 default: logger.PfcpLog.Errorf("Unknown PFCP message type: %d", msg.PfcpMessage.Header.MessageType) return } }
具体的Handler则调用具体的消息发送函数把控制消息发送给UPF,比如对上面的PFCP_HEARTBEAT_REQUEST
和PFCP_ASSOCIATION_SETUP_REQUEST
两类消息,对应的Handle**
函数分别是:
import ( "github.com/free5gc/pfcp" "github.com/free5gc/pfcp/pfcpUdp" pfcp_message "github.com/free5gc/smf/internal/pfcp/message" ) // https://github.com/free5gc/smf/blob/v1.2.5/internal/pfcp/handler/handler.go#L16C1-L19C2 func HandlePfcpHeartbeatRequest(msg *pfcpUdp.Message) { h := msg.PfcpMessage.Header pfcp_message.SendHeartbeatResponse(msg.RemoteAddr, h.SequenceNumber) } // https://github.com/free5gc/smf/blob/v1.2.5/internal/pfcp/handler/handler.go#L25C1-L49C2 func HandlePfcpAssociationSetupRequest(msg *pfcpUdp.Message) { req := msg.PfcpMessage.Body.(pfcp.PFCPAssociationSetupRequest) nodeID := req.NodeID upf := smf_context.RetrieveUPFNodeByNodeID(*nodeID) upf.UPIPInfo = *req.UserPlaneIPResourceInformation cause := pfcpType.Cause{ CauseValue: pfcpType.CauseRequestAccepted, } pfcp_message.SendPfcpAssociationSetupResponse(msg.RemoteAddr, cause) }
可见Handle**
函数也只是把收到的msg稍加处理,然后继续调用Send**
函数。
import ( "github.com/free5gc/pfcp" "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/pfcp/pfcpUdp" "github.com/free5gc/smf/internal/pfcp/udp" ) // https://github.com/free5gc/smf/blob/v1.2.5/internal/pfcp/message/send.go# func SendPfcpAssociationSetupResponse(addr *net.UDPAddr, cause pfcpType.Cause) { pfcpMsg, err := BuildPfcpAssociationSetupResponse(cause) message := &pfcp.Message{ Header: pfcp.Header{ Version: pfcp.PfcpVersion, MP: 0, S: pfcp.SEID_NOT_PRESENT, MessageType: pfcp.PFCP_ASSOCIATION_SETUP_RESPONSE, SequenceNumber: 1, }, Body: pfcpMsg, } udp.SendPfcpResponse(message, addr) }
具体来说,Send**
函数会会提取消息中的有用信息,稍加处理后然后重新Build**
一个PFCP消息,把这个消息交由UDP服务器发出去
// https://github.com/free5gc/smf/blob/v1.2.5/internal/pfcp/message/build.go#L29C1-L45C2 func BuildPfcpAssociationSetupResponse(cause pfcpType.Cause) (pfcp.PFCPAssociationSetupResponse, error) { msg := pfcp.PFCPAssociationSetupResponse{} msg.NodeID = &context.GetSelf().CPNodeID msg.Cause = &cause msg.RecoveryTimeStamp = &pfcpType.RecoveryTimeStamp{ RecoveryTimeStamp: udp.ServerStartTime } msg.CPFunctionFeatures = &pfcpType.CPFunctionFeatures{ SupportedFeatures: 0 } return msg, nil }
所有的PCFCP消息都会有一个udp服务器发送给UPF,这个服务器也负责接收从UPF发送过来的消息给SMF处理。因此,SMF除了运行一个sbiserver与控制面中其他NF交互以外,还运行一个pfcpserver用于和UPF交互,前者使用哦干的事HTTP协议,后者用的是PFCp协议。在pkg/service/init.go
中还可以看到相应的放服务器启动和停止的代码:
// https://github.com/free5gc/smf/blob/v1.2.5/pkg/service/init.go#L30 type SmfApp struct { // ...... pfcpStart func(*SmfApp) pfcpTerminate func() } // https://github.com/free5gc/smf/blob/v1.2.5/pkg/service/init.go#L162C1-L177C2 func (a *SmfApp) Start() { err := a.sbiServer.Run(context.Background(), &a.wg) a.pfcpStart(a) a.WaitRoutineStopped() } // https://github.com/free5gc/smf/blob/v1.2.5/pkg/service/init.go#L196C1-L210C2 func (a *SmfApp) terminateProcedure() { a.pfcpTerminate() problemDetails, err := a.Consumer().SendDeregisterNFInstance() a.sbiServer.Stop() logger.MainLog.Infof("SMF SBI Server terminated") }
Nsmf_PDUSession
接下来是重头戏,SMF中的PDU session管理,即其建立、删除、和更新。

可以看到Nsmf_PDUSession
中有两组增删改操作,分别针对SMcontext以及PDUSession,它们有什么异同?从概念上看,SMContext是SMF内部维护的一个数据结构,用于存储和管理单个PDU Session的所有相关信息,包含了PDU Session的配置、状态、QoS规则、UPF选择、IP地址分配等完整信息,是SMF用来管理PDU会话的内部实现机制。而PDU Session则是UE和数据网络之间的逻辑连接,代表实际的用户数据传输通道。可以认为,SMContext的创建过程包含了PDU Session的创建。在非漫游场景下,SMContext的创建和PDU Session的建立是一体的,由AMF触发;只有在漫游场景下,这两个对象才会分开:访问网络的V-SMF维护SMContext,家网络的H-SMF通过PDU Session接口控制实际的会话建立。不过free5gc目前没有支持漫游场景,所以PDU Session相关的接口都是"Not Implemented":
// https://github.com/free5gc/smf/blob/v1.2.5/internal/sbi/api_pdusession.go#L63 func (s *Server) PostPduSessions(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{}) } func (s *Server) UpdatePduSession(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{}) } func (s *Server) ReleasePduSession(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{}) }
free5gc目前只支持非漫游场景,而SMContext及其PDU Session的增删改三个操作都写在smf/internal/sbi/processor/pdu_session.go这个千行代码的文件里。
以下代码是SMContext的创建操作。操作的结果就是在smContextPool
中创建了一个SmContext
类型,把用户ID、设备ID、PDU session、UPF实例、UPT通道等全部绑定在一起:
// https://github.com/free5gc/smf/blob/v1.2.5/internal/context/sm_context.go#L26 var ( smContextPool sync.Map ) // https://github.com/free5gc/smf/blob/v1.2.5/internal/context/sm_context.go#L112 type SMContext struct { *models.SmContextCreateData Ref string // Reference of this SMContext UnauthenticatedSupi bool // Subscriber Permanent Identity Pei string // Permanent Equipment Identity PDUSessionID int32 Tunnel *UPTunnel SelectedUPF *UPNode SMPolicyID string ChargingID int32 // omitted 100+ lines ...... }
留意它的逻辑不止在于创建了一个SMContext类型smf_context.NewSMContext(createData.Supi, createData.PduSessionId)
和一个PDU Session类型HandlePDUSessionEstablishmentRequest(smContext, establishmentRequest)
,还有与PCF交互获取相关策略规则,应用这些规则,对PDU Session开始计费,与UPF交互激活数据面等一系列操作。
// https://github.com/free5gc/smf/blob/v1.2.5/internal/sbi/processor/pdu_session.go#L24 func (p *Processor) HandlePDUSessionSMContextCreate( c *gin.Context, request models.PostSmContextsRequest, isDone <-chan struct{}, ) { var response models.PostSmContextsResponse response.JsonData = new(models.SmContextCreatedData) // 创建SMContext并初始化基本信息 createData := request.JsonData smContext := smf_context.NewSMContext(createData.Supi, createData.PduSessionId) smContext.SetState(smf_context.ActivePending) smContext.SmContextCreateData = createData smContext.SmStatusNotifyUri = createData.SmContextStatusUri // 获取用户签约数据 smContext.DNNInfo = smf_context.RetrieveDnnInformation(smContext.SNssai, smContext.Dnn) p.Consumer().SendNFDiscoveryUDM() // smDataParams <- Dnn, PlmnId, SingleNssai sessSubData, rsp, err := p.Consumer().GetSmData(ctx, smContext.Supi, smDataParams) smContext.DnnConfiguration = sessSubData[0].DnnConfigurations[smContext.Dnn] if smContext.DnnConfiguration.UpSecurity != nil { smContext.UpSecurity = smContext.DnnConfiguration.UpSecurity } // 处理PDU Session的创建,主要处理PDU Session建立的初始化配置 // 完整的建立过程还要配合而后续的IP地址分配、数据路径建立、UPF激活等 m := nas.NewMessage() m.GsmMessageDecode(&request.BinaryDataN1SmMessage) establishmentRequest := m.PDUSessionEstablishmentRequest HandlePDUSessionEstablishmentRequest(smContext, establishmentRequest) // Discover and new Namf_Comm client for use later p.Consumer().SendNFDiscoveryServingAMF(smContext) for _, service := range *smContext.AMFProfile.NfServices { if service.ServiceName == models.ServiceName_NAMF_COMM { smContext.CommunicationClientApiPrefix = service.ApiPrefix } } // 给用户设备分配IP地址 smContext.AllocUeIP() // 与PCF建立会话关联,获取策略 p.Consumer().PCFSelection(smContext) smPolicyID, smPolicyDecision, err := p.Consumer().SendSMPolicyAssociationCreate(smContext) smContext.SMPolicyID = smPolicyID // 针对PDU Session计费 p.Consumer().CHFSelection(smContext) p.CreateChargingSession(smContext) // 应用PCF的策略规则 smContext.ApplySessionRules(smPolicyDecision) smContext.ApplyPccRules(smPolicyDecision) // 选择默认数据路径(没有就会创建一个) smContext.SelectDefaultDataPath() // 激活UPF会话(建立用户面/数据面) go func() { smContext.SendUpPathChgNotification("EARLY", SendUpPathChgEventExposureNotification) handler := func(smContext *smf_context.SMContext, success bool) { p.EstHandler(isDone, smContext, success) } ActivateUPFSession(smContext, handler) smContext.SendUpPathChgNotification("LATE", SendUpPathChgEventExposureNotification) smContext.PostRemoveDataPath() }() c.Render(http.StatusCreated, openapi.MultipartRelatedRender{Data: response}) }
SmContextCreateData,HandlePDUSessionEstablishmentRequest
// https://github.com/free5gc/openapi/blob/v1.0.8/models/model_sm_context_create_data.go type SmContextCreateData struct { Supi string `json:"supi,omitempty"` UnauthenticatedSupi bool `json:"unauthenticatedSupi,omitempty"` Pei string `json:"pei,omitempty"` Gpsi string `json:"gpsi,omitempty"` PduSessionId int32 `json:"pduSessionId,omitempty"` Dnn string `json:"dnn,omitempty"` SNssai *Snssai `json:"sNssai,omitempty"` HplmnSnssai *Snssai `json:"hplmnSnssai,omitempty"` ServingNfId string `json:"servingNfId"` Guami *Guami `json:"guami,omitempty"` ServiceName ServiceName `json:"serviceName,omitempty"` ServingNetwork *PlmnId `json:"servingNetwork"` RequestType RequestType `json:"requestType,omitempty"` N1SmMsg *RefToBinaryData `json:"n1SmMsg,omitempty"` AnType AccessType `json:"anType"` RatType RatType `json:"ratType,omitempty"` PresenceInLadn PresenceState `json:"presenceInLadn,omitempty"` UeLocation *UserLocation `json:"ueLocation,omitempty"` UeTimeZone string `json:"ueTimeZone,omitempty"` AddUeLocation *UserLocation `json:"addUeLocation,omitempty"` SmContextStatusUri string `json:"smContextStatusUri"` HSmfUri string `json:"hSmfUri,omitempty"` AdditionalHsmfUri []string `json:"additionalHsmfUri,omitempty"` OldPduSessionId int32 `json:"oldPduSessionId,omitempty"` PduSessionsActivateList []int32 `json:"pduSessionsActivateList,omitempty"` UeEpsPdnConnection string `json:"ueEpsPdnConnection,omitempty"` HoState HoState `json:"hoState,omitempty"` PcfId string `json:"pcfId,omitempty"` NrfUri string `json:"nrfUri,omitempty"` SupportedFeatures string `json:"supportedFeatures,omitempty"` SelMode DnnSelectionMode `json:"selMode,omitempty"` BackupAmfInfo []BackupAmfInfo `json:"backupAmfInfo,omitempty"` TraceData *TraceData `json:"traceData,omitempty"` UdmGroupId string `json:"udmGroupId,omitempty"` RoutingIndicator string `json:"routingIndicator,omitempty"` EpsInterworkingInd EpsInterworkingIndication `json:"epsInterworkingInd,omitempty"` IndirectForwardingFlag bool `json:"indirectForwardingFlag,omitempty"` }
// https://github.com/free5gc/smf/blob/v1.2.5/internal/sbi/processor/gsm_handler.go#L25 func HandlePDUSessionEstablishmentRequest( smCtx *smf_context.SMContext, req *nasMessage.PDUSessionEstablishmentRequest, ) error { // Retrieve PDUSessionID smCtx.PDUSessionID = int32(req.PDUSessionID.GetPDUSessionID()) logger.GsmLog.Infoln("In HandlePDUSessionEstablishmentRequest") // Retrieve PTI (Procedure transaction identity) smCtx.Pti = req.GetPTI() // Retrieve MaxIntegrityProtectedDataRate of UE for UP Security switch req.GetMaximumDataRatePerUEForUserPlaneIntegrityProtectionForUpLink() { case 0x00: smCtx.MaximumDataRatePerUEForUserPlaneIntegrityProtectionForUpLink = models. MaxIntegrityProtectedDataRate__64_KBPS case 0xff: smCtx.MaximumDataRatePerUEForUserPlaneIntegrityProtectionForUpLink = models. MaxIntegrityProtectedDataRate_MAX_UE_RATE } switch req.GetMaximumDataRatePerUEForUserPlaneIntegrityProtectionForDownLink() { case 0x00: smCtx.MaximumDataRatePerUEForUserPlaneIntegrityProtectionForDownLink = models. MaxIntegrityProtectedDataRate__64_KBPS case 0xff: smCtx.MaximumDataRatePerUEForUserPlaneIntegrityProtectionForDownLink = models. MaxIntegrityProtectedDataRate_MAX_UE_RATE } // Handle PDUSessionType if req.PDUSessionType != nil { requestedPDUSessionType := req.PDUSessionType.GetPDUSessionTypeValue() if err := smCtx.IsAllowedPDUSessionType(requestedPDUSessionType); err != nil { logger.CtxLog.Errorf("%s", err) return &GSMError{ GSMCause: nasMessage.Cause5GSMPDUSessionTypeIPv4OnlyAllowed, } } } else { // Set to default supported PDU Session Type switch smf_context.GetSelf().SupportedPDUSessionType { case "IPv4": smCtx.SelectedPDUSessionType = nasMessage.PDUSessionTypeIPv4 case "IPv6": smCtx.SelectedPDUSessionType = nasMessage.PDUSessionTypeIPv6 case "IPv4v6": smCtx.SelectedPDUSessionType = nasMessage.PDUSessionTypeIPv4IPv6 case "Ethernet": smCtx.SelectedPDUSessionType = nasMessage.PDUSessionTypeEthernet default: smCtx.SelectedPDUSessionType = nasMessage.PDUSessionTypeIPv4 } } return nil }
SMContext的删除(释放)也不仅仅在于删掉一个SMContext对象releaseSession(smContext)
,还需要删除其与PCF中policy的联系,修改状态机的状态,删除相应的计费过程,告知所有NF自己已经下线等。
// https://github.com/free5gc/smf/blob/v1.2.5/internal/sbi/processor/pdu_session.go#L841 func (p *Processor) HandlePDUSessionSMContextRelease( c *gin.Context, body models.ReleaseSmContextRequest, smContextRef string, ) { smContext := smf_context.GetSMContextByRef(smContextRef) smContext.StopT3591() // TS24.501 Table-10.3.3: Timers of 5GS session management smContext.StopT3592() // remove SM Policy Association if smContext.SMPolicyID != "" { p.Consumer().SendSMPolicyAssociationTermination(smContext) // to PCF smContext.SMPolicyID = "" } if smContext.UeCmRegistered { p.Consumer().UeCmDeregistration(smContext) } if !smContext.CheckState(smf_context.InActive) { smContext.SetState(smf_context.PFCPModification) } pfcpResponseStatus := releaseSession(smContext) switch pfcpResponseStatus { case smf_context.SessionReleaseSuccess: p.ReleaseChargingSession(smContext) smContext.SetState(smf_context.InActive) c.Status(http.StatusNoContent) case smf_context.SessionReleaseFailed: // Update SmContext Request(N1 PDU Session Release Request) // Send PDU Session Release Reject smContext.SetState(smf_context.Active) buf, err := smf_context.BuildGSMPDUSessionReleaseReject(smContext) errResponse.BinaryDataN1SmMessage = buf errResponse.JsonData.N1SmMsg = &models.RefToBinaryData{ContentId: "PDUSessionReleaseReject"} c.JSON(int(problemDetail.Status), errResponse) default: smContext.SetState(smf_context.Active) buf, err := smf_context.BuildGSMPDUSessionReleaseReject(smContext) errResponse.BinaryDataN1SmMessage = buf errResponse.JsonData.N1SmMsg = &models.RefToBinaryData{ContentId: "PDUSessionReleaseReject"} c.JSON(int(problemDetail.Status), errResponse) } p.RemoveSMContextFromAllNF(smContext, false) }
稍微解释一下StopT3591()
和StopT3591()
。这是一个简单的计时器机制,用于确保PDU会话修改过程的可靠性。T3591计时器在SMF发送PDU SESSION MODIFICATION COMMAND
消息给UE后启动,收到UE的PDU SESSION MODIFICATION COMPLETE
响应后停止。如果计时器超时还没收到响应,SMF会重传PDU SESSION MODIFICATION COMMAND
消息给UE。如果多次重传后仍然失败,SMF会认为修改操作失败并采取相应措施。T3592同理,不过处理的消息是PDU SESSION RELEASE COMMAND
。
后续函数调用链:releaseSession
func releaseSession(smContext *smf_context.SMContext) smf_context.PFCPSessionResponseStatus { smContext.SetState(smf_context.PFCPModification) for _, res := range ReleaseTunnel(smContext) { if res.Status != smf_context.SessionReleaseSuccess { return res.Status } } return smf_context.SessionReleaseSuccess } func ReleaseTunnel(smContext *smf_context.SMContext) []SendPfcpResult { resChan := make(chan SendPfcpResult) deletedPFCPNode := make(map[string]bool) for _, dataPath := range smContext.Tunnel.DataPathPool { var targetNodes []*smf_context.DataPathNode for node := dataPath.FirstDPNode; node != nil; node = node.Next() { targetNodes = append(targetNodes, node) } dataPath.DeactivateTunnelAndPDR(smContext) for _, node := range targetNodes { curUPFID, err := node.GetUPFID() if err != nil { logger.PduSessLog.Error(err) continue } if _, exist := deletedPFCPNode[curUPFID]; !exist { go deletePfcpSession(node.UPF, smContext, resChan) deletedPFCPNode[curUPFID] = true } } } // collect all responses resList := make([]SendPfcpResult, 0, len(deletedPFCPNode)) for i := 0; i < len(deletedPFCPNode); i++ { resList = append(resList, <-resChan) } return resList } func (p *Processor) RemoveSMContextFromAllNF(smContext *smf_context.SMContext, sendNotification bool) { smContext.SetState(smf_context.InActive) // remove SM Policy Association if smContext.SMPolicyID != "" { p.Consumer().SendSMPolicyAssociationTermination(smContext) smContext.SMPolicyID = "" } go p.sendSMContextStatusNotificationAndRemoveSMContext(smContext, sendNotification) } func (p *Processor) sendSMContextStatusNotificationAndRemoveSMContext( smContext *smf_context.SMContext, sendNotification bool, ) { smContext.SMLock.Lock() defer smContext.SMLock.Unlock() if sendNotification && len(smContext.SmStatusNotifyUri) != 0 { p.SendReleaseNotification(smContext) } smf_context.RemoveSMContext(smContext.Ref) } func (p *Processor) SendReleaseNotification(smContext *smf_context.SMContext) { problemDetails, err := p.Consumer().SendSMContextStatusNotification(smContext.SmStatusNotifyUri) } func RemoveSMContext(ref string) { var smContext *SMContext value, ok := smContextPool.Load(ref) smContext = value.(*SMContext) if smContext.SelectedUPF != nil && smContext.PDUAddress != nil { GetUserPlaneInformation().ReleaseUEIP(smContext.SelectedUPF, smContext.PDUAddress, smContext.UseStaticIP) smContext.SelectedUPF = nil } for _, pfcpSessionContext := range smContext.PFCPContext { seidSMContextMap.Delete(pfcpSessionContext.LocalSEID) } ReleaseTEID(smContext.LocalULTeid) ReleaseTEID(smContext.LocalDLTeid) smContextPool.Delete(ref) canonicalRef.Delete(canonicalName(smContext.Supi, smContext.PDUSessionID)) }
UpdateSmContextRequest, SmContextUpdateData
type UpdateSmContextRequest struct { JsonData *SmContextUpdateData `json:"jsonData,omitempty" multipart:"contentType:application/json"` BinaryDataN1SmMessage []byte `json:"binaryDataN1SmMessage,omitempty" multipart:"contentType:application/vnd.3gpp.5gnas,ref:JsonData.N1SmMsg.ContentId"` BinaryDataN2SmInformation []byte `json:"binaryDataN2SmInformation,omitempty" multipart:"contentType:application/vnd.3gpp.ngap,ref:JsonData.N2SmInfo.ContentId"` } type SmContextUpdateData struct { Pei string `json:"pei,omitempty"` Gpsi string `json:"gpsi,omitempty"` ServingNfId string `json:"servingNfId,omitempty"` Guami *Guami `json:"guami,omitempty"` ServingNetwork *PlmnId `json:"servingNetwork,omitempty"` BackupAmfInfo []BackupAmfInfo `json:"backupAmfInfo,omitempty"` AnType AccessType `json:"anType,omitempty"` RatType RatType `json:"ratType,omitempty"` PresenceInLadn PresenceState `json:"presenceInLadn,omitempty"` UeLocation *UserLocation `json:"ueLocation,omitempty"` UeTimeZone string `json:"ueTimeZone,omitempty"` AddUeLocation *UserLocation `json:"addUeLocation,omitempty"` UpCnxState UpCnxState `json:"upCnxState,omitempty"` HoState HoState `json:"hoState,omitempty"` ToBeSwitched bool `json:"toBeSwitched,omitempty"` FailedToBeSwitched bool `json:"failedToBeSwitched,omitempty"` N1SmMsg *RefToBinaryData `json:"n1SmMsg,omitempty"` N2SmInfo *RefToBinaryData `json:"n2SmInfo,omitempty"` N2SmInfoType N2SmInfoType `json:"n2SmInfoType,omitempty"` TargetId *NgRanTargetId `json:"targetId,omitempty"` TargetServingNfId string `json:"targetServingNfId,omitempty"` SmContextStatusUri string `json:"smContextStatusUri,omitempty"` DataForwarding bool `json:"dataForwarding,omitempty"` EpsBearerSetup []string `json:"epsBearerSetup,omitempty"` RevokeEbiList []int32 `json:"revokeEbiList,omitempty"` Release bool `json:"release,omitempty"` Cause Cause `json:"cause,omitempty"` NgApCause *NgApCause `json:"ngApCause,omitempty"` Var5gMmCauseValue int32 `json:"5gMmCauseValue,omitempty"` SNssai *Snssai `json:"sNssai,omitempty"` TraceData *TraceData `json:"traceData,omitempty"` EpsInterworkingInd EpsInterworkingIndication `json:"epsInterworkingInd,omitempty"` AnTypeCanBeChanged bool `json:"anTypeCanBeChanged,omitempty"` }
SMContext的更新是SMF中的一个核心功能,负责处理PDU会话的各种状态更新和转换。它需要处理多种不同类型的更新请求,并确保会话状态的正确转换。理解与之相关的HandlePDUSessionSMContextUpdate函数也需要大量前置知识,比如AMF与SMF之间的N1、N2接口,各种状态机的状态转移等。由于内容众多,详情见后文。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY