diff --git a/README.md b/README.md index aee3aecd..787308e5 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,8 @@ See the LICENSE file for more details. ## Copyright -Copyright (C) 2020-2021, RtBrick, Inc. \ No newline at end of file +Copyright (C) 2020-2021, RtBrick, Inc. + +## Contact + +bngblaster@rtbrick.com \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index 3f83c816..17f906a2 100644 --- a/docs/config.md +++ b/docs/config.md @@ -690,11 +690,12 @@ Attribute | Description | Default `destination-ipv4-address` | Overwrite the IPv4 destination address | `destination-ipv6-address` | Overwrite the IPv6 destination address | `threaded` | Run those streams in separate threads | false +`thread-group` | Assign this stream to thread group (1-255) | 0 (thread per stream) For L2TP downstream traffic the IPv4 TOS is applied to the outer IPv4 and inner IPv4 header. -The `pps` option has priority over `bps` where second is only a helper to calculate the `pps` -based on given `bps` and `length`. +The `pps` option supports also float numbers like 0.1, or 2.5 PPS and has priority over `bps` +where second is only a helper to calculate the `pps` based on given `bps` and `length`. ## Access-Line diff --git a/docs/ctrl.md b/docs/ctrl.md index 10859226..3560ade2 100644 --- a/docs/ctrl.md +++ b/docs/ctrl.md @@ -163,6 +163,8 @@ Attribute | Description `session-traffic-stop` (Alias: `session-traffic-disabled`) | Stop sending session traffic for all sessions `stream-traffic-start` (Alias: `stream-traffic-enabled`) | Start sending stream traffic for all sessions `stream-traffic-stop` (Alias: `stream-traffic-disabled`) | Stop sending stream traffic for all sessions +`stream-stats` | Display global stream traffic statistics +`stream-info` | Display stream traffic statistics identified by flow identifier (`flow-id `) `multicast-traffic-start` | Start sending multicast traffic from network interface `multicast-traffic-stop` | Stop sending multicast traffic from network interface `li-flows` | List all LI flows with detailed statistics @@ -171,6 +173,8 @@ Attribute | Description `cfm-cc-stop` | Stop EOAM CFM CC `cfm-cc-rdi-on` | Set EOAM CFM CC RDI `cfm-cc-rdi-off` | Unset EOAM CFM CC RDI +`traffic-start` | Start all traffic (session and streams) +`traffic-stop` | Stop all traffic (session and streams) ### Session Commands @@ -255,4 +259,4 @@ This command can be executed as shown below using the CLI tool. The L2TP session terminate command allows to test result (RFC2661) and disconnect (RFC3145) codes. -`$ sudo bngblaster-cli run.sock l2tp-session-terminate session-id 1 result-code 2 error-message "LCP request" disconnect-code 3 disconnect-message "LCP terminate request"` +`$ sudo bngblaster-cli run.sock l2tp-session-terminate session-id 1 result-code 2 error-message "LCP request" disconnect-code 3 disconnect-message "LCP terminate request"` \ No newline at end of file diff --git a/docs/intro.md b/docs/intro.md index 40f0e6e1..36e51532 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -4,21 +4,25 @@ Building a BNG from scratch requires a lot of testing but commercial BNG test so is often very expensive, resource intensive and provide a lot of administrative overhead to maintain such. -Therefore we decided to build our own BNG test solution which is completely build from scratch -targeted for max scaling with small resource footprint, simple to use and easy to integrate in -our test automation infrastructure. +Therefore we decided to build an open source network test software initially focused on BNG +and IPTV testing but constantly enhanced and planned for more common network equipment test +cases. The BNG Blaster was completely build from scratch, targeted for max scaling with small +resource footprint, simple to use and easy to integrate in any test automation infrastructure. The BNG Blaster is able to simulate more than hundred thousand PPPoE and IPoE subscribers including -IPTV, L2TPv2, QoS, forwarding verification and convergence testing capabilities. +IPTV, L2TPv2 (LNS emulation), L2BSA, QoS, forwarding verification and convergence testing capabilities. -* *High Scaling:* > 100K sessions -* *Low CPU and Memory Footprint:* < 100MB memory for 16K sessions +* *High Scaling:* > 100K sessions, > 1M PPS, and > 1M traffic flows +* *Low CPU and Memory Footprint:* ~300MB memory for 16K sessions * *Portable:* runs on every modern linux, virtual machines and containers * *User Space:* all protocols implemented in user-space from scratch and optimized for performance * *IPTV:* IGMP version 1, 2 and 3 with automated channel zapping test * *QoS:* define and analyze traffic streams * *Automation:* the BNG Blaster Controller provides an automation friendly REST API and robot keywords +Please send a mail to bngblaster@rtbrick.com if you are interested +to get access to the BNG Blaster Controller! + ``` $ bngblaster --help @@ -86,19 +90,21 @@ are currently considered as experimental. In the default mode (`packet_mmap_raw` buffer and send directly trough raw sockets. Sending and transmitting traffic is as easy as just by copying a packet into a buffer and setting a flag. This is super -efficient and hence we have measured the I/O performance of roughly 1M pps per single CPU thread, which is more than enough for -our purposes here. +efficient and hence we have measured the I/O performance of up to 1M pps per single CPU thread, which is more than enough +for our purposes here. BNG Blasters primary design goal is to simulate thousands of subscriber CPE's with a small hardware resource footprint. Simple to use and easy to integrate in our robot test automation infrastructure. This allows to simulate more than hundred thousand PPPoE subscribers including IPTV, traffic verification and convergence testing from a single medium scale virtual machine or to run the blaster directly from a laptop. -The BNG Blaster provides two types of interfaces. The first interface is called the access interface which emulates the PPPoE +The BNG Blaster provides three types of interfaces. The first interface is called the access interface which emulates the PPPoE sessions. The second interface-type is called network interface. This is used for emulating the core-facing side of the -internet. +internet. The last type is called a10nsp interface which emulates an layer two provider interface. The term A10 +refers to the end-to-end ADSL network reference model from TR-025. ![BNG Blaster Interfaces](images/bbl_interfaces.png) This allows to verify IP reachability by sending bidirectional traffic between all PPPoE sessions on access-interface and the -network interface. The network interface is also used to inject downstream multicast test traffic for IPTV tests. \ No newline at end of file +network interface. The network interface is also used to inject downstream multicast test traffic for IPTV tests. It is also +possible to send RAW traffic streams between multiple network interfaces. \ No newline at end of file diff --git a/docs/reports.md b/docs/reports.md index f5909cf8..dbc8d9f5 100644 --- a/docs/reports.md +++ b/docs/reports.md @@ -103,12 +103,12 @@ Session Traffic: Network IPv6: 500 Network IPv6PD: 500 First Sequence Number Received: - Access IPv4 MIN: 1 MAX: 2 - Access IPv6 MIN: 2 MAX: 2 - Access IPv6PD MIN: 2 MAX: 2 - Network IPv4 MIN: 1 MAX: 2 - Network IPv6 MIN: 2 MAX: 2 - Network IPv6PD MIN: 2 MAX: 2 + Access IPv4 MIN: 1 ( 1.000s) MAX: 2 ( 2.000s) + Access IPv6 MIN: 2 ( 2.000s) MAX: 2 ( 2.000s) + Access IPv6PD MIN: 2 ( 2.000s) MAX: 2 ( 2.000s) + Network IPv4 MIN: 1 ( 1.000s) MAX: 2 ( 2.000s) + Network IPv6 MIN: 2 ( 2.000s) MAX: 2 ( 2.000s) + Network IPv6PD MIN: 2 ( 2.000s) MAX: 2 ( 2.000s) ``` ## JSON Reports @@ -245,6 +245,10 @@ as shown in the example below. } ``` +The optional argument `-j sessions` allows to include per session statistics +in the report file. Similar with `-j streams` which allows to include per stream +statistics. Both options could be also combined. + ## Interface Statistics ## Session Traffic Statistics diff --git a/docs/streams.md b/docs/streams.md index 1c649ff9..cfd543e7 100644 --- a/docs/streams.md +++ b/docs/streams.md @@ -287,16 +287,20 @@ multicast MAC address. ## Threaded Streams -**WARNING**: The threading support is experimental and should be used with caution! +With threading enabled, those streams will be started per default in a dedicated thread per flow. +This means one thread per session and stream direction. A threaded, bidirectional stream assigned +to 10 sessions will therefore run in 20 threads. It is also possible to assign multiple +steams to a single thread using thread groups. -With threading enabled, those streams will be started in a dedicated thread per flow. This -means one thread per session and stream direction. A threaded , bidirectional stream assigned -to 10 sessions will therefore run in 20 threads. +In most environments we see between 200.000 and 300.000 PPS single threaded is working. Depending +on actual setup this can be also much more. With threaded streams we are also able to scale up to +10 million PPS depending on actual configuration and setup. This allows to start 1 million flows +with 1 PPS per flow over 4 threads in example to verify all prefixes of a BGP full table. -In most environments we see that 200.000 PPS single threaded is working. Depending on actual setup this -can be also more. With threaded streams we are also able to scale up to three million PPS or more. +The BNG Blaster is currently tested for up to 1 million flows which is not a hard limitation but +everything above should be considered as experimental. -## Start/Stop Session Stream Information +## Start/Stop Session Stream Traffic Session stream traffic can be started/stopped dynamically using the commands `stream-traffic-enabled` and `stream-traffic-disabled`. @@ -308,3 +312,20 @@ session identifier. `$ sudo bngblaster-cli run.sock stream-traffic-disabled` +Alternatively all session and stream traffic (including RAW streams) +can be started or stopped globally using the `traffic-start` and +`traffic-stop` commands. + +## Stream Configuration File + +The command line argument `-T ` allows to include +streams defined from a separate file. The format is equal to +streams defined in the actual configuration file. Such stream +configuration files could be generated by scripts and +easily merged with the base configuration. + +```json +{ + "streams": [] +} +``` \ No newline at end of file diff --git a/src/bbl.c b/src/bbl.c index a740af12..ffbcebff 100644 --- a/src/bbl.c +++ b/src/bbl.c @@ -54,6 +54,24 @@ teardown_handler(int sig) g_teardown_request_count++; } +void +enable_disable_traffic(bbl_ctx_s *ctx, bool status) +{ + bbl_session_s *session; + uint32_t i; + + g_traffic = status; + + /* Iterate over all sessions */ + for(i = 0; i < ctx->sessions; i++) { + session = ctx->session_list[i]; + if(session) { + session->session_traffic = status; + session->stream_traffic = status; + } + } +} + static bool bbl_add_multicast_packets(bbl_ctx_s *ctx) { @@ -93,10 +111,7 @@ bbl_add_multicast_packets(bbl_ctx_s *ctx) } group = htobe32(group); /* Generate multicast destination MAC */ - *(uint32_t*)(&mac[2]) = group; - mac[0] = 0x01; - mac[2] = 0x5e; - mac[3] &= 0x7f; + ipv4_multicast_mac(group, mac); eth.src = interface->mac; eth.dst = mac; eth.vlan_outer = interface->vlan; @@ -257,32 +272,10 @@ bbl_ctrl_job (timer_s *timer) if(ctx->sessions_outstanding) ctx->sessions_outstanding--; - if(ctx->sessions) { - if(ctx->sessions_terminated >= ctx->sessions) { - /* Now close all L2TP tunnels ... */ - if(ctx->l2tp_tunnels == 0) { - /* Stop event loop to close application! */ - CIRCLEQ_INIT(&ctx->timer_root.timer_bucket_qhead); - } else { - bbl_l2tp_stop_all_tunnel(ctx); - } - return; - } - } else { - /* Network interface only... */ - if(g_teardown) { - if(ctx->l2tp_tunnels == 0) { - /* Stop event loop to close application! */ - CIRCLEQ_INIT(&ctx->timer_root.timer_bucket_qhead); - } else { - bbl_l2tp_stop_all_tunnel(ctx); - } - return; - } - return; - } - if(g_teardown) { + if(ctx->l2tp_tunnels && ctx->sessions_terminated >= ctx->sessions) { + bbl_l2tp_stop_all_tunnel(ctx); + } /* Teardown phase ... */ if(g_teardown_request) { /* Put all sessions on the teardown list. */ @@ -507,7 +500,7 @@ main (int argc, char *argv[]) } if(config_streams_file) { if(!bbl_config_streams_load_json(config_streams_file, ctx)) { - fprintf(stderr, "Error: Failed to load stream configuration file %s\n", config_file); + fprintf(stderr, "Error: Failed to load stream configuration file %s\n", config_streams_file); exit(1); } } @@ -558,7 +551,7 @@ main (int argc, char *argv[]) if (interactive) endwin(); fprintf(stderr, "Error: Failed to init sessions\n"); exit(1); - }; + } } /* Setup control job. */ @@ -576,16 +569,39 @@ main (int argc, char *argv[]) * such that we do not accidentally smear ourselves. */ timer_add_periodic(&ctx->timer_root, &ctx->smear_timer, "Timer Smearing", 45, 12345678, ctx, &bbl_smear_job); + /* Smear all buckets. */ + timer_smear_all_buckets(&ctx->timer_root); + + /* Start threads. */ + bbl_stream_start_threads(ctx); + /* Start event loop. */ log_open(); clock_gettime(CLOCK_MONOTONIC, &ctx->timestamp_start); signal(SIGINT, teardown_handler); - timer_walk(&ctx->timer_root); - while(ctx->sessions_terminated < ctx->sessions && g_teardown_request_count < 10) { + while(g_teardown_request_count < 10) { + if(!ctx->l2tp_tunnels) { + if(ctx->sessions) { + /* With sessions, wait for all sessions + * to be terminated. */ + if(ctx->sessions_terminated >= ctx->sessions && ctx->l2tp_tunnels == 0) { + break; + } + } else { + /* Without sessions, we can stop immediately + * as soon as teardown was requested. */ + if(g_teardown) { + break; + } + } + } timer_walk(&ctx->timer_root); } clock_gettime(CLOCK_MONOTONIC, &ctx->timestamp_stop); + /* Stop threads. */ + bbl_stream_stop_threads(ctx); + /* Stop curses. Do this before the final reports. */ if(g_interactive) { endwin(); diff --git a/src/bbl.h b/src/bbl.h index 3a7b7319..d904d57f 100644 --- a/src/bbl.h +++ b/src/bbl.h @@ -35,6 +35,7 @@ #include #include #include +#include #define NCURSES_NOMACROS 1 #include @@ -66,4 +67,7 @@ WINDOW *log_win; WINDOW *stats_win; +void +enable_disable_traffic(bbl_ctx_s *ctx, bool status); + #endif \ No newline at end of file diff --git a/src/bbl_config.c b/src/bbl_config.c index 824c7536..35780901 100644 --- a/src/bbl_config.c +++ b/src/bbl_config.c @@ -652,6 +652,7 @@ json_parse_stream (bbl_ctx_s *ctx, json_t *stream, bbl_stream_config *stream_con json_t *value = NULL; const char *s = NULL; double bps; + double number; if (json_unpack(stream, "{s:s}", "type", &s) == 0) { if (strcmp(s, "ipv4") == 0) { @@ -729,7 +730,7 @@ json_parse_stream (bbl_ctx_s *ctx, json_t *stream, bbl_stream_config *stream_con value = json_object_get(stream, "pps"); if (value) { stream_config->pps = json_number_value(value); - if (stream_config->pps == 0) { + if (stream_config->pps <= 0) { fprintf(stderr, "JSON config error: Invalid value for stream->pps\n"); return false; } @@ -777,10 +778,21 @@ json_parse_stream (bbl_ctx_s *ctx, json_t *stream, bbl_stream_config *stream_con } } + /* Threading */ value = json_object_get(stream, "threaded"); if (json_is_boolean(value)) { stream_config->threaded = json_boolean_value(value); } + value = json_object_get(stream, "thread-group"); + if (value) { + number = json_number_value(value); + if(number > 0 && number < 256) { + stream_config->thread_group = number; + } else { + fprintf(stderr, "JSON config error: Invalid thread-group (valid range is 1-255) for stream %s\n", stream_config->name); + return false; + } + } /* Validate configuration */ if (stream_config->stream_group_id == 0) { @@ -1605,8 +1617,8 @@ bbl_config_init_defaults (bbl_ctx_s *ctx) { ctx->config.qdisc_bypass = true; ctx->config.sessions = 1; ctx->config.sessions_max_outstanding = 800; - ctx->config.sessions_start_rate = 400, - ctx->config.sessions_stop_rate = 400, + ctx->config.sessions_start_rate = 400; + ctx->config.sessions_stop_rate = 400; ctx->config.pppoe_discovery_timeout = 5; ctx->config.pppoe_discovery_retry = 10; ctx->config.ppp_mru = 1492; diff --git a/src/bbl_ctrl.c b/src/bbl_ctrl.c index 99959b45..cf66d126 100644 --- a/src/bbl_ctrl.c +++ b/src/bbl_ctrl.c @@ -33,8 +33,6 @@ extern volatile bool g_teardown; extern volatile bool g_teardown_request; -extern bool g_traffic; - typedef ssize_t callback_function(int fd, bbl_ctx_s *ctx, uint32_t session_id, json_t* arguments); static char * @@ -1134,6 +1132,103 @@ bbl_ctrl_cfm_cc_rdi_off(int fd, bbl_ctx_s *ctx, uint32_t session_id, json_t* arg return bbl_ctrl_cfm_cc_rdi(fd, ctx, session_id, false); } +ssize_t +bbl_ctrl_stream_stats(int fd, bbl_ctx_s *ctx, uint32_t session_id __attribute__((unused)), json_t* arguments __attribute__((unused))) { + ssize_t result = 0; + json_t *root = json_pack("{ss si s{si si}}", + "status", "ok", + "code", 200, + "stream-stats", + "total-flows", ctx->stats.stream_traffic_flows, + "verified-flows", ctx->stats.stream_traffic_flows_verified); + if(root) { + result = json_dumpfd(root, fd, 0); + json_decref(root); + } + return result; +} + +ssize_t +bbl_ctrl_stream_info(int fd, bbl_ctx_s *ctx, uint32_t session_id __attribute__((unused)), json_t* arguments) { + ssize_t result = 0; + + json_t *root; + json_t *json_stream = NULL; + + bbl_stream *stream; + void **search = NULL; + + int number = 0; + uint64_t flow_id; + + /* Unpack further arguments */ + if (json_unpack(arguments, "{s:i}", "flow-id", &number) != 0) { + return bbl_ctrl_status(fd, "error", 400, "missing flow-id"); + } + + flow_id = number; + search = dict_search(ctx->stream_flow_dict, &flow_id); + if(search) { + stream = *search; + if(stream->thread.thread) { + pthread_mutex_lock(&stream->thread.mutex); + } + json_stream = bbl_stream_json(stream); + root = json_pack("{ss si so*}", + "status", "ok", + "code", 200, + "stream-info", json_stream); + if(root) { + result = json_dumpfd(root, fd, 0); + json_decref(root); + } else { + result = bbl_ctrl_status(fd, "error", 500, "internal error"); + json_decref(json_stream); + } + if(stream->thread.thread) { + pthread_mutex_unlock(&stream->thread.mutex); + } + return result; + } else { + return bbl_ctrl_status(fd, "warning", 404, "stream not found"); + } +} + +ssize_t +bbl_ctrl_traffic(int fd, bbl_ctx_s *ctx, uint32_t session_id, bool status) { + bbl_session_s *session; + uint32_t i; + if(session_id) { + session = bbl_session_get(ctx, session_id); + if(session) { + session->stream_traffic = status; + return bbl_ctrl_status(fd, "ok", 200, NULL); + } else { + return bbl_ctrl_status(fd, "warning", 404, "session not found"); + } + } else { + /* Iterate over all sessions */ + for(i = 0; i < ctx->sessions; i++) { + session = ctx->session_list[i]; + if(session) { + session->stream_traffic = status; + } + } + return bbl_ctrl_status(fd, "ok", 200, NULL); + } +} + +ssize_t +bbl_ctrl_traffic_start(int fd, bbl_ctx_s *ctx, uint32_t session_id __attribute__((unused)), json_t* arguments __attribute__((unused))) { + enable_disable_traffic(ctx, true); + return bbl_ctrl_status(fd, "ok", 200, NULL); +} + +ssize_t +bbl_ctrl_traffic_stop(int fd, bbl_ctx_s *ctx, uint32_t session_id __attribute__((unused)), json_t* arguments __attribute__((unused))) { + enable_disable_traffic(ctx, false); + return bbl_ctrl_status(fd, "ok", 200, NULL); +} struct action { char *name; @@ -1170,11 +1265,15 @@ struct action actions[] = { {"stream-traffic-start", bbl_ctrl_stream_traffic_start}, {"stream-traffic-disabled", bbl_ctrl_stream_traffic_stop}, {"stream-traffic-stop", bbl_ctrl_stream_traffic_stop}, + {"stream-info", bbl_ctrl_stream_info}, + {"stream-stats", bbl_ctrl_stream_stats}, {"sessions-pending", bbl_ctrl_sessions_pending}, {"cfm-cc-start", bbl_ctrl_cfm_cc_start}, {"cfm-cc-stop", bbl_ctrl_cfm_cc_stop}, {"cfm-cc-rdi-on", bbl_ctrl_cfm_cc_rdi_on}, {"cfm-cc-rdi-off", bbl_ctrl_cfm_cc_rdi_off}, + {"traffic-start", bbl_ctrl_traffic_start}, + {"traffic-stop", bbl_ctrl_traffic_stop}, {NULL, NULL}, }; @@ -1206,7 +1305,7 @@ bbl_ctrl_socket_job (timer_s *timer) { } } else { /* New connection */ - bzero(buf, sizeof(buf)); + memset(buf, 0x0, sizeof(buf)); len = read(fd, buf, INPUT_BUFFER); if(len) { root = json_loads((const char*)buf, 0, &error); diff --git a/src/bbl_ctrl.h b/src/bbl_ctrl.h index 2b445f46..8ff9f9f3 100644 --- a/src/bbl_ctrl.h +++ b/src/bbl_ctrl.h @@ -16,4 +16,4 @@ bbl_ctrl_socket_open (bbl_ctx_s *ctx); bool bbl_ctrl_socket_close (bbl_ctx_s *ctx); -#endif +#endif \ No newline at end of file diff --git a/src/bbl_ctx.c b/src/bbl_ctx.c index d8e504e5..b2dedb3c 100644 --- a/src/bbl_ctx.c +++ b/src/bbl_ctx.c @@ -77,6 +77,7 @@ bbl_ctx_add (void) CIRCLEQ_INIT(&ctx->interface_qhead); ctx->flow_id = 1; + ctx->multicast_traffic = true; /* Initialize hash table dictionaries. */ ctx->vlan_session_dict = hashtable_dict_new((dict_compare_func)bbl_compare_key64, bbl_key64_hash, BBL_SESSION_HASHTABLE_SIZE); @@ -126,4 +127,4 @@ bbl_ctx_del (bbl_ctx_s *ctx) { timer_flush_root(&ctx->timer_root); free(ctx); return; -} +} \ No newline at end of file diff --git a/src/bbl_ctx.h b/src/bbl_ctx.h index 4a264649..700facdd 100644 --- a/src/bbl_ctx.h +++ b/src/bbl_ctx.h @@ -78,6 +78,8 @@ typedef struct bbl_ctx_ int ctrl_socket; char *ctrl_socket_path; + void *stream_thread; /* single linked list of threads */ + /* Interfaces */ struct { uint8_t count; @@ -290,4 +292,4 @@ bbl_ctx_add(void); void bbl_ctx_del(bbl_ctx_s *ctx); -#endif +#endif \ No newline at end of file diff --git a/src/bbl_def.h b/src/bbl_def.h index bf1cabdb..6ad4d99c 100644 --- a/src/bbl_def.h +++ b/src/bbl_def.h @@ -1,8 +1,8 @@ #ifndef __BBL_DEF_H__ #define __BBL_DEF_H__ -#define IO_BUFFER_LEN 2048 -#define SCRATCHPAD_LEN 2048 +#define IO_BUFFER_LEN 9216 +#define SCRATCHPAD_LEN 4096 #define CHALLENGE_LEN 16 #define FILE_PATH_LEN 128 @@ -135,5 +135,8 @@ typedef struct bbl_ctx_ bbl_ctx_s; typedef struct bbl_interface_ bbl_interface_s; typedef struct bbl_session_ bbl_session_s; typedef struct bbl_a10nsp_session_ bbl_a10nsp_session_t; +typedef struct bbl_stream_thread_ bbl_stream_thread; +typedef struct bbl_stream_config_ bbl_stream_config; +typedef struct bbl_stream_ bbl_stream; #endif \ No newline at end of file diff --git a/src/bbl_interactive.c b/src/bbl_interactive.c index cac88fce..60ec7dd8 100644 --- a/src/bbl_interactive.c +++ b/src/bbl_interactive.c @@ -16,6 +16,17 @@ #define PROGRESS_BAR_SIZE 60 #define STATS_WIN_SIZE 90 +#define STRING_SP_SIZE 64 + +#define VISIBLE(_expression) \ + visible = false; \ + if(_expression) { \ + if(pos > stats_win_postion) { \ + visible = true; \ + } \ + pos++; \ + } \ + if(visible) typedef enum { UI_VIEW_DEFAULT = 0, @@ -34,8 +45,6 @@ extern volatile bool g_teardown; extern volatile bool g_teardown_request; extern volatile bool g_banner; -extern bool g_traffic; - /* This global variable is used to switch between access interfaces. */ uint8_t g_access_if_selected = 0; uint8_t g_network_if_selected = 0; @@ -85,6 +94,7 @@ bbl_read_key_job (timer_s *timer) ch = getch(); switch (ch) { case KEY_F(1): + stats_win_postion = 0; g_view_selected++; if(g_view_selected >= UI_VIEW_MAX) { g_view_selected = UI_VIEW_DEFAULT; @@ -151,11 +161,11 @@ bbl_read_key_job (timer_s *timer) bbl_init_stats_win(ctx); break; case KEY_F(7): - g_traffic = true; + enable_disable_traffic(ctx, true); LOG(INFO, "Start traffic\n"); break; case KEY_F(8): - g_traffic = false; + enable_disable_traffic(ctx, false); LOG(INFO, "Stop traffic\n"); break; case KEY_F(9): @@ -163,6 +173,18 @@ bbl_read_key_job (timer_s *timer) g_teardown_request = true; LOG(INFO, "Teardown request\n"); break; + case KEY_DOWN: + if(g_view_selected == UI_VIEW_DEFAULT) { + if(stats_win_postion < 6) { + stats_win_postion++; + } + bbl_init_stats_win(ctx); + } + break; + case KEY_UP: + if(stats_win_postion) stats_win_postion--; + bbl_init_stats_win(ctx); + break; default: break; } @@ -171,8 +193,8 @@ bbl_read_key_job (timer_s *timer) /* * Format a progress bar. */ -char * -bbl_format_progress (uint16_t complete, uint16_t current) +static char * +bbl_format_progress (uint32_t complete, uint32_t current) { static char buf[PROGRESS_BAR_SIZE+1]; float percentage; @@ -208,9 +230,14 @@ bbl_stats_job (timer_s *timer) struct bbl_interface_ *network_if; struct bbl_interface_ *a10nsp_if; + char strsp[STRING_SP_SIZE]; + bbl_session_s *session; int i; + int pos = 1; /* position */ + bool visible = false; + access_if = ctx->interfaces.access_if[g_access_if_selected]; network_if = ctx->interfaces.network_if[g_network_if_selected]; a10nsp_if = ctx->interfaces.a10nsp_if[g_a10nsp_if_selected]; @@ -222,8 +249,7 @@ bbl_stats_job (timer_s *timer) } if(g_view_selected == UI_VIEW_DEFAULT) { - - if(ctx->sessions) { + VISIBLE((ctx->sessions)) { wprintw(stats_win, "\nSessions %10lu (%lu PPPoE / %lu IPoE)\n", ctx->sessions, ctx->sessions_pppoe, ctx->sessions_ipoe); /* Progress bar established sessions */ @@ -253,17 +279,11 @@ bbl_stats_job (timer_s *timer) wattroff(stats_win, COLOR_PAIR(COLOR_RED)); wprintw(stats_win, "]\n"); - /* Session stats */ - wprintw(stats_win, " Setup Time %10lu ms\n", ctx->stats.setup_time); - wprintw(stats_win, " Setup Rate %10.02lf CPS (MIN: %0.02lf AVG: %0.02lf MAX: %0.02lf)\n", - ctx->stats.cps, ctx->stats.cps_min, ctx->stats.cps_avg, ctx->stats.cps_max); - wprintw(stats_win, " Flapped %10lu\n", ctx->sessions_flapped); - - /* DHCP */ + /* DHCPv4 */ if(ctx->dhcp_requested || ctx->dhcp_established_max) { - wprintw(stats_win, "\nDHCP\n"); - wprintw(stats_win, " Sessions %10lu\n", ctx->dhcp_requested); - wprintw(stats_win, " Established %10lu [", ctx->dhcp_established); + + snprintf(strsp, STRING_SP_SIZE, "%u/%u", ctx->dhcp_established, ctx->dhcp_requested); + wprintw(stats_win, " DHCPv4 %15s [", strsp); if(ctx->dhcp_requested == ctx->dhcp_established) { wattron(stats_win, COLOR_PAIR(COLOR_GREEN)); wprintw(stats_win, "%s", bbl_format_progress(ctx->dhcp_requested, ctx->dhcp_established)); @@ -277,9 +297,8 @@ bbl_stats_job (timer_s *timer) } /* DHCPv6 */ if(ctx->dhcpv6_requested || ctx->dhcpv6_established_max) { - wprintw(stats_win, "\nDHCPv6\n"); - wprintw(stats_win, " Sessions %10lu\n", ctx->dhcpv6_requested); - wprintw(stats_win, " Established %10lu [", ctx->dhcpv6_established); + snprintf(strsp, STRING_SP_SIZE, "%u/%u", ctx->dhcpv6_established, ctx->dhcpv6_requested); + wprintw(stats_win, " DHCPv6 %15s [", strsp); if(ctx->dhcpv6_requested == ctx->dhcpv6_established) { wattron(stats_win, COLOR_PAIR(COLOR_GREEN)); wprintw(stats_win, "%s", bbl_format_progress(ctx->dhcpv6_requested, ctx->dhcpv6_established)); @@ -291,16 +310,21 @@ bbl_stats_job (timer_s *timer) } wprintw(stats_win, "]\n"); } + /* Session stats */ + wprintw(stats_win, " Setup Time %10lu ms\n", ctx->stats.setup_time); + wprintw(stats_win, " Setup Rate %10.02lf CPS (MIN: %0.02lf AVG: %0.02lf MAX: %0.02lf)\n", + ctx->stats.cps, ctx->stats.cps_min, ctx->stats.cps_avg, ctx->stats.cps_max); + wprintw(stats_win, " Flapped %10lu\n", ctx->sessions_flapped); } if(network_if) { - if(dict_count(ctx->li_flow_dict)) { + VISIBLE((dict_count(ctx->li_flow_dict))) { wprintw(stats_win, "\nLI Statistics\n"); wprintw(stats_win, " Flows %10lu\n", dict_count(ctx->li_flow_dict)); wprintw(stats_win, " Rx Packets %10lu (%7lu PPS)\n", network_if->stats.li_rx, network_if->stats.rate_li_rx.avg); } - if(ctx->config.l2tp_server) { + VISIBLE((ctx->config.l2tp_server)) { wprintw(stats_win, "\nL2TP LNS Statistics\n"); wprintw(stats_win, " Tunnels %10lu\n", ctx->l2tp_tunnels); wprintw(stats_win, " Established %10lu\n", ctx->l2tp_tunnels_established); @@ -319,41 +343,39 @@ bbl_stats_job (timer_s *timer) } } - if(ctx->stats.session_traffic_flows) { - wprintw(stats_win, "\nSession Traffic\n"); - wprintw(stats_win, " Flows %10lu\n", ctx->stats.session_traffic_flows); - /* Progress bar session traffic flows */ - wprintw(stats_win, " Verified %10lu [", ctx->stats.session_traffic_flows_verified); - if(ctx->stats.session_traffic_flows == ctx->stats.session_traffic_flows_verified) { - wattron(stats_win, COLOR_PAIR(COLOR_GREEN)); - wprintw(stats_win, "%s", bbl_format_progress(ctx->stats.session_traffic_flows, ctx->stats.session_traffic_flows_verified)); - wattroff(stats_win, COLOR_PAIR(COLOR_GREEN)); - } else { - wattron(stats_win, COLOR_PAIR(COLOR_BLACK)); - wprintw(stats_win, "%s", bbl_format_progress(ctx->stats.session_traffic_flows, ctx->stats.session_traffic_flows_verified)); - wattroff(stats_win, COLOR_PAIR(COLOR_BLACK)); + VISIBLE((ctx->stats.session_traffic_flows || ctx->stats.stream_traffic_flows)) { + wprintw(stats_win, "\nTraffic Flows Verified\n"); + if(ctx->stats.session_traffic_flows) { + snprintf(strsp, STRING_SP_SIZE, "%u/%u", ctx->stats.session_traffic_flows_verified, ctx->stats.session_traffic_flows); + wprintw(stats_win, " Session %14s [", strsp); + if(ctx->stats.session_traffic_flows == ctx->stats.session_traffic_flows_verified) { + wattron(stats_win, COLOR_PAIR(COLOR_GREEN)); + wprintw(stats_win, "%s", bbl_format_progress(ctx->stats.session_traffic_flows, ctx->stats.session_traffic_flows_verified)); + wattroff(stats_win, COLOR_PAIR(COLOR_GREEN)); + } else { + wattron(stats_win, COLOR_PAIR(COLOR_BLACK)); + wprintw(stats_win, "%s", bbl_format_progress(ctx->stats.session_traffic_flows, ctx->stats.session_traffic_flows_verified)); + wattroff(stats_win, COLOR_PAIR(COLOR_BLACK)); + } + wprintw(stats_win, "]\n"); + } + if(ctx->stats.stream_traffic_flows) { + snprintf(strsp, STRING_SP_SIZE, "%u/%u", ctx->stats.stream_traffic_flows_verified, ctx->stats.stream_traffic_flows); + wprintw(stats_win, " Stream %15s [", strsp); + if(ctx->stats.stream_traffic_flows == ctx->stats.stream_traffic_flows_verified) { + wattron(stats_win, COLOR_PAIR(COLOR_GREEN)); + wprintw(stats_win, "%s", bbl_format_progress(ctx->stats.stream_traffic_flows, ctx->stats.stream_traffic_flows_verified)); + wattroff(stats_win, COLOR_PAIR(COLOR_GREEN)); + } else { + wattron(stats_win, COLOR_PAIR(COLOR_BLACK)); + wprintw(stats_win, "%s", bbl_format_progress(ctx->stats.stream_traffic_flows, ctx->stats.stream_traffic_flows_verified)); + wattroff(stats_win, COLOR_PAIR(COLOR_BLACK)); + } + wprintw(stats_win, "]\n"); } - wprintw(stats_win, "]\n"); } - if(ctx->stats.stream_traffic_flows) { - wprintw(stats_win, "\nTraffic Streams\n"); - wprintw(stats_win, " Flows %10lu\n", ctx->stats.stream_traffic_flows); - /* Progress bar traffic stream flows */ - wprintw(stats_win, " Verified %10lu [", ctx->stats.stream_traffic_flows_verified); - if(ctx->stats.stream_traffic_flows == ctx->stats.stream_traffic_flows_verified) { - wattron(stats_win, COLOR_PAIR(COLOR_GREEN)); - wprintw(stats_win, "%s", bbl_format_progress(ctx->stats.stream_traffic_flows, ctx->stats.stream_traffic_flows_verified)); - wattroff(stats_win, COLOR_PAIR(COLOR_GREEN)); - } else { - wattron(stats_win, COLOR_PAIR(COLOR_BLACK)); - wprintw(stats_win, "%s", bbl_format_progress(ctx->stats.stream_traffic_flows, ctx->stats.stream_traffic_flows_verified)); - wattroff(stats_win, COLOR_PAIR(COLOR_BLACK)); - } - wprintw(stats_win, "]\n"); - } - - if(network_if) { + VISIBLE(network_if) { wprintw(stats_win, "\nNetwork Interface ("); for(i = 0; i < ctx->interfaces.network_if_count; i++) { if(i == g_network_if_selected) { @@ -390,8 +412,7 @@ bbl_stats_job (timer_s *timer) wprintw(stats_win, " Tx Multicast Packets %10lu |%7lu PPS\n", network_if->stats.mc_tx, network_if->stats.rate_mc_tx.avg); } - - if(a10nsp_if) { + VISIBLE(a10nsp_if) { wprintw(stats_win, "\nA10NSP Interface ("); for(i = 0; i < ctx->interfaces.a10nsp_if_count; i++) { if(i == g_a10nsp_if_selected) { @@ -416,8 +437,7 @@ bbl_stats_job (timer_s *timer) a10nsp_if->stats.session_ipv4_loss); } } - - if(access_if) { + VISIBLE(access_if) { wprintw(stats_win, "\nAccess Interface ("); for(i = 0; i < ctx->interfaces.access_if_count; i++) { if(i == g_access_if_selected) { @@ -599,6 +619,7 @@ bbl_init_curses (bbl_ctx_s *ctx) /* Stats window */ stats_win = newwin(LINES, STATS_WIN_SIZE, 0, 0); + stats_win_postion = 0; /* Log window */ log_win = newwin(LINES, COLS-STATS_WIN_SIZE, 0, STATS_WIN_SIZE); @@ -623,4 +644,4 @@ bbl_init_curses (bbl_ctx_s *ctx) 0, 100 * MSEC, ctx, &bbl_read_key_job); g_interactive = true; -} +} \ No newline at end of file diff --git a/src/bbl_interface.c b/src/bbl_interface.c index 05ff145a..baab3d22 100644 --- a/src/bbl_interface.c +++ b/src/bbl_interface.c @@ -266,6 +266,10 @@ bbl_add_network_interfaces(bbl_ctx_s *ctx) if(network_config->ip6.len && network_config->gateway6.len) { memcpy(&network_if->ip6, &network_config->ip6, sizeof(ipv6_prefix)); memcpy(&network_if->gateway6, &network_config->gateway6, sizeof(ipv6_prefix)); + memcpy(&network_if->gateway6_solicited_node_multicast, &ipv6_solicited_node_multicast, sizeof(ipv6addr_t)); + memcpy(((uint8_t*)&network_if->gateway6_solicited_node_multicast)+13, + ((uint8_t*)&network_if->gateway6.address)+13, 3); + /* Send initial ICMPv6 NS */ network_if->send_requests |= BBL_IF_SEND_ICMPV6_NS; } diff --git a/src/bbl_interface.h b/src/bbl_interface.h index 6bb18b0d..e76bc366 100644 --- a/src/bbl_interface.h +++ b/src/bbl_interface.h @@ -70,8 +70,9 @@ typedef struct bbl_interface_ ipv6_prefix ip6; ipv6_prefix gateway6; - bool icmpv6_nd_resolved; + ipv6addr_t gateway6_solicited_node_multicast; + bool icmpv6_nd_resolved; bool gateway_resolve_wait; uint8_t *mc_packets; diff --git a/src/bbl_io.c b/src/bbl_io.c index aa96d2de..c975ae17 100644 --- a/src/bbl_io.c +++ b/src/bbl_io.c @@ -55,7 +55,7 @@ bbl_io_packet_mmap_rx_job (timer_s *timer) { /* Get RX timestamp */ clock_gettime(CLOCK_MONOTONIC, &interface->rx_timestamp); - while ((tphdr->tp_status & TP_STATUS_USER)) { + while (tphdr->tp_status & TP_STATUS_USER) { eth_start = (uint8_t*)tphdr + tphdr->tp_mac; eth_len = tphdr->tp_len; interface->stats.packets_rx++; diff --git a/src/bbl_protocols.c b/src/bbl_protocols.c index 8ed97b69..4bc4a467 100644 --- a/src/bbl_protocols.c +++ b/src/bbl_protocols.c @@ -547,8 +547,6 @@ encode_icmpv6(uint8_t *buf, uint16_t *len, if(icmp->data_len) { /* Copy data */ - *(uint32_t*)buf = 0; - BUMP_WRITE_BUFFER(buf, len, sizeof(uint32_t)); memcpy(buf, icmp->data, icmp->data_len); BUMP_WRITE_BUFFER(buf, len, icmp->data_len); } else { @@ -809,7 +807,7 @@ encode_ipv4(uint8_t *buf, uint16_t *len, uint8_t *start = buf; uint16_t ipv4_len = *len; - uint16_t udp_len = *len; + uint16_t udp_len; uint8_t header_len = 5; /* header length 20 (4 * 5) */ if(ipv4->router_alert_option) { @@ -1483,7 +1481,7 @@ encode_pppoe_discovery(uint8_t *buf, uint16_t *len, *buf = ACCESS_LINE_DATA_LINK_ENCAPS; BUMP_WRITE_BUFFER(buf, len, sizeof(uint8_t)); /* (1)byte + (1)byte + (1)byte - * data link encaps 1 encaps 2 */; + * data link encaps 1 encaps 2 */ *(uint32_t*)buf = htobe32(access_line_profile->data_link_encaps); *buf = 3; BUMP_WRITE_BUFFER(buf, len, sizeof(uint32_t)); @@ -1883,7 +1881,6 @@ decode_icmp(uint8_t *buf, uint16_t len, /* Init ICMP header */ icmp = (bbl_icmp_t*)sp; BUMP_BUFFER(sp, sp_len, sizeof(bbl_icmp_t)); - //memset(icmp, 0x0, sizeof(bbl_icmp_t)); icmp->type = *buf; BUMP_BUFFER(buf, len, sizeof(uint8_t)); @@ -2444,7 +2441,6 @@ decode_udp(uint8_t *buf, uint16_t len, /* Init UDP header */ udp = (bbl_udp_t*)sp; BUMP_BUFFER(sp, sp_len, sizeof(bbl_udp_t)); - //memset(udp, 0x0, sizeof(bbl_udp_t)); udp->src = be16toh(*(uint16_t*)buf); BUMP_BUFFER(buf, len, sizeof(uint16_t)); @@ -2515,7 +2511,6 @@ decode_ipv6(uint8_t *buf, uint16_t len, /* Init IPv6 header */ ipv6 = (bbl_ipv6_t*)sp; BUMP_BUFFER(sp, sp_len, sizeof(bbl_ipv6_t)); - //memset(ipv6, 0x0, sizeof(bbl_ipv6_t)); /* Check if version is 6 */ if(((*buf >> 4) & 0xf) != 6) { @@ -2596,7 +2591,7 @@ decode_ipv4(uint8_t *buf, uint16_t len, /* Init IPv4 header */ ipv4 = (bbl_ipv4_t*)sp; BUMP_BUFFER(sp, sp_len, sizeof(bbl_ipv4_t)); - //memset(ipv4, 0x0, sizeof(bbl_ipv4_t)); + ipv4->router_alert_option = false; header = (struct ip*)buf; @@ -3379,7 +3374,6 @@ decode_pppoe_session(uint8_t *buf, uint16_t len, /* Init PPPoE header */ pppoe = (bbl_pppoe_session_t*)sp; BUMP_BUFFER(sp, sp_len, sizeof(bbl_pppoe_session_t)); - //memset(pppoe, 0x0, sizeof(bbl_pppoe_session_t)); header = (struct pppoe_ppp_session_header*)buf; BUMP_BUFFER(buf, len, sizeof(struct pppoe_ppp_session_header)); @@ -3444,7 +3438,6 @@ decode_arp(uint8_t *buf, uint16_t len, /* Init ARP header */ arp = (bbl_arp_t*)sp; BUMP_BUFFER(sp, sp_len, sizeof(bbl_arp_t)); - //memset(arp, 0x0, sizeof(bbl_arp_t)); BUMP_BUFFER(buf, len, 6); arp->code = be16toh(*(uint16_t*)buf); diff --git a/src/bbl_protocols.h b/src/bbl_protocols.h index ab0d8024..4d31502b 100644 --- a/src/bbl_protocols.h +++ b/src/bbl_protocols.h @@ -235,12 +235,11 @@ static const ipv6addr_t ipv6_link_local_prefix = {0xFE, 0x80, 0x00, 0x00, 0x00, static const ipv6addr_t ipv6_multicast_all_nodes = {0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; static const ipv6addr_t ipv6_multicast_all_routers = {0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}; static const ipv6addr_t ipv6_multicast_all_dhcp = {0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02}; +static const ipv6addr_t ipv6_solicited_node_multicast = {0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00}; /* MAC Addresses */ static const uint8_t broadcast_mac[ETH_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; -static const uint8_t multicast_mac[ETH_ADDR_LEN] = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00}; -static const uint8_t ipv6_multicast_mac[ETH_ADDR_LEN] = { 0x33, 0x33, 0xff, 0x00, 0x00, 0x10}; -static const uint8_t ipv6_multicast_mac_dhcp[ETH_ADDR_LEN] = { 0x33, 0x33, 0x00, 0x01, 0x00, 0x02}; + typedef enum protocol_error_ { PROTOCOL_SUCCESS = 0, diff --git a/src/bbl_rx.c b/src/bbl_rx.c index 293bfc47..0fe08b91 100644 --- a/src/bbl_rx.c +++ b/src/bbl_rx.c @@ -495,6 +495,8 @@ bbl_rx_icmpv6(bbl_ethernet_header_t *eth, bbl_ipv6_t *ipv6, bbl_interface_s *int } } else if(icmpv6->type == IPV6_ICMPV6_NEIGHBOR_SOLICITATION) { session->send_requests |= BBL_SEND_ICMPV6_NA; + } else if(icmpv6->type == IPV6_ICMPV6_ECHO_REQUEST) { + bbl_send_icmpv6_echo_reply(interface, session, eth, ipv6, icmpv6); } } @@ -1736,6 +1738,8 @@ bbl_rx_network_icmpv6(bbl_ethernet_header_t *eth, bbl_interface_s *interface) { secondary_ip6 = secondary_ip6->next; } } + } else if(icmpv6->type == IPV6_ICMPV6_ECHO_REQUEST) { + bbl_send_icmpv6_echo_reply(interface, NULL, eth, ipv6, icmpv6); } } diff --git a/src/bbl_send.c b/src/bbl_send.c index d9932a26..fe741f9d 100644 --- a/src/bbl_send.c +++ b/src/bbl_send.c @@ -87,6 +87,7 @@ bbl_send_to_buffer(bbl_interface_s *interface, bbl_ethernet_header_t *eth) return BBL_SEND_FULL; } slot = interface->send.ring + interface->send.write; + slot->packet_len = 0; if(encode_ethernet(slot->packet, &slot->packet_len, eth) == PROTOCOL_SUCCESS) { interface->send.write = interface->send.next++; if(interface->send.next == interface->send.size) { @@ -187,4 +188,23 @@ bbl_send_icmp_reply(bbl_interface_s *interface, ipv4->ttl = 64; icmp->type = ICMP_TYPE_ECHO_REPLY; return bbl_send_to_buffer(interface, eth); +} + +bbl_send_result_t +bbl_send_icmpv6_echo_reply(bbl_interface_s *interface, + bbl_session_s *session, + bbl_ethernet_header_t *eth, + bbl_ipv6_t *ipv6, + bbl_icmpv6_t *icmpv6) +{ + update_eth(interface, session, eth); + ipv6->dst = ipv6->src; + if(session) { + ipv6->src = session->ipv6_address; + } else { + ipv6->src = interface->ip6.address; + } + ipv6->ttl = 255; + icmpv6->type = IPV6_ICMPV6_ECHO_REPLY; + return bbl_send_to_buffer(interface, eth); } \ No newline at end of file diff --git a/src/bbl_send.h b/src/bbl_send.h index a243f246..1f637170 100644 --- a/src/bbl_send.h +++ b/src/bbl_send.h @@ -62,4 +62,11 @@ bbl_send_icmp_reply(bbl_interface_s *interface, bbl_ipv4_t *ipv4, bbl_icmp_t *icmp); +bbl_send_result_t +bbl_send_icmpv6_echo_reply(bbl_interface_s *interface, + bbl_session_s *session, + bbl_ethernet_header_t *eth, + bbl_ipv6_t *ipv6, + bbl_icmpv6_t *icmpv6); + #endif \ No newline at end of file diff --git a/src/bbl_stats.c b/src/bbl_stats.c index c2e05caf..485a7bd1 100644 --- a/src/bbl_stats.c +++ b/src/bbl_stats.c @@ -56,8 +56,10 @@ void bbl_stats_generate (bbl_ctx_s *ctx, bbl_stats_t * stats) { bbl_session_s *session; + bbl_stream *stream; struct bbl_interface_ *interface; + struct dict_itor *itor; uint32_t i; uint32_t join_delays = 0; @@ -98,54 +100,65 @@ bbl_stats_generate (bbl_ctx_s *ctx, bbl_stats_t * stats) { } /* Session Traffic */ - if(session->access_ipv4_rx_first_seq) stats->sessions_access_ipv4_rx++; - if(session->network_ipv4_rx_first_seq) stats->sessions_network_ipv4_rx++; - if(session->access_ipv6_rx_first_seq) stats->sessions_access_ipv6_rx++; - if(session->network_ipv6_rx_first_seq) stats->sessions_network_ipv6_rx++; - if(session->access_ipv6pd_rx_first_seq) stats->sessions_access_ipv6pd_rx++; - if(session->network_ipv6pd_rx_first_seq) stats->sessions_network_ipv6pd_rx++; - - if(stats->min_access_ipv4_rx_first_seq) { - if(session->access_ipv4_rx_first_seq < stats->min_access_ipv4_rx_first_seq) stats->min_access_ipv4_rx_first_seq = session->access_ipv4_rx_first_seq; - } else { - stats->min_access_ipv4_rx_first_seq = session->access_ipv4_rx_first_seq; + if(session->access_ipv4_rx_first_seq) { + stats->sessions_access_ipv4_rx++; + if(stats->min_access_ipv4_rx_first_seq) { + if(session->access_ipv4_rx_first_seq < stats->min_access_ipv4_rx_first_seq) stats->min_access_ipv4_rx_first_seq = session->access_ipv4_rx_first_seq; + } else { + stats->min_access_ipv4_rx_first_seq = session->access_ipv4_rx_first_seq; + } + if(session->access_ipv4_rx_first_seq > stats->max_access_ipv4_rx_first_seq) stats->max_access_ipv4_rx_first_seq = session->access_ipv4_rx_first_seq; } - if(session->access_ipv4_rx_first_seq > stats->max_access_ipv4_rx_first_seq) stats->max_access_ipv4_rx_first_seq = session->access_ipv4_rx_first_seq; - if(stats->min_network_ipv4_rx_first_seq) { - if(session->network_ipv4_rx_first_seq < stats->min_network_ipv4_rx_first_seq) stats->min_network_ipv4_rx_first_seq = session->network_ipv4_rx_first_seq; - } else { - stats->min_network_ipv4_rx_first_seq = session->network_ipv4_rx_first_seq; + if(session->network_ipv4_rx_first_seq) { + stats->sessions_network_ipv4_rx++; + if(stats->min_network_ipv4_rx_first_seq) { + if(session->network_ipv4_rx_first_seq < stats->min_network_ipv4_rx_first_seq) stats->min_network_ipv4_rx_first_seq = session->network_ipv4_rx_first_seq; + } else { + stats->min_network_ipv4_rx_first_seq = session->network_ipv4_rx_first_seq; + } + if(session->network_ipv4_rx_first_seq > stats->max_network_ipv4_rx_first_seq) stats->max_network_ipv4_rx_first_seq = session->network_ipv4_rx_first_seq; } - if(session->network_ipv4_rx_first_seq > stats->max_network_ipv4_rx_first_seq) stats->max_network_ipv4_rx_first_seq = session->network_ipv4_rx_first_seq; - if(stats->min_access_ipv6_rx_first_seq) { - if(session->access_ipv6_rx_first_seq < stats->min_access_ipv6_rx_first_seq) stats->min_access_ipv6_rx_first_seq = session->access_ipv6_rx_first_seq; - } else { - stats->min_access_ipv6_rx_first_seq = session->access_ipv6_rx_first_seq; + if(session->access_ipv6_rx_first_seq) { + stats->sessions_access_ipv6_rx++; + if(stats->min_access_ipv6_rx_first_seq) { + if(session->access_ipv6_rx_first_seq < stats->min_access_ipv6_rx_first_seq) stats->min_access_ipv6_rx_first_seq = session->access_ipv6_rx_first_seq; + } else { + stats->min_access_ipv6_rx_first_seq = session->access_ipv6_rx_first_seq; + } + if(session->access_ipv6_rx_first_seq > stats->max_access_ipv6_rx_first_seq) stats->max_access_ipv6_rx_first_seq = session->access_ipv6_rx_first_seq; } - if(session->access_ipv6_rx_first_seq > stats->max_access_ipv6_rx_first_seq) stats->max_access_ipv6_rx_first_seq = session->access_ipv6_rx_first_seq; - if(stats->min_network_ipv6_rx_first_seq) { - if(session->network_ipv6_rx_first_seq < stats->min_network_ipv6_rx_first_seq) stats->min_network_ipv6_rx_first_seq = session->network_ipv6_rx_first_seq; - } else { - stats->min_network_ipv6_rx_first_seq = session->network_ipv6_rx_first_seq; + if(session->network_ipv6_rx_first_seq) { + stats->sessions_network_ipv6_rx++; + if(stats->min_network_ipv6_rx_first_seq) { + if(session->network_ipv6_rx_first_seq < stats->min_network_ipv6_rx_first_seq) stats->min_network_ipv6_rx_first_seq = session->network_ipv6_rx_first_seq; + } else { + stats->min_network_ipv6_rx_first_seq = session->network_ipv6_rx_first_seq; + } + if(session->network_ipv6_rx_first_seq > stats->max_network_ipv6_rx_first_seq) stats->max_network_ipv6_rx_first_seq = session->network_ipv6_rx_first_seq; } - if(session->network_ipv6_rx_first_seq > stats->max_network_ipv6_rx_first_seq) stats->max_network_ipv6_rx_first_seq = session->network_ipv6_rx_first_seq; - if(stats->min_access_ipv6pd_rx_first_seq) { - if(session->access_ipv6pd_rx_first_seq < stats->min_access_ipv6pd_rx_first_seq) stats->min_access_ipv6pd_rx_first_seq = session->access_ipv6pd_rx_first_seq; - } else { - stats->min_access_ipv6pd_rx_first_seq = session->access_ipv6pd_rx_first_seq; + if(session->access_ipv6pd_rx_first_seq) { + stats->sessions_access_ipv6pd_rx++; + if(stats->min_access_ipv6pd_rx_first_seq) { + if(session->access_ipv6pd_rx_first_seq < stats->min_access_ipv6pd_rx_first_seq) stats->min_access_ipv6pd_rx_first_seq = session->access_ipv6pd_rx_first_seq; + } else { + stats->min_access_ipv6pd_rx_first_seq = session->access_ipv6pd_rx_first_seq; + } + if(session->access_ipv6pd_rx_first_seq > stats->max_access_ipv6pd_rx_first_seq) stats->max_access_ipv6pd_rx_first_seq = session->access_ipv6pd_rx_first_seq; } - if(session->access_ipv6pd_rx_first_seq > stats->max_access_ipv6pd_rx_first_seq) stats->max_access_ipv6pd_rx_first_seq = session->access_ipv6pd_rx_first_seq; - if(stats->min_network_ipv6pd_rx_first_seq) { - if(session->network_ipv6pd_rx_first_seq < stats->min_network_ipv6pd_rx_first_seq) stats->min_network_ipv6pd_rx_first_seq = session->network_ipv6pd_rx_first_seq; - } else { - stats->min_network_ipv6pd_rx_first_seq = session->network_ipv6pd_rx_first_seq; + if(session->network_ipv6pd_rx_first_seq) { + stats->sessions_network_ipv6pd_rx++; + if(stats->min_network_ipv6pd_rx_first_seq) { + if(session->network_ipv6pd_rx_first_seq < stats->min_network_ipv6pd_rx_first_seq) stats->min_network_ipv6pd_rx_first_seq = session->network_ipv6pd_rx_first_seq; + } else { + stats->min_network_ipv6pd_rx_first_seq = session->network_ipv6pd_rx_first_seq; + } + if(session->network_ipv6pd_rx_first_seq > stats->max_network_ipv6pd_rx_first_seq) stats->max_network_ipv6pd_rx_first_seq = session->network_ipv6pd_rx_first_seq; } - if(session->network_ipv6pd_rx_first_seq > stats->max_network_ipv6pd_rx_first_seq) stats->max_network_ipv6pd_rx_first_seq = session->network_ipv6pd_rx_first_seq; } } @@ -188,6 +201,37 @@ bbl_stats_generate (bbl_ctx_s *ctx, bbl_stats_t * stats) { stats->l2tp_data_rx += interface->stats.l2tp_data_rx; stats->li_rx += interface->stats.li_rx; } + + /* Iterate over all traffic streams */ + itor = dict_itor_new(ctx->stream_flow_dict); + dict_itor_first(itor); + for (; dict_itor_valid(itor); dict_itor_next(itor)) { + stream = (bbl_stream*)*dict_itor_datum(itor); + if(stream) { + if(stats->min_stream_loss) { + if(stream->loss < stats->min_stream_loss) stats->min_stream_loss = stream->loss; + } else { + stats->min_stream_loss = stream->loss; + } + if(stream->loss > stats->max_stream_loss) stats->max_stream_loss = stream->loss; + + if(stream->rx_first_seq) { + if(stats->min_stream_rx_first_seq) { + if(stream->rx_first_seq < stats->min_stream_rx_first_seq) stats->min_stream_rx_first_seq = stream->rx_first_seq; + } else { + stats->min_stream_rx_first_seq = stream->rx_first_seq; + } + if(stream->rx_first_seq > stats->max_stream_rx_first_seq) stats->max_stream_rx_first_seq = stream->rx_first_seq; + + if(stats->min_stream_delay_ns) { + if(stream->min_delay_ns < stats->min_stream_delay_ns) stats->min_stream_delay_ns = stream->min_delay_ns; + } else { + stats->min_stream_delay_ns = stream->min_delay_ns; + } + if(stream->max_delay_ns > stats->max_stream_delay_ns) stats->max_stream_delay_ns = stream->max_delay_ns; + } + } + } } void @@ -387,6 +431,15 @@ bbl_stats_stdout (bbl_ctx_s *ctx, bbl_stats_t * stats) { printf("\nTraffic Streams:\n"); printf(" Verified Traffic Flows: %u/%u\n", ctx->stats.stream_traffic_flows_verified, ctx->stats.stream_traffic_flows); + printf(" First Sequence Number Received MIN: %8lu MAX: %8lu\n", + stats->min_stream_rx_first_seq, + stats->max_stream_rx_first_seq); + printf(" Flow Receive Packet Loss MIN: %8lu MAX: %8lu\n", + stats->min_stream_loss, + stats->max_stream_loss); + printf(" Flow Receive Delay (nsec) MIN: %8lu MAX: %8lu\n", + stats->min_stream_delay_ns, + stats->max_stream_delay_ns); } if(ctx->config.igmp_group_count > 1) { @@ -649,6 +702,12 @@ bbl_stats_json (bbl_ctx_s *ctx, bbl_stats_t * stats) { jobj_sub = json_object(); json_object_set(jobj_sub, "total-flows", json_integer(ctx->stats.stream_traffic_flows)); json_object_set(jobj_sub, "verified-flows", json_integer(ctx->stats.stream_traffic_flows_verified)); + json_object_set(jobj_sub, "first-seq-rx-min", json_integer(stats->min_stream_rx_first_seq)); + json_object_set(jobj_sub, "first-seq-rx-max", json_integer(stats->max_stream_rx_first_seq)); + json_object_set(jobj_sub, "flow-rx-packet-loss-min", json_integer(stats->min_stream_loss)); + json_object_set(jobj_sub, "flow-rx-packet-loss-max", json_integer(stats->max_stream_loss)); + json_object_set(jobj_sub, "flow-rx-delay-min", json_integer(stats->min_stream_delay_ns)); + json_object_set(jobj_sub, "flow-rx-delay-max", json_integer(stats->max_stream_delay_ns)); json_object_set(jobj, "traffic-streams", jobj_sub); } diff --git a/src/bbl_stats.h b/src/bbl_stats.h index 0825175d..226dcf6b 100644 --- a/src/bbl_stats.h +++ b/src/bbl_stats.h @@ -74,6 +74,13 @@ typedef struct bbl_stats_ { uint64_t l2tp_data_rx; uint64_t li_rx; + + uint64_t min_stream_loss; + uint64_t max_stream_loss; + uint64_t min_stream_rx_first_seq; + uint64_t max_stream_rx_first_seq; + uint64_t min_stream_delay_ns; + uint64_t max_stream_delay_ns; } bbl_stats_t; void bbl_compute_avg_rate (bbl_rate_s *rate, uint64_t current_value); diff --git a/src/bbl_stream.c b/src/bbl_stream.c index a0327d19..89cb068d 100644 --- a/src/bbl_stream.c +++ b/src/bbl_stream.c @@ -12,7 +12,6 @@ #include "bbl_stream.h" #include "bbl_stats.h" #include "bbl_io.h" -#include extern volatile bool g_teardown; extern bool g_init_phase; @@ -91,6 +90,7 @@ FREE: stream->buf = NULL; stream->tx_len = 0; } + stream->send_window_packets = 0; return false; } @@ -221,7 +221,6 @@ bbl_stream_build_access_pppoe_packet(bbl_stream *stream) { return true; } - bool bbl_stream_build_a10nsp_pppoe_packet(bbl_stream *stream) { @@ -523,10 +522,7 @@ bbl_stream_build_network_packet(bbl_stream *stream) { /* All IPv4 multicast addresses start with 1110 */ if((ipv4.dst & htobe32(0xf0000000)) == htobe32(0xe0000000)) { /* Generate multicast destination MAC */ - *(uint32_t*)(&mac[2]) = ipv4.dst; - mac[0] = 0x01; - mac[2] = 0x5e; - mac[3] &= 0x7f; + ipv4_multicast_mac(ipv4.dst, mac); eth.dst = mac; bbl.type = BBL_TYPE_MULTICAST; bbl.mc_source = ipv4.src; @@ -723,148 +719,322 @@ bbl_stream_build_packet(bbl_stream *stream) { void * bbl_stream_tx_thread (void *thread_data) { - bbl_stream *stream = thread_data; - bbl_interface_s *interface = stream->interface; + bbl_stream_thread *thread = thread_data; - struct timespec send_windwow; - struct timespec now; - struct timespec sleep; - struct timespec rem; - - double d; - uint64_t packets; - uint64_t packets_expected; - - struct sockaddr_ll addr = {0}; - int fd_tx; - int qdisc_bypass = 1; + pthread_mutex_lock(&thread->mutex); + timer_smear_all_buckets(&thread->timer_root); + pthread_mutex_unlock(&thread->mutex); /* Open new TX socket for thread. */ - fd_tx = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0); - if (fd_tx == -1) { - if (errno == EPERM) { - LOG(ERROR, "socket() for interface %s Permission denied: Are you root?\n", interface->name); - return NULL; - } - LOG(ERROR, "socket() TX error %s (%d) for interface %s\n", strerror(errno), errno, interface->name); - return NULL; - } - addr.sll_family = PF_PACKET; - addr.sll_ifindex = interface->ifindex; - addr.sll_protocol = 0; - if (bind(fd_tx, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - LOG(ERROR, "bind() TX error %s (%d) for interface %s\n", - strerror(errno), errno, interface->name); - return NULL; - } - if (setsockopt(fd_tx, SOL_PACKET, PACKET_QDISC_BYPASS, &qdisc_bypass, sizeof(qdisc_bypass)) == -1) { - LOG(ERROR, "Setting qdisc bypass error %s (%d) for interface %s\n", strerror(errno), errno, interface->name); - return NULL; - } - - while(!g_teardown) { - packets = 0; - - if(!bbl_stream_can_send(stream)) { - /* Close send window */ - stream->send_window_packets = 0; - sleep.tv_nsec = (100 + (rand() % 900)) * MSEC; - nanosleep(&sleep, &rem); - continue; - } - if(!stream->buf) { - if(!bbl_stream_build_packet(stream)) { - LOG(ERROR, "Failed to build packet for stream %s\n", stream->config->name); - sleep.tv_nsec = 100 * MSEC; - nanosleep(&sleep, &rem); - continue; - } - } - - if(!g_traffic || (stream->session && !stream->session->stream_traffic)) { - /* Close send window */ - stream->send_window_packets = 0; - sleep.tv_nsec = 10 * MSEC; - nanosleep(&sleep, &rem); - continue; - } - - clock_gettime(CLOCK_MONOTONIC, &now); - if(stream->send_window_packets == 0) { - /* Open new send window */ - stream->send_window_start.tv_sec = now.tv_sec; - stream->send_window_start.tv_nsec = now.tv_nsec; - packets = 1; + while(true) { + pthread_mutex_lock(&thread->mutex); + if(thread->active) { + pthread_mutex_unlock(&thread->mutex); + timer_walk(&thread->timer_root); } else { - timespec_sub(&send_windwow, &now, &stream->send_window_start); - packets_expected = send_windwow.tv_sec * stream->config->pps; - d = (send_windwow.tv_nsec / 1000000000.0); - packets_expected += d * stream->config->pps; - - if(packets_expected > stream->send_window_packets) { - packets = packets_expected - stream->send_window_packets; - } + pthread_mutex_unlock(&thread->mutex); + break; } - - /* Update BBL header fields */ - *(uint32_t*)(stream->buf + (stream->tx_len - 8)) = now.tv_sec; - *(uint32_t*)(stream->buf + (stream->tx_len - 4)) = now.tv_nsec; - while(packets) { - *(uint64_t*)(stream->buf + (stream->tx_len - 16)) = stream->flow_seq; - /* Send packet ... */ - if (sendto(fd_tx, stream->buf, stream->tx_len, 0, (struct sockaddr*)&addr, sizeof(struct sockaddr_ll)) <0 ) { - LOG(IO, "Sendto failed with errno: %i\n", errno); - sleep.tv_nsec = 1 * MSEC; - nanosleep(&sleep, &rem); - clock_gettime(CLOCK_MONOTONIC, &now); - *(uint32_t*)(stream->buf + (stream->tx_len - 8)) = now.tv_sec; - *(uint32_t*)(stream->buf + (stream->tx_len - 4)) = now.tv_nsec; - continue; - } - stream->send_window_packets++; - stream->packets_tx++; - stream->flow_seq++; - packets--; - } - sleep.tv_nsec = 1000; /* 0.001ms */ - nanosleep(&sleep, &rem); } return NULL; } +/** + * This function synchronizes the data + * between TX stream threads and main + * thread. + * + * @param thread + */ void -bbl_stream_tx_thread_counter_sync (timer_s *timer) { - - bbl_stream *stream = timer->data; - bbl_session_s *session = stream->session; - bbl_interface_s *interface = stream->interface; +bbl_stream_tx_thread_sync(bbl_stream_thread *thread) { + bbl_interface_s *interface = thread->interface; + bbl_stream *stream = thread->stream; + bbl_session_s *session = NULL; uint64_t packets_tx; + uint64_t bytes_tx; uint64_t delta_packets; uint64_t delta_bytes; + + pthread_mutex_lock(&stream->thread.mutex); - packets_tx = stream->packets_tx; - delta_packets = packets_tx - stream->packets_tx_last_sync; - delta_bytes = delta_packets * stream->tx_len; + packets_tx = thread->packets_tx; + delta_packets = packets_tx - thread->packets_tx_last_sync; + bytes_tx = thread->bytes_tx; + delta_bytes = bytes_tx - thread->bytes_tx_last_sync; interface->stats.packets_tx += delta_packets; interface->stats.bytes_tx += delta_bytes; - if(session) { - if(stream->direction == STREAM_DIRECTION_UP) { - session->stats.packets_tx += delta_packets; - session->stats.bytes_tx += delta_bytes; - session->stats.accounting_packets_tx += delta_packets; - session->stats.accounting_bytes_tx += delta_bytes; - } else { - if(session->l2tp_session) { - interface->stats.l2tp_data_tx++; - session->l2tp_session->tunnel->stats.data_tx++; - session->l2tp_session->stats.data_tx++; - session->l2tp_session->stats.data_ipv4_tx++; + + thread->packets_tx_last_sync = packets_tx; + thread->bytes_tx_last_sync = bytes_tx; + + packets_tx = thread->sendto_failed; + delta_packets = packets_tx - thread->sendto_failed_last_sync; + interface->stats.sendto_failed += delta_packets; + + pthread_mutex_unlock(&stream->thread.mutex); + + while(stream) { + pthread_mutex_lock(&stream->thread.mutex); + if(stream->session) { + session = stream->session; + /* Sync counters ... */ + packets_tx = stream->packets_tx; + delta_packets = packets_tx - stream->packets_tx_last_sync; + delta_bytes = delta_packets * stream->tx_len; + if(stream->direction == STREAM_DIRECTION_UP) { + session->stats.packets_tx += delta_packets; + session->stats.bytes_tx += delta_bytes; + session->stats.accounting_packets_tx += delta_packets; + session->stats.accounting_bytes_tx += delta_bytes; + } else { + if(session->l2tp_session) { + interface->stats.l2tp_data_tx++; + session->l2tp_session->tunnel->stats.data_tx++; + session->l2tp_session->stats.data_tx++; + session->l2tp_session->stats.data_ipv4_tx++; + } } + stream->packets_tx_last_sync = packets_tx; + + /* Sync session states ... */ + if(bbl_stream_can_send(stream)) { + if(!stream->buf) { + if(!bbl_stream_build_packet(stream)) { + LOG(ERROR, "Failed to build packet for stream %s\n", stream->config->name); + } + } + } + if(stream->buf && g_traffic && session->stream_traffic) { + stream->thread.can_send = true; + } else { + stream->thread.can_send = false; + stream->send_window_packets = 0; + } + } else { + if(bbl_stream_can_send(stream)) { + if(!stream->buf) { + if(!bbl_stream_build_packet(stream)) { + LOG(ERROR, "Failed to build packet for stream %s\n", stream->config->name); + } + } + } + if(stream->buf && g_traffic) { + stream->thread.can_send = true; + } else { + stream->thread.can_send = false; + stream->send_window_packets = 0; + } + } + pthread_mutex_unlock(&stream->thread.mutex); + stream = stream->thread.next; + } +} + +void +bbl_stream_tx_thread_sync_timer(timer_s *timer) { + bbl_stream_thread *thread = timer->data; + bbl_stream_tx_thread_sync(thread); +} + +static bbl_stream_thread * +bbl_stream_thread_create(uint8_t thread_group, bbl_stream *stream) { + bbl_stream_thread *thread; + bbl_interface_s *interface = stream->interface; + + int qdisc_bypass = 1; + + if(thread_group) { + LOG(INFO, "Create stream TX thread-group %u\n", thread_group); + } else { + LOG(INFO, "Create stream TX thread for stream %s\n", stream->config->name); + } + thread = calloc(1, sizeof(bbl_stream_thread)); + thread->thread_group = thread_group; + thread->interface = interface; + + /* Init thread timer root */ + timer_init_root(&thread->timer_root); + + /* Init thread mutex */ + if (pthread_mutex_init(&thread->mutex, NULL) != 0) { + LOG(ERROR, "Failed to init stream TX thread mutex\n"); + return NULL; + } + + /* Setup RAW socket */ + thread->socket.fd_tx = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0); + if (thread->socket.fd_tx == -1) { + if (errno == EPERM) { + LOG(ERROR, "Thread: socket() for interface %s Permission denied: Are you root?\n", interface->name); + return NULL; + } + LOG(ERROR, "Thread: socket() TX error %s (%d) for interface %s\n", strerror(errno), errno, interface->name); + return NULL; + } + thread->socket.addr.sll_family = PF_PACKET; + thread->socket.addr.sll_ifindex = interface->ifindex; + thread->socket.addr.sll_protocol = 0; + if (bind(thread->socket.fd_tx, (struct sockaddr*)&thread->socket.addr, sizeof(thread->socket.addr)) == -1) { + LOG(ERROR, "Thread: bind() TX error %s (%d) for interface %s\n", + strerror(errno), errno, interface->name); + return NULL; + } + if(interface->ctx->config.qdisc_bypass) { + if (setsockopt(thread->socket.fd_tx, SOL_PACKET, PACKET_QDISC_BYPASS, &qdisc_bypass, sizeof(qdisc_bypass)) == -1) { + LOG(ERROR, "Thread: Setting qdisc bypass error %s (%d) for interface %s\n", strerror(errno), errno, interface->name); + return NULL; } } - stream->packets_tx_last_sync = packets_tx; + return thread; +} + +/** + * This function will add a stream to an existing + * thread or create a new thread based on thread + * group. + * + * @param ctx global context + * @param thread_group thread group + * @param stream traffic stream + */ +static bbl_stream_thread* +bbl_stream_add_to_thread(bbl_ctx_s *ctx, uint8_t thread_group, bbl_stream *stream) { + + bbl_stream_thread *thread; + + /* Search for existing thread group or create a new one */ + if(ctx->stream_thread) { + thread = ctx->stream_thread; + while(true) { + /* The thread group zero means that this stream + * requests a dedicated thread. The scope of thread + * groups is per interface. */ + if(thread_group && thread->thread_group == thread_group && + thread->interface == stream->interface) { + break; + } + if(thread->next) { + thread = thread->next; + } else { + /* Create new thread */ + thread->next = bbl_stream_thread_create(thread_group, stream); + thread = thread->next; + if(!thread) { + return NULL; + } + break; + } + } + } else { + /* Create first thread */ + thread = bbl_stream_thread_create(thread_group, stream); + ctx->stream_thread = thread; + } + + /* Append stream to thread */ + if(thread->stream) { + thread->stream_tail->thread.next = stream; + } else { + /* First stream in thread */ + thread->stream = stream; + } + thread->stream_tail = stream; + thread->stream_count++; + + /* Init stream mutex */ + if (pthread_mutex_init(&stream->thread.mutex, NULL) != 0) { + LOG(ERROR, "Failed to init stream mutex\n"); + return NULL; + } + + stream->thread.thread = thread; + return thread; +} + +/** + * This function starts all stream threads. + * + * @param ctx global context + * @return true if success and false if failed + */ +bool +bbl_stream_start_threads(bbl_ctx_s *ctx) { + + bbl_stream_thread *thread = ctx->stream_thread; + + while(thread) { + if(thread->thread_group) { + LOG(INFO, "Start stream TX thread-group %u\n", thread->thread_group); + } else { + LOG(INFO, "Start stream TX thread for stream %s\n", thread->stream->config->name); + } + thread->active = true; + timer_add_periodic(&ctx->timer_root, &thread->sync_timer, "Stream TX Thread Sync", 1, 0, thread, &bbl_stream_tx_thread_sync_timer); + pthread_create(&thread->thread_id, NULL, bbl_stream_tx_thread, (void *)thread); + thread = thread->next; + } + return true; +} + +/** + * This function stops all stream threads. + * + * @param ctx global context + */ +void +bbl_stream_stop_threads(bbl_ctx_s *ctx) { + + bbl_stream_thread *thread = ctx->stream_thread; + while(thread) { + if(thread->active) { + if(thread->thread_group) { + LOG(INFO, "Stop stream TX thread-group %u\n", thread->thread_group); + } else { + LOG(INFO, "Stop stream TX thread for stream %s\n", thread->stream->config->name); + } + /* Mark thread as inactive and stop counter sync job */ + pthread_mutex_lock(&thread->mutex); + thread->active = false; + pthread_mutex_unlock(&thread->mutex); + /* Wait for thread to be stopped */ + pthread_join(thread->thread_id, NULL); + /* Do final sync */ + bbl_stream_tx_thread_sync(thread); + } + thread = thread->next; + } +} + +uint64_t +bbl_stream_send_window(bbl_stream *stream, struct timespec *now) { + + bbl_ctx_s *ctx = stream->interface->ctx; + uint64_t packets = 1; + uint64_t packets_expected; + + struct timespec send_windwow; + + if(stream->send_window_packets == 0) { + /* Open new send window */ + stream->send_window_start.tv_sec = now->tv_sec; + stream->send_window_start.tv_nsec = now->tv_nsec; + } else { + timespec_sub(&send_windwow, now, &stream->send_window_start); + packets_expected = send_windwow.tv_sec * stream->config->pps; + packets_expected += stream->config->pps * ((double)send_windwow.tv_nsec / 1000000000.0); + + if(packets_expected > stream->send_window_packets) { + packets = packets_expected - stream->send_window_packets; + } + if(packets > ctx->config.io_stream_max_ppi) { + packets = ctx->config.io_stream_max_ppi; + } + } + + return packets; } void @@ -874,15 +1044,11 @@ bbl_stream_tx_job (timer_s *timer) { bbl_session_s *session = stream->session; bbl_interface_s *interface = stream->interface; - struct timespec send_windwow; struct timespec now; - double d; uint64_t packets = 1; - uint64_t packets_expected; if(!bbl_stream_can_send(stream)) { - stream->send_window_packets = 0; return; } if(!stream->buf) { @@ -899,24 +1065,7 @@ bbl_stream_tx_job (timer_s *timer) { } clock_gettime(CLOCK_MONOTONIC, &now); - if(stream->send_window_packets == 0) { - /* Open new send window */ - stream->send_window_start.tv_sec = now.tv_sec; - stream->send_window_start.tv_nsec = now.tv_nsec; - } else { - timespec_sub(&send_windwow, &now, &stream->send_window_start); - packets_expected = send_windwow.tv_sec * stream->config->pps; - d = (send_windwow.tv_nsec / 1000000000.0); - packets_expected += d * stream->config->pps; - - if(packets_expected > stream->send_window_packets) { - packets = packets_expected - stream->send_window_packets; - } - if(packets > interface->ctx->config.io_stream_max_ppi) { - packets = interface->ctx->config.io_stream_max_ppi; - } - } - + packets = bbl_stream_send_window(stream, &now); /* Update BBL header fields */ *(uint32_t*)(stream->buf + (stream->tx_len - 8)) = now.tv_sec; *(uint32_t*)(stream->buf + (stream->tx_len - 4)) = now.tv_nsec; @@ -948,6 +1097,49 @@ bbl_stream_tx_job (timer_s *timer) { } } +void +bbl_stream_tx_job_threaded (timer_s *timer) { + bbl_stream *stream = timer->data; + bbl_stream_thread *thread = stream->thread.thread; + + struct timespec now; + + uint64_t packets; + + pthread_mutex_lock(&stream->thread.mutex); + if(!stream->thread.can_send) { + pthread_mutex_unlock(&stream->thread.mutex); + return; + } + + pthread_mutex_lock(&thread->mutex); + + clock_gettime(CLOCK_MONOTONIC, &now); + packets = bbl_stream_send_window(stream, &now); + + /* Update BBL header fields */ + *(uint32_t*)(stream->buf + (stream->tx_len - 8)) = now.tv_sec; + *(uint32_t*)(stream->buf + (stream->tx_len - 4)) = now.tv_nsec; + while(packets) { + *(uint64_t*)(stream->buf + (stream->tx_len - 16)) = stream->flow_seq; + /* Send packet ... */ + if (sendto(thread->socket.fd_tx, stream->buf, stream->tx_len, 0, (struct sockaddr*)&thread->socket.addr, sizeof(struct sockaddr_ll)) <0 ) { + LOG(IO, "Thread: Sendto failed with errno: %i\n", errno); + thread->sendto_failed++; + packets = 0; + } else { + stream->packets_tx++; + stream->send_window_packets++; + stream->flow_seq++; + packets--; + thread->packets_tx++; + thread->bytes_tx += stream->tx_len; + } + } + pthread_mutex_unlock(&thread->mutex); + pthread_mutex_unlock(&stream->thread.mutex); +} + void bbl_stream_rate_job (timer_s *timer) { bbl_stream *stream = timer->data; @@ -955,20 +1147,28 @@ bbl_stream_rate_job (timer_s *timer) { bbl_compute_avg_rate(&stream->rate_packets_rx, stream->packets_rx); } +void +bbl_stream_rate_job_threaded (timer_s *timer) { + bbl_stream *stream = timer->data; + pthread_mutex_lock(&stream->thread.mutex); + bbl_compute_avg_rate(&stream->rate_packets_tx, stream->packets_tx); + bbl_compute_avg_rate(&stream->rate_packets_rx, stream->packets_rx); + pthread_mutex_unlock(&stream->thread.mutex); +} + bool bbl_stream_add(bbl_ctx_s *ctx, bbl_access_config_s *access_config, bbl_session_s *session) { bbl_stream_config *config; bbl_stream *stream; - bbl_stream *session_stream; + bbl_stream *session_stream; + bbl_stream_thread *thread; dict_insert_result result; time_t timer_sec = 0; long timer_nsec = 0; - pthread_t thread_id; - config = ctx->config.stream_config; /* * @@ -997,11 +1197,9 @@ bbl_stream_add(bbl_ctx_s *ctx, bbl_access_config_s *access_config, bbl_session_s return false; } - if(config->pps == 1) { - timer_sec = 1; - } else { - timer_nsec = 1000000000 / config->pps; - } + timer_nsec = SEC / config->pps; + timer_sec = timer_nsec / 1000000000; + timer_nsec = timer_nsec % 1000000000; if(config->direction & STREAM_DIRECTION_UP) { stream = calloc(1, sizeof(bbl_stream)); @@ -1029,14 +1227,20 @@ bbl_stream_add(bbl_ctx_s *ctx, bbl_access_config_s *access_config, bbl_session_s session->stream = stream; } if(config->threaded) { - pthread_create(&thread_id, NULL, bbl_stream_tx_thread, (void *)stream); - timer_add_periodic(&ctx->timer_root, &stream->timer, config->name, 1, 0, stream, &bbl_stream_tx_thread_counter_sync); + thread = bbl_stream_add_to_thread(ctx, config->thread_group, stream); + if(!thread) { + LOG(ERROR, "Failed to add stream %s to thread\n", config->name); + free(stream); + return false; + } + timer_add_periodic(&thread->timer_root, &stream->timer, config->name, timer_sec, timer_nsec, stream, &bbl_stream_tx_job_threaded); + timer_add_periodic(&thread->timer_root, &stream->timer_rate, "Threaded Rate Computation", 1, 0, stream, &bbl_stream_rate_job_threaded); } else { timer_add_periodic(&ctx->timer_root, &stream->timer, config->name, timer_sec, timer_nsec, stream, &bbl_stream_tx_job); + timer_add_periodic(&ctx->timer_root, &stream->timer_rate, "Rate Computation", 1, 0, stream, &bbl_stream_rate_job); } - timer_add_periodic(&ctx->timer_root, &stream->timer_rate, "Rate Computation", 1, 0, stream, &bbl_stream_rate_job); ctx->stats.stream_traffic_flows++; - LOG(DEBUG, "Traffic stream %s added in upstream with %u PPS (timer: %lu sec %lu nsec)\n", config->name, config->pps, timer_sec, timer_nsec); + LOG(DEBUG, "Traffic stream %s added in upstream with %lf PPS (timer: %lu sec %lu nsec)\n", config->name, config->pps, timer_sec, timer_nsec); } if(config->direction & STREAM_DIRECTION_DOWN) { stream = calloc(1, sizeof(bbl_stream)); @@ -1064,16 +1268,21 @@ bbl_stream_add(bbl_ctx_s *ctx, bbl_access_config_s *access_config, bbl_session_s session->stream = stream; } if(config->threaded) { - pthread_create(&thread_id, NULL, bbl_stream_tx_thread, (void *)stream); - timer_add_periodic(&ctx->timer_root, &stream->timer, config->name, 1, 0, stream, &bbl_stream_tx_thread_counter_sync); + thread = bbl_stream_add_to_thread(ctx, config->thread_group, stream); + if(!thread) { + LOG(ERROR, "Failed to add stream %s to thread\n", config->name); + free(stream); + return false; + } + timer_add_periodic(&thread->timer_root, &stream->timer, config->name, timer_sec, timer_nsec, stream, &bbl_stream_tx_job_threaded); + timer_add_periodic(&thread->timer_root, &stream->timer_rate, "Threaded Rate Computation", 1, 0, stream, &bbl_stream_rate_job_threaded); } else { timer_add_periodic(&ctx->timer_root, &stream->timer, config->name, timer_sec, timer_nsec, stream, &bbl_stream_tx_job); + timer_add_periodic(&ctx->timer_root, &stream->timer_rate, "Rate Computation", 1, 0, stream, &bbl_stream_rate_job); } - timer_add_periodic(&ctx->timer_root, &stream->timer_rate, "Rate Computation", 1, 0, stream, &bbl_stream_rate_job); ctx->stats.stream_traffic_flows++; - LOG(DEBUG, "Traffic stream %s added in downstream with %u PPS (timer %lu sec %lu nsec)\n", config->name, config->pps, timer_sec, timer_nsec); + LOG(DEBUG, "Traffic stream %s added in downstream with %lf PPS (timer %lu sec %lu nsec)\n", config->name, config->pps, timer_sec, timer_nsec); } - timer_smear_bucket(&ctx->timer_root, timer_sec, timer_nsec); } config = config->next; } @@ -1086,6 +1295,7 @@ bbl_stream_raw_add(bbl_ctx_s *ctx) { bbl_stream_config *config; bbl_stream *stream; + bbl_stream_thread *thread; bbl_interface_s *network_if; dict_insert_result result; @@ -1093,8 +1303,6 @@ bbl_stream_raw_add(bbl_ctx_s *ctx) { time_t timer_sec = 0; long timer_nsec = 0; - pthread_t thread_id; - config = ctx->config.stream_config; while(config) { @@ -1103,11 +1311,11 @@ bbl_stream_raw_add(bbl_ctx_s *ctx) { if(!network_if) { return false; } - if(config->pps == 1) { - timer_sec = 1; - } else { - timer_nsec = 1000000000 / config->pps; - } + + timer_nsec = SEC / config->pps; + timer_sec = timer_nsec / 1000000000; + timer_nsec = timer_nsec % 1000000000; + if(config->direction & STREAM_DIRECTION_DOWN) { stream = calloc(1, sizeof(bbl_stream)); stream->flow_id = ctx->flow_id++; @@ -1124,16 +1332,21 @@ bbl_stream_raw_add(bbl_ctx_s *ctx) { } *result.datum_ptr = stream; if(config->threaded) { - pthread_create(&thread_id, NULL, bbl_stream_tx_thread, (void *)stream); - timer_add_periodic(&ctx->timer_root, &stream->timer, config->name, 1, 0, stream, &bbl_stream_tx_thread_counter_sync); + thread = bbl_stream_add_to_thread(ctx, config->thread_group, stream); + if(!thread) { + LOG(ERROR, "Failed to add stream %s to thread\n", config->name); + free(stream); + return false; + } + timer_add_periodic(&thread->timer_root, &stream->timer, config->name, timer_sec, timer_nsec, stream, &bbl_stream_tx_job_threaded); + timer_add_periodic(&thread->timer_root, &stream->timer_rate, "Threaded Rate Computation", 1, 0, stream, &bbl_stream_rate_job_threaded); } else { timer_add_periodic(&ctx->timer_root, &stream->timer, config->name, timer_sec, timer_nsec, stream, &bbl_stream_tx_job); + timer_add_periodic(&ctx->timer_root, &stream->timer_rate, "Rate Computation", 1, 0, stream, &bbl_stream_rate_job); } - timer_add_periodic(&ctx->timer_root, &stream->timer_rate, "Rate Computation", 1, 0, stream, &bbl_stream_rate_job); ctx->stats.stream_traffic_flows++; - LOG(DEBUG, "RAW traffic stream %s added in downstream with %u PPS (timer %lu sec %lu nsec)\n", config->name, config->pps, timer_sec, timer_nsec); + LOG(DEBUG, "RAW traffic stream %s added in downstream with %lf PPS (timer %lu sec %lu nsec)\n", config->name, config->pps, timer_sec, timer_nsec); } - timer_smear_bucket(&ctx->timer_root, timer_sec, timer_nsec); } config = config->next; } diff --git a/src/bbl_stream.h b/src/bbl_stream.h index 38eaf0bf..d3b80d77 100644 --- a/src/bbl_stream.h +++ b/src/bbl_stream.h @@ -1,12 +1,11 @@ /* - * BNG Blaster (BBL) - Streams + * BNG Blaster (BBL) - Traffic Streams * * Christian Giese, Match 2021 * * Copyright (C) 2020-2021, RtBrick, Inc. * SPDX-License-Identifier: BSD-3-Clause */ - #ifndef __BBL_STREAM_H__ #define __BBL_STREAM_H__ @@ -30,7 +29,7 @@ typedef struct bbl_stream_config_ bbl_stream_type_t type; bbl_stream_direction_t direction; - uint32_t pps; + double pps; uint16_t length; uint8_t priority; /* IPv4 TOS or IPv6 TC */ uint8_t vlan_priority; @@ -43,7 +42,9 @@ typedef struct bbl_stream_config_ char *a10nsp_interface; bool threaded; - void *next; /* next bbl_stream_config */ + uint8_t thread_group; + + bbl_stream_config *next; /* Next stream config */ } bbl_stream_config; typedef struct bbl_stream_ @@ -87,19 +88,81 @@ typedef struct bbl_stream_ bbl_rate_s rate_packets_tx; bbl_rate_s rate_packets_rx; - void *next; /* next stream of same session */ + bbl_stream *next; /* Next stream of same session */ + + /* Attributes used for threaded streams only! */ + struct { + bbl_stream_thread *thread; + bbl_stream *next; /* Next stream in same thread */ + pthread_mutex_t mutex; + bool can_send; + } thread; } bbl_stream; +/* Structure for traffic stream threads + * with one or more streams. */ +typedef struct bbl_stream_thread_ +{ + /* The thread-group allows to assign + * multiple streams to one thread. The + * group zero has the special meaning of + * one thread per stream. */ + uint8_t thread_group; + pthread_t thread_id; + pthread_mutex_t mutex; + + /* True if thread is active! */ + bool active; + + /* Root for thread local timers */ + struct timer_root_ timer_root; + + /* Timer for synchronice job of thread + * counters with main counters. */ + struct timer_ *sync_timer; + + /* TX interface */ + bbl_interface_s *interface; + + /* TX interface file RAW socket */ + struct { + int fd_tx; + struct sockaddr_ll addr; + } socket; + + uint32_t stream_count; /* Number of streams in group */ + bbl_stream *stream; /* First stream in group */ + bbl_stream *stream_tail; /* Last stream in group */ + + /* Thread counters ... */ + + uint64_t packets_tx; + uint64_t packets_tx_last_sync; + uint64_t bytes_tx; + uint64_t bytes_tx_last_sync; + + uint64_t sendto_failed; + uint64_t sendto_failed_last_sync; + + void *next; /* Next stream thread */ +} bbl_stream_thread; + bool bbl_stream_add(bbl_ctx_s *ctx, bbl_access_config_s *access_config, bbl_session_s *session); bool bbl_stream_raw_add(bbl_ctx_s *ctx); +bool +bbl_stream_start_threads(bbl_ctx_s *ctx); + void -bbl_stream_tx_job (timer_s *timer); +bbl_stream_stop_threads(bbl_ctx_s *ctx); + +void +bbl_stream_tx_job(timer_s *timer); json_t * bbl_stream_json(bbl_stream *stream); -#endif +#endif \ No newline at end of file diff --git a/src/bbl_timer.c b/src/bbl_timer.c index 6b985ff4..d10759ba 100644 --- a/src/bbl_timer.c +++ b/src/bbl_timer.c @@ -16,7 +16,7 @@ #include "bbl.h" #include "bbl_timer.h" -/* +/** * Add two timestamps x and y, storing the result in result. */ void @@ -35,7 +35,7 @@ timespec_add (struct timespec *result, struct timespec *x, struct timespec *y) } } -/* +/** * Subtract the timestamps x from y, storing the result in result. */ void @@ -64,7 +64,7 @@ timespec_sub (struct timespec *result, struct timespec *x, struct timespec *y) } } -/* +/** * Format a timestamp in one of four buffers. * This way we can format upto 4 timespecs in one printf() call. */ @@ -83,7 +83,7 @@ timespec_format (struct timespec *x) return ret; } -/* +/** * Enqueue a timer for change processing. */ void @@ -103,7 +103,7 @@ timer_change (timer_s *timer) timer->on_change_list = true; } -void +static void timer_enqueue_bucket (timer_root_s *root, timer_s *timer, time_t sec, long nsec) { timer_bucket_s *timer_bucket; @@ -146,6 +146,64 @@ timer_enqueue_bucket (timer_root_s *root, timer_s *timer, time_t sec, long nsec) } /* + * Dequeue a timer from its timer_bucket. + */ +static void +timer_dequeue_bucket (timer_s *timer) +{ + timer_root_s *timer_root; + timer_bucket_s *timer_bucket; + + timer_bucket = timer->timer_bucket; + timer_root = timer_bucket->timer_root; + + CIRCLEQ_REMOVE(&timer_bucket->timer_qhead, timer, timer_qnode); + timer_bucket->timers--; + timer->timer_bucket = NULL; + + /* + * If the last timer of a bucket is gone, remove the bucket as well. + */ + if (!timer_bucket->timers) { + CIRCLEQ_REMOVE(&timer_root->timer_bucket_qhead, timer_bucket, timer_bucket_qnode); + + LOG(TIMER_DETAIL, " Delete timer bucket %lu.%06lus\n", + timer_bucket->sec, timer_bucket->nsec/1000); + + free(timer_bucket); + timer_root->buckets--; + } +} + +static void +timer_requeue (timer_s *timer, time_t sec, long nsec) +{ + timer_root_s *timer_root; + timer_bucket_s *timer_bucket; + + timer_bucket = timer->timer_bucket; + timer_root = timer_bucket->timer_root; + + timer_set_expire(timer, sec, nsec); + + /* + * If the expiration {sec,nsec} matches the bucket, then simply + * timer dequeue and enqueue to keep correct temporal ordering. + * If there is no match, do a slightly more expensive + * bucket dequeue and enqueue. + */ + if (timer_bucket->sec == sec && timer_bucket->nsec == nsec) { + CIRCLEQ_REMOVE(&timer_bucket->timer_qhead, timer, timer_qnode); + CIRCLEQ_INSERT_TAIL(&timer_bucket->timer_qhead, timer, timer_qnode); + } else { + timer_dequeue_bucket(timer); + timer_enqueue_bucket(timer_root, timer, sec, nsec); + } + + LOG(TIMER_DETAIL, " Reset %s timer, expire in %lu.%06lus\n", timer->name, sec, nsec/1000); +} + +/** * Smear all the timer of a given bucket to expire equi-distant. * Call this function periodically to avoid clustering of timers. */ @@ -179,8 +237,10 @@ timer_smear_bucket (timer_root_s *root, time_t sec, long nsec) step.tv_nsec = step_nsec - (step.tv_sec * 1e9); LOG(TIMER_DETAIL, "Smear %u timers in bucket %lu.%06lus\n", timer_bucket->timers, sec, nsec); - LOG(TIMER_DETAIL, "Now %s, last expire %s, step %s\n", timespec_format(&now), - timespec_format(&last_timer->expire), timespec_format(&step)); + LOG(TIMER_DETAIL, "Now %lu.%06lus, last expire %lu.%06lus, step %lu.%06lus\n", + now.tv_sec, now.tv_nsec / 1000, + last_timer->expire.tv_sec, last_timer->expire.tv_nsec / 1000, + step.tv_sec, step.tv_nsec / 1000); /* * Now walk all timers and space them apart. @@ -188,75 +248,65 @@ timer_smear_bucket (timer_root_s *root, time_t sec, long nsec) CIRCLEQ_FOREACH(timer, &timer_bucket->timer_qhead, timer_qnode) { timespec_add(&timer->expire, &now, &step); now = timer->expire; - LOG(TIMER_DETAIL, " Smear %s -> expire %s\n", timer->name, timespec_format(&timer->expire)); + LOG(TIMER_DETAIL, " Smear %s -> expire %lu.%06lus\n", timer->name, + timer->expire.tv_sec, timer->expire.tv_nsec / 1000); } return; } } -/* - * Dequeue a timer from its timer_bucket. +/** + * Smear all the timer of all buckets to expire equi-distant. */ void -timer_dequeue_bucket (timer_s *timer) +timer_smear_all_buckets (timer_root_s *root) { - timer_root_s *timer_root; timer_bucket_s *timer_bucket; - - timer_bucket = timer->timer_bucket; - timer_root = timer_bucket->timer_root; - - CIRCLEQ_REMOVE(&timer_bucket->timer_qhead, timer, timer_qnode); - timer_bucket->timers--; - timer->timer_bucket = NULL; + timer_s *timer, *last_timer; + struct timespec now, diff, step; + long step_nsec; /* - * If the last timer of a bucket is gone, remove the bucket as well. + * Find the bucket for smearing. */ - if (!timer_bucket->timers) { - CIRCLEQ_REMOVE(&timer_root->timer_bucket_qhead, timer_bucket, timer_bucket_qnode); + CIRCLEQ_FOREACH(timer_bucket, &root->timer_bucket_qhead, timer_bucket_qnode) { + /* + * Found the bucket. Next compute the timespan between now and last timer. + */ + last_timer = CIRCLEQ_LAST(&timer_bucket->timer_qhead); + if (!last_timer) { + return; + } + clock_gettime(CLOCK_MONOTONIC, &now); + timespec_sub(&diff, &last_timer->expire, &now); + step_nsec = (diff.tv_sec * 1e9 + diff.tv_nsec) / (timer_bucket->timers); /* calculate smear step */ + step.tv_sec = step_nsec / 1e9; + step.tv_nsec = step_nsec - (step.tv_sec * 1e9); - LOG(TIMER_DETAIL, " Delete timer bucket %lu.%06lus\n", - timer_bucket->sec, timer_bucket->nsec/1000); + LOG(TIMER_DETAIL, "Smear %u timers in bucket %lu.%06lus\n", timer_bucket->timers, timer_bucket->sec, timer_bucket->nsec); + LOG(TIMER_DETAIL, "Now %lu.%06lus, last expire %lu.%06lus, step %lu.%06lus\n", + now.tv_sec, now.tv_nsec / 1000, + last_timer->expire.tv_sec, last_timer->expire.tv_nsec / 1000, + step.tv_sec, step.tv_nsec / 1000); - free(timer_bucket); - timer_root->buckets--; + /* + * Now walk all timers and space them apart. + */ + CIRCLEQ_FOREACH(timer, &timer_bucket->timer_qhead, timer_qnode) { + timespec_add(&timer->expire, &now, &step); + now = timer->expire; + LOG(TIMER_DETAIL, " Smear %s -> expire %lu.%06lus\n", timer->name, + last_timer->expire.tv_sec, last_timer->expire.tv_nsec / 1000); + } + return; } } -void -timer_requeue (timer_s *timer, time_t sec, long nsec) -{ - timer_root_s *timer_root; - timer_bucket_s *timer_bucket; - - timer_bucket = timer->timer_bucket; - timer_root = timer_bucket->timer_root; - - timer_set_expire(timer, sec, nsec); - - /* - * If the expiration {sec,nsec} matches the bucket, then simply - * timer dequeue and enqueue to keep correct temporal ordering. - * If there is no match, do a slightly more expensive - * bucket dequeue and enqueue. - */ - if (timer_bucket->sec == sec && timer_bucket->nsec == nsec) { - CIRCLEQ_REMOVE(&timer_bucket->timer_qhead, timer, timer_qnode); - CIRCLEQ_INSERT_TAIL(&timer_bucket->timer_qhead, timer, timer_qnode); - } else { - timer_dequeue_bucket(timer); - timer_enqueue_bucket(timer_root, timer, sec, nsec); - } - - LOG(TIMER_DETAIL, " Reset %s timer, expire in %lu.%06lus\n", timer->name, sec, nsec/1000); -} - -/* +/** * We do not delete timers, but rather dequeue them and move them to * the garbage collection queue, where they may get recycled. */ -void +static void timer_del_internal (timer_s *timer) { timer_bucket_s *timer_bucket; @@ -276,7 +326,7 @@ timer_del_internal (timer_s *timer) } } -/* +/** * Mark a timer for deletion. */ void @@ -288,7 +338,7 @@ timer_del (timer_s *timer) } } -/* +/** * Set timer expiration. */ void @@ -309,10 +359,10 @@ timer_set_expire (timer_s *timer, time_t sec, long nsec) timer->expired = false; } -/* +/** * Deferred processing of all timers. */ -void +static void timer_process_changes (timer_root_s *root) { timer_s *timer; @@ -347,7 +397,7 @@ timer_process_changes (timer_root_s *root) } } -/* +/** * Enqueue a timer with a given callback function onto the hierarchical timer list. */ void @@ -369,9 +419,9 @@ timer_add (timer_root_s *root, if (timer) { timer_requeue(timer, sec, nsec); /* - * Update data and cb if there was a change. - * Do the reformatting of name only during a change due to snprintf() being expensive. - */ + * Update data and cb if there was a change. + * Do the reformatting of name only during a change due to snprintf() being expensive. + */ if (timer->data != data || timer->cb != cb) { snprintf(timer->name, sizeof(timer->name), "%s", name); timer->data = data; @@ -433,7 +483,7 @@ timer_add_periodic (timer_root_s *root, timer_s **ptimer, char *name, } } -/* +/** * Compare two timespecs. * * return -1 if ts1 is older than ts2 @@ -462,8 +512,10 @@ timespec_compare (struct timespec *ts1, struct timespec *ts2) return 0; } -/* +/** * Process the timer queue. + * + * @param root timer root */ void timer_walk (timer_root_s *root) @@ -473,138 +525,132 @@ timer_walk (timer_root_s *root) struct timespec now, min, sleep, rem; int res; - while (true) { + /* + * No buckets filled and we're done. + */ + if (CIRCLEQ_EMPTY(&root->timer_bucket_qhead)) { + return; + } + + clock_gettime(CLOCK_MONOTONIC, &now); + LOG(TIMER_DETAIL, "Walk timer queue, now %lu.%06lus\n", + now.tv_sec, now.tv_nsec / 1000); + min.tv_sec = 0; + min.tv_nsec = 0; + + /* + * Walk all buckets. + */ + CIRCLEQ_FOREACH(timer_bucket, &root->timer_bucket_qhead, timer_bucket_qnode) { + + LOG(TIMER_DETAIL, " Checking timer bucket %lu.%06lus\n", + timer_bucket->sec, timer_bucket->nsec/1000); /* - * No buckets filled and we're done. + * First pass. Call into expired nodes. */ - if (CIRCLEQ_EMPTY(&root->timer_bucket_qhead)) { - return; - } - - clock_gettime(CLOCK_MONOTONIC, &now); - LOG(TIMER_DETAIL, "Walk timer queue, now %s\n", timespec_format(&now)); - min.tv_sec = 0; - min.tv_nsec = 0; - - /* - * Walk all buckets. - */ - CIRCLEQ_FOREACH(timer_bucket, &root->timer_bucket_qhead, timer_bucket_qnode) { - - LOG(TIMER_DETAIL, " Checking timer bucket %lu.%06lus\n", - timer_bucket->sec, timer_bucket->nsec/1000); + CIRCLEQ_FOREACH(timer, &timer_bucket->timer_qhead, timer_qnode) { /* - * First pass. Call into expired nodes. + * Hitting the first non-expired timer means + * we're done processing this buckets queue. */ - CIRCLEQ_FOREACH(timer, &timer_bucket->timer_qhead, timer_qnode) { + if (timespec_compare(&timer->expire, &now) == 1) { + break; + } - /* - * Hitting the first non-expired timer means - * we're done processing this buckets queue. - */ - if ((timespec_compare(&timer->expire, &now) == 1)) { - break; - } + /* + * Everything from here one is expired. + */ + timer->expired = true; - /* - * Everything from here one is expired. - */ - timer->expired = true; - - /* Execute callback */ - if (timer->cb) { - LOG(TIMER_DETAIL, " Firing %s timer\n", timer->name); + /* Execute callback */ + if (timer->cb) { + LOG(TIMER_DETAIL, " Firing %s timer\n", timer->name); (*timer->cb)(timer); - } + } - if (timer->periodic) { - /* - * Periodic timers are simple de-queued and - * re-inserted at the tail of this buckets queue. - */ - timer_change(timer); - } else { - /* - * Everything else gets deleted. - */ - timer_del(timer); - } + if (timer->periodic) { + /* + * Periodic timers are simple de-queued and + * re-inserted at the tail of this buckets queue. + */ + timer_change(timer); + } else { + /* + * Everything else gets deleted. + */ + timer_del(timer); } } + } - /* - * Process all changes from the last timer run. - */ - timer_process_changes(root); + /* + * Process all changes from the last timer run. + */ + timer_process_changes(root); - /* - * Second pass. Figure out min sleep time. - */ - CIRCLEQ_FOREACH(timer_bucket, &root->timer_bucket_qhead, timer_bucket_qnode) { - CIRCLEQ_FOREACH(timer, &timer_bucket->timer_qhead, timer_qnode) { + /* + * Second pass. Figure out min sleep time. + */ + CIRCLEQ_FOREACH(timer_bucket, &root->timer_bucket_qhead, timer_bucket_qnode) { + CIRCLEQ_FOREACH(timer, &timer_bucket->timer_qhead, timer_qnode) { - /* - * Ignore deleted timers that wait for change processing. - */ - if (timer->delete) { - continue; - } + /* + * Ignore deleted timers that wait for change processing. + */ + if (timer->delete) { + continue; + } - /* - * First timer in the queue becomes the actual minimum. - */ - if (min.tv_sec == 0 && min.tv_nsec == 0) { - min.tv_sec = timer->expire.tv_sec; - min.tv_nsec = timer->expire.tv_nsec; - } + /* + * First timer in the queue becomes the actual minimum. + */ + if (min.tv_sec == 0 && min.tv_nsec == 0) { + min.tv_sec = timer->expire.tv_sec; + min.tv_nsec = timer->expire.tv_nsec; + } - /* - * Find the min timer. - */ - if (timespec_compare(&timer->expire, &min) == -1) { - min.tv_sec = timer->expire.tv_sec; - min.tv_nsec = timer->expire.tv_nsec; - LOG(TIMER_DETAIL, "New minimum sleep (%s) timer, found %s\n", - timer->name, timespec_format(&min)); - } + /* + * Find the min timer. + */ + if (timespec_compare(&timer->expire, &min) == -1) { + min.tv_sec = timer->expire.tv_sec; + min.tv_nsec = timer->expire.tv_nsec; + LOG(TIMER_DETAIL, "New minimum sleep (%s) timer, found %lu.%06lus\n", + timer->name, + min.tv_sec, min.tv_nsec / 1000); + } - /* - * Hitting the first non-expired timer means - * we're done processing this buckets queue. - */ - if ((timespec_compare(&timer->expire, &now) == 1)) { - break; - } + /* + * Hitting the first non-expired timer means + * we're done processing this buckets queue. + */ + if (timespec_compare(&timer->expire, &now) == 1) { + break; } } + } - /* - * Calculate the sleep timer. - */ - LOG(TIMER_DETAIL, " Now %s\n", timespec_format(&now)); - LOG(TIMER_DETAIL, " Min %s\n", timespec_format(&min)); + /* + * Calculate the sleep timer. + */ + LOG(TIMER_DETAIL, " Now %lu.%06lus\n", now.tv_sec, now.tv_nsec / 1000); + LOG(TIMER_DETAIL, " Min %lu.%06lus\n", min.tv_sec, min.tv_nsec / 1000); - clock_gettime(CLOCK_MONOTONIC, &now); - if (timespec_compare(&now, &min) == -1) { - timespec_sub(&sleep, &min, &now); - } else { - //sleep.tv_sec = 0; - //sleep.tv_nsec = 1 * MSEC; /* sleep time is negative, sleep at least some time */ - continue; - } + clock_gettime(CLOCK_MONOTONIC, &now); + if (timespec_compare(&now, &min) == -1) { + timespec_sub(&sleep, &min, &now); - LOG(TIMER_DETAIL, " Sleep %s\n", timespec_format(&sleep)); + LOG(TIMER_DETAIL, " Sleep %lu.%06lus\n", sleep.tv_sec, sleep.tv_nsec / 1000); res = nanosleep(&sleep, &rem); if (res == -1) { LOG(TIMER, " nanosleep(): error %s (%d)\n", strerror(errno), errno); - return; } } } -/* +/** * Init a timer root. */ void @@ -628,9 +674,9 @@ timer_flush_root (timer_root_s *timer_root) * First step. Walk all timers and move them onto the GC thread. */ CIRCLEQ_FOREACH(timer_bucket, &timer_root->timer_bucket_qhead, timer_bucket_qnode) { - CIRCLEQ_FOREACH(timer, &timer_bucket->timer_qhead, timer_qnode) { - timer_del(timer); - } + CIRCLEQ_FOREACH(timer, &timer_bucket->timer_qhead, timer_qnode) { + timer_del(timer); + } } timer_process_changes(timer_root); @@ -640,8 +686,8 @@ timer_flush_root (timer_root_s *timer_root) while (!CIRCLEQ_EMPTY(&timer_root->timer_gc_qhead)) { timer = CIRCLEQ_FIRST(&timer_root->timer_gc_qhead); CIRCLEQ_REMOVE(&timer_root->timer_gc_qhead, timer, timer_qnode); - timer_root->gc--; - free(timer); + timer_root->gc--; + free(timer); } } @@ -690,4 +736,4 @@ timer_test (void *ctx) timer_smear_bucket(&root, 10, 0); timer_walk(&root); -} +} \ No newline at end of file diff --git a/src/bbl_timer.h b/src/bbl_timer.h index 9503cc68..97022ecd 100644 --- a/src/bbl_timer.h +++ b/src/bbl_timer.h @@ -10,7 +10,8 @@ #ifndef __BBL_TIMER_H__ #define __BBL_TIMER_H__ -#define MSEC 1000*1000 /* 1 million nanoseconds */ +#define MSEC 1000000 /* 1 million nanoseconds == 1 msec */ +#define SEC 1000000000 /* 1 billion nanoseconds == 1 sec */ /* * Top level data structure for timers. @@ -80,8 +81,8 @@ void timer_add(timer_root_s *, timer_s **, char *, time_t , long , void *, void void timer_add_periodic(timer_root_s *, timer_s **, char *, time_t , long , void *, void *); void timer_del(timer_s *); void timer_smear_bucket(timer_root_s *, time_t, long); +void timer_smear_all_buckets (timer_root_s *root); void timer_walk(struct timer_root_ *); - void timespec_add(struct timespec *, struct timespec *, struct timespec *); void timespec_sub(struct timespec *, struct timespec *, struct timespec *); diff --git a/src/bbl_tx.c b/src/bbl_tx.c index fcc64630..e4438b5a 100644 --- a/src/bbl_tx.c +++ b/src/bbl_tx.c @@ -538,12 +538,12 @@ bbl_encode_packet_icmpv6_rs (bbl_session_s *session) { bbl_pppoe_session_t pppoe = {0}; bbl_ipv6_t ipv6 = {0}; bbl_icmpv6_t icmpv6 = {0}; + uint8_t mac[ETH_ADDR_LEN]; interface = session->interface; ctx = interface->ctx; interface->stats.icmpv6_tx++; - eth.dst = session->server_mac; eth.src = session->client_mac; eth.qinq = session->access_config->qinq; eth.vlan_outer = session->vlan_key.outer_vlan_id; @@ -555,13 +555,17 @@ bbl_encode_packet_icmpv6_rs (bbl_session_s *session) { if(session->ip6cp_state != BBL_PPP_OPENED) { return WRONG_PROTOCOL_STATE; } + eth.dst = session->server_mac; eth.type = ETH_TYPE_PPPOE_SESSION; eth.next = &pppoe; + pppoe.session_id = session->pppoe_session_id; pppoe.protocol = PROTOCOL_IPV6; pppoe.next = &ipv6; } else { /* IPoE */ + ipv6_multicast_mac(ipv6_multicast_all_routers, mac); + eth.dst = mac; eth.type = ETH_TYPE_IPV6; eth.next = &ipv6; } @@ -616,6 +620,7 @@ bbl_encode_packet_dhcpv6_request (bbl_session_s *session) { bbl_udp_t udp = {0}; bbl_dhcpv6_t dhcpv6 = {0}; access_line_t access_line = {0}; + uint8_t mac[ETH_ADDR_LEN]; if(session->dhcpv6_state == BBL_DHCP_INIT || session->dhcpv6_state == BBL_DHCP_BOUND) { @@ -649,7 +654,8 @@ bbl_encode_packet_dhcpv6_request (bbl_session_s *session) { pppoe.next = &ipv6; } else { /* IPoE */ - eth.dst = (void*)ipv6_multicast_mac_dhcp; + ipv6_multicast_mac(ipv6_multicast_all_dhcp, mac); + eth.dst = mac; eth.vlan_outer_priority = ctx->config.dhcpv6_vlan_priority; eth.type = ETH_TYPE_IPV6; eth.next = &ipv6; @@ -1628,6 +1634,7 @@ bbl_encode_interface_packet (bbl_interface_s *interface, uint8_t *buf, uint16_t bbl_arp_t arp = {0}; bbl_ipv6_t ipv6 = {0}; bbl_icmpv6_t icmpv6 = {0}; + uint8_t mac[ETH_ADDR_LEN]; *len = 0; @@ -1649,15 +1656,12 @@ bbl_encode_interface_packet (bbl_interface_s *interface, uint8_t *buf, uint16_t result = encode_ethernet(buf, len, ð); } else if(interface->send_requests & BBL_IF_SEND_ICMPV6_NS) { interface->send_requests &= ~BBL_IF_SEND_ICMPV6_NS; - if(*(uint32_t*)interface->gateway_mac == 0) { - eth.dst = (uint8_t*)ipv6_multicast_mac; - } else { - eth.dst = interface->gateway_mac; - } + ipv6_multicast_mac(interface->gateway6_solicited_node_multicast, mac); + eth.dst = mac; eth.type = ETH_TYPE_IPV6; eth.next = &ipv6; ipv6.src = interface->ip6.address; - ipv6.dst = interface->gateway6.address; + ipv6.dst = interface->gateway6_solicited_node_multicast; ipv6.protocol = IPV6_NEXT_HEADER_ICMPV6; ipv6.next = &icmpv6; ipv6.ttl = 255; @@ -1778,7 +1782,7 @@ bbl_tx(bbl_ctx_s *ctx, bbl_interface_s *interface, uint8_t *buf, uint16_t *len) return PROTOCOL_SUCCESS; } /* Write Multicast frames. */ - if(ctx->config.send_multicast_traffic && ctx->config.igmp_group_count && ctx->multicast_traffic ) { + if(ctx->config.send_multicast_traffic && ctx->config.igmp_group_count && ctx->multicast_traffic) { if(interface->mc_packet_cursor < ctx->config.igmp_group_count) { memcpy(buf, interface->mc_packets + (interface->mc_packet_cursor*interface->mc_packet_len), interface->mc_packet_len); *(uint64_t*)(buf + (interface->mc_packet_len - 16)) = interface->mc_packet_seq; @@ -1786,10 +1790,11 @@ bbl_tx(bbl_ctx_s *ctx, bbl_interface_s *interface, uint8_t *buf, uint16_t *len) *(uint32_t*)(buf + (interface->mc_packet_len - 4)) = interface->tx_timestamp.tv_nsec; *len = interface->mc_packet_len; interface->mc_packet_cursor++; + interface->stats.mc_tx++; return PROTOCOL_SUCCESS; } else { /* This must be the last send operation in this function to fill up remaining slots - * with multicast traffic but all other types of traffic have priority. */ + * with multicast traffic but all other types of traffic have priority. */ interface->mc_packet_cursor = 0; interface->mc_packet_seq++; } diff --git a/src/bbl_utils.c b/src/bbl_utils.c index 1584d2a3..bea68c8c 100644 --- a/src/bbl_utils.c +++ b/src/bbl_utils.c @@ -164,3 +164,31 @@ replace_substring (const char* source, *result_pos = '\0'; return result; } + +/** + * ipv4_multicast_mac + * + * @param ipv4 IPv4 multicast address + * @param mac target buffer to store multicast MAC + */ +void +ipv4_multicast_mac(const uint32_t ipv4, uint8_t* mac) { + *(uint32_t*)(&mac[2]) = ipv4; + mac[0] = 0x01; + mac[1] = 0x00; + mac[2] = 0x5e; + mac[3] &= 0x7f; +} + +/** + * ipv6_multicast_mac + * + * @param ipv6 IPv6 multicast address + * @param mac target buffer to store multicast MAC + */ +void +ipv6_multicast_mac(const uint8_t *ipv6, uint8_t* mac) { + *(uint32_t*)(&mac[2]) =*(uint32_t*)(&ipv6[12]); + mac[0] = 0x33; + mac[1] = 0x33; +} \ No newline at end of file diff --git a/src/bbl_utils.h b/src/bbl_utils.h index 7ebab36e..40651a68 100644 --- a/src/bbl_utils.h +++ b/src/bbl_utils.h @@ -21,7 +21,9 @@ char *format_mac_address(uint8_t *mac); char *format_ipv4_address(uint32_t *addr4); char *format_ipv6_address(ipv6addr_t *addr6); char *format_ipv6_prefix(ipv6_prefix *addr6); - +void ipv4_multicast_mac(const uint32_t ipv4, uint8_t* mac); +void ipv6_multicast_mac(const uint8_t *ipv6, uint8_t* mac); char *replace_substring (const char* s, const char* old, const char* new); const char *val2key (struct keyval_ *keyval, uint val); -#endif + +#endif \ No newline at end of file diff --git a/test/utils.c b/test/utils.c index 7d368b08..512136e3 100644 --- a/test/utils.c +++ b/test/utils.c @@ -77,6 +77,34 @@ test_replace_substring(void **unused) { assert_string_equal(replace_substring("1234{long-variable-name}567890", "{long-variable-name}", ""), "1234567890"); } +static void +test_ipv4_multicast_mac(void **unused) { + (void) unused; + + uint32_t ipv4; + uint8_t mac[ETH_ADDR_LEN] = {0}; + uint8_t mac_expected[ETH_ADDR_LEN] = {0x01, 0x00, 0x5e, 0x01, 0x02, 0x03}; + + inet_pton(AF_INET, "239.1.2.3", &ipv4); + ipv4_multicast_mac(ipv4, mac); + + assert_memory_equal(mac_expected, mac, ETH_ADDR_LEN); +} + +static void +test_ipv6_multicast_mac(void **unused) { + (void) unused; + + ipv6addr_t ipv6; + uint8_t mac[ETH_ADDR_LEN] = {0}; + uint8_t mac_expected[ETH_ADDR_LEN] = {0x33, 0x33, 0x01, 0x02, 0x03, 0x04}; + + inet_pton(AF_INET6, "ff02::0102:0304", ipv6); + ipv6_multicast_mac(ipv6, mac); + + assert_memory_equal(mac_expected, mac, ETH_ADDR_LEN); +} + int main() { const struct CMUnitTest tests[] = { cmocka_unit_test(test_val2key), @@ -85,6 +113,8 @@ int main() { cmocka_unit_test(test_format_ipv6_address), cmocka_unit_test(test_format_ipv6_prefix), cmocka_unit_test(test_replace_substring), + cmocka_unit_test(test_ipv4_multicast_mac), + cmocka_unit_test(test_ipv6_multicast_mac), }; return cmocka_run_group_tests(tests, NULL, NULL); -} +} \ No newline at end of file