mirror of
https://github.com/rtbrick/bngblaster.git
synced 2024-05-06 15:54:57 +00:00
Merge pull request #48 from rtbrick/traffic-stream-enhancements
Traffic Stream Enhancements
This commit is contained in:
@@ -20,4 +20,8 @@ See the LICENSE file for more details.
|
||||
|
||||
## Copyright
|
||||
|
||||
Copyright (C) 2020-2021, RtBrick, Inc.
|
||||
Copyright (C) 2020-2021, RtBrick, Inc.
|
||||
|
||||
## Contact
|
||||
|
||||
bngblaster@rtbrick.com
|
||||
+3
-2
@@ -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
|
||||
|
||||
|
||||
+5
-1
@@ -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 <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"`
|
||||
+17
-11
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
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.
|
||||
+10
-6
@@ -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
|
||||
|
||||
+28
-7
@@ -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 <filename>` 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": []
|
||||
}
|
||||
```
|
||||
@@ -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();
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <jansson.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define NCURSES_NOMACROS 1
|
||||
#include <curses.h>
|
||||
@@ -66,4 +67,7 @@
|
||||
WINDOW *log_win;
|
||||
WINDOW *stats_win;
|
||||
|
||||
void
|
||||
enable_disable_traffic(bbl_ctx_s *ctx, bool status);
|
||||
|
||||
#endif
|
||||
+15
-3
@@ -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;
|
||||
|
||||
+102
-3
@@ -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);
|
||||
|
||||
+1
-1
@@ -16,4 +16,4 @@ bbl_ctrl_socket_open (bbl_ctx_s *ctx);
|
||||
bool
|
||||
bbl_ctrl_socket_close (bbl_ctx_s *ctx);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
+2
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -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
|
||||
+5
-2
@@ -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
|
||||
+81
-60
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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++;
|
||||
|
||||
+3
-10
@@ -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);
|
||||
|
||||
+2
-3
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
+96
-37
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+391
-178
@@ -12,7 +12,6 @@
|
||||
#include "bbl_stream.h"
|
||||
#include "bbl_stats.h"
|
||||
#include "bbl_io.h"
|
||||
#include <pthread.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
+70
-7
@@ -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
|
||||
+220
-174
@@ -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 <step> 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 <step> 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);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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 *);
|
||||
|
||||
|
||||
+15
-10
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+4
-2
@@ -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
|
||||
+31
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user