added IP aging
[rrq/rrqmisc.git] / socket-sniff / socket-sniff.c
1 #include <arpa/inet.h>
2 #include <fcntl.h>
3 #include <linux/if_ether.h>
4 #include <linux/in.h>
5 #include <stddef.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/socket.h>
10 #include <sys/stat.h>
11 #include <sys/time.h>
12 #include <sys/types.h>
13 #include <unistd.h>
14
15 #include <hashvector.h>
16
17 // Seconds between outputs
18 static int DELAY = 5;
19
20 // Byte count fade-out between displays
21 static int FADE = 10000;
22
23 // Number of top usage to report
24 static int WORST = 20;
25
26 // Drop-out age
27 static int OLD = 600;
28
29 // Number of characters for text format IP holdings
30 #define IPBUFMAX 40
31
32 typedef struct _Count {
33     struct _Count *next;
34     struct _Count *prev;
35     struct timeval when;
36     int ignore;
37     int last;
38     int accum;
39     int total;
40     char ip[ IPBUFMAX ];
41 } Count;
42
43 static void die(char *m) {
44     fprintf( stderr, "%s\n", m );
45     exit( 1 );
46 }
47
48 // Return pointer to the key for an item
49 static void *Countp_itemkey(void *item) {
50     return ((Count*) item)->ip;
51 }
52
53 // Return 1 if the item has the key, or 0 otherwise.
54 static int Countp_haskey(void *item,void *key) {
55     return memcmp( key, Countp_itemkey( item ), IPBUFMAX ) == 0;
56 }
57
58 // Returns the hashcode for a key
59 static unsigned long Countp_hashcode(void *key) {
60     return hashvector_hashcode( key, IPBUFMAX );
61 }
62
63 static hashvector TBL = {
64     .table = { 256, 0 },
65     .fill = 0,
66     .holes = 0,
67     .keyhashcode = Countp_hashcode,
68     .itemkey = Countp_itemkey,
69     .haskey = Countp_haskey
70 };
71
72 static struct {
73     Count *head;
74     Count *tail;
75 } trail;
76
77 static char buffer[ IPBUFMAX ];
78
79 /*============================================================
80  * Reading ignore lines.
81  */
82 // Return pointer to the key for an item
83 static void *charp_itemkey(void *item) {
84     return item;
85 }
86
87 // Return 1 if the item has the key, or 0 otherwise.
88 static int charp_haskey(void *item,void *key) {
89     return strcmp( key, item ) == 0;
90 }
91
92 // Returns the hashcode for a key
93 static unsigned long charp_hashcode(void *key) {
94     return hashvector_hashcode( key, strlen( (const char *) key ) );
95 }
96
97 static hashvector IGN = {
98     .table = { 256, 0 },
99     .fill = 0,
100     .holes = 0,
101     .keyhashcode = charp_hashcode,
102     .itemkey = charp_itemkey,
103     .haskey = charp_haskey
104 };
105
106 static void read_ignore_file(char *filename) {
107     #define RDBLKSZ 1000000
108     static char block[ RDBLKSZ ];
109     static char *cur = block;
110     static char *end = block;
111     int fd = open( filename, O_RDONLY );
112     if ( fd < 0 ) {
113         die( "Cannot load the ignare file." );
114     }
115     for ( ;; ) {
116         char *p = cur;
117         size_t n;
118         for ( ;; ) { // move p to end of line
119             while ( p < end && *p != '\n' ) {
120                 p++;
121             }
122             if ( p < end ) {
123                 break;
124             }
125             if ( cur != block && cur != end ) {
126                 memmove( cur, block, end - cur );
127                 end -= cur - block;
128                 cur = block;
129                 p = end;
130             }
131             n = RDBLKSZ - ( end - cur );
132             n = read( fd, end, n );
133             if ( n <= 0 ) {
134                 return; // All done
135             }
136             end += n;
137         }
138         // Line from cur to '\n' at p < end.
139         char *ip = calloc( 1, p - cur + 1 );
140         memcpy( ip, cur, p - cur );
141         cur = p + 1;
142         hashvector_add( &IGN, ip );
143     }
144 }
145
146 /*============================================================*/
147
148 static int Countp_compare(const void *ax, const void *bx) {
149     Count *a = (Count*) ax;
150     Count *b = (Count*) bx;
151     if ( b->ignore ) {
152         return 1;
153     }
154     if ( a->ignore ) {
155         return -1;
156     }
157     int x = a->total - b->total;
158     if ( x ) {
159         return x;
160     }
161     return a->last - b->last;
162 }
163
164 static int Countp_fade_and_print(unsigned long index,void *x,void *d) {
165     if ( x ) {
166         Count *item = (Count *) x;
167         item->last = item->accum;
168         item->total += item->last - FADE;
169         item->accum = 0;
170         if ( item->total <= 0 ) {
171             item->total = 0;
172         } else if ( index < WORST && item->ignore == 0 ) {
173             fprintf( stdout, "... %s %d %d\n",
174                      item->ip, item->total, item->last );
175         }
176     }
177     return 0;
178 }
179
180 static int Countp_reclaim(pvector *pv,unsigned long ix,void *item,void *data) {
181     return 0;
182 }
183
184
185 // ip points to [ IPBUFMAX ] of ip address in text
186 static void add_show_table(char *ip,size_t length) {
187     static time_t show = 0;
188     Count *item;
189     int i = hashvector_find( &TBL, ip, (void**) &item );
190     struct timeval now;
191     if ( gettimeofday( &now, 0 ) ) {
192         perror( "gettimeofday" );
193         exit( 1 );
194     }
195     if ( i == 0 ) {
196         item = (Count *) calloc( 1, sizeof( Count ) );
197         memcpy( item->ip, ip, strlen( ip ) );
198         hashvector_add( &TBL, item );
199         item->ignore = hashvector_find( &IGN, ip, 0 );
200         for ( i = strlen( ip )-1; i > 1; i-- ) {
201             if ( ip[i] == '.' || ip[i] == ':' ) {
202                 item->ignore |= hashvector_find( &IGN, ip, 0 );
203             }
204             ip[i] = 0;
205         }
206         fprintf( stdout, "add %s\n", item->ip );
207     } else {
208     // Unlink item from the trail
209         if ( item->next ) {
210             item->next->prev = item->prev;
211         }
212         if ( item->prev ) {
213             item->prev->next = item->next;
214         }
215         if ( trail.head == item ) {
216             trail.head = item->next;
217         }
218         if ( trail.tail == item ) {
219             trail.tail = item->prev;
220         }
221         item->prev = item->next = 0;
222     }
223     item->accum += length;
224     item->when = now;
225     // Link in item to the trail end
226     if ( trail.head == 0 ) {
227         trail.head = item;
228     } else {
229         trail.tail->next = item;
230         item->prev = trail.tail;
231     }
232     trail.tail = item;
233     // Drop counters older than an hour
234     while ( trail.head->when.tv_sec + OLD < item->when.tv_sec ) {
235         Count *old = trail.head;
236         trail.head = old->next;
237         if ( trail.head ) {
238             trail.head->prev = 0;
239         }
240         fprintf( stdout, "drop %s\n", old->ip );
241         hashvector_delete( &TBL, old );
242         free( old );
243     }
244     if ( now.tv_sec < show ) {
245         return;
246     }
247     if ( now.tv_sec - show > DELAY ) {
248         show = now.tv_sec;
249     }
250     show += DELAY; // Time for next output
251     pvector ordered = { 0, 0 };
252     hashvector_contents( &TBL, &ordered );
253     pvector_qsort( &ordered, Countp_compare );
254     pvector_iterate( &ordered, Countp_fade_and_print, 0 );
255     pvector_resize( &ordered, 0, Countp_reclaim, 0 );
256     fprintf( stdout, "==%ld/%ld/%ld\n", TBL.fill, TBL.holes, TBL.table.size );
257 }
258
259 static char *ipv4_address(char *b) {
260     memset( buffer, 0, sizeof( buffer ) );
261     sprintf( buffer, "%hhu.%hhu.%hhu.%hhu", b[0], b[1], b[2], b[3] );
262     return buffer;
263 }
264
265 static char *ipv6_address(short *b) {
266     memset( buffer, 0, sizeof( buffer ) );
267     sprintf( buffer, "%x:%x:%x:%x:%x:%x:%x:%x",
268              ntohs(b[0]), ntohs(b[1]), ntohs(b[2]), ntohs(b[3]),
269              ntohs(b[4]), ntohs(b[5]), ntohs(b[6]), ntohs(b[7]) );
270     return buffer;
271 }
272
273 int main(int argc,char **argv) {
274     static char packet[ 2048 ];
275     int ARG = 1;
276     // Check for -fN to set FADE
277     if ( ARG < argc && strncmp( argv[ ARG ], "-d", 2 ) == 0 ) {
278         if ( sscanf( argv[ ARG ]+2, "%d", &DELAY ) != 1 ) {
279             die( "Missing/bad delay value" );
280         }
281         fprintf( stdout, "Delay is %d seconds between reports\n", DELAY );
282         ARG++;
283     }
284     if ( ARG < argc && strncmp( argv[ ARG ], "-f", 2 ) == 0 ) {
285         if ( sscanf( argv[ ARG ]+2, "%d", &FADE ) != 1 ) {
286             die( "Missing/bad fade value" );
287         }
288         fprintf( stdout, "Fading %d bytes before reports\n", FADE );
289         ARG++;
290     }
291     if ( ARG < argc && strncmp( argv[ ARG ], "-n", 2 ) == 0 ) {
292         if ( sscanf( argv[ ARG ]+2, "%d", &WORST ) != 1 ) {
293             die( "Missing/bad number to display" );
294         }
295         fprintf( stdout, "Displaying at most %d lines in reports\n", WORST );
296         ARG++;
297     }
298     if ( ARG < argc && strncmp( argv[ ARG ], "-a", 2 ) == 0 ) {
299         if ( sscanf( argv[ ARG ]+2, "%d", &OLD ) != 1 ) {
300             die( "Missing/bad drop-out age (seconds)" );
301         }
302         fprintf( stdout, "Displaying at most %d lines in reports\n", WORST );
303         ARG++;
304     }
305     if ( ARG < argc && strncmp( argv[ ARG ], "-i", 2 ) == 0 ) {
306         char *filename = argv[ ARG ] + 2;
307         if ( (*filename) == 0 ) {
308             die( "Missing/bad ignore filename" );
309         }
310         read_ignore_file( filename );
311         fprintf( stdout, "ignoring ip prefixes from %s\n", filename );
312         ARG++;
313     }
314     if ( ARG >= argc ) {
315         die( "Please tell which interface to sniff" );
316     }
317     setbuf( stdout, 0 );
318     int N;
319     char *iface = argv[ ARG ];
320     int fd = socket( AF_PACKET, SOCK_RAW, htons( ETH_P_ALL ) );
321     char flags[4] = { 1,0,0,0 };
322     if ( fd < 0 ) {
323         perror( "what?" );
324         die( "socket" );
325     }
326     if ( fd < 0 ) {
327         die( "socket" );
328     }
329     N = strlen(iface);
330     if ( setsockopt( fd, SOL_SOCKET, SO_BINDTODEVICE, iface, N ) ) {
331         die( "setsockopt bind to device" );
332     }
333     if ( setsockopt( fd, SOL_SOCKET, SO_BROADCAST, &flags, 4 ) ) {
334         die( "setsockopt broadcast" );
335     }
336     while ( ( N = read( fd, packet, 2048 ) ) > 0 ) {
337         if ( N < 54 ) {
338             continue;
339         }
340         int code = ntohs( *((short*)(packet+12)) );
341         if ( code == 0x0800 ) {
342             // 14+12=src  14+16=dst
343             char *p = ipv4_address( packet+30 );
344             if ( ( strncmp( p, "127.", 4 ) != 0 ) ) {
345                 add_show_table( p, N );
346             }
347         } else if ( code == 0x86dd ) {
348             // 14+8=src 14+24=dst
349             char *p = ipv6_address( (short*)(packet+38) );
350             if ( ( strncmp( p, "ff02:0:0:0:0:", 13 ) != 0 ) &&
351                  ( strncmp( p, "0:0:0:0:0:0:0:1", 15 ) != 0 ) ) {
352                 add_show_table( p, N );
353             }
354         } else if ( code == 0x8100 ) {
355             // ignore VLAN
356         } else {
357             // funny code
358         }
359     }
360     return 0;
361 }