Skip to content

fix: preserve QoS flow marking and charging across TI and ULCL updates - #253

Merged
Alonza0314 merged 11 commits into
free5gc:mainfrom
roundspring2003:fix/ambr-qer-omit-qfi
Oct 21, 2026
Merged

Alonza0314 merged 11 commits into
free5gc:mainfrom
roundspring2003:fix/ambr-qer-omit-qfi

Conversation

@roundspring2003

@roundspring2003 roundspring2003 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

A dedicated non-GBR QoS Flow's PDR can reference both the shared Session-AMBR QER and its own flow QER. When the shared QER carries the default QFI (for example 1) and the flow QER carries QFI 4, the same matching traffic receives conflicting packet-marking instructions. Removing QFI from the shared QER also exposes paths that previously relied on that QER for marking: static ULCL/MEC paths need an explicit flow QER and must preserve the QoS and charging policy of the traffic they carry.

This PR separates Session-AMBR enforcement from QoS flow marking and applies explicit QoS bindings during normal, DC and static ULCL path activation. A static MEC route can carry both default and dedicated flows; selecting that route does not itself select the default QoS Flow.

Changes

  • Make QFI optional on QERs. Omit it from shared Session-AMBR QERs while retaining their MBR and retaining QFI on flow QERs. Use the replacement go-pfcp encoder for establishment and modification, and skip rate-only QERs in NGAP modification confirmation.
  • Pass the selected QFI and corresponding QoS data into path activation. Reuse the session's actual DefQosQFI for default bindings, including DefQosFlowIndication, without creating another default flow or treating the indication as a new QoS profile. Dedicated flows retain their referenced QoS data; non-GBR flow MBR remains optional.
  • Recompute and compare the full flow QER profile even when the UPF/QFI mapping already exists. For the same QoS ID/QFI, changed QFI, gate, MBR or GBR settings receive a new QER ID; identical profiles retain their shared QER. Replacement uses Create QER plus retirement of the old QER, including when optional MBR/GBR fields disappear. Rebuilt PCC and static classifiers reference the new QER while old PDRs retain the old object until cleanup. Parse invalid rate data before allocating or replacing a QER.
  • Honor all PCC flow filters and their UL/DL directions. Additional PDRs share forwarding resources while carrying the appropriate classifiers and rule references.
  • Intersect static ULCL destination/port filters with existing PCC classifiers. Full and partial overlaps reuse the matching PCC rule's QFI, QoS data and charging references; remaining traffic falls back to the existing default flow. Preserve protocol, address, port and direction restrictions, PCC ordering, and the relative priority of explicit PCF/TI routing. Allocate PDR precedence values without renumbering PCC policy.
  • Collect PDR, FAR, QER and URR definitions from every classifier throughout ordinary activation and the static ULCL PFCP phases, including rules used only in downlink traffic. Remove the broad-filter and fixed-precedence overrides that would erase these bindings.
  • Compile normalized desired QoS/classifier/charging rules once per path, then reconcile them against the current PDRs using a classifier index. Reuse unchanged PDR objects and IDs, even for repeated notifications, unrelated policy changes, equivalent rate units or reordered filters. Replace only classifiers whose effective filter, precedence, QFI, QER profile or charging identity changes. Normal/DC activation and static refresh share the same planning/application code; no persisted policy fingerprint or cache invalidation is required.
  • Keep replaced PDRs in a retirement list so PFCP can remove them, release shared QERs only when no PDR still references them, and remove URRs only when their reference count reaches zero. Reusing a URR before its pending removal restores its previous state. Unchanged charging bindings retain their URR references and runtime quota.
  • Compute session-level charging references once per path rather than rescanning/parsing all PCC rules for each binding. Compare normalized QER values directly and give each UPF its own rule fields.
  • Attach the existing live session-level charging URR to service UL/DL PDRs on the same anchor instead of returning nil when it already exists. Preserve its shared reference count, and ignore retired entries retained only for final charging metadata so a new live URR can be created.
  • Skip PFCP modification and response waiting for established UPFs with no pending rule operations. Continue sending updates to other affected UPFs, including URR quota updates and queries; initial session establishment is unchanged.
  • Preserve final charging usage while excluding retired URRs from new quota updates and timer queries. Stop timers when deleting URRs, retain charging identity for final reports, and aggregate usage by both Rating Group and UPF so separate anchors retain their identity. Preserve credit requests for live services even when a final report shares their charging group.

