source: examples/tutorial/headerdemo.c @ d025ff7

4.0.1-hotfixescachetimestampsdevelopdpdk-ndagetsilivegetfragoffhelplibtrace4ndag_formatpfringrc-4.0.1rc-4.0.2rc-4.0.3rc-4.0.4ringdecrementfixringperformanceringtimestampfixes
Last change on this file since d025ff7 was d025ff7, checked in by Shane Alcock <salcock@…>, 11 years ago
  • Fix warnings in tutorial code
  • Property mode set to 100644
File size: 17.2 KB
Line 
1/* A much more complicated libtrace program designed to demonstrate combining
2 * various elements of libtrace to create a useful tool.
3 *
4 * Specifically, this program calculates the amount of header overhead for
5 * TCP and UDP traffic compared with the amount of application payload. It
6 * writes the byte counts regularly to generate data suitable for a time series
7 * graph.
8 *
9 */
10#include "libtrace.h"
11#include <stdio.h>
12#include <inttypes.h>
13#include <err.h>
14#include <assert.h>
15#include <getopt.h>
16#include <string.h>
17#include <stdlib.h>
18
19uint64_t udp_header = 0;
20uint64_t udp_payload = 0;
21uint64_t tcp_header = 0;
22uint64_t tcp_payload = 0;
23uint64_t not_ip = 0;
24
25uint32_t next_report = 0;
26uint32_t interval = 10;         /* Reporting interval defaults to 10 seconds. */
27
28/* This enum defines values for all the possible protocol cases that this
29 * program is interested in */
30typedef enum {
31        DEMO_PROTO_TCP,         /* The packet is a TCP packet */
32        DEMO_PROTO_UDP,         /* The packet is a UDP packet */
33        DEMO_PROTO_NOTIP,       /* The packet is NOT an IP packet */
34        DEMO_PROTO_OTHER,       /* The packet is none of the above */
35        DEMO_PROTO_UNKNOWN      /* Haven't yet determined anything about the
36                                   packet */
37} demo_proto_t;
38
39static void print_stats() {
40        printf("%u,%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 "\n",
41                next_report, tcp_header, tcp_payload, udp_header, 
42                udp_payload, not_ip);
43}
44
45static void check_report(libtrace_packet_t *packet) {
46        struct timeval ts;
47
48        /* Get the timestamp for the current packet */
49        ts = trace_get_timeval(packet);
50
51        /* If next_report is zero, then this is the first packet from the
52         * trace so we need to determine the time at which the first report
53         * must occur, i.e. "interval" seconds from now. */
54
55        if (next_report == 0) {
56                next_report = ts.tv_sec + interval;
57
58                /* Good opportunity to print some column headings */
59                printf("Time,TCP Headers,TCP Payload,UDP Headers,UDP Payload,Not IP\n");
60        }
61
62        /* Check whether we need to report our stats
63         *
64         * Compare the timestamp for the current packet against the time that
65         * the next report is due, a la timedemo.c
66         */
67
68        while ((uint32_t)ts.tv_sec > next_report) {
69                /* Print all our stats */
70                print_stats();
71
72                /* Reset the counters */
73                tcp_header = 0;
74                tcp_payload = 0;
75                udp_header = 0;
76                udp_payload = 0;
77                not_ip = 0;
78
79                /* Determine when the next report is due */
80                next_report += interval;
81        }
82}
83
84/* Calculates the number of bytes consumed by meta-data headers such as
85 * Linux SLL, RadioTap, etc.
86 */
87static uint64_t calc_meta_size(libtrace_packet_t *packet) {
88
89        uint64_t meta_size = 0;
90        void *meta_ptr = NULL;
91        libtrace_linktype_t ltype;
92        uint32_t remaining;
93        uint32_t prev_rem;
94
95        /* Get a pointer to the meta-data header */
96        meta_ptr = trace_get_packet_meta(packet, &ltype, &remaining);
97
98        /* If the result is NULL, there are no meta-data headers present */
99        if (meta_ptr == NULL) 
100                return meta_size;
101
102
103        /* Skip over any subsequent meta-data headers */
104        while (remaining > 0) {
105                prev_rem = remaining;
106                void *nexthdr = trace_get_payload_from_meta(meta_ptr,
107                        &ltype, &remaining);
108
109                /* If nexthdr is NULL, the current header is NOT a meta-data
110                 * header (and is almost certainly a link layer header) */
111                if (nexthdr == NULL) 
112                        break;
113               
114                /* Sanity check as remaining should never get larger! */
115                assert(prev_rem >= remaining);
116
117                /* Otherwise the header we called get_payload on was a
118                 * meta-data header so we need to add its length to the total
119                 * meta header size */ 
120                meta_size += (prev_rem - remaining);
121
122                /* Prepare ourselves for the next pass through the loop */
123                meta_ptr = nexthdr;
124        }
125
126        return meta_size;
127
128
129}
130
131/* Calculates the number of bytes consumed by link layer headers. Note that
132 * this will include any "layer 2.5" headers such as MPLS, VLAN or PPP.
133 */
134static uint64_t calc_link_size(libtrace_packet_t *packet) {
135        void *link_ptr;
136        void *nexthdr;
137        libtrace_linktype_t linktype;
138        uint32_t remaining;
139        uint32_t prev_rem;
140        uint16_t ethertype;
141
142        uint64_t link_size = 0;
143
144        /* Start by finding the layer 2 header */
145        link_ptr = trace_get_layer2(packet, &linktype, &remaining);
146
147        /* If there is no layer 2 header, the total link layer has to be
148         * zero bytes in size */
149        if (link_ptr == NULL) 
150                return link_size;
151
152        /* Calculate the size of the first layer 2 header by comparing
153         * remaining before and after we call trace_get_payload_from_layer2
154         */
155        prev_rem = remaining;
156        nexthdr = trace_get_payload_from_layer2(link_ptr, linktype, 
157                        &ethertype, &remaining);
158
159        /* Sanity check - remaining should never get larger! */
160        assert(prev_rem >= remaining);
161        /* Add the size of the layer 2 header to our overall link layer size */
162        link_size += (prev_rem - remaining);
163
164        /* Skip over any layer 2.5 headers, adding their size to our total
165         * link layer size.  */
166        while (remaining > 0) {
167                if (nexthdr == NULL)
168                        break;
169                prev_rem = remaining;
170
171                /* Ethertype will always contain the type of the current
172                 * header that we are up to, thanks to the efforts of the
173                 * trace_get_payload_from_* functions */
174                switch(ethertype) {
175                        case 0x8100:    /* VLAN */
176                                nexthdr = trace_get_payload_from_vlan(
177                                        nexthdr, &ethertype, &remaining);
178                                break;
179                        case 0x8847:    /* MPLS */
180                                nexthdr = trace_get_payload_from_mpls(
181                                        nexthdr, &ethertype, &remaining);
182                                break;
183                        case 0x8864:    /* PPPoE */
184                                /* This will also skip the PPP header */
185                                nexthdr = trace_get_payload_from_pppoe(
186                                        nexthdr, &ethertype, &remaining);
187                                break;
188                        default:
189                                /* This is just to provide a stopping condition
190                                 * for the while loop. */
191                                nexthdr = NULL;                 
192                }
193               
194                /* If we have reached a non-layer 2.5 header, i.e. IP, we
195                 * want to fall out and return the total size */
196                if (nexthdr == NULL)
197                        break;
198               
199                /* Otherwise, add the length of the skipped header to the
200                 * total, being sure to perform our usual sanity check first */
201                assert(prev_rem >= remaining);
202                link_size += (prev_rem - remaining);
203               
204        }
205
206        /* Return the total link layer size */
207        return link_size;
208}
209
210/* Calculates the number of bytes consumed by IP headers, including IPv6 */
211static uint64_t calc_ip_size(libtrace_packet_t *packet, demo_proto_t *proto) {
212
213        uint64_t ip_size = 0;
214       
215        void *ip_hdr;
216        void *nexthdr = NULL;;
217        uint16_t ethertype;
218        uint8_t protocol;
219        uint32_t remaining;
220        uint32_t prev_rem;
221        libtrace_ip_t *ip;
222        libtrace_ip6_t *ip6;
223
224        /* Start by finding the first layer 3 header */
225        ip_hdr = trace_get_layer3(packet, &ethertype, &remaining);
226
227        /* If no layer 3 headers are present, be sure to set proto
228         * appropriately so the total header length is added to the right
229         * category */
230        if (ip_hdr == NULL) {
231                *proto = DEMO_PROTO_NOTIP;
232                return ip_size;
233        }
234
235        prev_rem = remaining;
236
237        /* Unlike at the link layer, there is less scope for endlessly stacked
238         * headers so we don't need a fancy while loop */
239
240        /* Remember, ethertype tells us the type of the layer 3 header so we
241         * can cast appropriately */
242        switch(ethertype) {
243                case 0x0800:    /* IPv4 */
244                        /* Skip past the IPv4 header */
245                        ip = (libtrace_ip_t *)ip_hdr;
246                        nexthdr = trace_get_payload_from_ip(ip_hdr, &protocol, 
247                                        &remaining);
248                       
249                        /* Check for v6 over v4 and skip over it if present */
250                        if (nexthdr && protocol == 41) {
251                                ip6 = (libtrace_ip6_t *)nexthdr;
252                                nexthdr = trace_get_payload_from_ip6(
253                                        ip6, &protocol, &remaining);
254                        }
255                        break;
256                case 0x86DD:    /* IPv6 */
257                        /* Skip past the IPv6 header */
258                        ip6 = (libtrace_ip6_t *)ip_hdr;
259                        nexthdr = trace_get_payload_from_ip6(ip6, &protocol, 
260                                        &remaining);
261                        break;
262                default:
263                        /* Somehow we managed to get a layer 3 header that is
264                         * neither v4 nor v6 */
265                        *proto = DEMO_PROTO_NOTIP;
266                        return ip_size;
267        }
268
269        /* Update our layer 3 size with the number of bytes we just skipped
270         * past */
271        assert(prev_rem >= remaining);
272        ip_size += (prev_rem - remaining);
273
274        /* We can also use the protocol value from the get_payload function
275         * to determine the transport layer protocol */
276        if (protocol == 6) 
277                *proto = DEMO_PROTO_TCP;
278        else if (protocol == 17)
279                *proto = DEMO_PROTO_UDP;
280        else
281                *proto = DEMO_PROTO_OTHER;
282       
283        /* Return our total layer 3 size */
284        return ip_size;
285
286}
287
288/* Calculates the number of bytes consumed by the transport header, including
289 * options etc. */
290static uint64_t calc_transport_size(libtrace_packet_t *packet) {
291       
292        uint64_t trans_size = 0;
293
294        void *transport;
295        void *nexthdr;
296        uint8_t proto;
297        uint32_t remaining;
298        uint32_t prev_rem;
299        libtrace_tcp_t *tcp;
300        libtrace_udp_t *udp;
301
302        /* Start by finding the transport header */
303        transport = trace_get_transport(packet, &proto, &remaining);
304
305        /* No transport header makes our life very easy - we can just return
306         * zero */
307        if (transport == NULL)
308                return trans_size;
309
310        prev_rem = remaining;
311
312        /* Skip past the transport header. Transport headers (at least the ones
313         * we're interested in) can't be stacked so we only ever need to skip
314         * past the one header */
315
316        /* Switch based on the protocol value set by trace_get_transport */
317        switch (proto) {
318                case 6:         /* TCP */
319                        tcp = (libtrace_tcp_t *)transport;
320                        nexthdr = trace_get_payload_from_tcp(tcp, &remaining);
321                        break;
322                case 17:        /* UDP */
323                        udp = (libtrace_udp_t *)transport;
324                        nexthdr = trace_get_payload_from_udp(udp, &remaining);
325                        break;
326                default:
327                        /* We have no interest in ICMP, GRE etc, and we
328                         * should never have entered this function if the
329                         * packet is using those protocols anyway! */
330                        fprintf(stderr, "Unexpected protocol: %u\n", proto);
331                        return 0;
332        }
333
334        /* Determine how many bytes we just skipped over and add it to the
335         * total transport size */
336        assert(prev_rem >= remaining);
337        trans_size += (prev_rem - remaining);
338
339        /* Return the total size */
340        return trans_size;
341}
342
343static uint64_t calc_header_size(libtrace_packet_t *packet, demo_proto_t *proto) {
344
345        uint64_t size = 0;
346
347        /* Start with any meta-data headers */
348        size += calc_meta_size(packet);
349
350        /* Work out the size of link layer headers */
351        size += calc_link_size(packet);
352
353        /* Determine the size of the IP headers */
354        size += calc_ip_size(packet, proto);
355
356        /* If the previous function call determined we were not an IP packet,
357         * we can drop out now and return the current size */
358        if (*proto == DEMO_PROTO_NOTIP) 
359                return size;
360
361        /* We can also drop out if the packet is not using a protocol that we
362         * are interested in */
363        if (*proto == DEMO_PROTO_OTHER || *proto == DEMO_PROTO_UNKNOWN)
364                return 0;
365
366        /* Add on the transport headers */
367        size += calc_transport_size(packet);
368
369        /* Return the total amount of headers */
370        return size;
371}
372
373static uint64_t calc_payload_size(libtrace_packet_t *packet, demo_proto_t proto)
374{
375
376        uint64_t ip_plen = 0;
377        uint64_t headers = 0;
378
379        void *layer3;
380        uint16_t ethertype;
381        uint32_t remaining;
382
383        layer3 = trace_get_layer3(packet, &ethertype, &remaining);
384
385        /* This should NEVER happen, but it's a good habit to check it anyway */
386        if (layer3 == NULL)
387                return 0;
388
389        /* Find the payload length in the IP header
390         *
391         * We also determine the size of the IP header (again!) as the payload
392         * length includes the IP and transport headers */
393       
394        if (ethertype == 0x0800) {   /* IPv4 header */
395                libtrace_ip_t *ip = (libtrace_ip_t *)layer3;
396               
397                /* Remember to byte swap! */
398                ip_plen = ntohs(ip->ip_len);
399                /* This value is only 4 bits so byteswapping is unnecessary */
400                headers += (4 * ip->ip_hl);
401       
402       
403        } else if (ethertype == 0x86DD) { /* IPv6 header */
404                libtrace_ip6_t *ip = (libtrace_ip6_t *)layer3;
405               
406                /* Remember to byte swap! */
407                ip_plen = ntohs(ip->plen);
408                /* IPv6 does not have a variable length header */
409                headers += sizeof(libtrace_ip6_t);
410       
411       
412        } else {
413                /* Not an IP packet - this should also never happen */
414                return 0;
415        }
416
417       
418        /* Now we need to subtract the size of the transport header from the
419         * IP payload length. */
420        if (proto == DEMO_PROTO_TCP) {
421               
422                /* Determine the size of the TCP header so we can subtract
423                 * that from our total payload length */
424               
425                /* Since I already know the protocol and only need to
426                 * access a single value inside the TCP header, I can use
427                 * the trace_get_tcp() helper function instead of the more
428                 * verbose trace_get_transport() . */
429                libtrace_tcp_t *tcp = trace_get_tcp(packet);
430                if (tcp == NULL)
431                        return 0;
432
433                /* Again, byteswapping not required because the doff field is
434                 * only a single byte in size*/
435                headers += (tcp->doff * 4);
436        }
437
438        if (proto == DEMO_PROTO_UDP) {
439                /* UDP has a fixed length header so we don't even need to use
440                 * trace_get_udp() */
441                headers += sizeof(libtrace_udp_t);
442        }
443
444        assert(headers <= ip_plen);
445
446        /* Subtract the length of the IP and transport headers from the
447         * payload length contained within the IP header */
448        return ip_plen - headers;
449
450}
451
452static void per_packet(libtrace_packet_t *packet)
453{
454        uint64_t header_size = 0;
455        uint64_t payload_size = 0;
456        demo_proto_t protocol = DEMO_PROTO_UNKNOWN;
457
458        /* Check if we're due to report some stats */
459        check_report(packet);
460
461        /* We need to determine the amount of header in this packet */
462        header_size = calc_header_size(packet, &protocol);
463       
464        /* Now determine the payload size, if necessary */
465        if (protocol == DEMO_PROTO_TCP || protocol == DEMO_PROTO_UDP) {
466                payload_size = calc_payload_size(packet, protocol);
467        }
468
469        /* Update the appropriate counters */
470        switch(protocol) {
471                case DEMO_PROTO_TCP:
472                        tcp_header += header_size;
473                        tcp_payload += payload_size;
474                        break;
475
476                case DEMO_PROTO_UDP:
477                        udp_header += header_size;
478                        udp_payload += payload_size;
479                        break;
480
481                case DEMO_PROTO_NOTIP:
482                        not_ip += header_size;
483                        break;
484
485                case DEMO_PROTO_OTHER:
486                        break;
487
488                case DEMO_PROTO_UNKNOWN:
489                        break;
490        }
491
492
493}
494
495/* Due to the amount of error checking required in our main function, it
496 * is a lot simpler and tidier to place all the calls to various libtrace
497 * destroy functions into a separate function.
498 */
499static void libtrace_cleanup(libtrace_t *trace, libtrace_packet_t *packet, 
500                libtrace_filter_t *filter) {
501       
502        /* It's very important to ensure that we aren't trying to destroy
503         * a NULL structure, so each of the destroy calls will only occur
504         * if the structure exists */
505        if (trace)
506                trace_destroy(trace);
507       
508        if (packet)
509                trace_destroy_packet(packet);
510
511        if (filter)
512                trace_destroy_filter(filter);
513}
514
515static void usage(char *prog) {
516        fprintf(stderr, "Usage: %s [-i interval] [-f filter] inputURI\n",
517                prog);
518}
519
520
521int main(int argc, char *argv[])
522{
523        libtrace_t *trace = NULL;
524        libtrace_packet_t *packet = NULL;
525        libtrace_filter_t *filter = NULL;
526
527        int opt;
528        char *filterstring = NULL;
529
530        /* Ensure we have at least one argument after the program name */
531        if (argc < 2) {
532                usage(argv[0]);
533                return 1;
534        }
535
536        /* Using getopt to handle any command line flags that would set the
537         * reporting interval and a filter */
538        while ((opt = getopt(argc, argv, "i:f:")) != EOF) {
539                switch (opt) {
540                        case 'i':
541                                interval = atoi(optarg);
542                                break;
543                        case 'f':
544                                filterstring = optarg;
545                                break;
546                        default:
547                                usage(argv[0]);
548                                return 1;
549                }
550        }
551       
552        /* After processing the options, we still need an argument to define
553         * the input URI */
554        if (optind + 1 > argc) {
555                usage(argv[0]);
556                return 1;
557        }
558       
559        /* Create the filter if a filter string was provided */
560        if (filterstring != NULL) {
561                filter = trace_create_filter(filterstring);
562                if (filter == NULL) {
563                        fprintf(stderr, "Failed to create filter (%s)\n",
564                                filterstring);
565                        libtrace_cleanup(trace, packet, filter);
566                        return 1;
567                }
568        }
569
570        /* Creating and initialising a packet structure to store the packets
571         * that we're going to read from the trace */
572        packet = trace_create_packet();
573
574        if (packet == NULL) {
575                /* Unfortunately, trace_create_packet doesn't use the libtrace
576                 * error system. This is because libtrace errors are associated
577                 * with the trace structure, not the packet. In our case, we
578                 * haven't even created a trace at this point so we can't
579                 * really expect libtrace to set an error on it for us, can
580                 * we?
581                 */
582                perror("Creating libtrace packet");
583                libtrace_cleanup(trace, packet, filter);
584                return 1;
585        }
586
587        /* Opening and starting the input trace. Note that unlike the other
588         * examples, we can't just use argv[1] as we may have seen command
589         * line options. Instead we should use optind which will be set to
590         * the index of the first non-getopt argument  */
591        trace = trace_create(argv[optind]);
592
593        if (trace_is_err(trace)) {
594                trace_perror(trace,"Opening trace file");
595                libtrace_cleanup(trace, packet, filter);
596                return 1;
597        }
598
599        /* Apply a filter, if one was created */
600        if (filter != NULL) {
601                if (trace_config(trace, TRACE_OPTION_FILTER, filter) == -1) {
602                        trace_perror(trace, "Configuring filter");
603                        libtrace_cleanup(trace, packet, filter);
604                        return 1;
605                }
606        }
607
608        if (trace_start(trace) == -1) {
609                trace_perror(trace,"Starting trace");
610                libtrace_cleanup(trace, packet, filter);
611                return 1;
612        }
613
614        /* This loop will read packets from the trace until either EOF is
615         * reached or an error occurs (hopefully the former!)
616         *
617         * Remember, EOF will return 0 so we only want to continue looping
618         * as long as the return value is greater than zero
619         */
620        while (trace_read_packet(trace,packet)>0) {
621                /* Call our per_packet function for every packet */
622                per_packet(packet);
623        }
624
625        /* If the trace is in an error state, then we know that we fell out of
626         * the above loop because an error occurred rather than EOF being
627         * reached. Therefore, we should probably tell the user that something
628         * went wrong
629         */
630        if (trace_is_err(trace)) {
631                trace_perror(trace,"Reading packets");
632                libtrace_cleanup(trace, packet, filter);
633                return 1;
634        }
635       
636        /* Print out the contents of the counters before exiting */
637        print_stats();
638
639        libtrace_cleanup(trace, packet, filter);
640
641        return 0;
642}
Note: See TracBrowser for help on using the repository browser.