Add L2TP LAC mode#387
Open
olivier-matz-6wind wants to merge 25 commits into
Open
Conversation
Add a new bbl_l2tp_client_s structure in bbl_l2tp.h, extend the access interface config with an L2TP client group id (used to associate sessions with a group of L2TP clients), and parse the new top-level l2tp-client JSON array into the global context. Add the related API documentation. The implementation is done in next commits. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Add a l2tp_tunnel_hostname() static helper that returns the tunnel's display name (currently server->host_name). Replace all direct l2tp_tunnel->server->host_name accesses with this wrapper. The indirection is needed because, in LAC mode, the hostname will come from client->name instead. The next commit wires that in. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Add the AVP sets required for the five control messages that the LAC
originates during tunnel and session setup:
SCCRQ Protocol Version, Framing/Bearer Capabilities, Firmware Revision,
Host Name (from l2tp_client->name), Vendor Name ("bngblaster"),
Assigned Tunnel ID, Receive Window Size, and optional Challenge.
SCCCN Challenge Response (when the LNS issued a challenge in its SCCRP).
ICRQ Assigned Session ID, Call Serial Number (equal to session ID),
and Bearer Type (analog). The optional calling-number (AVP 22)
and called-number (AVP 21) fields are encoded if present in the
configuration.
ICCN TX Connect Speed (fixed at 100 Mbit/s) and Framing Type
(synchronous).
StopCCN Already handled by the existing LNS-mode encoder; no change
needed beyond reusing it from the LAC path.
The tunnel struct gains an is_lac boolean and a client pointer (alongside
the existing server pointer) so that both modes can share the same
encoding infrastructure.
Link: https://datatracker.ietf.org/doc/html/rfc2661
Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Extend the existing L2TP code to support the LAC role: - Check the value of l2tp_tunnel->is_lac to direct towards client or server pointer. - New bbl_l2tp_client_connect(): allocates a tunnel, registers it with the client's tunnel list, and sends SCCRQ to initiate the control connection. - New bbl_l2tp_sccrp_rx(): handles SCCRP from the LNS, decodes AVPs, sends SCCCN and moves the tunnel to ESTABLISHED, then triggers the first session. The session creation is only there for testing purposes. It will be updated in a next commit when PPP will be wired on top of L2TP: the sessions will be created dynamically. - New bbl_l2tp_client_session_connect(): allocates an L2TP session and sends ICRQ to open a new session within an established tunnel. - New bbl_l2tp_icrp_rx(): handles ICRP from the LNS, sends ICCN, and mark the L2TP session as established. - bbl_l2tp_handler_rx(): dispatches SCCRP and ICRP to the new LAC handlers. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Include LAC tunnels in the existing statistics outputs: - bbl_l2tp_ctrl_tunnels(): iterates over each client's tunnel list and appends a JSON object (with client-name and server-address instead of server-name) to the l2tp-tunnels response. - bbl_interactive.c: show the "L2TP LNS/LAC Statistics" block whenever either server or client configuration is present. - bbl_stats.c: apply the same condition to both the stdout and JSON stats outputs. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Call bbl_l2tp_client_connect() for every configured l2tp-client entry during the main initialization sequence in bbl.c, after network interfaces have been resolved. This ensures tunnels exist before any PPP session tries to use them, which is required until session-driven tunnel creation is implemented (in a latter commit). Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Add ACCESS_TYPE_PPPOL2TP to the access_type_t enum and recognize "type": "pppol2tp" in an access interface section. Add an ACCESS_TYPE_PPPOL2TP case to the control-job switch to silence the compiler: session bring-up is wired in a subsequent commit. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Add a sessions_pppol2tp counter in bbl_ctx_ structure, which is incremented for each ACCESS_TYPE_PPPOL2TP session during initialization. Include it in the session-counters JSON response, in the live session summary, and in final statistics. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
When an l2tp-client entry specifies a client-address that differs from the network interface's own IP, BNGBlaster must answer ARP requests for that address so the LNS can resolve it and send L2TP/UDP packets back. In bbl_l2tp_client_connect(), if client_address is set and distinct from the interface address, add it to network_interface->secondary_ip_addresses. The list is walked first to avoid duplicate entries, since bbl_l2tp_client_connect() may be called more than once for the same client (e.g. after a tunnel is torn down and recreated). Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Wire ACCESS_TYPE_PPPOL2TP sessions into the L2TP tunnel lifecycle. In ctrl job, call bbl_l2tp_client_session_get_tunnel() and enqueue the session via bbl_l2tp_client_session_connect(). Sessions that arrive before the tunnel reaches ESTABLISHED are queued on a new pending_session_qhead in the tunnel; they are drained and connected once bbl_l2tp_sccrp_rx() completes the SCCCN handshake. Add bbl_l2tp_client_session_get_tunnel(): given a PPPoL2TP session, find a LAC tunnel associated with the session's configured l2tp-client group id. Make bbl_l2tp_client_session_connect() public: it now takes a bbl_session_s * to associate with the new L2TP session. Set first_session_tx when SCCRQ is sent from bbl_l2tp_client_connect(), so that the global setup_time metric is correctly calculated for PPPoL2TP sessions (analogous to PADI for PPPoE). Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Decouple several PPP Rx helpers from the Ethernet header so they can be called from the L2TP data-plane path where no bbl_ethernet_header_s is present: - bbl_access_rx_icmpv6(), bbl_access_rx_icmp(), bbl_access_rx_ipv4_mc(): add if(!eth) return guards before any eth-dereference. - bbl_access_rx_ipv4() / bbl_access_rx_ipv6(): replace eth->length with eth ? eth->length : ipv4->len for accounting byte counts. - Export bbl_ppp_rx() (new declaration in bbl_access.h) so the L2TP data-RX path can dispatch decoded PPP frames into the same per-session handler. - bbl_access_rx_established_pppoe(): replace the bbl_ethernet_header_s *eth parameter with struct timespec *timestamp, removing the last implicit dependency on an Ethernet frame in that code path. - bbl_tcp_ipv4_rx_session() / bbl_tcp_ipv6_rx_session(): drop the unused eth parameter; update all call-sites in bbl_access.c, bbl_tcp.c and bbl_dhcp.c. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
In bbl_l2tp_data_rx(), add a LAC branch before the LNS state machine. When the tunnel is in LAC mode, incoming PPP frames are forwarded to bbl_ppp_rx() on the associated bbl_session_s instead of being processed as LNS responses. This allows LCP/PAP/CHAP/IPCP/IP6CP messages sent back by the real LNS to drive the PPP client state machine on the LAC session. bbl_ppp_rx() already accepts eth=NULL for L2TP-originated frames. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Three changes to complete the session linkage for LAC mode: - Initialize PPP state (lcp/ipcp/ip6cp, mru, magic number) for PPPOL2TP sessions at creation time, mirroring the existing PPPoE model. Without this, all PPP states remained at BBL_PPP_DISABLED and the LCP RX handler would silently drop packets. - In bbl_l2tp_client_session_connect(), set l2tp_session->pppoe_session and session->l2tp_session so the two structures reference each other. This cross-link is required by both the TX and RX paths added in subsequent phases. - After sending ICCN and marking the L2TP session established, start the PPP state machine on the associated bbl_session_s, and queue BBL_SEND_LCP_REQUEST. Without this the session would remain in BBL_L2TP_WAIT indefinitely. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Extract the repeated Ethernet + PPPoE session header construction from all PPP control-packet encoders into a single bbl_ppp_tx() helper. This is the TX counterpart of the previously introduced bbl_ppp_rx() function. The following encoders are updated to call bbl_ppp_tx(): bbl_tx_encode_packet_pap_request bbl_tx_encode_packet_chap_response bbl_tx_encode_packet_lcp_request / _response bbl_tx_encode_packet_ipcp_request / _response bbl_tx_encode_packet_ip6cp_request / _response No functional change — bbl_ppp_tx() currently handles ACCESS_TYPE_PPPOE only, matching the previous inline code exactly. The indirection will allow additional transports (PPPoL2TP) to be added in one place. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Introduce three transmit helpers to eliminate the remaining duplicated Ethernet header construction in bbl_tx.c: bbl_ipoe_tx(session, dst_mac, eth_type, vlan_priority, payload) IPoE Ethernet framing for direct-IP sessions. bbl_pppoe_disc_tx(session, payload) PPPoE discovery framing (ETH_TYPE_PPPOE_DISCOVERY) used by PADI / PADR / PADT encoders. bbl_session_tx(session, dst_mac_ipoe, ppp_protocol, ip_payload) Transport-agnostic helper that dispatches to bbl_ppp_tx() for ACCESS_TYPE_PPPOE or bbl_ipoe_tx() for ACCESS_TYPE_IPOE, using ipoe_vlan_priority. Callers that need a non-default VLAN priority (like DHCPv6) use bbl_ppp_tx() / bbl_ipoe_tx() directly. The following encoders are updated: bbl_encode_padi / padr / padt -> bbl_pppoe_disc_tx() bbl_tx_encode_packet_igmp -> bbl_session_tx() bbl_tx_encode_packet_icmpv6_rs -> bbl_session_tx() bbl_tx_encode_packet_icmpv6_ns -> bbl_ipoe_tx() bbl_tx_encode_packet_dhcpv6_req -> bbl_ppp_tx() + bbl_ipoe_tx() bbl_tx_encode_packet_dhcp -> bbl_ipoe_tx() No functional change. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Make PPP control encoders to work over L2TP tunnels in bbl_ppp_tx() using the transport helpers introduced in the previous commits. Because all PPP encoders (LCP, IPCP, IP6CP, PAP, CHAP) already call bbl_ppp_tx(), they all gain PPPoL2TP support with no further changes. Add PROTOCOL_QUEUED in bbl_protocols to signal that a packet was enqueued internally and write_buf must not be sent. In bbl_tx(), it is converted to EMPTY so the IO layer does not attempt a zero-byte send. Return EMPTY early in bbl_ppp_tx() when l2tp_session is NULL, silently dropping the in-flight PPP packet: there is no tunnel left to carry it. It can happen when an LCP (or other PPP) packet was received while the L2TP session was about to be torn down. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Relax the restriction that prevents untagged VLANs on access interfaces when they share a physical interface with a network interface. For L2TP LAC emulation, the access sessions are logical and will be encapsulated within an L2TP tunnel on the network interface. Therefore, a physical VLAN tag on the access interface is not required to separate subscriber traffic from transport traffic at Layer 2. Modify bbl_network_interfaces_add() to skip the untagged check when the access interface type is PPPoL2TP. Store access_type in bbl_access_interface_s to make this check straightforward. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
The bngblaster LNS was originally designed to work with a real LAC that proxies LCP negotiation from the subscriber side. The LNS only handled LCP echo-request and term-request, not conf-req/conf-ack. When bngblaster acts as both LAC and LNS (e.g. for self-testing), the LAC generates PPP sessions from scratch and sends LCP conf-req to the LNS. Add LCP conf-req/conf-ack handling to the LNS side so full PPP negotiation can complete: - On conf-req: send conf-ack accepting all peer options, then send own conf-req with auth=PAP and a per-session magic number. - On conf-ack: advance lcp_state to OPENED (or send conf-req if not yet sent). Add lcp_state to bbl_l2tp_session_s to track the two-way handshake, following the same BBL_PPP_LOCAL_ACK / BBL_PPP_PEER_ACK / BBL_PPP_OPENED pattern already used for IPCP and IP6CP. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Handle graceful teardown of PPPoL2TP sessions on the LAC side: In bbl_session_clear(), send CDN to the LNS, then call bbl_l2tp_session_delete() to release the L2TP session. This is triggered both from the global teardown path (SIGTERM) and from control commands (session-stop). In bbl_l2tp_session_delete(), when an L2TP session is deleted in LAC mode (CDN received, tunnel torn down, or initiated by bbl_session_clear), also move the linked PPP session to BBL_TERMINATED so that timers are stopped and counters are updated correctly. If it is the last session of the tunnel, close the tunnel with a StopCCN packet. Finally, in bbl_session_update_state(), reset lcp/ipcp/ip6cp state for PPPoL2TP sessions, mirroring the existing PPPoE behaviour. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Add bbl_stream_build_pppol2tp_packet() for ACCESS_TYPE_PPPOL2TP sessions. This builds an upstream L2TP data packet (LAC -> LNS) with the LAC's network interface as outer IP source and the client's assigned IP as inner PPP source, mirroring the existing bbl_stream_build_l2tp_packet() which covers the downstream direction (LNS -> LAC). Wire it into bbl_stream_build_packet() and route PPPoL2TP upstream streams through tx_network_interface (the tunnel interface) instead of tx_access_interface in bbl_stream_session_add(), so the stream is enqueued to the correct IO handle. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Route PPPoL2TP sessions through bbl_ppp_tx() in bbl_session_tx() so that ICMPv6 RS packets are sent as L2TP data frames rather than falling into the IPoE Ethernet path. Extend the IP6CP state guard in bbl_tx_encode_packet_icmpv6_rs() and bbl_tx_encode_packet_dhcpv6_request() to cover ACCESS_TYPE_PPPOL2TP, and route the DHCPv6 send dispatch through bbl_ppp_tx() for PPPoL2TP (matching the existing PPPoE branch). ICMPv6 NS is IPoE-only (Ethernet-level address resolution) and requires no changes. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
During a test with a real LNS, it appeared that the first LCP packet was transmitted too early, even before the ICCN packet, marking the end of the L2TP session setup. This happens because the sending of the ICCN packet is delayed by L2TP_TX_WAIT_MS (10 ms). Fixing this was however not enough: the tested LNS was still not able to handle the LCP packet arriving few microseconds after the ICCN packet, because the creation of its control socket (AF_PPPOX) happened too late. While this is an LNS problem, it makes sense to workaround it in bngblaster by waiting the ICCN ack before starting the LCP layer. Instead of inserting the session into session_tx_qhead immediately, tag the ICCN queue entry with the pending PPP session pointer. When the ack for this packet is received, the session is added in the queue, starting the PPP state machine. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
The upstream IPv4/IPv6 validation in bbl_stream_session_add() requires a reachable network interface address, an explicit destination, or an A10NSP interface. PPPoL2TP sessions have none of these: their inner addresses come from PPP negotiation, and the packet builders already handle the missing destination with appropriate fallbacks. Bypass the strict checks for ACCESS_TYPE_PPPOL2TP so that session-traffic streams (e.g. "ipv4-pps": 1, "ipv6-pps": 1) can be created without error. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
bbl_access_rx_icmpv6() returns immediatly when eth==NULL. The LAC data receive path always passes eth=NULL (there is no Ethernet frame after L2TP decapsulation), so Router Advertisements from the LNS are silently dropped, icmpv6_ra_received stays false, and IPv6 session traffic is never started. Remove this guard. The places that actually dereference eth are: - IPoE RA path (server_mac copy, bbl_access_rx_established_ipoe, bbl_access_icmpv6_ns): already inside ACCESS_TYPE_IPOE branch, safe. - NS handler (bbl_access_icmpv6_na): guarded with individual if(eth). - Echo-request handler (bbl_access_icmpv6_echo_reply): guarded with if(eth). - NA / IPoE gateway path (server_mac copy): guarded with if(eth). Also extend the ACTIVATE_ENDPOINT(session->endpoint.ipv6) call in the RA prefix handler to cover ACCESS_TYPE_PPPOL2TP alongside ACCESS_TYPE_PPPOE, so that the IPv6 stream endpoint becomes active after the RA is received. Note: the two other !eth early-returns in bbl_access_rx_icmp() and bbl_access_rx_ipv4_mc() are left in place: ICMPv4 replies and multicast accounting both require eth and have no meaningful PPPoL2TP equivalent. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Add a new LAC section in L2TP documentation with examples. Signed-off-by: Olivier Matz <olivier.matz@6wind.com>
Member
|
Thank you very much for this contribution. Although I am currently occupied with other tasks, I will aim to find an opportunity as soon as possible to review and test your merge request. Given the size of these changes, I will require a bit more time to go through everything properly. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bngblaster already supports the LNS role, allowing it to terminate L2TP tunnels from a real LAC under test. This series adds the complementary LAC role: Bngblaster can now act as a LAC, originating PPP sessions encapsulated in L2TP tunnels toward a real LNS under test. This makes it possible to scale-test LNS functionality directly, with full PPP negotiation (LCP, PAP/CHAP, IPCP, IP6CP) running over the L2TP data plane.
Major changes:
The LAC and LNS roles can be tested locally using a veth pair. An example is included in the documentation (last commit).
We also validated this series with a custom LNS based on accel-ppp.
For the sake of transparency, note that this series was designed with the help of IA agents (mostly claude, but also gemini), this was by the way the opportunity for me to evaluate these tools. All the code was carefully reviewed.
Feel free to send any comment. I know that there are many patches and that reviewing them will be a hard work. If I can help by splitting or reorganizing the pull request, please let me know.
Closes #386