source: examples/tutorial/headerdemo.c

4.0.1-hotfixescachetimestampsdevelopdpdk-ndagetsilivegetfragoffhelplibtrace4ndag_formatpfringrc-4.0.1rc-4.0.2rc-4.0.3rc-4.0.4ringdecrementfixringperformanceringtimestampfixes
Last change on this file was a3041a4, checked in by Shane Alcock <salcock@…>, 10 years ago
  • Fixed "unused but set" warnings that new gcc now likes to report
  • Property mode set to 100644
File size: 17.3 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_ip6_t *ip6;
222
223        /* Start by finding the first layer 3 header */
224        ip_hdr = trace_get_layer3(packet, &ethertype, &remaining);
225
226        /* If no layer 3 headers are present, be sure to set proto
227         * appropriately so the total header length is added to the right
228         * category */
229        if (ip_hdr == NULL) {
230                *proto = DEMO_PROTO_NOTIP;
231                return ip_size;
232        }
233
234        prev_rem = remaining;
235
236        /* Unlike at the link layer, there is less scope for endlessly stacked
237         * headers so we don't need a fancy while loop */
238
239        /* Remember, ethertype tells us the type of the layer 3 header so we
240         * can cast appropriately */
241        switch(ethertype) {
242                case 0x0800:    /* IPv4 */
243                        /* Skip past the IPv4 header */
244                        nexthdr = trace_get_payload_from_ip(ip_hdr, &protocol, 
245                                        &remaining);
246                       
247                        /* Check for v6 over v4 and skip over it if present */
248                        if (nexthdr && protocol == 41) {
249                                ip6 = (libtrace_ip6_t *)nexthdr;
250                                nexthdr = trace_get_payload_from_ip6(
251                                        ip6, &protocol, &remaining);
252                        }
253                        break;
254                case 0x86DD:    /* IPv6 */
255                        /* Skip past the IPv6 header */
256                        ip6 = (libtrace_ip6_t *)ip_hdr;
257                        nexthdr = trace_get_payload_from_ip6(ip6, &protocol, 
258                                        &remaining);
259                        break;
260                default:
261                        /* Somehow we managed to get a layer 3 header that is
262                         * neither v4 nor v6 */
263                        *proto = DEMO_PROTO_NOTIP;
264                        return ip_size;
265        }
266
267        /* Update our layer 3 size with the number of bytes we just skipped
268         * past */
269        assert(prev_rem >= remaining);
270        ip_size += (prev_rem - remaining);
271
272        /* We can also use the protocol value from the get_payload function
273         * to determine the transport layer protocol */
274        if (protocol == 6) 
275                *proto = DEMO_PROTO_TCP;
276        else if (protocol == 17)
277                *proto = DEMO_PROTO_UDP;
278        else
279                *proto = DEMO_PROTO_OTHER;
280       
281        /* Return our total layer 3 size */
282        return ip_size;
283
284}
285
286/* Calculates the number of bytes consumed by the transport header, including
287 * options etc. */
288static uint64_t calc_transport_size(libtrace_packet_t *packet) {
289       
290        uint64_t trans_size = 0;
291
292        void *transport;
293        void *nexthdr;
294        uint8_t proto;
295        uint32_t remaining;
296        uint32_t prev_rem;
297        libtrace_tcp_t *tcp;
298        libtrace_udp_t *udp;
299
300        /* Start by finding the transport header */
301        transport = trace_get_transport(packet, &proto, &remaining);
302
303        /* No transport header makes our life very easy - we can just return
304         * zero */
305        if (transport == NULL)
306                return trans_size;
307
308        prev_rem = remaining;
309
310        /* Skip past the transport header. Transport headers (at least the ones
311         * we're interested in) can't be stacked so we only ever need to skip
312         * past the one header */
313
314        /* Switch based on the protocol value set by trace_get_transport */
315        switch (proto) {
316                case 6:         /* TCP */
317                        tcp = (libtrace_tcp_t *)transport;
318                        nexthdr = trace_get_payload_from_tcp(tcp, &remaining);
319                        break;
320                case 17:        /* UDP */
321                        udp = (libtrace_udp_t *)transport;
322                        nexthdr = trace_get_payload_from_udp(udp, &remaining);
323                        break;
324                default:
325                        /* We have no interest in ICMP, GRE etc, and we
326                         * should never have entered this function if the
327                         * packet is using those protocols anyway! */
328                        fprintf(stderr, "Unexpected protocol: %u\n", proto);
329                        return 0;
330        }
331
332        /* If we don't have any post-transport payload, just return the
333         * transport header size */
334        if (!nexthdr)
335                return trans_size;
336       
337        /* Determine how many bytes we just skipped over and add it to the
338         * total transport size */
339        assert(prev_rem >= remaining);
340        trans_size += (prev_rem - remaining);
341
342        /* Return the total size */
343        return trans_size;
344}
345
346static uint64_t calc_header_size(libtrace_packet_t *packet, demo_proto_t *proto) {
347
348        uint64_t size = 0;
349
350        /* Start with any meta-data headers */
351        size += calc_meta_size(packet);
352
353        /* Work out the size of link layer headers */
354        size += calc_link_size(packet);
355
356        /* Determine the size of the IP headers */
357        size += calc_ip_size(packet, proto);
358
359        /* If the previous function call determined we were not an IP packet,
360         * we can drop out now and return the current size */
361        if (*proto == DEMO_PROTO_NOTIP) 
362                return size;
363
364        /* We can also drop out if the packet is not using a protocol that we
365         * are interested in */
366        if (*proto == DEMO_PROTO_OTHER || *proto == DEMO_PROTO_UNKNOWN)
367                return 0;
368
369        /* Add on the transport headers */
370        size += calc_transport_size(packet);
371
372        /* Return the total amount of headers */
373        return size;
374}
375
376static uint64_t calc_payload_size(libtrace_packet_t *packet, demo_proto_t proto)
377{
378
379        uint64_t ip_plen = 0;
380        uint64_t headers = 0;
381
382        void *layer3;
383        uint16_t ethertype;
384        uint32_t remaining;
385
386        layer3 = trace_get_layer3(packet, &ethertype, &remaining);
387
388        /* This should NEVER happen, but it's a good habit to check it anyway */
389        if (layer3 == NULL)
390                return 0;
391
392        /* Find the payload length in the IP header
393         *
394         * We also determine the size of the IP header (again!) as the payload
395         * length includes the IP and transport headers */
396       
397        if (ethertype == 0x0800) {   /* IPv4 header */
398                libtrace_ip_t *ip = (libtrace_ip_t *)layer3;
399               
400                /* Remember to byte swap! */
401                ip_plen = ntohs(ip->ip_len);
402                /* This value is only 4 bits so byteswapping is unnecessary */
403                headers += (4 * ip->ip_hl);
404       
405       
406        } else if (ethertype == 0x86DD) { /* IPv6 header */
407                libtrace_ip6_t *ip = (libtrace_ip6_t *)layer3;
408               
409                /* Remember to byte swap! */
410                ip_plen = ntohs(ip->plen);
411                /* IPv6 does not have a variable length header */
412                headers += sizeof(libtrace_ip6_t);
413       
414       
415        } else {
416                /* Not an IP packet - this should also never happen */
417                return 0;
418        }
419
420       
421        /* Now we need to subtract the size of the transport header from the
422         * IP payload length. */
423        if (proto == DEMO_PROTO_TCP) {
424               
425                /* Determine the size of the TCP header so we can subtract
426                 * that from our total payload length */
427               
428                /* Since I already know the protocol and only need to
429                 * access a single value inside the TCP header, I can use
430                 * the trace_get_tcp() helper function instead of the more
431                 * verbose trace_get_transport() . */
432                libtrace_tcp_t *tcp = trace_get_tcp(packet);
433                if (tcp == NULL)
434                        return 0;
435
436                /* Again, byteswapping not required because the doff field is
437                 * only a single byte in size*/
438                headers += (tcp->doff * 4);
439        }
440
441        if (proto == DEMO_PROTO_UDP) {
442                /* UDP has a fixed length header so we don't even need to use
443                 * trace_get_udp() */
444                headers += sizeof(libtrace_udp_t);
445        }
446
447        assert(headers <= ip_plen);
448
449        /* Subtract the length of the IP and transport headers from the
450         * payload length contained within the IP header */
451        return ip_plen - headers;
452
453}
454
455static void per_packet(libtrace_packet_t *packet)
456{
457        uint64_t header_size = 0;
458        uint64_t payload_size = 0;
459        demo_proto_t protocol = DEMO_PROTO_UNKNOWN;
460
461        /* Check if we're due to report some stats */
462        check_report(packet);
463
464        /* We need to determine the amount of header in this packet */
465        header_size = calc_header_size(packet, &protocol);
466       
467        /* Now determine the payload size, if necessary */
468        if (protocol == DEMO_PROTO_TCP || protocol == DEMO_PROTO_UDP) {
469                payload_size = calc_payload_size(packet, protocol);
470        }
471
472        /* Update the appropriate counters */
473        switch(protocol) {
474                case DEMO_PROTO_TCP:
475                        tcp_header += header_size;
476                        tcp_payload += payload_size;
477                        break;
478
479                case DEMO_PROTO_UDP:
480                        udp_header += header_size;
481                        udp_payload += payload_size;
482                        break;
483
484                case DEMO_PROTO_NOTIP:
485                        not_ip += header_size;
486                        break;
487
488                case DEMO_PROTO_OTHER:
489                        break;
490
491                case DEMO_PROTO_UNKNOWN:
492                        break;
493        }
494
495
496}
497
498/* Due to the amount of error checking required in our main function, it
499 * is a lot simpler and tidier to place all the calls to various libtrace
500 * destroy functions into a separate function.
501 */
502static void libtrace_cleanup(libtrace_t *trace, libtrace_packet_t *packet, 
503                libtrace_filter_t *filter) {
504       
505        /* It's very important to ensure that we aren't trying to destroy
506         * a NULL structure, so each of the destroy calls will only occur
507         * if the structure exists */
508        if (trace)
509                trace_destroy(trace);
510       
511        if (packet)
512                trace_destroy_packet(packet);
513
514        if (filter)
515                trace_destroy_filter(filter);
516}
517
518static void usage(char *prog) {
519        fprintf(stderr, "Usage: %s [-i interval] [-f filter] inputURI\n",
520                prog);
521}
522
523
524int main(int argc, char *argv[])
525{
526        libtrace_t *trace = NULL;
527        libtrace_packet_t *packet = NULL;
528        libtrace_filter_t *filter = NULL;
529
530        int opt;
531        char *filterstring = NULL;
532
533        /* Ensure we have at least one argument after the program name */
534        if (argc < 2) {
535                usage(argv[0]);
536                return 1;
537        }
538
539        /* Using getopt to handle any command line flags that would set the
540         * reporting interval and a filter */
541        while ((opt = getopt(argc, argv, "i:f:")) != EOF) {
542                switch (opt) {
543                        case 'i':
544                                interval = atoi(optarg);
545                                break;
546                        case 'f':
547                                filterstring = optarg;
548                                break;
549                        default:
550                                usage(argv[0]);
551                                return 1;
552                }
553        }
554       
555        /* After processing the options, we still need an argument to define
556         * the input URI */
557        if (optind + 1 > argc) {
558                usage(argv[0]);
559                return 1;
560        }
561       
562        /* Create the filter if a filter string was provided */
563        if (filterstring != NULL) {
564                filter = trace_create_filter(filterstring);
565                if (filter == NULL) {
566                        fprintf(stderr, "Failed to create filter (%s)\n",
567                                filterstring);
568                        libtrace_cleanup(trace, packet, filter);
569                        return 1;
570                }
571        }
572
573        /* Creating and initialising a packet structure to store the packets
574         * that we're going to read from the trace */
575        packet = trace_create_packet();
576
577        if (packet == NULL) {
578                /* Unfortunately, trace_create_packet doesn't use the libtrace
579                 * error system. This is because libtrace errors are associated
580                 * with the trace structure, not the packet. In our case, we
581                 * haven't even created a trace at this point so we can't
582                 * really expect libtrace to set an error on it for us, can
583                 * we?
584                 */
585                perror("Creating libtrace packet");
586                libtrace_cleanup(trace, packet, filter);
587                return 1;
588        }
589
590        /* Opening and starting the input trace. Note that unlike the other
591         * examples, we can't just use argv[1] as we may have seen command
592         * line options. Instead we should use optind which will be set to
593         * the index of the first non-getopt argument  */
594        trace = trace_create(argv[optind]);
595
596        if (trace_is_err(trace)) {
597                trace_perror(trace,"Opening trace file");
598                libtrace_cleanup(trace, packet, filter);
599                return 1;
600        }
601
602        /* Apply a filter, if one was created */
603        if (filter != NULL) {
604                if (trace_config(trace, TRACE_OPTION_FILTER, filter) == -1) {
605                        trace_perror(trace, "Configuring filter");
606                        libtrace_cleanup(trace, packet, filter);
607                        return 1;
608                }
609        }
610
611        if (trace_start(trace) == -1) {
612                trace_perror(trace,"Starting trace");
613                libtrace_cleanup(trace, packet, filter);
614                return 1;
615        }
616
617        /* This loop will read packets from the trace until either EOF is
618         * reached or an error occurs (hopefully the former!)
619         *
620         * Remember, EOF will return 0 so we only want to continue looping
621         * as long as the return value is greater than zero
622         */
623        while (trace_read_packet(trace,packet)>0) {
624                /* Call our per_packet function for every packet */
625                per_packet(packet);
626        }
627
628        /* If the trace is in an error state, then we know that we fell out of
629         * the above loop because an error occurred rather than EOF being
630         * reached. Therefore, we should probably tell the user that something
631         * went wrong
632         */
633        if (trace_is_err(trace)) {
634                trace_perror(trace,"Reading packets");
635                libtrace_cleanup(trace, packet, filter);
636                return 1;
637        }
638       
639        /* Print out the contents of the counters before exiting */
640        print_stats();
641
642        libtrace_cleanup(trace, packet, filter);
643
644        return 0;
645}
Note: See TracBrowser for help on using the repository browser.