Free5GC原始碼研究(9) - PCF研究(下)

zrq96發表於2024-11-12

前文再續書接上一回,繼續研究Free5GC中所實現的PCF的另外兩組服務:SMPolicy和PolicyAuthorization

SMPolicy

PCF中與SMF的互動,對session的控制有著很重的的分量,甚至連TS23.503中對與Policy Control的定義都是指PCF指示SMF去控制QoS流的過程。

Policy control: The process whereby the PCF indicates to the SMF how to control the QoS Flow. Policy control includes QoS control and/or gating control.

因為PCF的SMPolicy深度涉及了SMF的管理,因此想要研究SMPolicy就得先了解SMF相關的一些重要概念

  • QoS Flow 首先是上面提及的QoS流,全稱Quality of Service Flow,是一種服務質量得到保證的傳輸資料流,這裡的服務質量包括但不鹹魚傳輸速率和穩定性。每個QoS流都會由自己的ID(QFI),而一個QoS流的本質就是所有包含同一個QFI的資料包,它們在傳輸的時候會根據QFI得到不同的對待。QoS Flow的建立、維護、刪除等由SMF負責。Free5GC團隊寫了一篇部落格介紹它們對於QoS的理解。
  • PDU Session 全稱Packet Data Unit Session,是使用者裝置與資料網路之間的一種邏輯連線,用於承載QoS流。一個PDU Session可以承載多個QoS流,且會有一個由SMF建立的預設QoS流。
  • PCC rule 全稱Policy and Charging Control rule,標準文件中的原文是"A set of information enabling the detection of a service data flow and providing parameters for policy control and/or charging control and/or other control or support information"。簡單來說就是用來對資料流進行分類處理和收費的判斷規則。
  • Policy Control Request trigger 其實就是SMF應該主動發起與PCF互動的條件

瞭解了這些前置概念,大概就能讀懂下面這張關於NBNpcf_SMPolicyControl的表格了
img

雖然標準文件裡沒有說明要提供GET服務,但Free5GC團隊還是貼心的實現了一個根據smPolicyId找到相應的UeSmPolicyData的函式:

// https://github.com/free5gc/pcf/blob/v1.2.5/internal/sbi/processor/smpolicy.go#L546
func (p *Processor) HandleGetSmPolicyContextRequest(c *gin.Context, smPolicyId string) {
	ue := p.Context().PCFUeFindByPolicyId(smPolicyId)
	smPolicyData := ue.SmPolicyData[smPolicyId]  // ue.SmPolicyData裡都是UeSmPolicyData型別
	response := &models.SmPolicyControl{
		Policy:  smPolicyData.PolicyDecision,
		Context: smPolicyData.PolicyContext,
	}
	c.JSON(http.StatusOK, response)
}

刪除一個smPolicy的操作不僅要刪除UDR喝context裡的smPolicy資料,還需要刪除掉與之關聯的appSessionratingGroup。這個appSession在前文說過,是“PCF用來管理和追蹤第三方應用程式對網路資源請求的機制”,所以刪除一個使用者裝置的smPolicy,就會連帶刪除掉相應的第三方應用提供的服務;而ratingGroup則是用來對smPolicy所對應的QoS流計費的機制。

// https://github.com/free5gc/pcf/blob/v1.2.5/internal/sbi/processor/smpolicy.go#L487
func (p *Processor) HandleDeleteSmPolicyContextRequest(c *gin.Context, smPolicyId string) {
	ue := p.Context().PCFUeFindByPolicyId(smPolicyId)
	pcfSelf := p.Context()
	smPolicy := ue.SmPolicyData[smPolicyId]
    // Unsubscrice UDR
	p.Consumer().RemoveInfluenceDataSubscription(ue, smPolicy.SubscriptionID)
	delete(ue.SmPolicyData, smPolicyId)

	// Release related App Session
	terminationInfo := models.TerminationInfo{
		TermCause: models.TerminationCause_PDU_SESSION_TERMINATION,
	}
	for appSessionID := range smPolicy.AppSessions {
		if val, exist := pcfSelf.AppSessionPool.Load(appSessionID); exist {
			appSession := val.(*pcf_context.AppSessionData)
			p.SendAppSessionTermination(appSession, terminationInfo)
			pcfSelf.AppSessionPool.Delete(appSessionID)
		}
	}
	for _, ratingGroup := range ue.RatingGroupData[smPolicyId] {
		pcfSelf.RatingGroupIdGenerator.FreeID(int64(ratingGroup))
		filterCharging := bson.M{
			"ratingGroup": ratingGroup,
		}
		mongoapi.RestfulAPIDeleteMany(chargingDataColl, filterCharging)
	}
	delete(ue.RatingGroupData, smPolicyId)
	c.JSON(http.StatusNoContent, nil)
}

相比於GET和DELETE的簡單邏輯,CREATE和UPDATE的邏輯要複雜許多,free5gc用了幾百行程式碼來實現其操作。因為Npcf_SMPolicyControl_Create操作不僅僅只是為一個使用者和網路間的PDU session建立一個policy assoication,還需要做很多policy決策指示SMF怎麼去控制這個PDU session(這好像是PCF最優決策存在感的操作)。

