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