The forwarding platform
Line-rate IPoE forwarding, policed and accounted, on commodity x86.
A disaggregated BNG that turns raw subscriber packets into managed, authenticated, rate-limited, accounted sessions — and scales by adding nodes.
The system at a glance
Two planes, three cooperating processes
The BNG is not one program. Knowing which process does what is the foundation for operating and troubleshooting it. The data plane forwards; the control plane thinks; a small translator connects them.
The brain
Holds everything intelligent: the Session Manager state machine, the embedded DHCP server and address pools, the AAA client, the accounting engine, persistence, and the REST API you and your systems use.
The translator
A small process that connects the controller to VPP over gRPC. The controller makes a handful of high-level requests — "add this session," "change this rate," "give me the counters" — and the bridge turns them into VPP calls.
The muscle
Runs in userspace on DPDK, driving an access-facing and a core-facing NIC. Once a subscriber is set up, every one of their packets is handled here at line rate, with no involvement from the controller.
In steady state the controller sees none of a connected subscriber's traffic. Only two tiny classes of packet are ever punted up: DHCP from an unknown subscriber, and inbound RADIUS CoA. Everything else stays in VPP and is forwarded, policed, filtered, and counted — which is why the box forwards at line rate while the controller's CPU stays low with a full session table.
The data plane
Every packet, one session lookup
Because the access network is L3-routed, the BNG identifies each subscriber by the IP it assigned them — a /32 host address. A single lookup of the session pulls everything the data plane needs into one place: the anti-spoof reference, the captive flag, and the two rate meters. No separate firewall table, no separate rate-limit table.
- Deny by default — an unprovisioned address gets nothing
- Anti-spoof on every upstream packet — source IP must match the session
- Captive check — behind the walled garden, only portal traffic survives
- Shared edge-router MAC keeps the neighbour table small at 100k subs
Rate limiting
Green, yellow, red — a measurement, not just a decision
Every subscriber gets two rate limiters — upstream and downstream — implementing the RFC 2698 two-rate, three-colour policing model. Two numbers define each direction: the committed rate (CIR) they're guaranteed, and the peak rate (PIR) they may burst to.
| Colour | Meaning | What happens | On the bill? |
|---|---|---|---|
| Green | Within the committed rate (≤ CIR) | Forwarded, counted | Yes |
| Yellow | Bursting above CIR, within peak (≤ PIR) | Forwarded, counted | Yes |
| Red | Exceeds the peak rate (> PIR) | Dropped, counted | No (diagnostics only) |
A far richer per-subscriber picture
The data plane keeps a full set of counters split by colour, by direction, and by unit (bytes and packets). You can see not just how much a subscriber used, but how much was in-contract, how much was burst, and how much was shed at the cap — in each direction. A climbing red counter is both an upsell signal and an abuse signal.
Node-wide bands, one cheap query
Alongside the per-subscriber counters, the data plane keeps six node-wide band counters (green/yellow/red × up/down). You can read what the whole box is doing by QoS band without walking the session table — ideal for a high-frequency dashboard.
The control plane
The subscriber lifecycle, as a state machine
At the heart of the controller is the Session Manager — an explicit state machine that models the life of every subscriber. DHCP and authentication are a single coordinated act, which is why the BNG embeds its own DHCP server rather than relaying to an external one.
| State | Meaning |
|---|---|
| AUTHENTICATING | DHCP Discover arrived; asking AAA whether to admit. Transient. |
| OFFERING | AAA said yes; address offered, data plane pre-programmed. Transient. |
| CONNECTED | Fully online. Traffic flows at line rate; accounting and timers run. |
| CAPTIVE | Behind the walled garden — keeps its IP, reaches only the portal. |
| TERMINATING | Being torn down. Final accounting sent, then the session is removed. |
Onboarding: DHCP and AAA are one act
A Discover is punted and authenticated; on accept the controller resolves the plan, allocates an address, programs the data plane, and offers the lease; the subscriber's Request creates the lease, starts accounting, and arms timers. On an AAA reject, the subscriber isn't dropped — they're landed directly in CAPTIVE so they can reach the portal and sign up.
Teardown order is a correctness rule
Teardown reads the final counters from the data plane first, sends the closing accounting record second, and only then removes the session — stamping a standard terminate cause (User-Request, Idle-Timeout, Session-Timeout, Admin-Reset, Service-Unavailable) so your billing system knows why the session ended.
If AAA returns explicit rates, they're used; if it names a plan, the local plan table is used; if it returns neither, a default (Bronze) plan is applied and a warning is logged. A connected subscriber can also be re-rated in place — editing a plan walks the session table and updates every subscriber on it, with no re-DHCP and no interruption.
Captive portal & the prepaid model
Expiry demotes to captive — it doesn't disconnect
The captive portal is central to the target business: prepaid fixed-wireless. When a subscriber is in CAPTIVE state, the data plane permits exactly three things and silently drops the rest — DNS, HTTP, and HTTPS to the portal.
The redirect needs no in-path HTTP interception. A captive subscriber's lease hands out the portal's DNS server as their only resolver, and that server answers every lookup with the portal's address. Combined with the walled garden, any site they type resolves to — and can only reach — the portal.
Why demote, not disconnect? Keeping their IP and pushing them behind the portal means their next browser action is a "top up to continue" page — the exact conversion path a prepaid operator depends on. Only a Release, idle-out, or explicit disconnect actually removes the session.
Accounting
Usage becomes billable records
The BNG emits three kinds of record, and two details shape what the numbers mean.
Start
Sent when a subscriber connects, counters at zero.
Interim
Sent periodically during the session, carrying cumulative usage.
Stop
Sent at teardown, carrying final usage and the terminate cause.
Only green + yellow are billed
Red traffic was dropped at the policer — the subscriber never received it — so it's excluded from the billed octet counts. Red is still reported through the API for diagnostics; it just isn't on the bill.
Exact into the petabyte range
VPP counts in 64-bit; RADIUS octet fields are 32-bit, so each count is split into the octet field plus a "gigawords" field (units of 4 GB). Byte counts stay exact well past the petabyte range. Each session carries a stable accounting ID and echoes back any opaque Class value verbatim.
The counter substrate — per-colour, per-direction byte and packet counts refreshed from the data plane — is real and running today. Assembling those counts into RADIUS accounting packets on the wire is part of the deferred production RADIUS client.
Timers, scale & recovery
Restarts don't drop subscribers
Because the controller and data plane are separate processes, a restart of one must not disconnect subscribers carried by the other. Session state is durable — written to a local database on every state change — and the things you can't save (live timers, sockets) are rebuilt on startup from absolute expiry timestamps. A deadline already in the past just fires on the next pass.
- Controller restarts, data plane up: re-arm timers from saved deadlines — VPP keeps the live sessions
- Both fresh: rebuild state, then re-program every session so the two match
- Data plane diverged: tear down untrusted state, re-program from the controller's records
- Upgrades preserve live sessions; stale sessions past a recovery-age limit close cleanly (cause 15)
What bounds a node
Three time-based jobs run for every connected subscriber — interim accounting, session expiry, idle detection. Rather than hundreds of thousands of timers, the BNG folds them into a single batched loop that reads all due counters in one bulk request, writes state once, and emits the records that are due.
- ~100,000 concurrent subscribers on R360-class hardware
- The limit is memory / cache behaviour, not raw CPU
- Idle detection needs no timer — it's derived from the counters
- Growth is horizontal: add a node under the same config
Operating the BNG
What to watch — all on the management NIC
Everything here is available via the REST API and Prometheus. GET /bng/health rolls the box up to a single status — ok, degraded, or fault — using a deliberate rule.
fault
The data plane (VPP) is down, or all RADIUS endpoints are unreachable. Subscribers are affected; act now.
degraded
Exactly one of a redundant pair of RADIUS endpoints is unreachable. Still serving; investigate.
ok
Serving normally. A full address pool is reported but doesn't by itself drive the status to fault.
Two read paths for counters
The accounting view (GET /bng/sessions) is the billing-grade snapshot, refreshed on the interim cadence. The telemetry view (GET /bng/counters) is a pure read straight from the data plane — it takes no locks and triggers no save, so you can poll it as fast as you like for live dashboards.
The metrics that tell the story
Sessions by state, per-subscriber red counters, node-wide QoS bands, DHCP protocol counters, pool utilisation, and the controller's own save timing — your early warning that a node is approaching its ceiling. Structured JSON logs go to stdout; Prometheus is scraped from /metrics.
Scope & roadmap
Deliberate edges, and named next steps
Two different boundaries apply. Scope decisions are intentional edges of the product line — stable across releases. Caveats are specific to this Community-Edition Alpha, each with a defined path forward.
Product-line scope
- IPoE only — no PPPoE, no 802.1X/EAP
- L3-routed access — subscribers keyed by IP; no per-subscriber VLANs or QinQ
- Policing, not shaping — RFC 2698 drop-on-red; queue-based AQM is out of scope
- Pull-based notifications — Prometheus + Alertmanager replace SNMP traps
- No hard data-plane volume cap — RADIUS stays the authority for policy
On the roadmap
- Production RADIUS client — live sockets, retransmit/failover, CoA/Disconnect listener, accounting assembly, the eight QoS vendor attributes (PEN 65664)
- IPv6 — dual-stack / IPv6-only, DHCPv6, prefix delegation
- Per-worker session tables — a path toward ~400k–500k per node
Today the box runs a local AAA provider (open or gated) and operates standalone with no RADIUS server — exactly the Community-Edition free-tier on-ramp.
Put a line-rate BNG on commodity hardware
Download the Community Edition and stand up the forwarding stack standalone — then add QuantumTouch to operate it.