OmniPGW Configuration Guide
Complete Reference for runtime.exs Configuration
by Omnitouch Network Services
Table of Contents
- Overview
- Configuration File Structure
- Metrics Configuration
- Diameter/Gx Configuration
- S5/S8 Configuration
- Gn/Gp Configuration (GGSN)
- Sxb/PFCP Configuration
- UE IP Pool Configuration
- PCO Configuration
- Web UI Configuration
- Complete Example
- Configuration Validation
Overview
OmniPGW uses runtime configuration defined in config/runtime.exs. This file is evaluated at application startup and allows for dynamic configuration based on environment variables or external sources.
Configuration Philosophy
Key Principles:
- Single Source of Truth - All configuration in one file
- Type Safety - Configuration validated at startup
- Environment Flexibility - Support for dev, test, production
- Clear Defaults - Sensible defaults with explicit overrides
Configuration File Structure
File Location
pgw_c/
├── config/
│ ├── config.exs # Base configuration (imports runtime.exs)
│ ├── dev.exs # Development-specific config
│ ├── prod.exs # Production-specific config
│ └── runtime.exs # ← Main configuration file
Top-Level Structure
# config/runtime.exs
import Config
config :logger, level: :info
config :pgw_c,
metrics: %{...},
diameter: %{...},
s5s8: %{...},
sxb: %{...},
ue: %{...},
pco: %{...}
Configuration Sections
Metrics Configuration
Purpose
Configure the Prometheus metrics exporter for monitoring OmniPGW.
Configuration Block
config :pgw_c,
metrics: %{
# Enable/disable metrics exporter
enabled: true,
# IP address to bind HTTP server
ip_address: "0.0.0.0",
# Port for metrics endpoint
port: 9090,
# How often to poll registries (milliseconds)
registry_poll_period_ms: 10_000
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | Boolean | true | Enable metrics exporter |
ip_address | String (IP) | "0.0.0.0" | Bind address (0.0.0.0 = all interfaces) |
port | Integer | 9090 | HTTP port for /metrics endpoint |
registry_poll_period_ms | Integer | 10_000 | Polling interval for registry counts |
Examples
Production - Bind to specific IP:
metrics: %{
enabled: true,
ip_address: "10.0.0.20", # Management network
port: 9090,
registry_poll_period_ms: 5_000 # Poll every 5 seconds
}
Development - Localhost only:
metrics: %{
enabled: true,
ip_address: "127.0.0.1",
port: 42069, # Non-standard port
registry_poll_period_ms: 10_000
}
Disable metrics:
metrics: %{
enabled: false
}
Accessing Metrics
# Default endpoint
curl http://<ip_address>:<port>/metrics
# Example
curl http://10.0.0.20:9090/metrics
See: Monitoring & Metrics Guide for detailed metrics documentation.
Diameter/Gx Configuration
Purpose
Configure the Diameter protocol for Gx interface (PCRF communication).
Configuration Block
config :pgw_c,
diameter: %{
# IP address to listen for Diameter connections
listen_ip: "0.0.0.0",
# OmniPGW's Diameter identity (Origin-Host)
host: "omnipgw.epc.mnc001.mcc001.3gppnetwork.org",
# OmniPGW's Diameter realm (Origin-Realm)
realm: "epc.mnc001.mcc001.3gppnetwork.org",
# List of PCRF peers
peer_list: [
%{
# PCRF Diameter identity
host: "pcrf.epc.mnc001.mcc001.3gppnetwork.org",
# PCRF realm
realm: "epc.mnc001.mcc001.3gppnetwork.org",
# PCRF IP address
ip: "10.0.0.30",
# Initiate connection to PCRF
initiate_connection: true
}
]
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
listen_ip | String (IP) | Yes | Diameter listen address |
host | String (FQDN) | Yes | OmniPGW's Origin-Host (must be FQDN) |
realm | String (Domain) | Yes | OmniPGW's Origin-Realm |
peer_list | List | Yes | PCRF peer configurations |
Peer Configuration:
| Parameter | Type | Required | Description |
|---|---|---|---|
host | String (FQDN) | Yes | PCRF Diameter identity |
realm | String (Domain) | Yes | PCRF realm |
ip | String (IP) | Yes | PCRF IP address |
initiate_connection | Boolean | Yes | Whether OmniPGW connects to PCRF |
FQDN Format
Diameter identities MUST be FQDNs:
# CORRECT
host: "omnipgw.epc.mnc001.mcc001.3gppnetwork.org"
# INCORRECT
host: "omnipgw" # Not a FQDN
host: "10.0.0.20" # IP not allowed
3GPP Format:
<hostname>.epc.mnc<MNC>.mcc<MCC>.3gppnetwork.org
Examples:
- omnipgw.epc.mnc001.mcc001.3gppnetwork.org (MCC=001, MNC=001)
- pgw-c.epc.mnc260.mcc310.3gppnetwork.org (MCC=310, MNC=260 - US T-Mobile)
Examples
Single PCRF:
diameter: %{
listen_ip: "0.0.0.0",
host: "omnipgw.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
peer_list: [
%{
host: "pcrf.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
ip: "10.0.0.30",
initiate_connection: true
}
]
}
Multiple PCRFs (Redundancy):
diameter: %{
listen_ip: "0.0.0.0",
host: "omnipgw.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
peer_list: [
%{
host: "pcrf-primary.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
ip: "10.0.1.30",
initiate_connection: true
},
%{
host: "pcrf-backup.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
ip: "10.0.2.30",
initiate_connection: true
}
]
}
PCRF-Initiated Connection:
diameter: %{
listen_ip: "0.0.0.0",
host: "omnipgw.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
peer_list: [
%{
host: "pcrf.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
ip: "10.0.0.30",
initiate_connection: false # Wait for PCRF to connect
}
]
}
See: Diameter Gx Interface Documentation
S5/S8 Configuration
Purpose
Configure the GTP-C interface for communication with SGW-C.
Configuration Block
config :pgw_c,
s5s8: %{
# Local IPv4 address for S5/S8 interface
local_ipv4_address: "10.0.0.20",
# Optional: Local IPv6 address
local_ipv6_address: nil,
# Optional: Override default GTP-C port (2123)
local_port: 2123,
# GTP-C request timeout in milliseconds (default: 500ms)
# Timeout per attempt when waiting for GTP-C responses
request_timeout_ms: 500,
# Number of retry attempts for GTP-C requests (default: 3)
# Total maximum wait time = request_timeout_ms * request_attempts
request_attempts: 3
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
local_ipv4_address | String (IPv4) | Required | S5/S8 interface IPv4 address |
local_ipv6_address | String (IPv6) | nil | S5/S8 interface IPv6 address (optional) |
local_port | Integer | 2123 | UDP port for GTP-C (standard port 2123) |
request_timeout_ms | Integer | 500 | Timeout per retry attempt in milliseconds |
request_attempts | Integer | 3 | Number of retry attempts before giving up |
Protocol Details
- Protocol: GTP-C Version 2
- Transport: UDP
- Standard Port: 2123
- Direction: Receives from SGW-C
Examples
IPv4 Only (Common):
s5s8: %{
local_ipv4_address: "10.0.0.20"
}
IPv4 + IPv6 Dual-Stack:
s5s8: %{
local_ipv4_address: "10.0.0.20",
local_ipv6_address: "2001:db8::20"
}
Custom Port (Non-Standard):
s5s8: %{
local_ipv4_address: "10.0.0.20",
local_port: 2124 # Custom port
}
High Latency Network:
s5s8: %{
local_ipv4_address: "10.0.0.20",
request_timeout_ms: 1500, # 1.5 seconds per attempt
request_attempts: 3 # Total: 4.5 seconds max
}
Timeout Configuration
The S5/S8 interface uses configurable timeouts for GTP-C request/response transactions (Create Bearer Request, Delete Bearer Request).
Total Wait Time Calculation:
Total Maximum Wait = request_timeout_ms × request_attempts
Default: 500ms × 3 = 1.5 seconds
Tuning Guidelines:
| Network Latency | Recommended Timeout | Total Wait Time |
|---|---|---|
| Low latency (<50ms) | 200-300ms | 600-900ms |
| Normal (50-150ms) | 500ms (default) | 1.5s |
| High latency (>150ms) | 1000-2000ms | 3-6s |
| Satellite/unstable | 2000-3000ms | 6-9s |
When to Adjust:
- Increase timeout if seeing frequent "Create Bearer Request timed out" errors but Wireshark shows responses arriving
- Decrease timeout for faster failure detection in low-latency environments
- Increase retry attempts for unreliable networks with packet loss
Timeout Behavior:
- On timeout, error is logged:
"Create Bearer Request timed out" - Diameter error returned to PCRF: Result-Code 5012 (UNABLE_TO_COMPLY)
- Bearer remains in early storage for cleanup when Charging-Rule-Remove arrives
Network Planning
IP Address Selection:
- Use dedicated management/signaling network
- Ensure reachability from all SGW-C nodes
- Consider redundancy (VRRP/HSRP) for HA
Firewall Rules:
# Allow GTP-C from SGW-C
iptables -A INPUT -p udp --dport 2123 -s <sgw_c_network> -j ACCEPT
Gn/Gp Configuration (GGSN)
Purpose
Configure the GTP-C v1 interface for communication with SGSNs, enabling GGSN functionality for 2G/3G networks.
Configuration Block
config :pgw_c,
gn: %{
# Local IPv4 address for Gn interface
local_ipv4_address: "10.0.0.20",
# Optional: Local IPv6 address (most 2G/3G networks are IPv4-only)
local_ipv6_address: nil,
# GTP-C port (shared with S5/S8)
local_port: 2123
},
# DNS servers returned in PCO (shared with S5/S8)
dns: %{
primary_ipv4: {8, 8, 8, 8},
secondary_ipv4: {8, 8, 4, 4}
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
local_ipv4_address | String (IPv4) | Required | Gn interface IPv4 address (GGSN address) |
local_ipv6_address | String (IPv6) | nil | Gn interface IPv6 address (optional, rarely used) |
local_port | Integer | 2123 | UDP port for GTP-C v1 (shared with S5/S8) |
DNS Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
primary_ipv4 | Tuple | {8, 8, 8, 8} | Primary DNS server for PCO response |
secondary_ipv4 | Tuple | {8, 8, 4, 4} | Secondary DNS server for PCO response |
Protocol Details
- Protocol: GTP-C Version 1 (3GPP TS 29.060)
- Transport: UDP
- Standard Port: 2123 (shared with GTP-C v2)
- Direction: Receives from SGSNs
Coexistence with S5/S8
OmniPGW can operate as both PGW (4G) and GGSN (2G/3G) simultaneously:
- Both interfaces share UDP port 2123
- GTP version is auto-detected from message headers
- Same IP address can serve both 4G and 2G/3G traffic
- Shared infrastructure: IP pools, UPF/PFCP, online charging
See: Gn/Gp Interface Documentation
Sxb/PFCP Configuration
Purpose
Configure the PFCP interface for communication with PGW-U (User Plane).
Configuration Block
config :pgw_c,
sxb: %{
# Local IP address for PFCP communication
local_ip_address: "10.0.0.20",
# Optional: Override default PFCP port (8805)
local_port: 8805
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
local_ip_address | String (IP) | Required | PFCP listen address |
local_port | Integer | 8805 | PFCP UDP port |
Important:
- All UPF peers are automatically registered from the
upf_selectionconfiguration (rules + fallback pool) at startup- Auto-registered UPFs use sensible defaults:
- Auto-generated name:
"UPF-<ip>:<port>"- Passive PFCP association (wait for UPF to initiate)
- 5-second heartbeat interval
- UPF selection rules and pools are configured in the separate
upf_selectionsection. See UPF Selection Strategies below.- Dynamic UPF registration is supported for DNS-discovered UPFs that aren't in the configuration
Examples
Minimal Configuration:
sxb: %{
local_ip_address: "10.0.0.20"
}
# All UPFs in upf_selection will be automatically registered with:
# - Auto-generated name: "UPF-10.0.0.21:8805"
# - Passive PFCP association (wait for UPF to connect)
# - 5-second heartbeat interval
Custom PFCP Port:
sxb: %{
local_ip_address: "10.0.0.20",
local_port: 8806 # Non-standard PFCP port
}
Complete Example with UPF Selection:
sxb: %{
local_ip_address: "10.0.0.20"
},
upf_selection: %{
rules: [
%{
name: "IMS Pool",
priority: 10,
match_field: :apn,
match_regex: ~r/^ims$/,
upf_pool: [
%{remote_ip_address: "10.0.1.21", remote_port: 8805, weight: 100},
%{remote_ip_address: "10.0.1.22", remote_port: 8805, weight: 100}
]
}
],
fallback_pool: [
%{remote_ip_address: "10.0.2.21", remote_port: 8805, weight: 100}
]
}
# All 3 UPFs (10.0.1.21, 10.0.1.22, 10.0.2.21) are automatically registered
DNS-Based Selection (Dynamic Registration):
sxb: %{
local_ip_address: "10.0.0.20"
},
upf_selection: %{
dns_enabled: true,
dns_query_priority: [:ecgi, :tai],
dns_suffix: "epc.3gppnetwork.org",
fallback_pool: [
%{remote_ip_address: "10.0.2.21", remote_port: 8805, weight: 100}
]
}
# DNS-discovered UPFs will be dynamically registered on first use
UPF Selection Strategies
Important: UPF selection configuration has been simplified. All UPF peers are automatically registered from the upf_selection configuration.
Configuration Structure
UPF selection is configured in the upf_selection section which defines:
- Static Rules - Pattern-based routing with load balancing pools
- DNS Settings - Location-based dynamic UPF discovery
- Fallback Pool - Default pool when no rules match and DNS fails
Selection Priority Order
- Static Rules (Highest Priority) - Pattern-based routing with load balancing pools
- DNS-Based Selection (Lower Priority) - Location-based dynamic UPF discovery
- Fallback Pool (Lowest Priority) - Default pool when no rules match and DNS fails
UPF Selection Decision Flow
Available Match Fields
Static rules can match on any of these session attributes:
| Match Field | Description | Example Pattern |
|---|---|---|
:imsi | International Mobile Subscriber Identity | ^001001.* (test network) |
:apn | Access Point Name / DNN | ^internet\. or ^ims\. |
:serving_network_plmn_id | Serving network identifier | ^001001$ |
:sgw_ip_address | SGW IP address | ^10\.100\..* |
:uli_tai_plmn_id | Tracking Area PLMN ID | ^313.* |
:uli_ecgi_plmn_id | E-UTRAN Cell PLMN ID | ^313.* |
Selection Methods Comparison
| Method | When to Use | Pros | Cons |
|---|---|---|---|
| UPF Pools | Production deployments | Load balancing, HA, flexible weights | Requires multiple UPFs |
| APN-Based | Service differentiation | Route IMS/Internet separately | Static configuration |
| IMSI-Based | Roaming scenarios | Geographic routing | Regex complexity |
| DNS-Based | MEC/Edge computing | Dynamic, location-aware | Requires DNS infrastructure |
| Fallback Pool | Safety net | Always have a UPF | May not be optimal |
| Dry-Run Mode | Testing configs | Safe testing | No real traffic |
Complete Session Establishment Flow
This diagram shows the complete end-to-end flow of session establishment including UPF selection and PCO population:
Key Decision Points:
-
UPF Selection Priority:
- Static Rules (Pattern Match) → DNS Discovery → Fallback Pool
- Health filtering applied at all stages
- Active/Standby logic for high availability
- See: PFCP Interface for UPF communication details
-
PCO Population Priority:
- Rule PCO Override → P-CSCF DNS Discovery → Global PCO Config
- Per-field merging (rule overrides specific fields, global provides defaults)
- See: PCO Configuration for detailed PCO parameters
-
P-CSCF Discovery Priority:
- Per-Rule FQDN → Global DNS Discovery → Static Rule PCO → Global Static PCO
- See: P-CSCF Monitoring for discovery metrics and health tracking
-
Charging Integration:
- PCRF determines if online charging required (Rating-Group + Online=1)
- OCS grants quota before session establishment
- PGW-C tracks quota and requests more via CCR-Update
- See: Diameter Gx Interface and Diameter Gy Interface for charging details
Complete Configuration Example
Here's a complete example showing multi-pool UPF selection with automatic peer registration:
config :pgw_c,
# PFCP Interface - All UPFs are auto-registered from upf_selection
sxb: %{
local_ip_address: "127.0.0.20"
},
# UPF Selection Logic - All UPFs defined here are automatically registered
upf_selection: %{
# DNS-based selection settings
dns_enabled: false,
dns_query_priority: [:ecgi, :tai, :rai, :sai, :cgi],
dns_suffix: "epc.3gppnetwork.org",
dns_timeout_ms: 5000,
# Static selection rules (evaluated in priority order)
rules: [
# Rule 1: IMS Traffic - Highest Priority
%{
name: "IMS Traffic",
priority: 20,
match_field: :apn,
match_regex: "^ims",
upf_pool: [
%{remote_ip_address: "10.100.2.21", remote_port: 8805, weight: 80},
%{remote_ip_address: "10.100.2.22", remote_port: 8805, weight: 20}
]
},
# Rule 2: Enterprise APN
%{
name: "Enterprise Traffic",
priority: 15,
match_field: :apn,
match_regex: "^(enterprise|corporate)\.apn",
upf_pool: [
%{remote_ip_address: "10.100.3.21", remote_port: 8805, weight: 100}
]
},
# Rule 3: Internet Traffic - Load Balanced
%{
name: "Internet Traffic",
priority: 5,
match_field: :apn,
match_regex: "^internet",
upf_pool: [
%{remote_ip_address: "10.100.1.21", remote_port: 8805, weight: 33},
%{remote_ip_address: "10.100.1.22", remote_port: 8805, weight: 33},
%{remote_ip_address: "10.100.1.23", remote_port: 8805, weight: 34}
]
}
],
# Fallback pool - Used when no rules match and DNS fails
fallback_pool: [
%{remote_ip_address: "127.0.0.21", remote_port: 8805, weight: 100}
]
}
Key Features
Current Format:
- ✅ Automatic Registration: All UPFs from
upf_selectionare automatically registered at startup - ✅ Centralized Configuration: All UPF selection and peer configuration in one section
- ✅ Required Pools: All rules use
upf_poolformat (even for single UPF) - ✅ Structured Fallback: Dedicated
fallback_poolwith weighted distribution - ✅ DNS Integration: DNS settings alongside selection rules
- ✅ Dynamic Registration: DNS-discovered UPFs are automatically registered on first use
- ✅ Health Monitoring: All configured UPFs are monitored with 5-second heartbeats
Migration from Previous Format:
- Removed:
sxb.peer_listfield (no longer needed) - Removed:
selection_listembedded in peer configurations - All UPF definitions now go in
upf_selectionrules and fallback pool
How UPF Pools Work:
-
Health-Aware Selection: Only healthy UPFs receive traffic
- Healthy = PFCP association active + less than 3 consecutive missed heartbeats
- Unhealthy UPFs are automatically filtered out
- Falls back to all UPFs if none are healthy (fail-fast)
-
Active/Standby Support: Use
weight: 0for standby UPFs- Active UPFs (weight > 0): Receive traffic when healthy
- Standby UPFs (weight == 0): Only receive traffic when all active UPFs are down
- Standby UPFs are treated as
weight: 1when activated
-
Weighted Random Selection: Each session is randomly assigned to a healthy UPF based on weights
- In the example above: 70% go to .21, 20% to .22, 10% to .23
- Higher weight = more sessions assigned to that UPF
- Equal weights = equal distribution
-
Automatic Registration: All UPFs in pools are automatically registered at startup
- Auto-generated names:
"UPF-<ip>:<port>" - Default settings: passive PFCP association, 5-second heartbeats
- Immediate health tracking for all configured UPFs
- Auto-generated names:
Health-Aware Selection with Active/Standby
Weighted Random Selection Example:
Pool: [
UPF-A: weight 50, healthy ✓
UPF-B: weight 30, healthy ✓
UPF-C: weight 20, healthy ✓
]
Total Weight: 50 + 30 + 20 = 100
Weight Ranges:
UPF-A: 0-49 (50%)
UPF-B: 50-79 (30%)
UPF-C: 80-99 (20%)
Random number: 63 → Selects UPF-B
Random number: 15 → Selects UPF-A
Random number: 91 → Selects UPF-C
Active/Standby Failover Example:
Initial Pool: [
UPF-A: weight 100, healthy ✓ (Active)
UPF-B: weight 0, healthy ✓ (Standby)
]
Scenario 1: UPF-A Healthy
→ Use Active Pool: [UPF-A: 100]
→ All traffic to UPF-A
Scenario 2: UPF-A Fails
→ No active UPFs healthy
→ Activate Standby: [UPF-B: 1]
→ All traffic fails over to UPF-B
→ Log: "All active UPFs down, activating standby UPFs"
Scenario 3: Both Unhealthy
→ No healthy UPFs
→ Use full pool: [UPF-A: 100, UPF-B: 0]
→ Select with weights (attempt connection, may fail)
→ Log: "No healthy UPFs in pool, using full pool as fallback"
Common Weight Patterns:
# Equal distribution (25% each)
upf_pool: [
%{remote_ip_address: "10.0.1.1", remote_port: 8805, weight: 1},
%{remote_ip_address: "10.0.1.2", remote_port: 8805, weight: 1},
%{remote_ip_address: "10.0.1.3", remote_port: 8805, weight: 1},
%{remote_ip_address: "10.0.1.4", remote_port: 8805, weight: 1}
]
# Primary/backup load balanced (90% / 10%)
upf_pool: [
%{remote_ip_address: "10.0.1.21", remote_port: 8805, weight: 90},
%{remote_ip_address: "10.0.1.22", remote_port: 8805, weight: 10}
]
# Active/Standby (100% primary, 0% standby until primary fails)
upf_pool: [
%{remote_ip_address: "10.0.1.21", remote_port: 8805, weight: 100}, # Active
%{remote_ip_address: "10.0.1.22", remote_port: 8805, weight: 0} # Standby (only when active down)
]
# Active with multiple standbys (load balanced when activated)
upf_pool: [
%{remote_ip_address: "10.0.1.1", remote_port: 8805, weight: 100}, # Active
%{remote_ip_address: "10.0.1.2", remote_port: 8805, weight: 0}, # Standby 1
%{remote_ip_address: "10.0.1.3", remote_port: 8805, weight: 0} # Standby 2
]
# Result: Active gets 100%. If active fails, standbys get 50/50%.
# A/B testing (50% / 50%)
upf_pool: [
%{remote_ip_address: "10.0.1.100", remote_port: 8805, weight: 50}, # Old version
%{remote_ip_address: "10.0.1.200", remote_port: 8805, weight: 50} # New version
]
Use Cases:
- Active/Standby Failover: Use
weight: 0for hot standby UPFs that only activate when primaries fail - Health-Aware HA: Automatic failover when UPFs lose PFCP association or miss heartbeats
- Horizontal Scaling: Distribute load across multiple UPFs to increase capacity
- High Availability: Automatic distribution prevents single UPF overload
- Gradual Rollouts: Use weights for canary deployments (e.g., 95% old, 5% new)
- Cost Optimization: Route more traffic to higher-capacity UPFs
- Geographic Distribution: Balance sessions across edge UPFs
PCO (Protocol Configuration Options) Overrides:
Each UPF selection rule can optionally specify custom PCO values that override the default PCO configuration for matching sessions. This allows different APNs or traffic types to receive different network parameters.
How PCO Overrides Work:
- Partial Overrides: Only specify the PCO fields you want to override
- Default Fallback: Unspecified fields use values from the main
pcoconfig - Rule-Specific: Each rule can have different PCO overrides
- Priority Merging: Rule PCO takes priority over default PCO
PCO Population Hierarchy
Priority Order for Each PCO Field:
- Rule PCO Override (Highest Priority)
- P-CSCF DNS Discovery (for P-CSCF addresses only)
- Global PCO Configuration (Lowest Priority / Fallback)
Example: IMS Rule Overrides DNS, Enterprise Rule Overrides Everything
IMS Session (matched "IMS Traffic" rule):
├─ DNS Servers: FROM GLOBAL (not overridden in rule)
├─ P-CSCF: FROM DNS DISCOVERY (p_cscf_discovery_fqdn set in rule)
│ └─ Fallback: FROM RULE if DNS fails
└─ MTU: FROM GLOBAL (not overridden in rule)
Enterprise Session (matched "Enterprise Traffic" rule):
├─ DNS Servers: FROM RULE (192.168.1.10, 192.168.1.11)
├─ P-CSCF: FROM GLOBAL (not overridden in rule)
└─ MTU: FROM RULE (1500)
Default Session (no rule matched):
├─ DNS Servers: FROM GLOBAL
├─ P-CSCF: FROM GLOBAL or DNS if global discovery enabled
└─ MTU: FROM GLOBAL
Available PCO Override Fields:
primary_dns_server_address- Primary DNS server IP (IPv4)secondary_dns_server_address- Secondary DNS server IP (IPv4)primary_dns_server_ipv6_address- Primary DNS server IP (IPv6, container 0x0003)secondary_dns_server_ipv6_address- Secondary DNS server IP (IPv6, container 0x0003)primary_nbns_server_address- Primary WINS server IPsecondary_nbns_server_address- Secondary WINS server IPp_cscf_ipv4_address_list- List of IPv4 P-CSCF server IPs (for IMS, container 0x000C) - See PCO Configuration and P-CSCF Monitoring for dynamic P-CSCF discoveryp_cscf_ipv6_address_list- List of IPv6 P-CSCF server IPs (for IMS, container 0x0001)ipv4_link_mtu_size- MTU size in bytes
P-CSCF Discovery Per Rule:
In addition to PCO overrides, UPF selection rules can specify dynamic P-CSCF discovery:
p_cscf_discovery_fqdn- (String) FQDN for DNS-based P-CSCF discovery (e.g.,"pcscf.mnc001.mcc001.3gppnetwork.org")
When this parameter is set:
- PGW-C performs DNS lookup for the specified FQDN during session establishment
- DNS server returns list of P-CSCF IP addresses
- Discovered P-CSCF addresses are sent to UE via PCO
- If DNS lookup fails, falls back to
p_cscf_ipv4_address_listfrom PCO override (if specified) or global PCO config - See P-CSCF Monitoring for monitoring discovery success/failure rates
This is particularly useful for:
- IMS APNs - Different IMS networks with different P-CSCF servers
- Multi-tenant deployments - Different enterprises with dedicated P-CSCF infrastructure
- Geographic routing - DNS returns closest P-CSCF based on UE location
- High availability - DNS automatically returns only healthy P-CSCF servers
Example: IMS Traffic with Custom P-CSCF:
rules: [
%{
name: "IMS Traffic",
priority: 20,
match_field: :apn,
match_regex: "^ims",
upf_pool: [
%{remote_ip_address: "10.100.2.21", remote_port: 8805, weight: 80},
%{remote_ip_address: "10.100.2.22", remote_port: 8805, weight: 20}
],
# P-CSCF Discovery: Dynamically query DNS for P-CSCF addresses
# DNS lookup returns current P-CSCF IPs based on this FQDN
p_cscf_discovery_fqdn: "pcscf.mnc001.mcc001.3gppnetwork.org",
# IMS sessions get custom P-CSCF servers (used as fallback if DNS fails)
pco: %{
p_cscf_ipv4_address_list: ["203.0.113.100", "203.0.113.101"]
# DNS, NBNS, MTU will use defaults from main pco config
}
}
]
Example: Enterprise Traffic with Custom DNS:
rules: [
%{
name: "Enterprise Traffic",
priority: 15,
match_field: :apn,
match_regex: "^(enterprise|corporate)\.apn",
upf_pool: [
%{remote_ip_address: "10.100.3.21", remote_port: 8805, weight: 100}
],
# Enterprise sessions get corporate DNS and custom MTU
pco: %{
primary_dns_server_address: "192.168.1.10",
secondary_dns_server_address: "192.168.1.11",
ipv4_link_mtu_size: 1500
# P-CSCF, NBNS will use defaults from main pco config
}
}
]
Example: Complete Override (All PCO Fields):
rules: [
%{
name: "IoT APN - Fully Custom",
priority: 10,
match_field: :apn,
match_regex: "^iot\.m2m",
upf_pool: [
%{remote_ip_address: "10.100.5.21", remote_port: 8805, weight: 100}
],
# IoT sessions get completely custom PCO
pco: %{
primary_dns_server_address: "8.8.8.8",
secondary_dns_server_address: "8.8.4.4",
primary_nbns_server_address: "10.0.0.100",
secondary_nbns_server_address: "10.0.0.101",
p_cscf_ipv4_address_list: [], # No P-CSCF for IoT
ipv4_link_mtu_size: 1280 # Smaller MTU for constrained devices
}
}
]
Use Cases:
- IMS/VoLTE: Provide carrier-specific P-CSCF servers for voice services
- Enterprise APNs: Route corporate traffic through company DNS servers
- IoT/M2M: Use public DNS and optimized MTU for low-bandwidth devices
- Roaming: Provide local DNS servers for visiting subscribers
- Service Differentiation: Different network parameters per service type
DNS-Based UPF Selection:
Enable dynamic UPF selection based on User Location Information (ULI) using DNS NAPTR queries. DNS settings are now configured within the upf_selection section.
Note: This provides geographic or topology-based UPF selection. See PFCP Interface for PFCP association setup with dynamically discovered UPFs and Session Management for session establishment flows.
upf_selection: %{
# Enable DNS-based selection
dns_enabled: true,
# Location types to query in priority order
dns_query_priority: [:ecgi, :tai, :rai, :sai, :cgi],
# DNS suffix for 3GPP NAPTR queries
dns_suffix: "epc.3gppnetwork.org",
# DNS query timeout in milliseconds
dns_timeout_ms: 5000,
# ... rules and fallback_pool ...
}
DNS-based selection works as follows:
- Priority: DNS selection is used only when NO static rules match (lower priority)
- Query Generation: Builds DNS NAPTR queries based on UE location:
- ECGI query:
eci-<hex>.ecgi.epc.mnc<MNC>.mcc<MCC>.epc.3gppnetwork.org - TAI query:
tac-lb<hex>.tac-hb<hex>.tac.epc.mnc<MNC>.mcc<MCC>.epc.3gppnetwork.org - RAI, SAI, CGI queries follow similar 3GPP TS 23.003 format
- ECGI query:
- Fallback Hierarchy: Tries each location type in priority order until a match is found
- Peer Matching: DNS results are filtered against configured peer list
- Selection: Chooses matching peer (currently first match, load-based selection coming soon)
Example DNS Records (configure on your DNS server):
; NAPTR record for TAC 100 in PLMN 001-001
tac-lb64.tac-hb00.tac.epc.mnc001.mcc001.epc.3gppnetwork.org IN NAPTR 10 50 "a" "x-3gpp-upf:x-sxb" "" upf-edge-1.example.com.
; A record for the UPF
upf-edge-1.example.com IN A 10.100.1.21
Use Cases:
- Multi-access Edge Computing (MEC): Route sessions to geographically closest edge UPFs
- Dynamic UPF Discovery: Add/remove UPFs without reconfiguring PGW-C
- Load Balancing: Distribute load across UPFs based on location
- Network Slicing: Route different slices to different UPFs per location
UPF Health Monitoring
Automatic Health-Aware Selection: The PGW-C continuously monitors the health of all UPFs and automatically excludes unhealthy UPFs from selection.
Health Check Criteria
A UPF is considered healthy when ALL of the following conditions are met:
- PFCP Association Active: The UPF has an established PFCP association
- Heartbeat Responsiveness: Less than 3 consecutive missed heartbeats
- Process Alive: The UPF peer GenServer process is running
A UPF is considered unhealthy if ANY of the following are true:
- PFCP association is not established (
associated: false) - 3 or more consecutive heartbeat timeouts
- UPF peer process has crashed or is unresponsive
Monitoring Mechanism
For Configured UPFs (in upf_selection):
- Health tracking starts immediately at boot
- PFCP association is monitored continuously
- Heartbeats are sent every 5 seconds
missed_heartbeats_consecutivecounter tracks consecutive failures- All UPFs from rules and fallback pool are automatically registered
For DNS-Discovered UPFs (dynamic registration):
- Assumed healthy until first session attempt
- Registered automatically on first use
- Health tracking begins after registration
Selection Behavior
Active/Standby Mode (when using weight: 0):
- Filter to only healthy UPFs
- Separate into active (weight > 0) and standby (weight == 0)
- Use active UPFs if any are healthy
- Activate standby UPFs (treat as weight 1) if all active are unhealthy
- Fall back to full pool if no healthy UPFs exist
Load-Balanced Mode (all weight > 0):
- Filter to only healthy UPFs
- Perform weighted random selection among healthy UPFs
- Fall back to full pool if no healthy UPFs exist
Logging:
[debug] Using active UPF pool (2/3 healthy UPFs, 1 standby)
[info] All active UPFs down, activating standby UPFs (1 standby UPFs, treating weight 0 as 1)
[warning] No healthy UPFs in pool (3 total), using full pool as fallback
Checking UPF Health
Programmatically:
# Check if a specific UPF is healthy
iex> PGW_C.PFCP_Node.is_peer_healthy?({10, 100, 1, 21})
true
# Get detailed health information
iex> PGW_C.PFCP_Node.get_peer_health({10, 100, 1, 21})
%{
associated: true,
missed_heartbeats: 0,
healthy: true,
registered: true
}
Via Web UI:
- Navigate to
/upf_selectionin the control panel - View real-time health status for all UPFs in each pool
- Status badges: ✅ Active-UP, ⏸️ Standby-Ready, ❌ Active-DOWN, 🟡 Not Associated
- Role badges: ACTIVE (weight > 0), STANDBY (weight == 0), DYNAMIC (DNS-discovered, not in config)
- Heartbeat miss counter displayed for associated UPFs
Health Monitoring Best Practices
-
Configure UPFs in upf_selection: All UPFs in rules and fallback pools are automatically monitored
upf_selection: %{
rules: [
%{
name: "Internet Traffic",
priority: 10,
match_field: :apn,
match_regex: "^internet",
upf_pool: [
%{remote_ip_address: "10.100.1.21", remote_port: 8805, weight: 100}
]
}
],
fallback_pool: [
%{remote_ip_address: "10.100.2.21", remote_port: 8805, weight: 100}
]
}
# All UPFs automatically get:
# - 5-second heartbeats
# - Health monitoring from startup
# - Auto-generated names -
Use standby UPFs: Configure hot standbys with
weight: 0for automatic failoverupf_pool: [
%{remote_ip_address: "10.1.1.1", remote_port: 8805, weight: 100}, # Active
%{remote_ip_address: "10.1.1.2", remote_port: 8805, weight: 0} # Standby
] -
Monitor via Web UI: Regularly check UPF health status in the control panel
-
Heartbeat monitoring: The system uses a fixed threshold of 3 consecutive missed heartbeats to determine peer hea.
Dynamic UPF Registration
Feature: The PGW-C automatically registers and monitors UPFs discovered through DNS, even if they aren't in the upf_selection configuration.
How It Works
When any selection method (static rules, pools, or DNS) returns a UPF that's not already registered, the system automatically:
- Creates a PFCP Peer: Generates a default peer configuration for the unknown UPF
- Initiates PFCP Association: Attempts to establish a PFCP association with the UPF
- Registers in Peer Registry: Adds the UPF to the internal peer tracking system
- Starts Heartbeat Monitoring: Begins periodic heartbeat exchanges (10-second intervals)
- Tracks Liveness: Monitors the UPF for failures and recovery
Default Configuration for Dynamic UPFs
When a UPF is dynamically registered, it receives the following default configuration:
%{
name: "Dynamic-UPF-<IP>", # e.g., "Dynamic-UPF-10-100-1-21"
remote_ip_address: <discovered_ip>, # IP from DNS or selection
remote_port: 8805, # Standard PFCP port (overridable)
initiate_pfcp_association_setup: true, # PGW-C initiates association
heartbeat_period_ms: 10_000 # 10-second heartbeat interval
}
Note: Dynamic UPFs are registered purely for association management. They are used as targets in
upf_selectionrules, not as sources of selection logic.
Example: DNS Returns Unknown UPF
# DNS query returns: upf-edge-2.example.com -> 10.200.5.99
# This UPF is NOT in your upf_selection configuration
# Dynamic registration flow:
# 1. System detects unknown UPF 10.200.5.99
# 2. Logs: "UPF {10, 200, 5, 99} not pre-configured, attempting dynamic registration..."
# 3. Sends PFCP Association Setup Request to 10.200.5.99:8805
# 4. If UPF responds: Association established, session continues normally
# 5. If UPF doesn't respond: Session fails gracefully with clear error message
Benefits
✅ True Dynamic Discovery: DNS-based UPF selection now works without pre-configuration ✅ Automatic Scaling: Add UPFs to your network without restarting PGW-C ✅ Graceful Degradation: If association fails, sessions fail cleanly (no crashes) ✅ Backwards Compatible: Pre-configured UPFs continue to work exactly as before ✅ Full Monitoring: Dynamic UPFs get the same heartbeat monitoring as static peers
Failure Handling
If a dynamically discovered UPF fails to respond to PFCP Association Setup:
[error] PFCP Association Setup failed for dynamic UPF {10, 200, 5, 99}: :timeout
[error] Failed to dynamically register UPF {10, 200, 5, 99}: :timeout.
Session creation will fail. Consider adding this UPF to the upf_selection configuration.
The session creation will fail, but the PGW-C remains stable and continues processing other sessions.
When to Pre-Configure vs. Dynamic Registration
| Scenario | Recommendation |
|---|---|
| Production Core UPFs | Pre-configure in upf_selection (explicit configuration, monitored from startup) |
| DNS-Discovered Edge UPFs | Use dynamic registration (scales automatically with infrastructure) |
| Test/Development UPFs | Either approach works (dynamic is more convenient) |
| Mission-Critical UPFs | Pre-configure in upf_selection (ensures monitoring from startup) |
| Ephemeral/Auto-Scaled UPFs | Use dynamic registration (UPFs come and go dynamically) |
Monitoring Dynamic UPFs
Dynamic UPFs appear in logs with their auto-generated names:
[info] Creating dynamic PFCP peer configuration for Dynamic-UPF-10-200-5-99 ({10, 200, 5, 99}:8805)
[info] Dynamic UPF peer Dynamic-UPF-10-200-5-99 registered successfully with PID #PID<0.1234.0>
You can query the peer registry to see all registered peers (both static and dynamic):
# Get all registered peers
PGW_C.PFCP_Node.registered_peer_count()
# Check if a specific UPF is registered
PGW_C.PFCP_Node.get_peer({10, 200, 5, 99})
# Returns: {:ok, #PID<0.1234.0>} if registered, :error otherwise
Custom Port for Dynamic UPFs
If your UPFs use a non-standard PFCP port, you can manually trigger registration:
# Register UPF at custom port
PGW_C.PFCP_Node.register_dynamic_peer({10, 200, 5, 99}, 9999)
However, DNS-based selection and automatic registration always use port 8805 (standard PFCP port).
UPF Selection Dry-Run Mode:
Test and validate your UPF selection configuration without affecting real sessions:
config :pgw_c,
# Enable dry-run mode for testing (disabled by default)
upf_selection_dry_run: true
When dry-run mode is enabled:
- No Real Assignment: Sessions are not actually assigned to UPFs
- Detailed Logging: Selection decisions are logged with full details
- Error Return:
assign_sxb_peer/1returns{:error, :dry_run_mode} - Session Prevention: Returning an error prevents session creation
- Both Methods: Works with both static rules and DNS-based selection
Log Output Example:
[warning] ⚠️ UPF SELECTION DRY-RUN MODE ENABLED - No actual assignment will occur
[info] 🎯 DRY-RUN: Static rule matched
Method: Static Rule
Match Field: :apn
Match Regex: ~r/^internet\./
Priority: 10
Selected UPF: 10.0.1.21:8805
[warning] 🔍 DRY-RUN: Would assign UPF but skipping actual assignment
Session IMSI: 001001000000670
Session APN: internet.apn
Selection Method: static
Would Link To: 10.0.1.21:8805
⚠️ Returning error to prevent session creation
Testing via LiveView UI:
The UPF Selection LiveView page (/upf_selection) includes an interactive testing interface:
- Navigate to the UPF Selection page in the web panel
- Scroll to the "Test UPF Selection" section
- Enter test session attributes:
- IMSI (e.g.,
001001000000670) - APN (e.g.,
internet.apn) - Serving Network PLMN ID (e.g.,
001001)
- IMSI (e.g.,
- Click "Test Selection" to simulate the selection
- View detailed results showing:
- Which rule matched (or if DNS would be used)
- Selected UPF and peer name
- Match field and pattern details
- Rule priority
The LiveView testing interface simulates the selection logic without requiring dry-run mode to be enabled globally, making it safe to test in production environments without affecting real traffic.
Use Cases:
- Configuration Testing: Validate routing rules before deploying to production
- Troubleshooting: Understand why specific sessions route to specific UPFs
- Training: Demonstrate UPF selection logic to operations teams
- Development: Test new selection rules during development
Match Fields:
:imsi- International Mobile Subscriber Identity:apn- Access Point Name (APN/DNN):serving_network_plmn_id- Serving network PLMN ID:sgw_ip_address- SGW IP address:uli_tai_plmn_id- Tracking Area PLMN ID:uli_ecgi_plmn_id- E-UTRAN Cell PLMN ID
Heartbeat Tuning:
# Aggressive (detect failures quickly)
heartbeat_period_ms: 2_000 # 2 seconds
# Standard (recommended)
heartbeat_period_ms: 5_000 # 5 seconds
# Relaxed (high-latency networks)
heartbeat_period_ms: 10_000 # 10 seconds
See: PFCP Interface Documentation
UE IP Pool Configuration
Purpose
Configure IP address pools for UE allocation, organized by APN.
Configuration Block
config :pgw_c,
ue: %{
subnet_map: %{
# APN "internet" pools
"internet" => [
"100.64.0.0/20" # 4094 usable IPs
],
# APN "ims" pools
"ims" => [
"100.64.16.0/22" # 1022 usable IPs
],
# Default pool for unknown APNs
default: [
"42.42.42.0/24" # 254 usable IPs
]
}
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
subnet_map | Map | Yes | Maps APN names to subnet lists |
default | List (Subnets) | Yes | Fallback pool for unknown APNs |
Subnet Format
CIDR Notation: <network_address>/<prefix_length>
Usable IP Calculation:
| CIDR | Total IPs | Usable IPs | Example Range |
|---|---|---|---|
| /24 | 256 | 254 | 100.64.1.1 - 100.64.1.254 |
| /23 | 512 | 510 | 100.64.0.1 - 100.64.1.254 |
| /22 | 1024 | 1022 | 100.64.0.1 - 100.64.3.254 |
| /21 | 2048 | 2046 | 100.64.0.1 - 100.64.7.254 |
| /20 | 4096 | 4094 | 100.64.0.1 - 100.64.15.254 |
| /16 | 65536 | 65534 | 100.64.0.1 - 100.64.255.254 |
Examples
Simple Configuration:
ue: %{
subnet_map: %{
"internet" => ["100.64.1.0/24"],
default: ["42.42.42.0/24"]
}
}
Production Configuration:
ue: %{
subnet_map: %{
# General internet - large pool
"internet" => [
"100.64.0.0/18" # 16,382 IPs
],
# IMS (VoLTE)
"ims" => [
"100.64.64.0/22" # 1,022 IPs
],
# Enterprise APN
"enterprise.corp" => [
"10.100.0.0/16" # 65,534 IPs
],
# IoT devices
"iot.m2m" => [
"100.64.72.0/20" # 4,094 IPs
],
# Default fallback
default: [
"42.42.42.0/24" # 254 IPs
]
}
}
Load Balancing (Multiple Subnets per APN):
ue: %{
subnet_map: %{
"internet" => [
"100.64.0.0/22", # 1022 IPs
"100.64.4.0/22", # 1022 IPs
"100.64.8.0/22", # 1022 IPs
"100.64.12.0/22" # 1022 IPs
],
# Total: 4088 IPs, randomly distributed
default: ["42.42.42.0/24"]
}
}
Regex Pattern Matching:
For wildcard APN matching, use keys starting with ^ to indicate regex patterns:
ue: %{
subnet_map: %{
# Regex: APNs starting with "ims" (matches "ims", "ims.apn", "ims.something")
"^ims" => [
"100.64.10.0/24"
],
# Regex: APNs starting with "m2m." (matches "m2m.test", "m2m.prod")
"^m2m\." => [
"100.64.20.0/24"
],
# Exact match only
"enterprise.corp" => [
"10.100.0.0/16"
],
default: ["42.42.42.0/24"]
}
}
Pattern Rules:
- Keys starting with
^are regex patterns - Keys without
^are exact matches (backwards compatible) - Regex patterns are compiled at config parse time
- First matching pattern wins
- Backslashes must be escaped (
\for\)
Common Patterns:
- Starts with:
"^ims"matchesims,ims.apn,ims.foo.bar - Ends with:
"^.*\.corp$"matchesfoo.corp,bar.corp - Contains:
"^.*test.*"matchestest,foo.test.bar
IPv6 Support:
ue: %{
subnet_map: %{
# IPv4 pools
"internet" => [
"100.64.0.0/20"
],
# IPv6 pools (prefix delegation)
"internet.ipv6" => [
"2001:db8:1::/48"
],
default: [
"42.42.42.0/24"
]
}
}
Recommended IP Ranges
RFC 6598 (Carrier-Grade NAT):
- Range:
100.64.0.0/10 - Size: ~4 million IPs
- Purpose: Designed for service provider NAT
Private IP Ranges (RFC 1918):
10.0.0.0/8- 16 million IPs172.16.0.0/12- 1 million IPs192.168.0.0/16- 65,534 IPs
See: UE IP Pool Allocation Documentation
PCO Configuration
Purpose
Configure Protocol Configuration Options (PCO) sent to UE.
Configuration Block
config :pgw_c,
pco: %{
# DNS servers
primary_dns_server_address: "8.8.8.8",
secondary_dns_server_address: "8.8.4.4",
# IPv6 DNS servers (optional, container 0x0003)
primary_dns_server_ipv6_address: nil,
secondary_dns_server_ipv6_address: nil,
# NBNS servers (optional, for Windows devices)
primary_nbns_server_address: nil,
secondary_nbns_server_address: nil,
# P-CSCF addresses for IMS
p_cscf_ipv4_address_list: ["10.0.0.50", "10.0.0.51"], # container 0x000C
p_cscf_ipv6_address_list: [], # container 0x0001
# IPv4 MTU size
ipv4_link_mtu_size: 1400
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
primary_dns_server_address | String (IPv4) | Required | Primary DNS server |
secondary_dns_server_address | String (IPv4) | Optional | Secondary DNS server |
primary_dns_server_ipv6_address | String (IPv6) | nil | Primary IPv6 DNS server (container 0x0003) |
secondary_dns_server_ipv6_address | String (IPv6) | nil | Secondary IPv6 DNS server (container 0x0003) |
primary_nbns_server_address | String (IPv4) | nil | Primary NBNS (NetBIOS) server |
secondary_nbns_server_address | String (IPv4) | nil | Secondary NBNS server |
p_cscf_ipv4_address_list | List (IPv4) | [] | IPv4 P-CSCF addresses for IMS (container 0x000C) |
p_cscf_ipv6_address_list | List (IPv6) | [] | IPv6 P-CSCF addresses for IMS (container 0x0001) |
ipv4_link_mtu_size | Integer | 1400 | Maximum Transmission Unit size |
Examples
Public DNS (Google):
pco: %{
primary_dns_server_address: "8.8.8.8",
secondary_dns_server_address: "8.8.4.4",
ipv4_link_mtu_size: 1400
}
Private DNS:
pco: %{
primary_dns_server_address: "10.0.0.10",
secondary_dns_server_address: "10.0.0.11",
ipv4_link_mtu_size: 1400
}
IMS Configuration:
pco: %{
primary_dns_server_address: "10.0.0.10",
secondary_dns_server_address: "10.0.0.11",
# P-CSCF for IMS/VoLTE
p_cscf_ipv4_address_list: [
"10.0.0.50", # Primary P-CSCF
"10.0.0.51" # Secondary P-CSCF
],
ipv4_link_mtu_size: 1400
}
NBNS (Windows Compatibility):
pco: %{
primary_dns_server_address: "10.0.0.10",
secondary_dns_server_address: "10.0.0.11",
primary_nbns_server_address: "10.0.0.20",
secondary_nbns_server_address: "10.0.0.21",
ipv4_link_mtu_size: 1400
}
MTU Tuning:
# Standard Ethernet
ipv4_link_mtu_size: 1500
# Reduced for tunneling overhead
ipv4_link_mtu_size: 1400
# Jumbo frames (if supported)
ipv4_link_mtu_size: 9000
See: PCO Configuration Documentation
Web UI Configuration
Purpose
Configure the Control Panel web interface and REST API endpoints for managing and monitoring OmniPGW.
Note: Web UI configuration is only active in non-test environments. The configuration is automatically skipped when
config_env()is:test,:test_mock, or:test_impl.
Control Panel Configuration
config :control_panel,
# Define page navigation order
page_order: [
"/application",
"/configuration",
"/topology",
"/ue_search",
"/pgw_sessions",
"/session_history",
"/ip_pools",
"/diameter",
"/pfcp_sessions",
"/upf_status",
"/upf_selection",
"/pcscf_monitor",
"/gy_simulator",
"/logs"
]
# HTTPS Endpoint for Control Panel
config :control_panel, ControlPanelWeb.Endpoint,
url: [host: "0.0.0.0", path: "/"],
https: [
port: 8086,
keyfile: "priv/cert/omnitouch.pem",
certfile: "priv/cert/omnitouch.crt"
],
render_errors: [
formats: [html: ControlPanelWeb.ErrorHTML, json: ControlPanelWeb.ErrorJSON],
layout: false
]
Control Panel Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page_order | List (Strings) | Yes | Navigation menu page order (list of URL paths) |
url.host | String (IP) | Yes | Host address for URL generation |
url.path | String | Yes | Base path for all routes (usually "/") |
https.port | Integer | Yes | HTTPS port for web interface |
https.keyfile | String (Path) | Yes | Path to SSL/TLS private key file |
https.certfile | String (Path) | Yes | Path to SSL/TLS certificate file |
render_errors.formats | Keyword List | Yes | Error page rendering modules |
render_errors.layout | Boolean/Module | Yes | Error page layout (false = no layout) |
REST API Configuration
config :api_ex,
api: %{
# Network settings
port: 8443,
listen_ip: "0.0.0.0",
# API metadata
product_name: "PGW-C",
title: "API - PGW-C",
hostname: "localhost",
# TLS settings
enable_tls: true,
tls_cert_path: "priv/cert/omnitouch.crt",
tls_key_path: "priv/cert/omnitouch.pem",
# Route definitions
routes: [
%{
path: "/status",
module: ApiEx.Api.StatusController,
actions: [:index]
}
]
}
API Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
port | Integer | 8443 | HTTPS port for REST API |
listen_ip | String (IP) | "0.0.0.0" | API listen address (0.0.0.0 = all interfaces) |
product_name | String | "PGW-C" | Product name (for API metadata) |
title | String | "API - PGW-C" | API documentation title |
hostname | String | "localhost" | API server hostname |
enable_tls | Boolean | true | Enable HTTPS for API |
tls_cert_path | String (Path) | Required | Path to SSL/TLS certificate file |
tls_key_path | String (Path) | Required | Path to SSL/TLS private key file |
routes | List (Maps) | [] | API route definitions |
Route Definition Format
Each route in the routes list is a map with:
| Field | Type | Description | Example |
|---|---|---|---|
path | String | URL path for the route | "/status" |
module | Module | Controller module handling requests | ApiEx.Api.StatusController |
actions | List (Atoms) | Allowed controller actions | [:index, :show] |
Examples
Default Configuration (Development):
# Control Panel on localhost:8086
config :control_panel, ControlPanelWeb.Endpoint,
url: [host: "localhost", path: "/"],
https: [
port: 8086,
keyfile: "priv/cert/dev-key.pem",
certfile: "priv/cert/dev-cert.crt"
]
# API on localhost:8443
config :api_ex,
api: %{
port: 8443,
listen_ip: "127.0.0.1", # localhost only
hostname: "localhost",
enable_tls: true,
tls_cert_path: "priv/cert/dev-cert.crt",
tls_key_path: "priv/cert/dev-key.pem",
routes: [
%{path: "/status", module: ApiEx.Api.StatusController, actions: [:index]}
]
}
Production Configuration:
# Control Panel on all interfaces
config :control_panel, ControlPanelWeb.Endpoint,
url: [host: "pgw-c.example.com", path: "/"],
https: [
port: 443, # Standard HTTPS port
keyfile: "/etc/ssl/private/pgw-c.key",
certfile: "/etc/ssl/certs/pgw-c.crt"
]
# API on management interface
config :api_ex,
api: %{
port: 8443,
listen_ip: "10.0.0.20", # Management network
product_name: "OmniPGW-C",
hostname: "pgw-c-api.example.com",
enable_tls: true,
tls_cert_path: "/etc/ssl/certs/pgw-c-api.crt",
tls_key_path: "/etc/ssl/private/pgw-c-api.key",
routes: [
%{path: "/status", module: ApiEx.Api.StatusController, actions: [:index]},
%{path: "/sessions", module: ApiEx.Api.SessionController, actions: [:index, :show]}
]
}
Custom Page Order:
# Prioritize operational pages
config :control_panel,
page_order: [
"/ue_search", # Most frequently used
"/pgw_sessions",
"/upf_status",
"/logs",
"/diameter",
"/topology",
"/configuration",
"/ip_pools",
"/pfcp_sessions",
"/upf_selection",
"/pcscf_monitor",
"/gy_simulator",
"/session_history",
"/application"
]
Accessing Web UI
Control Panel:
# Default access
https://localhost:8086
# Production
https://pgw-c.example.com
REST API:
# Status endpoint
curl -k https://localhost:8443/status
# With proper certificate
curl https://pgw-c-api.example.com:8443/status
TLS Certificate Setup
Generate Self-Signed Certificate (Development):
# Generate private key and certificate
openssl req -x509 -newkey rsa:4096 -keyout priv/cert/omnitouch.pem \
-out priv/cert/omnitouch.crt -days 365 -nodes \
-subj "/CN=localhost"
Production Certificate:
For production, use certificates from a trusted Certificate Authority (CA):
- Let's Encrypt (free, automated)
- Commercial CA (DigiCert, GlobalSign, etc.)
- Internal CA for enterprise deployments
Security Considerations
- Always use TLS in production - Set
enable_tls: true - Restrict listen_ip - Use specific IP addresses in production (not 0.0.0.0)
- Use valid certificates - Avoid self-signed certs in production
- Firewall protection - Restrict access to management ports (8086, 8443)
- Strong key files - Use 4096-bit RSA or equivalent
- Regular updates - Rotate certificates before expiration
Troubleshooting
Issue: Cannot access Control Panel
# Check if port is listening
netstat -tulpn | grep 8086
# Check certificate files exist
ls -la priv/cert/
# Check logs for startup errors
tail -f /var/log/pgw_c/application.log
Issue: SSL/TLS errors
- Verify certificate and key paths are correct
- Ensure certificate matches the hostname
- Check certificate expiration:
openssl x509 -in cert.crt -text -noout - Verify key file permissions (should be readable by PGW-C process)
See: Monitoring Guide for Control Panel usage details
Complete Example
Production-Ready Configuration
# config/runtime.exs
import Config
# Logger configuration
config :logger, level: :info
config :pgw_c,
# Metrics (Prometheus)
metrics: %{
enabled: true,
ip_address: "10.0.0.20", # Management network
port: 9090,
registry_poll_period_ms: 5_000
},
# Diameter/Gx (PCRF interface)
diameter: %{
listen_ip: "0.0.0.0",
host: "omnipgw.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
peer_list: [
%{
host: "pcrf-primary.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
ip: "10.0.1.30",
initiate_connection: true
},
%{
host: "pcrf-backup.epc.mnc001.mcc001.3gppnetwork.org",
realm: "epc.mnc001.mcc001.3gppnetwork.org",
ip: "10.0.2.30",
initiate_connection: true
}
]
},
# S5/S8 (SGW-C interface)
s5s8: %{
local_ipv4_address: "10.0.0.20"
},
# Sxb/PFCP (PGW-U interface)
sxb: %{
local_ip_address: "10.0.0.20"
},
# UPF Selection
upf_selection: %{
rules: [
%{
name: "Default Internet",
priority: 1,
match_field: :apn,
match_regex: ~r/^internet/,
upf_pool: [
%{remote_ip_address: "10.0.0.21", remote_port: 8805, weight: 100},
%{remote_ip_address: "10.0.0.22", remote_port: 8805, weight: 0} # Standby
]
}
],
fallback_pool: [
%{remote_ip_address: "10.0.0.21", remote_port: 8805, weight: 100}
]
},
# UE IP Pools
ue: %{
subnet_map: %{
"internet" => [
"100.64.0.0/18" # 16,382 IPs
],
"ims" => [
"100.64.64.0/22" # 1,022 IPs
],
"enterprise.corp" => [
"10.100.0.0/16" # 65,534 IPs
],
default: [
"100.64.127.0/24" # 254 IPs
]
}
},
# Protocol Configuration Options
pco: %{
primary_dns_server_address: "8.8.8.8",
secondary_dns_server_address: "8.8.4.4",
p_cscf_ipv4_address_list: ["10.0.0.50", "10.0.0.51"],
ipv4_link_mtu_size: 1400
}
Configuration Validation
Startup Validation
OmniPGW validates configuration at startup. Check logs:
[info] Loading configuration from runtime.exs
[info] Validating configuration...
[info] Configuration valid
[info] Starting OmniPGW...
Common Validation Errors
Invalid IP Address:
[error] Invalid IP address in s5s8.local_ipv4_address: "10.0.0"
Missing Required Field:
[error] Missing required configuration: sxb.local_ip_address
Invalid CIDR:
[error] Invalid subnet in ue.subnet_map: "100.64.1.0/33"
Invalid Diameter Identity:
[error] Diameter host must be FQDN, not IP: "10.0.0.20"
Configuration Testing
Test configuration without starting:
# Validate syntax
mix compile
# Check configuration loading
iex -S mix
iex> Application.get_env(:pgw_c, :metrics)
Environment-Specific Configuration
Development
# config/dev.exs
import Config
config :logger, level: :debug
config :pgw_c,
metrics: %{
enabled: true,
ip_address: "127.0.0.1",
port: 42069,
registry_poll_period_ms: 10_000
},
diameter: %{
listen_ip: "0.0.0.0",
host: "omnipgw-dev.local",
realm: "local",
peer_list: [
%{
host: "pcrf-dev.local",
realm: "local",
ip: "127.0.0.1",
initiate_connection: true
}
]
},
s5s8: %{
local_ipv4_address: "127.0.0.10"
},
sxb: %{
local_ip_address: "127.0.0.20"
},
upf_selection: %{
fallback_pool: [
%{remote_ip_address: "127.0.0.21", remote_port: 8805, weight: 100}
]
},
ue: %{
subnet_map: %{
"internet" => ["100.64.1.0/24"],
default: ["42.42.42.0/24"]
}
},
pco: %{
primary_dns_server_address: "8.8.8.8",
secondary_dns_server_address: "8.8.4.4",
ipv4_link_mtu_size: 1400
}
Using Environment Variables
# config/runtime.exs
import Config
config :pgw_c,
metrics: %{
enabled: System.get_env("METRICS_ENABLED", "true") == "true",
ip_address: System.get_env("METRICS_IP", "0.0.0.0"),
port: String.to_integer(System.get_env("METRICS_PORT", "9090")),
registry_poll_period_ms: 10_000
},
diameter: %{
listen_ip: System.get_env("DIAMETER_LISTEN_IP", "0.0.0.0"),
host: System.get_env("DIAMETER_HOST") || raise("DIAMETER_HOST required"),
realm: System.get_env("DIAMETER_REALM") || raise("DIAMETER_REALM required"),
peer_list: [
%{
host: System.get_env("PCRF_HOST") || raise("PCRF_HOST required"),
realm: System.get_env("PCRF_REALM") || System.get_env("DIAMETER_REALM"),
ip: System.get_env("PCRF_IP") || raise("PCRF_IP required"),
initiate_connection: true
}
]
}
# ... rest of config
Usage:
export DIAMETER_HOST="omnipgw.epc.mnc001.mcc001.3gppnetwork.org"
export DIAMETER_REALM="epc.mnc001.mcc001.3gppnetwork.org"
export PCRF_HOST="pcrf.epc.mnc001.mcc001.3gppnetwork.org"
export PCRF_IP="10.0.0.30"
mix run --no-halt
Related Documentation
Interface Configuration
- PFCP Interface - Sxb/PFCP configuration, UPF communication, session establishment
- Diameter Gx Interface - PCRF policy control, PCC rules, QoS management
- Diameter Gy Interface - OCS online charging, quota management, credit control
- S5/S8 Interface - GTP-C v2 configuration, SGW-C communication (4G/LTE)
- Gn/Gp Interface - GTP-C v1 configuration, SGSN communication (2G/3G GGSN)
Network Configuration
- UE IP Allocation - IP pool management, APN-based allocation, DHCP
- PCO Configuration - Protocol Configuration Options, DNS, P-CSCF, MTU
- P-CSCF Monitoring - P-CSCF discovery monitoring, IMS health tracking
Operational Guides
- Session Management - PDN session lifecycle, bearer management
- Monitoring Guide - Prometheus metrics, alerts, dashboards
- Data CDR Format - Offline charging records, billing integration
OmniPGW Configuration Guide - by Omnitouch Network Services