Skip to main content

OmniPGW Configuration Guide

Complete Reference for runtime.exs Configuration

by Omnitouch Network Services


Table of Contents

  1. Overview
  2. Configuration File Structure
  3. Metrics Configuration
  4. Diameter/Gx Configuration
  5. S5/S8 Configuration
  6. Gn/Gp Configuration (GGSN)
  7. Sxb/PFCP Configuration
  8. UE IP Pool Configuration
  9. PCO Configuration
  10. Web UI Configuration
  11. Complete Example
  12. 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

ParameterTypeDefaultDescription
enabledBooleantrueEnable metrics exporter
ip_addressString (IP)"0.0.0.0"Bind address (0.0.0.0 = all interfaces)
portInteger9090HTTP port for /metrics endpoint
registry_poll_period_msInteger10_000Polling 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

ParameterTypeRequiredDescription
listen_ipString (IP)YesDiameter listen address
hostString (FQDN)YesOmniPGW's Origin-Host (must be FQDN)
realmString (Domain)YesOmniPGW's Origin-Realm
peer_listListYesPCRF peer configurations

Peer Configuration:

ParameterTypeRequiredDescription
hostString (FQDN)YesPCRF Diameter identity
realmString (Domain)YesPCRF realm
ipString (IP)YesPCRF IP address
initiate_connectionBooleanYesWhether 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

ParameterTypeDefaultDescription
local_ipv4_addressString (IPv4)RequiredS5/S8 interface IPv4 address
local_ipv6_addressString (IPv6)nilS5/S8 interface IPv6 address (optional)
local_portInteger2123UDP port for GTP-C (standard port 2123)
request_timeout_msInteger500Timeout per retry attempt in milliseconds
request_attemptsInteger3Number 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 LatencyRecommended TimeoutTotal Wait Time
Low latency (<50ms)200-300ms600-900ms
Normal (50-150ms)500ms (default)1.5s
High latency (>150ms)1000-2000ms3-6s
Satellite/unstable2000-3000ms6-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

ParameterTypeDefaultDescription
local_ipv4_addressString (IPv4)RequiredGn interface IPv4 address (GGSN address)
local_ipv6_addressString (IPv6)nilGn interface IPv6 address (optional, rarely used)
local_portInteger2123UDP port for GTP-C v1 (shared with S5/S8)

DNS Parameters

ParameterTypeDefaultDescription
primary_ipv4Tuple{8, 8, 8, 8}Primary DNS server for PCO response
secondary_ipv4Tuple{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

ParameterTypeDefaultDescription
local_ip_addressString (IP)RequiredPFCP listen address
local_portInteger8805PFCP UDP port

Important:

  • All UPF peers are automatically registered from the upf_selection configuration (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_selection section. 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:

  1. Static Rules - Pattern-based routing with load balancing pools
  2. DNS Settings - Location-based dynamic UPF discovery
  3. Fallback Pool - Default pool when no rules match and DNS fails

Selection Priority Order

  1. Static Rules (Highest Priority) - Pattern-based routing with load balancing pools
  2. DNS-Based Selection (Lower Priority) - Location-based dynamic UPF discovery
  3. 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 FieldDescriptionExample Pattern
:imsiInternational Mobile Subscriber Identity^001001.* (test network)
:apnAccess Point Name / DNN^internet\. or ^ims\.
:serving_network_plmn_idServing network identifier^001001$
:sgw_ip_addressSGW IP address^10\.100\..*
:uli_tai_plmn_idTracking Area PLMN ID^313.*
:uli_ecgi_plmn_idE-UTRAN Cell PLMN ID^313.*

Selection Methods Comparison

MethodWhen to UseProsCons
UPF PoolsProduction deploymentsLoad balancing, HA, flexible weightsRequires multiple UPFs
APN-BasedService differentiationRoute IMS/Internet separatelyStatic configuration
IMSI-BasedRoaming scenariosGeographic routingRegex complexity
DNS-BasedMEC/Edge computingDynamic, location-awareRequires DNS infrastructure
Fallback PoolSafety netAlways have a UPFMay not be optimal
Dry-Run ModeTesting configsSafe testingNo 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:

  1. 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
  2. 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
  3. 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
  4. 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_selection are automatically registered at startup
  • Centralized Configuration: All UPF selection and peer configuration in one section
  • Required Pools: All rules use upf_pool format (even for single UPF)
  • Structured Fallback: Dedicated fallback_pool with 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_list field (no longer needed)
  • Removed: selection_list embedded in peer configurations
  • All UPF definitions now go in upf_selection rules and fallback pool

How UPF Pools Work:

  1. 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)
  2. Active/Standby Support: Use weight: 0 for 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: 1 when activated
  3. 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
  4. 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

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: 0 for 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:

  1. Partial Overrides: Only specify the PCO fields you want to override
  2. Default Fallback: Unspecified fields use values from the main pco config
  3. Rule-Specific: Each rule can have different PCO overrides
  4. Priority Merging: Rule PCO takes priority over default PCO

PCO Population Hierarchy

Priority Order for Each PCO Field:

  1. Rule PCO Override (Highest Priority)
  2. P-CSCF DNS Discovery (for P-CSCF addresses only)
  3. 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 IP
  • secondary_nbns_server_address - Secondary WINS server IP
  • p_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 discovery
  • p_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:

  1. PGW-C performs DNS lookup for the specified FQDN during session establishment
  2. DNS server returns list of P-CSCF IP addresses
  3. Discovered P-CSCF addresses are sent to UE via PCO
  4. If DNS lookup fails, falls back to p_cscf_ipv4_address_list from PCO override (if specified) or global PCO config
  5. 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:

  1. Priority: DNS selection is used only when NO static rules match (lower priority)
  2. Query Generation: Builds DNS NAPTR queries based on UE location:
    • ECGI query: eci-<hex>.ecgi.epc.mnc&lt;MNC>.mcc&lt;MCC>.epc.3gppnetwork.org
    • TAI query: tac-lb<hex>.tac-hb<hex>.tac.epc.mnc&lt;MNC>.mcc&lt;MCC>.epc.3gppnetwork.org
    • RAI, SAI, CGI queries follow similar 3GPP TS 23.003 format
  3. Fallback Hierarchy: Tries each location type in priority order until a match is found
  4. Peer Matching: DNS results are filtered against configured peer list
  5. 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:

  1. PFCP Association Active: The UPF has an established PFCP association
  2. Heartbeat Responsiveness: Less than 3 consecutive missed heartbeats
  3. 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_consecutive counter 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):

  1. Filter to only healthy UPFs
  2. Separate into active (weight > 0) and standby (weight == 0)
  3. Use active UPFs if any are healthy
  4. Activate standby UPFs (treat as weight 1) if all active are unhealthy
  5. Fall back to full pool if no healthy UPFs exist

Load-Balanced Mode (all weight > 0):

  1. Filter to only healthy UPFs
  2. Perform weighted random selection among healthy UPFs
  3. 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_selection in 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

  1. 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
  2. Use standby UPFs: Configure hot standbys with weight: 0 for automatic failover

    upf_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
    ]
  3. Monitor via Web UI: Regularly check UPF health status in the control panel

  4. 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:

  1. Creates a PFCP Peer: Generates a default peer configuration for the unknown UPF
  2. Initiates PFCP Association: Attempts to establish a PFCP association with the UPF
  3. Registers in Peer Registry: Adds the UPF to the internal peer tracking system
  4. Starts Heartbeat Monitoring: Begins periodic heartbeat exchanges (10-second intervals)
  5. 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_selection rules, 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

ScenarioRecommendation
Production Core UPFsPre-configure in upf_selection (explicit configuration, monitored from startup)
DNS-Discovered Edge UPFsUse dynamic registration (scales automatically with infrastructure)
Test/Development UPFsEither approach works (dynamic is more convenient)
Mission-Critical UPFsPre-configure in upf_selection (ensures monitoring from startup)
Ephemeral/Auto-Scaled UPFsUse 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:

  1. No Real Assignment: Sessions are not actually assigned to UPFs
  2. Detailed Logging: Selection decisions are logged with full details
  3. Error Return: assign_sxb_peer/1 returns {:error, :dry_run_mode}
  4. Session Prevention: Returning an error prevents session creation
  5. 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:

  1. Navigate to the UPF Selection page in the web panel
  2. Scroll to the "Test UPF Selection" section
  3. Enter test session attributes:
    • IMSI (e.g., 001001000000670)
    • APN (e.g., internet.apn)
    • Serving Network PLMN ID (e.g., 001001)
  4. Click "Test Selection" to simulate the selection
  5. 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

