Skip to content

Commit 965d34f

Browse files
committed
pping: Add option to write to file
Add an option -w/--write <filename> which writes the output to the provided file instead of to stdout. Fail if file the provided file already exists to avoid data loss (if truncating file) or corrupting data (if appending file, JSON is not concatable). Signed-off-by: Simon Sundberg <simon.sundberg@kau.se>
1 parent 1e704be commit 965d34f

File tree

1 file changed

+94
-27
lines changed

1 file changed

+94
-27
lines changed

pping/pping.c

Lines changed: 94 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ static const char *__doc__ =
2626
#include <sys/epoll.h>
2727
#include <linux/unistd.h>
2828
#include <linux/membarrier.h>
29+
#include <limits.h>
2930

3031
#include "json_writer.h"
3132
#include "pping.h" //common structs for user-space and BPF parts
@@ -37,8 +38,6 @@ static const char *__doc__ =
3738

3839
#define PERF_BUFFER_PAGES 64 // Related to the perf-buffer size?
3940

40-
#define MAX_PATH_LEN 1024
41-
4241
#define MON_TO_REAL_UPDATE_FREQ \
4342
(1 * NS_PER_SECOND) // Update offset between CLOCK_MONOTONIC and CLOCK_REALTIME once per second
4443

@@ -129,10 +128,10 @@ struct pping_config {
129128
struct bpf_config bpf_config;
130129
struct bpf_tc_opts tc_ingress_opts;
131130
struct bpf_tc_opts tc_egress_opts;
132-
struct output_context out_ctx;
133131
struct map_cleanup_args clean_args;
134132
struct aggregation_config agg_conf;
135133
struct aggregation_maps agg_maps;
134+
struct output_context *out_ctx;
136135
char *object_path;
137136
char *ingress_prog;
138137
char *egress_prog;
@@ -146,7 +145,10 @@ struct pping_config {
146145
int ingress_prog_id;
147146
int egress_prog_id;
148147
char ifname[IF_NAMESIZE];
148+
char filename[PATH_MAX];
149+
enum pping_output_format format;
149150
enum xdp_attach_mode xdp_mode;
151+
bool write_to_file;
150152
bool force;
151153
bool created_tc_hook;
152154
};
@@ -171,6 +173,7 @@ static const struct option long_options[] = {
171173
{ "aggregate-subnets-v6", required_argument, NULL, '6' }, // Set the subnet size for IPv6 when aggregating (default 48)
172174
{ "aggregate-reverse", no_argument, NULL, ARG_AGG_REVERSE }, // Aggregate RTTs by dst IP of reply packet (instead of src like default)
173175
{ "aggregate-timeout", required_argument, NULL, AGG_ARG_TIMEOUT }, // Interval for timing out subnet entries in seconds (default 30s)
176+
{ "write", required_argument, NULL, 'w' }, // Write output to file (instead of stdout)
174177
{ 0, 0, NULL, 0 }
175178
};
176179

@@ -265,7 +268,7 @@ static int parse_bounded_long(long long *res, const char *str, long long low,
265268

266269
static int parse_arguments(int argc, char *argv[], struct pping_config *config)
267270
{
268-
int err, opt;
271+
int err, opt, len;
269272
double user_float;
270273
long long user_int;
271274

@@ -280,15 +283,17 @@ static int parse_arguments(int argc, char *argv[], struct pping_config *config)
280283
config->bpf_config.agg_rtts = false;
281284
config->bpf_config.agg_by_dst = false;
282285

283-
while ((opt = getopt_long(argc, argv, "hflTCsi:r:R:t:c:F:I:x:a:4:6:",
286+
while ((opt = getopt_long(argc, argv, "hflTCsi:r:R:t:c:F:I:x:a:4:6:w:",
284287
long_options, NULL)) != -1) {
285288
switch (opt) {
286289
case 'i':
287-
if (strlen(optarg) > IF_NAMESIZE) {
290+
len = strlen(optarg);
291+
if (len >= IF_NAMESIZE) {
288292
fprintf(stderr, "interface name too long\n");
289293
return -EINVAL;
290294
}
291-
strncpy(config->ifname, optarg, IF_NAMESIZE);
295+
memcpy(config->ifname, optarg, len);
296+
config->ifname[len] = '\0';
292297

293298
config->ifindex = if_nametoindex(config->ifname);
294299
if (config->ifindex == 0) {
@@ -340,11 +345,11 @@ static int parse_arguments(int argc, char *argv[], struct pping_config *config)
340345
break;
341346
case 'F':
342347
if (strcmp(optarg, "standard") == 0) {
343-
config->out_ctx.format = PPING_OUTPUT_STANDARD;
348+
config->format = PPING_OUTPUT_STANDARD;
344349
} else if (strcmp(optarg, "json") == 0) {
345-
config->out_ctx.format = PPING_OUTPUT_JSON;
350+
config->format = PPING_OUTPUT_JSON;
346351
} else if (strcmp(optarg, "ppviz") == 0) {
347-
config->out_ctx.format = PPING_OUTPUT_PPVIZ;
352+
config->format = PPING_OUTPUT_PPVIZ;
348353
} else {
349354
fprintf(stderr,
350355
"format must be \"standard\", \"json\" or \"ppviz\"\n");
@@ -429,6 +434,17 @@ static int parse_arguments(int argc, char *argv[], struct pping_config *config)
429434
config->agg_conf.timeout_interval =
430435
user_int * NS_PER_SECOND;
431436
break;
437+
case 'w':
438+
len = strlen(optarg);
439+
if (len >= sizeof(config->filename)) {
440+
fprintf(stderr, "File name too long\n");
441+
return -ENAMETOOLONG;
442+
}
443+
444+
memcpy(config->filename, optarg, len);
445+
config->filename[len] = '\0';
446+
config->write_to_file = true;
447+
break;
432448
case 'h':
433449
printf("HELP:\n");
434450
print_usage(argv);
@@ -1075,7 +1091,7 @@ static void print_map_clean_info(FILE *stream, const struct map_clean_event *e)
10751091

10761092
static void handle_event(void *ctx, int cpu, void *data, __u32 data_size)
10771093
{
1078-
struct output_context *out_ctx = ctx;
1094+
struct output_context *out_ctx = *(struct output_context **)ctx;
10791095
const union pping_event *e = data;
10801096

10811097
if (data_size < sizeof(e->event_type))
@@ -1640,6 +1656,60 @@ static int setup_periodical_map_cleaning(struct bpf_object *obj,
16401656
return err;
16411657
}
16421658

1659+
static struct output_context *open_output(const char *filename,
1660+
enum pping_output_format format,
1661+
struct aggregation_config *agg_conf)
1662+
{
1663+
struct output_context *out_ctx;
1664+
1665+
out_ctx = calloc(1, sizeof(*out_ctx));
1666+
if (!out_ctx)
1667+
return NULL;
1668+
1669+
out_ctx->format = format;
1670+
1671+
if (filename) {
1672+
out_ctx->stream = fopen(filename, "ax");
1673+
if (!out_ctx->stream)
1674+
goto err;
1675+
} else {
1676+
out_ctx->stream = stdout;
1677+
}
1678+
1679+
if (out_ctx->format == PPING_OUTPUT_JSON) {
1680+
out_ctx->jctx = jsonw_new(out_ctx->stream);
1681+
if (!out_ctx->jctx)
1682+
goto err;
1683+
jsonw_start_array(out_ctx->jctx);
1684+
}
1685+
1686+
if (agg_conf)
1687+
print_aggmetadata(out_ctx, agg_conf);
1688+
1689+
return out_ctx;
1690+
err:
1691+
free(out_ctx);
1692+
return NULL;
1693+
}
1694+
1695+
static int close_output(struct output_context *out_ctx)
1696+
{
1697+
int err = 0;
1698+
1699+
if (out_ctx->jctx) {
1700+
jsonw_end_array(out_ctx->jctx);
1701+
jsonw_destroy(&out_ctx->jctx);
1702+
}
1703+
1704+
if (out_ctx->stream && out_ctx->stream != stdout) {
1705+
if (fclose(out_ctx->stream) != 0)
1706+
err = -errno;
1707+
}
1708+
1709+
free(out_ctx);
1710+
return err;
1711+
}
1712+
16431713
static int init_signalfd(void)
16441714
{
16451715
sigset_t mask;
@@ -1976,7 +2046,7 @@ static int epoll_poll_events(int epfd, struct pping_config *config,
19762046
case PPING_EPEVENT_TYPE_AGGTIMER:
19772047
err = handle_aggregation_timer(
19782048
events[i].data.u64 & PPING_EPEVENT_MASK,
1979-
&config->out_ctx, &config->agg_maps,
2049+
config->out_ctx, &config->agg_maps,
19802050
&config->agg_conf);
19812051
break;
19822052
case PPING_EPEVENT_TYPE_SIGNAL:
@@ -2014,9 +2084,6 @@ int main(int argc, char *argv[])
20142084
.bpf_config = { .rate_limit = 100 * NS_PER_MS,
20152085
.rtt_rate = 0,
20162086
.use_srtt = false },
2017-
.out_ctx = { .format = PPING_OUTPUT_STANDARD,
2018-
.stream = stdout,
2019-
.jctx = NULL },
20202087
.clean_args = { .cleanup_interval = 1 * NS_PER_SECOND,
20212088
.valid_thread = false },
20222089
.agg_conf = { .aggregation_interval = 1 * NS_PER_SECOND,
@@ -2063,7 +2130,7 @@ int main(int argc, char *argv[])
20632130
if (!config.bpf_config.track_tcp && !config.bpf_config.track_icmp)
20642131
config.bpf_config.track_tcp = true;
20652132

2066-
if (config.out_ctx.format == PPING_OUTPUT_PPVIZ) {
2133+
if (config.format == PPING_OUTPUT_PPVIZ) {
20672134
if (config.bpf_config.agg_rtts) {
20682135
fprintf(stderr,
20692136
"The ppviz format does not support aggregated output\n");
@@ -2075,17 +2142,20 @@ int main(int argc, char *argv[])
20752142
}
20762143

20772144
fprintf(stderr, "Starting ePPing in %s mode tracking %s on %s\n",
2078-
output_format_to_str(config.out_ctx.format),
2145+
output_format_to_str(config.format),
20792146
tracked_protocols_to_str(&config), config.ifname);
20802147

2081-
if (config.out_ctx.format == PPING_OUTPUT_JSON) {
2082-
config.out_ctx.jctx = jsonw_new(config.out_ctx.stream);
2083-
jsonw_start_array(config.out_ctx.jctx);
2148+
config.out_ctx = open_output(
2149+
config.write_to_file ? config.filename : NULL, config.format,
2150+
config.bpf_config.agg_rtts ? &config.agg_conf : NULL);
2151+
if (!config.out_ctx) {
2152+
err = -errno;
2153+
fprintf(stderr, "Unable to open %s: %s\n",
2154+
config.write_to_file ? config.filename : "output",
2155+
get_libbpf_strerror(err));
2156+
return EXIT_FAILURE;
20842157
}
20852158

2086-
if (config.bpf_config.agg_rtts)
2087-
print_aggmetadata(&config.out_ctx, &config.agg_conf);
2088-
20892159
// Setup signalhandling (allow graceful shutdown on SIGINT/SIGTERM)
20902160
sigfd = init_signalfd();
20912161
if (sigfd < 0) {
@@ -2205,10 +2275,7 @@ int main(int argc, char *argv[])
22052275
close(sigfd);
22062276

22072277
cleanup_output:
2208-
if (config.out_ctx.format == PPING_OUTPUT_JSON && config.out_ctx.jctx) {
2209-
jsonw_end_array(config.out_ctx.jctx);
2210-
jsonw_destroy(&config.out_ctx.jctx);
2211-
}
2278+
close_output(config.out_ctx);
22122279

22132280
return err != 0 || detach_err != 0;
22142281
}

0 commit comments

Comments
 (0)