Skip to content

dev -> main - LAC, BGP TCP-AO/EVPN, AF_XDP and loopback IO, performance improvements - #405

Draft
GIC-de wants to merge 63 commits into
mainfrom
dev
Draft

GIC-de wants to merge 63 commits into
mainfrom
dev

Conversation

@GIC-de

@GIC-de GIC-de commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Summary

This merges the current dev branch into main (56 commits, 180 files changed). The main additions are:

  • L2TP LAC: a new PPPoL2TP access type, so BNG Blaster can act as an L2TPv2 LAC.
  • LNS: LCP keepalive, optional LNS LCP Conf-Request, and CHAP support.
  • BGP: TCP MD5 and TCP-AO authentication, plus the EVPN address family.
  • EVPN streams: labels resolved from type 1/2/5 routes, including EVPN VPWS.
  • New IO modes: AF_XDP and loopback.
  • Performance: major data path and startup improvements.

A full changelog is in the beta document.

New Features

  • L2TP Access Concentrator (LAC): new access type PPPoL2TP, configured in the new l2tp-client section and assigned with l2tp-client-group-id. It supports LCP, IPCP, IPv6CP and DHCPv6 over L2TP, and session-traffic streams. Original author: Oliver Matz.
  • LNS:
    • LCP keepalive (lcp-keepalive-interval, lcp-keepalive-retry).
    • New lcp-conf-request option, needed for LACs without proxy LCP.
    • CHAP (MD5) support.
  • BGP:
    • TCP MD5 (RFC 2385) and TCP-AO (hmac-sha-1-96, hmac-sha-256-128, aes-128-cmac-96).
    • EVPN address family.
    • learn-routes option.
    • New commands bgp-routes, bgp-routes-stats and bgp-evpn-routes.
    • RFC 4271 collision handling.
    • 4-octet AS numbers in bgp-sessions output.
  • EVPN streams: MPLS labels resolved from MAC/IP (type 2), IP prefix (type 5) and Ethernet A-D per EVI (type 1, VPWS) routes. Adds the vpws-* options and rx-control-word.
  • AF_XDP IO mode: includes RSS hash support and libxdp support. Build with -DBNGBLASTER_AF_XDP=on.
  • Loopback IO mode: connects two links of the same instance back to back through in-memory rings, configured with loopback-peer. It measures BNG Blaster's own IO performance and needs no special privileges.

Performance

  • Data path optimizations: higher throughput and instructions per cycle, fewer cache misses, less memory per stream.
  • DPDK 25.11.3 with locked memory pages.
  • AF_XDP TX descriptors are reserved and submitted per burst.
  • Stream setup with 10M streams takes about 4s instead of 31–80s (fixes a quadratic stream group lookup).
  • TXQ is allocated cache line aligned.

