5. Results

5.1 Subscriber onboarding — run-699-cpe.json

What it measures. Every simulated CPE performs a real DHCP DORA exchange
through the relay. The check is how many reach a bound session, and how fast.

Why it matters. This is the operation an ISP performs most: a subscriber
powering on. A platform that onboards slowly recovers slowly from an outage.

Target / bound 5,000 / 5,000
Success rate 100%
Rate 398.7 bound per second
Elapsed 12.54 s
DORA latency p50 1,389 ms
DORA latency p95 9,279 ms
Minimum / maximum 211.7 ms / 10,514 ms
Retransmits 1,469

Read the latency figures as queueing delay, not service time. The whole
fleet is launched at once, so a subscriber's measured DORA time includes waiting
behind the rest of the storm. The p50 of 1.4 s does not mean a single
subscriber waits 1.4 s on an idle box — the minimum of 211 ms is closer to that.
The 1,469 retransmits are the DHCP protocol doing its job under a burst; no
subscriber was lost.

5.2 In-rush, cold versus warm — run-699-dora-inrush.json

What it measures. A 900/s requested storm against a cold box, then the same
storm again once caches and sessions are warm.

Why it matters. It separates steady-state capability from first-contact
behaviour — the difference between a planned migration and a recovery.

Phase Completion rate limited_by
Cold 391.8/s none
Warm 405.5/s none

In-rush: cold and warm phases, with the cold-versus-warm comparison

limited_by: none means neither the platform nor the generators saturated at
this offered level, so these are shape results rather than a ceiling. The warm
phase being ~3.5% faster than cold is the expected direction and small.

5.3 Capacity and cap enforcement

The licence granted bng.max_sessions = 25000; the working set was 5,000. The
capacity suite verifies the cap is read from the grant and enforced at its
boundary, with sessions beyond it moved to a captive state rather than dropped.
All capacity checks passed. No figure — this is a pass/fail surface, not a
measurement.

5.4 Churn and reconciliation — run-699-churn.json

What it measures. Half the fleet is released and re-established repeatedly
for 300 s, while client-side and controller-side session counts are compared.

Why it matters. Churn is normal ISP life — evenings, link flaps, CPE
reboots. The question is whether the platform's own view of its sessions stays
truthful under it.

Integrity 1.0
Converged yes, in 25.1 s
Maximum divergence 391 sessions
Leaked sessions none (empty list, enumerated)
Orphaned sessions none (empty list, enumerated)

Churn-longevity: connected sessions and controller/client divergence over the churn window

The leak and orphan results are enumerated, not derived. This matters: a
leak count computed as total − final_bound is an arithmetic identity and
proves nothing. These are empty lists of specific sessions, which is evidence.

5.5 Lease churn — run-699-leasechurn.json

What it measures. A cohort is held stable while another half churns around
it, checking the stable cohort is neither disturbed nor lost from the view.

Cohort size 2,500
Stable cohort 2,500
Stable sessions bound correct throughout
Stable sessions in controller view correct throughout
Lease renewal exercised no

Lease-time and partial-churn cohorts

Two caveats, both important. First, the stable cohort was genuinely 2,500 —
worth stating because a stable cohort of zero would make this check pass
while testing nothing. Second, lease_renewal_exercised is false: the lease
time was not shortened for this run, so no lease actually reached renewal inside
the window. The cohort's continuity is real; lease renewal under churn is not
covered by this run.

5.6 Traffic forwarding and QoS — run-699-cpe.json

What it measures. Two distinct workloads, which must not be conflated.
A bulk throughput phase drives downstream HTTP reads to find aggregate
capacity; a separate shaping phase drives per-subscriber traffic through the
policer and compares each subscriber's achieved rate against its plan.

Bulk throughput (downstream)

Downstream mean 11,710 Mbps (11.7 Gbps)
Downstream peak 16,505 Mbps (16.5 Gbps)
Transferred 30.17 GB in 20.6 s
Upstream negligible — this phase is a downstream-dominant HTTP read

Throughput over time, downstream and upstream