Code layout

The implementation and regressions are grouped by responsibility, with seven new files in total (two implementation files and five test files):

  • internal/context/path_qos.go: common QoS planning/reconciliation, directional SDF handling and classifier cleanup.
  • internal/context/ulcl_qos.go: static route/PCC intersections, charging bindings and static refresh.
  • QER lifecycle helpers reside with the existing rule definitions in internal/context/pfcp_rules.go; PFCP rule collection and pending-operation checks reside in internal/sbi/processor/datapath.go.
  • Context regressions are grouped into datapath_qos_test.go, ulcl_qos_test.go and traffic_influence_policy_test.go. Processor regressions are grouped into datapath_internal_test.go and charging_trigger_internal_test.go. Shared-QER retirement encoding tests are included in the existing build_modification_test.go.

The file consolidation moves declarations and import aliases only; behavior and all existing test/benchmark cases are retained.

Standards rationale

  • TS 29.244 V19.6.0, Table 7.5.2.5-1, pp. 233–234: the Maximum Bitrate entry describes a Session-AMBR QER referenced by the PDRs of the session's non-GBR QoS flows. The QoS flow identifier entry describes QFI as an instruction for the UPF to insert or modify the packet's QFI.
  • TS 23.501 V19.9.0, §5.7.1.5: the SMF supplies the packet-marking information corresponding to the PCC rule's QoS Flow binding.

The conclusion that QFI 1 and QFI 4 on QERs referenced by the same dedicated-flow PDR create a semantic conflict is an interpretation of these requirements. This PR does not claim a blanket prohibition on QFI in every AMBR QER or a particular UPF outcome. The cited provisions do not supply a first-QER-wins or ignore-the-default-QFI resolution rule. The static-route intersection and precedence allocation are implementation choices for retaining the existing policy, not algorithms prescribed by these clauses.

Validation and scope

Passed locally:

  • go test ./...
  • go build ./...
  • go test -race ./internal/context ./internal/sbi/processor ./internal/pfcp/message
  • golangci-lint run ./... — 0 issues
  • git diff --check

Regression coverage includes optional QFI wire encoding, NGAP confirmation, normal/DC activation, TI policy changes, static full/partial PCC overlap, default fallback, direction/protocol/ports, explicit TI routing priority, PCC add/delete refresh, GBR and charging references, shared-rule retirement, final usage and late quota responses, and UPF charging identity. Mock PFCP tests encode and parse requests across the static ULCL phases, including downlink-only rule definitions. Additional wire regressions exercise QoS-data-only updates for the same QoS ID/QFI: MBR increases/decreases/addition/removal, GBR changes/addition/removal, equivalent rate units, shared PCC/static references, replacement rule IDs, and final cleanup. Normal and DC activation also cover changed and invalid rate data. Incremental regressions verify stable static PDR IDs on no-op/unrelated notifications, partial QoS and charging replacement, filter reordering, removal of only the affected direction, preservation of live URR quota/refcounts, and suppression of empty N4 modifications while URR updates/queries still reach the affected UPF.

Local control-plane benchmark

go test ./internal/context -run '^$' -bench '^BenchmarkStaticULCLNoopPolicyUpdate$' -benchmem -benchtime=200ms -count=3

Compared with 10af0d2 using the same benchmark fixture in an isolated copy. Median of three runs on the same machine; the workload includes ApplyPccRules() and local retirement cleanup for an unchanged policy. It excludes PFCP network latency and real UPF/UE execution. The current benchmark also checks that static PDR objects remain unchanged.

Classifiers Before (µs/op) After (µs/op) Time reduction Before (B/op) After (B/op)
1 119.3 67.9 43.1% 194072 111001
16 3173.0 537.7 83.1% 5663624 890657
64 37087.4 1958.6 94.7% 70660140 3350726

