From 55b720d43677bad50442f95ebeab9bd1a86f3ddf Mon Sep 17 00:00:00 2001 From: Chien-An Hu <52029623+Roy-Hu@users.noreply.github.com> Date: Sun, 3 Mar 2024 10:23:26 -0600 Subject: [PATCH] support setting charging rule in PCC by webconsole (#12) * support setting charging rule in PCC by webconsole * change charging reference * update charging data collection * update pdu charging data filter * refactor: createSMPolicyProcedure * fix ci error * createSMPolicyProcedure: handle charging only when no error * fix ci error * add nil check on chargingInterface * fix: Case-insensitive in pcf_util.go * update GetSMPolicyDnnData() * fix: qfi -> qosRef * change var name: dest -> tokens * fix: rating group is not match * fix linter error * fix: port handling in IP Filter * fix: flow description's destination IP can only accept string "assigned" * if port range is 1-65535, omit port range * fix: use case-insensitive mongoapi * Use Strength = 2 (cas-insensitive) when query to MongoDB * update util's hash * add tracelog * move RatingGroupIdGenerator to pcf_context * Fix: rebase and fix linter * Fix: remove unused code --------- Co-authored-by: Roy-Hu Co-authored-by: roy19991013 <80-ChienAn@users.noreply.gitlab.nems.cs.nctu.edu.tw> Co-authored-by: Ian Chen Co-authored-by: ianchen0119 Co-authored-by: brianchennn Co-authored-by: CTFang@WireLab --- go.mod | 2 +- go.sum | 4 +- internal/context/context.go | 4 + internal/context/ue.go | 2 + .../sbi/consumer/influenceDataSubscription.go | 2 +- internal/sbi/producer/smpolicy.go | 165 ++++++++++++++++-- internal/util/pcc_rule.go | 14 +- internal/util/pcf_util.go | 1 + 8 files changed, 166 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index aebf75e..901c7a6 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/cydev/zero v0.0.0-20160322155811-4a4535dd56e7 github.com/free5gc/openapi v1.0.7 - github.com/free5gc/util v1.0.5 + github.com/free5gc/util v1.0.5-0.20231219085815-8972321e9748 github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.9.1 github.com/google/uuid v1.4.0 diff --git a/go.sum b/go.sum index 83d8033..5714ec5 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8 github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/free5gc/openapi v1.0.7 h1:I0HOgqRgER6DbyqB6EBxusmbeouIhcOn4eOJvL3veJA= github.com/free5gc/openapi v1.0.7/go.mod h1:qv9KqEucoZSeENPRFGxfTe+33ZWYyiYFx1Rj+H0DoWA= -github.com/free5gc/util v1.0.5 h1:MjyEIX6gGbdS8FUAOxhE0FsMD6pmv5T+45LjBabVhS8= -github.com/free5gc/util v1.0.5/go.mod h1:jgiW/bNbNbX87CSTKXyfvkhQeDY9ThK+TtWTu4ILCS8= +github.com/free5gc/util v1.0.5-0.20231219085815-8972321e9748 h1:zx+0hkvsdwje0a3wPDz3gt9trLBRXUwpeHfQMWGY4MA= +github.com/free5gc/util v1.0.5-0.20231219085815-8972321e9748/go.mod h1:jgiW/bNbNbX87CSTKXyfvkhQeDY9ThK+TtWTu4ILCS8= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA= diff --git a/internal/context/context.go b/internal/context/context.go index 14dc1a0..7fd925b 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -49,6 +49,9 @@ type PCFContext struct { // lock DefaultUdrURILock sync.RWMutex + // Charging + RatingGroupIdGenerator *idgenerator.IDGenerator + OAuth2Required bool } @@ -153,6 +156,7 @@ func Init() { pcfContext.PcfServiceUris = make(map[models.ServiceName]string) pcfContext.PcfSuppFeats = make(map[models.ServiceName]openapi.SupportedFeature) pcfContext.BdtPolicyIDGenerator = idgenerator.NewGenerator(1, math.MaxInt64) + pcfContext.RatingGroupIdGenerator = idgenerator.NewGenerator(1, math.MaxInt64) InitpcfContext(&pcfContext) } diff --git a/internal/context/ue.go b/internal/context/ue.go index 253cf57..cb979ea 100644 --- a/internal/context/ue.go +++ b/internal/context/ue.go @@ -80,6 +80,7 @@ type UeSmPolicyData struct { PackFiltIdGenerator int32 PccRuleIdGenerator int32 ChargingIdGenerator int32 + // FlowMapsToPackFiltIds map[string][]string // use Flow Description(in TS 29214) as key map to pcc rule ids PackFiltMapToPccRuleId map[string]string // use PackFiltId as Key // Related to GBR @@ -156,6 +157,7 @@ func (ue *UeContext) NewUeSmPolicyData( // data.RefToAmPolicy = amData data.PccRuleIdGenerator = 1 data.ChargingIdGenerator = 1 + data.PcfUe = ue ue.SmPolicyData[key] = &data data.InfluenceDataToPccRule = make(map[string]string) diff --git a/internal/sbi/consumer/influenceDataSubscription.go b/internal/sbi/consumer/influenceDataSubscription.go index c37bc80..6a873f9 100644 --- a/internal/sbi/consumer/influenceDataSubscription.go +++ b/internal/sbi/consumer/influenceDataSubscription.go @@ -92,7 +92,7 @@ func RemoveInfluenceDataSubscription(ue *pcf_context.UeContext, subscriptionID s }() if httpResp.Status != localErr.Error() { err = localErr - return nil, err + return problemDetails, err } problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) problemDetails = &problem diff --git a/internal/sbi/producer/smpolicy.go b/internal/sbi/producer/smpolicy.go index bf9ce8a..41aa11b 100644 --- a/internal/sbi/producer/smpolicy.go +++ b/internal/sbi/producer/smpolicy.go @@ -16,6 +16,7 @@ import ( "github.com/free5gc/pcf/internal/logger" "github.com/free5gc/pcf/internal/sbi/consumer" "github.com/free5gc/pcf/internal/util" + "github.com/free5gc/util/flowdesc" "github.com/free5gc/util/httpwrapper" "github.com/free5gc/util/mongoapi" ) @@ -23,6 +24,7 @@ import ( const ( flowRuleDataColl = "policyData.ues.flowRule" qosFlowDataColl = "policyData.ues.qosFlow" + chargingDataColl = "policyData.ues.chargingData" ) // SmPoliciesPost - @@ -48,7 +50,7 @@ func HandleCreateSmPolicyRequest(request *httpwrapper.Request) *httpwrapper.Resp func newQosDataWithQosFlowMap(qosFlow map[string]interface{}) *models.QosData { qosData := &models.QosData{ - QosId: strconv.Itoa(int(qosFlow["qfi"].(float64))), + QosId: strconv.Itoa(int(qosFlow["qosRef"].(float64))), Qnc: false, Var5qi: int32(qosFlow["5qi"].(float64)), } @@ -72,6 +74,7 @@ func createSMPolicyProcedure(request models.SmPolicyContextData) ( header http.Header, response *models.SmPolicyDecision, problemDetails *models.ProblemDetails, ) { var err error + queryStrength := 2 // 2: case-insensitive, 3: case-sensitive logger.SmPolicyLog.Tracef("Handle Create SM Policy Request") if request.Supi == "" || request.SliceInfo == nil || len(request.SliceInfo.Sd) != 6 { @@ -207,7 +210,7 @@ func createSMPolicyProcedure(request models.SmPolicyContextData) ( } filter := bson.M{"ueId": ue.Supi, "snssai": util.SnssaiModelsToHex(*request.SliceInfo), "dnn": request.Dnn} - qosFlowInterface, err := mongoapi.RestfulAPIGetMany(qosFlowDataColl, filter) + qosFlowInterface, err := mongoapi.RestfulAPIGetMany(qosFlowDataColl, filter, queryStrength) if err != nil { logger.SmPolicyLog.Errorf("createSMPolicyProcedure error: %+v", err) } @@ -222,22 +225,158 @@ func createSMPolicyProcedure(request models.SmPolicyContextData) ( } // get flow rules from databases - flowRulesInterface, err := mongoapi.RestfulAPIGetMany(flowRuleDataColl, filter) + flowRulesInterface, err := mongoapi.RestfulAPIGetMany(flowRuleDataColl, filter, queryStrength) if err != nil { logger.SmPolicyLog.Errorf("createSMPolicyProcedure error: %+v", err) } - for _, flowRule := range flowRulesInterface { + 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 { + logger.SmPolicyLog.Errorf("Fail to get charging data to mongoDB err: %+v", err) + logger.SmPolicyLog.Errorf("chargingInterface %+v", chargingInterface) + util.SetPccRuleRelatedData(&decision, pcc, nil, nil, nil, nil) + } else if chargingInterface != nil { + rg, err1 := pcf_context.GetSelf().RatingGroupIdGenerator.Allocate() + if err1 != nil { + logger.SmPolicyLog.Error("rating group allocate error") + problemDetails := util.GetProblemDetail("rating group allocate error", util.ERROR_IDGENERATOR) + return nil, nil, &problemDetails + } + 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) + + chargingBsonM := bson.M{ + "ratingGroup": chgData.RatingGroup, + } + logger.SmPolicyLog.Traceln("put ratingGroup to MongoDB") + if _, err = mongoapi.RestfulAPIPutOne(chargingDataColl, filterCharging, chargingBsonM, queryStrength); err != nil { + logger.SmPolicyLog.Errorf("Fail to put charging data to mongoDB err: %+v", err) + } + + smPolicyData.ChargingIdGenerator++ + } + + logger.SmPolicyLog.Traceln("FlowRules for ueId:", ue.Supi, "snssai:", util.SnssaiModelsToHex(*request.SliceInfo)) + for i, flowRule := range flowRulesInterface { + logger.SmPolicyLog.Tracef("flowRule %d: %s\n", i, openapi.MarshToJsonString(flowRule)) precedence := int32(flowRule["precedence"].(float64)) - pccRule := util.CreatePccRule(smPolicyData.PccRuleIdGenerator, precedence, []models.FlowInformation{ - { - FlowDescription: flowRule["filter"].(string), - FlowDirection: models.FlowDirectionRm_BIDIRECTIONAL, - }, - }, "") - qfi := strconv.Itoa(int(flowRule["qfi"].(float64))) - util.SetPccRuleRelatedByQFI(&decision, pccRule, qfi) - smPolicyData.PccRuleIdGenerator++ + if val, ok := flowRule["filter"].(string); ok { + tokens := strings.Split(val, " ") + + FlowDescription := flowdesc.NewIPFilterRule() + FlowDescription.Action = flowdesc.Permit + FlowDescription.Dir = flowdesc.Out + FlowDescription.Src = tokens[0] + FlowDescription.Dst = "assigned" // Hardcode destination (TS 29.212 5.4.2) + + var err1, err2 error + portLowerBound := 1 + portUpperBound := 65535 + if len(tokens) > 1 { + portLowerBound, err1 = strconv.Atoi(strings.Split(tokens[1], "-")[0]) + portUpperBound, err2 = strconv.Atoi(strings.Split(tokens[1], "-")[1]) + } + + if err1 != nil || err2 != nil { + logger.SmPolicyLog.Warnln("Wrong Port format in IP Filter's setting:", tokens[1], ", set to 1-65535") + } + + if !(portLowerBound <= 1 && portUpperBound >= 65535) { // Port range need to be assigned + FlowDescription.SrcPorts = flowdesc.PortRanges{ + flowdesc.PortRange{ + Start: uint16(portLowerBound), + End: uint16(portUpperBound), + }, + } + } + + var FlowDescriptionStr string + FlowDescriptionStr, err = flowdesc.Encode(FlowDescription) + if err != nil { + logger.SmPolicyLog.Errorf("Error occurs when encoding flow despcription: %s\n", err) + } + + 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 := pcf_context.GetSelf().RatingGroupIdGenerator.Allocate() + if err1 != nil { + logger.SmPolicyLog.Error("rating group allocate error") + problemDetails := util.GetProblemDetail("rating group allocate error", util.ERROR_IDGENERATOR) + return nil, nil, &problemDetails + } + 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) + } + + chargingBsonM := bson.M{ + "ratingGroup": chgData.RatingGroup, + } + if _, err = mongoapi.RestfulAPIPutOne(chargingDataColl, filterCharging, chargingBsonM, 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++ + } + } + qosRef := strconv.Itoa(int(flowRule["qosRef"].(float64))) + util.SetPccRuleRelatedByQosRef(&decision, pccRule, qosRef) + smPolicyData.PccRuleIdGenerator++ + } } requestSuppFeat, err := openapi.NewSupportedFeature(request.SuppFeat) diff --git a/internal/util/pcc_rule.go b/internal/util/pcc_rule.go index 9293112..f94f048 100644 --- a/internal/util/pcc_rule.go +++ b/internal/util/pcc_rule.go @@ -29,12 +29,6 @@ func CreateDefaultPccRules(id int32) *models.PccRule { PacketFilterUsage: true, PackFiltId: "PackFiltId-0", }, - { - FlowDescription: "permit out ip from any to assigned", - FlowDirection: models.FlowDirectionRm_DOWNLINK, - PacketFilterUsage: true, - PackFiltId: "PackFiltId-1", - }, } return CreatePccRule(id, 255, flowInfo, "") } @@ -176,13 +170,11 @@ func GetPccRuleByFlowInfos(pccRules map[string]*models.PccRule, flowInfos []mode return nil } -func SetPccRuleRelatedByQFI(decision *models.SmPolicyDecision, pccRule *models.PccRule, qfi string) { - if decision.QosDecs == nil { - return - } else if qosFlow := decision.QosDecs[qfi]; qosFlow == nil { +func SetPccRuleRelatedByQosRef(decision *models.SmPolicyDecision, pccRule *models.PccRule, qfi string) { + if decision.QosDecs == nil || decision.QosDecs[qfi] == nil { return } - pccRule.RefQosData = []string{qfi} + pccRule.RefQosData = append(pccRule.RefQosData, qfi) if decision.PccRules == nil { decision.PccRules = make(map[string]*models.PccRule) } diff --git a/internal/util/pcf_util.go b/internal/util/pcf_util.go index 3b6a958..fee9974 100644 --- a/internal/util/pcf_util.go +++ b/internal/util/pcf_util.go @@ -37,6 +37,7 @@ var ( UNAUTHORIZED_SPONSORED_DATA_CONNECTIVITY = "UNAUTHORIZED_SPONSORED_DATA_CONNECTIVITY" PDU_SESSION_NOT_AVAILABLE = "PDU_SESSION_NOT_AVAILABLE" APPLICATION_SESSION_CONTEXT_NOT_FOUND = "APPLICATION_SESSION_CONTEXT_NOT_FOUND" + ERROR_IDGENERATOR = "ERROR_IDGENERATOR" PcpErrHttpStatusMap = map[string]int32{ ERROR_REQUEST_PARAMETERS: http.StatusBadRequest, USER_UNKNOWN: http.StatusBadRequest,