本文研究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的问题。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定义的通信协议就是这其中之一,而smf/internal/pfcp与upf/internal/pfcp则是此协议的实现。
$ tree NFs/smf/internal/pfcp/
NFs/smf/internal/pfcp/
├── dispatcher.go
├── handler
│ ├── handler.go
│ └── handler_test.go
├── message
│ ├── build.go
│ ├── send.go
│ └── send_test.go
├── reliable_pfcp_request_test.go
└── udp
├── udp.go
└── udp_test.go
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的创建操作。留意它的逻辑不止在于创建了一个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接口,各种状态机的状态转移等。
// https://github.com/free5gc/smf/blob/v1.2.5/internal/sbi/processor/pdu_session.go#L245
func (p *Processor) HandlePDUSessionSMContextUpdate(
c *gin.Context,
body models.UpdateSmContextRequest,
smContextRef string,
) {
smContext := smf_context.GetSMContextByRef(smContextRef)
var sendPFCPModification bool
var pfcpResponseStatus smf_context.PFCPSessionResponseStatus
var response models.UpdateSmContextResponse
response.JsonData = new(models.SmContextUpdatedData)
smContextUpdateData := body.JsonData
// 处理N1接口的消息(AMF转发)
if body.BinaryDataN1SmMessage != nil {
m := nas.NewMessage()
switch m.GsmHeader.GetMessageType() {
case nas.MsgTypePDUSessionReleaseRequest:
// release PDU Session
smContext.SetState(smf_context.PFCPModification)
pfcpResponseStatus = releaseSession(smContext)
case nas.MsgTypePDUSessionReleaseComplete:
// PDU Session released, wrap up things
smContext.CheckState(smf_context.InActivePending)
smContext.SetState(smf_context.InActive)
response.JsonData.UpCnxState = models.UpCnxState_DEACTIVATED
smContext.StopT3592()
// If CN tunnel resource is released, should
if smContext.Tunnel.ANInformation.IPAddress == nil {
p.RemoveSMContextFromAllNF(smContext, true)
}
case nas.MsgTypePDUSessionModificationRequest:
// modify PDU session
p.HandlePDUSessionModificationRequest(smContext, m.PDUSessionModificationRequest)
p.sendGSMPDUSessionModificationCommand(smContext, buf)
smf_context.BuildPDUSessionResourceModifyRequestTransfer(smContext)
c.Render(http.StatusOK, openapi.MultipartRelatedRender{Data: response})
return
case nas.MsgTypePDUSessionModificationComplete:
smContext.StopT3591()
case nas.MsgTypePDUSessionModificationReject:
smContext.StopT3591()
}
}
/* ================================================================ */
// 变更连接状态(UE与UPF之间的数据传输通道)
switch smContextUpdateData.UpCnxState {
case models.UpCnxState_ACTIVATING:
smContext.SetState(smf_context.ModificationPending)
n2Buf, err = smf_context.BuildPDUSessionResourceSetupRequestTransfer(smContext)
smContext.UpCnxState = models.UpCnxState_ACTIVATING
case models.UpCnxState_DEACTIVATED:
smContext.SetState(smf_context.ModificationPending)
smContext.UpCnxState = body.JsonData.UpCnxState
// Set FAR and AN, N3 Release Info
// 修改FAR规则,停止转发,开启缓存
farList = []*smf_context.FAR{}
for _, dataPath := range smContext.Tunnel.DataPathPool {
ANUPF := dataPath.FirstDPNode
DLPDR := ANUPF.DownLinkTunnel.PDR
if DLPDR == nil {
smContext.Log.Warnf("Access network resource is released")
} else {
DLPDR.FAR.State = smf_context.RULE_UPDATE
DLPDR.FAR.ApplyAction.Forw = false
DLPDR.FAR.ApplyAction.Buff = true
DLPDR.FAR.ApplyAction.Nocp = true
farList = append(farList, DLPDR.FAR)
sendPFCPModification = true
smContext.SetState(smf_context.PFCPModification)
}
}
}
/* ================================================================ */
// 处理N2接口的消息(AMF转发)
switch smContextUpdateData.N2SmInfoType {
// setup PDU session resource
case models.N2SmInfoType_PDU_RES_SETUP_RSP:
smContext.SetState(smf_context.ModificationPending)
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: &pfcpType.NetworkInstance{
NetworkInstance: smContext.Dnn,
FQDNEncoding: factory.SmfConfig.Configuration.NwInstFqdnEncoding,
},
}
DLPDR.State = smf_context.RULE_UPDATE
DLPDR.FAR.State = smf_context.RULE_UPDATE
pdrList = append(pdrList, DLPDR)
farList = append(farList, DLPDR.FAR)
}
}
smf_context.HandlePDUSessionResourceSetupResponseTransfer(
body.BinaryDataN2SmInformation, smContext)
sendPFCPModification = true
smContext.SetState(smf_context.PFCPModification)
case models.N2SmInfoType_PDU_RES_SETUP_FAIL:
smf_context.HandlePDUSessionResourceSetupUnsuccessfulTransfer(
body.BinaryDataN2SmInformation, smContext)
case models.N2SmInfoType_PDU_RES_MOD_RSP:
smf_context.HandlePDUSessionResourceModifyResponseTransfer(
body.BinaryDataN2SmInformation, smContext)
// release PDU session resource
case models.N2SmInfoType_PDU_RES_REL_RSP:
// remove an tunnel info
smContext.Tunnel.ANInformation = struct {
IPAddress net.IP
TEID uint32
}{nil, 0}
p.RemoveSMContextFromAllNF(smContext, true)
case models.N2SmInfoType_PATH_SWITCH_REQ:
smf_context.HandlePathSwitchRequestTransfer(
body.BinaryDataN2SmInformation, smContext)
smf_context.BuildPathSwitchRequestAcknowledgeTransfer(smContext)
for _, dataPath := range tunnel.DataPathPool {
if dataPath.Activated {
ANUPF := dataPath.FirstDPNode
DLPDR := ANUPF.DownLinkTunnel.PDR
pdrList = append(pdrList, DLPDR)
farList = append(farList, DLPDR.FAR)
}
}
smContext.SetState(smf_context.PFCPModification)
case models.N2SmInfoType_PATH_SWITCH_SETUP_FAIL:
smContext.SetState(smf_context.ModificationPending)
smf_context.HandlePathSwitchRequestSetupFailedTransfer(
body.BinaryDataN2SmInformation, smContext)
case models.N2SmInfoType_HANDOVER_REQUIRED:
smContext.SetState(smf_context.ModificationPending)
response.JsonData.N2SmInfo = &models.RefToBinaryData{ContentId: "Handover"}
}
/* ================================================================ */
// 处理HoState(Handover State,指UE在不同基站/接入网之间迁移过程中的状态)
switch smContextUpdateData.HoState {
case models.HoState_PREPARING:
smContext.SetState(smf_context.ModificationPending)
smContext.HoState = models.HoState_PREPARING
smf_context.HandleHandoverRequiredTransfer(
body.BinaryDataN2SmInformation, smContext)
smf_context.BuildPDUSessionResourceSetupRequestTransfer(smContext)
response.JsonData.HoState = models.HoState_PREPARING
case models.HoState_PREPARED:
smContext.SetState(smf_context.ModificationPending)
smContext.HoState = models.HoState_PREPARED
response.JsonData.HoState = models.HoState_PREPARED
smf_context.HandleHandoverRequestAcknowledgeTransfer(
body.BinaryDataN2SmInformation, smContext)
// request UPF establish indirect forwarding path for DL
if smContext.DLForwardingType == smf_context.IndirectForwarding {
ANUPF := smContext.IndirectForwardingTunnel.FirstDPNode
IndirectForwardingPDR := smContext.IndirectForwardingTunnel.FirstDPNode.UpLinkTunnel.PDR
pdrList = append(pdrList, IndirectForwardingPDR)
farList = append(farList, IndirectForwardingPDR.FAR)
// release indirect forwading path
if err = ANUPF.UPF.RemovePDR(IndirectForwardingPDR); err != nil {
logger.PduSessLog.Errorln("release indirect path: ", err)
}
sendPFCPModification = true
smContext.SetState(smf_context.PFCPModification)
}
smf_context.BuildHandoverCommandTransfer(smContext)
response.JsonData.HoState = models.HoState_PREPARING
case models.HoState_COMPLETED:
for _, dataPath := range tunnel.DataPathPool {
if dataPath.Activated {
ANUPF := dataPath.FirstDPNode
DLPDR := ANUPF.DownLinkTunnel.PDR
pdrList = append(pdrList, DLPDR)
farList = append(farList, DLPDR.FAR)
}
}
// remove indirect forwarding path
if smContext.DLForwardingType == smf_context.IndirectForwarding {
indirectForwardingPDR := smContext.IndirectForwardingTunnel.FirstDPNode.GetUpLinkPDR()
indirectForwardingPDR.State = smf_context.RULE_REMOVE
indirectForwardingPDR.FAR.State = smf_context.RULE_REMOVE
pdrList = append(pdrList, indirectForwardingPDR)
farList = append(farList, indirectForwardingPDR.FAR)
}
smContext.SetState(smf_context.PFCPModification)
smContext.HoState = models.HoState_COMPLETED
response.JsonData.HoState = models.HoState_COMPLETED
}
/* ================================================================ */
// 根据PDU Session状态机的转移采取行动
switch smContext.State() {
case smf_context.PFCPModification:
pfcpResponseStatus = p.updateAnUpfPfcpSession(
smContext, pdrList, farList, barList, qerList, urrList)
// 处理PFCP更新的结果
switch pfcpResponseStatus {
case smf_context.SessionUpdateSuccess:
smContext.SetState(smf_context.Active)
c.Render(http.StatusOK, openapi.MultipartRelatedRender{Data: response})
case smf_context.SessionUpdateFailed:
smContext.SetState(smf_context.Active)
updateSmContextError := models.UpdateSmContextErrorResponse{
JsonData: &models.SmContextUpdateError{
Error: &Nsmf_PDUSession.N1SmError,
},
} // Depends on the reason why N4 fail
c.JSON(http.StatusForbidden, updateSmContextError)
case smf_context.SessionReleaseSuccess:
p.ReleaseChargingSession(smContext)
smContext.SetState(smf_context.InActivePending)
c.Render(http.StatusOK, openapi.MultipartRelatedRender{Data: response})
case smf_context.SessionReleaseFailed:
// Update SmContext Request(N1 PDU Session Release Request)
// Send PDU Session Release Reject
smContext.SetState(smf_context.Active)
// problemDetail := models.ProblemDetails{
// 。。。。。。
c.JSON(int(problemDetail.Status), errResponse)
}
smContext.PostRemoveDataPath()
case smf_context.ModificationPending:
smContext.SetState(smf_context.Active)
c.Render(http.StatusOK, openapi.MultipartRelatedRender{Data: response})
case smf_context.InActive, smf_context.InActivePending:
c.Render(http.StatusOK, openapi.MultipartRelatedRender{Data: response})
default:
c.Render(http.StatusOK, openapi.MultipartRelatedRender{Data: response})
}
}
- GSM
- UPF:用户面功能,承载数据转发的网络功能
- ngap
- H-SMF, I-SMF, V-SMF
- NAS message
QoS机制就是通过NAS层面和AS层面的数据映射实现对用户底层数据的分类;根据业务特征来标记其QoS特征(5QI或QFI),并将这些特征分发给网络传输节点,比如通过QoS配置来实现NG-RAN的QoS控制,根据PCC规则(UPF的PDR)来实现在UPF处的QoS控制。
标签:10,Free5GC,json,源码,context,go,smf,omitempty,smContext From: https://www.cnblogs.com/zrq96/p/18546683