Fixes

  • OSPF: DR/BDR election now follows RFC 2328, the DBD duplicate check is fixed, and opaque LSAs are no longer sent to neighbors without the O-bit.
  • LCP: fixed CHAP option encoding. Peers dropped Conf-Requests that proposed CHAP.
  • L2TP:
    • Fixed an LNS crash on tunnel delete.
    • Fixed LAC teardown (CDN/StopCCN).
    • StopCCN result code is now 1 instead of the reserved 0.
  • Rates: average rates are computed from the actual elapsed time.
  • DPDK: fixed port stats, wrong-session stats and the flow sequence, and the sequence number is now returned on TX drop.
  • Streams: fixed stream-summary Prometheus export and stream prefetch. Gbps-upstream and count are now in the config schema.
  • Other:
    • Fixed UI rates and session-counters.
    • Fixed memory leaks in control socket JSON handling.
    • Config parser and schema fixes.
    • DHCP: messages with data after the END option are no longer rejected as protocol errors. Some DHCP servers send such Offers, and IPoE sessions behind them didn't come up (contributed by smpettit, fix DHCP decode of data after the END option #402).
    • LAG: stream packets received on a LAG member with RX threads are now handled in the RX thread. Before, jumbo frames over 4074 bytes on LAG members were dropped without a counter, so streams looked like total loss in the device under test (contributed by smpettit, fix stream RX in RX threads on LAG member interfaces #403).
    • BGP: hardened AS_PATH formatting in route output (bgp-routes) against buffer overflow.
      Interface stats: rx-polled in the JSON stats reported the RX byte count instead of the polled counter.

Upgrade Notes

  • Binary paths: bngblaster, bngblaster-cli and lspgen are now installed to /usr/bin. The deb post-install script creates compatibility symlinks in /usr/sbin.
  • Install contents: bash completion and the JSON config schema are now installed.
  • flow-id: the internal flow-id is now 32-bit. The BBL header stays wire compatible with older versions, and the Wireshark dissector is updated.
  • DPDK: DPDK builds require DPDK 25.11.3.
  • mlockall: mlockall is now used only with DPDK and AF_XDP.
  • Command output: command output now includes stream type and sub-type.

Testing

New integration tests run over veth pairs on every PR to main and nightly:

  • BGP against GoBGP: unicast, connection collision, MD5, TCP-AO and EVPN.
  • BNG Blaster LAC against BNG Blaster LNS.
  • IS-IS and OSPFv2 against FRR.
  • OSPFv2/v3 against BIRD.
  • All quickstart guide examples.

🤖 Generated with Claude Code

GIC-de and others added 30 commits September 3, 2026 14:03
+ increased troughput
+ increased instructions per cycle
+ reduced cache misses
+ reduced stream memory consumtion
+ upgrade to 25.11.3
+ performance optimisations
Introduces a new PPPoL2TP access type that lets BNG Blaster act as an
L2TPv2 LAC, tunneling PPPoE/PPP client sessions to an LNS: L2TP client
configuration and tunnel/session lifecycle, PPP session bring-up over
L2TP (including LCP/IPCP/IPv6CP and DHCPv6), upstream/downstream data
forwarding through the tunnel, and session-traffic streams for
PPPoL2TP sessions. Also adds LNS-side LCP server handling and related
stats, docs, and config schema updates.

Special thanks to Oliver Matz, who is the original author of this feature.

Original-Author: Oliver Matz
Co-Authored-By: Olivier Matz <olivier.matz@6wind.com>
Addresses the remaining code-quality findings from review of the LAC
feature: factor tunnel-level config (receive-window, max-retry,
congestion-mode, data-*, control-tos, hello-interval, lcp-padding,
secret) into a shared bbl_l2tp_tunnel_config_s embedded in both the
LNS and LAC config structs, with a config pointer on the tunnel object
so state-machine code no longer branches on is_lac to read them.
Also:
- factor the repeated JSON parsing of those fields for l2tp-server and
  l2tp-client into one helper
- factor the repeated MD5 challenge/response digest computation into
  bbl_l2tp_challenge_digest()
- collapse the LAC tunnel-selection loop from three full passes to two
- rename bbl_l2tp_session_s.pppoe_session to session, since it holds
  the LAC's own PPP session (not a PPPoE session) in LAC mode
- account PPPoL2TP received bytes using the PPP protocol field instead
  of the (absent) ethernet header length
- add a comment to the LCP CONF_ACK branch mirroring its sibling

No behavior change intended beyond the accounting byte count fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The l2tp-client-group-id row added while resolving the LAC merge was
1-2 characters narrower than the table's other rows.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GIC-de and others added 21 commits September 26, 2026 19:17
- BNG Blaster BGP against GoBGP: unicast, connection collision,
  MD5, TCP-AO and EVPN
- BNG Blaster LAC against BNG Blaster LNS
- all examples of the quickstart guide
- GitHub workflow for PRs to main and nightly runs
- ignore Python cache files

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow RFC 2328 for broadcast interfaces: stay in Waiting until the
wait timer expires or BackupSeen, trigger NeighborChange on 2-Way
transitions and run AdjOK? after each election. Fix the DBD duplicate
check and do not send opaque LSAs to neighbors without O-bit.

Add IS-IS and OSPFv2 integration tests against FRR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test OSPFv2 and OSPFv3 adjacency, DR/BDR election (including an
existing DR), database synchronization and OSPFv2 authentication
against BIRD 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FRR daemons exit on start if the user is not member of the frrvty
group, which root is not with the packaged FRR on CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mgmtd binds its frontend and backend sockets in /var/run/frr regardless
of --vty_socket and does not remove them on exit, so stale sockets from
the system FRR or previous tests made it fail to start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FRR daemons drop CAP_DAC_OVERRIDE, so they can't access the test
directory if it is below a 0750 home directory (e.g. /home/runner on
CI), and mgmtd exits as it can't create its pid file. Logs and config
are still kept in the test directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The adjacency can reach Full before the next hello from the neighbor
triggers the NeighborChange that settles the DR/BDR roles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Encode the 32-bit flow-id at offset 24 followed by 32 reserved bits
instead of reserved bits followed by the flow-id. This matches the
former 64-bit little-endian flow-id, so streams keep working between
old and new BNG Blaster versions and existing decoders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The StopCCN sent in reply to a received StopCCN and the StopCCN sent
by the LAC after the last session was closed did not set a result
code, which resulted in the reserved result code 0 on the wire. Send
result code 1 (general request to clear control connection) instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lspgen moved from /usr/sbin to /usr/bin together with bngblaster and
bngblaster-cli, but the postinst/postrm scripts only handled the
latter two, breaking scripts that call /usr/sbin/lspgen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parser only accepts DRAFT-LIHAWI-00 and DRAFT-LIHAWI-04, not the
documented short values 00 and 04.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CHAP algorithm byte was written without moving the write buffer,
so the following option overwrote it and the LCP length exceeded the
packet, causing the peer to drop LCP Conf-Requests proposing CHAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add l2tp-server option lcp-conf-request (default true) to control if
the LNS sends its own LCP Conf-Request after receiving a Conf-Request
from the client, which is required for LACs without proxy LCP.

The LNS still proposes PAP, but now switches to CHAP if the client
responds with a Conf-Nak for CHAP (MD5) and sends the CHAP challenge
once LCP is opened. Before, CHAP clients never completed LCP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bbl_txq_s contains cache line aligned members to prevent false
sharing between producer and consumer thread, but was allocated
with calloc, which only guarantees 16 byte alignment. Add
bbl_txq_alloc using aligned_alloc and check for allocation
failures. Add missing NULL check for aligned stream allocation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stream attribute Gbps-upstream was parsed but missing in the
config schema validation, which rejected it as invalid attribute.
Add missing stream count to the JSON schema and document the
K/M/G prefixes for bps-upstream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reserve TX ring slots for the whole burst at once and publish them
with a single producer update per round instead of per packet.
Submitting per packet lets NAPI (running on another core) consume
the ring in tiny chunks and bounces the producer cache line for
every packet, limiting TX throughput.

The burst is capped at the number of free TX frames, so the
reservation can not fail since the TX ring has as many slots as
there are TX frames. Unused reserved slots are released again at
the end of the round. no_buffer is now counted once per round
limited by free TX frames.
bbl_compute_avg_rate() assumed exactly one sample per second. If a rate
job ran late, the rate was too high. During warm-up, and for the final
partial sample in bbl_stream_final(), it was too low.

Pass the timestamp and store the elapsed time for each sample. The
average is now the sum of the counter deltas divided by the elapsed time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full stream groups were never removed from the group list, so every
new group required walking all full groups. With 10M streams this
took most of the startup time (31s for 1000 sessions with 10k flows
each, 80s for 10M raw streams).

Remove groups from the list once full, so the list holds at most one
group per PPS. Stream setup now takes about 4s for both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new IO mode loopback connects two links of the same instance back
to back via in-memory rings, without any kernel interface, driver or
NIC in between. This allows to measure the IO performance of the BNG
Blaster itself and runs without special privileges.

Each TX IO handle owns a lock-free single-producer/single-consumer
ring to the peer link, read by exactly one RX IO handle (round-robin
if the peer has fewer RX threads). Indices are published once per
burst and the slots are backed by pre-faulted huge pages.

The peer is configured per link with loopback-peer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@GIC-de GIC-de self-assigned this Oct 3, 2026
Comment thread code/bngblaster/src/bgp/bgp_rib.c Fixed
GIC-de and others added 7 commits October 3, 2026 12:56
bgp_rib_format_as_path() added the snprintf return value to the buffer
index without checking it (CodeQL). This could not overflow as the
AS_PATH is bounded by the maximum message size, but relied on checks
done in bgp_rib_attr_build().

Check the snprintf return value, keep room for the closing bracket and
NUL, close the bracket if truncated and check the segment length to
prevent reading beyond the AS_PATH.

Replace constant numbers in bgp_rib.c with defines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
decode_dhcp() read a length byte for the END option before handling it,
so a DHCP message with non-zero bytes after END was rejected as a decode
error whenever the byte following END exceeded the remaining length.
RFC 2132 section 3.2 defines END as a single octet that marks the end of
valid information, so stop parsing at END.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build the test packet with ether_header, iphdr, udphdr and dhcp_header
instead of raw buffer offsets and use the existing defines for header
lengths, ethertype and BOOTREPLY. Add missing newline at end of file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stream packets received on a LAG member with RX threads enabled were
dropped without being counted if the frame was larger than 4074 bytes,
so the stream was reported as never received even though the frames
arrived on the interface. This affects, for example, downstream jumbo
streams to access sessions on a LAG, which can be configured since
jumbo-frames support was added (#385).

bbl_rx_thread() looked up the network and access interfaces on the
receiving member, but these are bound to the LAG interface. The lookup
failed, so every stream packet was redirected to the main thread
through the TXQ, whose slots hold at most BBL_TXQ_BUFFER_LEN (4074)
bytes; larger frames were rejected by redirect() without a counter.

Resolve a LAG member to its LAG interface, as bbl_rx_handler() already
does on the main thread. Stream packets on LAG members are then handled
in the RX threads instead of all going through the main thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RX threads redirect all packets not handled in the thread to the main
thread via the TXQ. Packets longer than BBL_TXQ_BUFFER_LEN (4074 bytes)
were dropped without being counted, and so were packets dropped because
the TXQ was full, except for packet_mmap, which waits for the main
thread instead.

Count packets too long to redirect as RX to-long and packets dropped
due to a full TXQ as RX dropped, and add both to the interface stats
output (rx-to-long, rx-dropped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rx-polled counter of the interface stats JSON reported the RX bytes
instead of the polled counter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants