From 91144eecb9563823e6f61426ad757bc20e4aa069 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Mon, 16 Mar 2026 14:08:22 +0100 Subject: [PATCH 01/25] LAC: parse L2TP client configuration 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 --- code/bngblaster/src/bbl_config.c | 147 +++++++++++++++++- code/bngblaster/src/bbl_config.h | 1 + code/bngblaster/src/bbl_ctx.h | 3 + code/bngblaster/src/bbl_l2tp.h | 32 ++++ docsrc/sources/configuration/index.rst | 4 + .../configuration/interfaces_access.rst | 6 + docsrc/sources/configuration/lac.rst | 69 ++++++++ schemas/bngblaster-config.json | 118 ++++++++++++++ 8 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 docsrc/sources/configuration/lac.rst diff --git a/code/bngblaster/src/bbl_config.c b/code/bngblaster/src/bbl_config.c index a2445a65..ce009057 100644 --- a/code/bngblaster/src/bbl_config.c +++ b/code/bngblaster/src/bbl_config.c @@ -1066,7 +1066,7 @@ json_parse_access_interface(json_t *access_interface, bbl_access_config_s *acces "http-client-group-id", "icmp-client-group-id", "cfm-cc", "cfm-level", "cfm-interval", "cfm-md-name", "cfm-md-name-format", "cfm-ma-id", "cfm-ma-name", "cfm-ma-name-format", "cfm-seq", - "cfm-vlan-priority" + "cfm-vlan-priority", "l2tp-client-group-id" }; if(!schema_validate(access_interface, "access", schema, sizeof(schema)/sizeof(schema[0]))) { @@ -1449,6 +1449,11 @@ json_parse_access_interface(json_t *access_interface, bbl_access_config_s *acces } } + JSON_OBJ_GET_NUMBER(access_interface, value, "access", "l2tp-client-group-id", 0, 65535); + if(value) { + access_config->l2tp_client_group_id = json_number_value(value); + } + if(access_config->access_type == ACCESS_TYPE_PPPOE) { /* Disable IPv4 on PPPoE if IPCP is disabled. */ if(!access_config->ipcp_enable) { @@ -3281,6 +3286,7 @@ json_parse_config(json_t *root) bbl_access_line_profile_s *access_line_profile = NULL; bbl_l2tp_server_s *l2tp_server = NULL; + bbl_l2tp_client_s *l2tp_client = NULL; bbl_lag_config_s *lag_config = NULL; bbl_link_config_s *link_config = NULL; @@ -3311,7 +3317,7 @@ json_parse_config(json_t *root) "isis", "ospf", "bgp", "bgp-raw-update-files", "ldp", "ldp-raw-update-files", - "l2tp-server", "icmp-client", + "l2tp-server", "l2tp-client", "icmp-client", "http-client", "http-server", "arp-client" }; @@ -4528,6 +4534,143 @@ json_parse_config(json_t *root) fprintf(stderr, "JSON config error: List expected in L2TP server configuration but dictionary found\n"); } + /* L2TP Client Configuration (LAC) */ + section = json_object_get(root, "l2tp-client"); + if(json_is_array(section)) { + if(!g_ctx->config.network_config) { + fprintf(stderr, "JSON config error: Failed to add L2TP client because of missing or incomplete network interface config\n"); + return false; + } + size = json_array_size(section); + for(i = 0; i < size; i++) { + sub = json_array_get(section, i); + + const char *schema[] = { + "group-id", "name", "secret", "server-address", "client-address", + "network-interface", "receive-window-size", "max-retry", "congestion-mode", + "data-control-priority", "data-length", "data-offset", "control-tos", + "data-control-tos", "hello-interval", "lcp-padding", "calling-number", + "called-number" + }; + if(!schema_validate(sub, "l2tp-client", schema, sizeof(schema)/sizeof(schema[0]))) { + return false; + } + + if(!l2tp_client) { + g_ctx->config.l2tp_client = calloc(1, sizeof(bbl_l2tp_client_s)); + l2tp_client = g_ctx->config.l2tp_client; + } else { + l2tp_client->next = calloc(1, sizeof(bbl_l2tp_client_s)); + l2tp_client = l2tp_client->next; + } + JSON_OBJ_GET_NUMBER(sub, value, "l2tp-client", "group-id", 1, 65535); + if(value) { + l2tp_client->group_id = json_number_value(value); + } else { + fprintf(stderr, "JSON config error: Missing value for l2tp-client->group-id\n"); + return false; + } + if(json_unpack(sub, "{s:s}", "name", &s) == 0) { + l2tp_client->name = strdup(s); + } else { + fprintf(stderr, "JSON config error: Missing value for l2tp-client->name\n"); + return false; + } + if(json_unpack(sub, "{s:s}", "network-interface", &s) == 0) { + l2tp_client->network_interface = strdup(s); + } else { + fprintf(stderr, "JSON config error: Missing value for l2tp-client->network-interface\n"); + return false; + } + if(json_unpack(sub, "{s:s}", "client-address", &s) == 0) { + if(!inet_pton(AF_INET, s, &ipv4)) { + fprintf(stderr, "JSON config error: Invalid value for l2tp-client->client-address\n"); + return false; + } + l2tp_client->client_address = ipv4; + } + if(json_unpack(sub, "{s:s}", "secret", &s) == 0) { + l2tp_client->secret = strdup(s); + } + if(json_unpack(sub, "{s:s}", "server-address", &s) == 0) { + if(!inet_pton(AF_INET, s, &ipv4)) { + fprintf(stderr, "JSON config error: Invalid value for l2tp-client->server-address\n"); + return false; + } + l2tp_client->server_ip = ipv4; + CIRCLEQ_INIT(&l2tp_client->tunnel_qhead); + } else { + fprintf(stderr, "JSON config error: Missing value for l2tp-client->server-address\n"); + return false; + } + JSON_OBJ_GET_NUMBER(sub, value, "l2tp-client", "receive-window-size", 1, 65535); + if(value) { + l2tp_client->receive_window = json_number_value(value); + } else { + l2tp_client->receive_window = 16; + } + JSON_OBJ_GET_NUMBER(sub, value, "l2tp-client", "max-retry", 1, 65535); + if(value) { + l2tp_client->max_retry = json_number_value(value); + } else { + l2tp_client->max_retry = 5; + } + if(json_unpack(sub, "{s:s}", "congestion-mode", &s) == 0) { + if(strcmp(s, "default") == 0) { + l2tp_client->congestion_mode = BBL_L2TP_CONGESTION_DEFAULT; + } else if(strcmp(s, "slow") == 0) { + l2tp_client->congestion_mode = BBL_L2TP_CONGESTION_SLOW; + } else if(strcmp(s, "aggressive") == 0) { + l2tp_client->congestion_mode = BBL_L2TP_CONGESTION_AGGRESSIVE; + } else { + fprintf(stderr, "JSON config error: Invalid value for l2tp-client->congestion-mode\n"); + return false; + } + } else { + l2tp_client->congestion_mode = BBL_L2TP_CONGESTION_DEFAULT; + } + JSON_OBJ_GET_BOOL(sub, value, "l2tp-client", "data-control-priority"); + if(value) { + l2tp_client->data_control_priority = json_boolean_value(value); + } + JSON_OBJ_GET_BOOL(sub, value, "l2tp-client", "data-length"); + if(value) { + l2tp_client->data_length = json_boolean_value(value); + } + JSON_OBJ_GET_BOOL(sub, value, "l2tp-client", "data-offset"); + if(value) { + l2tp_client->data_offset = json_boolean_value(value); + } + + JSON_OBJ_GET_NUMBER(sub, value, "l2tp-client", "control-tos", 0, 255); + if(value) { + l2tp_client->control_tos = json_number_value(value); + } + JSON_OBJ_GET_NUMBER(sub, value, "l2tp-client", "data-control-tos", 0, 255); + if(value) { + l2tp_client->data_control_tos = json_number_value(value); + } + JSON_OBJ_GET_NUMBER(sub, value, "l2tp-client", "hello-interval", 0, 65535); + if(value) { + l2tp_client->hello_interval = json_number_value(value); + } else { + l2tp_client->hello_interval = 30; + } + JSON_OBJ_GET_NUMBER(sub, value, "l2tp-client", "lcp-padding", 0, 65535); + if(value) { + l2tp_client->lcp_padding = json_number_value(value); + } + if(json_unpack(sub, "{s:s}", "calling-number", &s) == 0) { + l2tp_client->calling_number = strdup(s); + } + if(json_unpack(sub, "{s:s}", "called-number", &s) == 0) { + l2tp_client->called_number = strdup(s); + } + } + } else if(json_is_object(section)) { + fprintf(stderr, "JSON config error: List expected in L2TP client configuration but dictionary found\n"); + } + /* ARP Client Configuration */ sub = json_object_get(root, "arp-client"); if(json_is_array(sub)) { diff --git a/code/bngblaster/src/bbl_config.h b/code/bngblaster/src/bbl_config.h index d85f1db6..3ccdde22 100644 --- a/code/bngblaster/src/bbl_config.h +++ b/code/bngblaster/src/bbl_config.h @@ -51,6 +51,7 @@ typedef struct bbl_access_config_ access_type_t access_type; /* pppoe or ipoe */ vlan_mode_t vlan_mode; /* 1:1 (default) or N:1 */ + uint16_t l2tp_client_group_id; uint16_t stream_group_id; uint16_t session_group_id; uint16_t http_client_group_id; diff --git a/code/bngblaster/src/bbl_ctx.h b/code/bngblaster/src/bbl_ctx.h index 0ad090e2..6037bed3 100644 --- a/code/bngblaster/src/bbl_ctx.h +++ b/code/bngblaster/src/bbl_ctx.h @@ -374,6 +374,9 @@ typedef struct bbl_ctx_ /* L2TP Server Config (LNS) */ bbl_l2tp_server_s *l2tp_server; + + /* L2TP Client Config (LAC) */ + struct bbl_l2tp_client_ *l2tp_client; } config; } bbl_ctx_s; diff --git a/code/bngblaster/src/bbl_l2tp.h b/code/bngblaster/src/bbl_l2tp.h index f192c40f..7a92aac4 100644 --- a/code/bngblaster/src/bbl_l2tp.h +++ b/code/bngblaster/src/bbl_l2tp.h @@ -91,6 +91,38 @@ typedef struct bbl_l2tp_server_ CIRCLEQ_HEAD(tunnel_, bbl_l2tp_tunnel_) tunnel_qhead; } bbl_l2tp_server_s; +/* L2TP Client Configuration (LAC) */ +typedef struct bbl_l2tp_client_ +{ + uint16_t group_id; /* l2tp-client-group-id: ties this entry to access interfaces */ + uint32_t server_ip; /* LNS address used for outer L2TP/UDP packets */ + uint32_t client_address; /* LAC address used for outer L2TP/UDP packets */ + uint16_t hello_interval; + uint16_t receive_window; + uint16_t max_retry; + uint16_t lcp_padding; + + bool data_control_priority; + bool data_length; + bool data_offset; + + uint8_t control_tos; + uint8_t data_control_tos; + + l2tp_congestion_mode_t congestion_mode; + + char *name; + char *secret; + char *network_interface; + char *calling_number; /* Optional ICRQ Calling Number (AVP 22) */ + char *called_number; /* Optional ICRQ Called Number (AVP 21) */ + + void *next; /* Pointer to next L2TP client configuration */ + + /* List of L2TP tunnel instances for the corresponding client */ + CIRCLEQ_HEAD(client_tunnel_, bbl_l2tp_tunnel_) tunnel_qhead; +} bbl_l2tp_client_s; + /* L2TP Session Key */ typedef struct l2tp_key_ { uint16_t tunnel_id; diff --git a/docsrc/sources/configuration/index.rst b/docsrc/sources/configuration/index.rst index b84a0c12..ce680fe3 100644 --- a/docsrc/sources/configuration/index.rst +++ b/docsrc/sources/configuration/index.rst @@ -147,6 +147,10 @@ L2TPv2 Server (LNS) ------------------- .. include:: lns.rst +L2TPv2 Client (LAC) +------------------- +.. include:: lac.rst + Traffic ------- .. include:: traffic.rst diff --git a/docsrc/sources/configuration/interfaces_access.rst b/docsrc/sources/configuration/interfaces_access.rst index 73d90c31..4cd0bc4b 100644 --- a/docsrc/sources/configuration/interfaces_access.rst +++ b/docsrc/sources/configuration/interfaces_access.rst @@ -176,6 +176,12 @@ | **arp-client-group-id** | | Set ARP group identifier. | | | | Default: 0 Range: 0 - 65535 | +-----------------------------------+----------------------------------------------------------------------+ +| **l2tp-client-group-id** | | Set L2TP client group identifier (LAC mode, requires | +| | | ``"type": "pppol2tp"``). Sessions on this access interface are | +| | | spread evenly across all ``l2tp-client`` entries with the same | +| | | ``group-id``. | +| | | Default: 0 Range: 0 - 65535 | ++-----------------------------------+----------------------------------------------------------------------+ | **icmp-client-group-id",** | | Set ICMP group identifier. | | | | Default: 0 Range: 0 - 65535 | +-----------------------------------+----------------------------------------------------------------------+ diff --git a/docsrc/sources/configuration/lac.rst b/docsrc/sources/configuration/lac.rst new file mode 100644 index 00000000..7fead191 --- /dev/null +++ b/docsrc/sources/configuration/lac.rst @@ -0,0 +1,69 @@ +.. code-block:: json + + { "l2tp-client": [] } + ++-------------------------------------------+---------------------------------------------------------------------+ +| Attribute | Description | ++===========================================+=====================================================================+ +| **group-id** | | Mandatory group identifier. All ``l2tp-client`` entries with | +| | | the same ``group-id`` form a group. Access interface sections | +| | | reference the group via ``l2tp-client-group-id``. Sessions are | +| | | spread evenly across all tunnels in the group. | +| | | Range: 1 - 65535 | ++-------------------------------------------+---------------------------------------------------------------------+ +| **name** | | Mandatory L2TP LAC client name sent as hostname (AVP 7). | ++-------------------------------------------+---------------------------------------------------------------------+ +| **network-interface** | | Mandatory name of the network interface used to reach the LNS. | ++-------------------------------------------+---------------------------------------------------------------------+ +| **server-address** | | Mandatory IPv4 address of the LNS to connect to. | ++-------------------------------------------+---------------------------------------------------------------------+ +| **client-address** | | Optional source IPv4 address used for outgoing L2TP/UDP packets. | +| | | Defaults to the network interface address when not set. When | +| | | set, BNG Blaster automatically answers ARP requests for this | +| | | address on the network interface. For now, all l2tp-clients must | +| | | have a different address. | ++-------------------------------------------+---------------------------------------------------------------------+ +| **secret** | | Tunnel secret. | ++-------------------------------------------+---------------------------------------------------------------------+ +| **receive-window-size** | | Control messages receive window size. | +| | | Default: 16 Range: 1 - 65535 | ++-------------------------------------------+---------------------------------------------------------------------+ +| **max-retry** | | Control messages max retry. | +| | | Default: 5 Range: 1 - 65535 | ++-------------------------------------------+---------------------------------------------------------------------+ +| **congestion-mode** | | Control messages congestion mode (default, slow or aggressive). | +| | | The BNG Blaster supports different congestion modes for the | +| | | reliable delivery of control messages. The default mode is | +| | | described in RFC2661 appendix A (Control Channel Slow Start and | +| | | Congestion Avoidance). The mode slow uses a fixed control window | +| | | size of 1 where aggressive sticks to max permitted based on peer | +| | | received window size. | +| | | Default: default | ++-------------------------------------------+---------------------------------------------------------------------+ +| **hello-interval** | | Set hello interval in seconds. | +| | | Default: 30 Range: 0 - 65535 | ++-------------------------------------------+---------------------------------------------------------------------+ +| **data-control-priority** | | Set the priority bit in the L2TP header for all non-IP data | +| | | packets (LCP, IPCP, ...). | +| | | Default: false | ++-------------------------------------------+---------------------------------------------------------------------+ +| **data-length** | | Set length bit for all data packets. | +| | | Default: false | ++-------------------------------------------+---------------------------------------------------------------------+ +| **data-offset** | | Set offset bit with offset zero for all data packets. | +| | | Default: false | ++-------------------------------------------+---------------------------------------------------------------------+ +| **control-tos** | | Set L2TP control traffic (SCCRQ, ICRQ, ...) TOS priority. | +| | | Default: 0 Range: 0 - 255 | ++-------------------------------------------+---------------------------------------------------------------------+ +| **data-control-tos** | | Set the L2TP tunnel TOS priority (outer IPv4 header) for all | +| | | non-IP data packets (LCP, IPCP, ...). | +| | | Default: 0 Range: 0 - 255 | ++-------------------------------------------+---------------------------------------------------------------------+ +| **lcp-padding** | | Add fixed padding to LCP packets sent from the LAC. | +| | | Default: 0 Range: 0 - 65535 | ++-------------------------------------------+---------------------------------------------------------------------+ +| **calling-number** | | Optional Calling Number string sent in ICRQ (AVP 22). | ++-------------------------------------------+---------------------------------------------------------------------+ +| **called-number** | | Optional Called Number string sent in ICRQ (AVP 21). | ++-------------------------------------------+---------------------------------------------------------------------+ diff --git a/schemas/bngblaster-config.json b/schemas/bngblaster-config.json index ed32d2dc..84ec7dfe 100644 --- a/schemas/bngblaster-config.json +++ b/schemas/bngblaster-config.json @@ -1238,6 +1238,124 @@ } } }, + "l2tp-client": { + "type": "array", + "description": "L2TPv2 Client (LAC) configuration", + "items": { + "type": "object", + "required": [ + "group-id", + "name", + "network-interface", + "server-address" + ], + "properties": { + "group-id": { + "type": "integer", + "description": "L2TP client group identifier, matched against l2tp-client-group-id in access interfaces", + "minimum": 1, + "maximum": 65535 + }, + "name": { + "type": "string", + "description": "L2TP LAC tunnel name (AVP 7)" + }, + "network-interface": { + "type": "string", + "description": "Network interface used for L2TP tunnel" + }, + "server-address": { + "type": "string", + "description": "L2TP server (LNS) address", + "format": "ipv4" + }, + "client-address": { + "type": "string", + "description": "L2TP client source address (defaults to network interface address)", + "format": "ipv4" + }, + "secret": { + "type": "string", + "description": "Tunnel secret" + }, + "receive-window-size": { + "type": "integer", + "description": "Control messages receive window size", + "default": 16, + "minimum": 1, + "maximum": 65535 + }, + "max-retry": { + "type": "integer", + "description": "Maximum number of tunnel setup retries", + "default": 5, + "minimum": 1, + "maximum": 65535 + }, + "congestion-mode": { + "type": "string", + "description": "Congestion control mode", + "enum": [ + "default", + "slow", + "aggressive" + ], + "default": "default" + }, + "data-control-priority": { + "type": "boolean", + "description": "Set priority bit for non-IP data packets", + "default": false + }, + "data-length": { + "type": "boolean", + "description": "Set length bit for all data packets", + "default": false + }, + "data-offset": { + "type": "boolean", + "description": "Set offset bit with zero for all data packets", + "default": false + }, + "control-tos": { + "type": "integer", + "description": "L2TP control traffic TOS priority", + "default": 0, + "minimum": 0, + "maximum": 255 + }, + "data-control-tos": { + "type": "integer", + "description": "L2TP tunnel TOS for non-IP data packets", + "default": 0, + "minimum": 0, + "maximum": 255 + }, + "hello-interval": { + "type": "integer", + "description": "Hello (keepalive) interval in seconds", + "default": 30, + "minimum": 0, + "maximum": 65535 + }, + "lcp-padding": { + "type": "integer", + "description": "Add fixed padding to LCP packets", + "default": 0, + "minimum": 0, + "maximum": 65535 + }, + "calling-number": { + "type": "string", + "description": "Calling number AVP value" + }, + "called-number": { + "type": "string", + "description": "Called number AVP value" + } + } + } + }, "isis": { "oneOf": [ { From 104089b2b2ba21af4a0a4dcd4743ea769dc972c4 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Fri, 20 Mar 2026 13:37:02 +0100 Subject: [PATCH 02/25] LAC: introduce wrapper for tunnel hostname 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 --- code/bngblaster/src/bbl_l2tp.c | 48 ++++++++++-------- code/bngblaster/src/bbl_l2tp.h | 3 ++ code/bngblaster/src/bbl_l2tp_avp.c | 79 ++++++++++++++++-------------- 3 files changed, 71 insertions(+), 59 deletions(-) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index 74f6171d..a9f9ec45 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -16,6 +16,12 @@ void bbl_l2tp_send(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_s *l2tp_session, l2tp_message_t l2tp_type); +const char* +l2tp_tunnel_hostname(bbl_l2tp_tunnel_s *l2tp_tunnel) +{ + return l2tp_tunnel->server->host_name; +} + const char* l2tp_message_string(l2tp_message_t type) { @@ -162,7 +168,7 @@ bbl_l2tp_session_delete(bbl_l2tp_session_s *l2tp_session) if(l2tp_session->key.session_id) { /* Here we skip the session with ID zero which is the tunnel session. */ LOG(DEBUG, "L2TP Debug (%s) Tunnel %u Session %u deleted\n", - l2tp_session->tunnel->server->host_name, l2tp_session->tunnel->tunnel_id, l2tp_session->key.session_id); + l2tp_tunnel_hostname(l2tp_session->tunnel), l2tp_session->tunnel->tunnel_id, l2tp_session->key.session_id); if(g_ctx->l2tp_sessions) g_ctx->l2tp_sessions--; } @@ -205,7 +211,7 @@ bbl_l2tp_tunnel_delete(bbl_l2tp_tunnel_s *l2tp_tunnel) if(l2tp_tunnel) { if(l2tp_tunnel->tunnel_id) { LOG(DEBUG, "L2TP Debug (%s) Tunnel %u deleted\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id); + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id); } if(g_ctx->l2tp_tunnels) g_ctx->l2tp_tunnels--; @@ -256,7 +262,7 @@ bbl_l2tp_tunnel_update_state(bbl_l2tp_tunnel_s *l2tp_tunnel, l2tp_tunnel_state_t if(l2tp_tunnel->state != state) { /* State has changed */ LOG(DEBUG, "L2TP Debug (%s) Tunnel (%u) state changed from %s to %s\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_tunnel_state_string(l2tp_tunnel->state), l2tp_tunnel_state_string(state)); @@ -267,7 +273,7 @@ bbl_l2tp_tunnel_update_state(bbl_l2tp_tunnel_s *l2tp_tunnel, l2tp_tunnel_state_t g_ctx->l2tp_tunnels_established_max = g_ctx->l2tp_tunnels_established; } LOG(L2TP, "L2TP Info (%s) Tunnel (%u) with %s (%s) established\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_tunnel->peer_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); } else if(l2tp_tunnel->state == BBL_L2TP_TUNNEL_ESTABLISHED) { @@ -340,7 +346,7 @@ bbl_l2tp_tunnel_tx_job(timer_s *timer) interface->stats.l2tp_control_retry++; if(q->retries > l2tp_tunnel->server->max_retry) { LOG(ERROR, "L2TP Error (%s) Tunnel (%u) max retry to %s (%s)\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_tunnel->peer_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); l2tp_tunnel->result_code = 2; @@ -633,15 +639,15 @@ bbl_l2tp_sccrq_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth l2tp_tunnel2->peer_tunnel_id == l2tp_tunnel->peer_tunnel_id) { if(l2tp_tunnel2->state > BBL_L2TP_TUNNEL_WAIT_CTR_CONN) { LOG(ERROR, "L2TP Error (%s) Tunnel (%u) SCCRQ in wrong state (%s) received from %s (%s)\n", - l2tp_tunnel2->server->host_name, l2tp_tunnel2->tunnel_id, - l2tp_tunnel_state_string(l2tp_tunnel2->state), + l2tp_tunnel_hostname(l2tp_tunnel2), l2tp_tunnel2->tunnel_id, + l2tp_tunnel_state_string(l2tp_tunnel2->state), l2tp_tunnel2->peer_name, format_ipv4_address(&ipv4->src)); bbl_l2tp_tunnel_update_state(l2tp_tunnel2, BBL_L2TP_TUNNEL_TERMINATED); } else { /* Seems to be an SCCRQ retry ... */ LOG(PACKET, "L2TP (%s) SCCRQ retry received from %s (%s)\n", - l2tp_tunnel2->server->host_name, l2tp_tunnel2->peer_name, + l2tp_tunnel_hostname(l2tp_tunnel2), l2tp_tunnel2->peer_name, format_ipv4_address(&ipv4->src)); } bbl_l2tp_tunnel_delete(l2tp_tunnel); @@ -655,7 +661,7 @@ bbl_l2tp_sccrq_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth l2tp_tunnel->server = l2tp_server; LOG(PACKET, "L2TP (%s) SCCRQ received from %s (%s)\n", - l2tp_server->host_name, l2tp_tunnel->peer_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->peer_name, format_ipv4_address(&ipv4->src)); /* Add dummy tunnel session, this session is only used @@ -681,7 +687,7 @@ bbl_l2tp_sccrq_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth result = dict_insert(g_ctx->l2tp_session_dict, &l2tp_session->key); if(!result.inserted) { LOG(ERROR, "L2TP Error (%s) Failed to add tunnel session\n", - l2tp_tunnel->server->host_name); + l2tp_tunnel_hostname(l2tp_tunnel)); free(l2tp_session); bbl_l2tp_tunnel_delete(l2tp_tunnel); return; @@ -707,7 +713,7 @@ bbl_l2tp_sccrq_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth /* We are not able to setup a session if no challenge * is received but there is a secret configured! */ LOG(ERROR, "L2TP Error (%s) Missing challenge in SCCRQ from %s\n", - l2tp_tunnel->server->host_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); l2tp_tunnel->result_code = 2; l2tp_tunnel->error_code = 6; l2tp_tunnel->error_message = "missing challenge"; @@ -718,7 +724,7 @@ bbl_l2tp_sccrq_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth /* We are not able to setup a session if challenge * is received but not secret configured! */ LOG(ERROR, "L2TP Error (%s) No secret found but challenge received in SCCRQ from %s\n", - l2tp_tunnel->server->host_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); l2tp_tunnel->result_code = 2; l2tp_tunnel->error_code = 6; l2tp_tunnel->error_message = "no challenge expected"; @@ -760,7 +766,7 @@ bbl_l2tp_scccn_rx(bbl_network_interface_s *interface, if(l2tp_tunnel->state == BBL_L2TP_TUNNEL_WAIT_CTR_CONN) { if(!bbl_l2tp_avp_decode_tunnel(l2tp, l2tp_tunnel)) { LOG(ERROR, "L2TP Error (%s) Invalid SCCCN received from %s\n", - l2tp_tunnel->server->host_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); l2tp_tunnel->result_code = 2; l2tp_tunnel->error_code = 6; l2tp_tunnel->error_message = "decode error"; @@ -778,7 +784,7 @@ bbl_l2tp_scccn_rx(bbl_network_interface_s *interface, MD5_Final(digest, &md5_ctx); if(memcmp(digest, l2tp_tunnel->peer_challenge_response, L2TP_MD5_DIGEST_LEN) != 0) { LOG(ERROR, "L2TP Error (%s) Wrong challenge response in SCCCN from %s\n", - l2tp_tunnel->server->host_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); l2tp_tunnel->result_code = 2; l2tp_tunnel->error_code = 6; l2tp_tunnel->error_message = "challenge authentication failed"; @@ -788,7 +794,7 @@ bbl_l2tp_scccn_rx(bbl_network_interface_s *interface, } } else { LOG(ERROR, "L2TP Error (%s) Missing challenge response in SCCCN from %s\n", - l2tp_tunnel->server->host_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); l2tp_tunnel->result_code = 2; l2tp_tunnel->error_code = 6; l2tp_tunnel->error_message = "missing challenge response"; @@ -866,7 +872,7 @@ bbl_l2tp_icrq_rx(bbl_network_interface_s *interface, result = dict_insert(g_ctx->l2tp_session_dict, &l2tp_session->key); if(!result.inserted) { LOG(ERROR, "L2TP Error (%s) Failed to add session\n", - l2tp_tunnel->server->host_name); + l2tp_tunnel_hostname(l2tp_tunnel)); free(l2tp_session); return; } @@ -904,7 +910,7 @@ bbl_l2tp_iccn_rx(bbl_network_interface_s *interface, if(l2tp_session->state == BBL_L2TP_SESSION_WAIT_CONN) { l2tp_session->state = BBL_L2TP_SESSION_ESTABLISHED; LOG(L2TP, "L2TP Info (%s) Tunnel (%u) from %s (%s) session (%u) established\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_tunnel->peer_name, format_ipv4_address(&l2tp_tunnel->peer_ip), l2tp_session->key.session_id); @@ -930,7 +936,7 @@ bbl_l2tp_cdn_rx(bbl_network_interface_s *interface, l2tp_session->state = BBL_L2TP_SESSION_TERMINATED; LOG(L2TP, "L2TP Info (%s) Tunnel (%u) from %s (%s) session (%u) terminated\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_tunnel->peer_name, format_ipv4_address(&l2tp_tunnel->peer_ip), l2tp_session->key.session_id); @@ -1247,7 +1253,7 @@ bbl_l2tp_handler_rx(bbl_network_interface_s *interface, if(l2tp_tunnel->nr == l2tp->ns) { /* In-Order packet received */ LOG(PACKET, "L2TP (%s) Tunnel (%u) %s received from %s\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_message_string(l2tp->type), format_ipv4_address(&ipv4->src)); /* Update tunnel */ @@ -1337,7 +1343,7 @@ bbl_l2tp_handler_rx(bbl_network_interface_s *interface, if(L2TP_SEQ_LT(l2tp->ns, l2tp_tunnel->nr)) { /* Duplicate packet received */ LOG(DEBUG, "L2TP Debug (%s) Tunnel (%u) Duplicate %s received with Ns. %u (expected %u) from %s\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_message_string(l2tp->type), l2tp->ns, l2tp_tunnel->nr, format_ipv4_address(&ipv4->src)); @@ -1353,7 +1359,7 @@ bbl_l2tp_handler_rx(bbl_network_interface_s *interface, } else { /* Out-of-Order packet received */ LOG(DEBUG, "L2TP Debug (%s) Tunnel (%u) Out-of-Order %s received with Ns. %u (expected %u) from %s\n", - l2tp_tunnel->server->host_name, l2tp_tunnel->tunnel_id, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_message_string(l2tp->type), l2tp->ns, l2tp_tunnel->nr, format_ipv4_address(&ipv4->src)); diff --git a/code/bngblaster/src/bbl_l2tp.h b/code/bngblaster/src/bbl_l2tp.h index 7a92aac4..3bda5b45 100644 --- a/code/bngblaster/src/bbl_l2tp.h +++ b/code/bngblaster/src/bbl_l2tp.h @@ -306,6 +306,9 @@ typedef struct bbl_l2tp_session_ char *peer_aci; } bbl_l2tp_session_s; +const char* +l2tp_tunnel_hostname(bbl_l2tp_tunnel_s *l2tp_tunnel); + const char* l2tp_message_string(l2tp_message_t type); diff --git a/code/bngblaster/src/bbl_l2tp_avp.c b/code/bngblaster/src/bbl_l2tp_avp.c index deaf925b..39728ff7 100644 --- a/code/bngblaster/src/bbl_l2tp_avp.c +++ b/code/bngblaster/src/bbl_l2tp_avp.c @@ -7,6 +7,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ #include "bbl.h" +#include "bbl_l2tp.h" #include "bbl_l2tp_avp.h" #include #include @@ -191,12 +192,12 @@ bbl_l2tp_avp_unhide(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_avp_t *avp, uint8_t if(!value) { LOG(L2TP, "L2TP Error (%s) Invalid hidden AVP\n", - l2tp_tunnel->server->host_name); + l2tp_tunnel_hostname(l2tp_tunnel)); return false; } if(!(random_vector && l2tp_tunnel->server->secret)) { LOG(L2TP, "L2TP Error (%s) Missing random-vector or secret\n", - l2tp_tunnel->server->host_name); + l2tp_tunnel_hostname(l2tp_tunnel)); return false; } @@ -216,7 +217,7 @@ bbl_l2tp_avp_unhide(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_avp_t *avp, uint8_t if(len + 2 > avp->len) { LOG(L2TP, "L2TP Error (%s) Decrypted length %u > AVP length %u\n", - l2tp_tunnel->server->host_name, len, avp->len); + l2tp_tunnel_hostname(l2tp_tunnel), len, avp->len); return false; } @@ -255,7 +256,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb if(avp.h) { if(!bbl_l2tp_avp_unhide(l2tp_tunnel, &avp, random_vector, random_vector_len)) { LOG(L2TP, "L2TP (%s) Failed to decrypt hidden AVP %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -269,7 +270,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_ASSIGNED_SESSION_ID: if(avp.len != 2) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP assigned session id AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -279,7 +280,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_CALL_SERIAL_NUMBER: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP call serial number AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -289,7 +290,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_FRAMING_TYPE: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP framing type AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -299,7 +300,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_BEARER_TYPE: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP bearer type AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -330,7 +331,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_TX_CONNECT_SPEED: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP tx connect speed AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -340,7 +341,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_RX_CONNECT_SPEED: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP rx connect speed AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -350,7 +351,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_PHYSICAL_CHANNEL_ID: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP physical channel AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -360,7 +361,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_PRIVATE_GROUP_ID: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP private group id AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -378,7 +379,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_PROXY_AUTHEN_TYPE: if(avp.len != 2) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP proxy auth type AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -403,7 +404,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb case L2TP_AVP_PROXY_AUTHEN_ID: if(avp.len != 2) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP proxy auth id AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -436,13 +437,13 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb default: if(avp.m) { LOG(L2TP, "L2TP Error (%s) Mandatory standard AVP with unknown type %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; } else { LOG(L2TP, "L2TP Warning (%s) Optional standard AVP with unknown type %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); } @@ -467,7 +468,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb default: if(avp.m) { LOG(L2TP, "L2TP Error (%s) Mandatory Broadband Forum AVP with unknown type %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -477,7 +478,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb } else { if(avp.m) { LOG(L2TP, "L2TP (%s) Mandatory AVP with unknown vendor %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.vendor, + l2tp_tunnel_hostname(l2tp_tunnel), avp.vendor, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -485,7 +486,7 @@ bbl_l2tp_avp_decode_session(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel, bb } } else { LOG(L2TP, "L2TP (%s) Failed to decdoe session attributes in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -509,7 +510,7 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) if(avp.h) { if(!bbl_l2tp_avp_unhide(l2tp_tunnel, &avp, random_vector, random_vector_len)) { LOG(L2TP, "L2TP (%s) Failed to decrypt hidden AVP %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -523,7 +524,7 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) case L2TP_AVP_PROTOCOL_VERSION: if(avp.len != 2 || be16toh(*(uint16_t*)avp.value) != 256) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP protocol version AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -532,7 +533,7 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) case L2TP_AVP_FRAMING_CAPABILITIES: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP framing capabilities AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -542,7 +543,7 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) case L2TP_AVP_BEARER_CAPABILITIES: if(avp.len != 4) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP bearer capabilities AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -552,7 +553,7 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) case L2TP_AVP_FIRMWARE_REVISION: if(avp.len != 2) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP firmware revision AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -576,7 +577,7 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) case L2TP_AVP_ASSIGNED_TUNNEL_ID: if(avp.len != 2) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP assigned tunnel id AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -586,7 +587,7 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) case L2TP_AVP_RECEIVE_WINDOW_SIZE: if(avp.len != 2) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP receive window size AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -604,7 +605,7 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) case L2TP_AVP_CHALLENGE_RESPONSE: if(avp.len != L2TP_MD5_DIGEST_LEN) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP challenge response AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -622,13 +623,13 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) default: if(avp.m) { LOG(L2TP, "L2TP Error (%s) Mandatory standard AVP with unknown type %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; } else { LOG(L2TP, "L2TP Warning (%s) Optional standard AVP with unknown type %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); } @@ -637,13 +638,14 @@ bbl_l2tp_avp_decode_tunnel(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) } else { if(avp.m) { LOG(L2TP, "L2TP (%s) Mandatory AVP with unknown vendor %u received from %s\n", - l2tp_tunnel->server->host_name, avp.vendor, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), avp.vendor, + format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; } } } else { LOG(L2TP, "L2TP (%s) Failed to decdoe tunnel attributes from %s\n", - l2tp_tunnel->server->host_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; } } @@ -673,7 +675,7 @@ bbl_l2tp_avp_decode_csun(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) case L2TP_AVP_CONNECT_SPEED_UPDATE: if(avp.len != 12) { LOG(L2TP, "L2TP Error (%s) Invalid L2TP connect speed update AVP in %s from %s\n", - l2tp_tunnel->server->host_name, + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; @@ -691,13 +693,13 @@ bbl_l2tp_avp_decode_csun(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) default: if(avp.m) { LOG(L2TP, "L2TP Error (%s) Mandatory standard AVP with unknown type %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; } else { LOG(L2TP, "L2TP Warning (%s) Optional standard AVP with unknown type %u in %s from %s\n", - l2tp_tunnel->server->host_name, avp.type, + l2tp_tunnel_hostname(l2tp_tunnel), avp.type, l2tp_message_string(l2tp->type), format_ipv4_address(&l2tp_tunnel->peer_ip)); } @@ -706,13 +708,14 @@ bbl_l2tp_avp_decode_csun(bbl_l2tp_s *l2tp, bbl_l2tp_tunnel_s *l2tp_tunnel) } else { if(avp.m) { LOG(L2TP, "L2TP (%s) Mandatory AVP with unknown vendor %u received from %s\n", - l2tp_tunnel->server->host_name, avp.vendor, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), avp.vendor, + format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; } } } else { LOG(L2TP, "L2TP (%s) Failed to decdoe tunnel attributes from %s\n", - l2tp_tunnel->server->host_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); return false; } } @@ -784,9 +787,9 @@ bbl_l2tp_avp_encode_attributes(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_ /* Host Name */ avp.m = true; avp.type = L2TP_AVP_HOST_NAME; - avp.len = strlen(l2tp_tunnel->server->host_name); + avp.len = strlen(l2tp_tunnel_hostname(l2tp_tunnel)); avp.value_type = L2TP_AVP_VALUE_BYTES; - avp.value = (void*)(l2tp_tunnel->server->host_name); + avp.value = (void*)l2tp_tunnel_hostname(l2tp_tunnel); bbl_l2tp_avp_encode(&buf, len, &avp); /* Vendor Name */ avp.m = false; From f86cd0cd187ab3192ff006d78474ae3c785875a7 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Wed, 18 Mar 2026 11:07:27 +0100 Subject: [PATCH 03/25] LAC: implement AVP encoding for LAC messages 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 --- code/bngblaster/src/bbl_l2tp.h | 5 +- code/bngblaster/src/bbl_l2tp_avp.c | 153 +++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/code/bngblaster/src/bbl_l2tp.h b/code/bngblaster/src/bbl_l2tp.h index 3bda5b45..eee8330c 100644 --- a/code/bngblaster/src/bbl_l2tp.h +++ b/code/bngblaster/src/bbl_l2tp.h @@ -168,8 +168,11 @@ typedef struct bbl_l2tp_tunnel_ /* Pointer to corresponding network interface */ bbl_network_interface_s *interface; - /* Pointer to L2TP server configuration */ + bool is_lac; + /* Pointer to L2TP server configuration (LNS mode) */ bbl_l2tp_server_s *server; + /* Pointer to L2TP client configuration (LAC mode) */ + bbl_l2tp_client_s *client; /* RFC5515 CSURQ */ uint16_t *csurq_requests; diff --git a/code/bngblaster/src/bbl_l2tp_avp.c b/code/bngblaster/src/bbl_l2tp_avp.c index 39728ff7..59df2879 100644 --- a/code/bngblaster/src/bbl_l2tp_avp.c +++ b/code/bngblaster/src/bbl_l2tp_avp.c @@ -751,6 +751,83 @@ bbl_l2tp_avp_encode_attributes(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_ bbl_l2tp_avp_encode(&buf, len, &avp); switch (l2tp_type) { + case L2TP_MESSAGE_SCCRQ: + { + /* Protocol Version */ + v16 = 256; + avp.m = true; + avp.type = L2TP_AVP_PROTOCOL_VERSION; + avp.len = 2; + avp.value_type = L2TP_AVP_VALUE_UINT16; + avp.value = (void*)&v16; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Framing Capabilities */ + v32 = 3; /* A + S */ + avp.m = true; + avp.type = L2TP_AVP_FRAMING_CAPABILITIES; + avp.len = 4; + avp.value_type = L2TP_AVP_VALUE_UINT32; + avp.value = (void*)&v32; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Bearer Capabilities */ + v32 = 3; /* A + D */ + avp.m = true; + avp.type = L2TP_AVP_BEARER_CAPABILITIES; + avp.len = 4; + avp.value_type = L2TP_AVP_VALUE_UINT32; + avp.value = (void*)&v32; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Firmware Revision */ + v16 = 1; + avp.m = false; + avp.type = L2TP_AVP_FIRMWARE_REVISION; + avp.len = 2; + avp.value_type = L2TP_AVP_VALUE_UINT16; + avp.value = (void*)&v16; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Host Name */ + avp.m = true; + avp.type = L2TP_AVP_HOST_NAME; + avp.len = strlen(l2tp_tunnel_hostname(l2tp_tunnel)); + avp.value_type = L2TP_AVP_VALUE_BYTES; + avp.value = (void*)l2tp_tunnel_hostname(l2tp_tunnel); + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Vendor Name */ + avp.m = false; + avp.type = L2TP_AVP_VENDOR_NAME; + avp.len = sizeof("bngblaster") - 1; + avp.value_type = L2TP_AVP_VALUE_BYTES; + avp.value = (void*)"bngblaster"; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Assigned Tunnel ID */ + avp.m = true; + avp.type = L2TP_AVP_ASSIGNED_TUNNEL_ID; + avp.len = 2; + avp.value_type = L2TP_AVP_VALUE_UINT16; + avp.value = (void*)(&l2tp_tunnel->tunnel_id); + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Receive Window Size */ + v16 = 4; + if(l2tp_tunnel->client->receive_window) { + v16 = l2tp_tunnel->client->receive_window; + } + avp.m = true; + avp.type = L2TP_AVP_RECEIVE_WINDOW_SIZE; + avp.len = 2; + avp.value_type = L2TP_AVP_VALUE_UINT16; + avp.value = (void*)&v16; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Challenge */ + if(l2tp_tunnel->challenge_len) { + avp.m = true; + avp.type = L2TP_AVP_CHALLENGE; + avp.len = l2tp_tunnel->challenge_len; + avp.value_type = L2TP_AVP_VALUE_BYTES; + avp.value = l2tp_tunnel->challenge; + bbl_l2tp_avp_encode(&buf, len, &avp); + } + break; + } case L2TP_MESSAGE_SCCRP: /* Protocol Version */ v16 = 256; @@ -835,6 +912,17 @@ bbl_l2tp_avp_encode_attributes(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_ bbl_l2tp_avp_encode(&buf, len, &avp); } break; + case L2TP_MESSAGE_SCCCN: + /* Challenge Response */ + if(l2tp_tunnel->challenge_response_len) { + avp.m = true; + avp.type = L2TP_AVP_CHALLENGE_RESPONSE; + avp.len = l2tp_tunnel->challenge_response_len; + avp.value_type = L2TP_AVP_VALUE_BYTES; + avp.value = l2tp_tunnel->challenge_response; + bbl_l2tp_avp_encode(&buf, len, &avp); + } + break; case L2TP_MESSAGE_STOPCCN: /* Assigned Tunnel ID */ avp.m = true; @@ -849,6 +937,51 @@ bbl_l2tp_avp_encode_attributes(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_ l2tp_tunnel->error_code, l2tp_tunnel->error_message); break; + case L2TP_MESSAGE_ICRQ: + /* Assigned Session ID */ + if(l2tp_session) { + avp.m = true; + avp.type = L2TP_AVP_ASSIGNED_SESSION_ID; + avp.len = 2; + avp.value_type = L2TP_AVP_VALUE_UINT16; + avp.value = (void*)(&l2tp_session->key.session_id); + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Call Serial Number */ + v32 = l2tp_session->key.session_id; + avp.m = true; + avp.type = L2TP_AVP_CALL_SERIAL_NUMBER; + avp.len = 4; + avp.value_type = L2TP_AVP_VALUE_UINT32; + avp.value = (void*)&v32; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Bearer Type */ + v32 = 2; /* Analog */ + avp.m = true; + avp.type = L2TP_AVP_BEARER_TYPE; + avp.len = 4; + avp.value_type = L2TP_AVP_VALUE_UINT32; + avp.value = (void*)&v32; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Calling Number (optional, AVP 22) */ + if(l2tp_tunnel->client && l2tp_tunnel->client->calling_number) { + avp.m = false; + avp.type = L2TP_AVP_CALLING_NUMBER; + avp.len = strlen(l2tp_tunnel->client->calling_number); + avp.value_type = L2TP_AVP_VALUE_BYTES; + avp.value = (void*)l2tp_tunnel->client->calling_number; + bbl_l2tp_avp_encode(&buf, len, &avp); + } + /* Called Number (optional, AVP 21) */ + if(l2tp_tunnel->client && l2tp_tunnel->client->called_number) { + avp.m = false; + avp.type = L2TP_AVP_CALLED_NUMBER; + avp.len = strlen(l2tp_tunnel->client->called_number); + avp.value_type = L2TP_AVP_VALUE_BYTES; + avp.value = (void*)l2tp_tunnel->client->called_number; + bbl_l2tp_avp_encode(&buf, len, &avp); + } + } + break; case L2TP_MESSAGE_ICRP: /* Assigned Session ID */ if(l2tp_session) { @@ -860,6 +993,26 @@ bbl_l2tp_avp_encode_attributes(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_ bbl_l2tp_avp_encode(&buf, len, &avp); } break; + case L2TP_MESSAGE_ICCN: + /* TX Connect Speed */ + if(l2tp_session) { + v32 = 100000000; + avp.m = true; + avp.type = L2TP_AVP_TX_CONNECT_SPEED; + avp.len = 4; + avp.value_type = L2TP_AVP_VALUE_UINT32; + avp.value = (void*)&v32; + bbl_l2tp_avp_encode(&buf, len, &avp); + /* Framing Type */ + v32 = 1; /* Synchronous */ + avp.m = true; + avp.type = L2TP_AVP_FRAMING_TYPE; + avp.len = 4; + avp.value_type = L2TP_AVP_VALUE_UINT32; + avp.value = (void*)&v32; + bbl_l2tp_avp_encode(&buf, len, &avp); + } + break; case L2TP_MESSAGE_CDN: /* Assigned Session ID */ if(l2tp_session) { From ed269375d3a35c9ee123c79146661541ae19ca06 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Fri, 20 Mar 2026 13:37:41 +0100 Subject: [PATCH 04/25] LAC: implement core L2TP protocol 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 --- code/bngblaster/src/bbl_l2tp.c | 368 +++++++++++++++++++++++++++++++-- code/bngblaster/src/bbl_l2tp.h | 3 + 2 files changed, 349 insertions(+), 22 deletions(-) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index a9f9ec45..94f167b7 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -19,7 +19,11 @@ bbl_l2tp_send(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_s *l2tp_session, const char* l2tp_tunnel_hostname(bbl_l2tp_tunnel_s *l2tp_tunnel) { - return l2tp_tunnel->server->host_name; + if(l2tp_tunnel->is_lac) { + return l2tp_tunnel->client->name; + } else { + return l2tp_tunnel->server->host_name; + } } const char* @@ -222,9 +226,13 @@ bbl_l2tp_tunnel_delete(bbl_l2tp_tunnel_s *l2tp_tunnel) while (!CIRCLEQ_EMPTY(&l2tp_tunnel->session_qhead)) { bbl_l2tp_session_delete(CIRCLEQ_FIRST(&l2tp_tunnel->session_qhead)); } - /* Remove tunnel from server object */ + /* Remove tunnel from server/client object */ if(CIRCLEQ_NEXT(l2tp_tunnel, tunnel_qnode) != NULL) { - CIRCLEQ_REMOVE(&l2tp_tunnel->server->tunnel_qhead, l2tp_tunnel, tunnel_qnode); + if(l2tp_tunnel->is_lac) { + CIRCLEQ_REMOVE(&l2tp_tunnel->client->tunnel_qhead, l2tp_tunnel, tunnel_qnode); + } else { + CIRCLEQ_REMOVE(&l2tp_tunnel->server->tunnel_qhead, l2tp_tunnel, tunnel_qnode); + } CIRCLEQ_NEXT(l2tp_tunnel, tunnel_qnode) = NULL; } /* Cleanup send queues */ @@ -303,6 +311,8 @@ bbl_l2tp_tunnel_tx_job(timer_s *timer) int backoff; uint16_t max_ns = l2tp_tunnel->peer_nr + l2tp_tunnel->cwnd; + uint16_t max_retry = l2tp_tunnel->is_lac ? + l2tp_tunnel->client->max_retry : l2tp_tunnel->server->max_retry; l2tp_tunnel->timer_tx_active = false; if(l2tp_tunnel->state == BBL_L2TP_TUNNEL_SEND_STOPCCN) { @@ -344,7 +354,7 @@ bbl_l2tp_tunnel_tx_job(timer_s *timer) if(q->retries) { l2tp_tunnel->stats.control_retry++; interface->stats.l2tp_control_retry++; - if(q->retries > l2tp_tunnel->server->max_retry) { + if(q->retries > max_retry) { LOG(ERROR, "L2TP Error (%s) Tunnel (%u) max retry to %s (%s)\n", l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, l2tp_tunnel->peer_name, format_ipv4_address(&l2tp_tunnel->peer_ip)); @@ -391,6 +401,9 @@ void bbl_l2tp_tunnel_control_job(timer_s *timer) { bbl_l2tp_tunnel_s *l2tp_tunnel = timer->data; + uint16_t hello_interval = l2tp_tunnel->is_lac ? + l2tp_tunnel->client->hello_interval : l2tp_tunnel->server->hello_interval; + l2tp_tunnel->state_seconds++; switch(l2tp_tunnel->state) { case BBL_L2TP_TUNNEL_WAIT_CTR_CONN: @@ -403,8 +416,8 @@ bbl_l2tp_tunnel_control_job(timer_s *timer) } break; case BBL_L2TP_TUNNEL_ESTABLISHED: - if(l2tp_tunnel->server->hello_interval) { - if(l2tp_tunnel->state_seconds % l2tp_tunnel->server->hello_interval == 0 && CIRCLEQ_EMPTY(&l2tp_tunnel->tx_qhead)) { + if(hello_interval) { + if(l2tp_tunnel->state_seconds % hello_interval == 0 && CIRCLEQ_EMPTY(&l2tp_tunnel->tx_qhead)) { bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_HELLO); } } @@ -463,9 +476,16 @@ bbl_l2tp_send(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_s *l2tp_session, eth.type = ETH_TYPE_IPV4; eth.next = &ipv4; ipv4.dst = l2tp_tunnel->peer_ip; - ipv4.src = l2tp_tunnel->server->ip; + if(l2tp_tunnel->is_lac) { + ipv4.src = l2tp_tunnel->client->client_address ? + l2tp_tunnel->client->client_address : + l2tp_tunnel->interface->ip.address; + ipv4.tos = l2tp_tunnel->client->control_tos; + } else { + ipv4.src = l2tp_tunnel->server->ip; + ipv4.tos = l2tp_tunnel->server->control_tos; + } ipv4.ttl = 64; - ipv4.tos = l2tp_tunnel->server->control_tos; ipv4.protocol = PROTOCOL_IPV4_UDP; ipv4.next = &udp; udp.src = L2TP_UDP_PORT; @@ -535,7 +555,6 @@ static void bbl_l2tp_send_data(bbl_l2tp_session_s *l2tp_session, uint16_t protocol, void *next) { bbl_l2tp_tunnel_s *l2tp_tunnel = l2tp_session->tunnel; - bbl_l2tp_server_s *l2tp_server = l2tp_tunnel->server; bbl_network_interface_s *interface = l2tp_tunnel->interface; bbl_l2tp_queue_s *q = calloc(1, sizeof(bbl_l2tp_queue_s)); bbl_ethernet_header_s eth = {0}; @@ -549,7 +568,13 @@ bbl_l2tp_send_data(bbl_l2tp_session_s *l2tp_session, uint16_t protocol, void *ne eth.type = ETH_TYPE_IPV4; eth.next = &ipv4; ipv4.dst = l2tp_tunnel->peer_ip; - ipv4.src = l2tp_tunnel->server->ip; + if(l2tp_tunnel->is_lac) { + ipv4.src = l2tp_tunnel->client->client_address ? + l2tp_tunnel->client->client_address : + l2tp_tunnel->interface->ip.address; + } else { + ipv4.src = l2tp_tunnel->server->ip; + } ipv4.ttl = 64; ipv4.protocol = PROTOCOL_IPV4_UDP; ipv4.next = &udp; @@ -561,13 +586,24 @@ bbl_l2tp_send_data(bbl_l2tp_session_s *l2tp_session, uint16_t protocol, void *ne l2tp.tunnel_id = l2tp_tunnel->peer_tunnel_id; l2tp.session_id = l2tp_session->peer_session_id; l2tp.protocol = protocol; - l2tp.with_length = l2tp_server->data_length; - l2tp.with_offset = l2tp_server->data_offset; - if(protocol != PROTOCOL_IPV4 && protocol != PROTOCOL_IPV6) { - if(l2tp_server->data_control_priority) { - l2tp.with_priority = true; + if(l2tp_tunnel->is_lac) { + l2tp.with_length = l2tp_tunnel->client->data_length; + l2tp.with_offset = l2tp_tunnel->client->data_offset; + if(protocol != PROTOCOL_IPV4 && protocol != PROTOCOL_IPV6) { + if(l2tp_tunnel->client->data_control_priority) { + l2tp.with_priority = true; + } + ipv4.tos = l2tp_tunnel->client->data_control_tos; + } + } else { + l2tp.with_length = l2tp_tunnel->server->data_length; + l2tp.with_offset = l2tp_tunnel->server->data_offset; + if(protocol != PROTOCOL_IPV4 && protocol != PROTOCOL_IPV6) { + if(l2tp_tunnel->server->data_control_priority) { + l2tp.with_priority = true; + } + ipv4.tos = l2tp_tunnel->server->data_control_tos; } - ipv4.tos = l2tp_tunnel->server->data_control_tos; } l2tp.next = next; if(encode_ethernet(q->packet, &len, ð) == PROTOCOL_SUCCESS) { @@ -1192,6 +1228,279 @@ bbl_l2tp_data_rx(bbl_network_interface_s *interface, } } +/** + * bbl_l2tp_client_session_connect + * + * Initiate a new L2TP session within an established LAC tunnel + * by sending ICRQ. + */ +static void +bbl_l2tp_client_session_connect(bbl_l2tp_tunnel_s *l2tp_tunnel) +{ + bbl_l2tp_session_s *l2tp_session; + dict_insert_result result; + void **search; + + l2tp_session = calloc(1, sizeof(bbl_l2tp_session_s)); + g_ctx->l2tp_sessions++; + l2tp_session->tunnel = l2tp_tunnel; + l2tp_session->state = BBL_L2TP_SESSION_WAIT_CONN; + + l2tp_session->key.tunnel_id = l2tp_tunnel->tunnel_id; + + /* Assign session id ... */ + while(true) { + l2tp_session->key.session_id = l2tp_tunnel->next_session_id++; + if(l2tp_session->key.session_id == 0) continue; /* skip session 0 */ + search = dict_search(g_ctx->l2tp_session_dict, &l2tp_session->key); + if(search) { + /* Used, try next ... */ + continue; + } else { + break; + } + } + result = dict_insert(g_ctx->l2tp_session_dict, &l2tp_session->key); + if(!result.inserted) { + LOG(ERROR, "L2TP Error (%s) Failed to add session\n", + l2tp_tunnel_hostname(l2tp_tunnel)); + free(l2tp_session); + return; + } + *result.datum_ptr = l2tp_session; + CIRCLEQ_INSERT_TAIL(&l2tp_tunnel->session_qhead, l2tp_session, session_qnode); + if(g_ctx->l2tp_sessions > g_ctx->l2tp_sessions_max) { + g_ctx->l2tp_sessions_max = g_ctx->l2tp_sessions; + } + bbl_l2tp_send(l2tp_tunnel, l2tp_session, L2TP_MESSAGE_ICRQ); +} + +/** + * bbl_l2tp_sccrp_rx + * + * Handle SCCRP received from LNS (LAC mode). + */ +static void +bbl_l2tp_sccrp_rx(bbl_network_interface_s *interface, + bbl_l2tp_tunnel_s *l2tp_tunnel, + bbl_ethernet_header_s *eth, bbl_l2tp_s *l2tp) +{ + uint8_t digest[L2TP_MD5_DIGEST_LEN]; + MD5_CTX md5_ctx; + uint8_t l2tp_type; + + UNUSED(interface); + UNUSED(eth); + + if(l2tp_tunnel->state != BBL_L2TP_TUNNEL_WAIT_CTR_CONN) { + return; + } + if(!bbl_l2tp_avp_decode_tunnel(l2tp, l2tp_tunnel)) { + LOG(ERROR, "L2TP Error (%s) Invalid SCCRP received from %s\n", + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel->result_code = 2; + l2tp_tunnel->error_code = 6; + l2tp_tunnel->error_message = "decode error"; + bbl_l2tp_tunnel_update_state(l2tp_tunnel, BBL_L2TP_TUNNEL_SEND_STOPCCN); + bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_STOPCCN); + return; + } + /* Validate challenge response if secret is configured */ + if(l2tp_tunnel->client->secret) { + if(l2tp_tunnel->peer_challenge_response_len) { + l2tp_type = L2TP_MESSAGE_SCCRP; + MD5_Init(&md5_ctx); + MD5_Update(&md5_ctx, &l2tp_type, 1); + MD5_Update(&md5_ctx, (unsigned char *)l2tp_tunnel->client->secret, strlen(l2tp_tunnel->client->secret)); + MD5_Update(&md5_ctx, l2tp_tunnel->challenge, l2tp_tunnel->challenge_len); + MD5_Final(digest, &md5_ctx); + if(memcmp(digest, l2tp_tunnel->peer_challenge_response, L2TP_MD5_DIGEST_LEN) != 0) { + LOG(ERROR, "L2TP Error (%s) Wrong challenge response in SCCRP from %s\n", + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel->result_code = 2; + l2tp_tunnel->error_code = 6; + l2tp_tunnel->error_message = "challenge authentication failed"; + bbl_l2tp_tunnel_update_state(l2tp_tunnel, BBL_L2TP_TUNNEL_SEND_STOPCCN); + bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_STOPCCN); + return; + } + } else { + LOG(ERROR, "L2TP Error (%s) Missing challenge response in SCCRP from %s\n", + l2tp_tunnel_hostname(l2tp_tunnel), format_ipv4_address(&l2tp_tunnel->peer_ip)); + l2tp_tunnel->result_code = 2; + l2tp_tunnel->error_code = 6; + l2tp_tunnel->error_message = "missing challenge response"; + bbl_l2tp_tunnel_update_state(l2tp_tunnel, BBL_L2TP_TUNNEL_SEND_STOPCCN); + bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_STOPCCN); + return; + } + /* Compute challenge response for SCCCN */ + if(l2tp_tunnel->peer_challenge_len) { + l2tp_tunnel->challenge_response = malloc(L2TP_MD5_DIGEST_LEN); + l2tp_tunnel->challenge_response_len = L2TP_MD5_DIGEST_LEN; + l2tp_type = L2TP_MESSAGE_SCCCN; + MD5_Init(&md5_ctx); + MD5_Update(&md5_ctx, &l2tp_type, 1); + MD5_Update(&md5_ctx, (unsigned char *)l2tp_tunnel->client->secret, strlen(l2tp_tunnel->client->secret)); + MD5_Update(&md5_ctx, l2tp_tunnel->peer_challenge, l2tp_tunnel->peer_challenge_len); + MD5_Final(l2tp_tunnel->challenge_response, &md5_ctx); + } + } + /* Now that peer_tunnel_id is known, patch it into the pre-built ZLB packet. + * The ZLB was encoded in bbl_l2tp_client_connect() when peer_tunnel_id was + * still 0; the tunnel_id field sits 4 bytes before the Ns field (tunnel_id(2) + * + session_id(2)), so its offset is ns_offset - 4. */ + if(l2tp_tunnel->zlb_qnode) { + *(uint16_t*)(l2tp_tunnel->zlb_qnode->packet + l2tp_tunnel->zlb_qnode->ns_offset - 4) = + htobe16(l2tp_tunnel->peer_tunnel_id); + } + + /* Send SCCCN and transition to established */ + bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_SCCCN); + bbl_l2tp_tunnel_update_state(l2tp_tunnel, BBL_L2TP_TUNNEL_ESTABLISHED); + + /* Initiate first session */ + bbl_l2tp_client_session_connect(l2tp_tunnel); +} + +/** + * bbl_l2tp_icrp_rx + * + * Handle ICRP received from LNS (LAC mode). + */ +static void +bbl_l2tp_icrp_rx(bbl_network_interface_s *interface, + bbl_l2tp_session_s *l2tp_session, + bbl_ethernet_header_s *eth, bbl_l2tp_s *l2tp) +{ + bbl_l2tp_tunnel_s *l2tp_tunnel = l2tp_session->tunnel; + + UNUSED(interface); + UNUSED(eth); + + if(!l2tp_session) { + return; + } + if(!bbl_l2tp_avp_decode_session(l2tp, l2tp_tunnel, l2tp_session)) { + l2tp_session->result_code = 2; + l2tp_session->error_code = 6; + l2tp_session->error_message = "decode error"; + bbl_l2tp_send(l2tp_tunnel, l2tp_session, L2TP_MESSAGE_CDN); + bbl_l2tp_session_delete(l2tp_session); + return; + } + if(l2tp_session->state == BBL_L2TP_SESSION_WAIT_CONN) { + /* Send ICCN */ + bbl_l2tp_send(l2tp_tunnel, l2tp_session, L2TP_MESSAGE_ICCN); + l2tp_session->state = BBL_L2TP_SESSION_ESTABLISHED; + LOG(L2TP, "L2TP Info (%s) Tunnel (%u) to %s (%s) session (%u) established\n", + l2tp_tunnel_hostname(l2tp_tunnel), l2tp_tunnel->tunnel_id, + l2tp_tunnel->peer_name, + format_ipv4_address(&l2tp_tunnel->peer_ip), + l2tp_session->key.session_id); + } +} + + +/** + * bbl_l2tp_client_connect + * + * Initiate a new L2TP tunnel as LAC by sending SCCRQ + * to the configured LNS server. + * + * @param l2tp_client L2TP client configuration. + * @return The new L2TP tunnel on success, or NULL on error. + */ +bbl_l2tp_tunnel_s * +bbl_l2tp_client_connect(bbl_l2tp_client_s *l2tp_client) +{ + bbl_network_interface_s *network_interface; + bbl_l2tp_tunnel_s *l2tp_tunnel; + bbl_l2tp_session_s *l2tp_session; + dict_insert_result result; + void **search; + + /* Find the network interface */ + network_interface = bbl_network_interface_get(l2tp_client->network_interface); + if(!network_interface) { + LOG(ERROR, "L2TP Error (%s) Network interface %s not found\n", + l2tp_client->name, l2tp_client->network_interface ? l2tp_client->network_interface : "default"); + return NULL; + } + + /* Create tunnel */ + l2tp_tunnel = calloc(1, sizeof(bbl_l2tp_tunnel_s)); + g_ctx->l2tp_tunnels++; + CIRCLEQ_INIT(&l2tp_tunnel->tx_qhead); + CIRCLEQ_INIT(&l2tp_tunnel->session_qhead); + + l2tp_tunnel->is_lac = true; + l2tp_tunnel->client = l2tp_client; + l2tp_tunnel->interface = network_interface; + l2tp_tunnel->peer_ip = l2tp_client->server_ip; + l2tp_tunnel->peer_receive_window = 4; + l2tp_tunnel->ssthresh = 4; + l2tp_tunnel->cwnd = 1; + l2tp_tunnel->state = BBL_L2TP_TUNNEL_WAIT_CTR_CONN; + + /* Add dummy tunnel session (session ID 0) for tunnel-level + * control message lookups in the session dict. */ + l2tp_session = calloc(1, sizeof(bbl_l2tp_session_s)); + l2tp_session->state = BBL_L2TP_SESSION_MAX; + l2tp_session->tunnel = l2tp_tunnel; + l2tp_session->key.session_id = 0; + + /* Assign tunnel id ... */ + while(true) { + l2tp_session->key.tunnel_id = g_ctx->next_tunnel_id++; + if(l2tp_session->key.tunnel_id == 0) continue; /* skip tunnel 0 */ + search = dict_search(g_ctx->l2tp_session_dict, &l2tp_session->key); + if(search) { + continue; + } else { + break; + } + } + l2tp_tunnel->tunnel_id = l2tp_session->key.tunnel_id; + result = dict_insert(g_ctx->l2tp_session_dict, &l2tp_session->key); + if(!result.inserted) { + LOG(ERROR, "L2TP Error (%s) Failed to add tunnel session\n", + l2tp_client->name); + free(l2tp_session); + free(l2tp_tunnel); + return NULL; + } + *result.datum_ptr = l2tp_session; + CIRCLEQ_INSERT_TAIL(&l2tp_tunnel->session_qhead, l2tp_session, session_qnode); + if(g_ctx->l2tp_tunnels > g_ctx->l2tp_tunnels_max) g_ctx->l2tp_tunnels_max = g_ctx->l2tp_tunnels; + + /* L2TP Challenge */ + if(l2tp_client->secret) { + l2tp_tunnel->challenge = malloc(L2TP_MD5_DIGEST_LEN); + l2tp_tunnel->challenge_len = L2TP_MD5_DIGEST_LEN; + RAND_bytes(l2tp_tunnel->challenge, l2tp_tunnel->challenge_len); + } + + /* Add tunnel to client config */ + CIRCLEQ_INSERT_TAIL(&l2tp_client->tunnel_qhead, l2tp_tunnel, tunnel_qnode); + + /* Start control timer */ + timer_add_periodic(&g_ctx->timer_root, &l2tp_tunnel->timer_ctrl, "L2TP Control", 1, 0, l2tp_tunnel, &bbl_l2tp_tunnel_control_job); + + /* Prepare ZLB */ + bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_ZLB); + + /* Send SCCRQ */ + bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_SCCRQ); + + LOG(L2TP, "L2TP Info (%s) Tunnel (%u) SCCRQ sent to %s\n", + l2tp_client->name, + l2tp_tunnel->tunnel_id, + format_ipv4_address(&l2tp_client->server_ip)); + + return l2tp_tunnel; +} + /** * bbl_l2tp_handler_rx * @@ -1209,17 +1518,18 @@ bbl_l2tp_handler_rx(bbl_network_interface_s *interface, bbl_ipv4_s *ipv4 = (bbl_ipv4_s*)eth->next; bbl_l2tp_session_s *l2tp_session; bbl_l2tp_tunnel_s *l2tp_tunnel; - + void **search; l2tp_key_t key = {0}; - void **search = NULL; - if(!g_ctx->config.l2tp_server) { - /* No L2TP server configuration found! */ + if(!g_ctx->config.l2tp_server && !g_ctx->config.l2tp_client) { + /* No L2TP configuration found! */ return; } if(l2tp->type == L2TP_MESSAGE_SCCRQ) { - bbl_l2tp_sccrq_rx(interface, eth, l2tp); + if(g_ctx->config.l2tp_server) { + bbl_l2tp_sccrq_rx(interface, eth, l2tp); + } return; } @@ -1269,7 +1579,9 @@ bbl_l2tp_handler_rx(bbl_network_interface_s *interface, } } /* Reliable Delivery of Control Messages */ - switch(l2tp_tunnel->server->congestion_mode) { + l2tp_congestion_mode_t cmode = l2tp_tunnel->is_lac ? + l2tp_tunnel->client->congestion_mode : l2tp_tunnel->server->congestion_mode; + switch(cmode) { case BBL_L2TP_CONGESTION_AGGRESSIVE: l2tp_tunnel->cwnd = l2tp_tunnel->peer_receive_window; break; @@ -1311,6 +1623,12 @@ bbl_l2tp_handler_rx(bbl_network_interface_s *interface, /* Handle received packet */ if(l2tp_tunnel->state != BBL_L2TP_TUNNEL_TERMINATED) { switch(l2tp->type) { + case L2TP_MESSAGE_SCCRP: + if(l2tp_tunnel->is_lac) { + bbl_l2tp_sccrp_rx(interface, l2tp_tunnel, eth, l2tp); + return; + } + break; case L2TP_MESSAGE_SCCCN: bbl_l2tp_scccn_rx(interface, l2tp_tunnel, eth, l2tp); return; @@ -1320,6 +1638,12 @@ bbl_l2tp_handler_rx(bbl_network_interface_s *interface, case L2TP_MESSAGE_ICRQ: bbl_l2tp_icrq_rx(interface, l2tp_tunnel, eth, l2tp); return; + case L2TP_MESSAGE_ICRP: + if(l2tp_tunnel->is_lac && l2tp_session->key.session_id) { + bbl_l2tp_icrp_rx(interface, l2tp_session, eth, l2tp); + return; + } + break; case L2TP_MESSAGE_ICCN: if(l2tp_session->key.session_id) { bbl_l2tp_iccn_rx(interface, l2tp_session, eth, l2tp); diff --git a/code/bngblaster/src/bbl_l2tp.h b/code/bngblaster/src/bbl_l2tp.h index eee8330c..8a504654 100644 --- a/code/bngblaster/src/bbl_l2tp.h +++ b/code/bngblaster/src/bbl_l2tp.h @@ -339,6 +339,9 @@ bbl_l2tp_handler_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *e void bbl_l2tp_stop_all_tunnel(); +bbl_l2tp_tunnel_s * +bbl_l2tp_client_connect(bbl_l2tp_client_s *l2tp_client); + json_t * l2tp_session_json(bbl_l2tp_session_s *l2tp_session); From 54118786b522256aa024dca62eac7dc0dccd0a84 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Fri, 20 Mar 2026 13:39:46 +0100 Subject: [PATCH 05/25] LAC: display L2TP client stats 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 --- code/bngblaster/src/bbl_interactive.c | 4 ++-- code/bngblaster/src/bbl_l2tp.c | 26 ++++++++++++++++++++++++++ code/bngblaster/src/bbl_stats.c | 6 +++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/code/bngblaster/src/bbl_interactive.c b/code/bngblaster/src/bbl_interactive.c index 2201857f..9e11677e 100644 --- a/code/bngblaster/src/bbl_interactive.c +++ b/code/bngblaster/src/bbl_interactive.c @@ -388,8 +388,8 @@ bbl_interactive_window_job(timer_s *timer) wprintw(stats_win, " RX Packets %10lu (%7lu PPS)\n", g_network_if->stats.li_rx, g_network_if->stats.rate_li_rx.avg); } - VISIBLE((g_ctx->config.l2tp_server)) { - wprintw(stats_win, "\nL2TP LNS Statistics\n"); + VISIBLE((g_ctx->config.l2tp_server || g_ctx->config.l2tp_client)) { + wprintw(stats_win, "\nL2TP LNS/LAC Statistics\n"); wprintw(stats_win, " Tunnels %10u\n", g_ctx->l2tp_tunnels); wprintw(stats_win, " Established %10u\n", g_ctx->l2tp_tunnels_established); wprintw(stats_win, " Sessions %10u\n", g_ctx->l2tp_sessions); diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index 94f167b7..62201d64 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -1998,6 +1998,7 @@ bbl_l2tp_ctrl_tunnels(int fd, uint32_t session_id __attribute__((unused)), json_ json_t *root, *tunnels, *tunnel; bbl_l2tp_server_s *l2tp_server = g_ctx->config.l2tp_server; + bbl_l2tp_client_s *l2tp_client = g_ctx->config.l2tp_client; bbl_l2tp_tunnel_s *l2tp_tunnel; tunnels = json_array(); @@ -2027,6 +2028,31 @@ bbl_l2tp_ctrl_tunnels(int fd, uint32_t session_id __attribute__((unused)), json_ l2tp_server = l2tp_server->next; } + while(l2tp_client) { + CIRCLEQ_FOREACH(l2tp_tunnel, &l2tp_client->tunnel_qhead, tunnel_qnode) { + + tunnel = json_pack("{ss ss ss si si ss ss ss ss si si si si si sI sI}", + "state", l2tp_tunnel_state_string(l2tp_tunnel->state), + "client-name", l2tp_client->name, + "server-address", format_ipv4_address(&l2tp_client->server_ip), + "tunnel-id", l2tp_tunnel->tunnel_id, + "peer-tunnel-id", l2tp_tunnel->peer_tunnel_id, + "peer-name", string_or_na(l2tp_tunnel->peer_name), + "peer-address", format_ipv4_address(&l2tp_tunnel->peer_ip), + "peer-vendor", string_or_na(l2tp_tunnel->peer_vendor), + "secret", string_or_na(l2tp_client->secret), + "control-packets-rx", l2tp_tunnel->stats.control_rx, + "control-packets-rx-dup", l2tp_tunnel->stats.control_rx_dup, + "control-packets-rx-out-of-order", l2tp_tunnel->stats.control_rx_ooo, + "control-packets-tx", l2tp_tunnel->stats.control_tx, + "control-packets-tx-retry", l2tp_tunnel->stats.control_retry, + "data-packets-rx", l2tp_tunnel->stats.data_rx, + "data-packets-tx", l2tp_tunnel->stats.data_tx); + json_array_append_new(tunnels, tunnel); + } + l2tp_client = l2tp_client->next; + } + root = json_pack("{ss si so}", "status", "ok", "code", 200, diff --git a/code/bngblaster/src/bbl_stats.c b/code/bngblaster/src/bbl_stats.c index f2df23c4..22eb74ef 100644 --- a/code/bngblaster/src/bbl_stats.c +++ b/code/bngblaster/src/bbl_stats.c @@ -427,8 +427,8 @@ bbl_stats_stdout(bbl_stats_s *stats) { printf(" Flows: %10lu\n", dict_count(g_ctx->li_flow_dict)); printf(" RX Packets: %10lu\n", stats->li_rx); } - if(g_ctx->config.l2tp_server) { - printf("\nL2TP LNS Statistics:"); + if(g_ctx->config.l2tp_server || g_ctx->config.l2tp_client) { + printf("\nL2TP LNS/LAC Statistics:"); printf("\n------------------------------------------------------------------------------\n"); printf(" Tunnels: %10u\n", g_ctx->l2tp_tunnels_max); printf(" Established: %10u\n", g_ctx->l2tp_tunnels_established_max); @@ -840,7 +840,7 @@ bbl_stats_json(bbl_stats_s * stats) json_object_set_new(jobj_sub, "rx-packets", json_integer(stats->li_rx)); json_object_set_new(jobj, "li-statistics", jobj_sub); } - if(g_ctx->config.l2tp_server) { + if(g_ctx->config.l2tp_server || g_ctx->config.l2tp_client) { jobj_sub = json_object(); json_object_set_new(jobj_sub, "tunnels", json_integer(g_ctx->l2tp_tunnels_max)); json_object_set_new(jobj_sub, "tunnels-established", json_integer(g_ctx->l2tp_tunnels_established_max)); From 0f25df73216b12bf2e390f25db2f1f2e2f9c6c28 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Wed, 18 Mar 2026 11:09:10 +0100 Subject: [PATCH 06/25] LAC: initialize L2TP tunnels at startup 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 --- code/bngblaster/src/bbl.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/bngblaster/src/bbl.c b/code/bngblaster/src/bbl.c index d1212c12..bfb5c899 100644 --- a/code/bngblaster/src/bbl.c +++ b/code/bngblaster/src/bbl.c @@ -239,6 +239,7 @@ bbl_ctrl_job(timer_s *timer) bbl_session_s *session; bbl_interface_s *interface; bbl_network_interface_s *network_interface; + bbl_l2tp_client_s *l2tp_client = g_ctx->config.l2tp_client; uint32_t i; @@ -270,6 +271,13 @@ bbl_ctrl_job(timer_s *timer) g_init_phase = false; LOG_NOARG(INFO, "All network interfaces resolved\n"); clock_gettime(CLOCK_MONOTONIC, &g_ctx->timestamp_resolved); + + /* Init L2TP client tunnels. */ + while(l2tp_client) { + bbl_l2tp_client_connect(l2tp_client); + l2tp_client = l2tp_client->next; + } + } if(g_teardown) { From 2e19ae6437181b56581998be35073747a1e0b49e Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Fri, 20 Mar 2026 16:07:23 +0100 Subject: [PATCH 07/25] LAC: introduce new PPPOL2TP access type 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 --- code/bngblaster/src/bbl.c | 3 +++ code/bngblaster/src/bbl_config.c | 2 ++ code/bngblaster/src/bbl_config.h | 2 +- code/bngblaster/src/bbl_def.h | 3 ++- schemas/bngblaster-config.json | 10 +++++++++- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/code/bngblaster/src/bbl.c b/code/bngblaster/src/bbl.c index bfb5c899..63757b58 100644 --- a/code/bngblaster/src/bbl.c +++ b/code/bngblaster/src/bbl.c @@ -387,6 +387,9 @@ bbl_ctrl_job(timer_s *timer) } } break; + case ACCESS_TYPE_PPPOL2TP: + /* later, just silent compiler for now */ + break; } bbl_session_tx_qnode_insert(session); /* Remove from idle queue */ diff --git a/code/bngblaster/src/bbl_config.c b/code/bngblaster/src/bbl_config.c index ce009057..8f554c72 100644 --- a/code/bngblaster/src/bbl_config.c +++ b/code/bngblaster/src/bbl_config.c @@ -1128,6 +1128,8 @@ json_parse_access_interface(json_t *access_interface, bbl_access_config_s *acces access_config->access_type = ACCESS_TYPE_IPOE; access_config->ipv4_enable = g_ctx->config.ipoe_ipv4_enable; access_config->ipv6_enable = g_ctx->config.ipoe_ipv6_enable; + } else if(strcmp(s, "pppol2tp") == 0) { + access_config->access_type = ACCESS_TYPE_PPPOL2TP; } else { fprintf(stderr, "JSON config error: Invalid value for access->type\n"); return false; diff --git a/code/bngblaster/src/bbl_config.h b/code/bngblaster/src/bbl_config.h index 3ccdde22..f025c72a 100644 --- a/code/bngblaster/src/bbl_config.h +++ b/code/bngblaster/src/bbl_config.h @@ -48,7 +48,7 @@ typedef struct bbl_access_config_ char *network_interface; char *a10nsp_interface; - access_type_t access_type; /* pppoe or ipoe */ + access_type_t access_type; /* pppoe, ipoe or pppol2tp */ vlan_mode_t vlan_mode; /* 1:1 (default) or N:1 */ uint16_t l2tp_client_group_id; diff --git a/code/bngblaster/src/bbl_def.h b/code/bngblaster/src/bbl_def.h index afd9c822..e3e2af0a 100644 --- a/code/bngblaster/src/bbl_def.h +++ b/code/bngblaster/src/bbl_def.h @@ -135,7 +135,8 @@ typedef enum { typedef enum { ACCESS_TYPE_PPPOE = 0, - ACCESS_TYPE_IPOE + ACCESS_TYPE_IPOE, + ACCESS_TYPE_PPPOL2TP, } __attribute__ ((__packed__)) access_type_t; typedef enum { diff --git a/schemas/bngblaster-config.json b/schemas/bngblaster-config.json index 84ec7dfe..33f25507 100644 --- a/schemas/bngblaster-config.json +++ b/schemas/bngblaster-config.json @@ -1746,7 +1746,8 @@ "description": "Access type", "enum": [ "pppoe", - "ipoe" + "ipoe", + "pppol2tp" ], "default": "pppoe" }, @@ -2113,6 +2114,13 @@ "minimum": 0, "maximum": 65535 }, + "l2tp-client-group-id": { + "type": "integer", + "description": "L2TP client group identifier (used with type pppol2tp)", + "default": 0, + "minimum": 0, + "maximum": 65535 + }, "tun": { "type": "boolean", "description": "Create dedicated TUN interface for each session", From 1606df4553b0b5dfeed348256115d009d8b9d55c Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Thu, 12 Mar 2026 09:37:59 +0100 Subject: [PATCH 08/25] LAC: count PPP over L2TP sessions 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 --- code/bngblaster/src/bbl_ctx.h | 1 + code/bngblaster/src/bbl_interactive.c | 3 ++- code/bngblaster/src/bbl_session.c | 5 ++++- code/bngblaster/src/bbl_stats.c | 4 +++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/code/bngblaster/src/bbl_ctx.h b/code/bngblaster/src/bbl_ctx.h index 6037bed3..91869bed 100644 --- a/code/bngblaster/src/bbl_ctx.h +++ b/code/bngblaster/src/bbl_ctx.h @@ -35,6 +35,7 @@ typedef struct bbl_ctx_ uint32_t interfaces; uint32_t sessions; uint32_t sessions_pppoe; + uint32_t sessions_pppol2tp; uint32_t sessions_ipoe; uint32_t sessions_established; uint32_t sessions_established_max; diff --git a/code/bngblaster/src/bbl_interactive.c b/code/bngblaster/src/bbl_interactive.c index 9e11677e..8c107907 100644 --- a/code/bngblaster/src/bbl_interactive.c +++ b/code/bngblaster/src/bbl_interactive.c @@ -314,7 +314,8 @@ bbl_interactive_window_job(timer_s *timer) if(g_view_selected == UI_VIEW_DEFAULT) { VISIBLE((g_ctx->sessions)) { - wprintw(stats_win, "\nSessions %10u (%u PPPoE / %u IPoE)\n", g_ctx->sessions, g_ctx->sessions_pppoe, g_ctx->sessions_ipoe); + wprintw(stats_win, "\nSessions %10u (%u PPPoE / %u PPPoL2TP / %u IPoE)\n", + g_ctx->sessions, g_ctx->sessions_pppoe, g_ctx->sessions_pppol2tp, g_ctx->sessions_ipoe); /* Progress bar established sessions */ wprintw(stats_win, " Established %10u [", g_ctx->sessions_established); diff --git a/code/bngblaster/src/bbl_session.c b/code/bngblaster/src/bbl_session.c index 768ae010..c8764a68 100644 --- a/code/bngblaster/src/bbl_session.c +++ b/code/bngblaster/src/bbl_session.c @@ -1086,6 +1086,8 @@ bbl_sessions_init() g_ctx->sessions++; if(session->access_type == ACCESS_TYPE_PPPOE) { g_ctx->sessions_pppoe++; + } else if(session->access_type == ACCESS_TYPE_PPPOL2TP) { + g_ctx->sessions_pppol2tp++; } else { g_ctx->sessions_ipoe++; } @@ -1603,12 +1605,13 @@ int bbl_session_ctrl_counters(int fd, uint32_t session_id __attribute__((unused)), json_t *arguments __attribute__((unused))) { int result = 0; - json_t *root = json_pack("{ss si s{si si si si si si si si si si si si si si si sf sf sf sf si si si si}}", + json_t *root = json_pack("{ss si s{si si si si si si si si si si si si si si si si sf sf sf sf si si si si}}", "status", "ok", "code", 200, "session-counters", "sessions", g_ctx->config.sessions, "sessions-pppoe", g_ctx->sessions_pppoe, + "sessions-pppol2tp", g_ctx->sessions_pppol2tp, "sessions-ipoe", g_ctx->sessions_ipoe, "sessions-established", g_ctx->sessions_established, "sessions-established-max", g_ctx->sessions_established_max, diff --git a/code/bngblaster/src/bbl_stats.c b/code/bngblaster/src/bbl_stats.c index 22eb74ef..c3049e9a 100644 --- a/code/bngblaster/src/bbl_stats.c +++ b/code/bngblaster/src/bbl_stats.c @@ -412,7 +412,8 @@ bbl_stats_stdout(bbl_stats_s *stats) { printf("\n==============================================================================\n"); printf("Test Duration: %lus\n", test_duration()); if(g_ctx->sessions) { - printf("Sessions PPPoE: %u IPoE: %u\n", g_ctx->sessions_pppoe, g_ctx->sessions_ipoe); + printf("Sessions PPPoE: %u PPPoL2TP: %u IPoE: %u\n", g_ctx->sessions_pppoe, g_ctx->sessions_pppol2tp, + g_ctx->sessions_ipoe); printf("Sessions established: %u/%u\n", g_ctx->sessions_established_max, g_ctx->sessions); printf("DHCPv6 sessions established: %u\n", g_ctx->dhcpv6_established_max); printf("Setup Time: %u ms\n", g_ctx->stats.setup_time); @@ -823,6 +824,7 @@ bbl_stats_json(bbl_stats_s * stats) if(g_ctx->sessions) { json_object_set_new(jobj, "sessions", json_integer(g_ctx->config.sessions)); json_object_set_new(jobj, "sessions-pppoe", json_integer(g_ctx->sessions_pppoe)); + json_object_set_new(jobj, "sessions-pppol2tp", json_integer(g_ctx->sessions_pppol2tp)); json_object_set_new(jobj, "sessions-ipoe", json_integer(g_ctx->sessions_ipoe)); json_object_set_new(jobj, "sessions-established", json_integer(g_ctx->sessions_established_max)); json_object_set_new(jobj, "sessions-flapped", json_integer(g_ctx->sessions_flapped)); From 748dc0dee1b1e6dd7f24bfe059aee671cbbe7adf Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Thu, 2 Apr 2026 17:52:55 +0200 Subject: [PATCH 09/25] LAC: register client-address for ARP replies 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 --- code/bngblaster/src/bbl_l2tp.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index 62201d64..950fa188 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -1428,6 +1428,25 @@ bbl_l2tp_client_connect(bbl_l2tp_client_s *l2tp_client) return NULL; } + /* Register client_address for ARP replies if distinct from the interface address */ + if(l2tp_client->client_address && l2tp_client->client_address != network_interface->ip.address) { + bbl_secondary_ip_s *secondary_ip = network_interface->secondary_ip_addresses; + bool already_registered = false; + while(secondary_ip) { + if(secondary_ip->ip == l2tp_client->client_address) { + already_registered = true; + break; + } + secondary_ip = secondary_ip->next; + } + if(!already_registered) { + secondary_ip = calloc(1, sizeof(bbl_secondary_ip_s)); + secondary_ip->ip = l2tp_client->client_address; + secondary_ip->next = network_interface->secondary_ip_addresses; + network_interface->secondary_ip_addresses = secondary_ip; + } + } + /* Create tunnel */ l2tp_tunnel = calloc(1, sizeof(bbl_l2tp_tunnel_s)); g_ctx->l2tp_tunnels++; From 77832a27d5764b1ed0c222714c758bde7d69d25e Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Fri, 20 Mar 2026 16:24:24 +0100 Subject: [PATCH 10/25] LAC: start PPPoL2TP sessions 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 --- code/bngblaster/src/bbl.c | 16 ++--- code/bngblaster/src/bbl_def.h | 1 + code/bngblaster/src/bbl_l2tp.c | 110 ++++++++++++++++++++++++++++-- code/bngblaster/src/bbl_l2tp.h | 7 ++ code/bngblaster/src/bbl_session.c | 1 + code/bngblaster/src/bbl_session.h | 1 + 6 files changed, 122 insertions(+), 14 deletions(-) diff --git a/code/bngblaster/src/bbl.c b/code/bngblaster/src/bbl.c index 63757b58..c516e7bc 100644 --- a/code/bngblaster/src/bbl.c +++ b/code/bngblaster/src/bbl.c @@ -18,6 +18,7 @@ #include "bbl_stream.h" #include "bbl_dhcp.h" #include "bbl_dhcpv6.h" +#include "bbl_l2tp.h" static unsigned int ctrl_job_period_ns = MSEC100; @@ -239,7 +240,7 @@ bbl_ctrl_job(timer_s *timer) bbl_session_s *session; bbl_interface_s *interface; bbl_network_interface_s *network_interface; - bbl_l2tp_client_s *l2tp_client = g_ctx->config.l2tp_client; + bbl_l2tp_tunnel_s *l2tp_tunnel; uint32_t i; @@ -272,12 +273,6 @@ bbl_ctrl_job(timer_s *timer) LOG_NOARG(INFO, "All network interfaces resolved\n"); clock_gettime(CLOCK_MONOTONIC, &g_ctx->timestamp_resolved); - /* Init L2TP client tunnels. */ - while(l2tp_client) { - bbl_l2tp_client_connect(l2tp_client); - l2tp_client = l2tp_client->next; - } - } if(g_teardown) { @@ -388,7 +383,12 @@ bbl_ctrl_job(timer_s *timer) } break; case ACCESS_TYPE_PPPOL2TP: - /* later, just silent compiler for now */ + /* PPP over L2TP (LAC) */ + session->session_state = BBL_L2TP_WAIT; + l2tp_tunnel = bbl_l2tp_client_session_get_tunnel(session); + if(l2tp_tunnel) { + bbl_l2tp_client_session_connect(l2tp_tunnel, session); + } break; } bbl_session_tx_qnode_insert(session); diff --git a/code/bngblaster/src/bbl_def.h b/code/bngblaster/src/bbl_def.h index e3e2af0a..81f21771 100644 --- a/code/bngblaster/src/bbl_def.h +++ b/code/bngblaster/src/bbl_def.h @@ -189,6 +189,7 @@ typedef enum { BBL_PPP_LINK, /* send LCP requests */ BBL_PPP_AUTH, /* send authentication requests */ BBL_PPP_NETWORK, /* send NCP requests */ + BBL_L2TP_WAIT, /* wait for L2TP session */ BBL_ESTABLISHED, /* established */ BBL_PPP_TERMINATING, /* send LCP terminate requests */ BBL_TERMINATING, /* send PADT */ diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index 950fa188..1939c162 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -1231,16 +1231,21 @@ bbl_l2tp_data_rx(bbl_network_interface_s *interface, /** * bbl_l2tp_client_session_connect * - * Initiate a new L2TP session within an established LAC tunnel - * by sending ICRQ. + * Initiate a new L2TP session within an established LAC tunnel by sending ICRQ. */ -static void -bbl_l2tp_client_session_connect(bbl_l2tp_tunnel_s *l2tp_tunnel) +void +bbl_l2tp_client_session_connect(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_session_s *session) { bbl_l2tp_session_s *l2tp_session; dict_insert_result result; void **search; + if(l2tp_tunnel->state != BBL_L2TP_TUNNEL_ESTABLISHED) { + /* Queue session to pending list */ + CIRCLEQ_INSERT_TAIL(&l2tp_tunnel->pending_session_qhead, session, session_l2tp_qnode); + return; + } + l2tp_session = calloc(1, sizeof(bbl_l2tp_session_s)); g_ctx->l2tp_sessions++; l2tp_session->tunnel = l2tp_tunnel; @@ -1359,8 +1364,12 @@ bbl_l2tp_sccrp_rx(bbl_network_interface_s *interface, bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_SCCCN); bbl_l2tp_tunnel_update_state(l2tp_tunnel, BBL_L2TP_TUNNEL_ESTABLISHED); - /* Initiate first session */ - bbl_l2tp_client_session_connect(l2tp_tunnel); + /* Process pending sessions */ + while(!CIRCLEQ_EMPTY(&l2tp_tunnel->pending_session_qhead)) { + bbl_session_s *session = CIRCLEQ_FIRST(&l2tp_tunnel->pending_session_qhead); + CIRCLEQ_REMOVE(&l2tp_tunnel->pending_session_qhead, session, session_l2tp_qnode); + bbl_l2tp_client_session_connect(l2tp_tunnel, session); + } } /** @@ -1452,6 +1461,7 @@ bbl_l2tp_client_connect(bbl_l2tp_client_s *l2tp_client) g_ctx->l2tp_tunnels++; CIRCLEQ_INIT(&l2tp_tunnel->tx_qhead); CIRCLEQ_INIT(&l2tp_tunnel->session_qhead); + CIRCLEQ_INIT(&l2tp_tunnel->pending_session_qhead); l2tp_tunnel->is_lac = true; l2tp_tunnel->client = l2tp_client; @@ -1511,6 +1521,11 @@ bbl_l2tp_client_connect(bbl_l2tp_client_s *l2tp_client) /* Send SCCRQ */ bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_SCCRQ); + /* Mark the start of session establishment for setup-time calculation. + * Equivalent to PADI in PPPoE: the first outbound control packet. */ + if(!g_ctx->stats.first_session_tx.tv_sec) { + clock_gettime(CLOCK_MONOTONIC, &g_ctx->stats.first_session_tx); + } LOG(L2TP, "L2TP Info (%s) Tunnel (%u) SCCRQ sent to %s\n", l2tp_client->name, @@ -1520,6 +1535,89 @@ bbl_l2tp_client_connect(bbl_l2tp_client_s *l2tp_client) return l2tp_tunnel; } +/** + * bbl_l2tp_client_session_get_tunnel + * + * Return a live LAC tunnel for the session's group-id, creating one per + * l2tp-client config entry on demand. A tunnel is "live" when its state + * is below BBL_L2TP_TUNNEL_SEND_STOPCCN; a tearing-down tunnel is not + * reused so that reconnecting sessions get a fresh tunnel. + */ +bbl_l2tp_tunnel_s * +bbl_l2tp_client_session_get_tunnel(bbl_session_s *session) +{ + bbl_l2tp_client_s *l2tp_client; + bbl_l2tp_tunnel_s *l2tp_tunnel; + uint16_t group_id = session->access_config->l2tp_client_group_id; + uint32_t tunnel_count = 0; + uint32_t target; + bool has_live; + + if(!group_id) { + LOG(ERROR, "L2TP Error (ID: %u) no L2TP client group-id was specified\n", session->session_id); + bbl_session_update_state(session, BBL_TERMINATED); + return NULL; + } + + /* Ensure every l2tp-client config entry in the group has a live tunnel. + * If the entry's tunnel_qhead is empty, or all its tunnels are already + * tearing down, create a new one now. */ + l2tp_client = g_ctx->config.l2tp_client; + while(l2tp_client) { + if(l2tp_client->group_id == group_id) { + has_live = false; + CIRCLEQ_FOREACH(l2tp_tunnel, &l2tp_client->tunnel_qhead, tunnel_qnode) { + if(l2tp_tunnel->is_lac && l2tp_tunnel->state < BBL_L2TP_TUNNEL_SEND_STOPCCN) { + has_live = true; + break; + } + } + if(!has_live) { + bbl_l2tp_client_connect(l2tp_client); + } + } + l2tp_client = l2tp_client->next; + } + + /* First pass: count live LAC tunnels in the group. */ + l2tp_client = g_ctx->config.l2tp_client; + while(l2tp_client) { + if(l2tp_client->group_id == group_id) { + CIRCLEQ_FOREACH(l2tp_tunnel, &l2tp_client->tunnel_qhead, tunnel_qnode) { + if(l2tp_tunnel->is_lac && l2tp_tunnel->state < BBL_L2TP_TUNNEL_SEND_STOPCCN) { + tunnel_count++; + } + } + } + l2tp_client = l2tp_client->next; + } + + if(!tunnel_count) { + LOG(ERROR, "L2TP Error (ID: %u) no tunnel available for L2TP client group-id %u\n", + session->session_id, group_id); + bbl_session_update_state(session, BBL_TERMINATED); + return NULL; + } + + /* Second pass: pick tunnel at index session_id % tunnel_count, + * giving an even round-robin distribution without any stored state. */ + target = session->session_id % tunnel_count; + tunnel_count = 0; + l2tp_client = g_ctx->config.l2tp_client; + while(l2tp_client) { + if(l2tp_client->group_id == group_id) { + CIRCLEQ_FOREACH(l2tp_tunnel, &l2tp_client->tunnel_qhead, tunnel_qnode) { + if(!l2tp_tunnel->is_lac || l2tp_tunnel->state >= BBL_L2TP_TUNNEL_SEND_STOPCCN) continue; + if(tunnel_count == target) return l2tp_tunnel; + tunnel_count++; + } + } + l2tp_client = l2tp_client->next; + } + + return NULL; +} + /** * bbl_l2tp_handler_rx * diff --git a/code/bngblaster/src/bbl_l2tp.h b/code/bngblaster/src/bbl_l2tp.h index 8a504654..add6d87e 100644 --- a/code/bngblaster/src/bbl_l2tp.h +++ b/code/bngblaster/src/bbl_l2tp.h @@ -163,6 +163,7 @@ typedef struct bbl_l2tp_tunnel_ CIRCLEQ_ENTRY(bbl_l2tp_tunnel_) tunnel_qnode; CIRCLEQ_HEAD(session_, bbl_l2tp_session_) session_qhead; + CIRCLEQ_HEAD(pending_session_, bbl_session_) pending_session_qhead; CIRCLEQ_HEAD(txq_, bbl_l2tp_queue_) tx_qhead; /* Pointer to corresponding network interface */ @@ -360,4 +361,10 @@ bbl_l2tp_ctrl_session_terminate(int fd, uint32_t session_id, json_t *arguments); int bbl_l2tp_ctrl_tunnels(int fd, uint32_t session_id __attribute__((unused)), json_t *arguments __attribute__((unused))); +bbl_l2tp_tunnel_s * +bbl_l2tp_client_session_get_tunnel(bbl_session_s *session); + +void +bbl_l2tp_client_session_connect(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_session_s *session); + #endif diff --git a/code/bngblaster/src/bbl_session.c b/code/bngblaster/src/bbl_session.c index c8764a68..55e8cce0 100644 --- a/code/bngblaster/src/bbl_session.c +++ b/code/bngblaster/src/bbl_session.c @@ -39,6 +39,7 @@ session_state_string(uint32_t state) case BBL_PPP_LINK: return "PPP Link"; case BBL_PPP_AUTH: return "PPP Authentication"; case BBL_PPP_NETWORK: return "PPP Network"; + case BBL_L2TP_WAIT: return "L2TP Wait"; case BBL_ESTABLISHED: return "Established"; case BBL_PPP_TERMINATING: return "PPP Terminating"; case BBL_TERMINATING: return "Terminating"; diff --git a/code/bngblaster/src/bbl_session.h b/code/bngblaster/src/bbl_session.h index 953e03e1..e64ab171 100644 --- a/code/bngblaster/src/bbl_session.h +++ b/code/bngblaster/src/bbl_session.h @@ -34,6 +34,7 @@ typedef struct bbl_session_ CIRCLEQ_ENTRY(bbl_session_) session_tx_qnode; CIRCLEQ_ENTRY(bbl_session_) session_network_tx_qnode; CIRCLEQ_ENTRY(bbl_session_) session_a10nsp_tx_qnode; + CIRCLEQ_ENTRY(bbl_session_) session_l2tp_qnode; bbl_access_config_s *access_config; bbl_access_interface_s *access_interface; /* where this session is attached to */ From 1c6ae9ef1af786f2b063ac89fcc79a0039964af1 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Mon, 23 Mar 2026 18:53:21 +0100 Subject: [PATCH 11/25] LAC: make PPP Rx more generic 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 --- code/bngblaster/src/bbl_access.c | 156 +++++++++++++++++-------------- code/bngblaster/src/bbl_access.h | 9 +- code/bngblaster/src/bbl_dhcp.c | 16 ++-- code/bngblaster/src/bbl_tcp.c | 8 +- code/bngblaster/src/bbl_tcp.h | 4 +- 5 files changed, 105 insertions(+), 88 deletions(-) diff --git a/code/bngblaster/src/bbl_access.c b/code/bngblaster/src/bbl_access.c index 2826ccca..cae74cfd 100644 --- a/code/bngblaster/src/bbl_access.c +++ b/code/bngblaster/src/bbl_access.c @@ -532,6 +532,10 @@ bbl_access_rx_icmpv6(bbl_access_interface_s *interface, { bbl_icmpv6_s *icmpv6 = (bbl_icmpv6_s*)ipv6->next; + if(!eth) { + return false; + } + if(session->access_type == ACCESS_TYPE_PPPOE && session->ip6cp_state != BBL_PPP_OPENED) { return false; @@ -616,6 +620,11 @@ static bool bbl_access_rx_icmp(bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_ipv4_s *ipv4) { bbl_icmp_s *icmp = (bbl_icmp_s*)ipv4->next; + + if(!eth) { + return false; + } + if(session->ip_address && session->ip_address == ipv4->dst) { if(icmp->type == ICMP_TYPE_ECHO_REQUEST) { /* Send ICMP reply... */ @@ -636,11 +645,16 @@ bbl_access_rx_ipv4_mc(bbl_access_interface_s *interface, bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_ipv4_s *ipv4) { - bbl_bbl_s *bbl = eth->bbl; + bbl_bbl_s *bbl; bbl_igmp_group_s *group = NULL; uint64_t loss; int i; + if(!eth) { + return; + } + bbl = eth->bbl; + for(i=0; i < IGMP_MAX_GROUPS; i++) { group = &session->igmp_groups[i]; if(ipv4->dst == group->group) { @@ -690,7 +704,7 @@ bbl_access_rx_ipv4(bbl_access_interface_s *interface, if(ipv4->offset & ~IPV4_DF) { session->stats.accounting_packets_rx++; - session->stats.accounting_bytes_rx += eth->length; + session->stats.accounting_bytes_rx += eth ? eth->length : ipv4->len; session->stats.ipv4_fragmented_rx++; interface->stats.ipv4_fragmented_rx++; bbl_fragment_rx(interface, NULL, eth, ipv4); @@ -717,14 +731,14 @@ bbl_access_rx_ipv4(bbl_access_interface_s *interface, } break; case PROTOCOL_IPV4_TCP: - bbl_tcp_ipv4_rx_session(session, eth, ipv4); + bbl_tcp_ipv4_rx_session(session, ipv4); break; default: break; } session->stats.accounting_packets_rx++; - session->stats.accounting_bytes_rx += eth->length; + session->stats.accounting_bytes_rx += eth ? eth->length : ipv4->len; /* All IPv4 multicast addresses start with 1110 */ if((ipv4->dst & htobe32(0xf0000000)) == htobe32(0xe0000000)) { @@ -758,13 +772,13 @@ bbl_access_rx_ipv6(bbl_access_interface_s *interface, bbl_access_rx_udp_ipv6(interface, session, eth, ipv6); return; case IPV6_NEXT_HEADER_TCP: - bbl_tcp_ipv6_rx_session(session, eth, ipv6); + bbl_tcp_ipv6_rx_session(session, ipv6); break; default: break; } session->stats.accounting_packets_rx++; - session->stats.accounting_bytes_rx += eth->length; + session->stats.accounting_bytes_rx += eth ? eth->length : ipv6->len; } static void @@ -807,15 +821,11 @@ bbl_access_l2tp(bbl_session_s *session, char *reply_message, uint8_t reply_messa static void bbl_access_rx_pap(bbl_access_interface_s *interface, bbl_session_s *session, - bbl_ethernet_header_s *eth) + bbl_pap_s *pap, + struct timespec *timestamp) { - bbl_pppoe_session_s *pppoes; - bbl_pap_s *pap; - - pppoes = (bbl_pppoe_session_s*)eth->next; - pap = (bbl_pap_s*)pppoes->next; - UNUSED(interface); + UNUSED(timestamp); if(session->session_state == BBL_PPP_AUTH) { switch(pap->code) { @@ -863,17 +873,13 @@ bbl_access_rx_pap(bbl_access_interface_s *interface, static void bbl_access_rx_chap(bbl_access_interface_s *interface, bbl_session_s *session, - bbl_ethernet_header_s *eth) + bbl_chap_s *chap, + struct timespec *timestamp) { - bbl_pppoe_session_s *pppoes; - bbl_chap_s *chap; - MD5_CTX md5_ctx; UNUSED(interface); - - pppoes = (bbl_pppoe_session_s*)eth->next; - chap = (bbl_chap_s*)pppoes->next; + UNUSED(timestamp); if(session->session_state == BBL_PPP_AUTH) { switch(chap->code) { @@ -966,12 +972,12 @@ bbl_access_ncp_success(uint8_t state) * * @param interface receiving interface * @param session corresponding session - * @param eth received packet + * @param timestamp received packet timestamp */ void bbl_access_rx_established_pppoe(bbl_access_interface_s *interface, bbl_session_s *session, - bbl_ethernet_header_s *eth) + struct timespec *timestamp) { UNUSED(interface); @@ -981,8 +987,12 @@ bbl_access_rx_established_pppoe(bbl_access_interface_s *interface, if(ipcp && ip6cp) { if(session->session_state != BBL_ESTABLISHED) { if(g_ctx->sessions_established_max < g_ctx->sessions) { - g_ctx->stats.last_session_established.tv_sec = eth->timestamp.tv_sec; - g_ctx->stats.last_session_established.tv_nsec = eth->timestamp.tv_nsec; + if(timestamp) { + g_ctx->stats.last_session_established.tv_sec = timestamp->tv_sec; + g_ctx->stats.last_session_established.tv_nsec = timestamp->tv_nsec; + } else { + clock_gettime(CLOCK_MONOTONIC, &g_ctx->stats.last_session_established); + } } bbl_session_update_state(session, BBL_ESTABLISHED); if(g_ctx->config.pppoe_session_time) { @@ -1005,10 +1015,10 @@ bbl_access_rx_established_pppoe(bbl_access_interface_s *interface, static void bbl_access_rx_ip6cp(bbl_access_interface_s *interface, bbl_session_s *session, - bbl_ethernet_header_s *eth) + bbl_ip6cp_s *ip6cp, + struct timespec *timestamp) { - bbl_pppoe_session_s *pppoes; - bbl_ip6cp_s *ip6cp; + UNUSED(interface); if(session->lcp_state != BBL_PPP_OPENED) { return; @@ -1026,8 +1036,6 @@ bbl_access_rx_ip6cp(bbl_access_interface_s *interface, return; } - pppoes = (bbl_pppoe_session_s*)eth->next; - ip6cp = (bbl_ip6cp_s*)pppoes->next; switch(ip6cp->code) { case PPP_CODE_CONF_REQUEST: @@ -1046,7 +1054,7 @@ bbl_access_rx_ip6cp(bbl_access_interface_s *interface, break; case BBL_PPP_LOCAL_ACK: session->ip6cp_state = BBL_PPP_OPENED; - bbl_access_rx_established_pppoe(interface, session, eth); + bbl_access_rx_established_pppoe(interface, session, timestamp); session->link_local_ipv6_address[0] = 0xfe; session->link_local_ipv6_address[1] = 0x80; *(uint64_t*)&session->link_local_ipv6_address[8] = session->ip6cp_ipv6_identifier; @@ -1078,7 +1086,7 @@ bbl_access_rx_ip6cp(bbl_access_interface_s *interface, break; case BBL_PPP_PEER_ACK: session->ip6cp_state = BBL_PPP_OPENED; - bbl_access_rx_established_pppoe(interface, session, eth); + bbl_access_rx_established_pppoe(interface, session, timestamp); session->link_local_ipv6_address[0] = 0xfe; session->link_local_ipv6_address[1] = 0x80; *(uint64_t*)&session->link_local_ipv6_address[8] = session->ip6cp_ipv6_identifier; @@ -1137,10 +1145,10 @@ bbl_access_rx_ipcp_conf_reject(bbl_session_s *session, bbl_ipcp_s *ipcp) static void bbl_access_rx_ipcp(bbl_access_interface_s *interface, bbl_session_s *session, - bbl_ethernet_header_s *eth) + bbl_ipcp_s *ipcp, + struct timespec *timestamp) { - bbl_pppoe_session_s *pppoes; - bbl_ipcp_s *ipcp; + UNUSED(interface); if(session->lcp_state != BBL_PPP_OPENED) { return; @@ -1158,8 +1166,7 @@ bbl_access_rx_ipcp(bbl_access_interface_s *interface, return; } - pppoes = (bbl_pppoe_session_s*)eth->next; - ipcp = (bbl_ipcp_s*)pppoes->next; + switch(ipcp->code) { case PPP_CODE_CONF_REQUEST: @@ -1182,7 +1189,7 @@ bbl_access_rx_ipcp(bbl_access_interface_s *interface, break; case BBL_PPP_LOCAL_ACK: session->ipcp_state = BBL_PPP_OPENED; - bbl_access_rx_established_pppoe(interface, session, eth); + bbl_access_rx_established_pppoe(interface, session, timestamp); ACTIVATE_ENDPOINT(session->endpoint.ipv4); session->version++; LOG(IP, "IPv4 (ID: %u) address %s\n", session->session_id, @@ -1241,7 +1248,7 @@ bbl_access_rx_ipcp(bbl_access_interface_s *interface, break; case BBL_PPP_PEER_ACK: session->ipcp_state = BBL_PPP_OPENED; - bbl_access_rx_established_pppoe(interface, session, eth); + bbl_access_rx_established_pppoe(interface, session, timestamp); ACTIVATE_ENDPOINT(session->endpoint.ipv4); session->version++; LOG(IP, "IPv4 (ID: %u) address %s\n", session->session_id, @@ -1385,16 +1392,11 @@ bbl_access_lcp_opened(bbl_session_s *session) static void bbl_access_rx_lcp(bbl_access_interface_s *interface, bbl_session_s *session, - bbl_ethernet_header_s *eth) + bbl_lcp_s *lcp, + struct timespec *timestamp) { - bbl_pppoe_session_s *pppoes; - bbl_lcp_s *lcp; - UNUSED(interface); - pppoes = (bbl_pppoe_session_s*)eth->next; - lcp = (bbl_lcp_s*)pppoes->next; - if(session->session_state < BBL_PPP_LINK) { return; } @@ -1582,7 +1584,7 @@ bbl_access_rx_lcp(bbl_access_interface_s *interface, LOG(PPPOE, "LCP PROTOCOL REJECT (ID: %u) Protocol 0x%04x reject received\n", session->session_id, lcp->protocol); } - bbl_access_rx_established_pppoe(interface, session, eth); + bbl_access_rx_established_pppoe(interface, session, timestamp); break; default: session->lcp_response_code = PPP_CODE_CODE_REJECT; @@ -1600,52 +1602,64 @@ bbl_access_rx_lcp(bbl_access_interface_s *interface, } } -static void -bbl_access_rx_session(bbl_access_interface_s *interface, - bbl_session_s *session, - bbl_ethernet_header_s *eth) +void +bbl_ppp_rx(bbl_access_interface_s *interface, + bbl_session_s *session, + bbl_ethernet_header_s *eth, + uint16_t protocol, + void *next) { - bbl_pppoe_session_s *pppoes; - - pppoes = (bbl_pppoe_session_s*)eth->next; - if(pppoes->session_id != session->pppoe_session_id || - memcmp(session->server_mac, eth->src, ETH_ADDR_LEN) != 0) { - return; - } + struct timespec *timestamp = eth ? ð->timestamp : NULL; - switch(pppoes->protocol) { + switch(protocol) { case PROTOCOL_LCP: - bbl_access_rx_lcp(interface, session, eth); - interface->stats.lcp_rx++; + bbl_access_rx_lcp(interface, session, (bbl_lcp_s*)next, timestamp); + if(interface) interface->stats.lcp_rx++; break; case PROTOCOL_IPCP: - bbl_access_rx_ipcp(interface, session, eth); - interface->stats.ipcp_rx++; + bbl_access_rx_ipcp(interface, session, (bbl_ipcp_s*)next, timestamp); + if(interface) interface->stats.ipcp_rx++; break; case PROTOCOL_IP6CP: - bbl_access_rx_ip6cp(interface, session, eth); - interface->stats.ip6cp_rx++; + bbl_access_rx_ip6cp(interface, session, (bbl_ip6cp_s*)next, timestamp); + if(interface) interface->stats.ip6cp_rx++; break; case PROTOCOL_PAP: - bbl_access_rx_pap(interface, session, eth); - interface->stats.pap_rx++; + bbl_access_rx_pap(interface, session, (bbl_pap_s*)next, timestamp); + if(interface) interface->stats.pap_rx++; break; case PROTOCOL_CHAP: - bbl_access_rx_chap(interface, session, eth); - interface->stats.chap_rx++; + bbl_access_rx_chap(interface, session, (bbl_chap_s*)next, timestamp); + if(interface) interface->stats.chap_rx++; break; case PROTOCOL_IPV4: - bbl_access_rx_ipv4(interface, session, eth, (bbl_ipv4_s*)pppoes->next); + bbl_access_rx_ipv4(interface, session, eth, (bbl_ipv4_s*)next); break; case PROTOCOL_IPV6: - bbl_access_rx_ipv6(interface, session, eth, (bbl_ipv6_s*)pppoes->next); + bbl_access_rx_ipv6(interface, session, eth, (bbl_ipv6_s*)next); break; default: - interface->stats.unknown++; + if(interface) interface->stats.unknown++; break; } } +static void +bbl_access_rx_session(bbl_access_interface_s *interface, + bbl_session_s *session, + bbl_ethernet_header_s *eth) +{ + bbl_pppoe_session_s *pppoes; + + pppoes = (bbl_pppoe_session_s*)eth->next; + if(pppoes->session_id != session->pppoe_session_id || + memcmp(session->server_mac, eth->src, ETH_ADDR_LEN) != 0) { + return; + } + + bbl_ppp_rx(interface, session, eth, pppoes->protocol, pppoes->next); +} + void bbl_access_lcp_start_delay(timer_s *timer) { diff --git a/code/bngblaster/src/bbl_access.h b/code/bngblaster/src/bbl_access.h index 73a479e0..0a76b4bd 100644 --- a/code/bngblaster/src/bbl_access.h +++ b/code/bngblaster/src/bbl_access.h @@ -133,12 +133,19 @@ bbl_access_rx_established_ipoe(bbl_access_interface_s *interface, void bbl_access_rx_established_pppoe(bbl_access_interface_s *interface, bbl_session_s *session, - bbl_ethernet_header_s *eth); + struct timespec *timestamp); void bbl_access_rx_handler(bbl_access_interface_s *interface, bbl_ethernet_header_s *eth); +void +bbl_ppp_rx(bbl_access_interface_s *interface, + bbl_session_s *session, + bbl_ethernet_header_s *eth, + uint16_t protocol, + void *next); + int bbl_access_ctrl_interfaces(int fd, uint32_t session_id __attribute__((unused)), json_t *arguments __attribute__((unused))); diff --git a/code/bngblaster/src/bbl_dhcp.c b/code/bngblaster/src/bbl_dhcp.c index 545227c2..da38372f 100644 --- a/code/bngblaster/src/bbl_dhcp.c +++ b/code/bngblaster/src/bbl_dhcp.c @@ -238,10 +238,10 @@ bbl_dhcp_rx(bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_dhcp_s *dhcp session->dhcp_address = dhcp->header->yiaddr; session->dhcp_server = dhcp->header->siaddr; session->dhcp_server_identifier = dhcp->server_identifier; - memcpy(session->dhcp_server_mac, eth->src, ETH_ADDR_LEN); + memcpy(session->dhcp_server_mac, eth ? eth->src : session->server_mac, ETH_ADDR_LEN); session->dhcp_lease_time = dhcp->lease_time; - session->dhcp_lease_timestamp.tv_sec = eth->timestamp.tv_sec; - session->dhcp_lease_timestamp.tv_nsec = eth->timestamp.tv_nsec; + session->dhcp_lease_timestamp.tv_sec = eth ? eth->timestamp.tv_sec : 0; + session->dhcp_lease_timestamp.tv_nsec = eth ? eth->timestamp.tv_nsec : 0; if(!(session->dhcp_address && session->dhcp_server_identifier && session->dhcp_lease_time)) { LOG(ERROR, "DHCP (ID: %u) Invalid DHCP-Offer!\n", session->session_id); bbl_dhcp_restart(session); @@ -260,10 +260,10 @@ bbl_dhcp_rx(bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_dhcp_s *dhcp session->dhcp_address = dhcp->header->yiaddr; session->dhcp_server = dhcp->header->siaddr; session->dhcp_server_identifier = dhcp->server_identifier; - memcpy(session->dhcp_server_mac, eth->src, ETH_ADDR_LEN); + memcpy(session->dhcp_server_mac, eth ? eth->src : session->server_mac, ETH_ADDR_LEN); session->dhcp_lease_time = dhcp->lease_time; - session->dhcp_lease_timestamp.tv_sec = eth->timestamp.tv_sec; - session->dhcp_lease_timestamp.tv_nsec = eth->timestamp.tv_nsec; + session->dhcp_lease_timestamp.tv_sec = eth ? eth->timestamp.tv_sec : 0; + session->dhcp_lease_timestamp.tv_nsec = eth ? eth->timestamp.tv_nsec : 0; if(!(session->dhcp_address && session->dhcp_server_identifier && session->dhcp_lease_time)) { LOG(ERROR, "DHCP (ID: %u) Invalid DHCP-ACK!\n", session->session_id); bbl_dhcp_restart(session); @@ -346,8 +346,8 @@ bbl_dhcp_rx(bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_dhcp_s *dhcp session->dhcp_server = dhcp->header->siaddr; session->dhcp_server_identifier = dhcp->server_identifier; session->dhcp_lease_time = dhcp->lease_time; - session->dhcp_lease_timestamp.tv_sec = eth->timestamp.tv_sec; - session->dhcp_lease_timestamp.tv_nsec = eth->timestamp.tv_nsec; + session->dhcp_lease_timestamp.tv_sec = eth ? eth->timestamp.tv_sec : 0; + session->dhcp_lease_timestamp.tv_nsec = eth ? eth->timestamp.tv_nsec : 0; if(!(session->dhcp_address && session->dhcp_server_identifier && session->dhcp_lease_time)) { LOG(ERROR, "DHCP (ID: %u) Invalid DHCP-ACK!\n", session->session_id); bbl_dhcp_restart(session); diff --git a/code/bngblaster/src/bbl_tcp.c b/code/bngblaster/src/bbl_tcp.c index cf7bb57a..2a13060c 100644 --- a/code/bngblaster/src/bbl_tcp.c +++ b/code/bngblaster/src/bbl_tcp.c @@ -886,15 +886,13 @@ bbl_tcp_ipv4_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth, /** * bbl_tcp_ipv4_rx_session * - * @param eth ethernet packet received * @param ipv4 ipv4 header received * @param session receiving session */ void -bbl_tcp_ipv4_rx_session(bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_ipv4_s *ipv4) +bbl_tcp_ipv4_rx_session(bbl_session_s *session, bbl_ipv4_s *ipv4) { struct pbuf *pbuf; - UNUSED(eth); if(!(g_ctx->tcp && session->netif.state)) { /* TCP not enabled! */ @@ -1000,15 +998,13 @@ bbl_tcp_ipv6_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth, /** * bbl_tcp_ipv6_rx_session * - * @param eth ethernet packet received * @param ipv6 ipv6 header received * @param session receiving session */ void -bbl_tcp_ipv6_rx_session(bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_ipv6_s *ipv6) +bbl_tcp_ipv6_rx_session(bbl_session_s *session, bbl_ipv6_s *ipv6) { struct pbuf *pbuf; - UNUSED(eth); if(!(g_ctx->tcp && session->netif.state)) { /* TCP not enabled! */ diff --git a/code/bngblaster/src/bbl_tcp.h b/code/bngblaster/src/bbl_tcp.h index 25285212..203c5896 100644 --- a/code/bngblaster/src/bbl_tcp.h +++ b/code/bngblaster/src/bbl_tcp.h @@ -118,13 +118,13 @@ void bbl_tcp_ipv4_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth, bbl_ipv4_s *ipv4); void -bbl_tcp_ipv4_rx_session(bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_ipv4_s *ipv4); +bbl_tcp_ipv4_rx_session(bbl_session_s *session, bbl_ipv4_s *ipv4); void bbl_tcp_ipv6_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth, bbl_ipv6_s *ipv6); void -bbl_tcp_ipv6_rx_session(bbl_session_s *session, bbl_ethernet_header_s *eth, bbl_ipv6_s *ipv6); +bbl_tcp_ipv6_rx_session(bbl_session_s *session, bbl_ipv6_s *ipv6); bool bbl_tcp_send(bbl_tcp_ctx_s *tcpc, uint8_t *buf, uint32_t len); From e733091c083b98edac17ad93f48ac54824c2c28e Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 24 Mar 2026 15:51:20 +0100 Subject: [PATCH 12/25] LAC: route received L2TP data to PPP client RX path 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 --- code/bngblaster/src/bbl_l2tp.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index 1939c162..13ffba6d 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -1108,6 +1108,17 @@ bbl_l2tp_data_rx(bbl_network_interface_s *interface, } l2tp_session->stats.data_rx++; + + /* LAC mode: route received PPP packets to the generic PPP client RX path. */ + if(l2tp_session->tunnel->is_lac) { + bbl_session_s *session = l2tp_session->pppoe_session; + if(session) { + bbl_ppp_rx(session->access_interface, session, NULL, + l2tp->protocol, l2tp->next); + } + return; + } + switch(l2tp->protocol) { case PROTOCOL_LCP: lcp_rx = (bbl_lcp_s*)l2tp->next; From 220c8e218b5eda1dde78de562063defdb07dedff Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 24 Mar 2026 09:15:56 +0100 Subject: [PATCH 13/25] LAC: wire up PPP session to L2TP session 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 --- code/bngblaster/src/bbl_l2tp.c | 12 ++++++++++++ code/bngblaster/src/bbl_session.c | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index 13ffba6d..c6453121 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -1288,6 +1288,9 @@ bbl_l2tp_client_session_connect(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_session_s *s if(g_ctx->l2tp_sessions > g_ctx->l2tp_sessions_max) { g_ctx->l2tp_sessions_max = g_ctx->l2tp_sessions; } + /* Link the PPP session and the L2TP session to each other. */ + l2tp_session->pppoe_session = session; + session->l2tp_session = l2tp_session; bbl_l2tp_send(l2tp_tunnel, l2tp_session, L2TP_MESSAGE_ICRQ); } @@ -1418,6 +1421,15 @@ bbl_l2tp_icrp_rx(bbl_network_interface_s *interface, l2tp_tunnel->peer_name, format_ipv4_address(&l2tp_tunnel->peer_ip), l2tp_session->key.session_id); + /* Start the PPP state machine for the associated LAC session. */ + if(l2tp_session->pppoe_session) { + bbl_session_s *session = l2tp_session->pppoe_session; + bbl_session_update_state(session, BBL_PPP_LINK); + session->lcp_state = BBL_PPP_INIT; + session->lcp_request_code = PPP_CODE_CONF_REQUEST; + session->send_requests |= BBL_SEND_LCP_REQUEST; + bbl_session_tx_qnode_insert(session); + } } } diff --git a/code/bngblaster/src/bbl_session.c b/code/bngblaster/src/bbl_session.c index 55e8cce0..575e46ac 100644 --- a/code/bngblaster/src/bbl_session.c +++ b/code/bngblaster/src/bbl_session.c @@ -1042,6 +1042,24 @@ bbl_sessions_init() if(g_ctx->config.pppoe_host_uniq) { session->pppoe_host_uniq = htobe64(i); } + } else if(session->access_type == ACCESS_TYPE_PPPOL2TP) { + session->mru = access_config->ppp_mru; + session->magic_number = htobe32(i); + session->lcp_state = BBL_PPP_CLOSED; + if(access_config->ipv4_enable && access_config->ipcp_enable) { + session->ipcp_state = BBL_PPP_CLOSED; + session->ipcp_request_dns1 = g_ctx->config.ipcp_request_dns1; + session->ipcp_request_dns2 = g_ctx->config.ipcp_request_dns2; + session->endpoint.ipv4 = ENDPOINT_ENABLED; + } + if(access_config->ipv6_enable && access_config->ip6cp_enable) { + session->ip6cp_state = BBL_PPP_CLOSED; + session->endpoint.ipv6 = ENDPOINT_ENABLED; + if(access_config->dhcpv6_enable) { + session->dhcpv6_state = BBL_DHCP_INIT; + session->endpoint.ipv6pd = ENDPOINT_ENABLED; + } + } } else if(session->access_type == ACCESS_TYPE_IPOE) { if(access_config->ipv4_enable) { session->endpoint.ipv4 = ENDPOINT_ENABLED; From 34e76371e5e1f2e3bdc0d0cb9085cdf75fab3639 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 24 Mar 2026 11:21:05 +0100 Subject: [PATCH 14/25] LAC: introduce bbl_ppp_tx() to eliminate duplicated PPP framing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- code/bngblaster/src/bbl_tx.c | 215 +++++++++-------------------------- 1 file changed, 54 insertions(+), 161 deletions(-) diff --git a/code/bngblaster/src/bbl_tx.c b/code/bngblaster/src/bbl_tx.c index 23015945..7b4d2324 100644 --- a/code/bngblaster/src/bbl_tx.c +++ b/code/bngblaster/src/bbl_tx.c @@ -255,6 +255,44 @@ bbl_tx_encode_packet_igmp(bbl_session_s *session) return encode_ethernet(session->write_buf, &session->write_idx, ð); } +/** + * bbl_ppp_tx + * + * Generic PPP transmit helper — the TX counterpart of bbl_ppp_rx(). + * Wraps a PPP payload in the framing appropriate for this session type + * and queues or writes the resulting packet. + * + * Currently handles ACCESS_TYPE_PPPOE: builds an Ethernet + PPPoE session + * header and writes the complete frame to session->write_buf via + * encode_ethernet(). Support for additional transports (e.g. PPPoL2TP) + * will be added here without touching the individual protocol encoders. + * + * @param session the PPP session + * @param protocol PPP protocol number (PROTOCOL_LCP, PROTOCOL_IPCP, ...) + * @param payload pointer to the protocol-specific payload struct + */ +static protocol_error_t +bbl_ppp_tx(bbl_session_s *session, uint16_t protocol, void *payload) +{ + bbl_ethernet_header_s eth = {0}; + bbl_pppoe_session_s pppoe = {0}; + + eth.dst = session->server_mac; + eth.src = session->client_mac; + eth.qinq = session->access_config->qinq; + eth.vlan_outer = session->vlan_key.outer_vlan_id; + eth.vlan_inner = session->vlan_key.inner_vlan_id; + eth.vlan_three = session->access_third_vlan; + eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; + eth.vlan_inner_priority = eth.vlan_outer_priority; + eth.type = ETH_TYPE_PPPOE_SESSION; + eth.next = &pppoe; + pppoe.session_id = session->pppoe_session_id; + pppoe.protocol = protocol; + pppoe.next = payload; + return encode_ethernet(session->write_buf, &session->write_idx, ð); +} + void bbl_tx_pap_timeout(timer_s *timer) { @@ -273,26 +311,8 @@ bbl_tx_pap_timeout(timer_s *timer) static protocol_error_t bbl_tx_encode_packet_pap_request(bbl_session_s *session) { - bbl_access_interface_s *access_interface = session->access_interface; - - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_pap_s pap = {0}; - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_PAP; - pppoe.next = &pap; - pap.code = PAP_CODE_REQUEST; pap.identifier = 1; pap.username = session->username; @@ -303,8 +323,8 @@ bbl_tx_encode_packet_pap_request(bbl_session_s *session) timer_add(&g_ctx->timer_root, &session->timer_auth, "Authentication Timeout", g_ctx->config.authentication_timeout, 0, session, &bbl_tx_pap_timeout); - access_interface->stats.pap_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + session->access_interface->stats.pap_tx++; + return bbl_ppp_tx(session, PROTOCOL_PAP, &pap); } void @@ -325,27 +345,8 @@ bbl_tx_chap_timeout(timer_s *timer) static protocol_error_t bbl_tx_encode_packet_chap_response(bbl_session_s *session) { - bbl_access_interface_s *access_interface = session->access_interface; - - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_chap_s chap = {0}; - access_interface->stats.chap_tx++; - - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_CHAP; - pppoe.next = &chap; chap.code = CHAP_CODE_RESPONSE; chap.identifier = session->chap_identifier; chap.challenge = session->chap_response; @@ -356,8 +357,8 @@ bbl_tx_encode_packet_chap_response(bbl_session_s *session) timer_add(&g_ctx->timer_root, &session->timer_auth, "Authentication Timeout", g_ctx->config.authentication_timeout, 0, session, &bbl_tx_chap_timeout); - access_interface->stats.chap_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + session->access_interface->stats.chap_tx++; + return bbl_ppp_tx(session, PROTOCOL_CHAP, &chap); } void @@ -705,30 +706,12 @@ bbl_tx_ip6cp_timeout(timer_s *timer) static protocol_error_t bbl_tx_encode_packet_ip6cp_request(bbl_session_s *session) { - bbl_access_interface_s *access_interface = session->access_interface; - - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_ip6cp_s ip6cp = {0}; if(session->ip6cp_state == BBL_PPP_CLOSED || session->ip6cp_state == BBL_PPP_OPENED) { return WRONG_PROTOCOL_STATE; } - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_IP6CP; - pppoe.next = &ip6cp; - ip6cp.code = session->ip6cp_request_code; ip6cp.identifier = ++session->ip6cp_identifier; if(ip6cp.code == PPP_CODE_CONF_REQUEST) { @@ -737,33 +720,15 @@ bbl_tx_encode_packet_ip6cp_request(bbl_session_s *session) timer_add(&g_ctx->timer_root, &session->timer_ip6cp, "IP6CP timeout", g_ctx->config.ip6cp_conf_request_timeout, 0, session, &bbl_tx_ip6cp_timeout); - access_interface->stats.ip6cp_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + session->access_interface->stats.ip6cp_tx++; + return bbl_ppp_tx(session, PROTOCOL_IP6CP, &ip6cp); } static protocol_error_t bbl_tx_encode_packet_ip6cp_response(bbl_session_s *session) { - bbl_access_interface_s *access_interface = session->access_interface; - - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_ip6cp_s ip6cp = {0}; - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_IP6CP; - pppoe.next = &ip6cp; - ip6cp.code = session->ip6cp_response_code; ip6cp.identifier = session->ip6cp_peer_identifier; if(session->ip6cp_options_len) { @@ -773,8 +738,8 @@ bbl_tx_encode_packet_ip6cp_response(bbl_session_s *session) ip6cp.ipv6_identifier = session->ip6cp_ipv6_identifier; } - access_interface->stats.ip6cp_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + session->access_interface->stats.ip6cp_tx++; + return bbl_ppp_tx(session, PROTOCOL_IP6CP, &ip6cp); } void @@ -801,30 +766,12 @@ bbl_ipcp_timeout(timer_s *timer) static protocol_error_t bbl_tx_encode_packet_ipcp_request(bbl_session_s *session) { - bbl_access_interface_s *access_interface = session->access_interface; - - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_ipcp_s ipcp = {0}; if(session->ipcp_state == BBL_PPP_CLOSED || session->ipcp_state == BBL_PPP_OPENED) { return WRONG_PROTOCOL_STATE; } - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_IPCP; - pppoe.next = &ipcp; - ipcp.code = session->ipcp_request_code; ipcp.identifier = ++session->ipcp_identifier; if(ipcp.code == PPP_CODE_CONF_REQUEST) { @@ -845,33 +792,15 @@ bbl_tx_encode_packet_ipcp_request(bbl_session_s *session) timer_add(&g_ctx->timer_root, &session->timer_ipcp, "IPCP timeout", g_ctx->config.ipcp_conf_request_timeout, 0, session, &bbl_ipcp_timeout); - access_interface->stats.ipcp_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + session->access_interface->stats.ipcp_tx++; + return bbl_ppp_tx(session, PROTOCOL_IPCP, &ipcp); } static protocol_error_t bbl_tx_encode_packet_ipcp_response(bbl_session_s *session) { - bbl_access_interface_s *access_interface = session->access_interface; - - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_ipcp_s ipcp = {0}; - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_IPCP; - pppoe.next = &ipcp; - ipcp.code = session->ipcp_response_code; ipcp.identifier = session->ipcp_peer_identifier; if(session->ipcp_options_len) { @@ -879,8 +808,8 @@ bbl_tx_encode_packet_ipcp_response(bbl_session_s *session) ipcp.options_len = session->ipcp_options_len; } - access_interface->stats.ipcp_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + session->access_interface->stats.ipcp_tx++; + return bbl_ppp_tx(session, PROTOCOL_IPCP, &ipcp); } void @@ -919,27 +848,9 @@ bbl_lcp_timeout(timer_s *timer) static protocol_error_t bbl_tx_encode_packet_lcp_request(bbl_session_s *session) { - bbl_access_interface_s *access_interface = session->access_interface; - - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_lcp_s lcp = {0}; uint16_t timeout = 1; /* default timeout 1 second */ - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_LCP; - pppoe.next = &lcp; - lcp.code = session->lcp_request_code; lcp.identifier = ++session->lcp_identifier; if(lcp.code == PPP_CODE_ECHO_REQUEST) { @@ -956,33 +867,15 @@ bbl_tx_encode_packet_lcp_request(bbl_session_s *session) timeout, 0, session, &bbl_lcp_timeout); } - access_interface->stats.lcp_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + session->access_interface->stats.lcp_tx++; + return bbl_ppp_tx(session, PROTOCOL_LCP, &lcp); } static protocol_error_t bbl_tx_encode_packet_lcp_response(bbl_session_s *session) { - bbl_access_interface_s *access_interface = session->access_interface; - - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_lcp_s lcp = {0}; - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_LCP; - pppoe.next = &lcp; - lcp.code = session->lcp_response_code; lcp.identifier = session->lcp_peer_identifier; @@ -999,8 +892,8 @@ bbl_tx_encode_packet_lcp_response(bbl_session_s *session) } } - access_interface->stats.lcp_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + session->access_interface->stats.lcp_tx++; + return bbl_ppp_tx(session, PROTOCOL_LCP, &lcp); } void From 3d20e970316f9529f42b98126fa36cf862126a0c Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 24 Mar 2026 11:47:47 +0100 Subject: [PATCH 15/25] LAC: add generic PPPoE/IPoE Tx helpers 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 --- code/bngblaster/src/bbl_tx.c | 338 +++++++++++++++-------------------- 1 file changed, 142 insertions(+), 196 deletions(-) diff --git a/code/bngblaster/src/bbl_tx.c b/code/bngblaster/src/bbl_tx.c index 7b4d2324..1bae3c97 100644 --- a/code/bngblaster/src/bbl_tx.c +++ b/code/bngblaster/src/bbl_tx.c @@ -12,6 +12,115 @@ #include "bbl_dhcp.h" #include "bbl_dhcpv6.h" +/** + * bbl_ppp_tx + * + * Generic PPP transmit helper — the TX counterpart of bbl_ppp_rx(). + * Wraps a PPP payload in the framing appropriate for this session type + * and queues or writes the resulting packet. + * + * Currently handles ACCESS_TYPE_PPPOE: builds an Ethernet + PPPoE session + * header and writes the complete frame to session->write_buf via + * encode_ethernet(). Support for additional transports (e.g. PPPoL2TP) + * will be added here without touching the individual protocol encoders. + * + * @param session the PPP session + * @param protocol PPP protocol number (PROTOCOL_LCP, PROTOCOL_IPCP, ...) + * @param payload pointer to the protocol-specific payload struct + */ +static protocol_error_t +bbl_ppp_tx(bbl_session_s *session, uint16_t protocol, void *payload) +{ + bbl_ethernet_header_s eth = {0}; + bbl_pppoe_session_s pppoe = {0}; + + eth.dst = session->server_mac; + eth.src = session->client_mac; + eth.qinq = session->access_config->qinq; + eth.vlan_outer = session->vlan_key.outer_vlan_id; + eth.vlan_inner = session->vlan_key.inner_vlan_id; + eth.vlan_three = session->access_third_vlan; + eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; + eth.vlan_inner_priority = eth.vlan_outer_priority; + eth.type = ETH_TYPE_PPPOE_SESSION; + eth.next = &pppoe; + pppoe.session_id = session->pppoe_session_id; + pppoe.protocol = protocol; + pppoe.next = payload; + return encode_ethernet(session->write_buf, &session->write_idx, ð); +} + +/** + * bbl_ipoe_tx + * + * IPoE transmit helper — wraps an IP payload in Ethernet framing for IPoE + * sessions. The caller supplies the destination MAC, EtherType, VLAN + * priority, and the payload pointer (typically a bbl_ipv4_s / bbl_ipv6_s). + */ +static protocol_error_t +bbl_ipoe_tx(bbl_session_s *session, uint8_t *dst_mac, uint16_t eth_type, + uint8_t vlan_priority, void *payload) +{ + bbl_ethernet_header_s eth = {0}; + eth.dst = dst_mac; + eth.src = session->client_mac; + eth.qinq = session->access_config->qinq; + eth.vlan_outer = session->vlan_key.outer_vlan_id; + eth.vlan_inner = session->vlan_key.inner_vlan_id; + eth.vlan_three = session->access_third_vlan; + eth.vlan_outer_priority = vlan_priority; + eth.vlan_inner_priority = vlan_priority; + eth.type = eth_type; + eth.next = payload; + return encode_ethernet(session->write_buf, &session->write_idx, ð); +} + +/** + * bbl_pppoe_disc_tx + * + * PPPoE discovery transmit helper — wraps a bbl_pppoe_discovery_s payload + * in Ethernet framing (ETH_TYPE_PPPOE_DISCOVERY) for PADI / PADR / PADT. + */ +static protocol_error_t +bbl_pppoe_disc_tx(bbl_session_s *session, void *payload) +{ + bbl_ethernet_header_s eth = {0}; + eth.dst = session->server_mac; + eth.src = session->client_mac; + eth.qinq = session->access_config->qinq; + eth.vlan_outer = session->vlan_key.outer_vlan_id; + eth.vlan_inner = session->vlan_key.inner_vlan_id; + eth.vlan_three = session->access_third_vlan; + eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; + eth.vlan_inner_priority = eth.vlan_outer_priority; + eth.type = ETH_TYPE_PPPOE_DISCOVERY; + eth.next = payload; + return encode_ethernet(session->write_buf, &session->write_idx, ð); +} + +/** + * bbl_session_tx + * + * Transport-agnostic IP transmit helper — dispatches to bbl_ppp_tx() for + * ACCESS_TYPE_PPPOE and to bbl_ipoe_tx() (with ipoe_vlan_priority) for + * ACCESS_TYPE_IPOE. + * + * @param ppp_protocol PPP protocol number: PROTOCOL_IPV4 or PROTOCOL_IPV6. + * Also determines the EtherType for the IPoE path. + * @param dst_mac_ipoe Destination MAC used on the IPoE path (ignored for PPPoE). + */ +static protocol_error_t +bbl_session_tx(bbl_session_s *session, uint8_t *dst_mac_ipoe, + uint16_t ppp_protocol, void *ip_payload) +{ + if(session->access_type == ACCESS_TYPE_PPPOE) { + return bbl_ppp_tx(session, ppp_protocol, ip_payload); + } + uint16_t eth_type = (ppp_protocol == PROTOCOL_IPV6) ? ETH_TYPE_IPV6 : ETH_TYPE_IPV4; + return bbl_ipoe_tx(session, dst_mac_ipoe, eth_type, + g_ctx->config.ipoe_vlan_priority, ip_payload); +} + void bbl_tx_igmp_timeout(timer_s *timer) { @@ -63,11 +172,8 @@ bbl_tx_encode_packet_igmp(bbl_session_s *session) { bbl_access_interface_s *access_interface = session->access_interface; - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_ipv4_s ipv4 = {0}; bbl_igmp_s igmp = {0}; - uint8_t mac[ETH_ADDR_LEN]; bbl_igmp_group_record_s *gr; int i, i2; @@ -80,13 +186,6 @@ bbl_tx_encode_packet_igmp(bbl_session_s *session) struct timespec timestamp; clock_gettime(CLOCK_MONOTONIC, ×tamp); - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - if(session->access_type == ACCESS_TYPE_PPPOE) { /* Check session and IPCP (PPP IPv4) state to prevent sending IGMP request * after session or IPCP has closed. */ @@ -94,25 +193,13 @@ bbl_tx_encode_packet_igmp(bbl_session_s *session) session->send_requests &= ~BBL_SEND_IGMP; return WRONG_PROTOCOL_STATE; } - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_IPV4; - pppoe.next = &ipv4; } else { /* IPoE */ if(session->session_state != BBL_ESTABLISHED) { session->send_requests &= ~BBL_SEND_IGMP; return WRONG_PROTOCOL_STATE; } - ipv4_multicast_mac(IPV4_MC_IGMP, mac); - eth.dst = mac; - eth.type = ETH_TYPE_IPV4; - eth.vlan_outer_priority = g_ctx->config.ipoe_vlan_priority; - eth.next = &ipv4; } - eth.vlan_inner_priority = eth.vlan_outer_priority; ipv4.dst = IPV4_MC_IGMP; ipv4.src = session->ip_address; ipv4.ttl = 1; @@ -193,11 +280,6 @@ bbl_tx_encode_packet_igmp(bbl_session_s *session) } else { ipv4.dst = group->group; igmp.group = group->group; - if(session->access_type != ACCESS_TYPE_PPPOE) { - /* IPoE */ - ipv4_multicast_mac(group->group, mac); - eth.dst = mac; - } if(session->igmp_version == IGMP_VERSION_2) { igmp.version = IGMP_VERSION_2; if(group->state == IGMP_GROUP_LEAVING) { @@ -252,45 +334,9 @@ bbl_tx_encode_packet_igmp(bbl_session_s *session) session->stats.igmp_tx++; access_interface->stats.igmp_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); -} - -/** - * bbl_ppp_tx - * - * Generic PPP transmit helper — the TX counterpart of bbl_ppp_rx(). - * Wraps a PPP payload in the framing appropriate for this session type - * and queues or writes the resulting packet. - * - * Currently handles ACCESS_TYPE_PPPOE: builds an Ethernet + PPPoE session - * header and writes the complete frame to session->write_buf via - * encode_ethernet(). Support for additional transports (e.g. PPPoL2TP) - * will be added here without touching the individual protocol encoders. - * - * @param session the PPP session - * @param protocol PPP protocol number (PROTOCOL_LCP, PROTOCOL_IPCP, ...) - * @param payload pointer to the protocol-specific payload struct - */ -static protocol_error_t -bbl_ppp_tx(bbl_session_s *session, uint16_t protocol, void *payload) -{ - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; - - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = protocol; - pppoe.next = payload; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + uint8_t mac[ETH_ADDR_LEN]; + ipv4_multicast_mac(ipv4.dst, mac); + return bbl_session_tx(session, mac, PROTOCOL_IPV4, &ipv4); } void @@ -377,38 +423,14 @@ bbl_tx_encode_packet_icmpv6_rs(bbl_session_s *session) { bbl_access_interface_s *access_interface = session->access_interface; - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_ipv6_s ipv6 = {0}; bbl_icmpv6_s icmpv6 = {0}; uint8_t mac[ETH_ADDR_LEN]; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - if(session->access_type == ACCESS_TYPE_PPPOE) { - if(session->ip6cp_state != BBL_PPP_OPENED) { - return WRONG_PROTOCOL_STATE; - } - eth.dst = session->server_mac; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.next = &pppoe; - - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_IPV6; - pppoe.next = &ipv6; - } else { - /* IPoE */ - ipv6_multicast_mac(ipv6_multicast_all_routers, mac); - eth.dst = mac; - eth.type = ETH_TYPE_IPV6; - eth.vlan_outer_priority = g_ctx->config.ipoe_vlan_priority; - eth.next = &ipv6; + if(session->access_type == ACCESS_TYPE_PPPOE && session->ip6cp_state != BBL_PPP_OPENED) { + return WRONG_PROTOCOL_STATE; } - eth.vlan_inner_priority = eth.vlan_outer_priority; + ipv6.dst = (void*)ipv6_multicast_all_routers; ipv6.src = (void*)session->link_local_ipv6_address; ipv6.ttl = 255; @@ -416,12 +438,13 @@ bbl_tx_encode_packet_icmpv6_rs(bbl_session_s *session) ipv6.next = &icmpv6; icmpv6.type = IPV6_ICMPV6_ROUTER_SOLICITATION; - timer_add(&g_ctx->timer_root, &session->timer_icmpv6, "ICMPv6", + timer_add(&g_ctx->timer_root, &session->timer_icmpv6, "ICMPv6", 5, 0, session, &bbl_icmpv6_timeout); session->stats.icmpv6_tx++; access_interface->stats.icmpv6_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + ipv6_multicast_mac(ipv6_multicast_all_routers, mac); + return bbl_session_tx(session, mac, PROTOCOL_IPV6, &ipv6); } void @@ -437,35 +460,22 @@ bbl_icmpv6_ns_timeout(timer_s *timer) static protocol_error_t bbl_tx_encode_packet_icmpv6_ns(bbl_session_s *session) { - bbl_ethernet_header_s eth = {0}; bbl_ipv6_s ipv6 = {0}; bbl_icmpv6_s icmpv6 = {0}; uint8_t mac[ETH_ADDR_LEN]; - ipv6addr_t ipv6_dst; if(!(session->access_type == ACCESS_TYPE_IPOE && ipv6_addr_not_zero(&session->icmpv6_ns_request))) { return WRONG_PROTOCOL_STATE; } - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.ipoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - memcpy((uint8_t*)&ipv6_dst, &ipv6_solicited_node_multicast, sizeof(ipv6addr_t)); ((uint8_t*)ipv6_dst)[13] = ((uint8_t*)session->icmpv6_ns_request)[13]; ((uint8_t*)ipv6_dst)[14] = ((uint8_t*)session->icmpv6_ns_request)[14]; ((uint8_t*)ipv6_dst)[15] = ((uint8_t*)session->icmpv6_ns_request)[15]; ipv6_multicast_mac(ipv6_dst, mac); - eth.dst = mac; - eth.type = ETH_TYPE_IPV6; - eth.next = &ipv6; ipv6.dst = (void*)ipv6_dst; ipv6.src = (void*)session->link_local_ipv6_address; ipv6.ttl = 255; @@ -480,7 +490,7 @@ bbl_tx_encode_packet_icmpv6_ns(bbl_session_s *session) session->stats.icmpv6_tx++; session->access_interface->stats.icmpv6_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + return bbl_ipoe_tx(session, mac, ETH_TYPE_IPV6, g_ctx->config.ipoe_vlan_priority, &ipv6); } void @@ -511,8 +521,6 @@ bbl_tx_encode_packet_dhcpv6_request(bbl_session_s *session) { bbl_access_interface_s *access_interface = session->access_interface; - bbl_ethernet_header_s eth = {0}; - bbl_pppoe_session_s pppoe = {0}; bbl_ipv6_s ipv6 = {0}; bbl_udp_s udp = {0}; bbl_dhcpv6_s dhcpv6 = {0}; @@ -521,8 +529,10 @@ bbl_tx_encode_packet_dhcpv6_request(bbl_session_s *session) struct timespec now; struct timespec time_diff; time_t elapsed = 0; - uint8_t mac[ETH_ADDR_LEN]; + uint8_t vlan_priority = g_ctx->config.dhcpv6_vlan_priority ? + g_ctx->config.dhcpv6_vlan_priority : + g_ctx->config.ipoe_vlan_priority; if(session->dhcpv6_state == BBL_DHCP_INIT || session->dhcpv6_state == BBL_DHCP_BOUND) { @@ -556,34 +566,9 @@ bbl_tx_encode_packet_dhcpv6_request(bbl_session_s *session) udp.src = DHCPV6_UDP_CLIENT; } - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - if(session->access_type == ACCESS_TYPE_PPPOE) { - if(session->ip6cp_state != BBL_PPP_OPENED) { - return WRONG_PROTOCOL_STATE; - } - eth.dst = session->server_mac; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.type = ETH_TYPE_PPPOE_SESSION; - eth.next = &pppoe; - pppoe.session_id = session->pppoe_session_id; - pppoe.protocol = PROTOCOL_IPV6; - pppoe.next = &ipv6; - } else { - /* IPoE */ - ipv6_multicast_mac(ipv6_multicast_all_dhcp, mac); - eth.dst = mac; - eth.vlan_outer_priority = g_ctx->config.ipoe_vlan_priority; - if(g_ctx->config.dhcpv6_vlan_priority) { - eth.vlan_outer_priority = g_ctx->config.dhcpv6_vlan_priority; - } - eth.type = ETH_TYPE_IPV6; - eth.next = &ipv6; + if(session->access_type == ACCESS_TYPE_PPPOE && session->ip6cp_state != BBL_PPP_OPENED) { + return WRONG_PROTOCOL_STATE; } - eth.vlan_inner_priority = eth.vlan_outer_priority; ipv6.dst = (void*)ipv6_multicast_all_dhcp; ipv6.src = (void*)session->link_local_ipv6_address; ipv6.ttl = 64; @@ -679,7 +664,12 @@ bbl_tx_encode_packet_dhcpv6_request(bbl_session_s *session) session->dhcpv6_retry++; session->stats.dhcpv6_tx++; access_interface->stats.dhcpv6_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + if(session->access_type == ACCESS_TYPE_PPPOE) { + return bbl_ppp_tx(session, PROTOCOL_IPV6, &ipv6); + } else { + ipv6_multicast_mac(ipv6_multicast_all_dhcp, mac); + return bbl_ipoe_tx(session, mac, ETH_TYPE_IPV6, vlan_priority, &ipv6); + } } void @@ -922,21 +912,9 @@ bbl_padr_timeout(timer_s *timer) static protocol_error_t bbl_encode_padi(bbl_session_s *session) { - bbl_ethernet_header_s eth = {0}; bbl_pppoe_discovery_s pppoe = {0}; access_line_s access_line = {0}; - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - - eth.type = ETH_TYPE_PPPOE_DISCOVERY; - eth.next = &pppoe; pppoe.code = PPPOE_PADI; if(session->pppoe_service_name) { pppoe.service_name = session->pppoe_service_name; @@ -959,26 +937,15 @@ bbl_encode_padi(bbl_session_s *session) access_line.profile = session->access_line_profile; pppoe.access_line = &access_line; } - return encode_ethernet(session->write_buf, &session->write_idx, ð); + return bbl_pppoe_disc_tx(session, &pppoe); } static protocol_error_t bbl_encode_padr(bbl_session_s *session) { - bbl_ethernet_header_s eth = {0}; bbl_pppoe_discovery_s pppoe = {0}; access_line_s access_line = {0}; - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_DISCOVERY; - eth.next = &pppoe; pppoe.code = PPPOE_PADR; pppoe.ac_cookie = session->pppoe_ac_cookie; pppoe.ac_cookie_len = session->pppoe_ac_cookie_len; @@ -1003,28 +970,16 @@ bbl_encode_padr(bbl_session_s *session) access_line.profile = session->access_line_profile; pppoe.access_line = &access_line; } - return encode_ethernet(session->write_buf, &session->write_idx, ð); + return bbl_pppoe_disc_tx(session, &pppoe); } static protocol_error_t bbl_encode_padt(bbl_session_s *session) { - bbl_ethernet_header_s eth = {0}; bbl_pppoe_discovery_s pppoe = {0}; - - eth.dst = session->server_mac; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.pppoe_vlan_priority; - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_PPPOE_DISCOVERY; - eth.next = &pppoe; pppoe.code = PPPOE_PADT; pppoe.session_id = session->pppoe_session_id; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + return bbl_pppoe_disc_tx(session, &pppoe); } protocol_error_t @@ -1089,7 +1044,6 @@ bbl_tx_encode_packet_dhcp(bbl_session_s *session) { bbl_access_interface_s *access_interface = session->access_interface; - bbl_ethernet_header_s eth = {0}; bbl_ipv4_s ipv4 = {0}; bbl_udp_s udp = {0}; struct dhcp_header header = {0}; @@ -1097,6 +1051,10 @@ bbl_tx_encode_packet_dhcp(bbl_session_s *session) access_line_s access_line = {0}; struct timespec now; time_t secs = 0; + uint8_t *dst_mac = NULL; + uint8_t vlan_priority = g_ctx->config.dhcp_vlan_priority ? + g_ctx->config.dhcp_vlan_priority : + g_ctx->config.ipoe_vlan_priority; if(session->dhcp_state == BBL_DHCP_INIT || session->dhcp_state == BBL_DHCP_BOUND) { @@ -1104,18 +1062,6 @@ bbl_tx_encode_packet_dhcp(bbl_session_s *session) } dhcp.header = &header; - eth.src = session->client_mac; - eth.qinq = session->access_config->qinq; - eth.vlan_outer = session->vlan_key.outer_vlan_id; - eth.vlan_inner = session->vlan_key.inner_vlan_id; - eth.vlan_three = session->access_third_vlan; - eth.vlan_outer_priority = g_ctx->config.ipoe_vlan_priority; - if(g_ctx->config.dhcp_vlan_priority) { - eth.vlan_outer_priority = g_ctx->config.dhcp_vlan_priority; - } - eth.vlan_inner_priority = eth.vlan_outer_priority; - eth.type = ETH_TYPE_IPV4; - eth.next = &ipv4; ipv4.src = session->ip_address; ipv4.ttl = 255; ipv4.tos = g_ctx->config.dhcp_tos; @@ -1175,7 +1121,7 @@ bbl_tx_encode_packet_dhcp(bbl_session_s *session) dhcp.type = DHCP_MESSAGE_DISCOVER; session->stats.dhcp_tx_discover++; LOG(DHCP, "DHCP (ID: %u) DHCP-Discover send\n", session->session_id); - eth.dst = (uint8_t*)broadcast_mac; + dst_mac = (uint8_t*)broadcast_mac; ipv4.dst = IPV4_BROADCAST; dhcp.parameter_request_list = true; dhcp.option_netmask = true; @@ -1200,7 +1146,7 @@ bbl_tx_encode_packet_dhcp(bbl_session_s *session) LOG(DHCP, "DHCP (ID: %u) DHCP-Request (init-reboot) send\n", session->session_id); dhcp.option_server_identifier = false; } - eth.dst = (uint8_t*)broadcast_mac; + dst_mac = (uint8_t*)broadcast_mac; ipv4.dst = IPV4_BROADCAST; dhcp.option_address = true; dhcp.address = session->dhcp_address; @@ -1216,7 +1162,7 @@ bbl_tx_encode_packet_dhcp(bbl_session_s *session) dhcp.type = DHCP_MESSAGE_REQUEST; session->stats.dhcp_tx_request++; LOG(DHCP, "DHCP (ID: %u) DHCP-Request (renewing) send\n", session->session_id); - eth.dst = session->dhcp_server_mac; + dst_mac = session->dhcp_server_mac; ipv4.dst = session->dhcp_server_identifier; header.ciaddr = session->ip_address; break; @@ -1224,7 +1170,7 @@ bbl_tx_encode_packet_dhcp(bbl_session_s *session) dhcp.type = DHCP_MESSAGE_RELEASE; session->stats.dhcp_tx_release++; LOG(DHCP, "DHCP (ID: %u) DHCP-Release send\n", session->session_id); - eth.dst = session->dhcp_server_mac; + dst_mac = session->dhcp_server_mac; ipv4.dst = session->dhcp_server_identifier; header.ciaddr = session->ip_address; dhcp.option_server_identifier = true; @@ -1252,7 +1198,7 @@ bbl_tx_encode_packet_dhcp(bbl_session_s *session) session->stats.dhcp_tx++; access_interface->stats.dhcp_tx++; - return encode_ethernet(session->write_buf, &session->write_idx, ð); + return bbl_ipoe_tx(session, dst_mac, ETH_TYPE_IPV4, vlan_priority, &ipv4); } void From 827ce4059f672dcfbe5afffa4dfa0da7d56b5e0c Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 24 Mar 2026 11:52:24 +0100 Subject: [PATCH 16/25] LAC: add PPP TX for PPPoL2TP 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 --- code/bngblaster/src/bbl_l2tp.c | 2 +- code/bngblaster/src/bbl_l2tp.h | 3 +++ code/bngblaster/src/bbl_protocols.h | 1 + code/bngblaster/src/bbl_tx.c | 14 +++++++++++++- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index c6453121..92783c91 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -551,7 +551,7 @@ bbl_l2tp_send(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_l2tp_session_s *l2tp_session, * @param protocol Payload type (IPCP, IPv4, ...). * @param next Payload structure. */ -static void +void bbl_l2tp_send_data(bbl_l2tp_session_s *l2tp_session, uint16_t protocol, void *next) { bbl_l2tp_tunnel_s *l2tp_tunnel = l2tp_session->tunnel; diff --git a/code/bngblaster/src/bbl_l2tp.h b/code/bngblaster/src/bbl_l2tp.h index add6d87e..2fbaab3d 100644 --- a/code/bngblaster/src/bbl_l2tp.h +++ b/code/bngblaster/src/bbl_l2tp.h @@ -364,6 +364,9 @@ bbl_l2tp_ctrl_tunnels(int fd, uint32_t session_id __attribute__((unused)), json_ bbl_l2tp_tunnel_s * bbl_l2tp_client_session_get_tunnel(bbl_session_s *session); +void +bbl_l2tp_send_data(bbl_l2tp_session_s *l2tp_session, uint16_t protocol, void *next); + void bbl_l2tp_client_session_connect(bbl_l2tp_tunnel_s *l2tp_tunnel, bbl_session_s *session); diff --git a/code/bngblaster/src/bbl_protocols.h b/code/bngblaster/src/bbl_protocols.h index 31c3e975..7e46d4e9 100644 --- a/code/bngblaster/src/bbl_protocols.h +++ b/code/bngblaster/src/bbl_protocols.h @@ -324,6 +324,7 @@ static const uint8_t all_dr_routers_mac[ETH_ADDR_LEN] = {0x01, 0x00, 0x5e, 0x00, typedef enum protocol_error_ { PROTOCOL_SUCCESS = 0, + PROTOCOL_QUEUED, /* packet enqueued internally; caller must not transmit write_buf */ SEND_ERROR, DECODE_ERROR, ENCODE_ERROR, diff --git a/code/bngblaster/src/bbl_tx.c b/code/bngblaster/src/bbl_tx.c index 1bae3c97..a7fe6c5c 100644 --- a/code/bngblaster/src/bbl_tx.c +++ b/code/bngblaster/src/bbl_tx.c @@ -31,6 +31,14 @@ static protocol_error_t bbl_ppp_tx(bbl_session_s *session, uint16_t protocol, void *payload) { + if(session->access_type == ACCESS_TYPE_PPPOL2TP) { + if(!session->l2tp_session) { + return EMPTY; + } + bbl_l2tp_send_data(session->l2tp_session, protocol, payload); + return PROTOCOL_QUEUED; + } + bbl_ethernet_header_s eth = {0}; bbl_pppoe_session_s pppoe = {0}; @@ -1373,7 +1381,8 @@ bbl_tx_encode_packet(bbl_session_s *session, uint8_t *buf, uint16_t *len) session->write_buf = buf; session->write_idx = 0; - if(session->send_requests & BBL_SEND_DISCOVERY) { + if(session->send_requests & BBL_SEND_DISCOVERY && + session->access_type != ACCESS_TYPE_PPPOL2TP) { result = bbl_tx_encode_packet_discovery(session); session->send_requests &= ~BBL_SEND_DISCOVERY; session->pppoe_retries++; @@ -1667,6 +1676,9 @@ bbl_tx(bbl_interface_s *interface, uint8_t *buf, uint16_t *len) access_interface->stats.bytes_tx += *len; session->stats.packets_tx++; session->stats.bytes_tx += *len; + } else if(result == PROTOCOL_QUEUED) { + /* Packet enqueued in a TX queue (ex: L2TP); nothing to transmit from write_buf. */ + result = EMPTY; } /* Remove only from TX queue if all requests are processed! */ bbl_session_tx_qnode_remove(session); From 15da8802462718de0bd6cb92f8fcc3ef397fcd23 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 24 Mar 2026 16:45:49 +0100 Subject: [PATCH 17/25] LAC: allow untagged access interfaces for LAC 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 --- code/bngblaster/src/bbl_access.c | 1 + code/bngblaster/src/bbl_access.h | 1 + code/bngblaster/src/bbl_network.c | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/code/bngblaster/src/bbl_access.c b/code/bngblaster/src/bbl_access.c index cae74cfd..bfaf5045 100644 --- a/code/bngblaster/src/bbl_access.c +++ b/code/bngblaster/src/bbl_access.c @@ -64,6 +64,7 @@ bbl_access_interfaces_add() access_interface->name = access_config->interface; access_interface->interface = interface; access_interface->ifindex = interface->ifindex; + access_interface->access_type = access_config->access_type; /* Init TXQ */ access_interface->txq = calloc(1, sizeof(bbl_txq_s)); diff --git a/code/bngblaster/src/bbl_access.h b/code/bngblaster/src/bbl_access.h index 0a76b4bd..88769b99 100644 --- a/code/bngblaster/src/bbl_access.h +++ b/code/bngblaster/src/bbl_access.h @@ -14,6 +14,7 @@ typedef struct bbl_access_interface_ { char *name; /* interface name */ uint32_t ifindex; /* interface index */ + access_type_t access_type; /* pppoe, ipoe or l2tp */ /* parent */ bbl_interface_s *interface; diff --git a/code/bngblaster/src/bbl_network.c b/code/bngblaster/src/bbl_network.c index 5e3bbbc7..e6cea135 100644 --- a/code/bngblaster/src/bbl_network.c +++ b/code/bngblaster/src/bbl_network.c @@ -67,7 +67,7 @@ bbl_network_interfaces_add() LOG(ERROR, "Failed to add network interface %s (duplicate)\n", ifname); return false; } - if(interface->access && network_config->vlan == 0) { + if(interface->access && interface->access->access_type != ACCESS_TYPE_PPPOL2TP && network_config->vlan == 0) { LOG(ERROR, "Failed to add network interface %s (untagged not allowed on access interfaces)\n", ifname); return false; } From bd251868429ef0f55c2fdc682d93e64c6a6af7aa Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 24 Mar 2026 17:03:13 +0100 Subject: [PATCH 18/25] LNS: add LCP server-side handling 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 --- code/bngblaster/src/bbl_l2tp.c | 36 +++++++++++++++++++++++++++++++++- code/bngblaster/src/bbl_l2tp.h | 1 + 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index 92783c91..caa9e2f8 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -1089,6 +1089,7 @@ bbl_l2tp_data_rx(bbl_network_interface_s *interface, bbl_ethernet_header_s *eth, bbl_l2tp_s *l2tp) { bbl_lcp_s *lcp_rx; + bbl_lcp_s lcp_tx; bbl_pap_s *pap_rx; bbl_pap_s pap_tx; bbl_chap_s *chap_rx; @@ -1132,7 +1133,40 @@ bbl_l2tp_data_rx(bbl_network_interface_s *interface, l2tp_session->disconnect_direction = 1; bbl_l2tp_send(l2tp_session->tunnel, l2tp_session, L2TP_MESSAGE_CDN); bbl_l2tp_session_delete(l2tp_session); - } + } else if(lcp_rx->code == PPP_CODE_CONF_REQUEST) { + /* Accept whatever the peer proposes. */ + lcp_rx->code = PPP_CODE_CONF_ACK; + bbl_l2tp_send_data(l2tp_session, PROTOCOL_LCP, lcp_rx); + if(l2tp_session->lcp_state == BBL_PPP_LOCAL_ACK) { + l2tp_session->lcp_state = BBL_PPP_OPENED; + } else if(l2tp_session->lcp_state != BBL_PPP_OPENED) { + memset(&lcp_tx, 0x0, sizeof(bbl_lcp_s)); + l2tp_session->lcp_state = BBL_PPP_PEER_ACK; + lcp_tx.code = PPP_CODE_CONF_REQUEST; + lcp_tx.identifier = 1; + lcp_tx.auth = PROTOCOL_PAP; + lcp_tx.magic = (uint32_t)l2tp_session->key.tunnel_id << 16 | + l2tp_session->key.session_id; + if(!lcp_tx.magic) lcp_tx.magic = 1; + lcp_tx.padding = l2tp_session->tunnel->server->lcp_padding; + bbl_l2tp_send_data(l2tp_session, PROTOCOL_LCP, &lcp_tx); + } + } else if(lcp_rx->code == PPP_CODE_CONF_ACK) { + if(l2tp_session->lcp_state == BBL_PPP_PEER_ACK) { + l2tp_session->lcp_state = BBL_PPP_OPENED; + } else if(l2tp_session->lcp_state != BBL_PPP_OPENED) { + memset(&lcp_tx, 0x0, sizeof(bbl_lcp_s)); + l2tp_session->lcp_state = BBL_PPP_LOCAL_ACK; + lcp_tx.code = PPP_CODE_CONF_REQUEST; + lcp_tx.identifier = 1; + lcp_tx.auth = PROTOCOL_PAP; + lcp_tx.magic = (uint32_t)l2tp_session->key.tunnel_id << 16 | + l2tp_session->key.session_id; + if(!lcp_tx.magic) lcp_tx.magic = 1; + lcp_tx.padding = l2tp_session->tunnel->server->lcp_padding; + bbl_l2tp_send_data(l2tp_session, PROTOCOL_LCP, &lcp_tx); + } + } break; case PROTOCOL_PAP: memset(&pap_tx, 0x0, sizeof(bbl_pap_s)); diff --git a/code/bngblaster/src/bbl_l2tp.h b/code/bngblaster/src/bbl_l2tp.h index 2fbaab3d..f1863bad 100644 --- a/code/bngblaster/src/bbl_l2tp.h +++ b/code/bngblaster/src/bbl_l2tp.h @@ -284,6 +284,7 @@ typedef struct bbl_l2tp_session_ uint16_t proxy_auth_challenge_len; uint16_t proxy_auth_response_len; + uint8_t lcp_state; uint8_t ipcp_state; uint8_t ip6cp_state; From 3d0be3e8b05c9a62ee82f585118ab89c238d99ff Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 24 Mar 2026 20:18:47 +0100 Subject: [PATCH 19/25] LAC: handle session teardown 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 --- code/bngblaster/src/bbl_l2tp.c | 39 +++++++++++++++++++++++++++++-- code/bngblaster/src/bbl_session.c | 25 ++++++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index caa9e2f8..2fe7641e 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -168,7 +168,10 @@ bbl_l2tp_force_stop(bbl_l2tp_tunnel_s *l2tp_tunnel) void bbl_l2tp_session_delete(bbl_l2tp_session_s *l2tp_session) { + bbl_l2tp_tunnel_s *l2tp_tunnel; + if(l2tp_session) { + l2tp_tunnel = l2tp_session->tunnel; if(l2tp_session->key.session_id) { /* Here we skip the session with ID zero which is the tunnel session. */ LOG(DEBUG, "L2TP Debug (%s) Tunnel %u Session %u deleted\n", @@ -184,9 +187,35 @@ bbl_l2tp_session_delete(bbl_l2tp_session_s *l2tp_session) /* Remove session from dict */ dict_remove(g_ctx->l2tp_session_dict, &l2tp_session->key); - /* Remove session from PPPoE session */ + /* Remove session from PPPoE/PPPoL2TP session */ if(l2tp_session->pppoe_session) { - l2tp_session->pppoe_session->l2tp_session = NULL; + bbl_session_s *session = l2tp_session->pppoe_session; + l2tp_session->pppoe_session = NULL; + session->l2tp_session = NULL; + /* In LAC mode the PPP session lifecycle is tied to the L2TP session: + * terminate it so that it can be restarted or counted as terminated. */ + if(l2tp_session->tunnel->is_lac && + session->session_state != BBL_TERMINATED && + session->session_state != BBL_IDLE) { + bbl_session_update_state(session, BBL_TERMINATED); + } + } + + /* If this was the last real session on an established tunnel, close it. + * The only entry left in session_qhead is the dummy tunnel session (id 0). */ + if(l2tp_tunnel->state == BBL_L2TP_TUNNEL_ESTABLISHED) { + bbl_l2tp_session_s *s; + bool has_sessions = false; + CIRCLEQ_FOREACH(s, &l2tp_tunnel->session_qhead, session_qnode) { + if(s->key.session_id != 0) { + has_sessions = true; + break; + } + } + if(!has_sessions) { + bbl_l2tp_tunnel_update_state(l2tp_tunnel, BBL_L2TP_TUNNEL_SEND_STOPCCN); + bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_STOPCCN); + } } /* Free tunnel memory */ @@ -852,6 +881,12 @@ bbl_l2tp_stopccn_rx(bbl_network_interface_s *interface, UNUSED(eth); UNUSED(l2tp); + /* RFC 2661 only requires a ZLB ACK in response to StopCCN, but many + * implementations expect a StopCCN back to complete the teardown + * immediately rather than waiting for their own timer to expire. */ + if(l2tp_tunnel->state < BBL_L2TP_TUNNEL_SEND_STOPCCN) { + bbl_l2tp_send(l2tp_tunnel, NULL, L2TP_MESSAGE_STOPCCN); + } bbl_l2tp_tunnel_update_state(l2tp_tunnel, BBL_L2TP_TUNNEL_RCVD_STOPCCN); } diff --git a/code/bngblaster/src/bbl_session.c b/code/bngblaster/src/bbl_session.c index 575e46ac..9acadc42 100644 --- a/code/bngblaster/src/bbl_session.c +++ b/code/bngblaster/src/bbl_session.c @@ -634,7 +634,8 @@ bbl_session_update_state(bbl_session_s *session, session_state_t new_state) } /* Reset all states */ - if(session->access_type == ACCESS_TYPE_PPPOE) { + if(session->access_type == ACCESS_TYPE_PPPOE || + session->access_type == ACCESS_TYPE_PPPOL2TP) { session->lcp_state = BBL_PPP_CLOSED; if(session->ipcp_state > BBL_PPP_DISABLED) { session->ipcp_state = BBL_PPP_CLOSED; @@ -691,7 +692,27 @@ bbl_session_clear(bbl_session_s *session) { session_state_t new_state = BBL_TERMINATED; - if(session->access_type == ACCESS_TYPE_PPPOE) { + if(session->access_type == ACCESS_TYPE_PPPOL2TP) { + switch(session->session_state) { + case BBL_IDLE: + bbl_session_update_state(session, BBL_TERMINATED); + break; + case BBL_PPP_TERMINATING: + case BBL_TERMINATING: + case BBL_TERMINATED: + break; + default: + bbl_session_update_state(session, BBL_PPP_TERMINATING); + if(session->l2tp_session) { + bbl_l2tp_send(session->l2tp_session->tunnel, + session->l2tp_session, L2TP_MESSAGE_CDN); + bbl_l2tp_session_delete(session->l2tp_session); + } else { + bbl_session_update_state(session, BBL_TERMINATED); + } + return; + } + } else if(session->access_type == ACCESS_TYPE_PPPOE) { switch(session->session_state) { case BBL_IDLE: case BBL_PPPOE_INIT: From ecee88bbc20c61122830305e8d6718e1ea5ff173 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Wed, 25 Mar 2026 10:05:38 +0100 Subject: [PATCH 20/25] LAC: support upstream data 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 --- code/bngblaster/src/bbl_stream.c | 161 ++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 2 deletions(-) diff --git a/code/bngblaster/src/bbl_stream.c b/code/bngblaster/src/bbl_stream.c index 2b3166a7..91961b01 100644 --- a/code/bngblaster/src/bbl_stream.c +++ b/code/bngblaster/src/bbl_stream.c @@ -854,6 +854,153 @@ bbl_stream_build_network_packet(bbl_stream_s *stream) return true; } +/* Build an upstream L2TP data stream packet (LAC -> LNS). + * Used for ACCESS_TYPE_PPPOL2TP sessions: the LAC is the sender, + * so the outer IP source is the LAC's network interface address and + * the inner PPP payload flows from the client toward the LNS. */ +static bool +bbl_stream_build_pppol2tp_packet(bbl_stream_s *stream) +{ + bbl_session_s *session = stream->session; + bbl_stream_config_s *config = stream->config; + + bbl_l2tp_session_s *l2tp_session = session->l2tp_session; + bbl_l2tp_tunnel_s *l2tp_tunnel = l2tp_session->tunnel; + bbl_network_interface_s *network_interface = l2tp_tunnel->interface; + + uint16_t buf_len = 0; + uint16_t tx_len = 0; + + bbl_ethernet_header_s eth = {0}; + bbl_ipv4_s l2tp_ipv4 = {0}; + bbl_udp_s l2tp_udp = {0}; + bbl_l2tp_s l2tp = {0}; + bbl_ipv4_s ipv4 = {0}; + bbl_ipv6_s ipv6 = {0}; + bbl_udp_s udp = {0}; + bbl_bbl_s bbl = {0}; + + eth.dst = network_interface->gateway_mac; + eth.src = network_interface->mac; + eth.vlan_outer = network_interface->vlan; + eth.vlan_inner = 0; + eth.type = ETH_TYPE_IPV4; + eth.next = &l2tp_ipv4; + l2tp_ipv4.src = network_interface->ip.address; + l2tp_ipv4.dst = l2tp_tunnel->peer_ip; + l2tp_ipv4.ttl = config->ttl; + l2tp_ipv4.tos = config->priority; + l2tp_ipv4.protocol = PROTOCOL_IPV4_UDP; + l2tp_ipv4.next = &l2tp_udp; + l2tp_udp.src = L2TP_UDP_PORT; + l2tp_udp.dst = L2TP_UDP_PORT; + l2tp_udp.protocol = UDP_PROTOCOL_L2TP; + l2tp_udp.next = &l2tp; + l2tp.type = L2TP_MESSAGE_DATA; + l2tp.tunnel_id = l2tp_tunnel->peer_tunnel_id; + l2tp.session_id = l2tp_session->peer_session_id; + l2tp.with_length = l2tp_tunnel->client->data_length; + l2tp.with_offset = l2tp_tunnel->client->data_offset; + udp.src = config->src_port; + udp.dst = config->dst_port; + udp.protocol = UDP_PROTOCOL_BBL; + udp.next = &bbl; + bbl.type = BBL_TYPE_UNICAST; + bbl.sub_type = stream->sub_type; + bbl.session_id = session->session_id; + bbl.ifindex = session->vlan_key.ifindex; + bbl.outer_vlan_id = session->vlan_key.outer_vlan_id; + bbl.inner_vlan_id = session->vlan_key.inner_vlan_id; + bbl.flow_id = stream->flow_id; + bbl.tos = config->priority; + bbl.direction = BBL_DIRECTION_UP; + + switch(stream->sub_type) { + case BBL_SUB_TYPE_IPV4: + l2tp.protocol = PROTOCOL_IPV4; + l2tp.next = &ipv4; + if(stream->config->ipv4_access_src_address) { + ipv4.src = stream->config->ipv4_access_src_address; + } else { + ipv4.src = session->ip_address; + } + if(stream->config->ipv4_destination_address) { + ipv4.dst = stream->config->ipv4_destination_address; + } else if(stream->config->ipv4_network_address) { + ipv4.dst = stream->config->ipv4_network_address; + } else { + ipv4.dst = MOCK_IP_LOCAL; + } + if(config->ipv4_df) { + ipv4.offset = IPV4_DF; + } + ipv4.ttl = config->ttl; + ipv4.tos = config->priority; + if(stream->tcp) { + ipv4.protocol = PROTOCOL_IPV4_TCP; + } else { + ipv4.protocol = PROTOCOL_IPV4_UDP; + } + ipv4.next = &udp; + if(config->length > 76) { + bbl.padding = config->length - 76; + } + stream->ipv4_src = ipv4.src; + stream->ipv4_dst = ipv4.dst; + break; + case BBL_SUB_TYPE_IPV6: + case BBL_SUB_TYPE_IPV6PD: + l2tp.protocol = PROTOCOL_IPV6; + l2tp.next = &ipv6; + if(*(uint64_t*)stream->config->ipv6_access_src_address) { + ipv6.src = stream->config->ipv6_access_src_address; + } else if(stream->sub_type == BBL_SUB_TYPE_IPV6) { + ipv6.src = session->ipv6_address; + } else { + ipv6.src = session->delegated_ipv6_address; + } + if(*(uint64_t*)stream->config->ipv6_destination_address) { + ipv6.dst = stream->config->ipv6_destination_address; + } else if(*(uint64_t*)stream->config->ipv6_network_address) { + ipv6.dst = stream->config->ipv6_network_address; + } else { + ipv6.dst = (void*)mock_ipv6_local; + } + ipv6.ttl = config->ttl; + ipv6.tos = config->priority; + if(stream->tcp) { + ipv6.protocol = IPV6_NEXT_HEADER_TCP; + } else { + ipv6.protocol = IPV6_NEXT_HEADER_UDP; + } + ipv6.next = &udp; + if(config->length > 96) { + bbl.padding = config->length - 96; + } + stream->ipv6_src = ipv6.src; + stream->ipv6_dst = ipv6.dst; + break; + default: + return false; + } + + buf_len = config->length + BBL_MAX_STREAM_OVERHEAD; + if(buf_len < 256) buf_len = 256; + stream->tx_buf = malloc(buf_len); + stream->tx_bbl_hdr_len = bbl.padding+BBL_HEADER_LEN; + if(encode_ethernet(stream->tx_buf, &tx_len, ð) != PROTOCOL_SUCCESS) { + free(stream->tx_buf); + stream->tx_buf = NULL; + return false; + } + stream->tx_len = tx_len; + return true; +} + +/* Build a downstream L2TP data stream packet (LNS -> LAC -> client). + * Used for ACCESS_TYPE_PPPOE sessions that are tunnelled via an LNS: + * the LNS is the sender, so the outer IP source is the LNS server address + * and the inner PPP payload flows from the LNS toward the client. */ static bool bbl_stream_build_l2tp_packet(bbl_stream_s *stream) { @@ -1029,6 +1176,10 @@ bbl_stream_build_packet(bbl_stream_s *stream) return bbl_stream_build_network_packet(stream); } } + } else if(stream->session->access_type == ACCESS_TYPE_PPPOL2TP) { + if(stream->session->l2tp_session && stream->direction == BBL_DIRECTION_UP) { + return bbl_stream_build_pppol2tp_packet(stream); + } } } return false; @@ -1851,8 +2002,14 @@ bbl_stream_session_add(bbl_stream_config_s *config, bbl_session_s *session) if(stream_up->config->raw_tcp) { stream_up->tcp = true; } - stream_up->tx_access_interface = access_interface; - stream_up->tx_interface = access_interface->interface; + if(session->access_type == ACCESS_TYPE_PPPOL2TP) { + /* PPPoL2TP upstream goes through the L2TP tunnel's network interface */ + stream_up->tx_network_interface = network_interface; + stream_up->tx_interface = network_interface->interface; + } else { + stream_up->tx_access_interface = access_interface; + stream_up->tx_interface = access_interface->interface; + } if(session->streams.tail) { session->streams.tail->session_next = stream_up; } else { From 6684fb12ea566e16f7c4b2f4f5615b8f0afc7b73 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Wed, 25 Mar 2026 10:22:31 +0100 Subject: [PATCH 21/25] LAC: support IPv6/ICMPv6/DHCPv6 over PPPoL2TP 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 --- code/bngblaster/src/bbl_tx.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/code/bngblaster/src/bbl_tx.c b/code/bngblaster/src/bbl_tx.c index a7fe6c5c..e9b2b397 100644 --- a/code/bngblaster/src/bbl_tx.c +++ b/code/bngblaster/src/bbl_tx.c @@ -121,7 +121,8 @@ static protocol_error_t bbl_session_tx(bbl_session_s *session, uint8_t *dst_mac_ipoe, uint16_t ppp_protocol, void *ip_payload) { - if(session->access_type == ACCESS_TYPE_PPPOE) { + if(session->access_type == ACCESS_TYPE_PPPOE || + session->access_type == ACCESS_TYPE_PPPOL2TP) { return bbl_ppp_tx(session, ppp_protocol, ip_payload); } uint16_t eth_type = (ppp_protocol == PROTOCOL_IPV6) ? ETH_TYPE_IPV6 : ETH_TYPE_IPV4; @@ -435,7 +436,9 @@ bbl_tx_encode_packet_icmpv6_rs(bbl_session_s *session) bbl_icmpv6_s icmpv6 = {0}; uint8_t mac[ETH_ADDR_LEN]; - if(session->access_type == ACCESS_TYPE_PPPOE && session->ip6cp_state != BBL_PPP_OPENED) { + if((session->access_type == ACCESS_TYPE_PPPOE || + session->access_type == ACCESS_TYPE_PPPOL2TP) && + session->ip6cp_state != BBL_PPP_OPENED) { return WRONG_PROTOCOL_STATE; } @@ -574,7 +577,9 @@ bbl_tx_encode_packet_dhcpv6_request(bbl_session_s *session) udp.src = DHCPV6_UDP_CLIENT; } - if(session->access_type == ACCESS_TYPE_PPPOE && session->ip6cp_state != BBL_PPP_OPENED) { + if((session->access_type == ACCESS_TYPE_PPPOE || + session->access_type == ACCESS_TYPE_PPPOL2TP) && + session->ip6cp_state != BBL_PPP_OPENED) { return WRONG_PROTOCOL_STATE; } ipv6.dst = (void*)ipv6_multicast_all_dhcp; @@ -672,7 +677,8 @@ bbl_tx_encode_packet_dhcpv6_request(bbl_session_s *session) session->dhcpv6_retry++; session->stats.dhcpv6_tx++; access_interface->stats.dhcpv6_tx++; - if(session->access_type == ACCESS_TYPE_PPPOE) { + if(session->access_type == ACCESS_TYPE_PPPOE || + session->access_type == ACCESS_TYPE_PPPOL2TP) { return bbl_ppp_tx(session, PROTOCOL_IPV6, &ipv6); } else { ipv6_multicast_mac(ipv6_multicast_all_dhcp, mac); From 670250d8561d39d7ca82c9670450b9e7c88a4f83 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Thu, 26 Mar 2026 15:14:06 +0100 Subject: [PATCH 22/25] LAC: delay LCP after ICCN ack 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 --- code/bngblaster/src/bbl_l2tp.c | 25 +++++++++++++++++++++++-- code/bngblaster/src/bbl_l2tp.h | 1 + 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/code/bngblaster/src/bbl_l2tp.c b/code/bngblaster/src/bbl_l2tp.c index 2fe7641e..bcef7c1e 100644 --- a/code/bngblaster/src/bbl_l2tp.c +++ b/code/bngblaster/src/bbl_l2tp.c @@ -356,6 +356,15 @@ bbl_l2tp_tunnel_tx_job(timer_s *timer) while(q != (const void *)(&l2tp_tunnel->tx_qhead)) { if(L2TP_SEQ_LT(q->ns, l2tp_tunnel->peer_nr)) { /* Delete acknowledged messages from queue. */ + /* If this entry was the ICCN for a PPP session, start the PPP + * state machine now that the LNS has acknowledged the ICCN. + * Waiting for the ACK gives the LNS time to complete its + * socket(AF_PPPOX)+connect() setup before the first PPP packet + * arrives, avoiding the kernel race where l2tp_session_find() + * returns NULL and silently drops the packet. */ + if(q->ppp_session) { + bbl_session_tx_qnode_insert(q->ppp_session); + } q_del = q; q = CIRCLEQ_NEXT(q, tunnel_tx_qnode); bbl_l2tp_tunnel_txq_remove(l2tp_tunnel, q_del); @@ -1490,14 +1499,26 @@ bbl_l2tp_icrp_rx(bbl_network_interface_s *interface, l2tp_tunnel->peer_name, format_ipv4_address(&l2tp_tunnel->peer_ip), l2tp_session->key.session_id); - /* Start the PPP state machine for the associated LAC session. */ + /* Prepare the PPP state machine but defer the TX insert until the ICCN + * has actually been submitted to the network interface TX queue. + * bbl_l2tp_tunnel_tx_job() will call bbl_session_tx_qnode_insert() once + * it appends the ICCN, guaranteeing ICCN is queued before the first LCP + * packet in network_interface->l2tp_tx_qhead. */ if(l2tp_session->pppoe_session) { bbl_session_s *session = l2tp_session->pppoe_session; bbl_session_update_state(session, BBL_PPP_LINK); session->lcp_state = BBL_PPP_INIT; session->lcp_request_code = PPP_CODE_CONF_REQUEST; session->send_requests |= BBL_SEND_LCP_REQUEST; - bbl_session_tx_qnode_insert(session); + /* Tag the ICCN queue entry so that PPP is started only after the + * LNS acknowledges the ICCN, giving it time to complete its + * socket(AF_PPPOX)+connect() setup before the first PPP packet arrives. + * bbl_l2tp_tunnel_tx_job() removes the entry from tx_qhead when the + * ACK is received, so the PPP session is started exactly once. */ + bbl_l2tp_queue_s *iccn_q = CIRCLEQ_LAST(&l2tp_tunnel->tx_qhead); + if(iccn_q != (void *)&l2tp_tunnel->tx_qhead) { + iccn_q->ppp_session = session; + } } } } diff --git a/code/bngblaster/src/bbl_l2tp.h b/code/bngblaster/src/bbl_l2tp.h index f1863bad..6b282f83 100644 --- a/code/bngblaster/src/bbl_l2tp.h +++ b/code/bngblaster/src/bbl_l2tp.h @@ -153,6 +153,7 @@ typedef struct bbl_l2tp_queue_ uint16_t packet_len; struct timespec last_tx_time; struct bbl_l2tp_tunnel_ *tunnel; + struct bbl_session_ *ppp_session; /* If set, start PPP when this ctrl pkt is acknowledged by the peer */ CIRCLEQ_ENTRY(bbl_l2tp_queue_) tunnel_tx_qnode; /* Tunnel TX queue (ctrl packets only) */ CIRCLEQ_ENTRY(bbl_l2tp_queue_) interface_tx_qnode; /* Interface TX queue */ } bbl_l2tp_queue_s; From 9b5a622e14a9f3484743e2da905fcc748554f7be Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Tue, 31 Mar 2026 15:58:35 +0200 Subject: [PATCH 23/25] LAC: allow session-traffic streams for PPPoL2TP sessions 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 --- code/bngblaster/src/bbl_stream.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/code/bngblaster/src/bbl_stream.c b/code/bngblaster/src/bbl_stream.c index 91961b01..f34a9adb 100644 --- a/code/bngblaster/src/bbl_stream.c +++ b/code/bngblaster/src/bbl_stream.c @@ -1956,16 +1956,18 @@ bbl_stream_session_add(bbl_stream_config_s *config, bbl_session_s *session) if(config->direction & BBL_DIRECTION_UP) { if(config->type == BBL_SUB_TYPE_IPV4) { - if(!((network_interface && network_interface->ip.address) || - config->ipv4_destination_address || + if(!(session->access_config->access_type == ACCESS_TYPE_PPPOL2TP || + (network_interface && network_interface->ip.address) || + config->ipv4_destination_address || config->ipv4_network_address || a10nsp_interface)) { LOG(ERROR, "Failed to add stream %s (upstream) because of missing IPv4 destination address\n", config->name); return false; } } else { - if(!((network_interface && *(uint64_t*)network_interface->ip6.address) || - *(uint64_t*)config->ipv6_destination_address || + if(!(session->access_config->access_type == ACCESS_TYPE_PPPOL2TP || + (network_interface && *(uint64_t*)network_interface->ip6.address) || + *(uint64_t*)config->ipv6_destination_address || *(uint64_t*)config->ipv6_network_address || a10nsp_interface)) { LOG(ERROR, "Failed to add stream %s (upstream) because of missing IPv6 destination address\n", config->name); From c6819d83d0e69324a6b52fe38f5c8c935a6908e2 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Wed, 1 Apr 2026 12:01:21 +0200 Subject: [PATCH 24/25] LAC: enable ICMPv6 RA processing and IPv6 endpoint 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 --- code/bngblaster/src/bbl_access.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/code/bngblaster/src/bbl_access.c b/code/bngblaster/src/bbl_access.c index bfaf5045..daa9ead0 100644 --- a/code/bngblaster/src/bbl_access.c +++ b/code/bngblaster/src/bbl_access.c @@ -533,10 +533,6 @@ bbl_access_rx_icmpv6(bbl_access_interface_s *interface, { bbl_icmpv6_s *icmpv6 = (bbl_icmpv6_s*)ipv6->next; - if(!eth) { - return false; - } - if(session->access_type == ACCESS_TYPE_PPPOE && session->ip6cp_state != BBL_PPP_OPENED) { return false; @@ -551,7 +547,8 @@ bbl_access_rx_icmpv6(bbl_access_interface_s *interface, memcpy(&session->ipv6_prefix, &icmpv6->prefix, sizeof(ipv6_prefix)); *(uint64_t*)&session->ipv6_address[0] = *(uint64_t*)session->ipv6_prefix.address; *(uint64_t*)&session->ipv6_address[8] = session->ip6cp_ipv6_identifier; - if(session->access_type == ACCESS_TYPE_PPPOE) { + if(session->access_type == ACCESS_TYPE_PPPOE || + session->access_type == ACCESS_TYPE_PPPOL2TP) { ACTIVATE_ENDPOINT(session->endpoint.ipv6); } session->version++; @@ -585,7 +582,7 @@ bbl_access_rx_icmpv6(bbl_access_interface_s *interface, } } } - } else if(icmpv6->type == IPV6_ICMPV6_NEIGHBOR_SOLICITATION) { + } else if(eth && icmpv6->type == IPV6_ICMPV6_NEIGHBOR_SOLICITATION) { if(memcmp(icmpv6->prefix.address, session->ipv6_address, IPV6_ADDR_LEN) == 0) { if(bbl_access_icmpv6_na(session, eth, ipv6, icmpv6) == BBL_TXQ_OK) { return true; @@ -595,19 +592,19 @@ bbl_access_rx_icmpv6(bbl_access_interface_s *interface, return true; } } - } else if(icmpv6->type == IPV6_ICMPV6_ECHO_REQUEST) { + } else if(eth && icmpv6->type == IPV6_ICMPV6_ECHO_REQUEST) { if(bbl_access_icmpv6_echo_reply(session, eth, ipv6, icmpv6) == BBL_TXQ_OK) { return true; } } else if(icmpv6->type == IPV6_ICMPV6_NEIGHBOR_ADVERTISEMENT) { - if(ipv6_addr_not_zero(&session->icmpv6_ns_request) && + if(ipv6_addr_not_zero(&session->icmpv6_ns_request) && memcmp(icmpv6->prefix.address, session->icmpv6_ns_request, IPV6_ADDR_LEN) == 0) { memset(session->icmpv6_ns_request, 0x0, IPV6_ADDR_LEN); if(memcmp(icmpv6->prefix.address, session->access_config->static_gateway6, IPV6_ADDR_LEN) == 0) { if(!session->icmpv6_ra_received) { session->icmpv6_ra_received = true; } - if(!session->arp_resolved) { + if(eth && !session->arp_resolved) { memcpy(session->server_mac, eth->src, ETH_ADDR_LEN); } bbl_access_rx_established_ipoe(interface, session, eth); From 58a565daa82062afa7cdad3a7539f3d56bc2e40a Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Wed, 1 Apr 2026 14:13:01 +0200 Subject: [PATCH 25/25] LAC: add documentation Add a new LAC section in L2TP documentation with examples. Signed-off-by: Olivier Matz --- docsrc/sources/access/l2tp.rst | 181 ++++++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 2 deletions(-) diff --git a/docsrc/sources/access/l2tp.rst b/docsrc/sources/access/l2tp.rst index 4b87bcfc..a3463abe 100644 --- a/docsrc/sources/access/l2tp.rst +++ b/docsrc/sources/access/l2tp.rst @@ -1,7 +1,7 @@ .. _l2tp: -L2TP ----- +L2TP (LNS Mode) +--------------- The BNG Blaster can emulate L2TPv2 (RFC2661) LNS servers to be able to test the L2TPv2 LAC functionality of the BNG device under @@ -398,3 +398,180 @@ It is also possible to display a single session. ``$ sudo bngblaster-cli run.sock l2tp-sessions tunnel-id 1 session-id 1`` +.. _l2tp-lac: + +L2TP (LAC Mode) +--------------- + +The BNG Blaster can also act as an L2TPv2 LAC (L2TP Access Concentrator), +generating PPP sessions encapsulated in L2TP tunnels toward a real LNS device +under test. This makes it possible to test LNS functionality directly. + +Each access interface section with ``"type": "pppol2tp"`` creates PPP sessions +that are tunnelled through the L2TP clients specified in the ``"l2tp-client"`` +list. The L2TP sessions are spread accross multiple tunnels (the ones whose +``"group-id"`` field match the ``"l2tp-client-group-id"`` field of the access +interface). + +Configuration +~~~~~~~~~~~~~ + +.. code-block:: json + + { + "interfaces": { + "network": { + "interface": "eth1", + "address": "10.0.0.1/24", + "gateway": "10.0.0.2" + }, + "access": [ + { + "interface": "eth1", + "type": "pppol2tp", + "l2tp-client-group-id": 1, + "outer-vlan-min": 1, + "outer-vlan-max": 1000, + "inner-vlan": 7, + "authentication-protocol": "PAP" + } + ] + }, + "ppp": { + "mru": 1492, + "authentication": { + "username": "user{session}@example.com", + "password": "test" + }, + "lcp": { + "conf-request-timeout": 5, + "conf-request-retry": 30 + }, + "ipcp": { + "enable": true + }, + "ip6cp": { + "enable": true + } + }, + "l2tp-client": [ + { + "group-id": 1, + "name": "LAC1", + "network-interface": "eth1", + "client-address": "10.0.1.1", + "server-address": "10.0.0.100", + "secret": "test", + "receive-window-size": 16 + }, + { + "group-id": 1, + "name": "LAC2", + "network-interface": "eth1", + "client-address": "10.0.1.2", + "server-address": "10.0.0.100", + "secret": "test", + "receive-window-size": 16 + } + ] + } + +.. include:: ../configuration/lac.rst + +LAC vs LNS Self-test +~~~~~~~~~~~~~~~~~~~~ + +The LAC and LNS roles can be tested locally without external network +devices using a pair of virtual ethernet interfaces. + +.. code-block:: none + + sudo ip link add veth-lac type veth peer name veth-lns + sudo ip link set veth-lac up + sudo ip link set veth-lns up + +Start the LNS instance first (terminal 1): + +.. code-block:: json + + { + "interfaces": { + "network": { + "interface": "veth-lns", + "address": "10.0.0.2", + "gateway": "10.0.0.1" + } + }, + "l2tp-server": [ + { + "name": "bbl-lns", + "address": "10.0.0.2", + "secret": "test" + } + ], + "ppp": { + "authentication": { + "username": "user@test", + "password": "password" + }, + "ipcp": { + "enable": true, + "request-ip": true, + "request-dns1": true, + "request-dns2": true + } + } + } + +``sudo bngblaster -C lns.json -l info -l l2tp`` + +Then start the LAC instance (terminal 2): + +.. code-block:: json + + { + "interfaces": { + "network": { + "interface": "veth-lac", + "address": "10.0.0.1", + "gateway": "10.0.0.2" + }, + "access": [ + { + "interface": "veth-lac", + "type": "pppol2tp", + "l2tp-client-group-id": 1 + } + ] + }, + "l2tp-client": [ + { + "group-id": 1, + "name": "LAC1", + "network-interface": "veth-lac", + "server-address": "10.0.0.2", + "secret": "test", + "max-retry": 10, + "receive-window-size": 4 + } + ], + "sessions": { + "count": 1 + }, + "ppp": { + "authentication": { + "username": "user@test", + "password": "password" + }, + "lcp": { + "conf-request-timeout": 1, + "conf-request-retry": 10 + }, + "ipcp": { + "enable": true + } + } + } + +``sudo bngblaster -C lac.json -l info -l l2tp`` +