luci.mk: move indexcache delete into postinst
[oweals/luci.git] / modules / luci-mod-status / 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 <jow@openwrt.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 #define _BSD_SOURCE
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdint.h>
25 #include <inttypes.h>
26 #include <fcntl.h>
27 #include <time.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <signal.h>
31 #include <endian.h>
32
33 #include <sys/stat.h>
34 #include <sys/mman.h>
35 #include <arpa/inet.h>
36
37 #include <dlfcn.h>
38 #include <iwinfo.h>
39
40 #define STEP_COUNT      60
41 #define STEP_TIME       1
42 #define TIMEOUT         10
43
44 #define PID_PATH        "/var/run/luci-bwc.pid"
45
46 #define DB_PATH         "/var/lib/luci-bwc"
47 #define DB_IF_FILE      DB_PATH "/if/%s"
48 #define DB_RD_FILE      DB_PATH "/radio/%s"
49 #define DB_CN_FILE      DB_PATH "/connections"
50 #define DB_LD_FILE      DB_PATH "/load"
51
52 #define IF_SCAN_PATTERN \
53         " %[^ :]:%" SCNu64 " %" SCNu64 \
54         " %*u %*u %*u %*u %*u %*u" \
55         " %" SCNu64 " %" SCNu64
56
57 #define LD_SCAN_PATTERN \
58         "%f %f %f"
59
60
61 struct file_map {
62         int fd;
63         int size;
64         char *mmap;
65 };
66
67 struct traffic_entry {
68         uint32_t time;
69         uint64_t rxb;
70         uint64_t rxp;
71         uint64_t txb;
72         uint64_t txp;
73 };
74
75 struct conn_entry {
76         uint32_t time;
77         uint32_t udp;
78         uint32_t tcp;
79         uint32_t other;
80 };
81
82 struct load_entry {
83         uint32_t time;
84         uint16_t load1;
85         uint16_t load5;
86         uint16_t load15;
87 };
88
89 struct radio_entry {
90         uint32_t time;
91         uint16_t rate;
92         uint8_t  rssi;
93         uint8_t  noise;
94 };
95
96 static int readpid(void)
97 {
98         int fd;
99         int pid = -1;
100         char buf[9] = { 0 };
101
102         if ((fd = open(PID_PATH, O_RDONLY)) > -1)
103         {
104                 if (read(fd, buf, sizeof(buf)))
105                 {
106                         buf[8] = 0;
107                         pid = atoi(buf);
108                 }
109
110                 close(fd);
111         }
112
113         return pid;
114 }
115
116 static int writepid(void)
117 {
118         int fd;
119         int wlen;
120         char buf[9] = { 0 };
121
122         if ((fd = open(PID_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0600)) > -1)
123         {
124                 wlen = snprintf(buf, sizeof(buf), "%i", getpid());
125                 write(fd, buf, wlen);
126                 close(fd);
127
128                 return 0;
129         }
130
131         return -1;
132 }
133
134 static int timeout = TIMEOUT;
135 static int countdown = -1;
136
137 static void reset_countdown(int sig)
138 {
139         countdown = timeout;
140
141 }
142
143
144 static char *progname;
145 static int prognamelen;
146
147 static struct iwinfo_ops *backend = NULL;
148
149
150 static int init_directory(char *path)
151 {
152         char *p = path;
153
154         for (p = &path[1]; *p; p++)
155         {
156                 if (*p == '/')
157                 {
158                         *p = 0;
159
160                         if (mkdir(path, 0700) && (errno != EEXIST))
161                                 return -1;
162
163                         *p = '/';
164                 }
165         }
166
167         return 0;
168 }
169
170 static int init_file(char *path, int esize)
171 {
172         int i, file;
173         char buf[sizeof(struct traffic_entry)] = { 0 };
174
175         if (init_directory(path))
176                 return -1;
177
178         if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0)
179         {
180                 for (i = 0; i < STEP_COUNT; i++)
181                 {
182                         if (write(file, buf, esize) < 0)
183                                 break;
184                 }
185
186                 close(file);
187
188                 return 0;
189         }
190
191         return -1;
192 }
193
194 static inline uint32_t timeof(void *entry)
195 {
196         return be32toh(((struct traffic_entry *)entry)->time);
197 }
198
199 static int update_file(const char *path, void *entry, int esize)
200 {
201         int rv = -1;
202         int file;
203         char *map;
204
205         if ((file = open(path, O_RDWR)) >= 0)
206         {
207                 map = mmap(NULL, esize * STEP_COUNT, PROT_READ | PROT_WRITE,
208                                    MAP_SHARED | MAP_LOCKED, file, 0);
209
210                 if ((map != NULL) && (map != MAP_FAILED))
211                 {
212                         if (timeof(entry) > timeof(map + esize * (STEP_COUNT-1)))
213                         {
214                                 memmove(map, map + esize, esize * (STEP_COUNT-1));
215                                 memcpy(map + esize * (STEP_COUNT-1), entry, esize);
216                         }
217
218                         munmap(map, esize * STEP_COUNT);
219
220                         rv = 0;
221                 }
222
223                 close(file);
224         }
225
226         return rv;
227 }
228
229 static int mmap_file(const char *path, int esize, struct file_map *m)
230 {
231         m->fd   = -1;
232         m->size = -1;
233         m->mmap = NULL;
234
235         if ((m->fd = open(path, O_RDONLY)) >= 0)
236         {
237                 m->size = STEP_COUNT * esize;
238                 m->mmap = mmap(NULL, m->size, PROT_READ,
239                                            MAP_SHARED | MAP_LOCKED, m->fd, 0);
240
241                 if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
242                         return 0;
243         }
244
245         return -1;
246 }
247
248 static void umap_file(struct file_map *m)
249 {
250         if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
251                 munmap(m->mmap, m->size);
252
253         if (m->fd > -1)
254                 close(m->fd);
255 }
256
257 static void * iw_open(void)
258 {
259         return dlopen("/usr/lib/libiwinfo.so", RTLD_LAZY);
260 }
261
262 static int iw_update(
263         void *iw, const char *ifname, uint16_t *rate, uint8_t *rssi, uint8_t *noise
264 ) {
265         struct iwinfo_ops *(*probe)(const char *);
266         int val;
267
268         if (!backend)
269         {
270                 probe = dlsym(iw, "iwinfo_backend");
271
272                 if (!probe)
273                         return 0;
274
275                 backend = probe(ifname);
276
277                 if (!backend)
278                         return 0;
279         }
280
281         *rate = (backend->bitrate && !backend->bitrate(ifname, &val)) ? val : 0;
282         *rssi = (backend->signal && !backend->signal(ifname, &val)) ? val : 0;
283         *noise = (backend->noise && !backend->noise(ifname, &val)) ? val : 0;
284
285         return 1;
286 }
287
288 static void iw_close(void *iw)
289 {
290         void (*finish)(void);
291
292         finish = dlsym(iw, "iwinfo_finish");
293
294         if (finish)
295                 finish();
296
297         dlclose(iw);
298 }
299
300
301 static int update_ifstat(
302         const char *ifname, uint64_t rxb, uint64_t rxp, uint64_t txb, uint64_t txp
303 ) {
304         char path[1024];
305
306         struct stat s;
307         struct traffic_entry e;
308
309         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
310
311         if (stat(path, &s))
312         {
313                 if (init_file(path, sizeof(struct traffic_entry)))
314                 {
315                         fprintf(stderr, "Failed to init %s: %s\n",
316                                         path, strerror(errno));
317
318                         return -1;
319                 }
320         }
321
322         e.time = htobe32(time(NULL));
323         e.rxb  = htobe64(rxb);
324         e.rxp  = htobe64(rxp);
325         e.txb  = htobe64(txb);
326         e.txp  = htobe64(txp);
327
328         return update_file(path, &e, sizeof(struct traffic_entry));
329 }
330
331 static int update_radiostat(
332         const char *ifname, uint16_t rate, uint8_t rssi, uint8_t noise
333 ) {
334         char path[1024];
335
336         struct stat s;
337         struct radio_entry e;
338
339         snprintf(path, sizeof(path), DB_RD_FILE, ifname);
340
341         if (stat(path, &s))
342         {
343                 if (init_file(path, sizeof(struct radio_entry)))
344                 {
345                         fprintf(stderr, "Failed to init %s: %s\n",
346                                         path, strerror(errno));
347
348                         return -1;
349                 }
350         }
351
352         e.time  = htobe32(time(NULL));
353         e.rate  = htobe16(rate);
354         e.rssi  = rssi;
355         e.noise = noise;
356
357         return update_file(path, &e, sizeof(struct radio_entry));
358 }
359
360 static int update_cnstat(uint32_t udp, uint32_t tcp, uint32_t other)
361 {
362         char path[1024];
363
364         struct stat s;
365         struct conn_entry e;
366
367         snprintf(path, sizeof(path), DB_CN_FILE);
368
369         if (stat(path, &s))
370         {
371                 if (init_file(path, sizeof(struct conn_entry)))
372                 {
373                         fprintf(stderr, "Failed to init %s: %s\n",
374                                         path, strerror(errno));
375
376                         return -1;
377                 }
378         }
379
380         e.time  = htobe32(time(NULL));
381         e.udp   = htobe32(udp);
382         e.tcp   = htobe32(tcp);
383         e.other = htobe32(other);
384
385         return update_file(path, &e, sizeof(struct conn_entry));
386 }
387
388 static int update_ldstat(uint16_t load1, uint16_t load5, uint16_t load15)
389 {
390         char path[1024];
391
392         struct stat s;
393         struct load_entry e;
394
395         snprintf(path, sizeof(path), DB_LD_FILE);
396
397         if (stat(path, &s))
398         {
399                 if (init_file(path, sizeof(struct load_entry)))
400                 {
401                         fprintf(stderr, "Failed to init %s: %s\n",
402                                         path, strerror(errno));
403
404                         return -1;
405                 }
406         }
407
408         e.time   = htobe32(time(NULL));
409         e.load1  = htobe16(load1);
410         e.load5  = htobe16(load5);
411         e.load15 = htobe16(load15);
412
413         return update_file(path, &e, sizeof(struct load_entry));
414 }
415
416 static int run_daemon(void)
417 {
418         FILE *info;
419         uint64_t rxb, txb, rxp, txp;
420         uint32_t udp, tcp, other;
421         uint16_t rate;
422         uint8_t rssi, noise;
423         float lf1, lf5, lf15;
424         char line[1024];
425         char ifname[16];
426         int i;
427         void *iw;
428         struct sigaction sa;
429
430         struct stat s;
431         const char *ipc = stat("/proc/net/nf_conntrack", &s)
432                 ? "/proc/net/ip_conntrack" : "/proc/net/nf_conntrack";
433
434         switch (fork())
435         {
436                 case -1:
437                         perror("fork()");
438                         return -1;
439
440                 case 0:
441                         if (chdir("/") < 0)
442                         {
443                                 perror("chdir()");
444                                 exit(1);
445                         }
446
447                         close(0);
448                         close(1);
449                         close(2);
450                         break;
451
452                 default:
453                         return 0;
454         }
455
456         /* setup USR1 signal handler to reset timer */
457         sa.sa_handler = reset_countdown;
458         sa.sa_flags   = SA_RESTART;
459         sigemptyset(&sa.sa_mask);
460         sigaction(SIGUSR1, &sa, NULL);
461
462         /* write pid */
463         if (writepid())
464         {
465                 fprintf(stderr, "Failed to write pid file: %s\n", strerror(errno));
466                 return 1;
467         }
468
469         /* initialize iwinfo */
470         iw = iw_open();
471
472         /* go */
473         for (reset_countdown(0); countdown >= 0; countdown--)
474         {
475                 /* alter progname for ps, top */
476                 memset(progname, 0, prognamelen);
477                 snprintf(progname, prognamelen, "luci-bwc %d", countdown);
478
479                 if ((info = fopen("/proc/net/dev", "r")) != NULL)
480                 {
481                         while (fgets(line, sizeof(line), info))
482                         {
483                                 if (strchr(line, '|'))
484                                         continue;
485
486                                 if (sscanf(line, IF_SCAN_PATTERN, ifname, &rxb, &rxp, &txb, &txp))
487                                 {
488                                         if (strncmp(ifname, "lo", sizeof(ifname)))
489                                                 update_ifstat(ifname, rxb, rxp, txb, txp);
490                                 }
491                         }
492
493                         fclose(info);
494                 }
495
496                 if (iw)
497                 {
498                         for (i = 0; i < 5; i++)
499                         {
500 #define iw_checkif(pattern) \
501                                 do {                                                      \
502                                         snprintf(ifname, sizeof(ifname), pattern, i);         \
503                                         if (iw_update(iw, ifname, &rate, &rssi, &noise))  \
504                                         {                                                     \
505                                                 update_radiostat(ifname, rate, rssi, noise);      \
506                                                 continue;                                         \
507                                         }                                                     \
508                                 } while(0)
509
510                                 iw_checkif("wlan%d");
511                                 iw_checkif("ath%d");
512                                 iw_checkif("wl%d");
513                         }
514                 }
515
516                 if ((info = fopen(ipc, "r")) != NULL)
517                 {
518                         udp   = 0;
519                         tcp   = 0;
520                         other = 0;
521
522                         while (fgets(line, sizeof(line), info))
523                         {
524                                 if (strstr(line, "TIME_WAIT"))
525                                         continue;
526
527                                 if ((strstr(line, "src=127.0.0.1 ") && strstr(line, "dst=127.0.0.1 "))
528                                 || (strstr(line, "src=::1 ") && strstr(line, "dst=::1 ")))
529                                         continue;
530
531                                 if (sscanf(line, "%*s %*d %s", ifname) || sscanf(line, "%s %*d", ifname))
532                                 {
533                                         if (!strcmp(ifname, "tcp"))
534                                                 tcp++;
535                                         else if (!strcmp(ifname, "udp"))
536                                                 udp++;
537                                         else
538                                                 other++;
539                                 }
540                         }
541
542                         update_cnstat(udp, tcp, other);
543
544                         fclose(info);
545                 }
546
547                 if ((info = fopen("/proc/loadavg", "r")) != NULL)
548                 {
549                         if (fscanf(info, LD_SCAN_PATTERN, &lf1, &lf5, &lf15))
550                         {
551                                 update_ldstat((uint16_t)(lf1  * 100),
552                                                           (uint16_t)(lf5  * 100),
553                                                           (uint16_t)(lf15 * 100));
554                         }
555
556                         fclose(info);
557                 }
558
559                 sleep(STEP_TIME);
560         }
561
562         unlink(PID_PATH);
563
564         if (iw)
565                 iw_close(iw);
566
567         return 0;
568 }
569
570 static void check_daemon(void)
571 {
572         int pid;
573
574         if ((pid = readpid()) < 0 || kill(pid, 0) < 0)
575         {
576                 /* daemon ping failed, try to start it up */
577                 if (run_daemon())
578                 {
579                         fprintf(stderr,
580                                 "Failed to ping daemon and unable to start it up: %s\n",
581                                 strerror(errno));
582
583                         exit(1);
584                 }
585         }
586         else if (kill(pid, SIGUSR1))
587         {
588                 fprintf(stderr, "Failed to send signal: %s\n", strerror(errno));
589                 exit(2);
590         }
591 }
592
593 static int run_dump_ifname(const char *ifname)
594 {
595         int i;
596         char path[1024];
597         struct file_map m;
598         struct traffic_entry *e;
599
600         check_daemon();
601         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
602
603         if (mmap_file(path, sizeof(struct traffic_entry), &m))
604         {
605                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
606                 return 1;
607         }
608
609         for (i = 0; i < m.size; i += sizeof(struct traffic_entry))
610         {
611                 e = (struct traffic_entry *) &m.mmap[i];
612
613                 if (!e->time)
614                         continue;
615
616                 printf("[ %" PRIu32 ", %" PRIu64 ", %" PRIu64
617                            ", %" PRIu64 ", %" PRIu64 " ]%s\n",
618                         be32toh(e->time),
619                         be64toh(e->rxb), be64toh(e->rxp),
620                         be64toh(e->txb), be64toh(e->txp),
621                         ((i + sizeof(struct traffic_entry)) < m.size) ? "," : "");
622         }
623
624         umap_file(&m);
625
626         return 0;
627 }
628
629 static int run_dump_radio(const char *ifname)
630 {
631         int i;
632         char path[1024];
633         struct file_map m;
634         struct radio_entry *e;
635
636         check_daemon();
637         snprintf(path, sizeof(path), DB_RD_FILE, ifname);
638
639         if (mmap_file(path, sizeof(struct radio_entry), &m))
640         {
641                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
642                 return 1;
643         }
644
645         for (i = 0; i < m.size; i += sizeof(struct radio_entry))
646         {
647                 e = (struct radio_entry *) &m.mmap[i];
648
649                 if (!e->time)
650                         continue;
651
652                 printf("[ %" PRIu32 ", %" PRIu16 ", %" PRIu8 ", %" PRIu8 " ]%s\n",
653                         be32toh(e->time),
654                         be16toh(e->rate), e->rssi, e->noise,
655                         ((i + sizeof(struct radio_entry)) < m.size) ? "," : "");
656         }
657
658         umap_file(&m);
659
660         return 0;
661 }
662
663 static int run_dump_conns(void)
664 {
665         int i;
666         char path[1024];
667         struct file_map m;
668         struct conn_entry *e;
669
670         check_daemon();
671         snprintf(path, sizeof(path), DB_CN_FILE);
672
673         if (mmap_file(path, sizeof(struct conn_entry), &m))
674         {
675                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
676                 return 1;
677         }
678
679         for (i = 0; i < m.size; i += sizeof(struct conn_entry))
680         {
681                 e = (struct conn_entry *) &m.mmap[i];
682
683                 if (!e->time)
684                         continue;
685
686                 printf("[ %" PRIu32 ", %" PRIu32 ", %" PRIu32 ", %" PRIu32 " ]%s\n",
687                         be32toh(e->time), be32toh(e->udp),
688                         be32toh(e->tcp), be32toh(e->other),
689                         ((i + sizeof(struct conn_entry)) < m.size) ? "," : "");
690         }
691
692         umap_file(&m);
693
694         return 0;
695 }
696
697 static int run_dump_load(void)
698 {
699         int i;
700         char path[1024];
701         struct file_map m;
702         struct load_entry *e;
703
704         check_daemon();
705         snprintf(path, sizeof(path), DB_LD_FILE);
706
707         if (mmap_file(path, sizeof(struct load_entry), &m))
708         {
709                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
710                 return 1;
711         }
712
713         for (i = 0; i < m.size; i += sizeof(struct load_entry))
714         {
715                 e = (struct load_entry *) &m.mmap[i];
716
717                 if (!e->time)
718                         continue;
719
720                 printf("[ %" PRIu32 ", %" PRIu16 ", %" PRIu16 ", %" PRIu16 " ]%s\n",
721                         be32toh(e->time),
722                         be16toh(e->load1), be16toh(e->load5), be16toh(e->load15),
723                         ((i + sizeof(struct load_entry)) < m.size) ? "," : "");
724         }
725
726         umap_file(&m);
727
728         return 0;
729 }
730
731
732 int main(int argc, char *argv[])
733 {
734         int opt;
735
736         progname = argv[0];
737         prognamelen = -1;
738
739         for (opt = 0; opt < argc; opt++)
740                 prognamelen += 1 + strlen(argv[opt]);
741
742         while ((opt = getopt(argc, argv, "t:i:r:cl")) > -1)
743         {
744                 switch (opt)
745                 {
746                         case 't':
747                                 timeout = atoi(optarg);
748                                 break;
749
750                         case 'i':
751                                 if (optarg)
752                                         return run_dump_ifname(optarg);
753                                 break;
754
755                         case 'r':
756                                 if (optarg)
757                                         return run_dump_radio(optarg);
758                                 break;
759
760                         case 'c':
761                                 return run_dump_conns();
762
763                         case 'l':
764                                 return run_dump_load();
765
766                         default:
767                                 break;
768                 }
769         }
770
771         fprintf(stderr,
772                 "Usage:\n"
773                 "       %s [-t timeout] -i ifname\n"
774                 "       %s [-t timeout] -r radiodev\n"
775                 "       %s [-t timeout] -c\n"
776                 "       %s [-t timeout] -l\n",
777                         argv[0], argv[0], argv[0], argv[0]
778         );
779
780         return 1;
781 }