Per-subscriber shaping

480 subscribers were sampled and measured in both directions against the
Bronze plan (25 Mbps down / 12 Mbps up ceilings):

Within contract 480 of 480
Silent (moved no traffic) 0
Unresolved 0
Downstream median 22.14 Mbps against a 25 Mbps ceiling
Upstream median 10.93 Mbps against a 12 Mbps ceiling

QoS shaping — every measured subscriber against its plan ceiling

The silent: 0 and unresolved: 0 counts carry weight: a subscriber that moved
no traffic is reported in its own category rather than counted as compliant, so
480 of 480 is 480 real measurements rather than a pass inflated by silence.

Per-subscriber downstream rate, scatter
Per-subscriber downstream rate, distribution
Per-subscriber upstream rate, scatter
Per-subscriber upstream rate, distribution

The policer, actively marking

Both directions carried real load through the policer, and the colour split is
the result worth publishing because it shows the policer doing work rather than
merely passing traffic. Figures are the totals the panels themselves report.

Downstream — 32.90 GB over 23.5 s (≈11.2 Gbps mean, ~15 Gbps peak):

Colour Volume Share
Green (conforming) 23.964 GB 72.8%
Yellow (marked out-of-contract) 5.927 GB 18.0%
Red (dropped) 3.010 GB / 2,007,004 pkts 9.1%

Downstream policer bands — green conforming, yellow marked, red dropped

Upstream — 30.72 GB over 29.7 s (≈8.3 Gbps mean, ~12.3 Gbps peak):

Colour Volume Share
Green (conforming) 19.884 GB 64.7%
Yellow (remarked) 10.336 GB 33.6%
Red (dropped) 0.502 GB / 334,895 pkts 1.6%

Upstream policer bands — green conforming, yellow remarked, red dropped

Roughly a quarter of offered traffic downstream, and a third upstream, was
deliberately over-contract, and the policer classified every bit of it. That is
the enforcement path working end to end under multi-gigabit load in both
directions, not an idle-path check.

What this run does not establish. Neither direction is a search for a
forwarding ceiling — these are the shaping workload's offered rates, chosen to
exercise the policer. §5.7 is where forwarding limits are measured.

5.7 Forwarding performance and core scaling — run-699-coreperf.json

What it measures. A matrix of worker counts (1–5) against packet sizes
(64B, 128B, 512B, 1518B, plus a TCP profile), each cell recording throughput,
per-core efficiency, thread balance, and what limited it.

This is the section most easily misread, so start with the taxonomy. The
matrix covers 20 UDP cells — five worker counts against four frame sizes —
and sorts each into one of three kinds, which are not interchangeable:

Kind Cells What it means
BNG ceiling 4 the box dropped packets — a real forwarding limit
Line rate, zero loss 13 the box forwarded everything offered, at 90% of link, with zero loss
Floor — rig under-offered 3 the box kept up but the rig never filled the link

Performance scaling matrix, all 20 UDP cells

Only the 4 ceiling cells are maximum-throughput numbers. But the 13
line-rate cells are not merely "our generators ran out" either — they are
legitimate wire-speed results: the box forwarded every packet that reached it
while the link was essentially full. At 4 workers and 128 B or larger,
saturating this box would need 26 to 270 Gbps against the rig's 20, so those
cells are the strongest reading this rig can produce rather than a shortfall.
Only the 3 floor cells are genuine under-offers.

The run's JSON additionally carries 4 TCP-profile cells, which the matrix does
not render; all four are generator-limited and no figure in this report is drawn
from them.

Only these four cells measure the platform:

Workers Packet size Throughput Per core Cycles/packet
1 64B 6.78 Mpps 3.25 Gbps ~330
1 128B 6.17 Mpps 6.12 Gbps ~344
2 64B 13.57 Mpps 3.26 Gbps ~330
2 128B 12.38 Mpps 6.14 Gbps ~345

1 worker, UDP 64B — limited by the platform
2 workers, UDP 64B — limited by the platform
1 worker, UDP 128B — limited by the platform
2 workers, UDP 128B — limited by the platform

