Skip to content
Docs / dflux TinyCore
ContactGet started

Forward user-plane traffic

Run the UPF with its userspace datapath so it really forwards packets over a TUN and GTP-U, then push traffic and read the metrics.

The default lab UPF runs with -datapath stub: the PFCP control plane works end to end, but the forwarder only records calls and never touches a packet. That is fine for the registration walkthrough, but it means no user-plane traffic actually moves. This tutorial switches to the userspace datapath — a real forwarder that opens a Linux TUN for N6 and a GTP-U UDP socket for N3 — so a UE can ping the Internet through the core. For the concepts, see User plane; for the full flag reference, see d3x-core-upf CLI.

This needs a Linux host and privileges
The userspace datapath opens /dev/net/tun, which requires CAP_NET_ADMIN — so run the UPF as root or grant the capability to the binary. It also needs a real GTP-U sender (a gNB simulator such as UERANSIM that builds a UE TUN), which only runs on Linux. None of this works in the macOS Docker lab, and the dflux Runner gNB simulator drives the control plane only — it does not push user-plane packets. So this tutorial runs the required NFs natively on a Linux host rather than in the Docker lab. See Running natively and UPF datapath.

1. Grant the capability

With the required NFs installed, either run the UPF as root or grant the installed binary the TUN capability once:

Bash

2. Start the UPF on the userspace datapath

Run the UPF with -datapath userspace. Unlike the stub, this opens the TUN named by -tun-name on N6 and binds the GTP-U socket given by -n3-addr on N3:

Bash

The other three functions (NRF, DPF, DMF) run as in any native bring-up; point the DMF's SMF at this UPF's N4 address (-upf 127.0.0.2:8805). The full four-shell startup is in Running natively.

The kernel side of the TUN is yours
The UPF deliberately does not run ip addr or iptables for you. Once it is up, give the TUN an address in the UE-IP pool, bring it up, and set up egress — either host-side MASQUERADE or in-process NAT44 with -nat44-addr. See UPF datapath for both options.
Bash

3. Bring up a UE and a PDU session

Connect a Linux gNB+UE simulator that builds a real UE interface — a typical lab uses UERANSIM. Configure it with the lab's PLMN (001/01), TAC, and slice (sst 1), and a seeded subscriber (for example imsi-001010000000001, key 465B5CE8B199B49FAA5F0A2EE238A6BC, OPc E8ED289DEBA952E4283B54E88E6183CA), exactly as in Register a UE and open a session. Once the PDU session establishes, the simulator creates a UE interface with an address from the pool:

Bash

4. Push and observe traffic

Send packets from the UE interface through the core:

Bash

Uplink packets arrive at the UPF over GTP-U on N3, are decapsulated, and leave on the TUN; downlink packets are re-encapsulated and sent back to the gNB. You can watch both halves:

Bash

5. Read the UPF metrics

The UPF serves Prometheus metrics on its -metrics-addr (:9095 here). After a session is up and traffic is flowing, the key counters move:

Bash
text

If the ping hangs, the tcpdump commands above plus upf_pfcp_messages_total tell you which leg is missing: no ICMP on the TUN means uplink decap is not happening; no GTP-U on the wire means downlink encap or the route back to the gNB is wrong.

Lab scope
This is a lab user plane. The userspace datapath is single-DNN per process, IPv4-first, and does not enforce per-session bitrate limits — the goal is a UE that reaches the Internet, not a production forwarder. For an IPv6 inner, multiple UPFs, or multiple DNNs, see the IPv6, Multi-UPF, and DNNs and IP pools guides.

Where to go next

For higher throughput options and the trade-offs between the stub, userspace, and DPDK datapaths, see UPF datapath. To wire metrics into a dashboard, see Observability.