Local free5gc/ci-test ULCL execution with real UPFs/UE passed 14/14 routing, policy and charging-usage cases (eight TI sessions and six static multipath sessions) using local PCF/SMF binaries. Session-URR regressions additionally encode and parse PFCP Modification requests and verify reference counts during service/default path removal and recreation. These e2e assertions do not establish wire QFI/MBR enforcement or successful final account settlement: CHF requestedUnit validation and the SMF/PCF account-deletion order are separate charging issues outside this PR. A separately patched local CHF accepts the valid final online reports; the account-deletion ordering issue remains. Static intersection supports the implemented IP flow-filter subset; AppId/Ethernet classification, unsupported filter syntax, missing QoS data, or insufficient PDR precedence space return errors. This change does not add a complete PFCP failure rollback mechanism.

The branch includes official main at 572d3bd; the deleted legacy internal/pfcp/message/build.go is not restored. Related PCF policy changes: free5gc/pcf#87.

@roundspring2003 roundspring2003 changed the title fix: omit default QFI from shared Session-AMBR QER fix: preserve QoS flow marking and charging across TI and ULCL updates Oct 19, 2026
@roundspring2003

Copy link
Copy Markdown
Contributor Author

@roundspring2003

Copy link
Copy Markdown
Contributor Author

Fixed the stale flow-QER profile issue in commit 10af0d2.

flowQER() now computes the desired QFI, gate status, MBR and GBR even when the UPF/QFI mapping already exists. An identical profile reuses the QER; a changed profile gets a new QER ID while preserving the QoS Flow's QFI. Rebuilt PDRs reference the replacement, and the existing retirement logic removes the old QER after its remaining PDR references retire. Using Create QER plus Remove QER also handles removal of optional MBR/GBR fields explicitly, without relying on omitted Update QER fields to clear prior settings.

Regression tests exercise QoS-data-only updates for the same QoS ID/QFI and encode/parse PFCP Session Modification requests. They cover MBR increases/decreases/addition/removal, GBR changes/addition/removal, unchanged equivalent rates, shared PCC/static ULCL references, and old-QER cleanup. Additional normal/DC activation tests cover changed and invalid rate data.

Local validation passed: go test ./..., go build ./..., race tests for context/processor/PFCP message packages, and golangci-lint run ./... (0 issues). Full free5gc/ci-test with real UPFs/UE has not been run for this revision.

@roundspring2003

Copy link
Copy Markdown
Contributor Author

Addresses the PR review's P2: Static ULCL rebuilds every active static PDR after each PCC update in commit 6b16b6c.

Static refresh now compiles normalized desired classifier/QoS/charging rules and reconciles them against existing PDRs using a classifier index. Unchanged rules retain their PDR objects and IDs, QER/URR references, and runtime quota; only changed or removed classifiers enter the replacement/retirement flow. Comparisons include effective filters, precedence, QFI, gate/MBR/GBR, Session-AMBR attachment, and charging identity. Filter reordering and equivalent bitrate units do not force replacement. The plan is rebuilt from current policy rather than relying on a persisted fingerprint cache.

The same planning/application code serves path activation and static refresh. Session-level charging references are collected once per path, avoiding the repeated PCC scans found during benchmarking. Existing PFCP sessions with no pending operations skip modification requests and response waiting; affected UPFs still receive their updates, including URR quota updates and queries.

Regression coverage includes repeated/empty/unrelated notifications, partial QoS and charging changes, reordered filters, direction-specific removal, preservation of live URR quota/refcounts, and suppression of empty N4 requests. Local full tests/build, targeted race tests, and lint passed.

For 64 classifiers, the local unchanged-policy benchmark improved from about 37.1 ms/op to 2.0 ms/op and 70.7 MB/op to 3.4 MB/op (median of three runs against 10af0d2 with the same fixture). This measures policy application and local cleanup, excluding network latency and real UPF/UE execution. Full free5gc/ci-test has not been run for this revision.

@Alonza0314
Alonza0314 merged commit 270e703 into free5gc:main Oct 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants