OmniUPF Walled Garden / Out-of-Credit Redirect
Table of Contents
- Overview
- Architecture
- PFCP Signaling Flow
- Captive Portal Detection
- Configuration
- Whitelist Management
- Per-Session Redirect URLs
- API
- Prometheus Metrics
- Troubleshooting
Overview
The Walled Garden feature provides native out-of-credit enforcement directly in the UPF, eliminating the need for external enforcement systems (MikroTik address lists, mangle rules, DNAT).
When a subscriber runs out of credit, the SMF sends a PFCP Session Modification with a FAR containing redirect_information. OmniUPF intercepts all traffic for that session in userspace and enforces a captive portal experience:
- DNS queries are spoofed to return the portal server IP, triggering captive portal detection on all major devices (Apple, Android, Windows)
- Traffic to the portal and whitelisted IPs (payment processors, CAPTCHA services) is forwarded normally so the subscriber can top up
- All other traffic is silently dropped
The subscriber sees a captive portal prompt and is directed to a top-up/payment page. Once credit is restored, the SMF updates the FAR back to FORW and normal forwarding resumes immediately.
Key Design Points
- No eBPF changes required -- reuses the existing
FAR_BUFFaction to redirect packets to userspace - Per-session redirect -- each session can have a different portal IP and redirect URL, determined by the SMF's
redirect_informationIE - Whitelist lives on the UPF -- the SMF only says "redirect this session"; the UPF decides what traffic to allow through
- Uplink-only interception -- the downlink FAR stays FORW so portal responses reach the UE via the normal GTP encap path
Architecture
Packet Flow
- UE sends uplink packet (DNS query, HTTP request, etc.) via GTP-U to the UPF
- eBPF PDR match finds the matching PDR, looks up the FAR
- FAR action is BUFF (overridden from FORW when redirect is active) -- eBPF sends the packet to the BufferListener on UDP port 22152
- BufferListener extracts the TEID, checks the RedirectIndex ETS table
- If TEID is in the RedirectIndex: Dispatcher processes the inner IP packet
- Decision tree:
- DNS query for non-whitelisted domain: spoof A record with portal IP
- DNS query for whitelisted domain: forward to real resolver, cache resolved IPs
- Traffic to portal IP or whitelisted IP: forward via raw socket
- Everything else: drop silently
- DNS/GTP-U responses are sent back to the UE via the downlink GTP-U path (encapsulated with the DL TEID and sent to the gNB)
N9 Loopback Handling (SGW + PGW Dual Session)
In 4G EPC deployments, OmniUPF often acts as both SGW-U and PGW-U simultaneously on the same node. The SGW-C and PGW-C each establish a separate PFCP session. The SGW session has an N9 FAR that forwards packets to the PGW session's uplink TEID (a loopback through the N3 interface). The PGW session performs the actual subscriber policy enforcement.
When activating a walled garden redirect via POST /v1/walled_garden, the activate logic detects this topology automatically:
- PGW session TEID(s) are identified from the target SEID's uplink PDRs.
- The code scans all PFCP sessions to find a session (the SGW session) that has a FAR whose
teidmatches one of the PGW UL PDR TEIDs and hasouter_header_creationset. That FAR is the N9 forward FAR pointing at the PGW. - Both the PGW UL FARs and the SGW's gNB-facing UL FARs are overridden to
BUFFin the eBPF map. This is necessary because the eBPF program sees the packet as it arrives from the eNB with the SGW's TEID, not the PGW's TEID. - Only the SGW's gNB-facing UL PDRs are registered in the RedirectIndex (not the PGW UL PDRs). The SGW UL PDR TEID is what eBPF will see in the incoming packet from the eNB.
- For the DL path (sending responses back to the UE), the Dispatcher uses the SGW session's DL FAR (the FAR forwarding toward the eNB, with
remoteip != n3_address). This FAR contains the current eNB TEID and gNB IP. The DL TEID is looked up live from the session state at response time, not cached at activation time - this handles the case where the eNB TEID is assigned by a subsequent Session Modification after redirect activation.
In a direct UPF topology (no SGW, SMF talks directly to UPF), only the session's own UL FARs and TEIDs are involved - the SGW detection code finds nothing and the main session's UL PDRs are registered directly.
PFCP Signaling Flow
PFCP IEs Involved
The SMF triggers a redirect by including redirect_information in the FAR's forwarding_parameters:
| IE | Description |
|---|---|
apply_action | Set to FORW by SMF (UPF overrides to BUFF internally) |
redirect_information | Contains redirect type and address |
forwarding_parameters | Contains the redirect_information and outer_header_creation |
Redirect Information types (per 3GPP TS 29.244 Table 8.2.20-1):
| Type | Value | UPF Behaviour |
|---|---|---|
| IPv4 | 0 | Use the provided IPv4 address string as the portal IP |
| IPv6 | 1 | Use the provided IPv6 address string as the portal IP |
| URL | 2 | Resolve the URL hostname to an IP; use that as the portal IP. The URL path is not used by the UPF -- it is stored in the API for visibility only. The portal web server is responsible for handling any path-based routing. |
| SIP URI | 3 | Not currently supported |
Captive Portal Detection
The walled garden triggers automatic captive portal detection on all major device platforms by spoofing DNS responses. When a device connects and attempts to verify internet connectivity, it queries well-known domains. The spoofed DNS response redirects these checks to the portal IP, which the device interprets as a captive portal.
Platform Detection Domains
| Platform | Detection Domain | Expected Response |
|---|---|---|
| Apple (iOS/macOS) | captive.apple.com | HTTP 200 with <HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML> |
| Android | connectivitycheck.gstatic.com | HTTP 204 |
| Windows | www.msftconnecttest.com | HTTP 200 with Microsoft Connect Test |
| Samsung | connectivitycheck.samsung.com | HTTP 200 |
When these domains resolve to the portal IP instead of their real addresses, the device detects a captive portal and presents the portal page to the user either as:
- A notification/popup (iOS, Android)
- An automatic browser redirect (Windows)
The portal server at the configured portal IP should serve appropriate responses for these detection URLs and then redirect to the top-up/payment page.
Configuration
Walled garden configuration lives in the runtime configuration file. On a production install (.deb package), the configuration file is at /etc/omniupf/runtime.exs. The release startup script (rel/env.sh) checks for this file and, if present, sets RELEASE_CONFIG_DIR=/etc/omniupf so that the Erlang release uses it instead of the bundled config/runtime.exs. The UPF must be restarted after configuration changes.
# =============================================================================
# Walled Garden / Out-of-Credit Redirect
# =============================================================================
# Enable walled garden redirect enforcement
walled_garden_enabled = true
# Portal server IP (captive portal / top-up page)
walled_garden_portal_ip = "10.179.2.1"
# Optional IPv6 portal server address (fallback for IPv6 subscribers)
walled_garden_portal_ip6 = "fd00:179:2::1"
# Upstream DNS resolver for whitelisted domain lookups
walled_garden_dns_resolver = "8.8.8.8"
# Whitelisted domains (subscribers can reach these while redirected)
# Supports wildcards: "*.stripe.com" matches "api.stripe.com"
walled_garden_whitelist = [
"stripe.com",
"*.stripe.com",
"js.stripe.com",
"hcaptcha.com",
"*.hcaptcha.com",
"newassets.hcaptcha.com",
]
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
walled_garden_enabled | Boolean | No | false | Master switch for walled garden functionality. When false, redirect_information in FARs is ignored. |
walled_garden_portal_ip | String (IPv4) | Yes (if enabled) | 10.179.2.1 | IP address of the captive portal / top-up server. This is the fallback portal IP used when the SMF's redirect_information URL hostname cannot be resolved, or when the redirect type is SIP URI. For IPv4/IPv6 types, the address from the IE is used directly. For URL type, the hostname is resolved at FAR creation time. |
walled_garden_portal_ip6 | String (IPv6) | No | nil | Optional IPv6 portal address, used as the fallback portal for IPv6 subscribers in the same way walled_garden_portal_ip is used for IPv4. When unset, IPv6 redirect fallback is unavailable. |
walled_garden_dns_resolver | String (IPv4) | No | 8.8.8.8 | Upstream DNS resolver used when forwarding queries for whitelisted domains. Only whitelisted domain queries are sent to this resolver; all other queries are spoofed locally. |
walled_garden_whitelist | List of Strings | No | [] | Domain patterns that subscribers can access while in the walled garden. See Whitelist Management for pattern syntax. |
Whitelist Management
The whitelist controls which domains (and their resolved IPs) subscribers can reach while redirected. This is configured on the UPF, not the SMF -- the SMF only triggers the redirect.
Pattern Syntax
| Pattern | Matches | Does Not Match |
|---|---|---|
stripe.com | stripe.com, api.stripe.com, js.stripe.com | evilstripe.com, notstripe.com |
*.stripe.com | api.stripe.com, js.stripe.com, dashboard.stripe.com | stripe.com (exact), evilstripe.com |
hcaptcha.com | hcaptcha.com, newassets.hcaptcha.com | evihcaptcha.com |
Patterns use subdomain anchoring: stripe.com matches the domain itself and any subdomain (foo.stripe.com), but not domains that merely contain the string (evilstripe.com). Matching is case-insensitive.
IP Caching
When the DNS proxy forwards a query for a whitelisted domain, the resolved IP addresses are automatically cached in the whitelist. This means:
- Subscriber queries
api.stripe.com - DNS proxy forwards to real resolver, gets back
104.18.7.25 104.18.7.25is added to the whitelisted IP cache- Subsequent HTTP/HTTPS traffic to
104.18.7.25is forwarded (not dropped)
The portal IP is always whitelisted regardless of configuration.
Recommended Whitelist Domains
For a typical top-up portal with Stripe payment processing and hCaptcha:
walled_garden_whitelist = [
# Payment processor
"stripe.com",
"*.stripe.com",
# CAPTCHA service
"hcaptcha.com",
"*.hcaptcha.com",
# Google Fonts (if portal uses them)
"fonts.googleapis.com",
"fonts.gstatic.com",
# CDN for portal assets (if hosted externally)
"cdn.example.com",
]
Per-Session Redirect URLs
Each PFCP session can have a different redirect target. The SMF controls this via the redirect_information IE in the FAR:
- IPv4 type: The provided IP string is parsed and used as the portal IP for that session
- IPv6 type: The provided IPv6 string is parsed and used as the portal IP for that session
- URL type: The hostname is extracted and resolved via DNS at FAR creation time. The resolved IP is used as the portal IP. The URL path is not used by the UPF -- it is stored for API visibility only.
This enables scenarios where different subscriber tiers, MVNOs, or service plans redirect to different portals:
| Session | SMF redirect_information | Portal IP Used | Redirect URL |
|---|---|---|---|
| Session A | IPv4: 10.179.2.1 | 10.179.2.1 | 10.179.2.1 |
| Session B | IPv6: 2001:db8::1 | 2001:db8::1 | 2001:db8::1 |
| Session C | URL: https://topup.mvno.com/recharge | Resolved IP of topup.mvno.com | https://topup.mvno.com/recharge |
The UPF stores the per-session portal IP and redirect URL, which are visible via the API.
API
The base path for all walled garden endpoints is /v1/walled_garden (no /api prefix). These endpoints are served by the Phoenix HTTP server on the configured api_port (default: 8080).
GET /v1/walled_garden
Returns all active walled garden redirect sessions, whitelisted IPs, cached CIDR ranges, and session details.
Response:
{
"redirect_count": 2,
"redirects": [
{
"teid": "0x4000",
"session_seid": 1,
"portal_ip": "10.179.2.1",
"redirect_url": null,
"ue_ip": "10.60.0.1",
"gnb_ip": "10.179.1.21",
"dl_teid": "0x5000",
"far_global_id": 42
},
{
"teid": "0x4001",
"session_seid": 2,
"portal_ip": "10.179.2.2",
"redirect_url": "https://topup.mvno.com",
"ue_ip": "10.60.0.2",
"gnb_ip": "10.179.1.21",
"dl_teid": "0x5001",
"far_global_id": 43
}
],
"whitelisted_ips": [
{"ip": "10.179.2.1", "type": "portal"},
{"ip": "104.18.7.25", "type": "resolved"},
{"ip": "104.18.6.25", "type": "resolved"}
],
"whitelisted_cidrs": ["192.168.0.0/24"]
}
Response Fields:
| Field | Description |
|---|---|
redirect_count | Number of active walled garden sessions |
redirects[].teid | Uplink TEID being intercepted (hex) |
redirects[].session_seid | PFCP session SEID |
redirects[].portal_ip | Portal IP for this specific session |
redirects[].redirect_url | Redirect URL from SMF (if URL type), or null |
redirects[].ue_ip | UE IP address |
redirects[].gnb_ip | gNB IP for GTP-U responses |
redirects[].dl_teid | Downlink TEID for GTP-U encapsulation |
redirects[].far_global_id | Internal FAR global ID |
whitelisted_ips | All IPs currently in the whitelist (portal + cached DNS resolutions) |
whitelisted_cidrs | CIDR ranges added via the whitelist API |
POST /v1/walled_garden
Activate walled garden redirect on an existing PFCP session by SEID. This is the operator/API-triggered path - the normal path is via PFCP Session Modification with redirect_information in a FAR. The API path is useful for testing and for operators who need to manually redirect a session without SMF involvement.
Request body:
{
"seid": 1,
"url": "http://10.179.2.1/"
}
| Field | Type | Required | Description |
|---|---|---|---|
seid | Integer | Yes | Local SEID of the PFCP session to redirect |
url | String | Yes | Redirect target - an IPv4/IPv6 address or a URL. If a URL is given, the hostname is resolved and the resulting IP is used as the portal IP. The full URL string is stored for API visibility. |
Response (200 OK):
{
"status": "redirect activated",
"info": {
"seid": 1,
"sgw_seid": 7,
"portal_ip": "10.179.2.1",
"ue_ip": "10.60.0.1",
"gnb_ip": "10.179.1.21",
"ul_teids": ["0x4000", "0x4006"]
}
}
The sgw_seid field is non-null when an N9 loopback (SGW+PGW paired session) was detected and its uplink TEID was also BUFF'd. ul_teids lists all uplink TEIDs that were registered in the redirect index.
Error responses:
| Status | Meaning |
|---|---|
| 404 | Session not found (SEID does not match any active PFCP session) |
| 400 | Missing required parameters |
DELETE /v1/walled_garden/:seid
Deactivate the walled garden redirect for a session, restoring normal forwarding. All uplink FARs for the session (including paired SGW session FARs in an N9 loopback topology) are set back to action=FORW in the eBPF map, and the TEIDs are unregistered from the redirect index.
Path parameter: :seid - the local SEID of the session to deactivate.
Response (200 OK):
{"status": "redirect removed", "info": {"seid": 1}}
Error responses:
| Status | Meaning |
|---|---|
| 404 | Session not found |
GET /v1/walled_garden/whitelist
Returns the current whitelist: individual cached IPs (portal IP and DNS-resolved IPs) and any CIDR ranges added via the API.
Response:
{
"ips": [
{"ip": "10.179.2.1", "type": "portal"},
{"ip": "104.18.7.25", "type": "resolved"}
],
"cidrs": ["192.168.100.0/24"]
}
POST /v1/walled_garden/whitelist
Add an IP address or CIDR range to the whitelist at runtime, without restarting the UPF. Changes are in-memory only and do not persist across restarts - add permanent entries to walled_garden_whitelist in runtime.exs.
Request body (add an IP):
{"ip": "203.0.113.10"}
Request body (add a CIDR range):
{"cidr": "192.168.100.0/24"}
Exactly one of ip or cidr must be present. Adding a CIDR range means any traffic to IPs within that range is forwarded by the dispatcher without needing a per-IP DNS cache entry.
Response (200 OK - IP added):
{"status": "added", "ip": "203.0.113.10"}
Response (200 OK - CIDR added):
{"status": "added", "cidr": "192.168.100.0/24"}
Error responses:
| Status | Meaning |
|---|---|
| 400 | Invalid IP, invalid CIDR, or missing body fields |
Prometheus Metrics
Gauges
Metric: upf_walled_garden_active_redirects
Type: Gauge
Description: Number of sessions currently in walled garden redirect state
Example queries:
# Current redirect count
upf_walled_garden_active_redirects
Counters
Metric: upf_walled_garden_packets_intercepted_total
Type: Counter
Description: Total packets intercepted by the walled garden (all uplink traffic from redirected sessions)
Metric: upf_walled_garden_packets_dropped_total
Type: Counter
Description: Total packets dropped by the walled garden (non-whitelisted, non-DNS traffic)
Metric: upf_walled_garden_packets_forwarded_total
Type: Counter
Labels:
dst_ip- Destination IP of the forwarded packet Description: Packets forwarded through the walled garden to whitelisted destinations. Labelled by destination IP for per-destination visibility.
Metric: upf_walled_garden_bytes_forwarded_total
Type: Counter
Labels:
dst_ip- Destination IP of the forwarded traffic Description: Bytes forwarded through the walled garden per destination IP. Use this to identify which whitelisted services subscribers access while redirected.
Metric: upf_walled_garden_dns_spoofed_total
Type: Counter
Labels:
domain- The domain that was spoofed Description: DNS queries spoofed by the walled garden. Labelled by queried domain.
Metric: upf_walled_garden_dns_forwarded_total
Type: Counter
Labels:
domain- The whitelisted domain that was forwarded Description: DNS queries forwarded to the real resolver (whitelisted domains). Labelled by domain.
Example Queries
# Active walled garden sessions
upf_walled_garden_active_redirects
# Intercept rate (packets/sec)
rate(upf_walled_garden_packets_intercepted_total[5m])
# Drop rate (should be majority of intercepted)
rate(upf_walled_garden_packets_dropped_total[5m])
# Per-destination forwarded traffic (bytes/sec)
sum by (dst_ip) (rate(upf_walled_garden_bytes_forwarded_total[5m]))
# Top 5 whitelisted destinations by traffic volume
topk(5, sum by (dst_ip) (rate(upf_walled_garden_bytes_forwarded_total[5m])))
# Most queried spoofed domains
topk(10, sum by (domain) (rate(upf_walled_garden_dns_spoofed_total[5m])))
# Whitelisted domain lookup rate
sum by (domain) (rate(upf_walled_garden_dns_forwarded_total[5m]))
# Ratio of dropped vs forwarded
sum(rate(upf_walled_garden_packets_dropped_total[5m]))
/ sum(rate(upf_walled_garden_packets_intercepted_total[5m]))
Troubleshooting
Captive Portal Not Appearing on Device
Symptoms: Subscriber is redirected (visible in API) but device does not show captive portal prompt.
Possible causes:
- Portal server not responding on the configured portal IP
- Portal server not handling platform-specific detection URLs
- DNS response not reaching the UE (check GTP-U path)
Resolution:
- Verify portal server is reachable:
curl http://<portal_ip>/ - Confirm portal handles detection URLs (e.g.,
GET /hotspot-detect.htmlfor Apple) - Check
GET /v1/walled_gardento confirm the session is registered - Check Prometheus metrics:
upf_walled_garden_dns_spoofed_totalshould be incrementing - Verify downlink GTP-U path is working (DL FAR should remain FORW)
Payment Page Not Loading
Symptoms: Subscriber sees captive portal but cannot reach the payment page (Stripe, etc.).
Possible causes:
- Payment processor domain not in whitelist
- Payment processor using a CDN IP that wasn't cached
- Whitelist pattern not matching subdomains correctly
Resolution:
- Check
GET /v1/walled_garden- verifywhitelisted_ipsincludes the payment processor IPs - Check Prometheus:
upf_walled_garden_dns_forwarded_total{domain="stripe.com"}should show lookups - Add missing domains to the whitelist (common:
js.stripe.com,m.stripe.network) - Check
upf_walled_garden_bytes_forwarded_totalbydst_ipto see what traffic is actually flowing
Redirect Not Activating
Symptoms: SMF sends Session Modification with redirect_information but subscriber traffic is not intercepted.
Possible causes:
walled_garden_enabledisfalse- The
redirect_informationIE is in the wrong FAR (must be in the uplink FAR) - FAR action not being overridden to BUFF
Resolution:
- Verify configuration:
walled_garden_enabled = true - Check
GET /v1/pfcp_sessions- look at the FAR action for the session. Uplink FAR should show action0x04(BUFF) - Check
GET /v1/walled_garden- the session's TEID should appear in the redirect list - Check UPF logs for
redirect_infomessages during session modification
Redirect Not Clearing After Top-Up
Symptoms: Subscriber has topped up but traffic is still being intercepted.
Possible causes:
- SMF has not sent the Session Modification to remove the redirect
- BUFF->FORW transition not being detected
Resolution:
- Check
GET /v1/walled_garden- is the session still listed? - Check
GET /v1/pfcp_sessions- verify the FAR action has been updated back to0x02(FORW) - Check SMF logs to confirm it sent the Session Modification
- Check UPF logs for "redirect removed, unregistering walled garden" message
Wrong Downlink TEID (Stale After Session Modification)
Symptoms: DNS spoofed responses or forwarded traffic are not reaching the UE; GTP-U packets are sent to the correct gNB IP but the eNB drops them as unknown TEID.
Cause: In 4G/SGW+PGW topologies, the eNB TEID in the SGW DL FAR may be zero or a placeholder TEID at the time the walled garden redirect is activated (e.g., the Initial Context Setup exchange happens after the Session Establishment). When the eNB allocates its TEID and sends it back via Session Modification, the DL FAR in the SGW session is updated - but if the Dispatcher had cached the old value, it would use the wrong TEID.
Resolution: The Dispatcher resolves the DL TEID live at response time by looking up the SGW session's current DL FAR (the FAR with outer_header_creation set and remoteip != n3_address). It only falls back to the value stored at activation time if the session lookup fails. Therefore, a stale TEID should self-correct as soon as the next DNS or forwarded response is sent. If you are still seeing the wrong TEID:
- Check
GET /v1/walled_garden- verifydl_teidin the redirect entry looks plausible (non-zero). - Check
GET /v1/pfcp_sessions- look at the SGW session's FAR entries; the gNB-facing FAR should have the currentteidandremoteip. - If the SGW session has been deleted (e.g., handover or release), the redirect entry will remain in the RedirectIndex but the live lookup will fail. In this case, use
DELETE /v1/walled_garden/:seidto clean up the stale entry and let the SMF re-establish.
Portal Returns 304 Not Modified
Symptoms: Subscriber's browser shows blank page or the captive portal page loads once but subsequent visits appear empty.
Cause: HTTP 304 (Not Modified) responses are sent when the browser has a cached version of the page and the server confirms nothing has changed. For captive portal flows, some portal web servers send 304 in response to the platform detection requests (/hotspot-detect.html, /generate_204, etc.) if the browser sends If-Modified-Since or If-None-Match headers. Some captive portal implementations also send 304 for the redirect page itself.
Resolution:
- The UPF forwards HTTP responses from the portal server transparently - it does not modify response codes. The issue is in the portal web server configuration.
- On the portal server, ensure that the platform detection endpoints return the correct status code (200, not 304) with the expected body content (see Captive Portal Detection).
- Configure the portal server to send
Cache-Control: no-storeand omitETag/Last-Modifiedheaders on detection endpoints so browsers do not cache them. - Verify with:
curl -v -H "If-None-Match: foo" http://<portal_ip>/hotspot-detect.html- the response should be 200, not 304.
Raw Socket Forwarding
When the Dispatcher forwards a packet to a whitelisted IP, it uses a raw socket (IPPROTO_RAW, protocol number 255) via Erlang's :socket module. A new raw socket is opened per packet, the full inner IP packet (as received from eBPF) is sent with sendto, and the socket is immediately closed.
How this works: The inner IP packet from the GTP-U payload already has a valid IP header with the UE IP as source and the destination server IP as destination. By injecting this packet via a raw socket using IPPROTO_RAW, the kernel routes it based on the destination IP using the host routing table. The UPF's N6 interface must have a route to the portal/whitelisted server for this to work.
Common issues:
| Symptom | Likely Cause | Fix |
|---|---|---|
| Forward silently fails | EPERM - raw socket requires root or CAP_NET_RAW | Ensure OmniUPF runs as root or has CAP_NET_RAW capability |
| Packets forwarded but no response reaches UE | N6 route to portal missing | Add route to portal subnet on the UPF host |
Walled garden: raw socket open failed in logs | Missing capability or kernel restriction | Check systemd service AmbientCapabilities=CAP_NET_RAW |
| Forward works but UE gets wrong IP as source | NAT on N6 rewrites source | Ensure portal replies to UE IP are routed back through UPF |
Check UPF logs for Walled garden forward failed and raw socket open failed messages. Use upf_walled_garden_packets_forwarded_total and upf_walled_garden_bytes_forwarded_total Prometheus metrics to confirm traffic is flowing.
High Cardinality Warning for Prometheus
Note: The dst_ip and domain labels on walled garden metrics can produce high cardinality if many unique destinations or domains are queried. In large deployments, consider using recording rules to aggregate these metrics:
# Recording rule to aggregate by /24 subnet instead of individual IP
sum by (dst_subnet) (
label_replace(
rate(upf_walled_garden_bytes_forwarded_total[5m]),
"dst_subnet", "$1.0/24", "dst_ip", "(\d+\.\d+\.\d+)\.\d+"
)
)