ParameterTypeRequiredDescription
subnet_mapMapYesMaps APN names to subnet lists
defaultList (Subnets)YesFallback pool for unknown APNs

Subnet Format

CIDR Notation: <network_address>/<prefix_length>

Usable IP Calculation:

CIDRTotal IPsUsable IPsExample Range
/24256254100.64.1.1 - 100.64.1.254
/23512510100.64.0.1 - 100.64.1.254
/2210241022100.64.0.1 - 100.64.3.254
/2120482046100.64.0.1 - 100.64.7.254
/2040964094100.64.0.1 - 100.64.15.254
/166553665534100.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" matches ims, ims.apn, ims.foo.bar
  • Ends with: "^.*\.corp$" matches foo.corp, bar.corp
  • Contains: "^.*test.*" matches test, 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"
]
}
}

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 IPs
  • 172.16.0.0/12 - 1 million IPs
  • 192.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

ParameterTypeDefaultDescription
primary_dns_server_addressString (IPv4)RequiredPrimary DNS server
secondary_dns_server_addressString (IPv4)OptionalSecondary DNS server
primary_dns_server_ipv6_addressString (IPv6)nilPrimary IPv6 DNS server (container 0x0003)
secondary_dns_server_ipv6_addressString (IPv6)nilSecondary IPv6 DNS server (container 0x0003)
primary_nbns_server_addressString (IPv4)nilPrimary NBNS (NetBIOS) server
secondary_nbns_server_addressString (IPv4)nilSecondary NBNS server
p_cscf_ipv4_address_listList (IPv4)[]IPv4 P-CSCF addresses for IMS (container 0x000C)
p_cscf_ipv6_address_listList (IPv6)[]IPv6 P-CSCF addresses for IMS (container 0x0001)
ipv4_link_mtu_sizeInteger1400Maximum 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

ParameterTypeRequiredDescription
page_orderList (Strings)YesNavigation menu page order (list of URL paths)
url.hostString (IP)YesHost address for URL generation
url.pathStringYesBase path for all routes (usually "/")
https.portIntegerYesHTTPS port for web interface
https.keyfileString (Path)YesPath to SSL/TLS private key file
https.certfileString (Path)YesPath to SSL/TLS certificate file
render_errors.formatsKeyword ListYesError page rendering modules
render_errors.layoutBoolean/ModuleYesError 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

ParameterTypeDefaultDescription
portInteger8443HTTPS port for REST API
listen_ipString (IP)"0.0.0.0"API listen address (0.0.0.0 = all interfaces)
product_nameString"PGW-C"Product name (for API metadata)
titleString"API - PGW-C"API documentation title
hostnameString"localhost"API server hostname
enable_tlsBooleantrueEnable HTTPS for API
tls_cert_pathString (Path)RequiredPath to SSL/TLS certificate file
tls_key_pathString (Path)RequiredPath to SSL/TLS private key file
routesList (Maps)[]API route definitions

Route Definition Format

Each route in the routes list is a map with:

FieldTypeDescriptionExample
pathStringURL path for the route"/status"
moduleModuleController module handling requestsApiEx.Api.StatusController
actionsList (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

  1. Always use TLS in production - Set enable_tls: true
  2. Restrict listen_ip - Use specific IP addresses in production (not 0.0.0.0)
  3. Use valid certificates - Avoid self-signed certs in production
  4. Firewall protection - Restrict access to management ports (8086, 8443)
  5. Strong key files - Use 4096-bit RSA or equivalent
  6. 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

Interface Configuration

Network Configuration

Operational Guides


Back to Operations Guide


OmniPGW Configuration Guide - by Omnitouch Network Services