diff --git a/code/bngblaster/src/bbl.c b/code/bngblaster/src/bbl.c index 50b831b9..ae18048b 100644 --- a/code/bngblaster/src/bbl.c +++ b/code/bngblaster/src/bbl.c @@ -527,6 +527,8 @@ main(int argc, char *argv[]) if(igmp_group_count) g_ctx->config.igmp_group_count = atoi(igmp_group_count); if(igmp_zap_interval) g_ctx->config.igmp_zap_interval = atoi(igmp_zap_interval); + bbl_fragment_init(); + #ifdef BNGBLASTER_DPDK /* Init DPDK. */ if(!io_dpdk_init()) { diff --git a/code/bngblaster/src/bbl.h b/code/bngblaster/src/bbl.h index 76eb739b..2d58fe45 100644 --- a/code/bngblaster/src/bbl.h +++ b/code/bngblaster/src/bbl.h @@ -50,6 +50,7 @@ #include "bbl_tcp.h" #include "bbl_http_client.h" #include "bbl_http_server.h" +#include "bbl_fragment.h" #include "io/io.h" #include "bgp/bgp.h" diff --git a/code/bngblaster/src/bbl_access.c b/code/bngblaster/src/bbl_access.c index c929be97..7c23c9b2 100644 --- a/code/bngblaster/src/bbl_access.c +++ b/code/bngblaster/src/bbl_access.c @@ -631,11 +631,11 @@ bbl_access_rx_ipv4(bbl_access_interface_s *interface, bbl_udp_s *udp; if(ipv4->offset & ~IPV4_DF) { - /* Reassembling of fragmented IPv4 packets is currently not supported. */ session->stats.accounting_packets_rx++; session->stats.accounting_bytes_rx += eth->length; session->stats.ipv4_fragmented_rx++; interface->stats.ipv4_fragmented_rx++; + bbl_fragment_rx(interface, NULL, eth, ipv4); return; } diff --git a/code/bngblaster/src/bbl_config.c b/code/bngblaster/src/bbl_config.c index ac7189cb..01f50422 100644 --- a/code/bngblaster/src/bbl_config.c +++ b/code/bngblaster/src/bbl_config.c @@ -3499,6 +3499,7 @@ json_parse_config(json_t *root) "stream-rate-calculation", "stream-delay-calculation", "stream-burst-ms", + "reassemble-fragments", "multicast-autostart", "udp-checksum" }; @@ -3531,6 +3532,10 @@ json_parse_config(json_t *root) if(value) { g_ctx->config.stream_burst_ms = json_number_value(value) * MSEC; } + JSON_OBJ_GET_BOOL(section, value, "traffic", "reassemble-fragments"); + if(value) { + g_ctx->config.traffic_reassemble_fragments = json_boolean_value(value); + } JSON_OBJ_GET_BOOL(section, value, "traffic", "multicast-autostart"); if(value) { g_ctx->config.multicast_traffic_autostart = json_boolean_value(value); diff --git a/code/bngblaster/src/bbl_ctx.h b/code/bngblaster/src/bbl_ctx.h index f9517547..602964d5 100644 --- a/code/bngblaster/src/bbl_ctx.h +++ b/code/bngblaster/src/bbl_ctx.h @@ -21,6 +21,7 @@ typedef struct bbl_ctx_ struct timer_ *keyboard_timer; struct timer_ *tcp_timer; + struct timer_ *fragmentation_timer; struct timespec timestamp_start; struct timespec timestamp_stop; @@ -93,6 +94,8 @@ typedef struct bbl_ctx_ ldp_instance_s *ldp_instances; ldp_raw_update_s *ldp_raw_updates; + bbl_fragment_s *ipv4_fragments; + /* Scratchpad memory */ uint8_t *sp; @@ -328,6 +331,7 @@ typedef struct bbl_ctx_ /* Global Traffic */ bool traffic_autostart; bool traffic_stop_verified; + bool traffic_reassemble_fragments; /* Stream Traffic */ bool stream_autostart; diff --git a/code/bngblaster/src/bbl_def.h b/code/bngblaster/src/bbl_def.h index 63870835..e933183d 100644 --- a/code/bngblaster/src/bbl_def.h +++ b/code/bngblaster/src/bbl_def.h @@ -244,5 +244,6 @@ typedef struct bbl_http_client_ bbl_http_client_s; typedef struct bbl_http_server_config_ bbl_http_server_config_s; typedef struct bbl_http_server_ bbl_http_server_s; typedef struct bbl_http_server_connection_ bbl_http_server_connection_s; +typedef struct bbl_fragment_ bbl_fragment_s; #endif \ No newline at end of file diff --git a/code/bngblaster/src/bbl_fragment.c b/code/bngblaster/src/bbl_fragment.c new file mode 100644 index 00000000..3c314fd0 --- /dev/null +++ b/code/bngblaster/src/bbl_fragment.c @@ -0,0 +1,188 @@ +/* + * BNG Blaster (BBL) - IP Fragmentation + * + * Christian Giese, October 2024 + * + * Copyright (C) 2020-2024, RtBrick, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "bbl.h" + +static void +bbl_fragment_free(bbl_fragment_s *ipv4_fragment) +{ + if(ipv4_fragment->next) { + ipv4_fragment->next->prev = ipv4_fragment->prev; + } + if(ipv4_fragment->prev) { + ipv4_fragment->prev->next = ipv4_fragment->next; + } else { + g_ctx->ipv4_fragments = ipv4_fragment->next; + } + free(ipv4_fragment); +} + +/** + * bbl_fragment_rx + * + * This function stores incoming IPv4 fragments in fragmentation buffers, + * which are organized as a doubly-linked list. A cleanup job periodically + * removes outdated buffers. Once all fragments of a single packet have been received, + * the packet is reassembled and processed. + * + * Currently, this function supports BBL stream traffic only! + * + * @param access_interface pointer to access interface on which packet was received + * @param network_interface pointer to network interface on which packet was received + * @param eth pointer to ethernet header structure of received packet + * @param ipv4 pointer to IPv4 header structure of received packet + */ +void +bbl_fragment_rx(bbl_access_interface_s *access_interface, + bbl_network_interface_s *network_interface, + bbl_ethernet_header_s *eth, bbl_ipv4_s *ipv4) +{ + if(!g_ctx->config.traffic_reassemble_fragments) return; + + bbl_fragment_s *fragment = g_ctx->ipv4_fragments; + bbl_stream_s *stream; + + uint8_t *bbl_start; + bbl_bbl_s bbl; + + uint16_t offset; + + while(fragment) { + if(fragment->id == ipv4->id && + fragment->src == ipv4->src && + fragment->dst == ipv4->dst) { + break; + } + fragment = fragment->next; + } + if(!fragment) { + fragment = calloc(1, sizeof(bbl_fragment_s)); + fragment->id = ipv4->id; + fragment->src = ipv4->src; + fragment->dst = ipv4->dst; + if(g_ctx->ipv4_fragments) { + g_ctx->ipv4_fragments->prev = fragment; + fragment->next = g_ctx->ipv4_fragments; + } + g_ctx->ipv4_fragments = fragment; + } + + offset = (ipv4->offset & 0x1FFF) * 8; + if(offset+ipv4->payload_len > sizeof(fragment->buf)) { + LOG(INFO, "IPv4 fragmented packet to big (%u)\n", (offset+ipv4->payload_len)); + bbl_fragment_free(fragment); + return; + } + + if(eth->length > fragment->max_length) { + fragment->max_length = eth->length; + } + if(offset > fragment->max_offset) { + fragment->max_offset = offset; + } + + fragment->fragments++; + fragment->timestamp = eth->timestamp.tv_sec; + + memcpy(fragment->buf+offset, ipv4->payload, ipv4->payload_len); + fragment->recived += ipv4->payload_len; + + if(!(ipv4->offset & IPV4_MF)) { + /* Last fragment recieved. */ + fragment->expected = offset + ipv4->payload_len; + } + if(fragment->recived == fragment->expected) { + /* All fragments received. */ + if(packet_is_bbl(fragment->buf, fragment->recived)) { + /* Currently, we support only the reassembly of BBL stream packets. */ + bbl_start = fragment->buf + (fragment->recived-BBL_HEADER_LEN); + bbl.type = *(bbl_start+8); + bbl.sub_type = *(bbl_start+9); + bbl.direction = *(bbl_start+10); + bbl.tos = *(bbl_start+11); + bbl.session_id = *(uint32_t*)(bbl_start+12); + if(bbl.type == BBL_TYPE_UNICAST) { + bbl.ifindex = *(uint32_t*)(bbl_start+16); + bbl.outer_vlan_id = *(uint16_t*)(bbl_start+20); + bbl.inner_vlan_id = *(uint16_t*)(bbl_start+22); + bbl.mc_source = 0; + bbl.mc_source = 0; + } else { + bbl.mc_source = *(uint32_t*)(bbl_start+16); + bbl.mc_source = *(uint32_t*)(bbl_start+20); + bbl.ifindex = 0; + bbl.outer_vlan_id = 0; + bbl.inner_vlan_id = 0; + } + bbl.flow_id = *(uint64_t*)(bbl_start+24); + bbl.flow_seq = *(uint64_t*)(bbl_start+32); + bbl.timestamp.tv_sec = *(uint32_t*)(bbl_start+40); + bbl.timestamp.tv_nsec = *(uint32_t*)(bbl_start+44); + + eth->bbl = &bbl; + eth->length = fragment->max_length; + + if(access_interface) { + stream = bbl_stream_rx(eth, NULL); + if(stream) { + if(stream->rx_access_interface == NULL) { + stream->rx_access_interface = access_interface; + } + } + } else if (network_interface) { + stream = bbl_stream_rx(eth, network_interface->mac); + if(stream) { + if(stream->rx_network_interface != network_interface) { + if(stream->rx_network_interface) { + /* RX interface has changed! */ + stream->rx_interface_changes++; + stream->rx_interface_changed_epoch = eth->timestamp.tv_sec; + } + stream->rx_network_interface = network_interface; + } + } + } + if(stream) { + if(fragment->fragments > stream->rx_fragments) { + stream->rx_fragments = fragment->fragments; + } + if(fragment->max_offset > stream->rx_fragment_offset) { + stream->rx_fragment_offset = fragment->max_offset; + } + } + } + bbl_fragment_free(fragment); + } +} + +void +bbl_fragment_cleanup_job(timer_s *timer) +{ + bbl_fragment_s *fragment = g_ctx->ipv4_fragments; + bbl_fragment_s *next = fragment; + + /* Delete all fragments older than 10 seconds. */ + uint32_t timestamp = timer->timestamp->tv_sec - 10; + while(next) { + fragment = next; + next = fragment->next; + if(fragment->timestamp < timestamp) { + bbl_fragment_free(fragment); + } + } +} + +void +bbl_fragment_init() +{ + if(!g_ctx->config.traffic_reassemble_fragments) return; + + timer_add_periodic(&g_ctx->timer_root, &g_ctx->fragmentation_timer, + "FRAGMENT", 3, 0, NULL, + &bbl_fragment_cleanup_job); +} \ No newline at end of file diff --git a/code/bngblaster/src/bbl_fragment.h b/code/bngblaster/src/bbl_fragment.h new file mode 100644 index 00000000..5bce0725 --- /dev/null +++ b/code/bngblaster/src/bbl_fragment.h @@ -0,0 +1,39 @@ +/* + * BNG Blaster (BBL) - IP Fragmentation + * + * Christian Giese, October 2024 + * + * Copyright (C) 2020-2024, RtBrick, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __BBL_FRAGMENT_H__ +#define __BBL_FRAGMENT_H__ + +typedef struct bbl_fragment_ { + uint32_t timestamp; + uint32_t src; + uint32_t dst; + uint16_t id; + uint16_t fragments; /* Number of fragments */ + uint16_t max_offset; /* Max offset value */ + uint16_t max_length; /* Max length (L2) */ + uint16_t recived; + uint16_t expected; + + struct bbl_fragment_ *prev; + struct bbl_fragment_ *next; + + uint8_t buf[4050]; + +} bbl_fragment_s; + +void +bbl_fragment_rx(bbl_access_interface_s *access_interface, + bbl_network_interface_s *network_interface, + bbl_ethernet_header_s *eth, bbl_ipv4_s *ipv4); + +void +bbl_fragment_init(); + +#endif diff --git a/code/bngblaster/src/bbl_network.c b/code/bngblaster/src/bbl_network.c index cad4d992..8d389c92 100644 --- a/code/bngblaster/src/bbl_network.c +++ b/code/bngblaster/src/bbl_network.c @@ -469,6 +469,8 @@ bbl_network_rx_handler(bbl_network_interface_s *interface, } else if(ipv4->protocol == PROTOCOL_IPV4_OSPF && interface->ospfv2_interface) { ospf_handler_rx_ipv4(interface, eth, ipv4); return; + } else if(ipv4->offset & ~IPV4_DF) { + bbl_fragment_rx(NULL, interface, eth, ipv4); } break; case ETH_TYPE_IPV6: diff --git a/code/bngblaster/src/bbl_stream.c b/code/bngblaster/src/bbl_stream.c index 8f1f0bcc..31601b12 100644 --- a/code/bngblaster/src/bbl_stream.c +++ b/code/bngblaster/src/bbl_stream.c @@ -2173,6 +2173,9 @@ bbl_stream_reset(bbl_stream_s *stream) stream->rx_min_delay_us = 0; stream->rx_max_delay_us = 0; stream->rx_len = 0; + stream->rx_fragments = 0; + stream->rx_fragment_offset = 0; + stream->rx_ttl = 0; stream->rx_priority = 0; stream->rx_outer_vlan_pbit = 0; stream->rx_inner_vlan_pbit = 0; @@ -2596,6 +2599,10 @@ bbl_stream_json(bbl_stream_s *stream, bool debug) json_object_set_new(root, "rx-interface-changes", json_integer(stream->rx_interface_changes)); json_object_set_new(root, "rx-interface-changed-epoch", json_integer(stream->rx_interface_changed_epoch)); } + if(stream->rx_fragments) { + json_object_set_new(root, "rx-fragments", json_integer(stream->rx_fragments)); + json_object_set_new(root, "rx-fragment-offset", json_integer(stream->rx_fragment_offset)); + } if(stream->config->rx_mpls1) { json_object_set_new(root, "rx-mpls1-expected", json_integer(stream->config->rx_mpls1_label)); } diff --git a/code/bngblaster/src/bbl_stream.h b/code/bngblaster/src/bbl_stream.h index a097a6cc..aabe4702 100644 --- a/code/bngblaster/src/bbl_stream.h +++ b/code/bngblaster/src/bbl_stream.h @@ -197,6 +197,9 @@ typedef struct bbl_stream_ __time_t rx_interface_changed_epoch; uint8_t rx_interface_changes; + uint8_t rx_fragments; + uint16_t rx_fragment_offset; /* Max fragmentation offset received */ + uint8_t rx_ttl; /* IPv4 or IPv6 TTL */ uint8_t rx_priority; /* IPv4 TOS or IPv6 TC */ uint8_t rx_outer_vlan_pbit; diff --git a/docs/_sources/configuration/traffic.rst.txt b/docs/_sources/configuration/traffic.rst.txt index 58348038..59204330 100644 --- a/docs/_sources/configuration/traffic.rst.txt +++ b/docs/_sources/configuration/traffic.rst.txt @@ -52,4 +52,9 @@ +---------------------------------+--------------------------------------------------------+ | **udp-checksum** | | Enable UDP checksums. | | | | Default: false | ++---------------------------------+--------------------------------------------------------+ +| **reassemble-fragments** | | Enable reassembly of fragmented IPv4 stream packets. | +| | | Currently, this is restricted to BBL stream traffic | +| | | only! | +| | | Default: false | +---------------------------------+--------------------------------------------------------+ \ No newline at end of file