From 69bc3984cfcf685447264da2fd3c424df0ea0986 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 16 Dec 2024 10:05:34 +0100 Subject: [PATCH] ICMP client (WIP) --- code/bngblaster/src/bbl.c | 1 + code/bngblaster/src/bbl.h | 1 + code/bngblaster/src/bbl_access.c | 21 +- code/bngblaster/src/bbl_config.c | 143 ++++++- code/bngblaster/src/bbl_config.h | 1 + code/bngblaster/src/bbl_ctrl.c | 3 + code/bngblaster/src/bbl_ctx.h | 3 + code/bngblaster/src/bbl_def.h | 2 + code/bngblaster/src/bbl_http_client.h | 6 - code/bngblaster/src/bbl_icmp_client.c | 553 ++++++++++++++++++++++++++ code/bngblaster/src/bbl_icmp_client.h | 117 ++++++ code/bngblaster/src/bbl_network.c | 16 +- code/bngblaster/src/bbl_network.h | 3 + code/bngblaster/src/bbl_session.c | 5 + code/bngblaster/src/bbl_session.h | 3 + code/common/src/logging.h | 1 + 16 files changed, 857 insertions(+), 22 deletions(-) create mode 100644 code/bngblaster/src/bbl_icmp_client.c create mode 100644 code/bngblaster/src/bbl_icmp_client.h diff --git a/code/bngblaster/src/bbl.c b/code/bngblaster/src/bbl.c index 15940c34..d5c2a3e2 100644 --- a/code/bngblaster/src/bbl.c +++ b/code/bngblaster/src/bbl.c @@ -140,6 +140,7 @@ struct keyval_ log_names[] = { { INFO, "info" }, { PCAP, "pcap" }, { IP, "ip" }, + { ICMP, "icmp" }, { LOSS, "loss" }, { L2TP, "l2tp" }, { DHCP, "dhcp" }, diff --git a/code/bngblaster/src/bbl.h b/code/bngblaster/src/bbl.h index 2d58fe45..7bbf775f 100644 --- a/code/bngblaster/src/bbl.h +++ b/code/bngblaster/src/bbl.h @@ -48,6 +48,7 @@ #include "bbl_li.h" #include "bbl_cfm.h" #include "bbl_tcp.h" +#include "bbl_icmp_client.h" #include "bbl_http_client.h" #include "bbl_http_server.h" #include "bbl_fragment.h" diff --git a/code/bngblaster/src/bbl_access.c b/code/bngblaster/src/bbl_access.c index c24775a3..89e8229d 100644 --- a/code/bngblaster/src/bbl_access.c +++ b/code/bngblaster/src/bbl_access.c @@ -567,12 +567,16 @@ 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(session->ip_address && - session->ip_address == ipv4->dst && - icmp->type == ICMP_TYPE_ECHO_REQUEST) { - /* Send ICMP reply... */ - if(bbl_access_icmp_reply(session, eth, ipv4, icmp) == BBL_TXQ_OK) { - return true; + if(session->ip_address && session->ip_address == ipv4->dst) { + if(icmp->type == ICMP_TYPE_ECHO_REQUEST) { + /* Send ICMP reply... */ + if(bbl_access_icmp_reply(session, eth, ipv4, icmp) == BBL_TXQ_OK) { + session->stats.icmp_tx++; + session->access_interface->stats.icmp_tx++; + return true; + } + } else { + return bbl_icmp_client_rx(session, NULL, eth, ipv4, icmp); } } return false; @@ -648,10 +652,7 @@ bbl_access_rx_ipv4(bbl_access_interface_s *interface, case PROTOCOL_IPV4_ICMP: session->stats.icmp_rx++; interface->stats.icmp_rx++; - if(bbl_access_rx_icmp(session, eth, ipv4)) { - session->stats.icmp_tx++; - interface->stats.icmp_tx++; - } + bbl_access_rx_icmp(session, eth, ipv4); return; case PROTOCOL_IPV4_UDP: udp = (bbl_udp_s*)ipv4->next; diff --git a/code/bngblaster/src/bbl_config.c b/code/bngblaster/src/bbl_config.c index 01f50422..c0ec8b5c 100644 --- a/code/bngblaster/src/bbl_config.c +++ b/code/bngblaster/src/bbl_config.c @@ -894,8 +894,9 @@ json_parse_access_interface(json_t *access_interface, bbl_access_config_s *acces "access-line-profile-id", "ipcp", "dhcp", "ipv4", "ip6cp", "dhcpv6", "dhcpv6-ldra", "ipv6", "igmp-autostart", - "igmp-version", "session-traffic-autostart", "session-group-id", - "stream-group-id", "http-client-group-id", + "igmp-version", "session-traffic-autostart", + "session-group-id", "stream-group-id", + "http-client-group-id", "icmp-client-group-id", "cfm-cc", "cfm-level", "cfm-ma-id", "cfm-ma-name" }; if(!schema_validate(access_interface, "access", schema, @@ -1224,6 +1225,12 @@ json_parse_access_interface(json_t *access_interface, bbl_access_config_s *acces access_config->tcp = true; } + value = json_object_get(access_interface, "icmp-client-group-id"); + JSON_OBJ_GET_NUMBER(access_interface, value, "access", "icmp-client-group-id", 0, 65535); + if(value) { + access_config->icmp_client_group_id = json_number_value(value); + } + JSON_OBJ_GET_BOOL(access_interface, value, "access", "cfm-cc"); if(value) { access_config->cfm_cc = json_boolean_value(value); @@ -2718,6 +2725,107 @@ json_parse_config_streams(json_t *root) return true; } +static bool +json_parse_icmp_client_config(json_t *icmp, bbl_icmp_client_config_s *icmp_client_config) +{ + json_t *value = NULL; + const char *s = NULL; + + const char *schema[] = { + "icmp-client-group-id", "network-interface", + "autostart", "start-delay", + "count", "results", + "interval", "size", "ttl", + "destination-address" + }; + + if(!schema_validate(icmp, "icmp-client", schema, + sizeof(schema)/sizeof(schema[0]))) { + return false; + } + + JSON_OBJ_GET_NUMBER(icmp, value, "icmp-client", "icmp-client-group-id", 1, 65535); + if(value) { + icmp_client_config->icmp_client_group_id = json_number_value(value); + } + if(json_unpack(icmp, "{s:s}", "network-interface", &s) == 0) { + if(icmp_client_config->icmp_client_group_id) { + fprintf(stderr, "JSON config error: At most one icmp-client-group-id or network-interface must be specified for icmp-clients.\n"); + return false; + } + icmp_client_config->network_interface = strdup(s); + } else if (!icmp_client_config->icmp_client_group_id) { + fprintf(stderr, "JSON config error: Missing value for icmp-client, either icmp-client-group-id or network-interface is required\n"); + return false; + } + + JSON_OBJ_GET_BOOL(icmp, value, "icmp-client", "autostart"); + if(value) { + icmp_client_config->autostart = json_boolean_value(value); + } else { + icmp_client_config->autostart = true; + } + + JSON_OBJ_GET_NUMBER(icmp, value, "icmp-client", "start-delay", 0, 65535); + if(value) { + icmp_client_config->start_delay = json_number_value(value); + } + + JSON_OBJ_GET_NUMBER(icmp, value, "icmp-client", "count", 0, 65535); + if(value) { + icmp_client_config->count = json_number_value(value); + } + + JSON_OBJ_GET_NUMBER(icmp, value, "icmp-client", "size", 0, 65507); + if(value) { + icmp_client_config->size = json_number_value(value); + } else { + icmp_client_config->size = 8; + } + + JSON_OBJ_GET_NUMBER(icmp, value, "icmp-client", "ttl", 1, 255); + if(value) { + icmp_client_config->ttl = json_number_value(value); + } else { + icmp_client_config->ttl = 64; + } + + JSON_OBJ_GET_NUMBER(icmp, value, "icmp-client", "results", 0, 65535); + if(value) { + icmp_client_config->results = json_number_value(value); + } else { + icmp_client_config->results = icmp_client_config->count; + } + if(!icmp_client_config->results) icmp_client_config->results = 3; + + value = json_object_get(icmp, "interval"); + if(value) { + icmp_client_config->interval = json_number_value(value); + if(icmp_client_config->interval <= 0) { + fprintf(stderr, "JSON config error: Invalid value for stream->pps\n"); + return false; + } + icmp_client_config->interval_sec = icmp_client_config->interval; + icmp_client_config->interval_nsec = (icmp_client_config->interval - icmp_client_config->interval_sec) * SEC; + } else { + icmp_client_config->interval = 1.0; + icmp_client_config->interval_sec = 1; + } + + if(json_unpack(icmp, "{s:s}", "destination-address", &s) == 0) { + if(!inet_pton(AF_INET, s, &icmp_client_config->destination)) { + fprintf(stderr, "JSON config error: Invalid value for icmp-client->destination-address\n"); + return false; + } + } else { + fprintf(stderr, "JSON config error: Missing value for icmp-client->destination-address\n"); + return false; + } + + return true; +} + + static bool json_parse_http_client_config(json_t *http, bbl_http_client_config_s *http_client_config) { @@ -2877,6 +2985,7 @@ json_parse_config(json_t *root) ospf_config_s *ospf_config = NULL; ldp_config_s *ldp_config = NULL; + bbl_icmp_client_config_s *icmp_client_config = NULL; bbl_http_client_config_s *http_client_config = NULL; bbl_http_server_config_s *http_server_config = NULL; @@ -2893,7 +3002,7 @@ json_parse_config(json_t *root) "isis", "ospf", "bgp", "bgp-raw-update-files", "ldp", "ldp-raw-update-files", - "l2tp-server", + "l2tp-server", "icmp-client", "http-client", "http-server" }; if(!schema_validate(root, "root", root_schema, @@ -4093,6 +4202,34 @@ json_parse_config(json_t *root) fprintf(stderr, "JSON config error: List expected in L2TP server configuration but dictionary found\n"); } + /* ICMP Client Configuration */ + sub = json_object_get(root, "icmp-client"); + if(json_is_array(sub)) { + /* Config is provided as array (multiple ICMP clients) */ + size = json_array_size(sub); + for(i = 0; i < size; i++) { + if(!icmp_client_config) { + g_ctx->config.icmp_client_config = calloc(1, sizeof(bbl_icmp_client_config_s)); + icmp_client_config = g_ctx->config.icmp_client_config; + } else { + icmp_client_config->next = calloc(1, sizeof(bbl_icmp_client_config_s)); + icmp_client_config = icmp_client_config->next; + } + if(!json_parse_icmp_client_config(json_array_get(sub, i), icmp_client_config)) { + return false; + } + } + } else if(json_is_object(sub)) { + /* Config is provided as object (single ICMP client) */ + icmp_client_config = calloc(1, sizeof(bbl_icmp_client_config_s)); + if(!g_ctx->config.icmp_client_config) { + g_ctx->config.icmp_client_config = icmp_client_config; + } + if(!json_parse_icmp_client_config(sub, icmp_client_config)) { + return false; + } + } + /* HTTP Client Configuration */ sub = json_object_get(root, "http-client"); if(json_is_array(sub)) { diff --git a/code/bngblaster/src/bbl_config.h b/code/bngblaster/src/bbl_config.h index 8403f4d9..cdb4990d 100644 --- a/code/bngblaster/src/bbl_config.h +++ b/code/bngblaster/src/bbl_config.h @@ -37,6 +37,7 @@ typedef struct bbl_access_config_ uint16_t stream_group_id; uint16_t session_group_id; uint16_t http_client_group_id; + uint16_t icmp_client_group_id; uint16_t access_outer_vlan; uint16_t access_outer_vlan_min; diff --git a/code/bngblaster/src/bbl_ctrl.c b/code/bngblaster/src/bbl_ctrl.c index c0b2bb65..460a18bc 100644 --- a/code/bngblaster/src/bbl_ctrl.c +++ b/code/bngblaster/src/bbl_ctrl.c @@ -277,6 +277,9 @@ struct action actions[] = { {"monkey-start", bbl_ctrl_monkey_start, schema_all_args, false}, {"monkey-stop", bbl_ctrl_monkey_stop, schema_all_args, false}, {"lag-info", bbl_lag_ctrl_info, schema_all_args, true}, + {"icmp-clients", bbl_icmp_client_ctrl, schema_all_args, true}, + {"icmp-clients-start", bbl_icmp_client_ctrl_start, schema_all_args, false}, + {"icmp-clients-stop", bbl_icmp_client_ctrl_stop, schema_all_args, false}, {"http-clients", bbl_http_client_ctrl, schema_all_args, true}, {"http-clients-start", bbl_http_client_ctrl_start, schema_all_args, false}, {"http-clients-stop", bbl_http_client_ctrl_stop, schema_all_args, false}, diff --git a/code/bngblaster/src/bbl_ctx.h b/code/bngblaster/src/bbl_ctx.h index 602964d5..76636810 100644 --- a/code/bngblaster/src/bbl_ctx.h +++ b/code/bngblaster/src/bbl_ctx.h @@ -197,6 +197,9 @@ typedef struct bbl_ctx_ /* LDP Instances */ ldp_config_s *ldp_config; + /* ICMP Client Instances */ + bbl_icmp_client_config_s *icmp_client_config; + /* HTTP Client/Server Instances */ bbl_http_client_config_s *http_client_config; bbl_http_server_config_s *http_server_config; diff --git a/code/bngblaster/src/bbl_def.h b/code/bngblaster/src/bbl_def.h index e933183d..27cee5ab 100644 --- a/code/bngblaster/src/bbl_def.h +++ b/code/bngblaster/src/bbl_def.h @@ -239,6 +239,8 @@ typedef struct bbl_stream_group_ bbl_stream_group_s; typedef struct bbl_stream_ bbl_stream_s; typedef struct bbl_tcp_ctx_ bbl_tcp_ctx_s; typedef struct bbl_ctrl_thread_ bbl_ctrl_thread_s; +typedef struct bbl_icmp_client_config_ bbl_icmp_client_config_s; +typedef struct bbl_icmp_client_ bbl_icmp_client_s; typedef struct bbl_http_client_config_ bbl_http_client_config_s; typedef struct bbl_http_client_ bbl_http_client_s; typedef struct bbl_http_server_config_ bbl_http_server_config_s; diff --git a/code/bngblaster/src/bbl_http_client.h b/code/bngblaster/src/bbl_http_client.h index 9fee8062..04b609c6 100644 --- a/code/bngblaster/src/bbl_http_client.h +++ b/code/bngblaster/src/bbl_http_client.h @@ -72,9 +72,6 @@ typedef struct bbl_http_client_ bool bbl_http_client_session_init(bbl_session_s *session); -bool -bbl_http_server_init(bbl_network_interface_s *network_interface); - int bbl_http_client_ctrl(int fd, uint32_t session_id, json_t *arguments __attribute__((unused))); @@ -84,7 +81,4 @@ bbl_http_client_ctrl_start(int fd, uint32_t session_id, json_t *arguments __attr int bbl_http_client_ctrl_stop(int fd, uint32_t session_id, json_t *arguments __attribute__((unused))); -bool -bbl_http_server_init(bbl_network_interface_s *network_interface); - #endif diff --git a/code/bngblaster/src/bbl_icmp_client.c b/code/bngblaster/src/bbl_icmp_client.c new file mode 100644 index 00000000..0e1be422 --- /dev/null +++ b/code/bngblaster/src/bbl_icmp_client.c @@ -0,0 +1,553 @@ +/* + * BNG Blaster (BBL) - ICMP Client + * + * Christian Giese, December 2024 + * + * Copyright (C) 2020-2024, RtBrick, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "bbl.h" + +extern volatile bool g_teardown; +extern bool g_init_phase; + +const char * +bbl_icmp_client_state_string(icmp_state_t state) +{ + switch(state) { + case ICMP_DOWN: return "down"; + case ICMP_WAIT: return "wait"; + case ICMP_STARTED: return "started"; + case ICMP_STOPPED: return "stopped"; + default: return "unknown"; + } +} + +const char * +bbl_icmp_client_result_string(icmp_result_t result) +{ + switch(result) { + case ICMP_RESULT_NONE: return "none"; + case ICMP_RESULT_WAIT: return "wait"; + case ICMP_RESULT_OKAY: return "okay"; + case ICMP_RESULT_UNREACHABLE: return "unreachable"; + case ICMP_RESULT_REDIRECTED: return "redirected"; + case ICMP_RESULT_FRAGMENTATION_NEEDED: return "fragmentation-needed"; + default: return "unknown"; + } +} + +static bbl_txq_result_t +bbl_icmp_client_tx(bbl_icmp_client_s *client, bbl_icmp_s *icmp) +{ + bbl_icmp_client_config_s *config = client->config; + bbl_session_s *session = client->session; + bbl_network_interface_s *network_interface = client->network_interface; + bbl_ethernet_header_s eth = {0}; + bbl_pppoe_session_s pppoe = {0}; + bbl_ipv4_s ipv4 = {0}; + + if(session) { + 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; + ipv4.src = session->ip_address; + } else { + eth.dst = network_interface->gateway_mac; + eth.src = network_interface->mac; + eth.vlan_outer = network_interface->vlan; + eth.vlan_inner = 0; + ipv4.src = network_interface->ip.address; + } + + if(session && session->access_type == ACCESS_TYPE_PPPOE) { + eth.type = ETH_TYPE_PPPOE_SESSION; + eth.next = &pppoe; + pppoe.session_id = session->pppoe_session_id; + pppoe.protocol = PROTOCOL_IPV4; + pppoe.next = &ipv4; + } else { + eth.type = ETH_TYPE_IPV4; + eth.next = &ipv4; + } + + ipv4.dst = config->destination; + ipv4.ttl = config->ttl; + ipv4.protocol = PROTOCOL_IPV4_ICMP; + ipv4.next = icmp; + + if(session) { + return bbl_txq_to_buffer(session->access_interface->txq, ð); + } else { + return bbl_txq_to_buffer(network_interface->txq, ð); + } +} + +void +bbl_icmp_client_send_job_ping(timer_s *timer) +{ + bbl_icmp_client_s *client = timer->data; + bbl_icmp_client_config_s *config = client->config; + bbl_icmp_client_result_ping_s *result; + bbl_icmp_s icmp = {0}; + + bbl_session_s *session = client->session; + bbl_network_interface_s *network_interface = client->network_interface; + + uint16_t slot = client->seq % config->results; + + if(session && session->endpoint.ipv4 != ENDPOINT_ACTIVE) { + return; + } + + result = &((bbl_icmp_client_result_ping_s*)client->result)[slot]; + memset(result, 0x0, sizeof(bbl_icmp_client_result_ping_s)); + *(uint16_t*)(client->data+2) = client->seq; + + icmp.type = ICMP_TYPE_ECHO_REQUEST; + icmp.data = client->data; + icmp.data_len = client->data_len; + + if(bbl_icmp_client_tx(client, &icmp) == BBL_TXQ_OK) { + result->seq = client->seq; + result->state = ICMP_RESULT_WAIT; + result->timestamp_tx.tv_sec = timer->timestamp->tv_sec; + result->timestamp_tx.tv_nsec = timer->timestamp->tv_nsec; + client->seq++; + client->send++; + if(session) { + session->stats.icmp_tx++; + session->access_interface->stats.icmp_tx++; + LOG(ICMP, "ICMP (ID: %u) send echo-request with id=%u seq=%u\n", + session->session_id, client->id, result->seq); + } else { + network_interface->stats.icmp_tx++; + LOG(ICMP, "ICMP (%s) send echo-request with id=%u seq=%u\n", + network_interface->name, client->id, result->seq); + } + } +} + +static void +bbl_icmp_client_start(bbl_icmp_client_s *client) +{ + bbl_icmp_client_config_s *config = client->config; + bbl_session_s *session = client->session; + + if(session && session->endpoint.ipv4 != ENDPOINT_ACTIVE) return; + + if(client->state != ICMP_STARTED) { + client->state = ICMP_STARTED; + client->seq = 0; + switch(config->mode) { + case ICMP_MODE_PING: + memset(client->result, 0x0, sizeof(bbl_icmp_client_result_ping_s) * client->results); + timer_add_periodic(&g_ctx->timer_root, &client->send_timer, + "ICMP SEND", config->interval_sec, config->interval_nsec, client, + &bbl_icmp_client_send_job_ping); + break; + default: + break; + } + } +} + +static void +bbl_icmp_client_stop(bbl_icmp_client_s *client) +{ + bbl_session_s *session = client->session; + + if(session->endpoint.ipv4 == ENDPOINT_ACTIVE) { + client->state = ICMP_STOPPED; + } else { + client->state = ICMP_DOWN; + } + timer_del(client->send_timer); +} + +void +bbl_icmp_client_state_job(timer_s *timer) +{ + bbl_icmp_client_s *client = timer->data; + bbl_icmp_client_config_s *config = client->config; + + if(client->session) { + if(client->session->endpoint.ipv4 != ENDPOINT_ACTIVE) { + client->state = ICMP_DOWN; + timer_del(client->send_timer); + return; + } + } else { + if(g_init_phase || g_teardown) { + client->state = ICMP_DOWN; + timer_del(client->send_timer); + return; + } + } + if(client->state == ICMP_DOWN) { + if(config->autostart) { + client->start_delay_countdown = client->config->start_delay; + client->state = ICMP_WAIT; + } else { + client->state = ICMP_STOPPED; + + } + } + if(client->state == ICMP_WAIT) { + if(client->start_delay_countdown) { + client->start_delay_countdown--; + } else { + bbl_icmp_client_start(client); + } + } +} + +static bool +bbl_icmp_client_add(bbl_icmp_client_config_s *config, + bbl_network_interface_s *network_interface, + bbl_session_s *session) +{ + bbl_icmp_client_s *client = calloc(1, sizeof(bbl_icmp_client_s)); + uint16_t id = 1; + + if(session) { + if(session->icmp_client) id = session->icmp_client->id+1; + client->next = session->icmp_client; + session->icmp_client = client; + client->session = session; + } else if (network_interface) { + if(network_interface->icmp_client) id = network_interface->icmp_client->id+1; + client->next = network_interface->icmp_client; + network_interface->icmp_client = client; + client->network_interface = network_interface; + } else { + return false; + } + + client->id = id; + client->config = config; + client->results = config->results; + client->destination = config->destination; + + switch(client->config->mode) { + case ICMP_MODE_PING: + client->result = calloc(client->results, sizeof(bbl_icmp_client_result_ping_s)); + client->data_len = client->config->size+2; + client->data = calloc(1, client->data_len); + *(uint16_t*)client->data = id; + break; + default: + return false; + } + + timer_add_periodic(&g_ctx->timer_root, &client->state_timer, + "ICMP", 1, 0, client, + &bbl_icmp_client_state_job); + return true; +} + +static bool +bbl_icmp_client_rx_echo_reply(bbl_session_s *session, + bbl_network_interface_s *network_interface, + bbl_ethernet_header_s *eth, + bbl_ipv4_s *ipv4, + bbl_icmp_s *icmp) +{ + bbl_icmp_client_s *client; + bbl_icmp_client_config_s *config; + bbl_icmp_client_result_ping_s *result; + + uint16_t slot = 0; + uint16_t id = *(uint16_t*)icmp->data; + uint16_t seq = *(uint16_t*)(icmp->data+2); + uint16_t size = icmp->data_len+6; + + struct timespec time_diff; + uint32_t ms = 0; + uint32_t rtt = 0; + + if(session) { + client = session->icmp_client; + } else { + client = network_interface->icmp_client; + } + + while(client) { + if(ipv4->src == client->destination && id == client->id) { + break; + } + client = client->next; + } + if(!client) return false; + + config = client->config; + slot = seq % config->results; + result = &((bbl_icmp_client_result_ping_s*)client->result)[slot]; + + if(result->seq == seq) { + result->size = icmp->data_len+6; + result->ttl = ipv4->ttl; + result->state = ICMP_RESULT_OKAY; + result->timestamp_rx.tv_sec = eth->timestamp.tv_sec; + result->timestamp_rx.tv_nsec = eth->timestamp.tv_nsec; + + timespec_sub(&time_diff, &result->timestamp_rx, &result->timestamp_tx); + ms = time_diff.tv_nsec / 1000000; /* convert nanoseconds to milliseconds */ + if(time_diff.tv_nsec % 1000000) ms++; /* simple roundup function */ + rtt = (time_diff.tv_sec * 1000) + ms; + } + + client->received++; + + if(session) { + LOG(ICMP, "ICMP (ID: %u) received echo-reply with id=%u seq=%u size=%u ttl=%u rtt=%ums\n", + session->session_id, id, seq, size, ipv4->ttl, rtt); + } else { + LOG(ICMP, "ICMP (%s) received echo-reply with id=%u seq=%u size=%u ttl=%u rtt=%ums\n", + network_interface->name, id, seq, size, ipv4->ttl, rtt); + } + return true; +} + +bool +bbl_icmp_client_rx(bbl_session_s *session, + bbl_network_interface_s *network_interface, + bbl_ethernet_header_s *eth, + bbl_ipv4_s *ipv4, + bbl_icmp_s *icmp) +{ + if(icmp->data_len < 4) return false; + + switch(icmp->type) { + case ICMP_TYPE_ECHO_REPLY: + return bbl_icmp_client_rx_echo_reply(session, network_interface, eth, ipv4, icmp); + default: + return false; + } +} + +/** + * Init ICMP clients on session. + */ +bool +bbl_icmp_client_session_init(bbl_session_s *session) +{ + bbl_icmp_client_config_s *config; + uint16_t icmp_client_group_id = session->access_config->icmp_client_group_id; + + /** Add clients of corresponding icmp-client-group-id */ + if(icmp_client_group_id) { + config = g_ctx->config.icmp_client_config; + while(config) { + if(config->icmp_client_group_id == icmp_client_group_id) { + if(!bbl_icmp_client_add(config, NULL, session)) { + return false; + } + } + config = config->next; + } + } + return true; +} + +/** + * Init ICMP clients on network interface. + */ +bool +bbl_icmp_client_network_interface_init(bbl_network_interface_s *network_interface) +{ + bbl_icmp_client_config_s *config = g_ctx->config.icmp_client_config; + + while(config) { + if(config->network_interface && + strcmp(config->network_interface, network_interface->name) == 0) { + if(!bbl_icmp_client_add(config, network_interface, NULL)) { + return false; + } + } + config = config->next; + } + return true; +} + +static json_t * +bbl_icmp_client_ping_result_json(bbl_icmp_client_s *client, uint16_t slot) +{ + json_t *root = NULL; + bbl_icmp_client_result_ping_s *result; + + result = &((bbl_icmp_client_result_ping_s*)client->result)[slot]; + + root = json_pack("{sI sI sI ss*}", + "seq", result->seq, + "size", result->size, + "ttl", result->ttl, + "state", bbl_icmp_client_result_string(result->state)); + + return root; +} + +static json_t * +bbl_icmp_client_json(bbl_icmp_client_s *client, bool detail) +{ + json_t *root = NULL; + json_t *results = NULL; + uint16_t slot; + + bbl_icmp_client_config_s *config; + + if(!client) { + return NULL; + } + + config = client->config; + + if(detail) { + results = json_array(); + for(slot = 0; slot < client->results; slot++) { + switch(config->mode) { + case ICMP_MODE_PING: + json_array_append_new(results, bbl_icmp_client_ping_result_json(client, slot)); + break; + default: + break; + } + } + } + + if(client->session) { + root = json_pack("{sI sI ss* ss* sI sI so*}", + "session-id", client->session->session_id, + "icmp-client-group-id", config->icmp_client_group_id, + "destination-address", format_ipv4_address(&config->destination), + "state", bbl_icmp_client_state_string(client->state), + "send", client->send, + "received", client->received, + "results", results); + } else { + root = json_pack("{ss* ss* ss* sI sI so*}", + "network-interface", config->network_interface, + "destination-address", format_ipv4_address(&config->destination), + "state", bbl_icmp_client_state_string(client->state), + "send", client->send, + "received", client->received, + "results", results); + } + return root; +} + +int +bbl_icmp_client_ctrl(int fd, uint32_t session_id, json_t *arguments __attribute__((unused))) +{ + int result = 0; + json_t *root; + json_t *json_clients = NULL; + uint32_t i; + + struct bbl_interface_ *interface; + bbl_network_interface_s *network_interface; + + bbl_session_s *session; + bbl_icmp_client_s *client; + + json_clients = json_array(); + + if(session_id) { + session = bbl_session_get(session_id); + if(session) { + client = session->icmp_client; + while(client) { + json_array_append_new(json_clients, bbl_icmp_client_json(client, true)); + client = client->next; + } + } + } else { + for(i = 0; i < g_ctx->sessions; i++) { + session = &g_ctx->session_list[i]; + client = session->icmp_client; + while(client) { + json_array_append_new(json_clients, bbl_icmp_client_json(client, false)); + client = client->next; + } + } + CIRCLEQ_FOREACH(interface, &g_ctx->interface_qhead, interface_qnode) { + network_interface = interface->network; + while(network_interface) { + client = network_interface->icmp_client; + while(client) { + json_array_append_new(json_clients, bbl_icmp_client_json(client, false)); + client = client->next; + } + network_interface = network_interface->next; + } + } + } + + root = json_pack("{ss si so*}", + "status", "ok", + "code", 200, + "icmp-clients", json_clients); + + if(root) { + result = json_dumpfd(root, fd, 0); + json_decref(root); + } else { + result = bbl_ctrl_status(fd, "error", 500, "internal error"); + json_decref(json_clients); + } + return result; +} + +static int +bbl_icmp_client_ctrl_start_stop(int fd, uint32_t session_id, bool start) +{ + bbl_session_s *session; + bbl_icmp_client_s *client; + uint32_t i; + + if(session_id) { + session = bbl_session_get(session_id); + if(session) { + client = session->icmp_client; + while(client) { + if(start) { + bbl_icmp_client_start(client); + } else { + bbl_icmp_client_stop(client); + } + client = client->next; + } + } else { + return bbl_ctrl_status(fd, "warning", 404, "session not found"); + } + } else { + for(i = 0; i < g_ctx->sessions; i++) { + session = &g_ctx->session_list[i]; + client = session->icmp_client; + while(client) { + if(start) { + bbl_icmp_client_start(client); + } else { + bbl_icmp_client_stop(client); + } + client = client->next; + } + } + } + return bbl_ctrl_status(fd, "ok", 200, NULL); +} + +int +bbl_icmp_client_ctrl_start(int fd, uint32_t session_id, json_t *arguments __attribute__((unused))) +{ + return bbl_icmp_client_ctrl_start_stop(fd, session_id, true); +} + +int +bbl_icmp_client_ctrl_stop(int fd, uint32_t session_id, json_t *arguments __attribute__((unused))) +{ + return bbl_icmp_client_ctrl_start_stop(fd, session_id, false); +} \ No newline at end of file diff --git a/code/bngblaster/src/bbl_icmp_client.h b/code/bngblaster/src/bbl_icmp_client.h new file mode 100644 index 00000000..a055677f --- /dev/null +++ b/code/bngblaster/src/bbl_icmp_client.h @@ -0,0 +1,117 @@ +/* + * BNG Blaster (BBL) - ICMP Client + * + * Christian Giese, December 2024 + * + * Copyright (C) 2020-2024, RtBrick, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __BBL_ICMP_CLIENT_H__ +#define __BBL_ICMP_CLIENT_H__ + +typedef enum { + ICMP_DOWN = 0, + ICMP_WAIT = 1, + ICMP_STARTED = 2, + ICMP_STOPPED = 3, +} __attribute__ ((__packed__)) icmp_state_t; + +typedef enum { + ICMP_RESULT_NONE = 0, + ICMP_RESULT_WAIT = 1, + ICMP_RESULT_OKAY = 2, + ICMP_RESULT_UNREACHABLE = 3, + ICMP_RESULT_REDIRECTED = 4, + ICMP_RESULT_FRAGMENTATION_NEEDED = 5 +} __attribute__ ((__packed__)) icmp_result_t; + +typedef enum { + ICMP_MODE_PING = 0, + ICMP_MODE_TRACEROUTE = 1 +} __attribute__ ((__packed__)) icmp_mode_t; + +typedef struct bbl_icmp_client_result_ping_ +{ + uint16_t seq; + uint16_t size; + uint8_t ttl; + uint8_t state; + struct timespec timestamp_tx; + struct timespec timestamp_rx; +} bbl_icmp_client_result_ping_s; + +typedef struct bbl_icmp_client_config_ +{ + uint16_t icmp_client_group_id; + char *network_interface; + + uint16_t results; + + uint16_t count; + uint16_t size; + uint8_t ttl; + + uint16_t start_delay; + uint32_t destination; /* set IPv4 destination address */ + + double interval; + time_t interval_sec; + long interval_nsec; + + bool autostart; + uint8_t mode; + + bbl_icmp_client_config_s *next; /* Next icmp client config */ +} bbl_icmp_client_config_s; + +typedef struct bbl_icmp_client_ +{ + uint32_t destination; /* set IPv4 destination address */ + uint16_t id; + uint16_t seq; + uint8_t state; + + bbl_session_s *session; + bbl_network_interface_s *network_interface; + + bbl_icmp_client_config_s *config; + bbl_icmp_client_s *next; /* Next icmp client of same session */ + + struct timer_ *state_timer; + struct timer_ *send_timer; + void *result; /* result structure is different based on type */ + uint16_t results; + uint16_t start_delay_countdown; + + uint32_t send; + uint32_t received; + + uint8_t *data; + uint16_t data_len; + +} bbl_icmp_client_s; + +bool +bbl_icmp_client_rx(bbl_session_s *session, + bbl_network_interface_s *network_interface, + bbl_ethernet_header_s *eth, + bbl_ipv4_s *ipv4, + bbl_icmp_s *icmp); + +bool +bbl_icmp_client_session_init(bbl_session_s *session); + +bool +bbl_icmp_client_network_interface_init(bbl_network_interface_s *network_interface); + +int +bbl_icmp_client_ctrl(int fd, uint32_t session_id, json_t *arguments __attribute__((unused))); + +int +bbl_icmp_client_ctrl_start(int fd, uint32_t session_id, json_t *arguments __attribute__((unused))); + +int +bbl_icmp_client_ctrl_stop(int fd, uint32_t session_id, json_t *arguments __attribute__((unused))); + +#endif diff --git a/code/bngblaster/src/bbl_network.c b/code/bngblaster/src/bbl_network.c index f873be43..e16d2a56 100644 --- a/code/bngblaster/src/bbl_network.c +++ b/code/bngblaster/src/bbl_network.c @@ -150,9 +150,15 @@ bbl_network_interfaces_add() return false; } - /* Init HTTP server */ + /* Init HTTP servers */ if(!bbl_http_server_init(network_interface)) { - LOG(ERROR, "Failed to init HTTP server for network interface %s\n", ifname); + LOG(ERROR, "Failed to init HTTP servers for network interface %s\n", ifname); + return false; + } + + /* Init ICMP clients */ + if(!bbl_icmp_client_network_interface_init(network_interface)) { + LOG(ERROR, "Failed to init ICMP clients for network interface %s\n", ifname); return false; } @@ -403,7 +409,11 @@ bbl_network_rx_icmp(bbl_network_interface_s *interface, bbl_icmp_s *icmp = (bbl_icmp_s*)ipv4->next; if(icmp->type == ICMP_TYPE_ECHO_REQUEST) { /* Send ICMP reply... */ - bbl_network_icmp_reply(interface, eth, ipv4, icmp); + if(bbl_network_icmp_reply(interface, eth, ipv4, icmp) == BBL_TXQ_OK) { + interface->stats.icmp_tx++; + } + } else { + bbl_icmp_client_rx(NULL, interface, eth, ipv4, icmp); } interface->stats.icmp_rx++; } diff --git a/code/bngblaster/src/bbl_network.h b/code/bngblaster/src/bbl_network.h index 2f49aa38..ece4860f 100644 --- a/code/bngblaster/src/bbl_network.h +++ b/code/bngblaster/src/bbl_network.h @@ -58,6 +58,9 @@ typedef struct bbl_network_interface_ uint64_t mc_packet_seq; uint16_t mc_packet_cursor; + /* ICMP */ + bbl_icmp_client_s *icmp_client; + /* TCP */ bbl_http_server_s *http_server; struct netif netif; /* LwIP interface */ diff --git a/code/bngblaster/src/bbl_session.c b/code/bngblaster/src/bbl_session.c index 30abb484..2cc63b98 100644 --- a/code/bngblaster/src/bbl_session.c +++ b/code/bngblaster/src/bbl_session.c @@ -1060,6 +1060,11 @@ bbl_sessions_init() return false; } + if(!bbl_icmp_client_session_init(session)) { + LOG_NOARG(ERROR, "Failed to create session ICMP client!\n"); + return false; + } + if(!bbl_http_client_session_init(session)) { LOG_NOARG(ERROR, "Failed to create session HTTP client!\n"); return false; diff --git a/code/bngblaster/src/bbl_session.h b/code/bngblaster/src/bbl_session.h index 93284a00..5ac82afa 100644 --- a/code/bngblaster/src/bbl_session.h +++ b/code/bngblaster/src/bbl_session.h @@ -112,6 +112,9 @@ typedef struct bbl_session_ void *access_line_profile; + /* ICMP */ + bbl_icmp_client_s *icmp_client; + /* TCP */ bbl_http_client_s *http_client; struct netif netif; /* LwIP interface */ diff --git a/code/common/src/logging.h b/code/common/src/logging.h index 0f275c00..12201b74 100644 --- a/code/common/src/logging.h +++ b/code/common/src/logging.h @@ -32,6 +32,7 @@ enum { ERROR, PCAP, IP, + ICMP, LOSS, L2TP, DHCP,