modules/admin-full: track non-tcp or udp connections, use /proc/net/nf_conntrack...
[project/luci.git] / modules / admin-full / src / luci-bwc.c
1 /*
2  * luci-bwc - Very simple bandwidth collector cache for LuCI realtime graphs
3  *
4  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdint.h>
23 #include <inttypes.h>
24 #include <fcntl.h>
25 #include <time.h>
26 #include <errno.h>
27 #include <unistd.h>
28
29 #include <sys/stat.h>
30 #include <sys/mman.h>
31 #include <arpa/inet.h>
32
33
34 #define STEP_COUNT      60
35 #define STEP_TIME       1
36
37 #define DB_PATH         "/var/lib/luci-bwc"
38 #define DB_IF_FILE      DB_PATH "/if/%s"
39 #define DB_CN_FILE      DB_PATH "/connections"
40 #define DB_LD_FILE      DB_PATH "/load"
41
42 #define IF_SCAN_PATTERN \
43         " %[^ :]:%" SCNu64 " %" SCNu64 \
44         " %*d %*d %*d %*d %*d %*d" \
45         " %" SCNu64 " %" SCNu64
46
47 #define LD_SCAN_PATTERN \
48         "%f %f %f"
49
50
51 struct file_map {
52         int fd;
53         int size;
54         char *mmap;
55 };
56
57 struct traffic_entry {
58         uint64_t time;
59         uint64_t rxb;
60         uint64_t rxp;
61         uint64_t txb;
62         uint64_t txp;
63 };
64
65 struct conn_entry {
66         uint64_t time;
67         uint32_t udp;
68         uint32_t tcp;
69         uint32_t other;
70 };
71
72 struct load_entry {
73         uint64_t time;
74         uint16_t load1;
75         uint16_t load5;
76         uint16_t load15;
77 };
78
79
80 static uint64_t htonll(uint64_t value)
81 {
82         int num = 1;
83
84         if (*(char *)&num == 1)
85                 return htonl((uint32_t)(value & 0xFFFFFFFF)) |
86                        htonl((uint32_t)(value >> 32));
87
88         return value;
89 }
90
91 #define ntohll htonll
92
93
94 static int init_directory(char *path)
95 {
96         char *p = path;
97
98         for (p = &path[1]; *p; p++)
99         {
100                 if (*p == '/')
101                 {
102                         *p = 0;
103
104                         if (mkdir(path, 0700) && (errno != EEXIST))
105                                 return -1;
106
107                         *p = '/';
108                 }
109         }
110
111         return 0;
112 }
113
114 static int init_file(char *path, int esize)
115 {
116         int i, file;
117         char buf[sizeof(struct traffic_entry)] = { 0 };
118
119         if (init_directory(path))
120                 return -1;
121
122         if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0)
123         {
124                 for (i = 0; i < STEP_COUNT; i++)
125                 {
126                         if (write(file, buf, esize) < 0)
127                                 break;
128                 }
129
130                 close(file);
131
132                 return 0;
133         }
134
135         return -1;
136 }
137
138 static int update_file(const char *path, void *entry, int esize)
139 {
140         int rv = -1;
141         int file;
142         char *map;
143
144         if ((file = open(path, O_RDWR)) >= 0)
145         {
146                 map = mmap(NULL, esize * STEP_COUNT, PROT_READ | PROT_WRITE,
147                                    MAP_SHARED | MAP_LOCKED, file, 0);
148
149                 if ((map != NULL) && (map != MAP_FAILED))
150                 {
151                         memmove(map, map + esize, esize * (STEP_COUNT-1));
152                         memcpy(map + esize * (STEP_COUNT-1), entry, esize);
153
154                         munmap(map, esize * STEP_COUNT);
155
156                         rv = 0;
157                 }
158
159                 close(file);
160         }
161
162         return rv;
163 }
164
165 static int mmap_file(const char *path, int esize, struct file_map *m)
166 {
167         m->fd   = -1;
168         m->size = -1;
169         m->mmap = NULL;
170
171         if ((m->fd = open(path, O_RDONLY)) >= 0)
172         {
173                 m->size = STEP_COUNT * esize;
174                 m->mmap = mmap(NULL, m->size, PROT_READ,
175                                            MAP_SHARED | MAP_LOCKED, m->fd, 0);
176
177                 if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
178                         return 0;
179         }
180
181         return -1;
182 }
183
184 static void umap_file(struct file_map *m)
185 {
186         if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
187                 munmap(m->mmap, m->size);
188
189         if (m->fd > -1)
190                 close(m->fd);
191 }
192
193
194 static int update_ifstat(
195         const char *ifname, uint64_t rxb, uint64_t rxp, uint64_t txb, uint64_t txp
196 ) {
197         char path[1024];
198
199         struct stat s;
200         struct traffic_entry e;
201
202         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
203
204         if (stat(path, &s))
205         {
206                 if (init_file(path, sizeof(struct traffic_entry)))
207                 {
208                         fprintf(stderr, "Failed to init %s: %s\n",
209                                         path, strerror(errno));
210
211                         return -1;
212                 }
213         }
214
215         e.time = htonll(time(NULL));
216         e.rxb  = htonll(rxb);
217         e.rxp  = htonll(rxp);
218         e.txb  = htonll(txb);
219         e.txp  = htonll(txp);
220
221         return update_file(path, &e, sizeof(struct traffic_entry));
222 }
223
224 static int update_cnstat(uint32_t udp, uint32_t tcp, uint32_t other)
225 {
226         char path[1024];
227
228         struct stat s;
229         struct conn_entry e;
230
231         snprintf(path, sizeof(path), DB_CN_FILE);
232
233         if (stat(path, &s))
234         {
235                 if (init_file(path, sizeof(struct conn_entry)))
236                 {
237                         fprintf(stderr, "Failed to init %s: %s\n",
238                                         path, strerror(errno));
239
240                         return -1;
241                 }
242         }
243
244         e.time  = htonll(time(NULL));
245         e.udp   = htonl(udp);
246         e.tcp   = htonl(tcp);
247         e.other = htonl(other);
248
249         return update_file(path, &e, sizeof(struct conn_entry));
250 }
251
252 static int update_ldstat(uint16_t load1, uint16_t load5, uint16_t load15)
253 {
254         char path[1024];
255
256         struct stat s;
257         struct load_entry e;
258
259         snprintf(path, sizeof(path), DB_LD_FILE);
260
261         if (stat(path, &s))
262         {
263                 if (init_file(path, sizeof(struct load_entry)))
264                 {
265                         fprintf(stderr, "Failed to init %s: %s\n",
266                                         path, strerror(errno));
267
268                         return -1;
269                 }
270         }
271
272         e.time   = htonll(time(NULL));
273         e.load1  = htons(load1);
274         e.load5  = htons(load5);
275         e.load15 = htons(load15);
276
277         return update_file(path, &e, sizeof(struct load_entry));
278 }
279
280 static int run_daemon(int nofork)
281 {
282         FILE *info;
283         uint64_t rxb, txb, rxp, txp;
284         uint32_t udp, tcp, other;
285         float lf1, lf5, lf15;
286         char line[1024];
287         char ifname[16];
288
289         struct stat s;
290         const char *ipc = stat("/proc/net/nf_conntrack", &s)
291                 ? "/proc/net/ip_conntrack" : "/proc/net/nf_conntrack";
292
293         if (!nofork)
294         {
295                 switch (fork())
296                 {
297                         case -1:
298                                 perror("fork()");
299                                 return -1;
300
301                         case 0:
302                                 if (chdir("/") < 0)
303                                 {
304                                         perror("chdir()");
305                                         exit(1);
306                                 }
307
308                                 close(0);
309                                 close(1);
310                                 close(2);
311                                 break;
312
313                         default:
314                                 exit(0);
315                 }
316         }
317
318
319         /* go */
320         while (1)
321         {
322                 if ((info = fopen("/proc/net/dev", "r")) != NULL)
323                 {
324                         while (fgets(line, sizeof(line), info))
325                         {
326                                 if (strchr(line, '|'))
327                                         continue;
328
329                                 if (sscanf(line, IF_SCAN_PATTERN, ifname, &rxb, &rxp, &txb, &txp))
330                                 {
331                                         if (strncmp(ifname, "lo", sizeof(ifname)))
332                                                 update_ifstat(ifname, rxb, rxp, txb, txp);
333                                 }
334                         }
335
336                         fclose(info);
337                 }
338
339                 if ((info = fopen(ipc, "r")) != NULL)
340                 {
341                         udp   = 0;
342                         tcp   = 0;
343                         other = 0;
344
345                         while (fgets(line, sizeof(line), info))
346                         {
347                                 if (sscanf(line, "%*s %*d %s", ifname) || sscanf(line, "%s %*d", ifname))
348                                 {
349                                         if (!strcmp(ifname, "tcp"))
350                                                 tcp++;
351                                         else if (!strcmp(ifname, "udp"))
352                                                 udp++;
353                                         else
354                                                 other++;
355                                 }
356                         }
357
358                         update_cnstat(udp, tcp, other);
359
360                         fclose(info);
361                 }
362
363                 if ((info = fopen("/proc/loadavg", "r")) != NULL)
364                 {
365                         if (fscanf(info, LD_SCAN_PATTERN, &lf1, &lf5, &lf15))
366                         {
367                                 update_ldstat((uint16_t)(lf1  * 100),
368                                                           (uint16_t)(lf5  * 100),
369                                                           (uint16_t)(lf15 * 100));
370                         }
371
372                         fclose(info);
373                 }
374
375                 sleep(STEP_TIME);
376         }
377 }
378
379 static int run_dump_ifname(const char *ifname)
380 {
381         int i;
382         char path[1024];
383         struct file_map m;
384         struct traffic_entry *e;
385
386         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
387
388         if (mmap_file(path, sizeof(struct traffic_entry), &m))
389         {
390                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
391                 return 1;
392         }
393
394         for (i = 0; i < m.size; i += sizeof(struct traffic_entry))
395         {
396                 e = (struct traffic_entry *) &m.mmap[i];
397
398                 if (!e->time)
399                         continue;
400
401                 printf("[ %" PRIu64 ", %" PRIu64 ", %" PRIu64
402                            ", %" PRIu64 ", %" PRIu64 " ]%s\n",
403                         ntohll(e->time),
404                         ntohll(e->rxb), ntohll(e->rxp),
405                         ntohll(e->txb), ntohll(e->txp),
406                         ((i + sizeof(struct traffic_entry)) < m.size) ? "," : "");
407         }
408
409         umap_file(&m);
410
411         return 0;
412 }
413
414 static int run_dump_conns(void)
415 {
416         int i;
417         char path[1024];
418         struct file_map m;
419         struct conn_entry *e;
420
421         snprintf(path, sizeof(path), DB_CN_FILE);
422
423         if (mmap_file(path, sizeof(struct conn_entry), &m))
424         {
425                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
426                 return 1;
427         }
428
429         for (i = 0; i < m.size; i += sizeof(struct conn_entry))
430         {
431                 e = (struct conn_entry *) &m.mmap[i];
432
433                 if (!e->time)
434                         continue;
435
436                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
437                         ntohll(e->time), ntohl(e->udp),
438                         ntohl(e->tcp), ntohl(e->other),
439                         ((i + sizeof(struct conn_entry)) < m.size) ? "," : "");
440         }
441
442         umap_file(&m);
443
444         return 0;
445 }
446
447 static int run_dump_load(void)
448 {
449         int i;
450         char path[1024];
451         struct file_map m;
452         struct load_entry *e;
453
454         snprintf(path, sizeof(path), DB_LD_FILE);
455
456         if (mmap_file(path, sizeof(struct load_entry), &m))
457         {
458                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
459                 return 1;
460         }
461
462         for (i = 0; i < m.size; i += sizeof(struct load_entry))
463         {
464                 e = (struct load_entry *) &m.mmap[i];
465
466                 if (!e->time)
467                         continue;
468
469                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
470                         ntohll(e->time),
471                         ntohs(e->load1), ntohs(e->load5), ntohs(e->load15),
472                         ((i + sizeof(struct load_entry)) < m.size) ? "," : "");
473         }
474
475         umap_file(&m);
476
477         return 0;
478 }
479
480
481 int main(int argc, char *argv[])
482 {
483         int opt;
484         int daemon = 0;
485         int nofork = 0;
486
487         while ((opt = getopt(argc, argv, "dfi:cl")) > -1)
488         {
489                 switch (opt)
490                 {
491                         case 'd':
492                                 daemon = 1;
493                                 break;
494
495                         case 'f':
496                                 nofork = 1;
497                                 break;
498
499                         case 'i':
500                                 if (optarg)
501                                         return run_dump_ifname(optarg);
502                                 break;
503
504                         case 'c':
505                                 return run_dump_conns();
506
507                         case 'l':
508                                 return run_dump_load();
509
510                         default:
511                                 break;
512                 }
513         }
514
515         if (daemon)
516                 return run_daemon(nofork);
517
518         else
519                 fprintf(stderr,
520                         "Usage:\n"
521                         "       %s -d [-f]\n"
522                         "       %s -i ifname\n"
523                         "       %s -c\n"
524                         "       %s -l\n",
525                                 argv[0], argv[0], argv[0], argv[0]
526                 );
527
528         return 1;
529 }