04_lab_4g_epc_docker
Part 4: Docker Lab - 4G LTE EPC with Open5GS
Learning Objective: Build a working 4G LTE Evolved Packet Core (EPC) using Open5GS on Docker, and understand the 4G-specific network functions, interfaces, and protocols.
This lab deploys a genuine 4G EPC using 4G-specific network functions: MME, HSS, PCRF, SGW-C, SGW-U, PGW-C (SMF), and PGW-U (UPF). It uses Diameter signaling (S6a, Gx), GTP-C/GTP-U tunneling, and S1AP — the protocols defined by 3GPP for LTE. This is architecturally distinct from the 5G SA lab in Part 6.
RAN Limitation: UERANSIM only supports 5G NR (gNB + 5G UE). It cannot simulate a 4G eNB. This lab focuses on the EPC core network itself. To connect an actual eNB and UE, you need srsRAN 4G with ZMQ (software radio simulation). Instructions for connecting srsRAN are provided at the end of this lab.
Table of Contents
- Prerequisites
- 4G EPC vs 5G SA — What's Different?
- Lab Architecture
- Docker Network Design
- Step 1: Project Setup
- Step 2: Docker Compose Configuration
- Step 3: Open5GS EPC Configurations
- Step 4: freeDiameter Configuration
- Step 5: Launch the EPC
- Step 6: Register a Subscriber
- Step 7: Verify EPC Health
- Step 8: Packet Capture and Protocol Analysis
- Connecting srsRAN 4G (Reference)
- Troubleshooting
- Exercises
- Summary
Prerequisites
Software
docker --version
# Docker version 24.0.0 or higher
docker compose version
# Docker Compose version v2.20.0 or higher
Disk Space
- ~3GB for Docker images
- ~1GB for logs and data
Docker Image Used
| Component | Image | Description |
|---|---|---|
| MongoDB | mongo:6.0 |
Subscriber database |
| Open5GS (all NFs) | gradiant/open5gs:2.7.6 |
Monolithic image containing all Open5GS daemons (4G + 5G) |
| Open5GS WebUI | gradiant/open5gs-webui:2.7.6 |
Web interface for subscriber management |
Unlike Part 6 (5G SA), which uses per-NF images from borieher/open5gs-<nf>, the 4G EPC lab uses the monolithic gradiant/open5gs image. This single image contains all Open5GS binaries — including 4G-specific daemons (open5gs-mmed, open5gs-hssd, open5gs-pcrfd, open5gs-sgwcd, open5gs-sgwud) that are not available as separate container images. Each container runs a different daemon via the command: directive.
4G EPC vs 5G SA — What's Different?
Before diving in, understand why this lab looks different from the 5G SA lab in Part 6:
| Aspect | 4G EPC (This Lab) | 5G SA (Part 6) |
|---|---|---|
| Control Plane Hub | MME (S1AP, port 36412) | AMF (NGAP, port 38412) |
| Subscriber DB | HSS (Diameter S6a) | UDM/UDR (SBI HTTP/2) |
| Policy Engine | PCRF (Diameter Gx) | PCF (SBI HTTP/2) |
| Serving Gateway | SGW-C + SGW-U (GTP-C, PFCP) | Not applicable (no SGW in 5G) |
| PDN Gateway | PGW-C/SMF + PGW-U/UPF (GTP-C, PFCP) | SMF + UPF (SBI, PFCP) |
| Service Discovery | None (point-to-point config) | NRF (SBI-based registry) |
| Inter-NF Signaling | Diameter + GTP-C | SBI (HTTP/2 REST) |
| Auth Protocol | EPS-AKA (KASME) | 5G-AKA (KAUSF, SUCI) |
| RAN Connection | eNB via S1AP (SCTP) | gNB via NGAP (SCTP) |
Lab Architecture
graph TB
subgraph "Docker Host"
subgraph "Control Plane Network (172.22.0.0/24)"
MongoDB[(MongoDB
172.22.0.2)]
WebUI[WebUI
172.22.0.3
:9999]
MME[MME
172.22.0.10
S1AP :36412
S6a Diameter
S11 GTP-C]
HSS[HSS
172.22.0.11
S6a Diameter]
PCRF[PCRF
172.22.0.12
Gx Diameter]
SGWC[SGW-C
172.22.0.13
S11 GTP-C
S5-C GTP-C
Sxa PFCP]
SMF[SMF / PGW-C
172.22.0.14
S5-C GTP-C
Gx Diameter
Sxb PFCP]
end
subgraph "User Plane Network (172.23.0.0/24)"
SGWU[SGW-U
172.23.0.10
S1-U GTP-U
S5-U GTP-U]
UPF[UPF / PGW-U
172.23.0.11
S5-U GTP-U
SGi to Internet]
end
end
eNB[eNB
srsRAN 4G
External]
Internet[Internet
via ogstun]
eNB <-->|S1-MME
S1AP / SCTP
port 36412| MME
eNB <-->|S1-U
GTP-U| SGWU
MME <-->|S6a
Diameter| HSS
MME <-->|S11
GTP-C| SGWC
SGWC <-->|Sxa
PFCP| SGWU
SGWC <-->|S5-C
GTP-C| SMF
SMF <-->|Gx
Diameter| PCRF
SMF <-->|Sxb
PFCP| UPF
SGWU <-->|S5-U
GTP-U| UPF
UPF <-->|SGi / N6
ogstun| Internet
HSS <-->|MongoDB| MongoDB
WebUI <-->|HTTP| MongoDB
style MME fill:#ffe1e1
style HSS fill:#f0e1ff
style PCRF fill:#e1ffe1
style SGWC fill:#ffe1f0
style SGWU fill:#ffe1f0
style SMF fill:#fff0e1
style UPF fill:#fff0e1
style MongoDB fill:#f9f
style WebUI fill:#9ffDocker Network Design
| Network | Subnet | Purpose |
|---|---|---|
| open5gs_cp | 172.22.0.0/24 | Control plane: Diameter (S6a, Gx), GTP-C (S11, S5-C), PFCP (Sxa, Sxb), MongoDB |
| open5gs_up | 172.23.0.0/24 | User plane: GTP-U (S1-U, S5-U), SGi to Internet |
The control plane and user plane are on separate Docker networks, mirroring CUPS (Control/User Plane Separation) as described in Part 1. The MME, SGW-C, and SMF are on the control plane network. The SGW-U and UPF are on the user plane network. SGW-C and SMF bridge both networks via PFCP to program their respective user plane counterparts.
Step 1: Project Setup
mkdir -p ~/open5gs_4g_lab/{config,freeDiameter,log}
cd ~/open5gs_4g_lab
Step 2: Docker Compose Configuration
Create docker-compose.yml:
version: '3.8'
services:
# === Infrastructure ===
mongodb:
image: mongo:6.0
container_name: open5gs_mongodb
networks:
open5gs_cp:
ipv4_address: 172.22.0.2
volumes:
- mongodb_data:/data/db
restart: unless-stopped
webui:
image: gradiant/open5gs-webui:2.7.6
container_name: open5gs_webui
networks:
open5gs_cp:
ipv4_address: 172.22.0.3
ports:
- "9999:9999"
environment:
- DB_URI=mongodb://172.22.0.2/open5gs
depends_on:
- mongodb
restart: unless-stopped
# === 4G EPC (Evolved Packet Core) - Control Plane ===
hss:
image: gradiant/open5gs:2.7.6
container_name: open5gs_hss
command: open5gs-hssd
volumes:
- ./config/hss.yaml:/opt/open5gs/etc/open5gs/hss.yaml
- ./freeDiameter/hss.conf:/opt/open5gs/etc/freeDiameter/hss.conf
networks:
open5gs_cp:
ipv4_address: 172.22.0.11
environment:
- DB_URI=mongodb://172.22.0.2/open5gs
depends_on:
- mongodb
restart: unless-stopped
pcrf:
image: gradiant/open5gs:2.7.6
container_name: open5gs_pcrf
command: open5gs-pcrfd
volumes:
- ./config/pcrf.yaml:/opt/open5gs/etc/open5gs/pcrf.yaml
- ./freeDiameter/pcrf.conf:/opt/open5gs/etc/freeDiameter/pcrf.conf
networks:
open5gs_cp:
ipv4_address: 172.22.0.12
environment:
- DB_URI=mongodb://172.22.0.2/open5gs
depends_on:
- mongodb
restart: unless-stopped
mme:
image: gradiant/open5gs:2.7.6
container_name: open5gs_mme
command: open5gs-mmed
volumes:
- ./config/mme.yaml:/opt/open5gs/etc/open5gs/mme.yaml
- ./freeDiameter/mme.conf:/opt/open5gs/etc/freeDiameter/mme.conf
networks:
open5gs_cp:
ipv4_address: 172.22.0.10
ports:
- "36412:36412/sctp"
depends_on:
- hss
cap_add:
- NET_ADMIN
restart: unless-stopped
sgwc:
image: gradiant/open5gs:2.7.6
container_name: open5gs_sgwc
command: open5gs-sgwcd
volumes:
- ./config/sgwc.yaml:/opt/open5gs/etc/open5gs/sgwc.yaml
networks:
open5gs_cp:
ipv4_address: 172.22.0.13
depends_on:
- mme
restart: unless-stopped
smf:
image: gradiant/open5gs:2.7.6
container_name: open5gs_smf
command: open5gs-smfd
volumes:
- ./config/smf.yaml:/opt/open5gs/etc/open5gs/smf.yaml
- ./freeDiameter/smf.conf:/opt/open5gs/etc/freeDiameter/smf.conf
networks:
open5gs_cp:
ipv4_address: 172.22.0.14
open5gs_up:
ipv4_address: 172.23.0.14
depends_on:
- pcrf
restart: unless-stopped
# === 4G EPC (Evolved Packet Core) - User Plane ===
sgwu:
image: gradiant/open5gs:2.7.6
container_name: open5gs_sgwu
command: open5gs-sgwud
volumes:
- ./config/sgwu.yaml:/opt/open5gs/etc/open5gs/sgwu.yaml
networks:
open5gs_cp:
ipv4_address: 172.22.0.15
open5gs_up:
ipv4_address: 172.23.0.10
ports:
- "2152:2152/udp"
depends_on:
- sgwc
restart: unless-stopped
upf:
image: gradiant/open5gs:2.7.6
container_name: open5gs_upf
command: open5gs-upfd
volumes:
- ./config/upf.yaml:/opt/open5gs/etc/open5gs/upf.yaml
networks:
open5gs_cp:
ipv4_address: 172.22.0.16
open5gs_up:
ipv4_address: 172.23.0.11
cap_add:
- NET_ADMIN
privileged: true
sysctls:
- net.ipv4.ip_forward=1
depends_on:
- smf
restart: unless-stopped
networks:
open5gs_cp:
driver: bridge
ipam:
config:
- subnet: 172.22.0.0/24
open5gs_up:
driver: bridge
ipam:
config:
- subnet: 172.23.0.0/24
volumes:
mongodb_data:
Notice the key differences from a 5G SA compose file:
- No NRF, SCP, AUSF, UDM, UDR, NSSF, BSF — these are 5G SA-only NFs
- HSS and PCRF are 4G-specific, using Diameter (not SBI)
- SGW-C and SGW-U exist only in 4G (5G SA has no serving gateway)
- MME replaces AMF, listening on S1AP port 36412 (not NGAP 38412)
- The
command:directive tells the monolithic image which daemon to run
Step 3: Open5GS EPC Configurations
Create each config file in config/:
MME (config/mme.yaml)
The MME is the central control plane hub — it handles UE attach, authentication (via HSS), bearer setup (via SGW-C), and paging.
logger:
level: info
mme:
freeDiameter: /opt/open5gs/etc/freeDiameter/mme.conf
s1ap:
server:
- address: 172.22.0.10
gtpc:
server:
- address: 172.22.0.10
client:
sgwc:
- address: 172.22.0.13
smf:
- address: 172.22.0.14
gummei:
- plmn_id:
mcc: 001
mnc: 01
mme_gid: 2
mme_code: 1
tai:
- plmn_id:
mcc: 001
mnc: 01
tac: 1
security:
integrity_order: [EIA2, EIA1, EIA0]
ciphering_order: [EEA2, EEA1, EEA0]
S1AP listens on port 36412 (SCTP) by default. This is the 4G control plane entry point — the equivalent of NGAP port 38412 in 5G SA. The gtpc.client section tells MME where SGW-C and PGW-C/SMF are, using GTP-C (not SBI). There is no NRF-based discovery in 4G — all peer addresses are configured explicitly.
Notice ciphering_order puts EEA2 first (AES-128). Never put EEA0 (null cipher) first in production — that enables the downgrade attack discussed in Part 8.
HSS (config/hss.yaml)
The HSS is the subscriber database — it stores IMSI, K, OPc, and subscriber profiles. It communicates with MME over the S6a Diameter interface.
logger:
level: info
hss:
freeDiameter: /opt/open5gs/etc/freeDiameter/hss.conf
db_uri: mongodb://172.22.0.2/open5gs
PCRF (config/pcrf.yaml)
The PCRF enforces QoS policies and charging rules. It communicates with PGW-C/SMF over the Gx Diameter interface.
logger:
level: info
pcrf:
freeDiameter: /opt/open5gs/etc/freeDiameter/pcrf.conf
db_uri: mongodb://172.22.0.2/open5gs
SGW-C (config/sgwc.yaml)
The Serving Gateway Control Plane manages bearer contexts and acts as the mobility anchor for inter-eNB handovers. It communicates with MME over S11 (GTP-C), with PGW-C/SMF over S5-C (GTP-C), and programs SGW-U over Sxa (PFCP).
logger:
level: info
sgwc:
gtpc:
server:
- address: 172.22.0.13
pfcp:
server:
- address: 172.22.0.13
client:
sgwu:
- address: 172.22.0.15
SGW-U (config/sgwu.yaml)
The Serving Gateway User Plane forwards user data between the eNB (S1-U) and PGW-U (S5-U). It is programmed by SGW-C over Sxa (PFCP).
logger:
level: info
sgwu:
pfcp:
server:
- address: 172.22.0.15
gtpu:
server:
- address: 172.23.0.10
The SGW-U's GTP-U address (172.23.0.10) is on the user plane network — this is where the eNB sends S1-U user data. In a real deployment, this address would be reachable from the RAN.
SMF / PGW-C (config/smf.yaml)
In Open5GS, the SMF binary handles both the 5G SMF role and the 4G PGW-C role. In this 4G lab, it acts as PGW-C — managing PDN sessions, IP allocation, and policy enforcement (via PCRF over Gx Diameter). It programs the UPF/PGW-U over Sxb (PFCP).
logger:
level: info
smf:
freeDiameter: /opt/open5gs/etc/freeDiameter/smf.conf
gtpc:
server:
- address: 172.22.0.14
pfcp:
server:
- address: 172.23.0.14
client:
upf:
- address: 172.23.0.11
subnet:
- addr: 10.45.0.1/16
dnn: internet
dns:
- 8.8.8.8
- 8.8.4.4
The SMF receives S5-C GTP-C from SGW-C (not SBI from AMF like in 5G SA). It also uses Diameter Gx to talk to PCRF (not SBI to PCF). This is the key architectural difference — 4G uses legacy protocols, not HTTP/2.
UPF / PGW-U (config/upf.yaml)
In Open5GS, the UPF binary handles both 5G UPF and 4G PGW-U roles. In this 4G lab, it acts as PGW-U — forwarding user data to the Internet via the ogstun TUN interface. It is programmed by PGW-C/SMF over Sxb (PFCP).
logger:
level: info
upf:
pfcp:
server:
- address: 172.23.0.11
gtpu:
server:
- address: 172.23.0.11
subnet:
- addr: 10.45.0.1/16
dnn: internet
Step 4: freeDiameter Configuration
4G EPC uses Diameter for signaling between MME↔HSS (S6a) and PGW-C↔PCRF (Gx). These require freeDiameter configuration files. Create each file in freeDiameter/:
What is freeDiameter? It is the open-source Diameter protocol stack used by Open5GS. In 5G SA, NFs communicate over SBI (HTTP/2 REST APIs) and don't need Diameter at all. In 4G, Diameter is the backbone of control plane signaling — this is one of the fundamental protocol differences between generations.
MME Diameter (freeDiameter/mme.conf)
Identity = "mme.localdomain";
Realm = "localdomain";
TLS_Cred = "/opt/open5gs/etc/freeDiameter/default.cert.pem",
"/opt/open5gs/etc/freeDiameter/default.key.pem";
TLS_CA = "/opt/open5gs/etc/freeDiameter/default.cacert.pem";
No_SCTP;
ConnectPeer = "hss.localdomain" { ConnectTo = "172.22.0.11"; Port = 3868; No_TLS; };
LoadExtension = "dbg_msg_dumps.fdx" : "0x8888";
LoadExtension = "dict_rfc5777.fdx";
LoadExtension = "dict_mip6i.fdx";
LoadExtension = "dict_nasreq.fdx";
LoadExtension = "dict_nas_mipv6.fdx";
LoadExtension = "dict_dcca.fdx";
LoadExtension = "dict_dcca_3gpp.fdx";
LoadExtension = "dict_s6a.fdx";
HSS Diameter (freeDiameter/hss.conf)
Identity = "hss.localdomain";
Realm = "localdomain";
TLS_Cred = "/opt/open5gs/etc/freeDiameter/default.cert.pem",
"/opt/open5gs/etc/freeDiameter/default.key.pem";
TLS_CA = "/opt/open5gs/etc/freeDiameter/default.cacert.pem";
No_SCTP;
ConnectPeer = "mme.localdomain" { ConnectTo = "172.22.0.10"; Port = 3868; No_TLS; };
LoadExtension = "dbg_msg_dumps.fdx" : "0x8888";
LoadExtension = "dict_rfc5777.fdx";
LoadExtension = "dict_mip6i.fdx";
LoadExtension = "dict_nasreq.fdx";
LoadExtension = "dict_nas_mipv6.fdx";
LoadExtension = "dict_dcca.fdx";
LoadExtension = "dict_dcca_3gpp.fdx";
LoadExtension = "dict_s6a.fdx";
SMF / PGW-C Diameter (freeDiameter/smf.conf)
Identity = "smf.localdomain";
Realm = "localdomain";
TLS_Cred = "/opt/open5gs/etc/freeDiameter/default.cert.pem",
"/opt/open5gs/etc/freeDiameter/default.key.pem";
TLS_CA = "/opt/open5gs/etc/freeDiameter/default.cacert.pem";
No_SCTP;
ConnectPeer = "pcrf.localdomain" { ConnectTo = "172.22.0.12"; Port = 3868; No_TLS; };
LoadExtension = "dbg_msg_dumps.fdx" : "0x8888";
LoadExtension = "dict_rfc5777.fdx";
LoadExtension = "dict_mip6i.fdx";
LoadExtension = "dict_nasreq.fdx";
LoadExtension = "dict_nas_mipv6.fdx";
LoadExtension = "dict_dcca.fdx";
LoadExtension = "dict_dcca_3gpp.fdx";
LoadExtension = "dict_dcca_3gpp/dict_gx.fdx";
PCRF Diameter (freeDiameter/pcrf.conf)
Identity = "pcrf.localdomain";
Realm = "localdomain";
TLS_Cred = "/opt/open5gs/etc/freeDiameter/default.cert.pem",
"/opt/open5gs/etc/freeDiameter/default.key.pem";
TLS_CA = "/opt/open5gs/etc/freeDiameter/default.cacert.pem";
No_SCTP;
ConnectPeer = "smf.localdomain" { ConnectTo = "172.22.0.14"; Port = 3868; No_TLS; };
LoadExtension = "dbg_msg_dumps.fdx" : "0x8888";
LoadExtension = "dict_rfc5777.fdx";
LoadExtension = "dict_mip6i.fdx";
LoadExtension = "dict_nasreq.fdx";
LoadExtension = "dict_nas_mipv6.fdx";
LoadExtension = "dict_dcca.fdx";
LoadExtension = "dict_dcca_3gpp.fdx";
LoadExtension = "dict_dcca_3gpp/dict_gx.fdx";
These configs use No_TLS for simplicity in the lab. In production, Diameter links must use TLS/DTLS — unencrypted Diameter is a critical vulnerability (see Part 8: Diameter Exploitation threat).
Step 5: Launch the EPC
# Start infrastructure first
docker compose up -d mongodb
sleep 3
# Start HSS and PCRF (Diameter servers must be up before clients)
docker compose up -d hss pcrf
sleep 3
# Start remaining control plane NFs
docker compose up -d mme sgwc smf
sleep 2
# Start user plane NFs
docker compose up -d sgwu upf
# Start WebUI
docker compose up -d webui
# Verify all containers are running
docker compose ps
Expected Output
NAME IMAGE STATUS
open5gs_hss gradiant/open5gs:2.7.6 Up
open5gs_mme gradiant/open5gs:2.7.6 Up
open5gs_mongodb mongo:6.0 Up
open5gs_pcrf gradiant/open5gs:2.7.6 Up
open5gs_sgwc gradiant/open5gs:2.7.6 Up
open5gs_sgwu gradiant/open5gs:2.7.6 Up
open5gs_smf gradiant/open5gs:2.7.6 Up
open5gs_upf gradiant/open5gs:2.7.6 Up
open5gs_webui gradiant/open5gs-webui:2.7.6 Up
Check Diameter Associations
Verify that MME has established a Diameter S6a connection to HSS:
docker compose logs mme | grep -i "diameter\|s6a\|connected"
Verify that SMF/PGW-C has established a Diameter Gx connection to PCRF:
docker compose logs smf | grep -i "diameter\|gx\|connected"
Check PFCP Associations
Verify SGW-C ↔ SGW-U (Sxa) and SMF ↔ UPF (Sxb) PFCP sessions:
docker compose logs sgwc | grep -i "pfcp\|associated"
docker compose logs smf | grep -i "pfcp\|associated"
Check S1AP Readiness
Verify MME is listening for eNB connections on S1AP:
docker compose logs mme | grep -i "s1ap\|sctp"
Expected: MME is listening on 172.22.0.10:36412 (SCTP).
Step 6: Register a Subscriber
- Open browser: http://localhost:9999
- Login:
- Username:
admin - Password:
1423
- Username:
- Click Subscribers -> + (Add)
- Fill in subscriber details:
- IMSI:
001010000000001 - K:
465B5CE8B199B49FAA5F0A2EE238A6BC - OPc:
E8ED289DEBA952E4283B54E88E6183CA - APN/DNN:
internet
- IMSI:
- Click SAVE
These credentials are stored in the HSS (MongoDB). When an eNB sends an Attach Request, MME queries HSS over Diameter S6a to retrieve authentication vectors (RAND, AUTN, XRES, KASME). Any mismatch in K/OPc between the HSS and the SIM/UE causes authentication failure.
Step 7: Verify EPC Health
Even without an eNB connected, you can verify the EPC is functioning correctly:
Check All Diameter Peers
# MME should show HSS as a connected peer
docker compose logs mme 2>&1 | grep -i "peer\|hss"
# SMF should show PCRF as a connected peer
docker compose logs smf 2>&1 | grep -i "peer\|pcrf"
Check PFCP Sessions
# SGW-C should show SGW-U as an associated UPF
docker compose logs sgwc 2>&1 | grep -i "pfcp\|sgwu\|associated"
# SMF should show UPF as an associated UPF
docker compose logs smf 2>&1 | grep -i "pfcp\|upf\|associated"
Check UPF TUN Interface
docker exec -it open5gs_upf ip addr show ogstun
Expected:
ogstun: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1400
inet 10.45.0.1/16 scope global ogstun
Test MongoDB Subscriber Data
docker exec -it open5gs_mongodb mongosh --eval \
'db = db.getSiblingDB("open5gs"); db.subscribers.find().pretty()'
You should see the subscriber record with IMSI, K, OPc, and APN.
Step 8: Packet Capture and Protocol Analysis
Even without a connected eNB, you can capture and analyze the 4G-specific signaling between core NFs.
Capture Diameter S6a (MME <-> HSS)
docker exec -it open5gs_mme tcpdump -i any -w /tmp/diameter_s6a.pcap tcp port 3868
# Let it run for 30 seconds to capture Diameter keepalives (CER/CEA, DWR/DWA)
# Ctrl+C to stop
docker cp open5gs_mme:/tmp/diameter_s6a.pcap .
Wireshark filter: diameter
You should see:
- CER/CEA (Capabilities-Exchange-Request/Answer) — the initial Diameter handshake
- DWR/DWA (Device-Watchdog-Request/Answer) — periodic keepalives
Capture Diameter Gx (SMF/PGW-C <-> PCRF)
docker exec -it open5gs_smf tcpdump -i any -w /tmp/diameter_gx.pcap tcp port 3868
docker cp open5gs_smf:/tmp/diameter_gx.pcap .
Wireshark filter: diameter
Capture PFCP (Control <-> User Plane)
# Sxa: SGW-C <-> SGW-U
docker exec -it open5gs_sgwc tcpdump -i any -w /tmp/pfcp_sxa.pcap udp port 8805
docker cp open5gs_sgwc:/tmp/pfcp_sxa.pcap .
# Sxb: SMF <-> UPF
docker exec -it open5gs_smf tcpdump -i any -w /tmp/pfcp_sxb.pcap udp port 8805
docker cp open5gs_smf:/tmp/pfcp_sxb.pcap .
Wireshark filter: pfcp
You should see:
- PFCP Association Setup Request/Response — control plane registers with user plane
- PFCP Heartbeat Request/Response — periodic liveness checks
Comparison with 5G SA: In 5G SA (Part 6), inter-NF communication uses SBI HTTP/2 on port 7777. Here in 4G, you see Diameter on port 3868 and GTP-C on port 2123 instead. Capturing these protocol differences is a key learning objective of this lab.
Connecting srsRAN 4G (Reference)
To complete the end-to-end 4G data path, you need a 4G eNB and UE simulator. srsRAN 4G with ZMQ (zero-message-queue) enables software-only LTE simulation without radio hardware.
Building srsRAN 4G
# Clone srsRAN 4G
git clone https://github.com/srsran/srsRAN_4G.git
cd srsRAN_4G
# Build with ZMQ support (software radio)
mkdir build && cd build
cmake .. -DENABLE_ZMQ=ON
make -j$(nproc)
# Key binaries:
# ./srsepc/src/srsepc - Standalone EPC (not needed, we use Open5GS)
# ./srsenb/src/srsenb - LTE eNB
# ./srsue/src/srsue - LTE UE
srsRAN eNB Configuration (Connecting to Open5GS MME)
Key settings in enb.conf:
[enb]
mcc = 001
mnc = 01
mme_addr = 172.22.0.10 # Open5GS MME IP
gtp_bind_addr = 127.0.1.1 # Local GTP-U bind
s1c_bind_addr = 127.0.1.1 # Local S1AP bind
n_prb = 50
[rf]
device_name = zmq
device_args = fail_on_disconnect=true,tx_port=tcp://*:2000,rx_port=tcp://localhost:2001,id=enb,base_srate=23.04e6
srsRAN UE Configuration
Key settings in ue.conf:
[usim]
mode = soft
algo = milenage
opc = E8ED289DEBA952E4283B54E88E6183CA
k = 465B5CE8B199B49FAA5F0A2EE238A6BC
imsi = 001010000000001
[rf]
device_name = zmq
device_args = tx_port=tcp://*:2001,rx_port=tcp://localhost:2000,id=ue,base_srate=23.04e6
The K, OPc, and IMSI values in the srsRAN UE config must match what you registered in the Open5GS WebUI (Step 6).
Expected Flow When Connected
UE ──[Uu/ZMQ]──> eNB ──[S1-MME/SCTP:36412]──> MME
│
MME ──[S6a/Diameter]──> HSS (auth vectors)
MME ──[S11/GTP-C]──> SGW-C
SGW-C ──[S5-C/GTP-C]──> SMF/PGW-C
SMF ──[Gx/Diameter]──> PCRF (QoS policy)
SMF ──[Sxb/PFCP]──> UPF/PGW-U (forwarding rules)
SGW-C ──[Sxa/PFCP]──> SGW-U (forwarding rules)
UE ──[Uu/ZMQ]──> eNB ──[S1-U/GTP-U]──> SGW-U ──[S5-U/GTP-U]──> UPF/PGW-U ──[SGi]──> Internet
Resources
| Resource | URL |
|---|---|
| srsRAN 4G GitHub | https://github.com/srsran/srsRAN_4G |
| Open5GS + srsRAN Tutorial | https://open5gs.org/open5gs/docs/tutorial/02-srsran-4g/ |
| srsRAN ZMQ Guide | https://docs.srsran.com/projects/4g/en/latest/app_notes/source/zeromq/source/index.html |
Troubleshooting
Diameter Peers Not Connecting
docker compose logs hss | grep -i "error\|failed\|diameter"
docker compose logs mme | grep -i "error\|failed\|diameter"
Common causes:
- HSS/PCRF not started before MME/SMF — restart MME/SMF after HSS/PCRF are up
- Mismatched
IdentityorRealmin freeDiameter configs - Wrong
ConnectToIP address in.conffiles
PFCP Association Failed
docker compose logs sgwc | grep -i "pfcp\|error"
docker compose logs smf | grep -i "pfcp\|error"
Common causes:
- SGW-U or UPF not reachable on PFCP port 8805
- Mismatched PFCP addresses between control and user plane configs
UPF Cannot Create ogstun
docker exec -it open5gs_upf ip addr show ogstun
Fix: UPF needs privileged: true and NET_ADMIN capability for TUN device creation. Verify these are set in docker-compose.yml.
No Internet from UPF
# Check NAT rules
docker exec -it open5gs_upf iptables -t nat -L
# Add NAT if missing
docker exec -it open5gs_upf iptables -t nat -A POSTROUTING \
-s 10.45.0.0/16 ! -o ogstun -j MASQUERADE
Cleanup
docker compose down -v # -v removes volumes (subscriber data)
Exercises
-
Diameter Analysis: Capture Diameter traffic on port 3868 between MME and HSS. Open in Wireshark and identify the CER/CEA handshake. What Application-ID is used for S6a? (Answer: 16777251 — 3GPP S6a/S6d)
-
PFCP Inspection: Capture PFCP traffic on port 8805 between SGW-C and SGW-U. Find the PFCP Association Setup Request. What node ID does SGW-C advertise?
-
Compare Protocols: In the 5G SA lab (Part 6), inter-NF communication uses SBI HTTP/2 on port 7777. In this 4G lab, which ports and protocols carry the equivalent signaling? Fill in:
- AMF ↔ UDM (5G) maps to ___ ↔ ___ using ___ protocol on port ___ (4G)
- AMF ↔ SMF (5G SBI) maps to ___ ↔ ___ using ___ protocol on port ___ (4G)
-
Security Gap Analysis: Examine the freeDiameter configs. What setting makes the Diameter links insecure? What would you change for production? (Hint: look at
No_TLS) -
GTP-C Inspection: Capture GTP-C traffic (UDP port 2123) between MME and SGW-C. This is the S11 interface that carries bearer management signaling. Compare this to SBI in 5G SA.
Summary
You have successfully:
- Deployed a 4G LTE EPC with all required NFs:
- MME — control plane hub (S1AP, S11 GTP-C, S6a Diameter)
- HSS — subscriber database (S6a Diameter, MongoDB)
- PCRF — policy and charging (Gx Diameter)
- SGW-C / SGW-U — serving gateway with CUPS (GTP-C, GTP-U, PFCP Sxa)
- SMF (PGW-C) / UPF (PGW-U) — PDN gateway with CUPS (GTP-C, GTP-U, PFCP Sxb, Diameter Gx)
- Configured Diameter signaling (freeDiameter) for S6a and Gx interfaces
- Configured GTP-C tunneling for S11 and S5-C
- Configured PFCP for CUPS (Sxa, Sxb)
- Registered a subscriber in HSS via WebUI
- Verified EPC health (Diameter associations, PFCP sessions, ogstun interface)
- Captured and analyzed 4G-specific protocols (Diameter, PFCP, GTP-C)
- Referenced srsRAN 4G for connecting a real LTE eNB + UE
Next: Part 5: 5G NSA Concepts ->