SmPolicyContextData, SmPolicyDecision
// https://github.com/free5gc/openapi/blob/v1.0.8/models/model_sm_policy_context_data.go
type SmPolicyContextData struct {
	AccNetChId              *AccNetChId            `json:"accNetChId,omitempty" yaml:"accNetChId" bson:"accNetChId" mapstructure:"AccNetChId"`
	ChargEntityAddr         *AccNetChargingAddress `json:"chargEntityAddr,omitempty" yaml:"chargEntityAddr" bson:"chargEntityAddr" mapstructure:"ChargEntityAddr"`
	Gpsi                    string                 `json:"gpsi,omitempty" yaml:"gpsi" bson:"gpsi" mapstructure:"Gpsi"`
	Supi                    string                 `json:"supi" yaml:"supi" bson:"supi" mapstructure:"Supi"`
	InterGrpIds             []string               `json:"interGrpIds,omitempty" yaml:"interGrpIds" bson:"interGrpIds" mapstructure:"InterGrpIds"`
	PduSessionId            int32                  `json:"pduSessionId" yaml:"pduSessionId" bson:"pduSessionId" mapstructure:"PduSessionId"`
	PduSessionType          PduSessionType         `json:"pduSessionType" yaml:"pduSessionType" bson:"pduSessionType" mapstructure:"PduSessionType"`
	Chargingcharacteristics string                 `json:"chargingcharacteristics,omitempty" yaml:"chargingcharacteristics" bson:"chargingcharacteristics" mapstructure:"Chargingcharacteristics"`
	Dnn                     string                 `json:"dnn" yaml:"dnn" bson:"dnn" mapstructure:"Dnn"`
	NotificationUri         string                 `json:"notificationUri" yaml:"notificationUri" bson:"notificationUri" mapstructure:"NotificationUri"`
	AccessType              AccessType             `json:"accessType,omitempty" yaml:"accessType" bson:"accessType" mapstructure:"AccessType"`
	RatType                 RatType                `json:"ratType,omitempty" yaml:"ratType" bson:"ratType" mapstructure:"RatType"`
	ServingNetwork          *NetworkId             `json:"servingNetwork,omitempty" yaml:"servingNetwork" bson:"servingNetwork" mapstructure:"ServingNetwork"`
	UserLocationInfo        *UserLocation          `json:"userLocationInfo,omitempty" yaml:"userLocationInfo" bson:"userLocationInfo" mapstructure:"UserLocationInfo"`
	UeTimeZone              string                 `json:"ueTimeZone,omitempty" yaml:"ueTimeZone" bson:"ueTimeZone" mapstructure:"UeTimeZone"`
	Pei                     string                 `json:"pei,omitempty" yaml:"pei" bson:"pei" mapstructure:"Pei"`
	Ipv4Address             string                 `json:"ipv4Address,omitempty" yaml:"ipv4Address" bson:"ipv4Address" mapstructure:"Ipv4Address"`
	Ipv6AddressPrefix       string                 `json:"ipv6AddressPrefix,omitempty" yaml:"ipv6AddressPrefix" bson:"ipv6AddressPrefix" mapstructure:"Ipv6AddressPrefix"`
	// Indicates the IPv4 address domain
	IpDomain     string                `json:"ipDomain,omitempty" yaml:"ipDomain" bson:"ipDomain" mapstructure:"IpDomain"`
	SubsSessAmbr *Ambr                 `json:"subsSessAmbr,omitempty" yaml:"subsSessAmbr" bson:"subsSessAmbr" mapstructure:"SubsSessAmbr"`
	SubsDefQos   *SubscribedDefaultQos `json:"subsDefQos,omitempty" yaml:"subsDefQos" bson:"subsDefQos" mapstructure:"SubsDefQos"`
	// Contains the number of supported packet filter for signalled QoS rules.
	NumOfPackFilter int32 `json:"numOfPackFilter,omitempty" yaml:"numOfPackFilter" bson:"numOfPackFilter" mapstructure:"NumOfPackFilter"`
	// If it is included and set to true, the online charging is applied to the PDU session.
	Online bool `json:"online,omitempty" yaml:"online" bson:"online" mapstructure:"Online"`
	// If it is included and set to true, the offline charging is applied to the PDU session.
	Offline bool `json:"offline,omitempty" yaml:"offline" bson:"offline" mapstructure:"Offline"`
	// If it is included and set to true, the 3GPP PS Data Off is activated by the UE.
	Var3gppPsDataOffStatus bool `json:"3gppPsDataOffStatus,omitempty" yaml:"3gppPsDataOffStatus" bson:"3gppPsDataOffStatus" mapstructure:"Var3gppPsDataOffStatus"`
	// If it is included and set to true, the reflective QoS is supported by the UE.
	RefQosIndication bool               `json:"refQosIndication,omitempty" yaml:"refQosIndication" bson:"refQosIndication" mapstructure:"RefQosIndication"`
	TraceReq         *TraceData         `json:"traceReq,omitempty" yaml:"traceReq" bson:"traceReq" mapstructure:"TraceReq"`
	SliceInfo        *Snssai            `json:"sliceInfo" yaml:"sliceInfo" bson:"sliceInfo" mapstructure:"SliceInfo"`
	QosFlowUsage     QosFlowUsage       `json:"qosFlowUsage,omitempty" yaml:"qosFlowUsage" bson:"qosFlowUsage" mapstructure:"QosFlowUsage"`
	ServNfId         *ServingNfIdentity `json:"servNfId,omitempty" yaml:"servNfId" bson:"servNfId" mapstructure:"ServNfId"`
	SuppFeat         string             `json:"suppFeat,omitempty" yaml:"suppFeat" bson:"suppFeat" mapstructure:"SuppFeat"`
	SmfId            string             `json:"smfId,omitempty" yaml:"smfId" bson:"smfId" mapstructure:"SmfId"`
	RecoveryTime     *time.Time         `json:"recoveryTime,omitempty" yaml:"recoveryTime" bson:"recoveryTime" mapstructure:"RecoveryTime"`
}


