BNG Blaster 0.8 WIP - 2

This commit is contained in:
Christian Giese
2022-08-24 21:54:09 +00:00
parent d9317807dc
commit a6fc065d21
38 changed files with 1223 additions and 393 deletions
+15 -4
View File
@@ -41,10 +41,21 @@ target_include_directories(bngblaster PRIVATE ${LWIP_INCLUDE_DIRS})
target_compile_definitions(bngblaster PRIVATE ${LWIP_DEFINITIONS} ${LWIP_MBEDTLS_DEFINITIONS})
target_link_libraries(bngblaster ${LWIP_SANITIZER_LIBS} lwipcore lwipcontribportunix)
# Add experimental netmap support
if(BNGBLASTER_NETMAP)
add_definitions(-DBNGBLASTER_NETMAP)
target_link_libraries(bngblaster netmap)
# Add DPDK support
if(BNGBLASTER_DPDK)
message(STATUS "Build bngblaster with DPDK support")
find_package(PkgConfig REQUIRED)
if (PKG_CONFIG_FOUND)
pkg_check_modules(DPDK "libdpdk")
if (DPDK_FOUND)
message(STATUS "Found DPDK via pkg-config")
add_definitions(-DBNGBLASTER_DPDK)
add_definitions(${DPDK_CFLAGS})
set(DPDK_LIBS -Wl,--whole-archive ${DPDK_LIBRARIES} -lpthread -lnuma -ldl -Wl,--no-whole-archive)
include_directories(${DPDK_INCLUDE_DIR})
target_link_libraries(bngblaster ${DPDK_LIBS})
endif()
endif()
endif()
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 8.0)
+2 -2
View File
@@ -230,8 +230,8 @@ bbl_print_version (void)
printf(" SHA: %s\n", GIT_SHA);
}
printf("IO Modes: packet_mmap_raw (default), packet_mmap, raw");
#ifdef BNGBLASTER_NETMAP
printf(", netmap");
#ifdef BNGBLASTER_DPDK
printf(", dpdk");
#endif
printf("\n");
}
+3
View File
@@ -22,6 +22,7 @@
#include "bbl_def.h"
#include "bbl_protocols.h"
#include "io/io_def.h"
#include "bgp/bgp_def.h"
#include "isis/isis_def.h"
@@ -39,6 +40,8 @@
#include "bbl_a10nsp.h"
#include "bbl_li.h"
#include "bbl_tcp.h"
#include "io/io.h"
#include "bgp/bgp.h"
#include "isis/isis.h"
+1 -1
View File
@@ -63,7 +63,7 @@ bbl_a10nsp_interfaces_add()
CIRCLEQ_INSERT_TAIL(&g_ctx->a10nsp_interface_qhead, a10nsp_interface, a10nsp_interface_qnode);
/* Init TXQ */
a10nsp_interface->txq = calloc(1, sizeof(bbl_txq_t));
a10nsp_interface->txq = calloc(1, sizeof(bbl_txq_s));
bbl_txq_init(a10nsp_interface->txq, BBL_TXQ_DEFAULT_SIZE);
/* Init ethernet */
+1 -1
View File
@@ -22,7 +22,7 @@
typedef struct bbl_a10nsp_interface_
{
bbl_interface_s *interface;
bbl_txq_t *txq;
bbl_txq_s *txq;
uint8_t mac[ETH_ADDR_LEN];
bool qinq;
+2 -2
View File
@@ -71,7 +71,7 @@ bbl_access_interfaces_add()
CIRCLEQ_INSERT_TAIL(&g_ctx->access_interface_qhead, access_interface, access_interface_qnode);
/* Init TXQ */
access_interface->txq = calloc(1, sizeof(bbl_txq_t));
access_interface->txq = calloc(1, sizeof(bbl_txq_s));
bbl_txq_init(access_interface->txq, BBL_TXQ_DEFAULT_SIZE);
/* TX list init */
@@ -254,7 +254,7 @@ bbl_access_igmp_zapping(timer_s *timer)
}
}
if(!ctx->zapping && group->state < IGMP_GROUP_ACTIVE) {
if(!g_ctx->zapping && group->state < IGMP_GROUP_ACTIVE) {
return;
}
+1 -1
View File
@@ -13,7 +13,7 @@
typedef struct bbl_access_interface_
{
bbl_interface_s *interface;
bbl_txq_t *txq;
bbl_txq_s *txq;
uint8_t mac[ETH_ADDR_LEN];
uint32_t send_requests;
+60 -25
View File
@@ -317,12 +317,66 @@ json_parse_link(json_t *link, bbl_link_config_s *link_config)
return false;
}
}
value = json_object_get(link, "qdisc-bypass");
if (json_is_number(value)) {
link_config->qdisc_bypass = json_number_value(value);
} else {
link_config->qdisc_bypass = g_ctx->config.qdisc_bypass;
}
if (json_unpack(link, "{s:s}", "io-mode", &s) == 0) {
if (strcmp(s, "packet_mmap_raw") == 0) {
link_config->io_mode = IO_MODE_PACKET_MMAP_RAW;
} else if (strcmp(s, "packet_mmap") == 0) {
link_config->io_mode = IO_MODE_PACKET_MMAP;
} else if (strcmp(s, "raw") == 0) {
link_config->io_mode = IO_MODE_RAW;
#if BNGBLASTER_DPDK
} else if (strcmp(s, "dpdk") == 0) {
link_config->io_mode = IO_MODE_DPDK;
#endif
} else {
fprintf(stderr, "Config error: Invalid value for links->io-mode\n");
return false;
}
} else {
link_config->io_mode = g_ctx->config.io_mode;
}
value = json_object_get(link, "io-slots");
if (json_is_number(value)) {
link_config->io_slots = json_number_value(value);
} else {
link_config->io_slots = g_ctx->config.io_slots;
}
value = json_object_get(link, "io-stream-max-ppi");
if (json_is_number(value)) {
link_config->io_stream_max_ppi = json_number_value(value);
} else {
link_config->io_stream_max_ppi = g_ctx->config.io_stream_max_ppi;
}
value = json_object_get(link, "tx-interval");
if (json_is_number(value)) {
link_config->tx_interval = json_number_value(value) * MSEC;
} else {
link_config->tx_interval = g_ctx->config.tx_interval;
}
value = json_object_get(link, "rx-interval");
if (json_is_number(value)) {
link_config->rx_interval = json_number_value(value) * MSEC;
} else {
link_config->rx_interval = g_ctx->config.rx_interval;
}
value = json_object_get(link, "tx-threads");
if (value) {
link_config->tx_threads = json_number_value(value);
}
value = json_object_get(link, "rx-threads");
if (value) {
link_config->rx_threads = json_number_value(value);
}
value = json_object_get(link, "lag-id");
if (value) {
link_config->lag_id = json_number_value(value);
}
value = json_object_get(link, "lacp-priority");
if (value) {
link_config->lacp_priority = json_number_value(value);
@@ -335,25 +389,6 @@ json_parse_link(json_t *link, bbl_link_config_s *link_config)
} else {
link_config->lacp_interval = 1000;
}
if (json_unpack(link, "{s:s}", "io-mode", &s) == 0) {
if (strcmp(s, "packet_mmap_raw") == 0) {
link_config->io_mode = IO_MODE_PACKET_MMAP_RAW;
#if BNGBLASTER_NETMAP
} else if (strcmp(s, "netmap") == 0) {
link_config->io_mode = IO_MODE_NETMAP;
#endif
} else if (strcmp(s, "packet_mmap") == 0) {
link_config->io_mode = IO_MODE_PACKET_MMAP;
} else if (strcmp(s, "raw") == 0) {
link_config->io_mode = IO_MODE_RAW;
} else {
fprintf(stderr, "Config error: Invalid value for links->io-mode\n");
return false;
}
} else {
link_config->io_mode = g_ctx->config.io_mode;
}
return true;
}
@@ -2036,14 +2071,14 @@ json_parse_config(json_t *root)
if (json_unpack(section, "{s:s}", "io-mode", &s) == 0) {
if (strcmp(s, "packet_mmap_raw") == 0) {
g_ctx->config.io_mode = IO_MODE_PACKET_MMAP_RAW;
#if BNGBLASTER_NETMAP
} else if (strcmp(s, "netmap") == 0) {
g_ctx->config.io_mode = IO_MODE_NETMAP;
#endif
} else if (strcmp(s, "packet_mmap") == 0) {
g_ctx->config.io_mode = IO_MODE_PACKET_MMAP;
} else if (strcmp(s, "raw") == 0) {
g_ctx->config.io_mode = IO_MODE_RAW;
#if BNGBLASTER_DPDK
} else if (strcmp(s, "dpdk") == 0) {
g_ctx->config.io_mode = IO_MODE_DPDK;
#endif
} else {
fprintf(stderr, "Config error: Invalid value for interfaces->io-mode\n");
return false;
+11 -1
View File
@@ -123,8 +123,18 @@ typedef struct bbl_link_config_
char *interface;
char *description;
uint8_t mac[ETH_ADDR_LEN];
bbl_io_mode_t io_mode;
io_mode_t io_mode;
uint16_t io_slots;
uint16_t io_stream_max_ppi; /* Traffic stream max packets per interval */
bool qdisc_bypass;
uint64_t tx_interval; /* TX interval in nsec */
uint64_t rx_interval; /* RX interval in nsec */
uint8_t tx_threads;
uint8_t rx_threads;
uint8_t lag_id;
uint32_t lacp_priority;
+1
View File
@@ -87,6 +87,7 @@ typedef struct bbl_ctx_
char *ctrl_socket_path;
void *stream_thread; /* single linked list of threads */
io_thread_s *io_threads; /* single linked list of threads */
bool tcp;
+4 -13
View File
@@ -45,14 +45,6 @@
#include "lwip/api.h"
#endif
/* Experimental NETMAP Support */
#ifdef BNGBLASTER_NETMAP
#define LIBNETMAP_NOTHREADSAFE
#include <net/netmap.h>
#define NETMAP_WITH_LIBS
#include <net/netmap_user.h>
#endif
#define IO_BUFFER_LEN 9216
#define SCRATCHPAD_LEN 4096
#define CHALLENGE_LEN 16
@@ -108,11 +100,10 @@
#define DATA_TRAFFIC_MAX_LEN 1920
typedef enum {
IO_MODE_PACKET_MMAP_RAW = 0, /* RX packet_mmap ring / TX raw sockets */
IO_MODE_PACKET_MMAP, /* RX/TX packet_mmap ring */
IO_MODE_RAW, /* RX/TX raw sockets */
IO_MODE_NETMAP /* RX/TX netmap ring */
} __attribute__ ((__packed__)) bbl_io_mode_t;
INTERFACE_DISABLED = 0,
INTERFACE_UP,
INTERFACE_DOWN
} __attribute__ ((__packed__)) bbl_interface_state_type_t;
typedef enum {
ACCESS_TYPE_PPPOE = 0,
+6 -55
View File
@@ -25,7 +25,7 @@ bbl_interface_rate_job(timer_s *timer) {
* @brief This functions locks the interface
* creating the file "/run/lock/bngblaster_<interface>.lock".
*
* @param interface interface
* @param interface_name interface name
* @return false if failed to lock (e.g. in use)
*/
static bool
@@ -84,45 +84,11 @@ bbl_interface_unlock_all()
}
}
static bool
bbl_interface_set_kernel_info(bbl_interface_s *interface)
{
struct ifreq ifr = {0};
if(interface->io.mode == IO_MODE_DPDK) {
/* This will not work for DPDK bound interfaces. */
return true;
}
int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface->name);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) {
LOG(ERROR, "Getting MAC address error %s (%d) for interface %s\n",
strerror(errno), errno, interface->name);
close(fd);
return false;
}
memcpy(&interface->mac, ifr.ifr_hwaddr.sa_data, IFHWADDRLEN);
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface->name);
if (ioctl(fd, SIOCGIFINDEX, &ifr) == -1) {
LOG(ERROR, "Get interface index error %s (%d) for interface %s\n",
strerror(errno), errno, interface->name);
close(fd);
return false;
}
interface->ifindex = ifr.ifr_ifindex;
close(fd);
return true;
}
/**
* bbl_add_interface
*
* @param interface interface name
* @param link_config optional link configuration
* @param link_config link configuration
* @return interface
*/
static bbl_interface_s *
@@ -143,32 +109,17 @@ bbl_interface_add(char *interface_name, bbl_link_config_s *link_config)
}
CIRCLEQ_INSERT_TAIL(&g_ctx->interface_qhead, interface, interface_qnode);
if(!bbl_interface_set_kernel_info(interface)) {
return NULL;
}
interface->config = link_config;
interface->io.rx_buf = malloc(IO_BUFFER_LEN);
interface->io.tx_buf = malloc(IO_BUFFER_LEN);
interface->io.mode = link_config->io_mode;
if(*(uint64_t*)link_config->mac & 0xffffffffffff00) {
memcpy(interface->mac, link_config->mac, ETH_ADDR_LEN);
}
if(!bbl_lag_interface_add(interface, link_config)) {
return NULL;
}
/* The BNG Blaster supports multiple IO modes where packet_mmap is
* selected per default. */
if(!bbl_io_add_interface(interface)) {
if(!io_interface_init(interface)) {
return NULL;
}
/*
* Timer to compute periodic rates.
*/
timer_add_periodic(&g_ctx->timer_root, &interface->rate_job, "Rate Computation", 1, 0, interface,
&bbl_interface_rate_job);
/* Timer to compute periodic rates. */
timer_add_periodic(&g_ctx->timer_root, &interface->rate_job, "Rate Computation",
1, 0, interface, &bbl_interface_rate_job);
return interface;
}
+5 -31
View File
@@ -21,40 +21,18 @@ typedef struct bbl_interface_
bbl_access_interface_s *access;
bbl_network_interface_s *network;
bbl_interface_state_type_t state;
uint8_t mac[ETH_ADDR_LEN];
uint32_t send_requests;
CIRCLEQ_ENTRY(bbl_interface_) interface_qnode;
CIRCLEQ_ENTRY(bbl_interface_) interface_lag_qnode;
struct {
bbl_io_mode_t mode;
int fd_tx;
int fd_rx;
struct tpacket_req req_tx;
struct tpacket_req req_rx;
struct sockaddr_ll addr;
uint8_t *rx_buf; /* RX buffer */
uint16_t rx_len;
uint8_t *tx_buf; /* TX buffer */
uint16_t tx_len;
uint8_t *ring_tx; /* TX ring buffer */
uint8_t *ring_rx; /* RX ring buffer */
uint16_t cursor_tx; /* slot # inside the ring buffer */
uint16_t cursor_rx; /* slot # inside the ring buffer */
uint16_t queued_tx;
bool pollout;
bool ctrl; /* control traffic */
#ifdef BNGBLASTER_NETMAP
struct nm_desc *port;
#endif
io_handle_s *rx;
io_handle_s *tx;
uint8_t *sp;
bool ctrl;
} io;
uint32_t ifindex; /* interface index */
@@ -83,10 +61,6 @@ typedef struct bbl_interface_
struct timer_ *tx_job;
struct timer_ *rx_job;
struct timer_ *rate_job;
struct timespec tx_timestamp; /* user space timestamps */
struct timespec rx_timestamp; /* user space timestamps */
} bbl_interface_s;
void
+2 -17
View File
@@ -10,9 +10,6 @@
#include "bbl_pcap.h"
#include "bbl_rx.h"
#include "bbl_tx.h"
#ifdef BNGBLASTER_NETMAP
#include "bbl_io_netmap.h"
#endif
void
bbl_io_packet_mmap_rx_job(timer_s *timer)
@@ -308,13 +305,8 @@ bbl_io_send(bbl_interface_s *interface, uint8_t *packet, uint16_t packet_len)
case IO_MODE_PACKET_MMAP:
result = bbl_io_packet_mmap_send(interface, packet, packet_len);
break;
case IO_MODE_NETMAP:
#ifdef BNGBLASTER_NETMAP
result = bbl_io_netmap_send(interface, packet, packet_len);
#else
result = false;
#endif
break;
default:
return false;
}
if(result) {
@@ -376,13 +368,6 @@ bbl_io_add_interface(bbl_interface_s *interface)
int qdisc_bypass = 1;
int slots = g_ctx->config.io_slots;
#ifdef BNGBLASTER_NETMAP
if(interface->io.mode == IO_MODE_NETMAP) {
return bbl_io_netmap_add_interface(ctx, interface);
}
#endif
/*
* Open RAW socket for all ethertypes.
* https://man7.org/linux/man-pages/man7/packet.7.html
+1
View File
@@ -21,6 +21,7 @@
#ifndef __BBL_IO_H__
#define __BBL_IO_H__
bool
bbl_io_send(bbl_interface_s *interface, uint8_t *packet, uint16_t packet_len);
-197
View File
@@ -1,197 +0,0 @@
/*
* BNG Blaster (BBL) - Netmap
*
* Christian Giese, October 2020
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "bbl.h"
#include "bbl_pcap.h"
#include "bbl_rx.h"
#include "bbl_tx.h"
#ifdef BNGBLASTER_NETMAP
void
bbl_io_netmap_rx_job (timer_s *timer)
{
bbl_interface_s *interface;
bbl_ctx_s *ctx;
struct netmap_ring *ring;
unsigned int i;
uint8_t *eth_start;
uint16_t eth_len;
bbl_ethernet_header_t *eth;
protocol_error_t decode_result;
interface = timer->data;
if (!interface) {
return;
}
ctx = interface->ctx;
/* Get RX timestamp */
clock_gettime(CLOCK_MONOTONIC, &interface->rx_timestamp);
ring = NETMAP_RXRING(interface->io.port->nifp, 0);
while (!nm_ring_empty(ring)) {
i = ring->cur;
eth_start = (uint8_t*)NETMAP_BUF(ring, ring->slot[i].buf_idx);
eth_len = ring->slot[i].len;
interface->stats.packets_rx++;
interface->stats.bytes_rx += eth_len;
interface->io.ctrl = true;
decode_result = decode_ethernet(eth_start, eth_len, interface->ctx->sp_rx, SCRATCHPAD_LEN, &eth);
if(decode_result == PROTOCOL_SUCCESS) {
#if 0
/* Copy RX timestamp */
eth->timestamp.tv_sec = ring->ts.tv_sec;
eth->timestamp.tv_nsec = ring->ts.tv_usec * 1000;
#endif
/* Copy RX timestamp */
eth->timestamp.tv_sec = interface->rx_timestamp.tv_sec;
eth->timestamp.tv_nsec = interface->rx_timestamp.tv_nsec;
bbl_rx_handler(interface, eth);
} else if (decode_result == UNKNOWN_PROTOCOL) {
interface->stats.unknown++;
} else {
interface->stats.decode_error++;
}
/* Dump the packet into pcap file. */
if (g_ctx->pcap.write_buf && (interface->io.ctrl || g_ctx->pcap.include_streams)) {
pcapng_push_packet_header(ctx, &interface->rx_timestamp, eth_start, eth_len,
interface->pcap_index, PCAPNG_EPB_FLAGS_INBOUND);
}
ring->head = ring->cur = nm_ring_next(ring, i);
}
pcapng_fflush(ctx);
ioctl(interface->io.port->fd, NIOCRXSYNC, NULL);
}
void
bbl_io_netmap_tx_job (timer_s *timer)
{
bbl_interface_s *interface;
bbl_ctx_s *ctx;
struct netmap_ring *ring;
unsigned int i;
uint8_t *buf;
uint16_t len;
uint16_t packets = 0;
protocol_error_t tx_result = IGNORED;
interface = timer->data;
if (!interface) {
return;
}
ctx = interface->ctx;
/* Get TX timestamp */
clock_gettime(CLOCK_MONOTONIC, &interface->tx_timestamp);
ring = NETMAP_TXRING(interface->io.port->nifp, 0);
while(tx_result != EMPTY) {
/* Check if this slot available for writing. */
if(nm_ring_empty(ring)) {
interface->stats.no_buffer++;
break;
}
i = ring->cur;
buf = (uint8_t*)NETMAP_BUF(ring, ring->slot[i].buf_idx);
tx_result = bbl_tx(ctx, interface, buf, &len);
if(tx_result == PROTOCOL_SUCCESS) {
packets++;
interface->stats.packets_tx++;
interface->stats.bytes_tx += len;
ring->slot[i].len = len;
ring->head = ring->cur = nm_ring_next(ring, i);
/* Dump the packet into pcap file. */
if(g_ctx->pcap.write_buf && (interface->io.ctrl || g_ctx->pcap.include_streams)) {
pcapng_push_packet_header(ctx, &interface->tx_timestamp,
buf, len, interface->pcap_index,
PCAPNG_EPB_FLAGS_OUTBOUND);
}
}
}
if(packets) {
pcapng_fflush(ctx);
ioctl(interface->io.port->fd, NIOCTXSYNC, NULL);
}
}
/**
* bbl_io_netmap_send
*
* Send single packet trough given interface.
*
* @param interface interface.
* @param packet packet to be send
* @param packet_len packet length
*/
bool
bbl_io_netmap_send (bbl_interface_s *interface, uint8_t *packet, uint16_t packet_len) {
struct netmap_ring *ring;
unsigned int i;
uint8_t *buf;
ring = NETMAP_TXRING(interface->io.port->nifp, 0);
if (nm_ring_empty(ring)) {
interface->stats.no_buffer++;
return false;
}
i = ring->cur;
buf = (uint8_t*)NETMAP_BUF(ring, ring->slot[i].buf_idx);
memcpy(buf, packet, packet_len);
ring->slot[i].len = packet_len;
ring->head = ring->cur = nm_ring_next(ring, i);
return true;
}
/**
* bbl_io_netmap_add_interface
*
* @param ctx global context
* @param interface interface.
*/
bool
bbl_io_netmap_add_interface(bbl_interface_s *interface) {
char timer_name[128];
char netmap_port[128];
snprintf(netmap_port, sizeof(netmap_port), "netmap:%s", interface->name);
/*
* Open netmap port.
*/
interface->io.port = nm_open(netmap_port, NULL, NETMAP_NO_TX_POLL, NULL);
if (interface->io.port == NULL) {
if (!errno) {
LOG(ERROR, "Failed to nm_open(%s): not a netmap port\n", netmap_port);
} else {
LOG(ERROR, "Failed to nm_open(%s): %s\n", netmap_port, strerror(errno));
}
return false;
}
/*
* Add an periodic timer for polling I/O.
*/
snprintf(timer_name, sizeof(timer_name), "%s TX", interface->name);
timer_add_periodic(&g_ctx->timer_root, &interface->tx_job, timer_name, 0, g_ctx->config.tx_interval, interface, &bbl_io_netmap_tx_job);
snprintf(timer_name, sizeof(timer_name), "%s RX", interface->name);
timer_add_periodic(&g_ctx->timer_root, &interface->rx_job, timer_name, 0, g_ctx->config.rx_interval, interface, &bbl_io_netmap_rx_job);
return true;
}
#endif
-22
View File
@@ -1,22 +0,0 @@
/*
* BNG Blaster (BBL) - Netmap
*
* Christian Giese, October 2020
*
* Netmap is a an framework for very fast packet I/O from userspace.
* https://github.com/luigirizzo/netmap
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __BBL_NETMAP_H__
#define __BBL_NETMAP_H__
bool
bbl_io_netmap_send(bbl_interface_s *interface, uint8_t *packet, uint16_t packet_len);
bool
bbl_io_netmap_add_interface(bbl_interface_s *interface);
#endif
+4 -1
View File
@@ -19,7 +19,7 @@ typedef struct bbl_lag_
CIRCLEQ_HEAD(interface_, bbl_interface_ ) interface_qhead; /* list of interfaces */
CIRCLEQ_ENTRY(bbl_lag_) lag_qnode;
bool up;
bbl_interface_state_type_t state;
struct {
uint64_t packets_tx;
@@ -32,6 +32,9 @@ typedef struct bbl_lag_
bbl_rate_s rate_bytes_rx;
} stats;
struct timer_ *lacp_timer;
struct timer_ *tx_job;
struct timer_ *rate_job;
} bbl_lag_s;
+1 -1
View File
@@ -93,7 +93,7 @@ bbl_network_interfaces_add()
CIRCLEQ_INSERT_TAIL(&g_ctx->network_interface_qhead, network_interface, network_interface_qnode);
/* Init TXQ */
network_interface->txq = calloc(1, sizeof(bbl_txq_t));
network_interface->txq = calloc(1, sizeof(bbl_txq_s));
bbl_txq_init(network_interface->txq, BBL_TXQ_DEFAULT_SIZE);
/* Init ethernet */
+1 -1
View File
@@ -15,7 +15,7 @@ typedef struct bbl_network_interface_
char *name;
bbl_interface_s *interface;
struct bbl_network_interface_ *next;
bbl_txq_t *txq;
bbl_txq_s *txq;
uint16_t vlan;
bbl_mpls_t tx_label;
+3
View File
@@ -1812,6 +1812,9 @@ bbl_tx(bbl_interface_s *interface, uint8_t *buf, uint16_t *len)
return bbl_tx_encode_interface_packet(interface, buf, len);
}
if(interface->state != INTERFACE_UP) {
return EMPTY;
}
if(interface->access) {
access_interface = interface->access;
+46 -5
View File
@@ -11,7 +11,8 @@
#include "bbl_session.h"
bool
bbl_txq_init(bbl_txq_t *txq, uint16_t size) {
bbl_txq_init(bbl_txq_s *txq, uint16_t size)
{
txq->ring = malloc(size * sizeof(bbl_txq_slot_t));
if(!txq->ring) {
return false;
@@ -24,7 +25,8 @@ bbl_txq_init(bbl_txq_t *txq, uint16_t size) {
}
bool
bbl_txq_is_empty(bbl_txq_t *txq) {
bbl_txq_is_empty(bbl_txq_s *txq)
{
if(txq->read == txq->write) {
return true;
}
@@ -32,7 +34,8 @@ bbl_txq_is_empty(bbl_txq_t *txq) {
}
bool
bbl_txq_is_full(bbl_txq_t *txq) {
bbl_txq_is_full(bbl_txq_s *txq)
{
if(txq->read == txq->next) {
return true;
}
@@ -48,7 +51,8 @@ bbl_txq_is_full(bbl_txq_t *txq) {
* @return number of bytes copied
*/
uint16_t
bbl_txq_from_buffer(bbl_txq_t *txq, uint8_t *buf) {
bbl_txq_from_buffer(bbl_txq_s *txq, uint8_t *buf)
{
bbl_txq_slot_t *slot;
if(txq->read == txq->write) {
@@ -73,7 +77,8 @@ bbl_txq_from_buffer(bbl_txq_t *txq, uint8_t *buf) {
* @return bbl_txq_result_t
*/
bbl_txq_result_t
bbl_txq_to_buffer(bbl_txq_t *txq, bbl_ethernet_header_t *eth) {
bbl_txq_to_buffer(bbl_txq_s *txq, bbl_ethernet_header_t *eth)
{
bbl_txq_slot_t *slot;
if(txq->read == txq->next) {
@@ -92,4 +97,40 @@ bbl_txq_to_buffer(bbl_txq_t *txq, bbl_ethernet_header_t *eth) {
txq->stats.encode_error++;
return BBL_TXQ_ENCODE_ERROR;
}
}
bbl_txq_slot_t *
bbl_txq_read_slot(bbl_txq_s *txq) {
if(txq->read == txq->write) {
return NULL;
}
return txq->ring + txq->read;
}
void
bbl_txq_read_next(bbl_txq_s *txq)
{
txq->read++;
if(txq->read == txq->size) {
txq->read = 0;
}
}
bbl_txq_slot_t *
bbl_txq_write_slot(bbl_txq_s *txq) {
if(txq->read == txq->next) {
txq->stats.full++;
return NULL;
}
return txq->ring + txq->write;
}
void
bbl_txq_write_next(bbl_txq_s *txq)
{
txq->write = txq->next++;
if(txq->next == txq->size) {
txq->next = 0;
}
}
+20 -11
View File
@@ -14,7 +14,7 @@
#define __BBL_TXQ_H__
#define BBL_TXQ_DEFAULT_SIZE 4096
#define BBL_TXQ_BUFFER_LEN 4092
#define BBL_TXQ_BUFFER_LEN 4074
typedef enum bbl_ring_result_ {
BBL_TXQ_OK = 0,
@@ -23,35 +23,44 @@ typedef enum bbl_ring_result_ {
} bbl_txq_result_t;
typedef struct bbl_txq_slot_ {
struct timespec timestamp;
uint16_t vlan_tci;
uint16_t vlan_tpid;
uint16_t packet_len;
uint8_t packet[BBL_TXQ_BUFFER_LEN];
} bbl_txq_slot_t;
typedef struct bbl_txq_ {
bbl_txq_slot_t *ring; /* ring buffer */
uint16_t size; /* number of send slots */
uint16_t read; /* current read slot */
uint16_t write; /* current write slot */
uint16_t next; /* next write slot */
uint16_t size; /* number of send slots */
char _pad0 __attribute__((__aligned__(CACHE_LINE_SIZE))); /* empty cache line */
volatile uint16_t write; /* current write slot */
volatile uint16_t next; /* next write slot */
struct {
uint32_t full;
uint32_t encode_error;
} stats;
} bbl_txq_t;
char _pad1 __attribute__((__aligned__(CACHE_LINE_SIZE))); /* empty cache line */
volatile uint16_t read; /* current read slot */
} bbl_txq_s;
bool
bbl_txq_init(bbl_txq_t *txq, uint16_t slots);
bbl_txq_init(bbl_txq_s *txq, uint16_t slots);
bool
bbl_txq_is_empty(bbl_txq_t *txq);
bbl_txq_is_empty(bbl_txq_s *txq);
bool
bbl_txq_is_full(bbl_txq_t *txq);
bbl_txq_is_full(bbl_txq_s *txq);
uint16_t
bbl_txq_from_buffer(bbl_txq_t *txq, uint8_t *buf);
bbl_txq_from_buffer(bbl_txq_s *txq, uint8_t *buf);
bbl_txq_result_t
bbl_txq_to_buffer(bbl_txq_t *txq, bbl_ethernet_header_t *eth);
bbl_txq_to_buffer(bbl_txq_s *txq, bbl_ethernet_header_t *eth);
#endif
+50
View File
@@ -0,0 +1,50 @@
/*
* BNG Blaster (BBL) - IO
*
* Christian Giese, July 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "io.h"
/**
* bbl_io_send
*
* Send single packet trough given interface.
*
* @param interface interface.
* @param packet packet to be send
* @param packet_len packet length
*/
bool
bbl_io_send(bbl_interface_t *interface, uint8_t *packet, uint16_t packet_len) {
bbl_ctx_t *ctx = interface->ctx;
bool result = false;
switch (interface->io.mode) {
case IO_MODE_PACKET_MMAP_RAW:
case IO_MODE_RAW:
result = bbl_io_raw_send(interface, packet, packet_len);
break;
case IO_MODE_PACKET_MMAP:
result = bbl_io_packet_mmap_send(interface, packet, packet_len);
break;
default:
return false;
}
if(result) {
interface->stats.packets_tx++;
interface->stats.bytes_tx += packet_len;
/* Dump the packet into pcap file. */
if(g_ctx->pcap.write_buf && (interface->io.ctrl || ctx->pcap.include_streams)) {
pcapng_push_packet_header(ctx, &interface->tx_timestamp,
packet, packet_len, interface->pcap_index,
PCAPNG_EPB_FLAGS_OUTBOUND);
pcapng_fflush(ctx);
}
}
return result;
}
+24
View File
@@ -0,0 +1,24 @@
/*
* BNG Blaster (BBL) - IO
*
* Christian Giese, August 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __BBL_IO_H__
#define __BBL_IO_H__
#include "../bbl.h"
#include "../bbl_pcap.h"
#include "../bbl_rx.h"
#include "../bbl_tx.h"
#include "io_def.h"
#include "io_socket.h"
#include "io_interface.h"
#include "io_raw.h"
#include "io_packet_mmap.h"
#endif
+101
View File
@@ -0,0 +1,101 @@
/*
* BNG Blaster (BBL) - IO Definitions
*
* Christian Giese, August 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __BBL_IO_DEF_H__
#define __BBL_IO_DEF_H__
typedef struct io_handle_ io_handle_s;
typedef struct io_thread_ io_thread_s;
typedef enum io_result_ {
IO_SUCCESS,
IO_REDIRECT,
IO_ERROR,
IO_DECODE_ERROR,
IO_ENCODE_ERROR,
IO_FULL,
IO_EMPTY
} __attribute__ ((__packed__)) io_result_t;
typedef enum {
IO_DISABLED = 0,
IO_INGRESS = 1,
IO_EGRESS = 2,
IO_DUPLEX = 3
} __attribute__ ((__packed__)) io_direction_t;
typedef enum {
IO_MODE_DISABLED = 0,
IO_MODE_PACKET_MMAP_RAW, /* packet_mmap ring (RX) and raw sockets (TX) */
IO_MODE_PACKET_MMAP, /* packet_mmap ring */
IO_MODE_RAW, /* raw sockets */
IO_MODE_DPDK, /* DPDK */
IO_MODE_AF_XDP /* AF_XDP */
} __attribute__ ((__packed__)) io_mode_t;
typedef struct io_handle_ {
io_mode_t mode;
io_direction_t direction;
int id;
int fd;
int fanout_id;
int fanout_type;
struct tpacket_req req;
struct sockaddr_ll addr;
uint8_t *ring; /* ring buffer */
uint16_t cursor; /* ring buffer cursor */
uint16_t queued;
bool polled;
bbl_interface_s *interface;
bbl_txq_s *txq;
uint8_t *sp;
uint8_t *buf;
uint16_t buf_len;
bbl_ethernet_header_t *eth;
struct timespec timestamp; /* user space timestamps */
io_thread_s *thread;
struct {
uint64_t packets;
uint64_t bytes;
uint64_t stream_packets;
uint64_t stream_bytes;
} stats;
struct io_handle_ *next;
} io_handle_s;
typedef void (*io_thread_start_fn)(void *arg);
typedef struct io_thread_ {
pthread_t thread;
pthread_mutex_t mutex;
volatile bool active;
volatile bool stopped;
io_thread_start_fn start_fn;
struct timer_root_ timer_root;
struct timer_ *ctrl_timer;
struct timer_ *io_timer;
struct timer_ *main_rx_job; /* main loop RX job */
io_handle_s *io;
struct io_thread_ *next;
} io_thread_s;
#endif
+130
View File
@@ -0,0 +1,130 @@
/*
* BNG Blaster (BBL) - IO Interface Functions
*
* Christian Giese, July 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "io.h"
static bool
set_kernel_info(bbl_interface_s *interface)
{
struct ifreq ifr = {0};
int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface->name);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) {
LOG(ERROR, "Getting MAC address error %s (%d) for interface %s\n",
strerror(errno), errno, interface->name);
close(fd);
return false;
}
memcpy(&interface->mac, ifr.ifr_hwaddr.sa_data, IFHWADDRLEN);
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface->name);
if (ioctl(fd, SIOCGIFINDEX, &ifr) == -1) {
LOG(ERROR, "Get interface index error %s (%d) for interface %s\n",
strerror(errno), errno, interface->name);
close(fd);
return false;
}
interface->ifindex = ifr.ifr_ifindex;
close(fd);
return true;
}
/* Set interface in promisc mode. */
static bool
set_promisc(bbl_interface_s *interface) {
/* Taken and adapted from:
* https://stackoverflow.com/questions/41678219/how-to-properly-put-network-interface-into-promiscuous-mode-on-linux
* This prevents the ioctl get flags / set flags race condition. */
struct packet_mreq mreq = {0};
int sfd;
/* This socket is only opened, but not closed. Closing the socket would reset
* its flags - effectively removing the just added promisc mode.
* We want to keep the interface in promisc mode until the end of the program. */
if ((sfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
LOG_NOARG(ERROR, "Unable to open control socket for promiscuous mode activation\n");
return -1;
}
mreq.mr_type = PACKET_MR_PROMISC;
mreq.mr_ifindex = interface->ifindex;
if(setsockopt(sfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0) {
LOG(ERROR, "Failed to put interface %s in promiscuous mode\n", interface->name);
return false;
}
return true;
}
static bool
io_interface_init_rx(bbl_interface_s *interface)
{
bbl_link_config_s *config = interface->config;
io_handle_s *io;
uint8_t count = 1;
if(config->rx_threads) {
count = config->rx_threads;
}
while(count) {
io = calloc(1, sizeof(io_handle_s));
if(!io) return false;
io->id = count--;
io->mode = config->io_mode;
if(io->mode == IO_MODE_PACKET_MMAP_RAW) {
io->mode = IO_MODE_PACKET_MMAP;
}
io->direction = IO_INGRESS;
io->next = interface->io.rx;
interface->io.rx = io->next;
io->sp = malloc(SCRATCHPAD_LEN);
io->interface = interface;
switch(io->mode) {
case IO_MODE_PACKET_MMAP:
if(!io_packet_mmap_init(io)) {
return false;
}
break;
case IO_MODE_RAW:
if(!io_raw_init(io)) {
return false;
}
break;
default:
return false;
}
}
return true;
}
/**
* io_interface_init
*
* @param interface interface.
*/
bool
io_interface_init(bbl_interface_s *interface)
{
bbl_link_config_s *config = interface->config;
if(config->io_mode != IO_MODE_DPDK) {
set_kernel_info(interface);
set_promisc(interface);
}
if(*(uint64_t*)config->mac & 0xffffffffffff00) {
memcpy(interface->mac, config->mac, ETH_ADDR_LEN);
}
interface->io.sp = malloc(SCRATCHPAD_LEN);
/* RX */
}
+15
View File
@@ -0,0 +1,15 @@
/*
* BNG Blaster (BBL) - IO Interface Functions
*
* Christian Giese, August 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __BBL_IO_INTERFACE_H__
#define __BBL_IO_INTERFACE_H__
bool
io_interface_init(bbl_interface_s *interface);
#endif
+194
View File
@@ -0,0 +1,194 @@
/*
* BNG Blaster (BBL) - IO PACKET_MMAP
*
* Christian Giese, July 2022
*
* PACKET_MMAP provides a size configurable circular buffer mapped in user space
* that can be used to either send or receive packets. This way reading packets
* just needs to wait for them, most of the time there is no need to issue a single
* system call. Concerning transmission, multiple packets can be sent through one
* system call to get the highest bandwidth. By using a shared buffer between the
* kernel and the user also has the benefit of minimizing packet copies.
*
* https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "io.h"
static void
poll_kernel(io_handle_s *io, short events)
{
struct pollfd fds[1] = {0};
fds[0].fd = io->fd;
fds[0].events = events;
fds[0].revents = 0;
if (poll(fds, 1, 0) == -1) {
LOG(IO, "Failed to poll interface %s",
io->interface->name);
}
}
void
io_packet_mmap_rx_job(timer_s *timer)
{
io_handle_s *io = timer->data;
bbl_interface_s *interface = io->interface;
uint8_t *frame_ptr;
struct tpacket2_hdr *tphdr;
bbl_ethernet_header_t *eth;
uint16_t vlan;
protocol_error_t decode_result;
bool pcap = false;
assert(io->mode == IO_MODE_PACKET_MMAP);
assert(io->direction == IO_INGRESS);
assert(io->thread == NULL);
frame_ptr = io->ring + (io->cursor * io->req.tp_frame_size);
tphdr = (struct tpacket2_hdr*)frame_ptr;
if(!(tphdr->tp_status & TP_STATUS_USER)) {
/* If no buffer is available poll kernel */
poll_kernel(io, POLLIN);
interface->stats.poll_rx++;
return;
}
/* Get RX timestamp */
clock_gettime(CLOCK_MONOTONIC, &io->timestamp);
while(tphdr->tp_status & TP_STATUS_USER) {
io->buf = (uint8_t*)tphdr + tphdr->tp_mac;
io->buf_len = tphdr->tp_len;
interface->stats.packets_rx++;
interface->stats.bytes_rx += io->buf_len;
interface->io.ctrl = true;
decode_result = decode_ethernet(io->buf, io->buf_len, io->sp, SCRATCHPAD_LEN, &eth);
if(decode_result == PROTOCOL_SUCCESS) {
vlan = tphdr->tp_vlan_tci & ETH_VLAN_ID_MAX;
if(vlan && eth->vlan_outer != vlan) {
/* The outer VLAN is stripped from header */
eth->vlan_inner = eth->vlan_outer;
eth->vlan_inner_priority = eth->vlan_outer_priority;
eth->vlan_outer = vlan;
eth->vlan_outer_priority = tphdr->tp_vlan_tci >> 13;
if(tphdr->tp_vlan_tpid == ETH_TYPE_QINQ) {
eth->qinq = true;
}
}
/* Copy RX timestamp */
//eth->timestamp.tv_sec = tphdr->tp_sec; /* ktime/hw timestamp */
//eth->timestamp.tv_nsec = tphdr->tp_nsec; /* ktime/hw timestamp */
eth->timestamp.tv_sec = io->timestamp.tv_sec;
eth->timestamp.tv_nsec = io->timestamp.tv_nsec;
bbl_rx_handler(interface, eth);
} else if (decode_result == UNKNOWN_PROTOCOL) {
interface->stats.unknown++;
} else {
interface->stats.decode_error++;
}
/* Dump the packet into pcap file. */
if (g_ctx->pcap.write_buf && (interface->io.ctrl || g_ctx->pcap.include_streams)) {
pcap = true;
pcapng_push_packet_header(&io->timestamp, io->buf, io->buf_len,
interface->pcap_index, PCAPNG_EPB_FLAGS_INBOUND);
}
/* Return ownership back to kernel. */
tphdr->tp_status = TP_STATUS_KERNEL;
/* Get next packet. */
io->cursor = (io->cursor + 1) % io->req.tp_frame_nr;
frame_ptr = io->ring + (io->cursor * io->req.tp_frame_size);
tphdr = (struct tpacket2_hdr*)frame_ptr;
}
if(pcap) {
pcapng_fflush();
}
}
void
bbl_io_packet_mmap_tx_job(timer_s *timer)
{
io_handle_s *io = timer->data;
bbl_interface_s *interface = io->interface;
protocol_error_t tx_result = IGNORED;
struct tpacket2_hdr* tphdr;
uint8_t *frame_ptr;
bool pcap = false;
assert(io->mode == IO_MODE_PACKET_MMAP);
assert(io->direction == IO_EGRESS);
assert(io->thread == NULL);
frame_ptr = io->ring + (io->cursor * io->req.tp_frame_size);
tphdr = (struct tpacket2_hdr *)frame_ptr;
if (tphdr->tp_status != TP_STATUS_AVAILABLE) {
if(io->polled) {
/* We already polled kernel. */
return;
}
/* If no buffer is available poll kernel. */
poll_kernel(io, POLLOUT);
io->polled = true;
interface->stats.poll_tx++;
} else {
io->polled = false;
/* Get TX timestamp */
clock_gettime(CLOCK_MONOTONIC, &io->timestamp);
while(tx_result != EMPTY) {
/* Check if this slot available for writing. */
if (tphdr->tp_status != TP_STATUS_AVAILABLE) {
interface->stats.no_buffer++;
break;
}
io->buf = frame_ptr + TPACKET2_HDRLEN - sizeof(struct sockaddr_ll);
tx_result = bbl_tx(interface, io->buf, &io->buf_len);
if (tx_result == PROTOCOL_SUCCESS) {
io->queued++;
tphdr->tp_len = io->buf_len;
tphdr->tp_status = TP_STATUS_SEND_REQUEST;
interface->stats.packets_tx++;
interface->stats.bytes_tx += io->buf_len;
/* Dump the packet into pcap file. */
if(g_ctx->pcap.write_buf && (interface->io.ctrl || g_ctx->pcap.include_streams)) {
pcap = true;
pcapng_push_packet_header(&io->timestamp, io->buf, io->buf_len,
interface->pcap_index, PCAPNG_EPB_FLAGS_OUTBOUND);
}
/* Request send from kernel. */
tphdr->tp_status = TP_STATUS_SEND_REQUEST;
/* Get next slot. */
io->cursor = (io->cursor + 1) % io->req.tp_frame_nr;
frame_ptr = io->ring + (io->cursor * io->req.tp_frame_size);
tphdr = (struct tpacket2_hdr *)frame_ptr;
}
}
if(pcap) {
pcapng_fflush();
}
}
if(io->queued) {
/* Notify kernel. */
if (sendto(io->fd, NULL, 0, 0, NULL, 0) == -1) {
LOG(IO, "Sendto failed with errno: %i\n", errno);
interface->stats.sendto_failed++;
} else {
io->queued = 0;
}
}
}
bool
io_packet_mmap_init(io_handle_s *io) {
bbl_interface_s *interface = io->interface;
bbl_link_config_s *config = interface->config;
}
+13
View File
@@ -0,0 +1,13 @@
/*
* BNG Blaster (BBL) - IO PACKET_MMAP
*
* Christian Giese, July 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __BBL_IO_PACKET_MMAP_H__
#define __BBL_IO_PACKET_MMAP_H__
#endif
+110
View File
@@ -0,0 +1,110 @@
/*
* BNG Blaster (BBL) - IO RAW
*
* Christian Giese, July 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "io.h"
/**
* This job is for RAW RX in main thread!
*/
void
io_raw_rx_job(timer_s *timer)
{
io_handle_s *io = timer->data;
bbl_interface_s *interface = io->interface;
struct sockaddr saddr;
int saddr_size = sizeof(saddr);
bbl_ethernet_header_t *eth;
protocol_error_t decode_result;
bool pcap = false;
assert(io->mode == IO_MODE_RAW);
assert(io->direction == IO_INGRESS);
assert(io->thread == NULL);
/* Get RX timestamp */
clock_gettime(CLOCK_MONOTONIC, &io->timestamp);
while(true) {
io->buf_len = recvfrom(io->fd, io->buf, IO_BUFFER_LEN, 0, &saddr , (socklen_t*)&saddr_size);
if(io->buf_len < 14 || io->buf_len > IO_BUFFER_LEN) {
break;
}
io->stats.packets++;
io->stats.bytes += io->buf_len;
interface->io.ctrl = true;
decode_result = io_decode_ethernet(io->buf, io->buf_len, io->sp, SCRATCHPAD_LEN, &eth);
if(decode_result == PROTOCOL_SUCCESS) {
/* Copy RX timestamp */
eth->timestamp.tv_sec = io->timestamp.tv_sec;
eth->timestamp.tv_nsec = io->timestamp.tv_nsec;
bbl_rx_handler(interface, eth);
} else if (decode_result == UNKNOWN_PROTOCOL) {
interface->stats.unknown++;
} else {
interface->stats.decode_error++;
}
/* Dump the packet into pcap file. */
if (g_ctx->pcap.write_buf && (interface->io.ctrl || g_ctx->pcap.include_streams)) {
pcap = true;
pcapng_push_packet_header(&io->timestamp, io->buf, io->buf_len,
interface->pcap_index, PCAPNG_EPB_FLAGS_INBOUND);
}
}
if(pcap) {
pcapng_fflush();
}
}
/**
* This job is for RAW TX in main thread!
*/
void
io_raw_tx_job(timer_s *timer)
{
io_handle_s *io = timer->data;
bbl_interface_s *interface = io->interface;
protocol_error_t tx_result = IGNORED;
bool pcap = false;
assert(io->mode == IO_MODE_RAW);
assert(io->direction == IO_EGRESS);
assert(io->thread == NULL);
/* Get TX timestamp */
clock_gettime(CLOCK_MONOTONIC, &io->timestamp);
while(tx_result != EMPTY) {
/* If sendto fails, the failed packet remains in TX buffer to be retried
* in the next interval. */
if(!io->buf_len) {
tx_result = bbl_tx(interface, io->buf, &io->buf_len);
}
if(tx_result == PROTOCOL_SUCCESS) {
if (sendto(io->fd, io->buf, io->buf_len, 0, (struct sockaddr*)&io->addr, sizeof(struct sockaddr_ll)) <0 ) {
LOG(IO, "Sendto failed with errno: %i\n", errno);
interface->stats.sendto_failed++;
return;
}
interface->stats.packets_tx++;
interface->stats.bytes_tx += io->buf_len;
/* Dump the packet into pcap file. */
if(g_ctx->pcap.write_buf && (interface->io.ctrl || g_ctx->pcap.include_streams)) {
pcap = true;
pcapng_push_packet_header(&io->timestamp, io->buf, io->buf_len,
interface->pcap_index, PCAPNG_EPB_FLAGS_OUTBOUND);
}
}
io->buf_len = 0;
}
if(pcap) {
pcapng_fflush();
}
}
+12
View File
@@ -0,0 +1,12 @@
/*
* BNG Blaster (BBL) - IO RAW
*
* Christian Giese, July 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __BBL_IO_RAW_H__
#define __BBL_IO_RAW_H__
#endif
+142
View File
@@ -0,0 +1,142 @@
/*
* BNG Blaster (BBL) - IO Linux Socket Functions
*
* Christian Giese, August 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "io.h"
/* Bypass TC_QDISC, such that the kernel is hammered 30% less with
* processing packets. Only for the TX FD. */
static bool
set_qdisc_bypass(io_handle_s *io) {
/* PACKET_QDISC_BYPASS (since Linux 3.14)
* By default, packets sent through packet sockets pass through
* the kernel's qdisc (traffic control) layer, which is fine for
* the vast majority of use cases. For traffic generator appli
* ances using packet sockets that intend to brute-force flood
* the networkfor example, to test devices under load in a simi
* lar fashion to pktgenthis layer can be bypassed by setting
* this integer option to 1. A side effect is that packet
* buffering in the qdisc layer is avoided, which will lead to
* increased drops when network device transmit queues are busy;
* therefore, use at your own risk. */
int qdisc_bypass = 1;
if (setsockopt(io->fd, SOL_PACKET, PACKET_QDISC_BYPASS, &qdisc_bypass, sizeof(qdisc_bypass)) == -1) {
LOG(ERROR, "Failed to set qdisc bypass for interface %s - %s (%d)\n",
io->interface->name, strerror(errno), errno);
return false;
}
return true;
}
/* Set fanout group. */
static bool
set_fanout(io_handle_s *io) {
if(io->direction == IO_INGRESS && io->fanout_id) {
int fanout_arg = (io->fanout_id | (io->fanout_type << 16));
if (setsockopt(io->fd, SOL_PACKET, PACKET_FANOUT, &fanout_arg, sizeof(fanout_arg)) == -1) {
LOG(ERROR, "Failed to set fanout group for interface %s - %s (%d)\n",
io->interface->name, strerror(errno), errno);
return false;
}
}
return true;
}
/* Set packet version (TPACKET_V1, TPACKET_V2 or TPACKET_V3). */
static bool
set_packet_version(io_handle_s *io, int version) {
if ((setsockopt(io->fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version))) == -1) {
LOG(ERROR, "Failed to set packet version error for interface %s - %s (%d)\n",
io->interface->name, strerror(errno), errno);
return false;
}
return true;
}
/* Setup ringbuffer. */
static void *
set_ring(io_handle_s *io, int slots) {
/* The following are conditions that are checked in packet_set_ring:
* - tp_block_size must be a multiple of PAGE_SIZE (1)
* - tp_frame_size must be greater than TPACKET_HDRLEN (obvious)
* - tp_frame_size must be a multiple of TPACKET_ALIGNMENT
* - tp_frame_nr must be exactly frames_per_block*tp_block_nr
* Note that tp_block_size should be chosen to be a power of two
* or there will be a waste of memory. */
size_t ring_size = 0;
int flag = 0;
if(io->direction == IO_EGRESS) {
flag = PACKET_RX_RING;
} else {
flag = PACKET_TX_RING;
}
io->req.tp_block_size = sysconf(_SC_PAGESIZE); /* 4096 */
io->req.tp_frame_size =io->req.tp_block_size/2; /* 2048 */
io->req.tp_block_nr = slots/2;
io->req.tp_frame_nr = slots;
ring_size = io->req.tp_block_nr * io->req.tp_block_size;
LOG(DEBUG, "Setup %d byte packet_mmap ringbuffer (%d slots) for interface %s\n",
ring_size, slots, io->interface->name);
if (setsockopt(io->fd, SOL_PACKET, flag, &io->req, sizeof(struct tpacket_req)) == -1) {
LOG(ERROR, "Allocating ringbuffer error for interface %s - %s (%d)\n",
io->interface->name, strerror(errno), errno);
return NULL;
}
return mmap(0, ring_size, PROT_READ|PROT_WRITE, MAP_SHARED, io->fd, 0);
}
bool
io_socket_open(io_handle_s *io) {
bbl_interface_s *interface = io->interface;
assert(io->mode == IO_MODE_PACKET_MMAP || io->mode == IO_MODE_RAW);
int protocol = 0;
if(io->direction == IO_INGRESS) {
protocol = htobe16(ETH_P_ALL);
}
/* Open RAW socket for all ethertypes.
* https://man7.org/linux/man-pages/man7/packet.7.html */
io->fd = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK, protocol);
if (io->fd == -1) {
LOG(ERROR, "Failed to open socket for interface %s - %s (%d)\n",
io->interface->name, strerror(errno), errno);
return false;
}
/* Limit socket to the given interface index. */
io->addr.sll_family = PF_PACKET;
io->addr.sll_ifindex = io->interface->ifindex;
io->addr.sll_protocol = protocol;
if (bind(io->fd, (struct sockaddr*)&io->addr, sizeof(io->addr)) == -1) {
LOG(ERROR, "Failed to bind socket for interface %s - %s (%d)\n",
io->interface->name, strerror(errno), errno);
return false;
}
if(io->direction == IO_EGRESS && interface->config->qdisc_bypass) {
if(!set_qdisc_bypass(io)) {
return false;
}
}
if(io->mode == IO_MODE_PACKET_MMAP) {
if(!set_packet_version(io, TPACKET_V2)) {
return false;
}
if(!set_ring(io, interface->config->io_slots)) {
return false;
}
if(!set_fanout(io)) {
return false;
}
}
return true;
}
+15
View File
@@ -0,0 +1,15 @@
/*
* BNG Blaster (BBL) - IO Linux Socket Functions
*
* Christian Giese, August 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __BBL_IO_SOCKET_H__
#define __BBL_IO_SOCKET_H__
bool
io_socket_open(io_handle_s *io);
#endif
+192
View File
@@ -0,0 +1,192 @@
/*
* BNG Blaster (BBL) - IO RX Threads
*
* Christian Giese, July 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "io.h"
void
io_thread_rx_job(timer_s *timer)
{
io_handle_s *io = timer->data;
bbl_txq_s *txq = io->txq;
bbl_txq_slot_t *slot;
bbl_interface_s *interface = io->interface;
bbl_ethernet_header_t *eth;
uint16_t vlan;
protocol_error_t decode_result;
bool pcap = false;
while(slot = bbl_txq_read_slot(txq)) {
decode_result = decode_ethernet(slot->packet, slot->packet_len, interface->io.sp, SCRATCHPAD_LEN, &eth);
if(decode_result == PROTOCOL_SUCCESS) {
vlan = slot->vlan_tci & ETH_VLAN_ID_MAX;
if(vlan && eth->vlan_outer != vlan) {
/* Restore outer VLAN */
eth->vlan_inner = eth->vlan_outer;
eth->vlan_inner_priority = eth->vlan_outer_priority;
eth->vlan_outer = vlan;
eth->vlan_outer_priority = slot->vlan_tci >> 13;
if(slot->vlan_tpid == ETH_TYPE_QINQ) {
eth->qinq = true;
}
}
/* Copy RX timestamp */
eth->timestamp.tv_sec = slot->timestamp.tv_sec;
eth->timestamp.tv_nsec = slot->timestamp.tv_nsec;
bbl_rx_handler(interface, eth);
} else if (decode_result == UNKNOWN_PROTOCOL) {
interface->stats.unknown++;
} else {
interface->stats.decode_error++;
}
/* Dump the packet into pcap file. */
if (g_ctx->pcap.write_buf && (interface->io.ctrl || g_ctx->pcap.include_streams)) {
pcap = true;
pcapng_push_packet_header(&io->timestamp, io->buf, io->buf_len,
interface->pcap_index, PCAPNG_EPB_FLAGS_INBOUND);
}
bbl_txq_read_next(txq);
}
if(pcap) {
pcapng_fflush();
}
}
void
io_thread_tx_job(timer_s *timer)
{
io_handle_s *io = timer->data;
bbl_txq_s *txq = io->txq;
bbl_txq_slot_t *slot;
bbl_interface_s *interface = io->interface;
protocol_error_t tx_result = IGNORED;
bool pcap = false;
/* Get TX timestamp */
struct timespec timestamp;
clock_gettime(CLOCK_MONOTONIC, &timestamp);
while(slot = bbl_txq_write_slot(txq)) {
tx_result = bbl_tx(interface, slot->packet, &slot->packet_len);
if(tx_result == PROTOCOL_SUCCESS) {
interface->stats.packets_tx++;
interface->stats.bytes_tx += slot->packet_len;
/* Dump the packet into pcap file. */
if(g_ctx->pcap.write_buf && (interface->io.ctrl || g_ctx->pcap.include_streams)) {
pcap = true;
pcapng_push_packet_header(&timestamp, slot->packet, slot->packet_len,
interface->pcap_index, PCAPNG_EPB_FLAGS_OUTBOUND);
}
bbl_txq_write_next(txq);
} else if(tx_result == EMPTY) {
break;
}
}
if(pcap) {
pcapng_fflush();
}
}
void *
io_thread_timer(void *thread_data)
{
io_thread_s *thread = thread_data;
pthread_mutex_lock(&thread->mutex);
timer_smear_all_buckets(&thread->timer_root);
pthread_mutex_unlock(&thread->mutex);
while(thread->active) {
timer_walk(&thread->timer_root);
}
thread->stopped = true;
return NULL;
}
bool
io_thread_init(io_handle_s *io)
{
bbl_interface_s *interface = io->interface;
bbl_link_config_s *config = interface->config;
io_thread_s *thread;
uint16_t slots = config->io_slots;
thread = calloc(1, sizeof(io_thread_s));
thread->next = g_ctx->io_threads;
g_ctx->io_threads = thread;
io->thread = thread;
io->fanout_id = interface->ifindex;
io->fanout_type = PACKET_FANOUT_HASH;
/* Init thread mutex */
if (pthread_mutex_init(&thread->mutex, NULL) != 0) {
LOG_NOARG(ERROR, "Failed to init mutex\n");
return false;
}
/* Init thread timer root */
timer_init_root(&thread->timer_root);
if(io->direction == IO_INGRESS) {
if(slots < (UINT16_MAX/2)) {
slots = slots*2;
}
/** Start job reading from RX thread TXQ. */
timer_add_periodic(&g_ctx->timer_root, &thread->main_rx_job, "RX",
0, config->rx_interval,
io, &io_thread_rx_job);
} else if(!interface->tx_job) {
/** Start job writing to first TX thread TXQ */
timer_add_periodic(&g_ctx->timer_root, &interface->rx_job, "TX",
0, config->rx_interval,
io, &io_thread_tx_job);
}
/* Init TXQ */
io->txq = calloc(1, sizeof(bbl_txq_s));
if(!(io->txq && bbl_txq_init(io->txq, slots))) {
return false;
}
return true;
}
void
io_thread_start_all()
{
io_thread_s *thread = g_ctx->io_threads;
while(thread) {
pthread_create(&thread->thread, NULL, thread->start_fn, (void *)thread);
thread = thread->next;
}
}
void
io_thread_stop_all()
{
io_thread_s *thread = g_ctx->io_threads;
while(thread) {
pthread_mutex_lock(&thread->mutex);
thread->active = false;
pthread_mutex_unlock(&thread->mutex);
thread = thread->next;
}
/* Wait for threads to be stopped */
thread = g_ctx->io_threads;
while(thread) {
pthread_join(thread->thread, NULL);
}
}
+21
View File
@@ -0,0 +1,21 @@
/*
* BNG Blaster (BBL) - IO RX Threads
*
* Christian Giese, July 2022
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __BBL_IO_THREAD_H__
#define __BBL_IO_THREAD_H__
bool
io_thread_init(io_handle_s *io);
void
io_thread_start_all();
void
io_thread_stop_all();
#endif
+13 -1
View File
@@ -36,10 +36,22 @@
#define ISO_STR_LEN 64
#define SUB_STR_LEN 256
#define CACHE_LINE_SIZE 64
/* Macro Definitions */
#define UNUSED(x) (void)x
#ifndef likely
#define likely(x) __builtin_expect((x),1)
#endif /* likely */
#ifndef unlikely
#define unlikely(x) __builtin_expect((x),0)
#endif /* unlikely */
#define POWEROF2(x) ((((x)-1) & (x)) == 0) /* true if x is a power of 2 */
#define BITS_TO_BYTES(_len) ((_len+7) >> 3)
#define UNUSED(x) (void)x
/* Key-Value structure. */
typedef struct keyval_ {