@@ -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
266269static 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
10761092static 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+
16431713static 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
22072277cleanup_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