// https://github.com/free5gc/openapi/blob/v1.0.8/models/model_sm_policy_decision.go
type SmPolicyDecision struct {
	// A map of Sessionrules with the content being the SessionRule as described in subclause 5.6.2.7.
	SessRules map[string]*SessionRule `json:"sessRules,omitempty" yaml:"sessRules" bson:"sessRules" mapstructure:"SessRules"`
	// A map of PCC rules with the content being the PCCRule as described in subclause 5.6.2.6.
	PccRules map[string]*PccRule `json:"pccRules,omitempty" yaml:"pccRules" bson:"pccRules" mapstructure:"PccRules"`
	// If it is included and set to true, it indicates the P-CSCF Restoration is requested.
	PcscfRestIndication bool `json:"pcscfRestIndication,omitempty" yaml:"pcscfRestIndication" bson:"pcscfRestIndication" mapstructure:"PcscfRestIndication"`
	// Map of QoS data policy decisions.
	QosDecs map[string]*QosData `json:"qosDecs,omitempty" yaml:"qosDecs" bson:"qosDecs" mapstructure:"QosDecs"`
	// Map of Charging data policy decisions.
	ChgDecs      map[string]*ChargingData `json:"chgDecs,omitempty" yaml:"chgDecs" bson:"chgDecs" mapstructure:"ChgDecs"`
	ChargingInfo *ChargingInformation     `json:"chargingInfo,omitempty" yaml:"chargingInfo" bson:"chargingInfo" mapstructure:"ChargingInfo"`
	// Map of Traffic Control data policy decisions.
	TraffContDecs map[string]*TrafficControlData `json:"traffContDecs,omitempty" yaml:"traffContDecs" bson:"traffContDecs" mapstructure:"TraffContDecs"`
	// Map of Usage Monitoring data policy decisions.
	UmDecs map[string]*UsageMonitoringData `json:"umDecs,omitempty" yaml:"umDecs" bson:"umDecs" mapstructure:"UmDecs"`
	// Map of QoS characteristics for non standard 5QIs. This map uses the 5QI values as keys.
	QosChars           map[string]*QosCharacteristics `json:"qosChars,omitempty" yaml:"qosChars" bson:"qosChars" mapstructure:"QosChars"`
	ReflectiveQoSTimer int32                          `json:"reflectiveQoSTimer,omitempty" yaml:"reflectiveQoSTimer" bson:"reflectiveQoSTimer" mapstructure:"ReflectiveQoSTimer"`
	// A map of condition data with the content being as described in subclause 5.6.2.9.
	Conds            map[string]*ConditionData `json:"conds,omitempty" yaml:"conds" bson:"conds" mapstructure:"Conds"`
	RevalidationTime *time.Time                `json:"revalidationTime,omitempty" yaml:"revalidationTime" bson:"revalidationTime" mapstructure:"RevalidationTime"`
	// Indicates the offline charging is applicable to the PDU session or PCC rule.
	Offline bool `json:"offline,omitempty" yaml:"offline" bson:"offline" mapstructure:"Offline"`
	// Indicates the online charging is applicable to the PDU session or PCC rule.
	Online bool `json:"online,omitempty" yaml:"online" bson:"online" mapstructure:"Online"`
	// Defines the policy control request triggers subscribed by the PCF.
	PolicyCtrlReqTriggers []PolicyControlRequestTrigger `json:"policyCtrlReqTriggers,omitempty" yaml:"policyCtrlReqTriggers" bson:"policyCtrlReqTriggers" mapstructure:"PolicyCtrlReqTriggers"`
	// Defines the last list of rule control data requested by the PCF.
	LastReqRuleData  []RequestedRuleData `json:"lastReqRuleData,omitempty" yaml:"lastReqRuleData" bson:"lastReqRuleData" mapstructure:"LastReqRuleData"`
	LastReqUsageData *RequestedUsageData `json:"lastReqUsageData,omitempty" yaml:"lastReqUsageData" bson:"lastReqUsageData" mapstructure:"LastReqUsageData"`
	// Map of PRA information.
	PraInfos     map[string]*PresenceInfoRm `json:"praInfos,omitempty" yaml:"praInfos" bson:"praInfos" mapstructure:"PraInfos"`
	Ipv4Index    int32                      `json:"ipv4Index,omitempty" yaml:"ipv4Index" bson:"ipv4Index" mapstructure:"Ipv4Index"`
	Ipv6Index    int32                      `json:"ipv6Index,omitempty" yaml:"ipv6Index" bson:"ipv6Index" mapstructure:"Ipv6Index"`
	QosFlowUsage QosFlowUsage               `json:"qosFlowUsage,omitempty" yaml:"qosFlowUsage" bson:"qosFlowUsage" mapstructure:"QosFlowUsage"`
	SuppFeat     string                     `json:"suppFeat,omitempty" yaml:"suppFeat" bson:"suppFeat" mapstructure:"SuppFeat"`
}

// https://github.com/free5gc/openapi/blob/v1.0.8/models/model_pcc_rule.go
type PccRule struct {
	// An array of IP flow packet filter information.
	FlowInfos []FlowInformation `json:"flowInfos,omitempty" yaml:"flowInfos" bson:"flowInfos" mapstructure:"FlowInfos"`
	// A reference to the application detection filter configured at the UPF.
	AppId string `json:"appId,omitempty" yaml:"appId" bson:"appId" mapstructure:"AppId"`
	// Represents the content version of some content.
	ContVer int32 `json:"contVer,omitempty" yaml:"contVer" bson:"contVer" mapstructure:"ContVer"`
	// Univocally identifies the PCC rule within a PDU session.
	PccRuleId     string        `json:"pccRuleId" yaml:"pccRuleId" bson:"pccRuleId" mapstructure:"PccRuleId"`
	Precedence    int32         `json:"precedence,omitempty" yaml:"precedence" bson:"precedence" mapstructure:"Precedence"`
	AfSigProtocol AfSigProtocol `json:"afSigProtocol,omitempty" yaml:"afSigProtocol" bson:"afSigProtocol" mapstructure:"AfSigProtocol"`
	// Indication of application relocation possibility.
	AppReloc bool `json:"appReloc,omitempty" yaml:"appReloc" bson:"appReloc" mapstructure:"AppReloc"`
	// A reference to the QoSData policy type decision type. It is the qosId described in subclause 5.6.2.8. (NOTE)
	RefQosData []string `json:"refQosData,omitempty" yaml:"refQosData" bson:"refQosData" mapstructure:"RefQosData"`
	// A reference to the TrafficControlData policy decision type. It is the tcId described in subclause 5.6.2.10. (NOTE)
	RefTcData []string `json:"refTcData,omitempty" yaml:"refTcData" bson:"refTcData" mapstructure:"RefTcData"`
	// A reference to the ChargingData policy decision type. It is the chgId described in subclause 5.6.2.11. (NOTE)
	RefChgData []string `json:"refChgData,omitempty" yaml:"refChgData" bson:"refChgData" mapstructure:"RefChgData"`
	// A reference to UsageMonitoringData policy decision type. It is the umId described in subclause 5.6.2.12. (NOTE)
	RefUmData []string `json:"refUmData,omitempty" yaml:"refUmData" bson:"refUmData" mapstructure:"RefUmData"`
	// A reference to the condition data. It is the condId described in subclause 5.6.2.9.
	RefCondData string `json:"refCondData,omitempty" yaml:"refCondData" bson:"refCondData" mapstructure:"RefCondData"`
}

// https://github.com/free5gc/openapi/blob/v1.0.8/models/model_session_rule.go
type SessionRule struct {
	AuthSessAmbr *Ambr                 `json:"authSessAmbr,omitempty" yaml:"authSessAmbr" bson:"authSessAmbr" mapstructure:"AuthSessAmbr"`
	AuthDefQos   *AuthorizedDefaultQos `json:"authDefQos,omitempty" yaml:"authDefQos" bson:"authDefQos" mapstructure:"AuthDefQos"`
	// Univocally identifies the session rule within a PDU session.
	SessRuleId string `json:"sessRuleId" yaml:"sessRuleId" bson:"sessRuleId" mapstructure:"SessRuleId"`
	// A reference to UsageMonitoringData policy decision type. It is the umId described in subclause 5.6.2.12.
	RefUmData string `json:"refUmData,omitempty" yaml:"refUmData" bson:"refUmData" mapstructure:"RefUmData"`
	// A reference to the condition data. It is the condId described in subclause 5.6.2.9.
	RefCondData string `json:"refCondData,omitempty" yaml:"refCondData" bson:"refCondData" mapstructure:"RefCondData"`
}

與AMPolicy類似,想去記憶體裡的context看看有沒有舊的smPopliy,有的話刪掉delete(ue.SmPolicyData, smPolicyID),然後建立新的ue.NewUeSmPolicyData(smPolicyID, request, &smData)。這以後,就要開始做一系列複雜的決策, 包括但不限於指定適用的Session rules和PCC rules,從資料庫獲取QoS流規則和流量控制規則並決定流量控制、收費、和QoS引數等。決策的複雜性很大程度上導致了CREATE操作的複雜性——太多東西要決定了。

// https://github.com/free5gc/pcf/blob/v1.2.5/internal/sbi/processor/smpolicy.go#L28C1-L484C2
func (p *Processor) HandleCreateSmPolicyRequest(c *gin.Context, request models.SmPolicyContextData) {

    // set up pcf context, uw context, udr client ......

	var smData models.SmPolicyData
	smPolicyID := fmt.Sprintf("%s-%d", ue.Supi, request.PduSessionId)
	smPolicyData := ue.SmPolicyData[smPolicyID]  // 這個`smPolicyData`是`UeSmPolicyData`型別
	if smPolicyData == nil || smPolicyData.SmPolicyData == nil {
		param := Nudr_DataRepository.PolicyDataUesUeIdSmDataGetParamOpts{
			Snssai: optional.NewInterface(util.MarshToJsonString(*request.SliceInfo)),
			Dnn:    optional.NewString(request.Dnn),
		}
		smData, response, err1 = client.DefaultApi.PolicyDataUesUeIdSmDataGet(ctx, ue.Supi, &param)
	} else {
		smData = *smPolicyData.SmPolicyData
	}

	if smPolicyData != nil {
		delete(ue.SmPolicyData, smPolicyID)  // 刪掉舊的smPolicy
	}
	smPolicyData = ue.NewUeSmPolicyData(smPolicyID, request, &smData)  // 建立新的smPolicy

	// 根據SMF發過來的請求以及資料庫中的資料進行Policy Decision
	decision := models.SmPolicyDecision{
		SessRules:     make(map[string]*models.SessionRule),
		PccRules:      make(map[string]*models.PccRule),
		TraffContDecs: make(map[string]*models.TrafficControlData),
	}

    // decide session rules
	SessRuleId := fmt.Sprintf("SessRuleId-%d", request.PduSessionId)
	sessRule := models.SessionRule{
		AuthSessAmbr: request.SubsSessAmbr,
		SessRuleId:   SessRuleId,
	}
	defQos := request.SubsDefQos
	if defQos != nil {
		sessRule.AuthDefQos = &models.AuthorizedDefaultQos{
			Var5qi:        defQos.Var5qi,
			Arp:           defQos.Arp,
			PriorityLevel: defQos.PriorityLevel,
		}
	}
	decision.SessRules[SessRuleId] = &sessRule

	// make data network related decisions
	dnnData := util.GetSMPolicyDnnData(smData, request.SliceInfo, request.Dnn)
	if dnnData != nil {
		decision.Online = dnnData.Online
		decision.Offline = dnnData.Offline
		// ......
	} else {
		decision.Online = request.Online
		decision.Offline = request.Offline
	}

    // make QoS related decisions
	filter := bson.M{"ueId": ue.Supi, "snssai": util.SnssaiModelsToHex(*request.SliceInfo), "dnn": request.Dnn}
	qosFlowInterface, err := mongoapi.RestfulAPIGetMany(qosFlowDataColl, filter, queryStrength)
	for _, qosFlow := range qosFlowInterface {
		qosData := newQosDataWithQosFlowMap(qosFlow)
		if decision.QosDecs == nil {
			decision.QosDecs = make(map[string]*models.QosData)
		}
		decision.QosDecs[qosData.QosId] = qosData
	}
	flowRulesInterface, err := mongoapi.RestfulAPIGetMany(flowRuleDataColl, filter, queryStrength)

    // decide PCC rules
	pcc := util.CreateDefaultPccRules(smPolicyData.PccRuleIdGenerator)
	smPolicyData.PccRuleIdGenerator++
	filterCharging := bson.M{
		"ueId":   ue.Supi,
		"snssai": util.SnssaiModelsToHex(*request.SliceInfo),
		"dnn":    "",
		"filter": "",
	}
	chargingInterface, err := mongoapi.RestfulAPIGetOne(chargingDataColl, filterCharging, queryStrength)
	if err != nil {
		util.SetPccRuleRelatedData(&decision, pcc, nil, nil, nil, nil)
	} else if chargingInterface != nil {
		rg, err1 := p.Context().RatingGroupIdGenerator.Allocate()
		chgData := &models.ChargingData{
			ChgId:          util.GetChgId(smPolicyData.ChargingIdGenerator),
			RatingGroup:    int32(rg),
			ReportingLevel: models.ReportingLevel_RAT_GR_LEVEL,
			MeteringMethod: models.MeteringMethod_VOLUME,
		}
		switch chargingInterface["chargingMethod"].(string) {
		case "Online":
			chgData.Online = true
			chgData.Offline = false
		case "Offline":
			chgData.Online = false
			chgData.Offline = true
		}
		util.SetPccRuleRelatedData(&decision, pcc, nil, nil, chgData, nil)
		chargingInterface["ratingGroup"] = chgData.RatingGroup
		mongoapi.RestfulAPIPutOne(chargingDataColl, chargingInterface, chargingInterface, queryStrength)
		if ue.RatingGroupData == nil {
			ue.RatingGroupData = make(map[string][]int32)
		}
		ue.RatingGroupData[smPolicyID] = append(ue.RatingGroupData[smPolicyID], chgData.RatingGroup)
		smPolicyData.ChargingIdGenerator++
	}
	for i, flowRule := range flowRulesInterface {
		precedence := int32(flowRule["precedence"].(float64))
		if val, ok := flowRule["filter"].(string); ok {
			tokens := strings.Split(val, " ")
			FlowDescription := flowdesc.NewIPFilterRule()

			// set FlowDescription attributes ......

			FlowDescriptionStr, err = flowdesc.Encode(FlowDescription)
			pccRule := util.CreatePccRule(smPolicyData.PccRuleIdGenerator, precedence, []models.FlowInformation{
				{
					FlowDescription: FlowDescriptionStr,
					FlowDirection:   models.FlowDirectionRm_DOWNLINK,
				},
			}, "")
			filterCharging := bson.M{
				"ueId":   ue.Supi,
				"snssai": util.SnssaiModelsToHex(*request.SliceInfo),
				"dnn":    request.Dnn,
				"filter": val,
			}
			var chargingInterface map[string]interface{}
			chargingInterface, err = mongoapi.RestfulAPIGetOne(chargingDataColl, filterCharging, 2)
			if err != nil {
				logger.SmPolicyLog.Errorf("Fail to get charging data to mongoDB err: %+v", err)
			} else {
				rg, err1 := p.Context().RatingGroupIdGenerator.Allocate()
				chgData := &models.ChargingData{
					ChgId:          util.GetChgId(smPolicyData.ChargingIdGenerator),
					RatingGroup:    int32(rg),
					ReportingLevel: models.ReportingLevel_RAT_GR_LEVEL,
					MeteringMethod: models.MeteringMethod_VOLUME,
				}
				switch chargingInterface["chargingMethod"].(string) {
				case "Online":
					chgData.Online = true
					chgData.Offline = false
				case "Offline":
					chgData.Online = false
					chgData.Offline = true
				}
				if decision.ChgDecs == nil {
					decision.ChgDecs = make(map[string]*models.ChargingData)
				}
				chargingInterface["ratingGroup"] = chgData.RatingGroup
				if _, err = mongoapi.RestfulAPIPutOne(
					chargingDataColl, chargingInterface, chargingInterface, queryStrength); err != nil {
					logger.SmPolicyLog.Errorf("Fail to put charging data to mongoDB err: %+v", err)
				} else {
					util.SetPccRuleRelatedData(&decision, pccRule, nil, nil, chgData, nil)
					smPolicyData.ChargingIdGenerator++
				}
				if ue.RatingGroupData == nil {
					ue.RatingGroupData = make(map[string][]int32)
				}
				ue.RatingGroupData[smPolicyID] = append(ue.RatingGroupData[smPolicyID], chgData.RatingGroup)
			}
			qosRef := strconv.Itoa(int(flowRule["qosRef"].(float64)))
			util.SetPccRuleRelatedByQosRef(&decision, pccRule, qosRef)
			smPolicyData.PccRuleIdGenerator++
		}
	}

	requestSuppFeat, err := openapi.NewSupportedFeature(request.SuppFeat)
	decision.SuppFeat = pcfSelf.PcfSuppFeats[models.ServiceName_NPCF_SMPOLICYCONTROL].
		NegotiateWith(requestSuppFeat).String()
	decision.QosFlowUsage = request.QosFlowUsage
	// TODO: Trigger about UMC, ADC, NetLoc,...
	decision.PolicyCtrlReqTriggers = util.PolicyControlReqTrigToArray(0x40780f)
	smPolicyData.PolicyDecision = &decision

	// Gmake traffica influcence related decisions
	reqParam := Nudr_DataRepository.ApplicationDataInfluenceDataGetParamOpts{
		Dnns:             optional.NewInterface([]string{request.Dnn}),
		Snssais:          optional.NewInterface(util.MarshToJsonString([]models.Snssai{*request.SliceInfo})),
		InternalGroupIds: optional.NewInterface(request.InterGrpIds),
		Supis:            optional.NewInterface([]string{request.Supi}),
	}
	trafficInfluDatas, resp, err := udrClient.InfluenceDataApi.ApplicationDataInfluenceDataGet(ctx, &reqParam)
	if len(trafficInfluDatas) != 0 {
		// UE identity in UDR appData and apply appData to sm poliocy
		var precedence int32 = 23
		for _, tiData := range trafficInfluDatas {
			pccRule := util.CreatePccRule(smPolicyData.PccRuleIdGenerator, precedence, nil, tiData.AfAppId)
			util.SetSmPolicyDecisionByTrafficInfluData(&decision, pccRule, tiData)
			influenceID := getInfluenceID(tiData.ResUri)
			if influenceID != "" {
				smPolicyData.InfluenceDataToPccRule[influenceID] = pccRule.PccRuleId
			}
			smPolicyData.PccRuleIdGenerator++
			if precedence < Precedence_Maximum {
				precedence++
			}
		}
	}

	// Subscribe to Traffic Influence Data in UDR
	subscriptionID, problemDetail, err := p.Consumer().CreateInfluenceDataSubscription(ue, request)
	smPolicyData.SubscriptionID = subscriptionID

	c.JSON(http.StatusCreated, decision)
}

UPDATE操作與CREATE操作類似,在此略過。

PolicyAuthorization

從名字中只能看出來PolicyAuthorization好像是想給某個policy授權?但實際上,PolicyAuthorization更像是一個AF(Application Function)與PCF之間的介面,AF透過這個介面發起請求,PCF為這個應用分配合適的資源和策略。這些第三方應用包括影片流媒體服務、VoIP通話、線上遊戲、企業內線服務等。

Application Function (AF): Element offering application(s) that use PDU session resources

img

PolicyAuthorizationSMPolicyControl關係相當密切:當AF透過PolicyAuthorization請求資源時,PCF會建立相應的PCC Rules,透過SMPolicyControl將這些規則下發給SMF執行,且在AppSession和SmPolicy之間建立對映關係。PolicyAuthorization的GET和DELETE操作都與SMPolicy異曲同工:GET操作根據appSessionId直接從Context找到資料返回,DELETE操作除了要刪除Context中的資料delete(smPolicy.AppSessions, appSessionId); pcfSelf.AppSessionPool.Delete(appSessionId)還需要刪除相關的PCC Rules並通知SMF刪除事宜。

// https://github.com/free5gc/pcf/blob/main/internal/sbi/processor/policyauthorization.go#L499
func (p *Processor) HandleGetAppSessionContext(c *gin.Context, appSessionId string) {
	pcfSelf := p.Context()
	var appSession *pcf_context.AppSessionData
	val, ok := pcfSelf.AppSessionPool.Load(appSessionId)
	appSession = val.(*pcf_context.AppSessionData)
	c.JSON(http.StatusOK, appSession.AppSessionContext)
}

// https://github.com/free5gc/pcf/blob/main/internal/sbi/processor/policyauthorization.go#L438
func (p *Processor) HandleDeleteAppSessionContext(
	c *gin.Context, appSessionId string,
	eventsSubscReqData *models.EventsSubscReqData,
) {
	pcfSelf := p.Context()
	var appSession *pcf_context.AppSessionData
	if val, ok := pcfSelf.AppSessionPool.Load(appSessionId); ok {
		appSession = val.(*pcf_context.AppSessionData)
	}
	// Remove related pcc rule resource
	smPolicy := appSession.SmPolicyData
	deletedSmPolicyDec := models.SmPolicyDecision{}
	for _, pccRuleID := range appSession.RelatedPccRuleIds {
		smPolicy.RemovePccRule(pccRuleID, &deletedSmPolicyDec)
	}
	delete(smPolicy.AppSessions, appSessionId)
	pcfSelf.AppSessionPool.Delete(appSessionId)
	smPolicy.ArrangeExistEventSubscription()
	// Notify SMF About Pcc Rule removal
	notification := models.SmPolicyNotification{
		ResourceUri:      util.GetResourceUri(models.ServiceName_NPCF_SMPOLICYCONTROL, smPolicyID),
		SmPolicyDecision: &deletedSmPolicyDec,
	}
	go p.SendSMPolicyUpdateNotification(smPolicy.PolicyContext.NotificationUri, &notification)
	c.JSON(http.StatusNoContent, nil)
}
AppSessionContext, AppSessionContextReqData

// Represents an Individual Application Session Context resource.
type AppSessionContext struct {
	AscReqData  *AppSessionContextReqData  `json:"ascReqData,omitempty" yaml:"ascReqData" bson:"ascReqData" mapstructure:"AscReqData"`
	AscRespData *AppSessionContextRespData `json:"ascRespData,omitempty" yaml:"ascRespData" bson:"ascRespData" mapstructure:"AscRespData"`
	EvsNotif    *EventsNotification        `json:"evsNotif,omitempty" yaml:"evsNotif" bson:"evsNotif" mapstructure:"EvsNotif"`
}

// Identifies the service requirements of an Individual Application Session Context.
type AppSessionContextReqData struct {
	// Contains an AF application identifier.
	AfAppId   string                `json:"afAppId,omitempty" yaml:"afAppId" bson:"afAppId" mapstructure:"AfAppId"`
	AfRoutReq *AfRoutingRequirement `json:"afRoutReq,omitempty" yaml:"afRoutReq" bson:"afRoutReq" mapstructure:"AfRoutReq"`
	// Contains an identity of an application service provider.
	AspId string `json:"aspId,omitempty" yaml:"aspId" bson:"aspId" mapstructure:"AspId"`
	// string identifying a BDT Reference ID as defined in subclause 5.3.3 of 3GPP TS 29.154.
	BdtRefId      string                    `json:"bdtRefId,omitempty" yaml:"bdtRefId" bson:"bdtRefId" mapstructure:"BdtRefId"`
	Dnn           string                    `json:"dnn,omitempty" yaml:"dnn" bson:"dnn" mapstructure:"Dnn"`
	EvSubsc       *EventsSubscReqData       `json:"evSubsc,omitempty" yaml:"evSubsc" bson:"evSubsc" mapstructure:"EvSubsc"`
	MedComponents map[string]MediaComponent `json:"medComponents,omitempty" yaml:"medComponents" bson:"medComponents" mapstructure:"MedComponents"`
	IpDomain      string                    `json:"ipDomain,omitempty" yaml:"ipDomain" bson:"ipDomain" mapstructure:"IpDomain"`
	// indication of MPS service request
	MpsId string `json:"mpsId,omitempty" yaml:"mpsId" bson:"mpsId" mapstructure:"MpsId"`
	// string providing an URI formatted according to IETF RFC 3986.
	NotifUri  string  `json:"notifUri" yaml:"notifUri" bson:"notifUri" mapstructure:"NotifUri"`
	SliceInfo *Snssai `json:"sliceInfo,omitempty" yaml:"sliceInfo" bson:"sliceInfo" mapstructure:"SliceInfo"`
	// Contains an identity of a sponsor.
	SponId     string           `json:"sponId,omitempty" yaml:"sponId" bson:"sponId" mapstructure:"SponId"`
	SponStatus SponsoringStatus `json:"sponStatus,omitempty" yaml:"sponStatus" bson:"sponStatus" mapstructure:"SponStatus"`
	Supi       string           `json:"supi,omitempty" yaml:"supi" bson:"supi" mapstructure:"Supi"`
	Gpsi       string           `json:"gpsi,omitempty" yaml:"gpsi" bson:"gpsi" mapstructure:"Gpsi"`
	SuppFeat   string           `json:"suppFeat" yaml:"suppFeat" bson:"suppFeat" mapstructure:"SuppFeat"`
	UeIpv4     string           `json:"ueIpv4,omitempty" yaml:"ueIpv4" bson:"ueIpv4" mapstructure:"UeIpv4"`
	UeIpv6     string           `json:"ueIpv6,omitempty" yaml:"ueIpv6" bson:"ueIpv6" mapstructure:"UeIpv6"`
	UeMac      string           `json:"ueMac,omitempty" yaml:"ueMac" bson:"ueMac" mapstructure:"UeMac"`
}

// https://github.com/free5gc/openapi/blob/main/models/model_media_component.go
// Identifies a media component.
type MediaComponent struct {
	// Contains an AF application identifier.
	AfAppId   string                `json:"afAppId,omitempty" yaml:"afAppId" bson:"afAppId" mapstructure:"AfAppId"`
	AfRoutReq *AfRoutingRequirement `json:"afRoutReq,omitempty" yaml:"afRoutReq" bson:"afRoutReq" mapstructure:"AfRoutReq"`
	// Represents the content version of some content.
	ContVer     int32                        `json:"contVer,omitempty" yaml:"contVer" bson:"contVer" mapstructure:"ContVer"`
	Codecs      []string                     `json:"codecs,omitempty" yaml:"codecs" bson:"codecs" mapstructure:"Codecs"`
	FStatus     FlowStatus                   `json:"fStatus,omitempty" yaml:"fStatus" bson:"fStatus" mapstructure:"FStatus"`
	MarBwDl     string                       `json:"marBwDl,omitempty" yaml:"marBwDl" bson:"marBwDl" mapstructure:"MarBwDl"`
	MarBwUl     string                       `json:"marBwUl,omitempty" yaml:"marBwUl" bson:"marBwUl" mapstructure:"MarBwUl"`
	MedCompN    int32                        `json:"medCompN" yaml:"medCompN" bson:"medCompN" mapstructure:"MedCompN"`
	MedSubComps map[string]MediaSubComponent `json:"medSubComps,omitempty" yaml:"medSubComps" bson:"medSubComps" mapstructure:"MedSubComps"`
	MedType     MediaType                    `json:"medType,omitempty" yaml:"medType" bson:"medType" mapstructure:"MedType"`
	MirBwDl     string                       `json:"mirBwDl,omitempty" yaml:"mirBwDl" bson:"mirBwDl" mapstructure:"MirBwDl"`
	MirBwUl     string                       `json:"mirBwUl,omitempty" yaml:"mirBwUl" bson:"mirBwUl" mapstructure:"MirBwUl"`
	ResPrio     ReservPriority               `json:"resPrio,omitempty" yaml:"resPrio" bson:"resPrio" mapstructure:"ResPrio"`
}

