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