Initial commit from project. lib dependencies need checking
[packeteer.git] / packeteer.c
1 /*
2  * Packeteer 
3  * Copyright August 2007 TJ <linux@tjworld.net>
4  *
5  * Checks that network packets seen by pcap are seen at the netfilters
6  * level and haven't been silently discarded by the kernel.
7  * 
8  * Depends on libcap and netfilters.
9  * 
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
14  * 
15  * 
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.
20  *
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.
25  *
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
29  *
30  */
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <sys/types.h>
35 #include <unistd.h>
36 #include <string.h>
37 #include <signal.h>
38 #include <time.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>
45 #include <pthread.h>
46 #include "packeteer.h"
47 #include "pcap.h"
48 #include "netfilters.h"
49
50 // program version
51 #define VER "0.99"
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
56
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
62
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" };
65
66 // value of 'ret' is index into this array
67 unsigned char *error_messages[] = {
68                 "Success",
69                 "Unused",
70                 "allocate memory for trackers",
71                 "initialise pcap",
72                 "initialise netfilters_queue",
73                 "start pcap",
74                 "start netfilters_queue"
75 };
76
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
89
90 // protect tracker data structures from multi-thread corruption
91 pthread_mutex_t tracker_mutex = PTHREAD_MUTEX_INITIALIZER;
92
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
98
99 // array of packet trackers
100 struct packet_track *tracker_packets; 
101
102
103 /* useful function not in the standard C string library */
104 char *strupr(char *str) {
105  char *pos;
106  if (str != NULL) {
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)
110    }
111   }
112  }
113  return str;
114 }
115
116
117 /* get the time for prefixing to loggging 
118    @timestamp if NULL, current time is used
119  * */
120 char *print_time(time_t *timestamp) {
121  struct tm now;
122  time_t t;
123  if (timestamp == NULL) // use the current time 
124   time(&t);
125  else // use the timestamp
126   memcpy(&t, timestamp, sizeof(t));
127  
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);
130  
131  return time_string;
132 }
133
134
135 /* prints the column headers for the packet reporting */
136 void headers_print(void) {
137  if (!headers_done) {
138   fprintf(stderr, "       Time               #    IP           checksums SYN ACK Seq        Ack        skb->len\n");
139   headers_done = 1; // don't print again
140  }
141 }
142
143
144 /* calculate IP header checksum 
145    Uses the standard summing-checksum algorithm defined for IP and TCP 
146  * */
147 static unsigned short ip_checksum(struct iphdr *iph) {
148  u_int16_t tmp;
149  unsigned int size;
150  u_int16_t *end, *position = (u_int16_t *) iph;
151  unsigned long checksum = 0;
152  
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);
159
160  iph->check = tmp; // return original value
161  checksum = (checksum >> 16) + (checksum & 0xFFFF);
162  checksum += (checksum >> 16);
163
164  return((unsigned short)~checksum);
165 }
166
167
168 /* calculate the actual sizes and locations of the Ether, IP and TCP headers in a packet */
169 void headers_calc(unsigned char *packet) {
170  
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;
182  
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); 
185 }
186
187
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;
192
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;
201   }
202   ret = i;
203   
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  
206
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  
209  }
210  if (verbose) fprintf(stderr, "tracker_init(): resulted in %d trackers\n", ret);
211  return ret; // number of trackers initialised, or -1 for failure
212 }
213
214
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
218  
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
225    }
226   }
227  }
228  return ret;
229 }
230
231
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
235  
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
244      }
245     }
246    }
247   }
248  }
249  return ret;
250 }
251
252
253 /* allocate a tracker slot prior to allocating memory */
254 static int tracker_allocate(void) {
255  int i, ret = -1; // default; failed
256  
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
261    if (simulation) { 
262     tracker_packets[i].simulation = rand() > RAND_MAX/2 ? 1 : 0; // 50% chance of 'losing' a packet
263    }
264    
265    ret = i;
266    break;
267   }
268  }
269  return ret;
270 }
271
272
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
276  int tracker;
277  unsigned char *tmp, *packet;
278  const struct iphdr *ip; /* The IP header */
279  const struct tcphdr *tcp; /* The TCP header */
280
281  if (raw != NULL) {
282   tracker = tracker_allocate(); // reserve a slot
283   if (tracker != -1) {
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);
299    }
300   }
301  }
302  return ret;
303 }
304
305
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
309  int tracker;
310  
311  if (checksum != 0) {
312   tracker = tracker_allocate(); // reserve a slot
313   if (tracker != -1) {
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;
316    ret = tracker;
317    if (verbose) fprintf(stderr, "checksum_add: %d %8.8x\n", tracker, tracker_packets[tracker].checksum);
318    
319   }
320  }
321  return ret;
322 }
323
324
325 /* remove packet from tracker */
326 static int tracker_packet_remove(int tracker) {
327  int ret = -1;
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
335     }
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
341    }
342   }
343  }
344  return ret;
345 }
346
347
348 /* release memory used by trackers */
349 static int tracker_free(void) {
350  int i, count = 0;
351  
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
356         count++;
357    }
358   }
359  }
360
361  return count;
362 }
363
364
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)
369  */
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;
373
374  if (verbose) fprintf(stderr, "start=0x%lX end=0x%lX %s qty=%d\n", start, end, (end==NULL ? "(NULL)" : ""), qty);
375  
376  stop = end != NULL ? end : start + qty;
377  for (pos = start; pos < stop; pos++, count++) {
378   fprintf(stdout, " %2.2X", *pos);
379   fflush(stdout);
380   if (count >= 1024) break; // something might have gone wrong - limit the damage!
381  }
382  
383  return count;
384 }
385
386
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;
392  
393  if (tracker >= 0 && tracker < tracker_max_packets) {
394
395   // fill global headers structure will info about this packet
396   headers_calc(tracker_packets[tracker].packet);
397
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");
401    ret = -1;
402   }
403   
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 *** ");
409    ret = -1;
410   }
411   
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 *** ");
416    ret = -1;
417   }
418   
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 *** ");
423    ret = -1;
424   }
425
426   fprintf(stdout, "\n");
427
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));
434  }
435 }
436
437
438 /* log packets that didn't make it through to netfilters */
439 static int report_lost_packet(int tracker) {
440  int ret = tracker;
441  struct in_addr s;
442
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);
450  fflush(stdout);
451  
452  return ret;
453 }
454
455
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
459  * */
460 static int detect_lost_packets(unsigned int report) {
461  int ret=0;
462  int i, header_done=0;
463  
464  if (verbose) fprintf(stderr, "Detecting lost packets\n");
465  pthread_mutex_lock(&tracker_mutex);
466  if (verbose) fprintf(stderr, "Scanning\n");
467  
468  discarded_packet_count = 0; // reset counter
469
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++;  
475      if (report) {
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
478
479       fprintf(stdout, "       Time         #    IP           checksums SYN ACK Seq        Ack        skb->len\n");
480       report_lost_packet(i);
481       test_lost_packet(i);
482      }
483     }
484    }
485   }
486  }
487  if (verbose) fprintf(stderr, "Scanning complete\n");
488
489  pthread_mutex_unlock(&tracker_mutex);
490  
491  return discarded_packet_count;
492 }
493
494
495 /* pcap reporting a new packet 
496    @return slot number in tracker of packet
497  */
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;
501
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);
504  
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.
508    * 
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
511    * 
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
514    * by packeteer.
515    * 
516    * If the rule is -F SYN then the captured packet will be the initial request for a connection
517    */
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);
521   }
522  }
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);
527   }
528  }
529
530  pthread_mutex_unlock(&tracker_mutex);
531  
532  return tracker;
533 }
534
535
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
539  */
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
544
545  if (verbose) fprintf(stderr, "packet_netf(): checksum %8.8x ip %x tcp %x\n", checksum, ntohs(ip->check), ntohs(tcp->check));
546
547  pthread_mutex_lock(&tracker_mutex);
548
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);
552  
553  if (tracker == -1 && tracker_allow) { // no, so add it
554   tracker = tracker_checksum_add(checksum);
555  }
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
559    * 
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
562    * 
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
565    * by packeteer.
566    * 
567    * If the rule is -F SYN then the captured packet will be the initial request for a connection
568    */
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);
572   }
573  }
574
575  pthread_mutex_unlock(&tracker_mutex);
576          
577  return tracker;
578 }
579
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
583 }
584
585
586 /* 
587    Sets up two child threads to monitor pcap and netfilters view of arriving packets,
588    then detects discrepencies and reports them
589  */
590 int main(int argc, char **argv) {
591  int arg, len = 0,lost = 0, lastlost = 0;
592  int i, ret = 0; 
593  int showOptions=0;
594  int queue = -1; // netfilters queue number; -1 indicates to use default value (80)
595  char pcap_filter[FILTER_BUF];
596  char *part = NULL; 
597  char *device = NULL; // name of device to monitor
598  struct in_addr_filter *tmp;
599  struct in_addr ipaddr;
600  
601  printf("\n"
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);
605    
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
610     if (arg+1 < argc) {
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));
618       }
619      }
620     }
621     break;
622     
623     case 'f': // flag TCP (CWNR, ECN, URG, ACK, PUSH, RST, SYN, FIN) 
624      if (arg+1 < argc) {
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]);
629         break;
630        }
631       }
632      }
633      break;
634      
635     case 'F': // flag to track TCP (CWNR, ECN, URG, ACK, PUSH, RST, SYN, FIN) 
636      if (arg+1 < argc) {
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]);
641         break;
642        }
643       }
644      }
645      break;
646      
647     case 'i': // interface
648      if (arg+1 < argc) {
649           device = argv[arg+1];
650       printf("Monitoring interface: %s\n", device);
651      }
652      break;
653     
654     case 'l': // simulate X milliseconds of lag in circuit
655      if (arg+1 < argc) {
656           netfilters_lag = atol(argv[arg+1]) * 1000; // adjust to microseconds
657       printf("Introducing %d milliseconds of lag\n", netfilters_lag/1000);
658      }
659      break;
660   
661     case 'n': // number of packets seen by pcap before quitting
662      if (arg+1 < argc) {
663           pcap_count_max = atol(argv[arg+1]);
664       printf("Will stop after %u packets\n", pcap_count_max);
665      }
666      break;
667   
668     case 'p': // pcap filter rule
669      if (arg+1 < argc) {
670           pcap_filter[0] = 0;
671           for(arg++; arg < argc; arg++) {
672            part = argv[arg];
673            if (part[0] == '"') {
674             part++; // start after "     
675            }
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
680            }
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
686            }
687            strcat(pcap_filter, " "); // insert space between operands
688           }
689
690           printf("pcap filter: %s\n", pcap_filter);
691      }
692      break;
693
694     case 'q': // queue-num for NFQUEUE
695      if (arg+1 < argc) {
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");
700           }
701           else
702        printf("iptables -j NFQUEUE --queue-num %d\n", queue);
703      }
704      break;
705      
706     case 's': // simulation
707      simulation = 1; // enable simulation of 'lost' packets
708      printf("Simulating 'lost' packets\n");
709      break;
710      
711     case 't': // tracker table size
712      if (arg+1 < argc) {
713           tracker_max_packets = atol(argv[arg+1]);
714           
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;
719
720       printf("Tracker table has %d entries\n", tracker_max_packets);
721      }
722      break;
723   
724     case 'v': // verbose
725      verbose = 1; // enable lots of progress messages
726      printf("Verbose messages\n");
727      break;
728      
729     case 'h': // help
730     default:
731      showOptions=1;
732      break;
733    } // end switch
734   }
735  }
736  if (queue == -1) queue = 80; // use default value
737
738  if ( ret == 1 || argc == 1 || showOptions || device == NULL) {
739   printf("\n"
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 );  
756  }
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
769      
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);
773          lastlost = lost;
774         }
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; 
778        }
779        fprintf(stdout, "Stopping...");
780        tracker_allow = 0; // refuse to add any more packets to the tracker
781
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
785
786        fprintf(stdout, " after %u packets\n", pcap_count);
787        detect_lost_packets(1); // report LOST packets
788       }
789       else {
790        pcap_exit(); // shutdown pcap cleanly
791        ret = 6;
792       }
793      }
794      else ret = 5; 
795
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
799     }
800     else ret = 4;
801    }
802    else ret = 3;
803
804    free(tracker_packets); // release tracker memory
805   }
806   else ret = 2;
807  }
808  if (ret > 0) fprintf(stderr, "Error: failed to %s\n", error_messages[ret]);
809  
810  return ret;
811 }