// https://github.com/free5gc/openapi/blob/main/models/model_media_sub_component.go
// Identifies a media subcomponent
type MediaSubComponent struct {
	EthfDescs []EthFlowDescription `json:"ethfDescs,omitempty" yaml:"ethfDescs" bson:"ethfDescs" mapstructure:"EthfDescs"`
	FNum      int32                `json:"fNum" yaml:"fNum" bson:"fNum" mapstructure:"FNum"`
	FDescs    []string             `json:"fDescs,omitempty" yaml:"fDescs" bson:"fDescs" mapstructure:"FDescs"`
	FStatus   FlowStatus           `json:"fStatus,omitempty" yaml:"fStatus" bson:"fStatus" mapstructure:"FStatus"`
	MarBwDl   string               `json:"marBwDl,omitempty" yaml:"marBwDl" bson:"marBwDl" mapstructure:"MarBwDl"`
	MarBwUl   string               `json:"marBwUl,omitempty" yaml:"marBwUl" bson:"marBwUl" mapstructure:"MarBwUl"`
	// 2-octet string, where each octet is encoded in hexadecimal representation. The first octet contains the IPv4 Type-of-Service or the IPv6 Traffic-Class field and the second octet contains the ToS/Traffic Class mask field.
	TosTrCl   string    `json:"tosTrCl,omitempty" yaml:"tosTrCl" bson:"tosTrCl" mapstructure:"TosTrCl"`
	FlowUsage FlowUsage `json:"flowUsage,omitempty" yaml:"flowUsage" bson:"flowUsage" mapstructure:"FlowUsage"`
}
與`NpcfSMPolicyControl`一樣,CREATE方法對應的處理函式又長又瑣碎:首先將`appSession`與`ueSmPolicyData`進行繫結,然後是PCF的核心任務,也就是處理[媒體元件(Media Components)](https://github.com/free5gc/openapi/blob/main/models/model_media_component.go)——為每個媒體元件建立或更新PCC規則、設定QoS引數、處理流量路由資訊,然後是處理事件訂閱——這些事件訂閱允許AF獲知網路狀態的變化,從而可以相應地調整其應用行為或服務質量要求(比如當接入型別發生變化時,AF可能需要調整影片流的位元率),處理贊助連線(Sponsored Connectivity,可以先簡單理解為[定向流量](https://baike.baidu.com/item/%E5%AE%9A%E5%90%91%E6%B5%81%E9%87%8F/385830))資訊,最後是建立`appSession`ID儲存`appSession`資料,並向SMF傳送策略更新通知。
// https://github.com/free5gc/pcf/blob/main/internal/sbi/processor/policyauthorization.go#L154
// Radically simplified!
func (p *Processor) postAppSessCtxProcedure(appSessCtx *models.AppSessionContext) (
    *models.AppSessionContext, *models.ProblemDetails,
) {
    // Initial BDT policy indication(the only one which is not related to session)
    if appSessCtx.ascReqData.BdtRefId != "" {
        p.handleBDTPolicyInd(pcf_context, appSessCtx)
        pcfSelf.AppSessionPool.Store(ascReqData.BdtRefId, appSessCtx)
        return appSessCtx, nil
    }

    ueSmPolicyData := pcfSelf.SessionBinding(appSessCtx.ascReqData)

    // Handle Pcc rules
    for _, medComp := range ascReqData.MedComponents {
        pccRule = util.GetPccRuleByAfAppId(smPolicy.PolicyDecision.PccRules, appID)
        qosData := util.CreateQosData(smPolicy.PccRuleIdGenerator, var5qi, 8)
        util.SetPccRuleRelatedData(ueSmPolicyData.PolicyDecision, pccRule, nil, &qosData, nil, nil)
    } else {
        // update pccRule's qos
        for _, qosID := range pccRule.RefQosData {
            qosData = *smPolicy.PolicyDecision.QosDecs[qosID]、
            qosData, ul, dl = updateQosInMedComp(*smPolicy.PolicyDecision.QosDecs[qosID], &medComp)
            modifyRemainBitRate(smPolicy, &qosData, ul, dl)
            smPolicy.PolicyDecision.QosDecs[qosData.QosId] = &qosData
        }
    }
    // Initial provisioning of traffic routing information
    pccRule := provisioningOfTrafficRoutingInfo(smPolicy, ascReqData.AfAppId, ascReqData.AfRoutReq, "")
    relatedPccRuleIds := make(map[string]string)
    key := fmt.Sprintf("appID-%s", ascReqData.AfAppId)
    relatedPccRuleIds[key] = pccRule.PccRuleId
    // Event Subscription
	eventSubs := make(map[models.AfEvent]models.AfNotifMethod)
    for _, subs := range ascReqData.EvSubsc.Events {
        eventSubs[subs.Event] = subs.NotifMethod
        var trig models.PolicyControlRequestTrigger
        switch subs.Event {
            case models.AfEvent_ACCESS_TYPE_CHANGE:
                trig = models.PolicyControlRequestTrigger_AC_TY_CH
            // more cases ...
            default:
                logger.PolicyAuthLog.Warn("AF Event is unknown")
                continue
        }
        if !util.CheckPolicyControlReqTrig(smPolicy.PolicyDecision.PolicyCtrlReqTriggers, trig) {
            smPolicy.PolicyDecision.PolicyCtrlReqTriggers = append(smPolicy.PolicyDecision.PolicyCtrlReqTriggers, trig)
        }   
    }
    // Initial provisioning of sponsored connectivity information
    umID := util.GetUmId(ascReqData.AspId, ascReqData.SponId)
    umData := extractUmData(umID, eventSubs, ascReqData.EvSubsc.UsgThres)
    handleSponsoredConnectivityInformation(smPolicy, relatedPccRuleIds, ascReqData.AspId,
        ascReqData.SponId, ascReqData.SponStatus, umData, &updateSMpolicy)
	// Allocate App Session Id
	appSessID := ue.AllocUeAppSessionId(pcfSelf)
	// Associate App Session to SMPolicy
	smPolicy.AppSessions[appSessID] = true
	data := pcf_context.AppSessionData{
		AppSessionId:      appSessID,
		AppSessionContext: appSessCtx,
		SmPolicyData:      smPolicy,
	}
	if len(relatedPccRuleIds) > 0 {
		data.RelatedPccRuleIds = relatedPccRuleIds
		data.PccRuleIdMapToCompId = reverseStringMap(relatedPccRuleIds)
	}
	appSessCtx.EvsNotif = &models.EventsNotification{}
	// Set Event Subsciption related Data ...
	pcfSelf.AppSessionPool.Store(appSessID, &data)
	// Send Notification to SMF
	go p.SendSMPolicyUpdateNotification(smPolicy.PolicyContext.NotificationUri, &notification)
    return appSessCtx, nil
}

至此我研究了一遍PCF的機制及其原始碼,但我總還有一些雲裡霧裡的感覺。我認為這是因為PCF深度介入AMF以及SMF,尤其是SMF。所以想要全面理解PCF就必須等到研究完AMF和SMF才能做到,至少要知道PCF所指定的policy是怎麼被應用和實施的。所以下文先從SMF開始。

相關文章