Physical RAN Integration¶
Connect a physical femtocell or small-cell gNB (e.g. nCELL-F2240) instead of, or alongside, UERANSIM.
Interfaces: Host, Worker, Bridge¶
| Layer | Name | Where | Example | Purpose |
|---|---|---|---|---|
| Host interface | PHYSICAL_RAN_BRIDGE |
Your laptop/NUC | enx00e04c6817b7 |
Host NIC on the same L2 network as the gNB. Can be a built-in Ethernet port, a USB-to-Ethernet adapter, or any NIC — Vagrant bridges it into the worker VM. |
| Worker interface | physical_ran_interface |
Inside worker VM | enp0s9 |
Virtual NIC created by VirtualBox. Linux names it (e.g. enp0s9). Leave empty in group_vars for auto-detect by subnet IP. |
| Bridge | br-ran |
Worker VM (OVS) | br-ran |
OVS bridge. The worker interface is added to it; br-ran gets the gateway IP (192.168.6.1). |
Flow: Host NIC → (VirtualBox bridge) → Worker NIC (enp0s9) → (OVS) → br-ran → patch ports → br-n2 / br-n3 → AMF / UPF pods.
Verification: When you run vagrant reload worker with PHYSICAL_RAN_BRIDGE=<nic>, Vagrant persists the applied value to .physical_ran_bridge_applied. The dashboard reads this and shows a ✓ next to the Host PC NIC when it matches, no trust required.
Architecture¶
Worker-as-Router Design¶
The worker VM acts as a transport router between the physical RAN network and the 5G overlay networks, mirroring how a real transport network connects a cell site to the core.
PHYSICAL RAN NETWORK WORKER VM (Router) 5G CORE PODS
192.168.6.0/24 (Overlay Networks)
┌──────────┐ ┌───────────────────────────────────┐
│ UE │ │ │
│(via Uu) │ │ br-ran (192.168.6.1/24) │
└────┬─────┘ │ │ │
│ radio │ ├── patch-ran-n2 ──┐ │
┌────┴─────┐ enp0s9 │ └── patch-ran-n3 ──┼──┐ │
│ gNB │ ─────────────────> │ │ │ │ ┌──────────────┐
│ .5.100 │ (L2 bridged) │ br-n2 (10.202.0.1) │ │ │ │ AMF │
└──────────┘ │ └── patch-n2-ran ──┘ │ │──> │ n2: .202.0.100│
│ │ │ │ n2phy: .5.150 │
│ br-n3 (10.203.0.1) │ │ └──────────────┘
│ (10.203.0.254) │ │
│ └── patch-n3-ran ─────┘ │ ┌──────────────┐
│ │──> │ UPF-Cloud │
│ br-n4 (10.204.0.1) │ │ n3: .203.0.101│
│ │ └──────────────┘
└───────────────────────────────────┘
Why This Approach Is Correct¶
- Mirrors real deployments: In production 5G, the gNB connects to a transport network that reaches both the AMF (N2) and UPF (N3). OVS patch ports replicate this shared transport.
- Network isolation preserved: Each N-interface remains a separate OVS bridge with its own VXLAN tunnel and subnet. The worker only routes between the physical transport and the overlays.
- No NAT or tunneling hacks: The gNB communicates directly via L2 (patch ports provide bridge-level connectivity) for N2 signaling, and via L3 routing for N3 GTP-U to the UPF.
Data Path: PDU Session User Plane¶
UE ──(Uu radio)──> gNB (192.168.6.100)
│
│ GTP-U encapsulated, dst = 10.203.0.101 (UPF N3)
│
▼
enp0s9 (bridged into br-ran)
│
br-ran (192.168.6.1/24)
│
patch-ran-n3 ──> patch-n3-ran
│
br-n3 (10.203.0.1/24, 10.203.0.254/24)
│
UPF-Cloud pod (n3: 10.203.0.101)
│
ogstun ──> iptables MASQUERADE ──> n6 ──> Data Network
Return path:
UPF has route: 192.168.6.0/24 via 10.203.0.254 dev n3
Worker br-n3 (10.203.0.254) forwards to br-ran via patch ports
br-ran delivers to gNB via enp0s9
IP Addressing Summary¶
| Component | Interface | IP | Role |
|---|---|---|---|
| Worker | br-ran | 192.168.6.1/24 | Gateway for physical RAN subnet |
| Worker | br-n2 | 10.202.0.1/24 | N2 overlay gateway |
| Worker | br-n3 | 10.203.0.1/24 | N3 overlay gateway |
| Worker | br-n3 (secondary) | 10.203.0.254/24 | UPF return-route next-hop |
| AMF | n2phy | 192.168.6.150/24 | NGAP endpoint for physical gNB |
| AMF | n2 | 10.202.0.100/24 | NGAP endpoint (overlay) |
| UPF-Cloud | n3 | 10.203.0.101/24 | GTP-U endpoint |
| gNB | eth | 192.168.6.100/24 | Physical RAN interface |
OVS DaemonSet vs NAD (what runs where)¶
| Component | Where | What it does |
|---|---|---|
OVS DaemonSet (ds-net-setup-worker) |
Worker node (hostNetwork) | Runs ovs-setup.sh to create/remove br-ran, patch ports, gateway IPs. When RAN_BRIDGE_MODE=disabled, it tears down br-ran. |
| NAD n2-physical | Kubernetes API (cluster) | NetworkAttachmentDefinition that tells Multus how to attach pods to br-ran. It is a cluster resource, not "on" the worker. Disable deletes it via Ansible. |
| Playbook guard | Ansible (phase 4 & 5) | When physical_ran_enabled is true, checks whether br-ran exists on the worker before creating n2-physical and before AMF attachment. If the bridge is missing, physical-RAN-specific resources are skipped for that run so the base system can still finish deploying. Bypass: physical_ran_skip_bridge_check: true in group_vars or -e physical_ran_skip_bridge_check=true. |
1. Enable Integration¶
Step 1: Configure Ansible¶
Edit ansible/group_vars/all.yml:
physical_ran_enabled: true
physical_ran_interface: "" # Leave empty for auto-detect by subnet IP
physical_ran_subnet: "192.168.6.0/24"
amf_physical_ran_ip: "192.168.6.150"
ran_bridge_mode: n2_n3
ran_interface: "{{ physical_ran_interface }}"
physical_ran_interface is the worker NIC name (e.g. enp0s9). When empty, the OVS setup script auto-detects it by finding the interface with an IP in physical_ran_subnet.
Step 2: Configure Vagrantfile¶
The worker VM needs a bridged network adapter connected to the same physical network as the gNB. In the Vagrantfile this is the ran_network:
worker.vm.network "private_network", ip: "192.168.6.1",
virtualbox__intnet: "5g-ran-network"
If using a USB Ethernet adapter on the host, bridge it instead:
worker.vm.network "public_network", bridge: "enxe2b7aa97626e"
After changing the Vagrantfile, reload the VM:
vagrant reload worker
Step 3: Apply Overlay + Core Changes¶
vagrant ssh ansible
cd ~/ansible-ro
# Re-deploy the OVS DaemonSet (creates br-ran, patch ports, gateway IPs)
ansible-playbook phases/04-overlay-network/playbook.yml --tags overlay
# Re-deploy 5G Core (adds n2-physical NAD to AMF, PHYSICAL_RAN_SUBNET to UPF)
ansible-playbook phases/05-5g-core/playbook.yml --tags deployments
2. Configure the gNB¶
Network Configuration (Web UI)¶
For commercial femtocells, configure these in the device's web UI (typically under LAN, Network, or Ethernet settings):
| Parameter | Value | Notes |
|---|---|---|
| gNB IP | 192.168.6.100/24 |
Any free IP in the RAN subnet |
| Default gateway | 192.168.6.1 |
Required. Worker's br-ran. Without this, GTP-U to UPF (10.203.0.101) fails. |
| Static routes (if supported) | 10.202.0.0/16 via 192.168.6.110.203.0.0/16 via 192.168.6.1 |
Alternative if the UI has a "Static routes" or "Route table" section |
Critical: The gNB reaches the AMF (192.168.6.150) on the same subnet. To reach the UPF (10.203.0.101), it must use 192.168.6.1 as gateway. If "Default gateway" is empty or wrong, you get Network is unreachable when the gNB tries to send GTP-U.
5G Parameters¶
| Parameter | Value |
|---|---|
| MCC | 001 |
| MNC | 01 |
| TAC | 1 |
| AMF IP | 192.168.6.150 (AMF's n2phy interface) |
| AMF SCTP Port | 38412 |
| S-NSSAI | SST=1, SD=0x000001 |
3. Physical Connection¶
With VirtualBox¶
The gNB must be on the same L2 segment as the host NIC specified in PHYSICAL_RAN_BRIDGE. Vagrant bridges that NIC into the worker VM.
NUC / Server: the gNB is connected via a router or switch to one of the NUC's built-in Ethernet ports:
[NUC]
└── enp2s0 ─── [Router / Switch] ─── gNB (192.168.6.100)
│
[VirtualBox] │
└── Worker VM (enp0s9) ────┘
bridged to enp2s0
./testbed-config ran enp2s0: use the NIC name that is on the gNB's network.
Laptop: USB-to-Ethernet adapter bridged to the same switch:
[Laptop]
└── USB Ethernet (enx00e04c...) ─── [Switch] ─── gNB (192.168.6.100)
│
[VirtualBox] │
└── Worker VM (enp0s9) ─────────────┘
bridged to USB adapter
./testbed-config ran enx00e04c6817b7: use the adapter's interface name.
Bare Metal (production)¶
Connect the gNB directly to the worker's dedicated RAN NIC. No VirtualBox bridging needed.
4. Verify¶
OVS Bridges and Patch Ports¶
vagrant ssh worker
sudo ovs-vsctl show | grep -A8 br-ran
Expected:
Bridge br-ran
Port enp0s9
Interface enp0s9
Port patch-ran-n2
Interface patch-ran-n2
type: patch
options: {peer=patch-n2-ran}
Port patch-ran-n3
Interface patch-ran-n3
type: patch
options: {peer=patch-n3-ran}
Gateway IPs¶
ip -4 addr show br-ran | grep inet # 192.168.6.1/24
ip -4 addr show br-n3 | grep inet # 10.203.0.1/24 and 10.203.0.254/24
gNB Reachability¶
# From the gNB
ping 192.168.6.1 # Worker br-ran gateway
ping 192.168.6.150 # AMF n2phy
ping 10.203.0.101 # UPF N3 (via routing through worker)
AMF Registration¶
sudo k3s kubectl logs -f -l app=amf -n 5g | grep -i gnb
Expected:
[Added] Number of gNBs is now 1
UPF Return Route¶
sudo k3s kubectl exec -n 5g deploy/upf-cloud -- ip route show | grep 192.168.5
Expected:
192.168.6.0/24 via 10.203.0.254 dev n3
5. Switch Back to UERANSIM¶
# ansible/group_vars/all.yml
physical_ran_enabled: false
ran_bridge_mode: disabled
vagrant ssh ansible
cd ~/ansible-ro
ansible-playbook phases/04-overlay-network/playbook.yml --tags overlay
ansible-playbook phases/05-5g-core/playbook.yml --tags deployments
ansible-playbook phases/06-ueransim-mec/playbook.yml
Troubleshooting¶
| Problem | Cause | Solution |
|---|---|---|
ping 192.168.6.1 fails |
br-ran has no IP | Re-run overlay playbook; check RAN_SUBNET env var |
ping 10.203.0.101 fails from gNB |
Missing route on gNB | Commercial femtocell: set Default gateway = 192.168.6.1 in web UI. Software gNB: ip route add 10.203.0.0/16 via 192.168.6.1 |
| UPF can't reach gNB (no GTP-U downlink) | Missing return route in UPF | Check PHYSICAL_RAN_SUBNET env var in UPF deployment |
| AMF doesn't see gNB | PLMN mismatch or SCTP issue | Check MCC/MNC/TAC; sudo modprobe sctp on worker |
failed to find bridge br-ran |
NAD / AMF reference br-ran but OVS never created it |
Worker needs RAN NIC + overlay applied; restart ds-net-setup-worker on worker. Same root cause as a skipped physical-RAN configuration (table OVS DaemonSet vs NAD). |
| Physical RAN is skipped during deploy | physical_ran_enabled true, br-ran missing on worker |
Apply PHYSICAL_RAN_BRIDGE to the worker VM (testbed-config provision now reloads the worker automatically when needed, or run vagrant reload worker), then re-run overlay/core or enable later from the dashboard. |
| br-ran persists after Disable | DS pod restarted before teardown; old script | Fixed: ovs-setup.sh now tears down br-ran when RAN_BRIDGE_MODE=disabled. Re-run Disable or ansible-playbook phases/04-overlay-network/playbook.yml --tags overlay -e ran_bridge_mode=disabled |
| NAD n2-physical persists after Disable | Playbook only skipped creation, never deleted | Fixed: multus_install now deletes the NAD when physical_ran_enabled=false. Re-run ansible-playbook phases/04-overlay-network/playbook.yml --tags nad -e physical_ran_enabled=false |
macvlan: device or resource busy |
n2-physical NAD misconfigured | Ensure NAD uses type: ovs, bridge: br-ran |
| UE authenticated but no data | PDU session fails at PFCP | Check SMF→UPF N4 connectivity; check UPF logs |
Useful Commands¶
# Check OVS bridge details
sudo ovs-vsctl show
# Check all bridge IPs on worker
ip -4 addr show | grep -E 'br-(ran|n2|n3)'
# Check UPF routing table
sudo k3s kubectl exec -n 5g deploy/upf-cloud -- ip route
# Check AMF NGAP listener
sudo k3s kubectl exec -n 5g deploy/amf -- ss -Slnp | grep 38412
# Capture GTP-U traffic on br-n3
sudo tcpdump -i br-n3 udp port 2152 -c 10
# Check subscribers in MongoDB
sudo k3s kubectl exec -n 5g deploy/mongodb -- mongosh open5gs --eval "db.subscribers.find()"