Scaling from one worker to two is near-linear — 6.78 → 13.57 Mpps at 64B is
2.00×, and per-core throughput is unchanged (3.25 → 3.26 Gbps). That is the
useful result in this section: the forwarding path scales with cores rather than
contending.

Per-packet cost is measured, not derived. clocks_per_vector × nodes_per_packet gives ~330 cycles per packet at 64B. This is the most durable
efficiency figure in the report because it survives hardware comparison: on a
2.0 GHz core, 330 cycles is ~165 ns of CPU per packet.

Thread balance is 0.9997–1.0 across the multi-worker cells, confirming the
one-queue-per-worker placement in §3.3 distributes evenly.

The generator ceiling is demonstrated, not merely asserted. A
platform-limited number rises when workers are added. The generator-limited
cells do not move at all — they are byte-identical across every worker count:

Packet size 1 worker 2 3 4 5
512B 4.229 Mpps 4.229 4.229 4.229 4.229
1518B 1.463 Mpps 1.463 1.463 1.463 1.463
128B bng bng 15.2 15.2 15.2
64B bng bng rss 21.93 21.93

Five different dataplane configurations returning one identical figure is the
signature of a constraint that is not the dataplane. The rig's ceilings are
~20 Gbps aggregate, 15.2 Mpps at 128B and 21.93 Mpps at 64B.

For the 512 B and 1518 B rows this is line rate with zero loss: the box
forwarded every packet offered while the link was essentially full. Saturating
it at those frame sizes would need far more than the rig's 20 Gbps, so the cells
are the strongest reading available here, not a shortfall. The 64 B cells at
four and five workers are the genuine floors — there the rig simply under-offered.

Beyond two workers the 64 B cells stop finding a platform ceiling. At three
workers that cell becomes RSS-limited; at four and five the rig under-offers:

3 workers, UDP 64B — limited by RSS
5 workers, UDP 64B — limited by the generators

5.8 Local-AAA performance — run-699-aaa.json

What it measures. Four experiments against the platform's own subscriber
store, each carrying its own limited_by.

Local-AAA experiments

A — authentication latency. limited_by: none. Cost is flat in the
resident base
: 0.14 µs per resident subscriber with r² = 0.10, i.e. no
meaningful slope. Across the measured sweep — bases of 1,000, 3,000, 5,000 and
10,000 — p50 DORA latency sits between 297.8 ms and 301.9 ms. Authenticating
against the largest base measured costs what it costs against the smallest. The
sweep does not extend to the 25,000 licensed cap.

B — single-subscriber provisioning. limited_by: serial-by-design — one
operation in flight on purpose. This is a latency measurement and must not be
read as a provisioning throughput figure.

C — bulk base build. limited_by: controller, so publishable with the
component named. Build time is linear in the resident base (O(N)): 0.05 µs
per resident subscriber, r² = 0.92. Adding one subscriber costs more as the base
grows — relevant when planning bulk imports.

D — local-AAA in-rush. 5,000 of 5,000 bound, completion 100% at
381.3/s, with offered_headroom = 3.07: work arrived 3.1× faster than the
box drained it, so this rate is the platform's own.

The cost of local-AAA admission. Comparing D against the open-admission
cold in-rush in §5.2 — the same storm, differing only in admission — gives
381.3/s against 391.8/s. Local-AAA admission costs 2.7%. This is the only
clean measurement of it in the suite.

5.9 Unauthenticated admission — run-699-unauth.json

What it measures. A layered probe: ICMP, TCP and HTTP from identities at
varying degrees of legitimacy, verifying the walled garden admits what it should
and no more.

Layered unauthenticated-admission verdicts

The baseline identity forwarded correctly at all three layers (ICMP echo-reply,
TCP SYN-ACK, HTTP 200), establishing that the probe itself works before the
restrictive cases are read. All admission checks passed.

5.10 Management surface

The console, licence API, security and authentication suites all passed, with
two exceptions recorded in §6. These cover API key handling, role enforcement,
session security, licence activation and the console's own rendering.