3 * Copyright August 2007 TJ <linux@tjworld.net>
5 * Checks that network packets seen by pcap are seen at the netfilters
6 * level and haven't been silently discarded by the kernel.
8 * Depends on libcap and netfilters.
10 * To work correctly the rule set for pcap (using -p) must match the
11 * iptables rule set for netfilters. If the rules don't match then
12 * the pcap and netfilters monitors will receive different packets and
13 * lots of false positives reporting LOST packets will be generated
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 3 of the License, or
19 * (at your option) any later version.
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * You should have received a copy of the GNU General Public License
27 * along with this program in the file LICENSE-GPLv3.txt;
28 * if not, you can view it online at http://www.gnu.org/copyleft/gpl.html
34 #include <sys/types.h>
39 #include <netinet/in.h>
40 #include <netinet/ether.h>
41 #include <netinet/ip.h>
42 #include <netinet/tcp.h>
43 #include <arpa/inet.h>
44 #include <netinet/if_ether.h>
46 #include "packeteer.h"
48 #include "netfilters.h"
52 // size of string buffer used to receive pcap rule text
53 #define FILTER_BUF 1024
54 // default maximum number of packets in the tracker at any one time
55 #define TRACKER_DEFAULT_MAX_PACKETS 256
57 extern unsigned long pcap_count; // packet counters
58 extern unsigned long netfilters_count;
59 extern int pcap_control; // thread control flags
60 extern int netfilters_control;
61 extern unsigned long netfilters_lag; // simulate lag in circuit
63 // indexes of strings can act as bit-shift values. e.g. tcp_flags_strings[4], 1 << 4 = 0x10 == ACK
64 char *tcp_flags_strings[] = {"FIN", "SYN", "RST", "PUSH", "ACK", "URG", "ECN" ,"CWNR" };
66 // value of 'ret' is index into this array
67 unsigned char *error_messages[] = {
70 "allocate memory for trackers",
72 "initialise netfilters_queue",
74 "start netfilters_queue"
77 unsigned int tracker_max_packets = TRACKER_DEFAULT_MAX_PACKETS; // number of entries in the tracker table; default 256
78 unsigned long pcap_count_max = 1000; // default; maximum number of packets to monitor before quitting
79 struct in_addr_filter *ip_addr_filters = NULL; // linked-list of ip addresses for filter matching
80 unsigned char tcp_flags_filter = 0; // TCP flags for filter matching
81 unsigned char tracker_tcp_flags_filter = 0; // TCP flags to be tracked
82 unsigned int discarded_packet_count = 0; // count how many packets appear to have been silently discarded
83 unsigned int simulation = 0; // flag controlling whether to simulate packet-loss
84 unsigned int verbose = 0; // flag controlling verbose debugging messages
85 unsigned int headers_done = 0; // flag controlling printing of column headers for packet-reporting
86 unsigned int tracker_allow = 1; // flag permitting new packets to be added to the tracker table
87 int interrupted = 0; // flag controlling main loop. SIGINT (Ctrl-C) sets it
88 char time_string[10]; // the log-file timestamp
90 // protect tracker data structures from multi-thread corruption
91 pthread_mutex_t tracker_mutex = PTHREAD_MUTEX_INITIALIZER;
93 // calculate header sizes once and use in all modules
94 int size_ethernet = sizeof(struct ether_header);
95 int size_ip = sizeof(struct iphdr);
96 int size_tcp = sizeof(struct tcphdr);
97 int size_headers; // used to calculate memory allocation for tracker
99 // array of packet trackers
100 struct packet_track *tracker_packets;
103 /* useful function not in the standard C string library */
104 char *strupr(char *str) {
107 for(pos=str; *pos != 0; pos++) {
108 if(*pos >= 'a' && *pos <= 'z') {
109 *pos = *pos & 0xDF; // mask the upper/lower case bit (uppercase | 0x20 = lowercase)
117 /* get the time for prefixing to loggging
118 @timestamp if NULL, current time is used
120 char *print_time(time_t *timestamp) {
123 if (timestamp == NULL) // use the current time
125 else // use the timestamp
126 memcpy(&t, timestamp, sizeof(t));
128 localtime_r(timestamp == NULL ? &t : timestamp, &now);
129 sprintf(time_string, "%2.2d:%2.2d:%2.2d ",now.tm_hour, now.tm_min, now.tm_sec);
135 /* prints the column headers for the packet reporting */
136 void headers_print(void) {
138 fprintf(stderr, " Time # IP checksums SYN ACK Seq Ack skb->len\n");
139 headers_done = 1; // don't print again
144 /* calculate IP header checksum
145 Uses the standard summing-checksum algorithm defined for IP and TCP
147 static unsigned short ip_checksum(struct iphdr *iph) {
150 u_int16_t *end, *position = (u_int16_t *) iph;
151 unsigned long checksum = 0;
153 tmp = iph->check; // back-up of the header checksum
154 iph->check = 0; // temporarily zero the checksum whilst calculating
155 size = iph->ihl * 4 / 2; // number of 16-bit words in IP header
156 end = position + size;
157 for (position = (u_int16_t *) iph; position < end; position++)
158 checksum += ntohs(*position);
160 iph->check = tmp; // return original value
161 checksum = (checksum >> 16) + (checksum & 0xFFFF);
162 checksum += (checksum >> 16);
164 return((unsigned short)~checksum);
168 /* calculate the actual sizes and locations of the Ether, IP and TCP headers in a packet */
169 void headers_calc(unsigned char *packet) {
171 // calculate the headers based on the length values stored in them (rather than using those defined in the static header files)
172 headers.ether = (struct ether_header *)packet;
173 headers.ether_len = size_ethernet; // fixed-size
174 headers.ip = (struct iphdr *)((unsigned char *)headers.ether + headers.ether_len);
175 headers.ip_len = headers.ip->ihl * 4;
176 headers.tcp = (struct tcphdr *)((unsigned char *)headers.ip + headers.ip_len);
177 headers.tcp_len = headers.tcp->doff * 4;
178 headers.payload = ((unsigned char *)headers.tcp + (headers.tcp_len * 4));
179 headers.pkt_len = ntohs(headers.ip->tot_len) + headers.ether_len;
180 headers.payload_len = headers.pkt_len - headers.tcp_len - headers.ip_len - headers.ether_len;
181 headers.headers_len = headers.tcp_len + headers.ip_len + headers.ether_len;
183 //fprintf(stdout, "Addr: Ether=0x%lX IP=0x%lX TCP=0x%lX Payload=0x%lX\n", headers.ether, headers.ip, headers.tcp, headers.payload);
184 //fprintf(stdout, "Size: Ether=%10d IP=%10d TCP=%10d Packet =%10d\n", headers.ether_len, headers.ip_len, headers.tcp_len, headers.pkt_len);
188 /* allocate tracker memory and set all packets unallocated */
189 int tracker_init(void) {
190 int i, ret = -1; // default result; failed
191 size_headers = size_ethernet + size_ip + size_tcp;
193 tracker_packets = (struct packet_track *)malloc(tracker_max_packets * sizeof(struct packet_track) );
194 if (tracker_packets != NULL) {
195 for(i=0; i < tracker_max_packets; i++) {
196 tracker_packets[i].packet = NULL;
197 tracker_packets[i].checksum = 0;
198 tracker_packets[i].allocated = 0;
199 tracker_packets[i].pcap = 0;
200 tracker_packets[i].netf = 0;
204 if (tcp_flags_filter == 0) // no user-defined TCP flags to match
205 tcp_flags_filter = 0xFF; // so set to match all TCP flags
207 if (tracker_tcp_flags_filter == 0) // no user-defined TCP flags to track
208 tracker_tcp_flags_filter = 0xFF; // so set to track all TCP flags
210 if (verbose) fprintf(stderr, "tracker_init(): resulted in %d trackers\n", ret);
211 return ret; // number of trackers initialised, or -1 for failure
215 /* Get index of existing packet, or -1 if it doesn't exist */
216 static int tracker_findby_checksum(u_int32_t checksum) {
217 int i, ret = -1; // default; packet isn't in tracker
219 for(i=0; i < tracker_max_packets; i++) {
220 if (tracker_packets[i].allocated) { // valid packet
221 if (tracker_packets[i].checksum == checksum ) {
222 ret = i; // report matching entry
223 if (verbose) fprintf(stderr, "tracker_findby_checksum() = %d\n", i);
224 break; // leave the loop now
232 /* Get index of existing packet, matching source and destination, or -1 if it doesn't exist */
233 tracker_findby_address(u_int32_t ip_src, u_int32_t ip_dst, u_int16_t port_src, u_int16_t port_dst) {
234 int i, ret = -1; // default; packet isn't in tracker
236 for(i=0; i < tracker_max_packets; i++) {
237 if (tracker_packets[i].allocated) { // valid packet
238 if (tracker_packets[i].packet != NULL ) {
239 if (ip_src == tracker_packets[i].ip->saddr && ip_dst == tracker_packets[i].ip->daddr) {
240 if (port_src == tracker_packets[i].tcp->source && port_dst == tracker_packets[i].tcp->dest) {
241 ret = i; // report matching entry
242 if (verbose) fprintf(stderr, "tracker_findby_address() = %d\n", i);
243 break; // leave the loop now
253 /* allocate a tracker slot prior to allocating memory */
254 static int tracker_allocate(void) {
255 int i, ret = -1; // default; failed
257 for(i=0; i < tracker_max_packets; i++) {
258 if (!tracker_packets[i].allocated) { // empty slot
259 tracker_packets[i].allocated = 1;
260 // in simulation mode, randomly choose packets to be retained in tracker, thus appearing to be LOST
262 tracker_packets[i].simulation = rand() > RAND_MAX/2 ? 1 : 0; // 50% chance of 'losing' a packet
273 /* Add packet data to the tracker */
274 static int tracker_packet_add(unsigned char *raw, u_int32_t skb_len) {
275 int ret = -1; // default; failed
277 unsigned char *tmp, *packet;
278 const struct iphdr *ip; /* The IP header */
279 const struct tcphdr *tcp; /* The TCP header */
282 tracker = tracker_allocate(); // reserve a slot
284 headers_calc(raw); // how much memory do we need to allocate?
285 packet = malloc(headers.headers_len); // allocate memory to copy headers into
286 if (packet != NULL) { // ok, got the memory needed
287 tmp = memcpy(packet, raw, headers.headers_len); // copy packet into tracker memory
288 tracker_packets[tracker].packet = packet;
289 tracker_packets[tracker].ether = (struct ether_header *)packet;
290 tracker_packets[tracker].ip = (struct iphdr*)(packet + headers.ether_len);
291 tracker_packets[tracker].tcp = (struct tcphdr*)(packet + headers.ether_len + headers.ip_len);
292 tracker_packets[tracker].checksum = (ntohs(tracker_packets[tracker].ip->check) << 16)
293 | ntohs(tracker_packets[tracker].tcp->check); // combine the checksums
294 tracker_packets[tracker].skb_len = skb_len; // skb->len from net/ipv4/ip_input.c::rcv() via libpcap pkthdr->len
295 tracker_packets[tracker].pcap = 1;
296 time(&tracker_packets[tracker].timestamp); // record the time the packet was tracked
297 ret = tracker; // success
298 if (verbose) fprintf(stderr, "packet_add: %d %8.8x\n", tracker, tracker_packets[tracker].checksum);
306 /* allocate a tracker and set the checksum, but don't copy any packet data */
307 static int tracker_checksum_add(checksum) {
308 int ret = -1; // default status; failed
312 tracker = tracker_allocate(); // reserve a slot
314 tracker_packets[tracker].checksum = checksum; // set the checksum, so when pcap adds the complete packet it'll use this slot
315 tracker_packets[tracker].netf = 1;
317 if (verbose) fprintf(stderr, "checksum_add: %d %8.8x\n", tracker, tracker_packets[tracker].checksum);
325 /* remove packet from tracker */
326 static int tracker_packet_remove(int tracker) {
328 if (tracker >= 0 && tracker < tracker_max_packets) { // within bounds
329 if (tracker_packets[tracker].allocated) { // make sure it is in use
330 if (!tracker_packets[tracker].simulation) { // don't remove if it is flagged to be 'LOST'
331 if (verbose) fprintf(stderr, "tracker_remove: %d %8.8x\n", tracker, tracker_packets[tracker].checksum);
332 if(tracker_packets[tracker].packet != NULL) {
333 free(tracker_packets[tracker].packet); // release memory
334 tracker_packets[tracker].packet = NULL; // mark as definitely empty
336 tracker_packets[tracker].checksum = 0;
337 tracker_packets[tracker].pcap = 0;
338 tracker_packets[tracker].netf = 0;
339 tracker_packets[tracker].allocated = 0;
340 ret = tracker; // success; report slot ID of removed packet
348 /* release memory used by trackers */
349 static int tracker_free(void) {
352 for(i=0; i < tracker_max_packets; i++) {
353 if (tracker_packets[i].allocated) { // in use
354 if (tracker_packets[i].packet != NULL) { // memory allocated
355 tracker_packet_remove(i); // release
365 /* print the hex values of a series of bytes
366 @start the address of the first byte
367 @end the address of the last byte+1 (optional, can be NULL) in which case...
368 @qty the number of bytes to display (ignored if @end is not NULL)
370 int print_bytes_as_hex(unsigned char *start, unsigned char *end, unsigned int qty) {
371 unsigned char *pos, *stop;
372 unsigned int count = 0;
374 if (verbose) fprintf(stderr, "start=0x%lX end=0x%lX %s qty=%d\n", start, end, (end==NULL ? "(NULL)" : ""), qty);
376 stop = end != NULL ? end : start + qty;
377 for (pos = start; pos < stop; pos++, count++) {
378 fprintf(stdout, " %2.2X", *pos);
380 if (count >= 1024) break; // something might have gone wrong - limit the damage!
387 /* Test the packet using the same discard-checks net/ipv4/ip_input.c::rcv() performs */
388 static int test_lost_packet(int tracker) {
389 int ret = 0; // default is a pass
390 u_int16_t recalc_checksum;
391 unsigned int ip_len_struct;
393 if (tracker >= 0 && tracker < tracker_max_packets) {
395 // fill global headers structure will info about this packet
396 headers_calc(tracker_packets[tracker].packet);
398 // compare IP header version and minimum length
399 if (tracker_packets[tracker].ip->ihl < 5 || tracker_packets[tracker].ip->version != 4) {
400 fprintf(stdout, "ip.ihl < 5 || ip.version != 4\n");
404 // compare IP header checksum with a freshly calculated one
405 recalc_checksum = ip_checksum(tracker_packets[tracker].ip);
406 fprintf(stdout, "Kernel IP checksum calc: %4.4X", recalc_checksum);
407 if (recalc_checksum != ntohs(tracker_packets[tracker].ip->check)) {
408 fprintf(stdout, " *** BAD CHECKSUM *** ");
412 // compare IP header length to IP header structure size
413 fprintf(stdout, ", IP header length = %d", headers.ip_len);
414 if (headers.ip_len != size_ip) {
415 fprintf(stdout, " *** DIFFERENT *** ");
419 // compare IP packet 'total length' + ethernet header to size of packet buffer
420 fprintf(stdout, ", IP total length = %d", headers.pkt_len);
421 if (headers.pkt_len != tracker_packets[tracker].skb_len) {
422 fprintf(stdout, " *** PACKET LENGTH DIFFERENCE *** ");
426 fprintf(stdout, "\n");
428 fprintf(stdout, "Ethernet:");
429 fprintf(stdout, " = %d bytes\n", print_bytes_as_hex((unsigned char *)headers.ether, NULL, headers.ether_len));
430 fprintf(stdout, "IP :");
431 fprintf(stdout, " = %d bytes\n", print_bytes_as_hex((unsigned char *)headers.ip, NULL, headers.ip_len));
432 fprintf(stdout, "TCP :");
433 fprintf(stdout, " = %d bytes\n", print_bytes_as_hex((unsigned char *)headers.tcp, NULL, headers.tcp_len));
438 /* log packets that didn't make it through to netfilters */
439 static int report_lost_packet(int tracker) {
443 s.s_addr = tracker_packets[tracker].ip->saddr;
444 fprintf(stdout, "LOST: %10s %4d %15s %4.4X %4.4X %1d %1d %10u %10u %6d\n",
445 print_time(&tracker_packets[tracker].timestamp), tracker, inet_ntoa(s),
446 ntohs(tracker_packets[tracker].ip->check), ntohs(tracker_packets[tracker].tcp->check),
447 tracker_packets[tracker].tcp->syn, tracker_packets[tracker].tcp->ack,
448 tracker_packets[tracker].tcp->seq, tracker_packets[tracker].tcp->ack_seq,
449 tracker_packets[tracker].skb_len);
456 /* Check if any packets in the tracker are orphaned
457 @report flag controlling printing report; 0 = no report, 1 = report
458 @return qty of LOST packets
460 static int detect_lost_packets(unsigned int report) {
462 int i, header_done=0;
464 if (verbose) fprintf(stderr, "Detecting lost packets\n");
465 pthread_mutex_lock(&tracker_mutex);
466 if (verbose) fprintf(stderr, "Scanning\n");
468 discarded_packet_count = 0; // reset counter
470 for(i=0; i < tracker_max_packets; i++) {
471 if (tracker_packets[i].allocated) { // in use
472 if (tracker_packets[i].packet != NULL) { // pcap has been here
473 if (!tracker_packets[i].netf) { // netfilters hasn't reported this packet
474 discarded_packet_count++;
476 if (header_done) fprintf(stdout, "\n"); // prefix each report with a linefeed for clarity
477 else header_done++; // except for the first report, because the columns headers should be right above the first line of data
479 fprintf(stdout, " Time # IP checksums SYN ACK Seq Ack skb->len\n");
480 report_lost_packet(i);
487 if (verbose) fprintf(stderr, "Scanning complete\n");
489 pthread_mutex_unlock(&tracker_mutex);
491 return discarded_packet_count;
495 /* pcap reporting a new packet
496 @return slot number in tracker of packet
498 int packet_pcap(int count, const struct ether_header *ethernet, const struct iphdr *ip, const struct tcphdr *tcp, u_int32_t skb_len) {
499 u_int32_t checksum = (ntohs(ip->check) << 16) | ntohs(tcp->check); // combine the checksums
500 unsigned char tracker_tcp_flags_match = 0;
502 if (verbose) fprintf(stderr, "packet_pcap(): checksum %8.8x ip %x tcp %x\n", checksum, ntohs(ip->check), ntohs(tcp->check));
503 pthread_mutex_lock(&tracker_mutex);
505 int tracker = tracker_findby_checksum(checksum); // is it in the tracker already?
506 if (tracker == -1 && tracker_allow) { // no, so add it
507 /* check if this packet matches -F rules. *Only* if it does should it be aded.
509 * This allows a packet matching -F rule(s) to be added to the tracker, and subsequent packets
510 * in the same connection to remove it. A neat way of checking handshaking and other scenarios
512 * any packets dropped by the network stack as part of the conversation will thus cause a LOST
513 * packet as far as packeteer is concerned, and the first packet in the sequence will be reported
516 * If the rule is -F SYN then the captured packet will be the initial request for a connection
518 tracker_tcp_flags_match = *( ((unsigned char *)tcp) + 13) & tracker_tcp_flags_filter;
519 if (tracker_tcp_flags_match) { // only add packets that match the tracking rule
520 tracker = tracker_packet_add((unsigned char *)ethernet, skb_len);
523 else { // yes, so 'tick it off' and remove - don't need to save the packet data into memory
524 tracker_tcp_flags_match = *( ((unsigned char *)tcp) + 13) & tracker_tcp_flags_filter;
525 if (!tracker_tcp_flags_match) { // don't remove starting packets of conversations
526 tracker = tracker_packet_remove(tracker);
530 pthread_mutex_unlock(&tracker_mutex);
536 /* netfilters reporting a new packet
537 It is possible for netfilters to report *before* pcap does, so we need to set
538 the checksum so when pcap reports the tracker is updated, not duplicated
540 int packet_netf(int count, const struct iphdr *ip, const struct tcphdr *tcp) {
541 int r, remove = 1; // simulation control
542 unsigned char tracker_tcp_flags_match = 0;
543 u_int32_t checksum = (ntohs(ip->check) << 16) | ntohs(tcp->check); // combine the checksums
545 if (verbose) fprintf(stderr, "packet_netf(): checksum %8.8x ip %x tcp %x\n", checksum, ntohs(ip->check), ntohs(tcp->check));
547 pthread_mutex_lock(&tracker_mutex);
549 int tracker = tracker_findby_checksum(checksum); // is it in the tracker already?
550 if (tracker == -1) // the same packet isn't, but this might be a packet in the same conversation
551 tracker = tracker_findby_address(ip->saddr, ip->daddr, tcp->source, tcp->dest);
553 if (tracker == -1 && tracker_allow) { // no, so add it
554 tracker = tracker_checksum_add(checksum);
556 else { // yes, so 'tick it off' and remove
557 /* check if this packet matches -F rules. If it does, *do not* remove it. That will be done
558 * when a subsequent packet comes through with the same source & destination IP address/ports
560 * This allows a packet matching -F rule(s) to be added to the tracker, and subsequent packets
561 * in the same connection to remove it. A neat way of checking handshaking and other scenarios
563 * any packets dropped by the network stack as part of the conversation will thus cause a LOST
564 * packet as far as packeteer is concerned, and the first packet in the sequence will be reported
567 * If the rule is -F SYN then the captured packet will be the initial request for a connection
569 tracker_tcp_flags_match = *( ((unsigned char *)tcp) + 13) & tracker_tcp_flags_filter;
570 if (!tracker_tcp_flags_match) { // don't remove starting packets of conversations
571 if (remove) tracker = tracker_packet_remove(tracker);
575 pthread_mutex_unlock(&tracker_mutex);
580 /* SIGINT handler - will cause the main loop to break */
581 void sig_int(int sig) {
582 interrupted++; // the main loop's while() clause will break
587 Sets up two child threads to monitor pcap and netfilters view of arriving packets,
588 then detects discrepencies and reports them
590 int main(int argc, char **argv) {
591 int arg, len = 0,lost = 0, lastlost = 0;
594 int queue = -1; // netfilters queue number; -1 indicates to use default value (80)
595 char pcap_filter[FILTER_BUF];
597 char *device = NULL; // name of device to monitor
598 struct in_addr_filter *tmp;
599 struct in_addr ipaddr;
602 "Packeteer version %s © 2007 TJ http://intuitivenipple.net\n"
603 "Licensed on the terms of GPL version 3\n\n"
604 "Monitors network packets at pcap and netfilters stages looking for packets silently dropped by the kernel.\n\n", VER);
606 for (arg=1; arg < argc; arg++) {
607 if (argv[arg][0] == '-' && strlen(argv[arg]) > 1) {
608 switch(argv[arg][1]) {
609 case 'a': // IP address
611 if (inet_aton(argv[arg+1], &ipaddr)) { // try converting text to an IP number
612 tmp = (struct in_addr_filter *)malloc(sizeof(ip_addr_filter));
613 if (tmp != NULL) { // allocated memory
614 memcpy(&tmp->ip_addr, &ipaddr, sizeof(struct in_addr)); // set ip address
615 tmp->next = ip_addr_filters; // link into existing list
616 ip_addr_filters = tmp; // put this filter at head of list
617 fprintf(stdout, "Will match IP address %s\n", inet_ntoa(ip_addr_filters->ip_addr));
623 case 'f': // flag TCP (CWNR, ECN, URG, ACK, PUSH, RST, SYN, FIN)
625 for(i=0; i < TCP_FLAGS_MAX; i++) { // scan the tcp flags string array
626 if (strcmp(tcp_flags_strings[i], strupr(argv[arg+1])) == 0) { // compare regardless of case
627 tcp_flags_filter |= 1 << i; // bitwise-OR the flag (index in tcp_flags_string is also the bit-shift)
628 fprintf(stdout, "Will match TCP flag %s\n", tcp_flags_strings[i]);
635 case 'F': // flag to track TCP (CWNR, ECN, URG, ACK, PUSH, RST, SYN, FIN)
637 for(i=0; i < TCP_FLAGS_MAX; i++) { // scan the tcp flags string array
638 if (strcmp(tcp_flags_strings[i], strupr(argv[arg+1])) == 0) { // compare regardless of case
639 tracker_tcp_flags_filter |= 1 << i; // bitwise-OR the flag (index in tcp_flags_string is also the bit-shift)
640 fprintf(stdout, "Tracking TCP flag %s\n", tcp_flags_strings[i]);
647 case 'i': // interface
649 device = argv[arg+1];
650 printf("Monitoring interface: %s\n", device);
654 case 'l': // simulate X milliseconds of lag in circuit
656 netfilters_lag = atol(argv[arg+1]) * 1000; // adjust to microseconds
657 printf("Introducing %d milliseconds of lag\n", netfilters_lag/1000);
661 case 'n': // number of packets seen by pcap before quitting
663 pcap_count_max = atol(argv[arg+1]);
664 printf("Will stop after %u packets\n", pcap_count_max);
668 case 'p': // pcap filter rule
671 for(arg++; arg < argc; arg++) {
673 if (part[0] == '"') {
674 part++; // start after "
676 if ( ( len + strlen(part) ) > FILTER_BUF ) {
677 fprintf(stderr, "Error: pcap filter rule exceeds %d bytes in length\n", FILTER_BUF);
678 ret = 1; // error code
679 break; // drop out of for() loop and exit
681 strcat(pcap_filter, part);
682 len = strlen(pcap_filter);
683 if (pcap_filter[ len-1 ] == '"') {
684 pcap_filter[len-1] = 0; // replace " with string-terminator
685 break; // end of filter-rule, so leave loop
687 strcat(pcap_filter, " "); // insert space between operands
690 printf("pcap filter: %s\n", pcap_filter);
694 case 'q': // queue-num for NFQUEUE
696 queue = atoi(argv[arg+1]);
697 if (queue < 0 || queue > 65535) {
698 queue = -1; // out of bounds, so reset so default is used
699 fprintf(stderr, "Error: queue-num out of bounds; using default (80)\n");
702 printf("iptables -j NFQUEUE --queue-num %d\n", queue);
706 case 's': // simulation
707 simulation = 1; // enable simulation of 'lost' packets
708 printf("Simulating 'lost' packets\n");
711 case 't': // tracker table size
713 tracker_max_packets = atol(argv[arg+1]);
715 if (tracker_max_packets < TRACKER_DEFAULT_MAX_PACKETS) // check and adjust bounds
716 tracker_max_packets = TRACKER_DEFAULT_MAX_PACKETS;
717 if (tracker_max_packets > 4096)
718 tracker_max_packets = 4096;
720 printf("Tracker table has %d entries\n", tracker_max_packets);
725 verbose = 1; // enable lots of progress messages
726 printf("Verbose messages\n");
736 if (queue == -1) queue = 80; // use default value
738 if ( ret == 1 || argc == 1 || showOptions || device == NULL) {
740 "\t-a\t<source IP> address to match, e.g. 10.0.100.4 (-a can be repeated)\n"
741 "\t-f\t<TCP flag> to match. One of CWNR ECN URG ACK PUSH RST SYN FIN. (-f repeated ORs flags)\n"
742 "\t-F\t<TCP flag> to TRACK. One of CWNR ECN URG ACK PUSH RST SYN FIN. (-F repeated ORs flags)\n"
743 "\t-h\tshow this help\n"
744 "\t-i\t<interface> e.g. eth0\n"
745 "\t-l\t<millisconds> simulate lag in circuit (applied in the netfilters monitor)\n"
746 "\t-n\t<0-%u> Number of packets to monitor before stopping (defaults to 1000)\n"
747 "\t-p\t\"pcap filter rule (see man tcpdump)\"\n"
748 "\t-q\t<0-65535> iptables target queue (-j NFQUEUE --queue-num %u) (defaults to 80)\n"
749 "\t-s\tSimulate - randomly 'lose' packets to test packeteer\n"
750 "\t-t\t<256-4096> Number of entries in the tracking table (defaults to %u)\n"
751 "\t-v\tVerbose messages\n"
752 "\nE.g.\tiptables -t raw -I PREROUTING -p tcp -d 10.0.0.1 --dport 80 -j NFQUEUE --queue-num 80\n\n"
753 "\tpacketeer -i eth1 -n 2000000 -q 80 -p \"dst host 10.0.0.1 and tcp dst port 80\"\n\n"
754 "\tiptables -t raw -D PREROUTING -p tcp -d 10.0.0.1 --dport 80 -j NFQUEUE --queue-num 80\n\n",
755 ( (long) -1), queue, TRACKER_DEFAULT_MAX_PACKETS );
757 else if (ret == 0) { // run the filters
758 if (tracker_init() > 0) { // prepare the trackers
759 if (pcap_init(device, pcap_filter) == 0) {
760 if (netfilters_init( (u_int16_t)queue) == 0) {
761 if (pcap_start() == 0) {
762 if (netfilters_start() == 0) {
763 fprintf(stdout, "Monitoring a maximum of %u packets, using %u tracker slots. press Ctrl-C to stop\n",
764 pcap_count_max, tracker_max_packets);
765 signal(SIGINT, sig_int);
766 while (pcap_count < pcap_count_max && !interrupted) {
767 // fprintf(stderr, "C"); // prove the timer is running
768 sleep(1); // wait one second
770 lost = detect_lost_packets(0); // no reporting, just the total qty
771 if (lastlost != lost) { // only report when the count changes
772 fprintf(stdout, "LOST: %10s %d packets\n", print_time(NULL), lost);
775 // run out of tracker slots, or a thread has stopped, so shutdown program
776 // TRACKER_MAX_PACKETS-64 is used to leave some headroom for packets being processed
777 if (lost >= (tracker_max_packets-64) || !pcap_control || !netfilters_control) break;
779 fprintf(stdout, "Stopping...");
780 tracker_allow = 0; // refuse to add any more packets to the tracker
782 pcap_exit(); // shutdown the pcap monitor first, to allow time for netfilters to 'see' packets still in the queue
783 sleep(2); // give netfilters time to process outstanding packets
784 netfilters_exit(); // shutdown the netfilters monitor cleanly
786 fprintf(stdout, " after %u packets\n", pcap_count);
787 detect_lost_packets(1); // report LOST packets
790 pcap_exit(); // shutdown pcap cleanly
796 fprintf(stdout, "Lost packets count=%d\n", discarded_packet_count);
797 arg = tracker_free();
798 if (verbose) fprintf(stderr, "Released %d trackers\n", arg); // release allocated memory
804 free(tracker_packets); // release tracker memory
808 if (ret > 0) fprintf(stderr, "Error: failed to %s\n", error_messages[ret]);