Free5GC原始碼研究(10) - SMF研究(上)

zrq96發表於2024-11-28

本文研究Session Management Function (SMF)的功能

SMF的概念

對於free5gc各NF的研究來到了最終階段,只剩SMF和AMF兩個功能,是時候回顧一下TS23.501中的這幾張網路架構圖。首先是這一張經典的非漫遊情境下各NF的互動架構:
Free5GC原始碼研究(10) - SMF研究(上)

這張圖裡,核心網所有的NF透過SBI匯流排相連,本質上就是說所有NF都可以透過HTTP協議呼叫其他NF提供的服務。核心網中的SMF和AMF兩個功能比較特殊,因為它們除了與核心網中其他NF互動,還與核心網外的其他實體互動:AMF要與使用者裝置和接入網通訊,而SMF則要與使用者面通訊。下面這張圖也展示了5G的系統架構,不過把各NF之間的存在的互動全部顯式的展示了出來,所以看起來會有點雜亂。而且留意圖中兩個藍色的SMF和兩個綠色的UPF,這是說明一個網路中每一個NF可以有多個例項。這張圖描述的場景是使用者裝置與網路建立多個PDU session進行通訊,每個PDU session由一個SMF和一個UPF來維護。

Free5GC原始碼研究(10) - SMF研究(上)

下面這張圖則更有利於我們全面地理解SMF的功能:漫遊情境下兩個網路間各NF地互動。

Free5GC原始碼研究(10) - SMF研究(上)

這張圖除了看起來更雜亂以外,還多了幾個新名詞。首先是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這類路由

img

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的資料面轉發行為。

Free5GC原始碼研究(10) - SMF研究(上)

為什麼之前研究的NF之間互動不需要專門的通訊協議,而SMF與UPF之間卻需要?這自然是因為UPF不在核心網控制面之內,不對外暴露HTTP服務介面。free5gc/upf/internal/內部甚至都找不到一個sbi/子目錄。控制面與使用者面之間的互動只能透過SMF與UPF之間專門的介面,PFCP定義的通訊協議就是這其中之一,而smf/internal/pfcpupf/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管理,即其建立、刪除、和更新。

Free5GC原始碼研究(10) - SMF研究(上)

可以看到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控制。

相關文章