├── dev_gcc_warns.sh ├── .gitignore ├── pidfile.h ├── daylog.h ├── dev_clang_warns.sh ├── dev_analyze.sh ├── html.h ├── acct.h ├── dns.h ├── ncache.h ├── LICENSE ├── README ├── http.h ├── bsd.h ├── graph_db.h ├── dev_all.c ├── cap.h ├── localip.h ├── static ├── c-ify.c ├── style.css └── graph.js ├── conv.h ├── contrib ├── cx.ath.darkstat ├── darkproxy.php ├── ReadMe.MacOS ├── LisezMoi.MacOS └── darkstat_export ├── opt.h ├── README.git ├── NEWS ├── cdefs.h ├── addr.h ├── INSTALL ├── db.h ├── err.h ├── AUTHORS ├── now.h ├── decode.h ├── html.c ├── test_headers.sh ├── test_addr.c ├── release.sh ├── hosts_db.h ├── addr.c ├── pidfile.c ├── str.h ├── export-format.txt ├── ncache.c ├── daylog.c ├── Makefile.in ├── bsd.c ├── localip.c ├── now.c ├── err.c ├── queue.h ├── hosts_sort.c ├── configure.ac ├── ChangeLog ├── str.c ├── acct.c ├── db.c ├── install-sh ├── conv.c ├── dns.c └── graph_db.c /dev_gcc_warns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | # 3 | # Build with lots of GCC warnings enabled. 4 | # 5 | TARGET=dev_all.c 6 | 7 | gcc -O -c -fstrict-aliasing --all-warnings --extra-warnings $TARGET 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | Makefile 4 | a.out 5 | autom4te.cache 6 | darkstat-* 7 | c-ify 8 | config.h 9 | config.h.in 10 | config.log 11 | config.status 12 | configure 13 | configure.lineno 14 | darkstat 15 | darkstat.8 16 | graphjs.h 17 | stylecss.h 18 | -------------------------------------------------------------------------------- /pidfile.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2007 Emil Mikulic. 3 | * 4 | * pidfile.h: pidfile manglement 5 | */ 6 | 7 | void pidfile_create(const char *chroot_dir, const char *filename, 8 | const char *privdrop_user); 9 | void pidfile_write_close(void); 10 | void pidfile_unlink(void); 11 | 12 | /* vim:set ts=3 sw=3 tw=78 et: */ 13 | -------------------------------------------------------------------------------- /daylog.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2007 Emil Mikulic. 3 | * 4 | * daylog.h: daily usage log 5 | */ 6 | 7 | #include "graph_db.h" /* for graph_dir */ 8 | 9 | void daylog_init(const char *filename); 10 | void daylog_free(void); 11 | void daylog_acct(uint64_t amount, enum graph_dir dir); 12 | 13 | /* vim:set ts=3 sw=3 tw=78 et: */ 14 | -------------------------------------------------------------------------------- /dev_clang_warns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | # 3 | # Build with lots of Clang warnings enabled. 4 | # 5 | TARGET=dev_all.c 6 | 7 | # Adjust to suit: 8 | LLVM=$HOME/llvm 9 | CLANG=$LLVM/install/bin/clang 10 | 11 | $CLANG -Weverything -Wno-padded -Wno-format-non-iso -Wno-cast-align \ 12 | -Wno-disabled-macro-expansion -Wno-used-but-marked-unused -O -c $TARGET 13 | -------------------------------------------------------------------------------- /dev_analyze.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | # 3 | # Run the clang static analyzer. 4 | # 5 | TARGET=dev_all.c 6 | 7 | # Adjust to suit: 8 | LLVM=$HOME/llvm 9 | CHECKER=$LLVM/llvm/tools/clang/tools/scan-build/ccc-analyzer 10 | CLANG=$LLVM/install/bin/clang 11 | 12 | $LLVM/llvm/tools/clang/tools/scan-build/scan-build \ 13 | -analyze-headers \ 14 | --use-analyzer=$LLVM/install/bin/clang \ 15 | $CLANG -c $TARGET 16 | -------------------------------------------------------------------------------- /html.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * 3 | * html.h: HTML header/footer templating for web interface. 4 | * copyright (c) 2006 Ben Stewart. 5 | * copyright (c) 2010 Malte S. Stretz. 6 | */ 7 | 8 | struct str; 9 | 10 | void html_open(struct str *buf, const char *title, 11 | const unsigned int path_depth, const int want_graph_js); 12 | void html_close(struct str *buf); 13 | 14 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 15 | -------------------------------------------------------------------------------- /acct.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2012 Emil Mikulic. 3 | * 4 | * acct.h: traffic accounting 5 | */ 6 | 7 | #include 8 | 9 | struct pktsummary; 10 | struct local_ips; 11 | 12 | extern uint64_t acct_total_packets, acct_total_bytes; 13 | 14 | void acct_init_localnet(const char *spec); 15 | void acct_for(const struct pktsummary * const sm, 16 | const struct local_ips * const local_ips); 17 | 18 | /* vim:set ts=3 sw=3 tw=80 expandtab: */ 19 | -------------------------------------------------------------------------------- /dns.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2011 Emil Mikulic. 3 | * 4 | * dns.h: synchronous DNS in a child process. 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | struct addr; 11 | 12 | void dns_init(const char *privdrop_user); 13 | void dns_stop(void); 14 | void dns_queue(const struct addr *const ipaddr); 15 | void dns_poll(void); 16 | 17 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 18 | -------------------------------------------------------------------------------- /ncache.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2006 Emil Mikulic. 3 | * 4 | * ncache.h: cache of protocol and service names. 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | void ncache_init(void); 11 | void ncache_free(void); 12 | const char *getproto(const int proto); 13 | const char *getservtcp(const int port); 14 | const char *getservudp(const int port); 15 | 16 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Parts of the darkstat source code are covered by a BSD license (actually 2 | closer to the ISC license template favored by the OpenBSD project). 3 | These are usually the more generalized, reusable parts of the code. 4 | 5 | Other parts of the darkstat code are covered by the GPL. These other 6 | parts are usually not generic code, but are specific to darkstat and its 7 | purpose. 8 | 9 | All of the source code is clearly annotated to show which license covers 10 | which parts. 11 | 12 | Due to the viral nature of the GPL, once linked, the entire darkstat 13 | binary is infected with the GPL. 14 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | darkstat is a network statistics gatherer. 2 | 3 | It sniffs packets on a specified interface, accumulates statistics, and 4 | serves them up over HTTP. 5 | 6 | See the "AUTHORS" file for credits, and who to e-mail when things break. 7 | See the "LICENSE" file for an explanation of the source code licensing. 8 | See the "INSTALL" file for installation instructions. 9 | See the "darkstat.8" manual page for usage instructions. 10 | 11 | If your system doesn't have enough copies of the full text of the GNU 12 | General Public License already, we have provided another one in the 13 | "COPYING.GPL" file. 14 | -------------------------------------------------------------------------------- /http.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * http.h: embedded webserver. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | void http_init_base(const char *url); 12 | void http_add_bindaddr(const char *bindaddr); 13 | void http_listen(const unsigned short bindport); 14 | void http_fd_set(fd_set *recv_set, fd_set *send_set, int *max_fd, 15 | struct timeval *timeout, int *need_timeout); 16 | void http_poll(fd_set *read_set, fd_set *write_set); 17 | void http_stop(void); 18 | 19 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 20 | -------------------------------------------------------------------------------- /bsd.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2011-2014 Emil Mikulic. 3 | * 4 | * bsd.h: *BSD compatibility. 5 | */ 6 | 7 | #include 8 | #include "config.h" 9 | #ifdef HAVE_BSD_STRING_H 10 | # include 11 | #endif 12 | #ifdef HAVE_BSD_UNISTD_H 13 | # include 14 | #endif 15 | 16 | #ifndef HAVE_STRLCPY 17 | size_t strlcpy(char *dst, const char *src, size_t siz); 18 | #endif 19 | 20 | #ifndef HAVE_STRLCAT 21 | size_t strlcat(char *dst, const char *src, size_t siz); 22 | #endif 23 | 24 | #ifndef HAVE_SETPROCTITLE 25 | #define setproctitle(fmt) /* no-op */ 26 | #endif 27 | 28 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 29 | -------------------------------------------------------------------------------- /graph_db.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2006-2011 Emil Mikulic. 3 | * 4 | * graph_db.h: round robin database for graph data 5 | */ 6 | #ifndef __DARKSTAT_GRAPH_DB_H 7 | #define __DARKSTAT_GRAPH_DB_H 8 | 9 | #include /* for uint64_t on Linux and OS X */ 10 | 11 | enum graph_dir { 12 | MIN_GRAPH_DIR = 1, 13 | GRAPH_IN = 1, 14 | GRAPH_OUT = 2, 15 | MAX_GRAPH_DIR = 2 16 | }; 17 | 18 | void graph_init(void); 19 | void graph_reset(void); 20 | void graph_free(void); 21 | void graph_acct(uint64_t amount, enum graph_dir dir); 22 | void graph_rotate(void); 23 | int graph_import(const int fd); 24 | int graph_export(const int fd); 25 | 26 | struct str *html_front_page(void); 27 | struct str *xml_graphs(void); 28 | 29 | #endif 30 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 31 | -------------------------------------------------------------------------------- /dev_all.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2014 Emil Mikulic. 3 | * 4 | * dev_all.c: a single compilation unit of all darkstat code. 5 | * Useful for static analysis. 6 | * 7 | * You may use, modify and redistribute this file under the terms of the 8 | * GNU General Public License version 2. (see COPYING.GPL) 9 | */ 10 | #include "acct.c" 11 | #include "addr.c" 12 | #include "bsd.c" 13 | #include "cap.c" 14 | #include "conv.c" 15 | #include "daylog.c" 16 | #include "db.c" 17 | #include "decode.c" 18 | #include "dns.c" 19 | #include "err.c" 20 | #include "graph_db.c" 21 | #include "hosts_db.c" 22 | #include "hosts_sort.c" 23 | #include "html.c" 24 | #include "http.c" 25 | #include "localip.c" 26 | #include "ncache.c" 27 | #include "now.c" 28 | #include "pidfile.c" 29 | #include "str.c" 30 | 31 | #include "darkstat.c" 32 | -------------------------------------------------------------------------------- /cap.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * cap.h: interface to libpcap. 5 | */ 6 | 7 | #include /* OpenBSD needs this before select */ 8 | #include /* FreeBSD 4 needs this for struct timeval */ 9 | #include 10 | 11 | extern unsigned int cap_pkts_recv, cap_pkts_drop; 12 | 13 | void cap_add_ifname(const char *ifname); /* call one or more times */ 14 | void cap_add_filter(const char *filter); /* call zero or more times */ 15 | void cap_start(const int promisc); 16 | void cap_fd_set(fd_set *read_set, int *max_fd, 17 | struct timeval *timeout, int *need_timeout); 18 | int cap_poll(fd_set *read_set); 19 | void cap_stop(void); 20 | void cap_free_args(void); 21 | 22 | void cap_from_file(const char *capfile); 23 | 24 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 25 | -------------------------------------------------------------------------------- /localip.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * localip.h: determine the local IPs of an interface 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | #ifndef __DARKSTAT_LOCALIP_H 10 | #define __DARKSTAT_LOCALIP_H 11 | 12 | #include 13 | 14 | struct local_ips { 15 | int is_valid; 16 | time_t last_update_mono; 17 | int num_addrs; 18 | struct addr *addrs; 19 | }; 20 | 21 | void localip_init(struct local_ips *ips); 22 | void localip_free(struct local_ips *ips); 23 | 24 | void localip_update(const char *iface, struct local_ips *ips); 25 | int is_localip(const struct addr * const a, 26 | const struct local_ips * const ips); 27 | 28 | #endif 29 | /* vim:set ts=3 sw=3 tw=80 et: */ 30 | -------------------------------------------------------------------------------- /static/c-ify.c: -------------------------------------------------------------------------------- 1 | /* DON'T LOOK AT MY FACE! MY HIDEOUS FACE! */ 2 | #include 3 | #include 4 | int 5 | main(int argc, char **argv) 6 | { 7 | int c, eol; 8 | if (argc != 2) { 9 | fprintf(stderr, "usage: %s name outfile.h\n", 10 | argv[0]); 11 | exit(EXIT_FAILURE); 12 | } 13 | printf("/* this file was automatically generated */\n" 14 | "static char %s[] =", argv[1]); 15 | eol = 1; 16 | while ((c = getchar()) != EOF) { 17 | if (eol) { 18 | printf("\n\""); 19 | eol = 0; 20 | } 21 | switch (c) { 22 | case '\n': printf("\\n\""); eol = 1; break; 23 | case '"': printf("\\\""); break; 24 | case '\\': printf("\\\\"); break; 25 | default: putchar(c); 26 | } 27 | } 28 | printf(";\n" 29 | "static const size_t %s_len = sizeof(%s) - 1;\n", 30 | argv[1], argv[1]); 31 | return (0); 32 | } 33 | -------------------------------------------------------------------------------- /conv.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2011 Emil Mikulic. 3 | * 4 | * conv.h: convenience functions. 5 | */ 6 | 7 | #include 8 | 9 | void *xmalloc(const size_t size); 10 | void *xcalloc(const size_t num, const size_t size); 11 | void *xrealloc(void *original, const size_t size); 12 | char *xstrdup(const char *s); 13 | char *split_string(const char *src, const size_t left, const size_t right); 14 | void strntoupper(char *str, const size_t length); 15 | int str_starts_with(const char *haystack, const char *needle); 16 | char**split(const char delimiter, const char *str, unsigned int *num_chunks); 17 | char *qs_get(const char *qs, const char *key); 18 | 19 | void daemonize_start(void); 20 | void daemonize_finish(void); 21 | void privdrop(const char *chroot_dir, const char *privdrop_user); 22 | void fd_set_nonblock(const int fd); 23 | void fd_set_block(const int fd); 24 | 25 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 26 | -------------------------------------------------------------------------------- /contrib/cx.ath.darkstat: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UserName 6 | root 7 | GroupName 8 | wheel 9 | KeepAlive 10 | 11 | Label 12 | cx.ath.darkstat 13 | Nice 14 | 1 15 | ProgramArguments 16 | 17 | 23 | --no-daemon 24 | -i 25 | en0 26 | -b 27 | 127.0.0.1 28 | 29 | RunAtLoad 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /opt.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2012 Emil Mikulic. 3 | * 4 | * opt.h: global options 5 | */ 6 | 7 | /* Capture options. */ 8 | extern int opt_want_pppoe; 9 | extern int opt_want_macs; 10 | extern int opt_want_hexdump; 11 | extern int opt_want_snaplen; 12 | extern int opt_wait_secs; 13 | 14 | /* Error/logging options. */ 15 | extern int opt_want_verbose; 16 | extern int opt_want_syslog; 17 | 18 | /* Accounting options. */ 19 | extern unsigned int opt_highest_port; 20 | extern int opt_want_local_only; 21 | 22 | /* Hosts table reduction - when the number of entries is about to exceed 23 | * , we reduce the table to the top entries. 24 | */ 25 | extern unsigned int opt_hosts_max; 26 | extern unsigned int opt_hosts_keep; 27 | extern unsigned int opt_ports_max; 28 | extern unsigned int opt_ports_keep; 29 | 30 | /* Hosts output options. */ 31 | extern int opt_want_lastseen; 32 | 33 | /* Initialized in cap.c, added to */ 34 | extern char *title_interfaces; 35 | 36 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 37 | -------------------------------------------------------------------------------- /contrib/darkproxy.php: -------------------------------------------------------------------------------- 1 | <?php 2 | # copyright (c) 2011 Malte S. Stretz 3 | # 4 | # This is a simple proxy script to test the proper implementation of 5 | # relative hrefs in combination with eg. lighttpd 1.4 which doesn't 6 | # support the features of mod_proxy_core yet. 7 | # 8 | # You may use, modify and redistribute this file under the terms of the 9 | # GNU General Public License version 2. (see COPYING.GPL) 10 | # Feel free to relicense this code under any Open Source License 11 | # approved by the Open Source Initiative. 12 | 13 | $darkstat = "http://localhost:667"; 14 | 15 | if ($_SERVER['PATH_INFO'] == '') { 16 | header("Status: 303 Move!", true); 17 | header("Location: " . $_SERVER['PHP_SELF'] . "/", true); 18 | exit; 19 | } 20 | 21 | function header_cb($proxy, $h) { 22 | header($h); 23 | return strlen($h); 24 | } 25 | 26 | $proxy = curl_init(); 27 | curl_setopt($proxy, CURLOPT_URL, $darkstat . $_SERVER['PATH_INFO']); 28 | curl_setopt($proxy, CURLOPT_HEADERFUNCTION, 'header_cb'); 29 | curl_exec($proxy); 30 | curl_close($proxy); 31 | -------------------------------------------------------------------------------- /README.git: -------------------------------------------------------------------------------- 1 | These instructions are for developers and other people building darkstat 2 | from git instead of a release tarball. This file shouldn't end up in a 3 | release tarball or a binary package (like *.deb) 4 | 5 | To build the latest version of darkstat from git, do: 6 | 7 | git clone https://www.unix4lyfe.org/git/darkstat 8 | cd darkstat 9 | autoconf 10 | autoheader 11 | ./configure --enable-warnings 12 | make 13 | 14 | Test the binary without daemonizing it (running it in the background): 15 | 16 | sudo ./darkstat -i eth0 --no-daemon --verbose 17 | 18 | To view the manpage: 19 | 20 | nroff -man darkstat.8 | less 21 | 22 | To build with sanitizers: 23 | 24 | CFLAGS="-g -fsanitize=address -fsanitize=undefined" ./configure 25 | 26 | To see what make is doing: 27 | 28 | make V=1 29 | 30 | Suggested valgrind invocation: 31 | (note that valgrind doesn't work with a -fsanitize=address build) 32 | 33 | sudo valgrind --leak-check=full --show-reachable=yes ./darkstat -i eth0 --no-daemon --verbose --chroot $PWD --export DB --import DB --daylog DAYLOG --user $USER 34 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Changes to defaults, most recent first: 2 | 3 | - After v3.0.717, the user must specify a --chroot dir for chroot() to happen. 4 | We don't set a default in the configure script anymore. 5 | 6 | - After v3.0.708, --debug was split into --verbose and --no-daemon. 7 | 8 | - Since v3.0.694, darkstat is able to save its internal database into a file and 9 | reload it on startup. The file format is, by design, incompatible with the 10 | format from darkstat v2. 11 | 12 | - After v3.0.626, daemonizing can be suppressed with the "--debug" commandline 13 | argument, to force darkstat to stay in the foreground for debugging purposes. 14 | 15 | The "-d" argument is no longer recognized, and will prevent darkstat 16 | from starting, so make sure you adjust any startup scripts you may have. 17 | 18 | - v3.0.540 and earlier defaulted to running in the foreground and had a "-d" 19 | commandline argument to get darkstat to daemonize, detach from the controlling 20 | terminal, and run in the background. 21 | 22 | After 540, the default has been inverted. darkstat will daemonize by default. 23 | 24 | vim:set noet ts=8 sw=8 tw=80: 25 | -------------------------------------------------------------------------------- /cdefs.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * cdefs.h: compiler-specific defines 5 | * 6 | * This file borrows from FreeBSD's sys/cdefs.h 7 | */ 8 | 9 | #ifdef __GNUC__ 10 | # define _unused_ __attribute__((__unused__)) 11 | # define _noreturn_ __attribute__((__noreturn__)) 12 | # define _printflike_(fmtarg, firstvararg) \ 13 | __attribute__((__format__ (__printf__, fmtarg, firstvararg) )) 14 | #else 15 | # define _unused_ 16 | # define _noreturn_ 17 | # define _printflike_(fmtarg, firstvararg) 18 | #endif 19 | 20 | #ifndef MAX 21 | # define MAX(a,b) ((a) > (b) ? (a) : (b)) 22 | #endif 23 | 24 | #ifndef MIN 25 | # define MIN(a,b) ((a) < (b) ? (a) : (b)) 26 | #endif 27 | 28 | #if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112L 29 | # ifdef __COUNTER__ 30 | # define _Static_assert(x, y) __Static_assert(x, __COUNTER__) 31 | # else 32 | # define _Static_assert(x, y) __Static_assert(x, __LINE__) 33 | # endif 34 | # define __Static_assert(x, y) ___Static_assert(x, y) 35 | # define ___Static_assert(x, y) typedef char __assert_ ## y[(x) ? 1 : -1] 36 | #endif 37 | 38 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 39 | -------------------------------------------------------------------------------- /addr.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2011 Emil Mikulic. 3 | * 4 | * addr.h: compound IPv4/IPv6 address 5 | * (because struct sockaddr_storage stores too much) 6 | * 7 | * You may use, modify and redistribute this file under the terms of the 8 | * GNU General Public License version 2. (see COPYING.GPL) 9 | */ 10 | #ifndef __DARKSTAT_ADDR_H 11 | #define __DARKSTAT_ADDR_H 12 | 13 | #include <sys/types.h> /* for in_addr_t, at least on OpenBSD */ 14 | #include <sys/socket.h> /* for AF_INET6 */ 15 | #include <netinet/in.h> /* for in6_addr */ 16 | 17 | struct addr { 18 | union { 19 | in_addr_t v4; 20 | struct in6_addr v6; 21 | } ip; 22 | enum { IPv4 = 4, IPv6 = 6 } family; 23 | }; 24 | 25 | int addr_equal(const struct addr * const a, const struct addr * const b); 26 | const char *addr_to_str(const struct addr * const a); 27 | void addr_mask(struct addr *a, const struct addr * const mask); 28 | int addr_inside(const struct addr * const a, 29 | const struct addr * const net, const struct addr * const mask); 30 | 31 | /* Returns 0 on success, gai_strerror() code otherwise. */ 32 | int str_to_addr(const char *s, struct addr *a); 33 | 34 | #endif 35 | /* vim:set ts=3 sw=3 tw=78 et: */ 36 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installation instructions 2 | ------------------------- 3 | 4 | $ ./configure 5 | $ make 6 | $ make install 7 | 8 | 9 | Quickstart 10 | ---------- 11 | 12 | $ darkstat -i eth0 13 | 14 | 15 | Slightly slower start 16 | --------------------- 17 | 18 | $ man darkstat 19 | 20 | 21 | Packaging 22 | --------- 23 | 24 | The install target respects DESTDIR. If you are packaging darkstat or 25 | installing into a chroot, you can: 26 | 27 | $ make install DESTDIR=/chroot/whatever 28 | 29 | 30 | Portability 31 | ----------- 32 | 33 | I, the darkstat maintainer, mostly develop darkstat on Debian GNU/Linux, but 34 | mostly run darkstat on FreeBSD. 35 | 36 | darkstat usually builds out-of-the-box on FreeBSD, although you should probably 37 | install it from ports. 38 | 39 | In the past, darkstat has also been reported to work on: 40 | 41 | - Solaris (with Sun C 5.8, and libpcap installed) 42 | - Fedora Core (with libpcap-devel installed) 43 | - OpenBSD 44 | - NetBSD 45 | - Mac OS X 46 | - AIX 47 | - Ubuntu (you need build-essential, zlib1g-dev, libpcap-dev) 48 | - Mandrake 49 | - OpenSUSE 50 | 51 | Sadly, darkstat doesn't run on GNU/Hurd 0.3 because the BPF there doesn't 52 | support non-blocking operation (FIONBIO). 53 | -------------------------------------------------------------------------------- /db.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * 3 | * db.h: load and save in-memory database from/to file 4 | * copyright (c) 2007-2012 Ben Stewart, Emil Mikulic. 5 | */ 6 | 7 | #include <sys/types.h> /* for size_t */ 8 | #include <stdint.h> /* for uint64_t */ 9 | 10 | struct addr; 11 | 12 | void db_import(const char *filename); 13 | void db_export(const char *filename); 14 | void test_64order(void); 15 | 16 | /* read helpers */ 17 | unsigned int xtell(const int fd); 18 | int readn(const int fd, void *dest, const size_t len); 19 | int read8(const int fd, uint8_t *dest); 20 | int expect8(const int fd, uint8_t expecting); 21 | int read16(const int fd, uint16_t *dest); 22 | int read32(const int fd, uint32_t *dest); 23 | int read64(const int fd, uint64_t *dest); 24 | int readaddr_ipv4(const int fd, struct addr *dest); 25 | int readaddr(const int fd, struct addr *dest); 26 | int read_file_header(const int fd, const uint8_t expected[4]); 27 | 28 | /* write helpers */ 29 | int writen(const int fd, const void *dest, const size_t len); 30 | int write8(const int fd, const uint8_t i); 31 | int write16(const int fd, const uint16_t i); 32 | int write32(const int fd, const uint32_t i); 33 | int write64(const int fd, const uint64_t i); 34 | int writeaddr(const int fd, const struct addr *const a); 35 | 36 | /* vim:set ts=3 sw=3 tw=78 et: */ 37 | -------------------------------------------------------------------------------- /err.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * err.h: BSD-like err() and warn() functions 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include "cdefs.h" 20 | 21 | void err(const int code, const char *format, ...) 22 | _noreturn_ _printflike_(2, 3); 23 | void errx(const int code, const char *format, ...) 24 | _noreturn_ _printflike_(2, 3); 25 | 26 | void warn(const char *format, ...) _printflike_(1, 2); 27 | void warnx(const char *format, ...) _printflike_(1, 2); 28 | 29 | void verbosef(const char *format, ...) _printflike_(1, 2); 30 | void dverbosef(const char *format _unused_, ...) _printflike_(1, 2); 31 | 32 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 33 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | AUTHORS 2 | ------- 3 | 4 | - Emil Mikulic <emikulic@gmail.com> 5 | Primary maintainer. 6 | 7 | (please read the manpage before sending me an e-mail about how your 8 | graphs are all blank) 9 | 10 | Big thanks to everyone who helped out, in no particular order: 11 | 12 | - Ben Stewart 13 | Web interface design for v3, import/export code and file format design. 14 | 15 | - Chris Kuethe 16 | Security, cool patches, OpenBSD port maintainer. 17 | 18 | - Bartosz Kuzma 19 | DLT_PPP and DLT_PPP_SERIAL decoding, pkgsrc maintainer. 20 | 21 | - Claudio Leite - DLT_PPP_ETHER decoding. 22 | 23 | - Can Erkin Acar - BIOCSETWF patch. 24 | 25 | - Ingo Bressler - DLT_LINUX_SLL decoding. 26 | 27 | - Dennis Jansen 28 | Motivation for keeping memory use down, cool patches. 29 | 30 | - Anton S. Ustyuzhanin - DLT_RAW decoding. 31 | 32 | - Cristian Rodriguez - SUSE package maintainer. 33 | 34 | - Rene Mayorga - Debian package maintainer. 35 | 36 | - Cedric Delfosse - Debian package maintainer (retired). 37 | 38 | - Damian Lozinski - initial implementation of average KB/s on graphs. 39 | 40 | - Damien Clauzel - launchd config and Mac OS X instructions. 41 | 42 | - Mats Erik Andersson - for doing the IPv6 heavy lifting. 43 | 44 | Cyro W. Corte Real Filho, Jean-Edouard Babin, Leif Terrens, Moritz Grimm, 45 | Andreas Reimann, Colin Phipps, Cheng-Lung Sung, Martin Wilke, Piotr Kalina, 46 | Carlo Florendo, Malte S. Stretz, Dirk Koopman, and others. 47 | 48 | My apologies if I missed anyone - please mail any corrections to me (Emil) 49 | -------------------------------------------------------------------------------- /now.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * now.h: a cache of the current time. 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #include <sys/types.h> 19 | 20 | void now_init(void); 21 | void now_update(void); /* once per event loop (in darkstat.c) */ 22 | 23 | time_t now_real(void); 24 | time_t now_mono(void); 25 | 26 | /* Monotonic times can be negative (a time from before the machine booted) so 27 | * treat them as signed. */ 28 | time_t mono_to_real(const int64_t t); 29 | int64_t real_to_mono(const time_t t); 30 | 31 | /* Emits warnings if a call is too slow. */ 32 | struct timespec; 33 | void timer_start(struct timespec *t); 34 | void timer_stop(const struct timespec * const t, 35 | const int64_t nsec, 36 | const char *warning); 37 | 38 | /* vim:set ts=3 sw=3 tw=80 et: */ 39 | -------------------------------------------------------------------------------- /decode.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2012 Emil Mikulic. 3 | * 4 | * decode.h: packet decoding. 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | #ifndef __DARKSTAT_DECODE_H 10 | #define __DARKSTAT_DECODE_H 11 | 12 | #include "addr.h" 13 | 14 | #ifndef ETHER_ADDR_LEN 15 | # define ETHER_ADDR_LEN 6 16 | #endif 17 | 18 | #define IPPROTO_INVALID 254 /* special: means don't do proto accounting */ 19 | 20 | #ifndef IPPROTO_OSPF 21 | # define IPPROTO_OSPF 89 22 | #endif 23 | 24 | #define PPPOE_HDR_LEN 8 25 | 26 | /* Decoding creates a summary which is passed to accounting. */ 27 | struct pktsummary { 28 | /* Fields are in host byte order (except IPs) */ 29 | struct addr src, dst; 30 | uint16_t len; 31 | uint8_t proto; /* IPPROTO_INVALID means don't do proto accounting */ 32 | uint8_t tcp_flags; /* only for TCP */ 33 | uint16_t src_port, dst_port; /* only for TCP, UDP */ 34 | uint8_t src_mac[ETHER_ADDR_LEN], /* only for Ethernet */ 35 | dst_mac[ETHER_ADDR_LEN]; /* only for Ethernet */ 36 | }; 37 | 38 | struct pcap_pkthdr; /* from pcap.h */ 39 | 40 | #define DECODER_ARGS const struct pcap_pkthdr *pheader, \ 41 | const u_char *pdata, \ 42 | struct pktsummary *sm 43 | 44 | /* Returns 0 on decode failure (meaning accounting should not be performed) */ 45 | typedef int (decoder_fn)(DECODER_ARGS); 46 | 47 | struct linkhdr { 48 | int linktype; 49 | unsigned int hdrlen; 50 | decoder_fn *decoder; 51 | }; 52 | 53 | const struct linkhdr *getlinkhdr(const int linktype); 54 | int getsnaplen(const struct linkhdr *lh); 55 | 56 | #endif /* __DARKSTAT_DECODE_H */ 57 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 58 | -------------------------------------------------------------------------------- /html.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * 3 | * html.c: HTML header/footer templating for web interface. 4 | * copyright (c) 2006 Ben Stewart. 5 | * copyright (c) 2010 Malte S. Stretz. 6 | * 7 | * You may use, modify and redistribute this file under the terms of the 8 | * GNU General Public License version 2. (see COPYING.GPL) 9 | */ 10 | 11 | #include "config.h" 12 | #include "str.h" 13 | #include "html.h" 14 | #include "opt.h" 15 | 16 | #include <assert.h> 17 | 18 | static const char *relpaths[] = { 19 | ".", 20 | "..", 21 | "../.." 22 | }; 23 | 24 | void html_open(struct str *buf, const char *title, 25 | const unsigned int path_depth, const int want_graph_js) 26 | { 27 | const char *root; 28 | assert(path_depth < (sizeof(relpaths)/sizeof(*relpaths))); 29 | root = relpaths[path_depth]; 30 | 31 | str_appendf(buf, 32 | "<!DOCTYPE html>\n" 33 | "<html>\n" 34 | "<head>\n" 35 | "<title>%s (darkstat %s)\n" 36 | "\n" 37 | "\n" 38 | "\n", 39 | title, title_interfaces, root); 40 | 41 | if (want_graph_js) 42 | str_appendf(buf, 43 | "\n" 44 | , root); 45 | 46 | str_appendf(buf, 47 | "\n" 48 | "\n" 49 | "
\n" 50 | "
    " /* no whitespace (newlines) in list */ 51 | "
  • " PACKAGE_STRING "
  • " 52 | "
  • graphs
  • " 53 | "
  • hosts
  • " 54 | "
  • homepage
  • " 55 | "
\n" 56 | "
\n" 57 | "
\n" 58 | "

%s

\n" 59 | , root, root, title); 60 | } 61 | 62 | void html_close(struct str *buf) 63 | { 64 | str_append(buf, 65 | "
\n" 66 | "\n" 67 | "\n"); 68 | } 69 | 70 | /* vim:set ts=4 sw=4 tw=80 et: */ 71 | -------------------------------------------------------------------------------- /test_headers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # copyright (c) 2011-2012 Emil Mikulic. 3 | 4 | a="\033[33;1m" 5 | z="\033[m" 6 | problem=0 7 | 8 | check_deps() { 9 | echo checking header dependencies... 10 | # Except for the c-ify output, every header should bring in all of its 11 | # dependencies, and be able to be included multiple times. 12 | src=_test_hdr.c 13 | obj=_test_hdr.o 14 | files=`ls *.h | fgrep -v -e graphjs.h -e stylecss.h` 15 | 16 | for f in $files; do 17 | echo " * $f" 18 | cat >$src </dev/null; then 23 | echo "${a}===> $f can't be included by itself${z}" 24 | problem=1 25 | gcc -c $src 26 | else 27 | cat >$src </dev/null; then 33 | echo "${a}===> $f can't be included twice${z}" 34 | problem=1 35 | gcc -c $src 36 | fi 37 | fi 38 | done 39 | 40 | rm $src $obj 41 | } 42 | 43 | check_defines() { 44 | header=$1 45 | defines="(^|[^a-zA-Z_])($2)" 46 | files=$3 47 | 48 | echo checking $header users... 49 | 50 | # Check that files expecting defines include it. 51 | for file in `egrep -l "$defines" $files`; do 52 | if ! fgrep -q '#include "'$header'"' $file; then 53 | echo "${a}===> $file should include $header${z}" 54 | problem=1 55 | egrep --color=always "$defines" $file 56 | fi 57 | done 58 | 59 | # And that others don't. 60 | for file in `fgrep -l '#include "'$header'"' *.[ch]`; do 61 | if ! egrep -q "$defines" $file; then 62 | echo "${a}===> $file should not include $header${z}" 63 | problem=1 64 | fi 65 | done 66 | } 67 | 68 | # -=- 69 | 70 | check_deps 71 | 72 | # Check config.h: build a list of macros that are or could be defined. 73 | defines=`egrep '#define|#undef' config.h | cut -d# -f2 | cut -d' ' -f2 | 74 | sort -u | tr '\n' '|' | sed -e 's/|$//'` 75 | # Check against all files except itself. 76 | files=`ls *.[ch] | fgrep -v config.h` 77 | #echo "config.h defines: ($defines)" 78 | check_defines config.h "$defines" "$files" 79 | 80 | defines=`sed -e 's/# \+/#/;' < cdefs.h | grep '#define' | cut -d' ' -f2 | 81 | sed -e 's/(.\+/\\\\(/' | tr '\n' '|' | sed -e 's/|$//'` 82 | files=`ls *.[ch] | fgrep -v -e cdefs.h -e graphjs.h -e stylecss.h` 83 | check_defines cdefs.h "$defines" "$files" 84 | 85 | exit $problem 86 | 87 | # vim:set ts=2 sw=2 et: 88 | -------------------------------------------------------------------------------- /test_addr.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2011 Emil Mikulic. 3 | * 4 | * test_addr.c: tests for addr module 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #include "addr.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | void test(const char *in, const char *expect_out, int expect_result) 17 | { 18 | struct addr a; 19 | int success, ret; 20 | const char *out; 21 | 22 | success = 1; 23 | ret = str_to_addr(in, &a); 24 | 25 | if (ret != expect_result) 26 | success = 0; 27 | 28 | if (ret == 0) 29 | out = addr_to_str(&a); 30 | else 31 | out = "(error)"; 32 | 33 | if (expect_out && (strcmp(out, expect_out) != 0)) 34 | success = 0; 35 | 36 | printf("%s:", success ? "PASS" : "FAIL"); 37 | 38 | printf(" \"%s\" -> \"%s\"", in, out); 39 | if (expect_out && (strcmp(out, expect_out) != 0)) 40 | printf(" (expected \"%s\")", expect_out); 41 | 42 | if (ret != expect_result) 43 | printf(" [ret %d, expected %d]", ret, expect_result); 44 | 45 | if (ret != 0) 46 | printf(" [err: %s]", gai_strerror(ret)); 47 | 48 | printf("\n"); 49 | } 50 | 51 | void test_inside(const char *a, const char *net, const char *mask, int expect) 52 | { 53 | struct addr aa, anet, amask; 54 | 55 | str_to_addr(a, &aa); 56 | str_to_addr(net, &anet); 57 | str_to_addr(mask, &amask); 58 | 59 | printf("%s: %s in %s/%s\n", 60 | addr_inside(&aa, &anet, &amask) ? "PASS" : "FAIL", 61 | a, net, mask); 62 | } 63 | 64 | int main() 65 | { 66 | test("0.0.0.0", "0.0.0.0", 0); 67 | test("192.168.1.2", "192.168.1.2", 0); 68 | 69 | test("::", "::", 0); 70 | test("::0", "::", 0); 71 | test("::00", "::", 0); 72 | test("::000", "::", 0); 73 | test("::0000", "::", 0); 74 | 75 | test("::1", "::1", 0); 76 | test("::01", "::1", 0); 77 | test("::001", "::1", 0); 78 | test("::0001", "::1", 0); 79 | 80 | test("2404:6800:8004::68", "2404:6800:8004::68", 0); 81 | test("2404:6800:8004:0000:0000:0000:0000:0068", "2404:6800:8004::68", 0); 82 | 83 | test(".", NULL, EAI_NONAME); 84 | test(":", NULL, EAI_NONAME); 85 | test("23.75.345.200", NULL, EAI_NONAME); 86 | 87 | test_inside("192.168.1.2", "192.168.0.0", "255.255.0.0", 1); 88 | test_inside("2001:0200::3eff:feb1:44d7", 89 | "2001:0200::", 90 | "ffff:ffff::", 1); 91 | 92 | return 0; 93 | } 94 | 95 | /* vim:set ts=3 sw=3 tw=78 et: */ 96 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # release.sh: script to roll a release tarball of darkstat. 4 | # copyright (c) 2006-2013 Emil Mikulic. 5 | # 6 | # This is for developer use only and lives in the repo but 7 | # shouldn't end up in a tarball. 8 | # 9 | # Release checklist: 10 | # - git tag 3.0.xxx 11 | # - git push --tags 12 | # - Update website 13 | # - Mail announcement to darkstat-announce@googlegroups.com 14 | # - Update FreeBSD port, eg: http://www.freebsd.org/cgi/query-pr.cgi?pr=181277 15 | # - Update freecode.com 16 | # 17 | if [ $# -ne 1 ]; then 18 | echo "usage: $0 3.0.0rc0" >&2 19 | exit 1 20 | fi 21 | 22 | NAME=darkstat 23 | VERSION="$1" 24 | 25 | files="\ 26 | AUTHORS \ 27 | ChangeLog \ 28 | COPYING.GPL \ 29 | INSTALL \ 30 | LICENSE \ 31 | Makefile.in \ 32 | NEWS \ 33 | README \ 34 | acct.c \ 35 | acct.h \ 36 | addr.c \ 37 | addr.h \ 38 | bsd.c \ 39 | bsd.h \ 40 | cap.c \ 41 | cap.h \ 42 | cdefs.h \ 43 | contrib \ 44 | conv.c \ 45 | conv.h \ 46 | darkstat.8.in \ 47 | darkstat.c \ 48 | daylog.c \ 49 | daylog.h \ 50 | db.c \ 51 | db.h \ 52 | decode.c \ 53 | decode.h \ 54 | dns.c \ 55 | dns.h \ 56 | err.c \ 57 | err.h \ 58 | export-format.txt \ 59 | graph_db.c \ 60 | graph_db.h \ 61 | graphjs.h \ 62 | hosts_db.c \ 63 | hosts_db.h \ 64 | hosts_sort.c \ 65 | html.c \ 66 | html.h \ 67 | http.c \ 68 | http.h \ 69 | install-sh \ 70 | localip.c \ 71 | localip.h \ 72 | ncache.c \ 73 | ncache.h \ 74 | now.c \ 75 | now.h \ 76 | opt.h \ 77 | pidfile.c \ 78 | pidfile.h \ 79 | queue.h \ 80 | static \ 81 | str.c \ 82 | str.h \ 83 | stylecss.h \ 84 | tree.h \ 85 | " 86 | # end packing list 87 | 88 | say() { 89 | echo ==\> "$@" >&2 90 | } 91 | 92 | run() { 93 | say "$@" 94 | "$@" || { say ERROR!; exit 1; } 95 | } 96 | 97 | PKG=$NAME-$VERSION 98 | say releasing $PKG 99 | run make depend 100 | run make graphjs.h stylecss.h 101 | run autoconf 102 | run autoheader 103 | run ./config.status 104 | run ./test_headers.sh 105 | if git status --porcelain | egrep -v '^\?\?' -q; then 106 | say ERROR: uncommitted changes: 107 | git status 108 | exit 1 109 | fi 110 | run mkdir $PKG 111 | run cp -r $files $PKG/. 112 | run sed -e "/AC_INIT/s/darkstat, [^,)]*/darkstat, $VERSION/" configure.ac > $PKG/configure.ac 113 | say set version: `grep '^AC_INIT' $PKG/configure.ac` 114 | (cd $PKG 115 | run autoconf 116 | run autoheader 117 | run rm -r autom4te.cache 118 | ) || exit 1 119 | 120 | # package it up 121 | run tar chof $PKG.tar $PKG 122 | run bzip2 -9vv $PKG.tar 123 | say output: 124 | ls -l $PKG.tar.bz2 125 | say FINISHED! 126 | -------------------------------------------------------------------------------- /hosts_db.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * hosts_db.h: database of hosts, ports, protocols. 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | #ifndef __DARKSTAT_HOSTS_DB_H 10 | #define __DARKSTAT_HOSTS_DB_H 11 | 12 | #include /* for uint64_t */ 13 | 14 | #include "addr.h" 15 | 16 | struct hashtable; 17 | 18 | struct host { 19 | struct addr addr; 20 | char *dns; 21 | uint8_t mac_addr[6]; 22 | /* last_seen_mono is converted to/from time_t in export/import. 23 | * It can be negative (due to machine reboots). 24 | */ 25 | int64_t last_seen_mono; 26 | struct hashtable *ports_tcp; 27 | struct hashtable *ports_tcp_remote; 28 | struct hashtable *ports_udp; 29 | struct hashtable *ports_udp_remote; 30 | struct hashtable *ip_protos; 31 | }; 32 | 33 | struct port_tcp { 34 | uint16_t port; 35 | uint64_t syn; 36 | }; 37 | 38 | struct port_udp { 39 | uint16_t port; 40 | }; 41 | 42 | struct ip_proto { 43 | uint8_t proto; 44 | }; 45 | 46 | struct bucket { 47 | struct bucket *next; 48 | uint64_t in, out, total; 49 | union { 50 | struct host host; 51 | struct port_tcp port_tcp; 52 | struct port_udp port_udp; 53 | struct ip_proto ip_proto; 54 | } u; 55 | }; 56 | 57 | enum sort_dir { IN, OUT, TOTAL, LASTSEEN }; 58 | 59 | extern int hosts_db_show_macs; 60 | 61 | void hosts_db_init(void); 62 | void hosts_db_reduce(void); 63 | void hosts_db_reset(void); 64 | void hosts_db_free(void); 65 | int hosts_db_import(const int fd); 66 | int hosts_db_export(const int fd); 67 | 68 | struct bucket *host_find(const struct addr *const a); /* can return NULL */ 69 | struct bucket *host_get(const struct addr *const a); 70 | struct bucket *host_get_port_tcp(struct bucket *host, const uint16_t port); 71 | struct bucket *host_get_port_tcp_remote(struct bucket *host, 72 | const uint16_t port); 73 | struct bucket *host_get_port_udp(struct bucket *host, const uint16_t port); 74 | struct bucket *host_get_port_udp_remote(struct bucket *host, 75 | const uint16_t port); 76 | struct bucket *host_get_ip_proto(struct bucket *host, const uint8_t proto); 77 | 78 | /* Web pages. */ 79 | struct str *html_hosts(const char *uri, const char *query); 80 | 81 | /* From hosts_sort */ 82 | void qsort_buckets(const struct bucket **a, size_t n, 83 | size_t left, size_t right, const enum sort_dir d); 84 | 85 | #endif /* __DARKSTAT_HOSTS_DB_H */ 86 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 87 | -------------------------------------------------------------------------------- /addr.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2011 Emil Mikulic. 3 | * 4 | * addr.c: compound IPv4/IPv6 address 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #include "addr.h" 11 | 12 | #include /* for inet_ntop */ 13 | #include 14 | #include /* for memcmp */ 15 | #include /* for getaddrinfo */ 16 | 17 | int addr_equal(const struct addr * const a, const struct addr * const b) 18 | { 19 | if (a->family != b->family) 20 | return 0; 21 | if (a->family == IPv4) 22 | return (a->ip.v4 == b->ip.v4); 23 | else { 24 | assert(a->family == IPv6); 25 | return (memcmp(&(a->ip.v6), &(b->ip.v6), sizeof(a->ip.v6)) == 0); 26 | } 27 | } 28 | 29 | static char _addrstrbuf[INET6_ADDRSTRLEN]; 30 | const char *addr_to_str(const struct addr * const a) 31 | { 32 | if (a->family == IPv4) { 33 | struct in_addr in; 34 | in.s_addr = a->ip.v4; 35 | return (inet_ntoa(in)); 36 | } else { 37 | assert(a->family == IPv6); 38 | inet_ntop(AF_INET6, &(a->ip.v6), _addrstrbuf, sizeof(_addrstrbuf)); 39 | return (_addrstrbuf); 40 | } 41 | } 42 | 43 | int str_to_addr(const char *s, struct addr *a) 44 | { 45 | struct addrinfo hints, *ai; 46 | int ret; 47 | 48 | memset(&hints, 0, sizeof(hints)); 49 | hints.ai_family = AF_UNSPEC; 50 | hints.ai_flags = AI_NUMERICHOST; 51 | 52 | if ((ret = getaddrinfo(s, NULL, &hints, &ai)) != 0) 53 | return (ret); 54 | 55 | if (ai->ai_family == AF_INET) { 56 | a->family = IPv4; 57 | a->ip.v4 = ((const struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr; 58 | } else if (ai->ai_family == AF_INET6) { 59 | a->family = IPv6; 60 | memcpy(&(a->ip.v6), 61 | ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr.s6_addr, 62 | sizeof(a->ip.v6)); 63 | } else { 64 | ret = EAI_FAMILY; 65 | } 66 | 67 | freeaddrinfo(ai); 68 | return (ret); 69 | } 70 | 71 | void addr_mask(struct addr *a, const struct addr * const mask) 72 | { 73 | assert(a->family == mask->family); 74 | if (a->family == IPv4) 75 | a->ip.v4 &= mask->ip.v4; 76 | else { 77 | size_t i; 78 | 79 | assert(a->family == IPv6); 80 | for (i=0; iip.v6.s6_addr); i++) 81 | a->ip.v6.s6_addr[i] &= mask->ip.v6.s6_addr[i]; 82 | } 83 | } 84 | 85 | int addr_inside(const struct addr * const a, 86 | const struct addr * const net, const struct addr * const mask) 87 | { 88 | struct addr masked; 89 | 90 | assert(a->family == net->family); 91 | assert(a->family == mask->family); 92 | 93 | masked = *a; 94 | addr_mask(&masked, mask); 95 | return (addr_equal(&masked, net)); 96 | } 97 | 98 | /* vim:set ts=3 sw=3 tw=78 et: */ 99 | -------------------------------------------------------------------------------- /contrib/ReadMe.MacOS: -------------------------------------------------------------------------------- 1 | +--------------------+ 2 | | Darkstat for MacOS | 3 | +--------------------+ 4 | 5 | Darkstat run smoothly on MacOS, as a service using the Apple standard manager, launchd, or as a simple process. In any cases, the usual requirements are used for accessing network interfaces and ports than for other UNIX platforms. 6 | 7 | In order to use darkstat as a system service, we have to tell launchd the necessary informations about the new service we want it to manage. 8 | 9 | A configuration file (cx.ath.darkstat) has already been prepared, you just have to put it into the appropriate directory and to activate it. The configuration has been set-up for listening on the "en0" (ethernet) network interface, and to run darkstat with a nice of 1. 10 | 11 | 12 | 13 | +------------+ 14 | | Activation | 15 | +------------+ 16 | 17 | Procedure for installing the service's configuration: 18 | 19 | 1) Edit the file "cx.ath.darkstat" for specifying the full access path to darkstat: 20 | if you are using the macports distribution, use "/opt/local/sbin/darkstat" 21 | if you are using the fink distribution, use "/sw/sbin/darkstat" 22 | if you are using a manual installation, it is up to you. 23 | 24 | 2) Copy the configuration file: 25 | sudo cp cx.ath.darkstat /Library/LaunchDaemons/ 26 | sudo chown root:wheel /Library/LaunchDaemons/cx.ath.darkstat 27 | sudo chmod 644 /Library/LaunchDaemons/cx.ath.darkstat 28 | 29 | 3) Activate darkstat: 30 | sudo launchctl load /Library/LaunchDaemons/cx.ath.darkstat 31 | 32 | From now, darkstat is under the control of launchd and will be started at boot time. 33 | 34 | 35 | 36 | +------------------------+ 37 | | Desactivating darkstat | 38 | +------------------------+ 39 | 40 | For deactivating darkstat until the next boot, you can use the command: sudo launchctl unload /Library/LaunchDaemons/cx.ath.darkstat 41 | 42 | For permanently deactivating darkstat, you can use one of the two following solutions: 43 | 44 | 1) using the command: sudo launchctl unload -w /Library/LaunchDaemons/cx.ath.darkstat 45 | The "-w" option will modify the configuration file, adding a key forbidding the activation of the service. 46 | 47 | In order to reactivate the service, you will have to use the command: sudo launchctl load -w /Library/LaunchDaemons/cx.ath.darkstat 48 | The "-w" option will modify the configuration file, removing a key forbidding the activation of the service. 49 | 50 | 2) simply remove the file /Library/LaunchDaemons/cx.ath.darkstat 51 | 52 | 53 | 54 | +------------------+ 55 | | More information | 56 | +------------------+ 57 | 58 | Suggested further readings: 59 | - darkstat: man 1 darkstat 60 | - launchctl, the control tool for launchd: man 1 launchctl 61 | - launchd service configuration: man 5 launchd.plist 62 | - using nice: man 1 nice 63 | 64 | 65 | -- 66 | Damien Clauzel, Damien@Clauzel.nom.fr 67 | 1/07/2008 68 | -------------------------------------------------------------------------------- /pidfile.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2007-2014 Emil Mikulic. 3 | * 4 | * pidfile.h: pidfile manglement 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include "err.h" 20 | #include "str.h" 21 | #include "pidfile.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | static int pidfd = -1; 30 | static const char *pidname = NULL; 31 | 32 | void pidfile_create(const char *chroot_dir, 33 | const char *filename, 34 | const char *privdrop_user) { 35 | struct passwd *pw; 36 | 37 | if (pidfd != -1) 38 | errx(1, "pidfile already created"); 39 | 40 | errno = 0; 41 | pw = getpwnam(privdrop_user); 42 | 43 | if (pw == NULL) { 44 | if (errno == 0) 45 | errx(1, "getpwnam(\"%s\") failed: no such user", privdrop_user); 46 | else 47 | err(1, "getpwnam(\"%s\") failed", privdrop_user); 48 | } 49 | 50 | if (chroot_dir != NULL) { 51 | if (chdir(chroot_dir) == -1) { 52 | err(1, "chdir(\"%s\") failed", chroot_dir); 53 | } 54 | } 55 | pidname = filename; 56 | pidfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600); 57 | if (pidfd == -1) 58 | err(1, "couldn't not create pidfile"); 59 | if (chown(filename, pw->pw_uid, pw->pw_gid) == -1) 60 | err(1, "couldn't chown pidfile"); 61 | } 62 | 63 | void 64 | pidfile_write_close(void) 65 | { 66 | struct str *s; 67 | size_t len; 68 | char *buf; 69 | 70 | if (pidfd == -1) 71 | errx(1, "cannot write pidfile: not created"); 72 | 73 | s = str_make(); 74 | str_appendf(s, "%u\n", (unsigned int)getpid()); 75 | str_extract(s, &len, &buf); 76 | 77 | if (write(pidfd, buf, len) != (int)len) 78 | err(1, "couldn't write to pidfile"); 79 | free(buf); 80 | if (close(pidfd) == -1) 81 | warn("problem closing pidfile"); 82 | } 83 | 84 | void 85 | pidfile_unlink(void) 86 | { 87 | if (pidname == NULL) 88 | return; /* pidfile wasn't created */ 89 | if (unlink(pidname) == -1) 90 | warn("problem unlinking pidfile"); 91 | } 92 | 93 | /* vim:set ts=3 sw=3 tw=78 et: */ 94 | -------------------------------------------------------------------------------- /str.h: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * str.h: string buffer with pool-based reallocation 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #ifndef __DARKSTAT_STR_H 19 | #define __DARKSTAT_STR_H 20 | 21 | #include "cdefs.h" 22 | 23 | #include 24 | #include 25 | #include /* for uint64_t */ 26 | 27 | typedef long long signed int qd; /* as in appendf("%qd") */ 28 | typedef long long unsigned int qu; /* as in appendf("%qu") */ 29 | typedef long long unsigned int lld; /* as in printf("%lld") */ 30 | typedef long long unsigned int llu; /* as in printf("%llu") */ 31 | 32 | _Static_assert(sizeof(qd) == sizeof(int64_t), "qd must be int64_t sized"); 33 | _Static_assert(sizeof(qu) == sizeof(uint64_t), "qu must be uint64_t sized"); 34 | _Static_assert(sizeof(lld) == sizeof(int64_t), "lld must be int64_t sized"); 35 | _Static_assert(sizeof(llu) == sizeof(uint64_t), "llu must be uint64_t sized"); 36 | 37 | /* Note: the contents are 8-bit clean and not zero terminated! */ 38 | 39 | struct str; 40 | 41 | struct str *str_make(void); 42 | void str_free(struct str *s); 43 | void str_extract(struct str *buf, size_t *len, char **str); 44 | void str_appendn(struct str *buf, const char *s, const size_t len); 45 | void str_appendstr(struct str *buf, const struct str *s); 46 | 47 | #ifdef __GNUC__ 48 | /* amusing efficiency hack */ 49 | # include 50 | # define str_append(buf, s) \ 51 | str_appendn(buf, s, \ 52 | (__builtin_constant_p(s) ? sizeof(s)-1 : strlen(s)) ) 53 | #else 54 | void str_append(struct str *buf, const char *s); 55 | #endif 56 | 57 | size_t xvasprintf(char **result, const char *format, va_list va) 58 | _printflike_(2, 0); 59 | size_t xasprintf(char **result, const char *format, ...) _printflike_(2, 3); 60 | void str_vappendf(struct str *s, const char *format, va_list va) 61 | _printflike_(2, 0); 62 | void str_appendf(struct str *s, const char *format, ...) _printflike_(2, 3); 63 | 64 | struct str *length_of_time(const time_t t); 65 | ssize_t str_write(const struct str * const buf, const int fd); 66 | size_t str_len(const struct str * const buf); 67 | 68 | #endif /* __DARKSTAT_STR_H */ 69 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 70 | -------------------------------------------------------------------------------- /contrib/LisezMoi.MacOS: -------------------------------------------------------------------------------- 1 | +---------------------+ 2 | | Darkstat pour MacOS | 3 | +---------------------+ 4 | 5 | Darkstat fonctionne parfaitement sur MacOS, sous forme de service utilisant le gestionnaire standard d'Apple, launchd, ou en tant que simple processus. Dans tous les cas, les conditions habituelles d'accès aux interfaces réseaux et aux ports s'appliquent de la même façon que pour les autres plate-formes UNIX. 6 | 7 | Pour utiliser darkstat en tant que service système, nous devons donner à launchd les informations décrivant le nouveau service qu'on souhaite lui adjoindre. 8 | 9 | Un fichier de configuration (cx.ath.darkstat) a déjà été préparé, il suffit de le placer dans le répertoire approprié et de l'activer. La configuration a été conçue pour écouter sur l'interface réseau « en0 » (ethernet) et faire tourner darkstat avec une politesse de 1. 10 | 11 | 12 | 13 | +-----------------+ 14 | | Mise en service | 15 | +-----------------+ 16 | 17 | Procédure pour installer la configuration du service : 18 | 19 | 1) Éditez le fichier « cx.ath.darkstat » pour y définir le chemin d'accès complet à darkstat : 20 | si vous utiliser la distribution macports, mettez « /opt/local/sbin/darkstat » 21 | si vous utiliser la distribution fink, mettez « /sw/sbin/darkstat » 22 | si vous utiliser une installation manuelle, à vous de savoir. 23 | 24 | 2) Copiez le fichier de configuration : 25 | sudo cp cx.ath.darkstat /Library/LaunchDaemons/ 26 | sudo chown root:wheel /Library/LaunchDaemons/cx.ath.darkstat 27 | sudo chmod 644 /Library/LaunchDaemons/cx.ath.darkstat 28 | 29 | 3) Activez darkstat : 30 | sudo launchctl load /Library/LaunchDaemons/cx.ath.darkstat 31 | 32 | Désormais, darkstat est sous contrôle de launchd et sera lancé au démarrage de la machine. 33 | 34 | 35 | 36 | +---------------------+ 37 | | Désactiver darkstat | 38 | +---------------------+ 39 | 40 | Pour désactiver darkstat jusqu'au prochain démarrage, utilisez la commande : sudo launchctl unload /Library/LaunchDaemons/cx.ath.darkstat 41 | 42 | Pour désactiver darkstat de façon permanente, vous pouvez effectuer au choix l'un des deux opérations suivantes : 43 | 44 | 1) utilisez la commande : sudo launchctl unload -w /Library/LaunchDaemons/cx.ath.darkstat 45 | L'option « -w » va modifier le fichier de configuration pour ajouter une clé interdisant l'activation du service. 46 | 47 | Pour réactiver le service par la suite, il faut employer la commande : sudo launchctl load -w /Library/LaunchDaemons/cx.ath.darkstat 48 | L'option « -w » va modifier le fichier de configuration pour retirer une clé interdisant l'activation du service. 49 | 50 | 2) supprimer simplement le fichier /Library/LaunchDaemons/cx.ath.darkstat 51 | 52 | 53 | 54 | +---------------------+ 55 | | Plus d'informations | 56 | +---------------------+ 57 | 58 | Pour en savoir plus : 59 | - darkstat : man 1 darkstat 60 | - launchctl, l'outil de contrôle de launchd : man 1 launchctl 61 | - la configuration d'un service launchd : man 5 launchd.plist 62 | - définition de la politesse : man 1 nice 63 | 64 | 65 | -- 66 | Damien Clauzel, Damien@Clauzel.nom.fr 67 | 1/07/2008 68 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * 3 | * style.css: CSS stylesheet for web interface. 4 | * copyright (c) 2006 Ben Stewart. 5 | * colors broken in 2007 by Emil Mikulic. 6 | * 7 | * You may use, modify and redistribute this file under the terms of the 8 | * GNU General Public License version 2. (see COPYING.GPL) 9 | */ 10 | 11 | body { background-color: #fff; z-index: 0; } 12 | .content { z-index: 1; 13 | position: absolute; top:15px; left:10px; } 14 | div.menu { z-index: 2; 15 | position: absolute; top:0; left:0; 16 | width: 100%; background-color: #789; 17 | border-bottom: 1px solid black; 18 | font-size:11px; } 19 | ul.menu { list-style: none; margin:0; padding:2px 0 3px 0; } 20 | ul.menu li { list-style: none; display: inline; margin:0; 21 | padding:2px 0 3px 0; 22 | border-right:1px solid white; } 23 | ul.menu li.label 24 | { padding-left:10px; padding-right:10px; color:#000; 25 | text-shadow: 0px 1px 0px #9ab; } 26 | ul.menu li a { color: white; text-decoration: none; 27 | text-shadow: 0px 1px 0px #456; 28 | border-bottom: none; padding:2px 15px 3px 15px; } 29 | ul.menu li a:hover 30 | { background-color: #9ab; } 31 | h1, h2, h3, h4, h5, h6 32 | { margin-top:10px; margin-bottom:5px; color: #000000; 33 | font-family: Arial, sans-serif; font-weight:bold; } 34 | .pageheader { border-bottom: 2px dotted black; } 35 | table { border-collapse: collapse; } 36 | td, th { border:1px solid #C0C0C0; padding:1px 5px 1px 5px; } 37 | td.num { text-align:right; } 38 | th { background-color:#EFEFEF; font-weight: bold; 39 | padding-top:2px; padding-bottom:2px; } 40 | th a { color:black; border-bottom:1px dotted; } 41 | tr.alt1 { background:#FFFFFF; } 42 | tr.alt2 { background:#FAFAFA; } 43 | tr.alt1:hover, tr.alt2:hover { background:#EFEFEF; } 44 | body, td, th, p, input, textarea 45 | { font-family: Tahoma, Verdana, sans-serif; 46 | font-size: small; } 47 | tt { font-family: Courier New, monospace; 48 | font-size: small; } 49 | a:hover { border-bottom: 1px dotted #666; } 50 | a { text-decoration: none; color: #666; } 51 | div.outergraph { float:left; margin-right:10px; margin-bottom:20px; } 52 | div.graph { border: 1px solid black; } 53 | div.graphtitle { text-align:center; font-weight:bold; } 54 | div.bar_in { background: #678; } 55 | div.bar_out { background: #abc; } 56 | 57 | #graph_reload,#graph_autoreload { border:1px solid black; 58 | padding:2px 10px 2px 10px; margin-left:5px; color:black; } 59 | 60 | #graph_reload:hover,#graph_autoreload:hover { background:#9ab; color:black; } 61 | 62 | div.legend table { margin-left:auto; margin-right:auto; /* center */ 63 | border:0; } 64 | div.legend td { border:0; padding:0 0.2em 0 0.2em; font-size:11px; 65 | color:#444; } 66 | div.legend td.dir { text-align:right; } 67 | div.legend td.rate { text-align:right; white-space: nowrap; } 68 | -------------------------------------------------------------------------------- /export-format.txt: -------------------------------------------------------------------------------- 1 | The darkstat export format was designed by Ben Stewart. 2 | Note that all integers are stored in network order (big-endian). 3 | 4 | FILE HEADER 0xDA314159 darkstat export format 5 | SECTION HEADER 0xDA 'H' 'S' 0x01 hosts_db ver1 6 | HOST COUNT 0x00000001 1 host follows 7 | For each host: 8 | HOST HEADER 'H' 'S' 'T' 0x04 host ver4 9 | ADDRESS FAMILY 0x04 Either 4 or 6. 10 | IPv4 ADDR 0x0A010101 IPv4 10.1.1.1 11 | or for 0x06: 12 | IPv6 ADDR 0x0000 0000 0000 0000 0000 0000 0000 0001 13 | meaning IPv6 ::1 14 | LASTSEEN 0x0000 0000 4800 0123 64-bit time_t meaning: 15 | 2008-04-12 00:24:03 UTC 16 | MACADDR 0x001122334455 00:11:22:33:44:55 17 | HOSTNAME 0x09 "localhost" 9 is the string length 18 | IN 0x0000000000123456 Bytes in: 1193046 19 | OUT 0x0000000000789ABC Bytes out: 7903932 20 | PROTOS DATA 'P' start ip proto data 21 | IP PROTO COUNT 0x03 3 ip_proto entries 22 | IP PROTO 0x06 tcp 23 | IN 0x0000000000123456 Bytes in: 1193046 24 | OUT 0x0000000000789ABC Bytes out: 7903932 25 | IP PROTO 0x11 udp 26 | IN 0x0000000000000444 Bytes in: 1092 27 | OUT 0x0000000000000555 Bytes out: 1365 28 | IP PROTO 0x01 icmp 29 | IN 0x0000000000000001 Bytes in: 1 30 | OUT 0x0000000000000002 Bytes out: 2 31 | TCP DATA 'T' start tcp proto data 32 | TCP PROTO COUNT 0x0001 1 tcp_proto entry 33 | PORT 0x0050 http (port 80) 34 | SYN COUNT 0x0000000000000003 SYNs: 3 35 | IN 0x0000000000000001 Bytes in: 1 36 | OUT 0x0000000000000002 Bytes out: 2 37 | UDP DATA 'U' start udp proto data 38 | UDP PROTO COUNT 0x0001 1 udp_proto entry 39 | PORT 0x0045 tftp (port 69) 40 | IN 0x0000000000000001 Bytes in: 1 41 | OUT 0x0000000000000002 Bytes out: 2 42 | REMOTE TCP DATA 't' (as above) 43 | REMOTE UDP DATA 'u' (as above) 44 | SECTION HEADER 0xDA 'G' 'R' 0x01 graph_db ver1 45 | LAST_TIME (time_t as 64-bit uint) 46 | For each of 4 graphs: (60 seconds, 60 minutes, 24 hours, 31 days) 47 | 8 bits - number of bars in this graph 48 | 8 bits - index of last_time bar, in the range [0:n_bars) 49 | For each bar: 50 | 64 bits - bytes in 51 | 64 bits - bytes out 52 | 53 | Host header version 1 is just version 2 without the lastseen time. 54 | 55 | Host header version 2 is just version 3 without the address family 56 | byte (or the possibility of an IPv6 address). 57 | 58 | Host header version 3 is just version 4 without the remote TCP and UDP ports. 59 | -------------------------------------------------------------------------------- /ncache.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * ncache.c: cache of protocol and service names. 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #include "conv.h" 11 | #include "err.h" 12 | #include "ncache.h" 13 | #include "tree.h" 14 | #include "bsd.h" /* for strlcpy */ 15 | 16 | #include /* ntohs */ 17 | #include 18 | #include 19 | #include 20 | 21 | struct name_rec { 22 | RB_ENTRY(name_rec) ptree; 23 | int num; 24 | char *name; 25 | }; 26 | 27 | static int 28 | rec_cmp(struct name_rec *a, struct name_rec *b) 29 | { 30 | if (a->num < b->num) return (-1); else 31 | if (a->num > b->num) return (+1); else 32 | return (0); 33 | } 34 | 35 | RB_HEAD(nc_tree, name_rec); 36 | RB_GENERATE_STATIC(nc_tree, name_rec, ptree, rec_cmp) 37 | 38 | static struct nc_tree 39 | t_proto = RB_INITIALIZER(&name_rec), 40 | t_servtcp = RB_INITIALIZER(&name_rec), 41 | t_servudp = RB_INITIALIZER(&name_rec); 42 | 43 | static void 44 | add_rec(struct nc_tree *tree, const int num, const char *name) 45 | { 46 | struct name_rec *e, *r = xmalloc(sizeof(*r)); 47 | 48 | r->num = num; 49 | e = RB_INSERT(nc_tree, tree, r); 50 | 51 | if (e != NULL) { 52 | size_t newlen; 53 | 54 | /* record exists: append service name, free record */ 55 | newlen = strlen(e->name) + strlen(name) + 2; 56 | e->name = xrealloc(e->name, newlen); 57 | strlcat(e->name, " ", newlen); 58 | strlcat(e->name, name, newlen); 59 | free(r); 60 | } 61 | else { 62 | /* record added: fill out name field */ 63 | r->name = xstrdup(name); 64 | } 65 | } 66 | 67 | void 68 | ncache_init(void) 69 | { 70 | struct protoent *pe; 71 | struct servent *se; 72 | int count, ctcp, cudp; 73 | 74 | count = 0; 75 | setprotoent(0); 76 | while ((pe = getprotoent()) != NULL) { 77 | add_rec(&t_proto, pe->p_proto, pe->p_name); 78 | count++; 79 | } 80 | endprotoent(); 81 | verbosef("loaded %d protos", count); 82 | 83 | count = ctcp = cudp = 0; 84 | setservent(0); 85 | while ((se = getservent()) != NULL) { 86 | if (strcmp(se->s_proto, "tcp") == 0) { 87 | add_rec(&t_servtcp, ntohs(se->s_port), se->s_name); 88 | ctcp++; 89 | } 90 | else if (strcmp(se->s_proto, "udp") == 0) { 91 | add_rec(&t_servudp, ntohs(se->s_port), se->s_name); 92 | cudp++; 93 | } 94 | count++; 95 | } 96 | endservent(); 97 | verbosef("loaded %d tcp and %d udp servs, from total %d", 98 | ctcp, cudp, count); 99 | } 100 | 101 | static void 102 | tree_free(struct nc_tree *tree) 103 | { 104 | struct name_rec *curr, *next; 105 | 106 | for (curr = RB_MIN(nc_tree, tree); curr != NULL; curr = next) { 107 | next = RB_NEXT(nc_tree, tree, curr); 108 | RB_REMOVE(nc_tree, tree, curr); 109 | free(curr->name); 110 | free(curr); 111 | } 112 | } 113 | 114 | void 115 | ncache_free(void) 116 | { 117 | tree_free(&t_proto); 118 | tree_free(&t_servtcp); 119 | tree_free(&t_servudp); 120 | } 121 | 122 | #define FIND(tree,n) { \ 123 | struct name_rec r, *f; \ 124 | r.num = n; \ 125 | f = RB_FIND(nc_tree, &tree, &r); \ 126 | if (f == NULL) \ 127 | return (""); \ 128 | else \ 129 | return (f->name); \ 130 | } 131 | 132 | const char * 133 | getproto(const int proto) 134 | FIND(t_proto, proto) 135 | 136 | const char * 137 | getservtcp(const int port) 138 | FIND(t_servtcp, port) 139 | 140 | const char * 141 | getservudp(const int port) 142 | FIND(t_servudp, port) 143 | 144 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 145 | -------------------------------------------------------------------------------- /daylog.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2007-2014 Emil Mikulic. 3 | * 4 | * daylog.c: daily usage log 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #define _GNU_SOURCE 1 /* for O_NOFOLLOW on Linux */ 11 | 12 | #include "cdefs.h" 13 | #include "err.h" 14 | #include "daylog.h" 15 | #include "str.h" 16 | #include "now.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | static const char *daylog_fn = NULL; 27 | static time_t today_real, tomorrow_real; 28 | static uint64_t bytes_in, bytes_out, pkts_in, pkts_out; 29 | 30 | #define DAYLOG_DATE_LEN 26 /* strlen("1900-01-01 00:00:00 +1234") + 1 */ 31 | static char datebuf[DAYLOG_DATE_LEN]; 32 | 33 | static char *fmt_date(time_t when) { 34 | if (strftime(datebuf, 35 | DAYLOG_DATE_LEN, 36 | "%Y-%m-%d %H:%M:%S %z", 37 | localtime(&when)) == 0) 38 | errx(1, "strftime() failed in fmt_date()"); 39 | return datebuf; 40 | } 41 | 42 | /* Given some time today, find the first second of tomorrow. */ 43 | static time_t tomorrow(time_t t_before) { 44 | time_t t_after; 45 | struct tm tm, *lt; 46 | 47 | lt = localtime(&t_before); 48 | memcpy(&tm, lt, sizeof(tm)); 49 | tm.tm_sec = 0; 50 | tm.tm_min = 0; 51 | tm.tm_hour = 0; 52 | tm.tm_mday = lt->tm_mday + 1; /* tomorrow */ 53 | t_after = mktime(&tm); 54 | assert(t_after > t_before); 55 | return t_after; 56 | } 57 | 58 | /* Warns on error. */ 59 | static void daylog_write(const char *format, ...) _printflike_(1, 2); 60 | static void daylog_write(const char *format, ...) { 61 | int fd; 62 | ssize_t wr; 63 | va_list va; 64 | struct str *buf; 65 | 66 | assert(daylog_fn != NULL); 67 | fd = open(daylog_fn, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW, 0600); 68 | if (fd == -1) { 69 | warn("daylog_write: couldn't open '%s' for append", daylog_fn); 70 | return; 71 | } 72 | 73 | buf = str_make(); 74 | va_start(va, format); 75 | str_vappendf(buf, format, va); 76 | va_end(va); 77 | 78 | wr = str_write(buf, fd); 79 | if (wr == -1) 80 | warn("daylog_write: couldn't write to '%s'", daylog_fn); 81 | else if (wr != (ssize_t)str_len(buf)) 82 | warnx("daylog_write: truncated write to '%s': wrote %d of %d bytes", 83 | daylog_fn, 84 | (int)wr, 85 | (int)str_len(buf)); 86 | close(fd); 87 | str_free(buf); 88 | } 89 | 90 | static void daylog_emit(void) { 91 | daylog_write("%s|%qu|%qu|%qu|%qu|%qu\n", 92 | fmt_date(today_real), 93 | (qu)today_real, 94 | (qu)bytes_in, 95 | (qu)bytes_out, 96 | (qu)pkts_in, 97 | (qu)pkts_out); 98 | } 99 | 100 | void daylog_init(const char *filename) { 101 | daylog_fn = filename; 102 | today_real = now_real(); 103 | tomorrow_real = tomorrow(today_real); 104 | verbosef("today is %llu, tomorrow is %llu", 105 | (llu)today_real, 106 | (llu)tomorrow_real); 107 | bytes_in = bytes_out = pkts_in = pkts_out = 0; 108 | 109 | daylog_write("# logging started at %s (%qu)\n", 110 | fmt_date(today_real), (qu)today_real); 111 | } 112 | 113 | void daylog_free(void) { 114 | today_real = now_real(); 115 | daylog_emit(); /* Emit what's currently accumulated before we exit. */ 116 | daylog_write("# logging stopped at %s (%qu)\n", 117 | fmt_date(today_real), (qu)today_real); 118 | } 119 | 120 | void daylog_acct(uint64_t amount, enum graph_dir dir) { 121 | if (daylog_fn == NULL) 122 | return; /* daylogging disabled */ 123 | 124 | /* Check if we need to update the log. */ 125 | if (now_real() >= tomorrow_real) { 126 | daylog_emit(); 127 | 128 | today_real = now_real(); 129 | tomorrow_real = tomorrow(today_real); 130 | bytes_in = bytes_out = pkts_in = pkts_out = 0; 131 | verbosef("updated daylog, tomorrow = %llu", (llu)tomorrow_real); 132 | } 133 | 134 | /* Accounting. */ 135 | if (dir == GRAPH_IN) { 136 | bytes_in += amount; 137 | pkts_in++; 138 | } else { 139 | assert(dir == GRAPH_OUT); 140 | bytes_out += amount; 141 | pkts_out++; 142 | } 143 | } 144 | 145 | /* vim:set ts=3 sw=3 tw=78 et: */ 146 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | # vim:set ts=8 sw=8 sts=8 noet: 2 | # 3 | # darkstat 3 4 | # copyright (c) 2001-2014 Emil Mikulic. 5 | # 6 | # You may use, modify and redistribute this file under the terms of the 7 | # GNU General Public License version 2. (see COPYING.GPL) 8 | 9 | CC = @CC@ 10 | CFLAGS = @CFLAGS@ 11 | CPP = @CPP@ 12 | CPPFLAGS = @CPPFLAGS@ 13 | INSTALL = @INSTALL@ 14 | LDFLAGS = @LDFLAGS@ 15 | LIBS = @LIBS@ 16 | 17 | HOSTCC ?= $(CC) 18 | HOSTCFLAGS ?= $(CFLAGS) 19 | 20 | prefix = @prefix@ 21 | exec_prefix = @exec_prefix@ 22 | sbindir = @sbindir@ 23 | datarootdir = @datarootdir@ 24 | mandir = @mandir@ 25 | 26 | SRCS = \ 27 | acct.c \ 28 | addr.c \ 29 | bsd.c \ 30 | cap.c \ 31 | conv.c \ 32 | darkstat.c \ 33 | daylog.c \ 34 | db.c \ 35 | decode.c \ 36 | dns.c \ 37 | err.c \ 38 | graph_db.c \ 39 | hosts_db.c \ 40 | hosts_sort.c \ 41 | html.c \ 42 | http.c \ 43 | localip.c \ 44 | ncache.c \ 45 | now.c \ 46 | pidfile.c \ 47 | str.c 48 | 49 | OBJS = $(SRCS:%.c=%.o) 50 | 51 | STATICHS = \ 52 | stylecss.h \ 53 | graphjs.h 54 | 55 | all: darkstat 56 | 57 | darkstat: $(OBJS) 58 | $(AM_V_LINK) 59 | $(AM_V_at)$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LIBS) -o $@ 60 | 61 | .c.o: 62 | $(AM_V_CC) 63 | $(AM_V_at)$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ 64 | 65 | clean: 66 | rm -f darkstat 67 | rm -f $(OBJS) 68 | rm -f $(STATICHS) 69 | rm -f c-ify 70 | 71 | depend: config.status $(STATICHS) 72 | cp Makefile.in Makefile.in.old 73 | sed '/^# Automatically generated dependencies$$/,$$d' \ 74 | Makefile.in 75 | echo "# Automatically generated dependencies" >>Makefile.in 76 | $(CPP) $(CPPFLAGS) -MM $(SRCS) >>Makefile.in 77 | ./config.status 78 | rm -f Makefile.in.old 79 | 80 | graphjs.h: static/graph.js 81 | $(AM_V_CIFY) 82 | $(AM_V_at)./c-ify graph_js $@ 83 | 84 | stylecss.h: static/style.css 85 | $(AM_V_CIFY) 86 | $(AM_V_at)./c-ify style_css $@ 87 | 88 | $(STATICHS): c-ify 89 | c-ify: static/c-ify.c 90 | $(AM_V_HOSTCC) 91 | $(AM_V_at)$(HOSTCC) $(HOSTCFLAGS) static/c-ify.c -o $@ 92 | 93 | install: darkstat 94 | $(INSTALL) -d $(DESTDIR)$(sbindir) 95 | $(INSTALL) -m 555 darkstat $(DESTDIR)$(sbindir) 96 | $(INSTALL) -d $(DESTDIR)$(mandir)/man8 97 | $(INSTALL) -m 444 darkstat.8 $(DESTDIR)$(mandir)/man8 98 | 99 | .PHONY: all install clean depend 100 | 101 | # silent-rules 102 | AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ 103 | AM_V_CC = $(am__v_CC_$(V)) 104 | am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) 105 | am__v_CC_0 = @echo " CC " $@; 106 | AM_V_LINK = $(am__v_LINK_$(V)) 107 | am__v_LINK_ = $(am__v_LINK_$(AM_DEFAULT_VERBOSITY)) 108 | am__v_LINK_0 = @echo " LINK " $@; 109 | AM_V_HOSTCC = $(am__v_HOSTCC_$(V)) 110 | am__v_HOSTCC_ = $(am__v_HOSTCC_$(AM_DEFAULT_VERBOSITY)) 111 | am__v_HOSTCC_0 = @echo " HOSTCC" $@; 112 | AM_V_CIFY = $(am__v_CIFY_$(V)) 113 | am__v_CIFY_ = $(am__v_CIFY_$(AM_DEFAULT_VERBOSITY)) 114 | am__v_CIFY_0 = @echo " C-IFY " $@; 115 | AM_V_at = $(am__v_at_$(V)) 116 | am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) 117 | am__v_at_0 = @ 118 | 119 | # Automatically generated dependencies 120 | acct.o: acct.c acct.h decode.h addr.h conv.h daylog.h graph_db.h err.h \ 121 | cdefs.h hosts_db.h localip.h now.h opt.h 122 | addr.o: addr.c addr.h 123 | bsd.o: bsd.c bsd.h config.h cdefs.h 124 | cap.o: cap.c acct.h cdefs.h cap.h config.h conv.h decode.h addr.h err.h \ 125 | hosts_db.h localip.h now.h opt.h queue.h str.h 126 | conv.o: conv.c conv.h err.h cdefs.h 127 | darkstat.o: darkstat.c acct.h cap.h cdefs.h config.h conv.h daylog.h \ 128 | graph_db.h db.h dns.h err.h hosts_db.h addr.h http.h localip.h ncache.h \ 129 | now.h pidfile.h str.h 130 | daylog.o: daylog.c cdefs.h err.h daylog.h graph_db.h str.h now.h 131 | db.o: db.c err.h cdefs.h hosts_db.h addr.h graph_db.h db.h 132 | decode.o: decode.c cdefs.h decode.h addr.h err.h opt.h 133 | dns.o: dns.c cdefs.h cap.h conv.h decode.h addr.h dns.h err.h hosts_db.h \ 134 | queue.h str.h tree.h bsd.h config.h 135 | err.o: err.c cdefs.h err.h opt.h pidfile.h bsd.h config.h 136 | graph_db.o: graph_db.c cap.h conv.h db.h acct.h err.h cdefs.h str.h \ 137 | html.h graph_db.h now.h opt.h 138 | hosts_db.o: hosts_db.c cdefs.h conv.h decode.h addr.h dns.h err.h \ 139 | hosts_db.h db.h html.h ncache.h now.h opt.h str.h 140 | hosts_sort.o: hosts_sort.c cdefs.h err.h hosts_db.h addr.h 141 | html.o: html.c config.h str.h cdefs.h html.h opt.h 142 | http.o: http.c cdefs.h config.h conv.h err.h graph_db.h hosts_db.h addr.h \ 143 | http.h now.h queue.h str.h stylecss.h graphjs.h 144 | localip.o: localip.c addr.h bsd.h config.h conv.h err.h cdefs.h localip.h \ 145 | now.h 146 | ncache.o: ncache.c conv.h err.h cdefs.h ncache.h tree.h bsd.h config.h 147 | now.o: now.c err.h cdefs.h now.h str.h 148 | pidfile.o: pidfile.c err.h cdefs.h str.h pidfile.h 149 | str.o: str.c conv.h err.h cdefs.h str.h 150 | -------------------------------------------------------------------------------- /bsd.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2011 Emil Mikulic. 3 | * 4 | * bsd.c: *BSD compatibility. 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include "bsd.h" 20 | #include "cdefs.h" 21 | #include "config.h" 22 | 23 | #include /* for strlen */ 24 | 25 | /* strlcpy() and strlcat() are derived from: 26 | * 27 | * $OpenBSD: strlcpy.c,v 1.4 28 | * $FreeBSD: src/lib/libc/string/strlcpy.c,v 1.8 29 | * 30 | * $OpenBSD: strlcat.c,v 1.2 31 | * $FreeBSD: src/lib/libc/string/strlcat.c,v 1.10 32 | * 33 | * under the following license: 34 | * 35 | * Copyright (c) 1998 Todd C. Miller 36 | * All rights reserved. 37 | * 38 | * Redistribution and use in source and binary forms, with or without 39 | * modification, are permitted provided that the following conditions 40 | * are met: 41 | * 1. Redistributions of source code must retain the above copyright 42 | * notice, this list of conditions and the following disclaimer. 43 | * 2. Redistributions in binary form must reproduce the above copyright 44 | * notice, this list of conditions and the following disclaimer in the 45 | * documentation and/or other materials provided with the distribution. 46 | * 3. The name of the author may not be used to endorse or promote products 47 | * derived from this software without specific prior written permission. 48 | * 49 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 50 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 51 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 52 | * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 53 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 54 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 55 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 56 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 57 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 58 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | */ 60 | 61 | #ifndef HAVE_STRLCPY 62 | /* 63 | * Copy src to string dst of size siz. At most siz-1 characters 64 | * will be copied. Always NUL terminates (unless siz == 0). 65 | * Returns strlen(src); if retval >= siz, truncation occurred. 66 | */ 67 | size_t 68 | strlcpy(char * restrict dst, const char * restrict src, const size_t siz) 69 | { 70 | char *d = dst; 71 | const char *s = src; 72 | size_t n = siz; 73 | 74 | /* Copy as many bytes as will fit */ 75 | if (n != 0 && --n != 0) { 76 | do { 77 | if ((*d++ = *s++) == 0) 78 | break; 79 | } while (--n != 0); 80 | } 81 | 82 | /* Not enough room in dst, add NUL and traverse rest of src */ 83 | if (n == 0) { 84 | if (siz != 0) 85 | *d = '\0'; /* NUL-terminate dst */ 86 | while (*s++) 87 | ; 88 | } 89 | 90 | return (size_t)(s - src - 1); /* count does not include NUL */ 91 | } 92 | #endif 93 | 94 | #ifndef HAVE_STRLCAT 95 | /* 96 | * Appends src to string dst of size siz (unlike strncat, siz is the 97 | * full size of dst, not space left). At most siz-1 characters 98 | * will be copied. Always NUL terminates (unless siz <= strlen(dst)). 99 | * Returns strlen(src) + MIN(siz, strlen(initial dst)). 100 | * If retval >= siz, truncation occurred. 101 | */ 102 | size_t 103 | strlcat(char * restrict dst, const char * restrict src, const size_t siz) 104 | { 105 | char *d = dst; 106 | const char *s = src; 107 | size_t n = siz; 108 | size_t dlen; 109 | 110 | /* Find the end of dst and adjust bytes left but don't go past end */ 111 | while (n-- != 0 && *d != '\0') 112 | d++; 113 | dlen = (size_t)(d - dst); 114 | n = siz - dlen; 115 | 116 | if (n == 0) 117 | return(dlen + strlen(s)); 118 | while (*s != '\0') { 119 | if (n != 1) { 120 | *d++ = *s; 121 | n--; 122 | } 123 | s++; 124 | } 125 | *d = '\0'; 126 | 127 | return (dlen + (size_t)(s - src)); /* count does not include NUL */ 128 | } 129 | #endif 130 | 131 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 132 | -------------------------------------------------------------------------------- /localip.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2012 Emil Mikulic. 3 | * 4 | * localip.c: determine local IPs of an interface 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #include "addr.h" 11 | #include "bsd.h" /* for strlcpy */ 12 | #include "config.h" /* for HAVE_IFADDRS_H */ 13 | #include "conv.h" 14 | #include "err.h" 15 | #include "localip.h" 16 | #include "now.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef HAVE_IFADDRS_H 28 | # include 29 | #else 30 | # ifdef HAVE_SYS_SOCKIO_H 31 | # include /* for SIOCGIFADDR, especially on Solaris */ 32 | # endif 33 | # include 34 | #endif 35 | 36 | void localip_init(struct local_ips *ips) { 37 | ips->is_valid = 0; 38 | ips->last_update_mono = 0; 39 | ips->num_addrs = 0; 40 | ips->addrs = NULL; 41 | } 42 | 43 | void localip_free(struct local_ips *ips) { 44 | if (ips->addrs != NULL) 45 | free(ips->addrs); 46 | } 47 | 48 | static void add_ip(const char *iface, 49 | struct local_ips *ips, 50 | int *idx, 51 | struct addr *a) { 52 | if (ips->num_addrs <= *idx) { 53 | /* Grow. */ 54 | ips->addrs = xrealloc(ips->addrs, sizeof(*(ips->addrs)) * (*idx + 1)); 55 | ips->num_addrs++; 56 | assert(ips->num_addrs > *idx); 57 | verbosef("interface '%s' gained new address %s", iface, addr_to_str(a)); 58 | } else { 59 | /* Warn about changed address. */ 60 | if (!addr_equal(ips->addrs + *idx, a)) { 61 | static char before[INET6_ADDRSTRLEN]; 62 | strncpy(before, addr_to_str(ips->addrs + *idx), INET6_ADDRSTRLEN); 63 | verbosef("interface '%s' address %d/%d changed from %s to %s", 64 | iface, *idx+1, ips->num_addrs, before, addr_to_str(a)); 65 | } 66 | } 67 | ips->addrs[*idx] = *a; 68 | (*idx)++; 69 | } 70 | 71 | /* Returns 0 on failure. */ 72 | void localip_update(const char *iface, struct local_ips *ips) { 73 | struct addr a; 74 | int new_addrs = 0; 75 | 76 | if (iface == NULL) { 77 | /* reading from capfile */ 78 | ips->is_valid = 0; 79 | return; 80 | } 81 | 82 | if (ips->last_update_mono == now_mono()) { 83 | /* Too soon, bail out. */ 84 | return; 85 | } 86 | ips->last_update_mono = now_mono(); 87 | 88 | #ifdef HAVE_IFADDRS_H 89 | { 90 | struct ifaddrs *ifas, *ifa; 91 | 92 | if (getifaddrs(&ifas) < 0) 93 | err(1, "getifaddrs() failed"); 94 | 95 | for (ifa=ifas; ifa; ifa=ifa->ifa_next) { 96 | if (strncmp(ifa->ifa_name, iface, IFNAMSIZ)) 97 | continue; /* Wrong interface. */ 98 | 99 | if (!ifa->ifa_addr) 100 | continue; /* This can be NULL, e.g. for ppp0. */ 101 | 102 | if (ifa->ifa_addr->sa_family == AF_INET) { 103 | a.family = IPv4; 104 | a.ip.v4 = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; 105 | add_ip(iface, ips, &new_addrs, &a); 106 | } 107 | if (ifa->ifa_addr->sa_family == AF_INET6) { 108 | struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)ifa->ifa_addr; 109 | # if 0 110 | if ( IN6_IS_ADDR_LINKLOCAL(&(sa6->sin6_addr)) 111 | || IN6_IS_ADDR_SITELOCAL(&(sa6->sin6_addr)) ) 112 | continue; 113 | # endif 114 | a.family = IPv6; 115 | memcpy(&(a.ip.v6), &sa6->sin6_addr, sizeof(a.ip.v6)); 116 | add_ip(iface, ips, &new_addrs, &a); 117 | } 118 | } 119 | freeifaddrs(ifas); 120 | } 121 | #else /* don't HAVE_IFADDRS_H */ 122 | { 123 | int tmp = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); 124 | struct ifreq ifr; 125 | struct sockaddr sa; 126 | 127 | strlcpy(ifr.ifr_name, iface, IFNAMSIZ); 128 | ifr.ifr_addr.sa_family = AF_INET; 129 | if (ioctl(tmp, SIOCGIFADDR, &ifr) != -1) { 130 | sa = ifr.ifr_addr; 131 | a.family = IPv4; 132 | a.ip.v4 = ((struct sockaddr_in*)(&ifr.ifr_addr))->sin_addr.s_addr; 133 | add_ip(iface, ips, &new_addrs, &a); 134 | } 135 | close(tmp); 136 | } 137 | #endif 138 | if (new_addrs == 0) { 139 | if (ips->is_valid) 140 | verbosef("interface '%s' no longer has any addresses", iface); 141 | ips->is_valid = 0; 142 | } else { 143 | if (!ips->is_valid) 144 | verbosef("interface '%s' now has addresses", iface); 145 | ips->is_valid = 1; 146 | if (ips->num_addrs != new_addrs) 147 | verbosef("interface '%s' number of addresses decreased from %d to %d", 148 | iface, ips->num_addrs, new_addrs); 149 | ips->num_addrs = new_addrs; 150 | } 151 | } 152 | 153 | int is_localip(const struct addr * const a, 154 | const struct local_ips * const ips) { 155 | int i; 156 | 157 | for (i=0; inum_addrs; i++) { 158 | if (addr_equal(a, ips->addrs+i)) 159 | return 1; 160 | } 161 | return 0; 162 | } 163 | 164 | /* vim:set ts=3 sw=3 tw=80 et: */ 165 | -------------------------------------------------------------------------------- /now.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2012-2014 Emil Mikulic. 3 | * 4 | * now.c: a cache of the current time. 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #include "err.h" 19 | #include "now.h" 20 | #include "str.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #if defined(__MACH__) && !defined(__gnu_hurd__) 27 | /* Fake up clock_gettime() on OS X. */ 28 | # include 29 | # include 30 | # include 31 | # include 32 | 33 | typedef int clockid_t; 34 | # define CLOCK_REALTIME 0 35 | # define CLOCK_MONOTONIC 1 36 | 37 | static uint64_t mono_first = 0; 38 | 39 | int clock_gettime(clockid_t clk_id, struct timespec *tp) { 40 | if (clk_id == CLOCK_REALTIME) { 41 | struct timeval tv; 42 | gettimeofday(&tv, NULL); 43 | tp->tv_sec = tv.tv_sec; 44 | tp->tv_nsec = tv.tv_usec * 1000; 45 | return 0; 46 | } 47 | if (clk_id == CLOCK_MONOTONIC) { 48 | uint64_t t = mach_absolute_time(); 49 | mach_timebase_info_data_t timebase; 50 | mach_timebase_info(&timebase); 51 | if (!mono_first) { 52 | mono_first = t; 53 | } 54 | uint64_t tdiff = (t - mono_first) * timebase.numer / timebase.denom; 55 | tp->tv_sec = tdiff / 1000000000; 56 | tp->tv_nsec = tdiff % 1000000000; 57 | return 0; 58 | } 59 | return -1; 60 | } 61 | #endif /* __MACH__ */ 62 | 63 | static struct timespec clock_real, clock_mono; 64 | static int now_initialized = 0; 65 | 66 | time_t now_real(void) { 67 | assert(now_initialized); 68 | return clock_real.tv_sec; 69 | } 70 | 71 | time_t now_mono(void) { 72 | assert(now_initialized); 73 | return clock_mono.tv_sec; 74 | } 75 | 76 | static int before(const struct timespec *a, const struct timespec *b) { 77 | if (a->tv_sec < b->tv_sec) 78 | return 1; 79 | if (a->tv_sec == b->tv_sec && a->tv_nsec < b->tv_nsec) 80 | return 1; 81 | return 0; 82 | } 83 | 84 | static void warn_backwards(const char *name, 85 | const struct timespec * const t0, 86 | const struct timespec * const t1) { 87 | verbosef("%s clock went backwards from %lld.%09lld to %lld.%09lld", 88 | name, 89 | (lld)t0->tv_sec, 90 | (lld)t0->tv_nsec, 91 | (lld)t1->tv_sec, 92 | (lld)t1->tv_nsec); 93 | } 94 | 95 | static void clock_update(const clockid_t clk_id, 96 | struct timespec *dest, 97 | const char *name) { 98 | struct timespec t; 99 | 100 | clock_gettime(clk_id, &t); 101 | if (now_initialized && before(&t, dest)) { 102 | warn_backwards(name, &t, dest); 103 | } 104 | memcpy(dest, &t, sizeof(t)); 105 | } 106 | 107 | static void all_clocks_update(void) { 108 | clock_update(CLOCK_REALTIME, &clock_real, "realtime"); 109 | clock_update(CLOCK_MONOTONIC, &clock_mono, "monotonic"); 110 | } 111 | 112 | void now_init(void) { 113 | assert(!now_initialized); 114 | all_clocks_update(); 115 | now_initialized = 1; 116 | } 117 | 118 | void now_update(void) { 119 | assert(now_initialized); 120 | all_clocks_update(); 121 | } 122 | 123 | time_t mono_to_real(const int64_t t) { 124 | assert(now_initialized); 125 | return (time_t)(t - (int64_t)clock_mono.tv_sec + (int64_t)clock_real.tv_sec); 126 | } 127 | 128 | int64_t real_to_mono(const time_t t) { 129 | assert(now_initialized); 130 | return (int64_t)(t - clock_real.tv_sec + clock_mono.tv_sec); 131 | } 132 | 133 | void timer_start(struct timespec *t) { 134 | clock_gettime(CLOCK_MONOTONIC, t); 135 | } 136 | 137 | static int64_t ts_diff(const struct timespec * const a, 138 | const struct timespec * const b) { 139 | return (int64_t)(a->tv_sec - b->tv_sec) * 1000000000 + 140 | a->tv_nsec - b->tv_nsec; 141 | } 142 | 143 | void timer_stop(const struct timespec * const t0, 144 | const int64_t nsec, 145 | const char *warning) { 146 | struct timespec t1; 147 | int64_t diff; 148 | 149 | clock_gettime(CLOCK_MONOTONIC, &t1); 150 | if (before(&t1, t0)) { 151 | warn_backwards("monotonic timer", t0, &t1); 152 | return; 153 | } 154 | diff = ts_diff(&t1, t0); 155 | if (diff > nsec) { 156 | warnx("%s (took %lld nsec, over threshold of %lld nsec)", 157 | warning, 158 | (lld)diff, 159 | (lld)nsec); 160 | } 161 | } 162 | 163 | /* vim:set ts=3 sw=3 tw=80 et: */ 164 | -------------------------------------------------------------------------------- /err.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2012 Emil Mikulic. 3 | * 4 | * err.c: BSD-like err() and warn() functions 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include "cdefs.h" 20 | #include "err.h" 21 | #include "opt.h" 22 | #include "pidfile.h" 23 | #include "bsd.h" /* for strlcpy */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | static void to_syslog(const char *type, const int want_err, 34 | const char *format, va_list va) _printflike_(3, 0); 35 | 36 | static void 37 | to_syslog(const char *type, const int want_err, 38 | const char *format, va_list va) 39 | { 40 | char buf[512]; 41 | size_t pos = 0; 42 | int saved_errno = errno; 43 | 44 | if (type != NULL) { 45 | strlcpy(buf, type, sizeof(buf)); 46 | pos = strlen(buf); 47 | } 48 | vsnprintf(buf+pos, sizeof(buf)-pos, format, va); 49 | if (want_err) { 50 | strlcat(buf, ": ", sizeof(buf)); 51 | strlcat(buf, strerror(saved_errno), sizeof(buf)); 52 | } 53 | syslog(LOG_DEBUG, "%s", buf); 54 | } 55 | 56 | void 57 | err(const int code, const char *format, ...) 58 | { 59 | va_list va; 60 | 61 | va_start(va, format); 62 | if (opt_want_syslog) 63 | to_syslog("ERROR: ", 1, format, va); 64 | else { 65 | fprintf(stderr, "%5d: error: ", (int)getpid()); 66 | vfprintf(stderr, format, va); 67 | fprintf(stderr, ": %s\n", strerror(errno)); 68 | } 69 | va_end(va); 70 | pidfile_unlink(); 71 | exit(code); 72 | } 73 | 74 | void 75 | errx(const int code, const char *format, ...) 76 | { 77 | va_list va; 78 | 79 | va_start(va, format); 80 | if (opt_want_syslog) 81 | to_syslog("ERROR: ", 0, format, va); 82 | else { 83 | fprintf(stderr, "%5d: error: ", (int)getpid()); 84 | vfprintf(stderr, format, va); 85 | fprintf(stderr, "\n"); 86 | } 87 | va_end(va); 88 | pidfile_unlink(); 89 | exit(code); 90 | } 91 | 92 | void 93 | warn(const char *format, ...) 94 | { 95 | va_list va; 96 | 97 | va_start(va, format); 98 | if (opt_want_syslog) 99 | to_syslog("WARNING: ", 1, format, va); 100 | else { 101 | fprintf(stderr, "%5d: warning: ", (int)getpid()); 102 | vfprintf(stderr, format, va); 103 | fprintf(stderr, ": %s\n", strerror(errno)); 104 | } 105 | va_end(va); 106 | } 107 | 108 | void 109 | warnx(const char *format, ...) 110 | { 111 | va_list va; 112 | 113 | va_start(va, format); 114 | if (opt_want_syslog) 115 | to_syslog("WARNING: ", 0, format, va); 116 | else { 117 | fprintf(stderr, "%5d: warning: ", (int)getpid()); 118 | vfprintf(stderr, format, va); 119 | fprintf(stderr, "\n"); 120 | } 121 | va_end(va); 122 | } 123 | 124 | /* We interlock verbosef() between processes by using a pipe with a single 125 | * byte in it. This pipe must be initialized before the first fork() in order 126 | * to work. Then, verbosef() will block on a read() until it is able to 127 | * retrieve the byte. After doing its business, it will put a byte back into 128 | * the pipe. 129 | * 130 | * This is completely silly and largely unnecessary. 131 | */ 132 | static int inited = 0; 133 | static int lockpipe[2]; 134 | 135 | static void unlock(void); 136 | 137 | static void 138 | initlock(void) 139 | { 140 | if (pipe(lockpipe) == -1) 141 | err(1, "pipe(lockpipe)"); 142 | inited = 1; 143 | unlock(); 144 | } 145 | 146 | static void 147 | lock(void) 148 | { 149 | char buf[1]; 150 | 151 | if (!inited) initlock(); 152 | if (read(lockpipe[0], buf, 1) != 1) { 153 | fprintf(stderr, "lock failed!\n"); 154 | pidfile_unlink(); 155 | exit(1); 156 | } 157 | } 158 | 159 | static void 160 | unlock(void) 161 | { 162 | char c = 0; 163 | 164 | if (write(lockpipe[1], &c, 1) != 1) { 165 | fprintf(stderr, "unlock failed!\n"); 166 | pidfile_unlink(); 167 | exit(1); 168 | } 169 | } 170 | 171 | void 172 | verbosef(const char *format, ...) 173 | { 174 | va_list va; 175 | 176 | if (!opt_want_verbose) return; 177 | va_start(va, format); 178 | if (opt_want_syslog) 179 | to_syslog(NULL, 0, format, va); 180 | else { 181 | lock(); 182 | fprintf(stderr, "darkstat (%05d): ", (int)getpid()); 183 | vfprintf(stderr, format, va); 184 | fprintf(stderr, "\n"); 185 | unlock(); 186 | } 187 | va_end(va); 188 | } 189 | 190 | void 191 | dverbosef(const char *format _unused_, ...) 192 | { 193 | /* disabled / do-nothing verbosef */ 194 | } 195 | 196 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 197 | -------------------------------------------------------------------------------- /queue.h: -------------------------------------------------------------------------------- 1 | /* This is a stripped down version of FreeBSD's 2 | * src/sys/sys/queue.h,v 1.60.2.1 3 | * 4 | * The original file's license: 5 | * 6 | * Copyright (c) 1991, 1993 7 | * The Regents of the University of California. All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions 11 | * are met: 12 | * 1. Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 4. Neither the name of the University nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software 19 | * without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | * SUCH DAMAGE. 32 | */ 33 | 34 | #define STAILQ_HEAD(name, type) \ 35 | struct name { \ 36 | struct type *stqh_first;/* first element */ \ 37 | struct type **stqh_last;/* addr of last next element */ \ 38 | } 39 | 40 | #define STAILQ_HEAD_INITIALIZER(head) \ 41 | { NULL, &(head).stqh_first } 42 | 43 | #define STAILQ_ENTRY(type) \ 44 | struct { \ 45 | struct type *stqe_next; /* next element */ \ 46 | } 47 | 48 | #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) 49 | 50 | #define STAILQ_FIRST(head) ((head)->stqh_first) 51 | 52 | #define STAILQ_FOREACH(var, head, field) \ 53 | for((var) = STAILQ_FIRST((head)); \ 54 | (var); \ 55 | (var) = STAILQ_NEXT((var), field)) 56 | 57 | #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) 58 | 59 | #ifdef STAILQ_INSERT_TAIL 60 | #undef STAILQ_INSERT_TAIL 61 | #endif 62 | 63 | #define STAILQ_INSERT_TAIL(head, elm, field) do { \ 64 | STAILQ_NEXT((elm), field) = NULL; \ 65 | *(head)->stqh_last = (elm); \ 66 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 67 | } while (0) 68 | 69 | #ifdef STAILQ_REMOVE_HEAD 70 | #undef STAILQ_REMOVE_HEAD 71 | #endif 72 | 73 | #define STAILQ_REMOVE_HEAD(head, field) do { \ 74 | if ((STAILQ_FIRST((head)) = \ 75 | STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ 76 | (head)->stqh_last = &STAILQ_FIRST((head)); \ 77 | } while (0) 78 | 79 | #undef LIST_HEAD 80 | #define LIST_HEAD(name, type) \ 81 | struct name { \ 82 | struct type *lh_first; /* first element */ \ 83 | } 84 | 85 | #undef LIST_HEAD_INITIALIZER 86 | #define LIST_HEAD_INITIALIZER(head) \ 87 | { NULL } 88 | 89 | #undef LIST_ENTRY 90 | #define LIST_ENTRY(type) \ 91 | struct { \ 92 | struct type *le_next; /* next element */ \ 93 | struct type **le_prev; /* address of previous next element */ \ 94 | } 95 | 96 | #undef LIST_FIRST 97 | #define LIST_FIRST(head) ((head)->lh_first) 98 | 99 | #undef LIST_FOREACH 100 | #define LIST_FOREACH(var, head, field) \ 101 | for ((var) = LIST_FIRST((head)); \ 102 | (var); \ 103 | (var) = LIST_NEXT((var), field)) 104 | 105 | #undef LIST_FOREACH_SAFE 106 | #define LIST_FOREACH_SAFE(var, head, field, tvar) \ 107 | for ((var) = LIST_FIRST((head)); \ 108 | (var) && ((tvar) = LIST_NEXT((var), field), 1); \ 109 | (var) = (tvar)) 110 | 111 | #undef LIST_INIT 112 | #define LIST_INIT(head) do { \ 113 | LIST_FIRST((head)) = NULL; \ 114 | } while (0) 115 | 116 | #undef LIST_INSERT_HEAD 117 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 118 | if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ 119 | LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ 120 | LIST_FIRST((head)) = (elm); \ 121 | (elm)->field.le_prev = &LIST_FIRST((head)); \ 122 | } while (0) 123 | 124 | #undef LIST_NEXT 125 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 126 | 127 | #undef LIST_REMOVE 128 | #define LIST_REMOVE(elm, field) do { \ 129 | if (LIST_NEXT((elm), field) != NULL) \ 130 | LIST_NEXT((elm), field)->field.le_prev = \ 131 | (elm)->field.le_prev; \ 132 | *(elm)->field.le_prev = LIST_NEXT((elm), field); \ 133 | } while (0) 134 | -------------------------------------------------------------------------------- /hosts_sort.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2012 Emil Mikulic. 3 | * 4 | * hosts_sort.c: quicksort a table of buckets. 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #include "cdefs.h" 11 | #include "err.h" 12 | #include "hosts_db.h" 13 | 14 | static int cmp_u64(const uint64_t a, const uint64_t b) { 15 | if (a < b) return (1); 16 | if (a > b) return (-1); 17 | return (0); 18 | } 19 | 20 | static int cmp_i64(const int64_t a, const int64_t b) { 21 | if (a < b) return (1); 22 | if (a > b) return (-1); 23 | return (0); 24 | } 25 | 26 | /* Comparator for sorting 'struct bucket' */ 27 | static int cmp(const struct bucket * const *x, const struct bucket * const *y, 28 | const enum sort_dir dir) { 29 | switch (dir) { 30 | case IN: 31 | return cmp_u64((*x)->in, (*y)->in); 32 | case OUT: 33 | return cmp_u64((*x)->out, (*y)->out); 34 | case TOTAL: 35 | return cmp_u64((*x)->total, (*y)->total); 36 | case LASTSEEN: 37 | return cmp_i64((*x)->u.host.last_seen_mono, 38 | (*y)->u.host.last_seen_mono); 39 | default: 40 | errx(1, "cmp: unknown direction: %d", dir); 41 | } 42 | } 43 | 44 | /* 45 | * The quicksort code is derived from FreeBSD's 46 | * src/lib/libc/stdlib/qsort.c v1.12 47 | */ 48 | 49 | /*- 50 | * Copyright (c) 1992, 1993 51 | * The Regents of the University of California. All rights reserved. 52 | * 53 | * Redistribution and use in source and binary forms, with or without 54 | * modification, are permitted provided that the following conditions 55 | * are met: 56 | * 1. Redistributions of source code must retain the above copyright 57 | * notice, this list of conditions and the following disclaimer. 58 | * 2. Redistributions in binary form must reproduce the above copyright 59 | * notice, this list of conditions and the following disclaimer in the 60 | * documentation and/or other materials provided with the distribution. 61 | * 3. All advertising materials mentioning features or use of this software 62 | * must display the following acknowledgement: 63 | * This product includes software developed by the University of 64 | * California, Berkeley and its contributors. 65 | * 4. Neither the name of the University nor the names of its contributors 66 | * may be used to endorse or promote products derived from this software 67 | * without specific prior written permission. 68 | * 69 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 70 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 71 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 72 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 73 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 74 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 75 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 76 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 77 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 78 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 79 | * SUCH DAMAGE. 80 | */ 81 | 82 | static void 83 | vecswap(const struct bucket **pi, const struct bucket **pj, int n) 84 | { 85 | if (n <= 0) 86 | return; 87 | 88 | do { 89 | const struct bucket *t = *pi; 90 | *pi++ = *pj; 91 | *pj++ = t; 92 | } while (--n > 0); 93 | } 94 | 95 | #define swap(a, b) { \ 96 | const struct bucket *t = *(const struct bucket **)(a); \ 97 | *(const struct bucket **)(a) = *(const struct bucket **)(b); \ 98 | *(const struct bucket **)(b) = t; \ 99 | } 100 | 101 | static const struct bucket ** 102 | med3(const struct bucket **a, 103 | const struct bucket **b, 104 | const struct bucket **c, 105 | const enum sort_dir dir) 106 | { 107 | return (cmp(a, b, dir) < 0) 108 | ? (cmp(b, c, dir) < 0 ? b : (cmp(a, c, dir) < 0 ? c : a )) 109 | : (cmp(b, c, dir) > 0 ? b : (cmp(a, c, dir) < 0 ? a : c )); 110 | } 111 | 112 | /* Partial sort - only sort elements in the range [left:right] */ 113 | void 114 | qsort_buckets(const struct bucket **a, size_t n, 115 | size_t left, size_t right, 116 | const enum sort_dir dir) 117 | { 118 | const struct bucket **pa, **pb, **pc, **pd, **pl, **pm, **pn; 119 | int d, r, swap_cnt; 120 | 121 | loop: 122 | swap_cnt = 0; 123 | if (n < 7) { 124 | for (pm = a+1; pm < a+n; pm++) 125 | for (pl = pm; 126 | (pl > a) && (cmp(pl-1, pl, dir) > 0); 127 | pl--) 128 | swap(pl, pl-1); 129 | return; 130 | } 131 | pm = a + (n / 2); 132 | if (n > 7) { 133 | pl = a; 134 | pn = a + (n - 1); 135 | if (n > 40) { 136 | d = (n / 8); 137 | pl = med3(pl, pl + d, pl + 2 * d, dir); 138 | pm = med3(pm - d, pm, pm + d, dir); 139 | pn = med3(pn - 2 * d, pn - d, pn, dir); 140 | } 141 | pm = med3(pl, pm, pn, dir); 142 | } 143 | swap(a, pm); 144 | pa = pb = a + 1; 145 | 146 | pc = pd = a + (n - 1); 147 | for (;;) { 148 | while (pb <= pc && (r = cmp(pb, a, dir)) <= 0) { 149 | if (r == 0) { 150 | swap_cnt = 1; 151 | swap(pa, pb); 152 | pa++; 153 | } 154 | pb++; 155 | } 156 | while (pb <= pc && (r = cmp(pc, a, dir)) >= 0) { 157 | if (r == 0) { 158 | swap_cnt = 1; 159 | swap(pc, pd); 160 | pd--; 161 | } 162 | pc--; 163 | } 164 | if (pb > pc) 165 | break; 166 | swap(pb, pc); 167 | swap_cnt = 1; 168 | pb++; 169 | pc--; 170 | } 171 | if (swap_cnt == 0) { /* Switch to insertion sort */ 172 | for (pm = a + 1; pm < a+n; pm++) 173 | for (pl = pm; 174 | (pl > a) && (cmp(pl-1, pl, dir) > 0); 175 | pl--) 176 | swap(pl, pl-1); 177 | return; 178 | } 179 | 180 | pn = a + n; 181 | r = MIN(pa - a, pb - pa); 182 | vecswap(a, pb - r, r); 183 | r = MIN(pd - pc, pn - pd - 1); 184 | vecswap(pb, pn - r, r); 185 | if (((r = pb - pa) > 1) && ((unsigned)r >= left)) 186 | qsort_buckets(a, r, left, right, dir); 187 | if (((r = pd - pc) > 1) && (n - r <= right)) { 188 | /* Iterate rather than recurse to save stack space */ 189 | if (n - r > left) 190 | left = 0; 191 | else 192 | left -= n - r; 193 | right -= n - r; 194 | a += n - r; 195 | n = r; 196 | goto loop; 197 | } 198 | /* qsort(pn - r, r, cmp);*/ 199 | } 200 | 201 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 202 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # Need at least 2.64 for PACKAGE_URL 2 | AC_PREREQ([2.64]) 3 | AC_INIT(darkstat, 3.0.0-git, , , https://unix4lyfe.org/darkstat/) 4 | AC_CONFIG_SRCDIR([darkstat.c]) 5 | AC_CONFIG_HEADER([config.h]) 6 | 7 | RULE="------------------------------------------------------------" 8 | 9 | # Allow configure-time override of PRIVDROP_USER. 10 | AC_ARG_WITH(privdrop-user, AS_HELP_STRING([--with-privdrop-user], 11 | [specify which user to drop privileges to (default: nobody)]), 12 | [_pdu="$withval"], 13 | [_pdu="nobody"]) 14 | AC_DEFINE_UNQUOTED(PRIVDROP_USER, "$_pdu", [User to privdrop to.]) 15 | 16 | # Checks for programs. 17 | AC_PROG_INSTALL 18 | AC_PROG_CC 19 | 20 | # Compiler's language features. 21 | AC_C_RESTRICT 22 | 23 | m4_pattern_allow([^AM_DEFAULT_VERBOSITY$]) 24 | AC_ARG_ENABLE([silent-rules], 25 | [ --enable-silent-rules less verbose build output (undo: 'make V=1') 26 | --disable-silent-rules verbose build output (undo: 'make V=0')]) 27 | case $enable_silent_rules in 28 | no) AM_DEFAULT_VERBOSITY=1;; 29 | *) AM_DEFAULT_VERBOSITY=0;; 30 | esac 31 | AC_SUBST([AM_DEFAULT_VERBOSITY]) 32 | 33 | # Let user disable debugging symbols so we create smaller binaries. 34 | AC_MSG_CHECKING(if we want debug code) 35 | AC_ARG_ENABLE(debug, AS_HELP_STRING([--disable-debug], 36 | [turn off debugging code and asserts]), 37 | [if test "x$enableval" = "xno" ; then 38 | CFLAGS="$CFLAGS -DNDEBUG -g0" 39 | AC_MSG_RESULT(nope) 40 | elif test "x$enableval" = "xyes" ; then 41 | AC_MSG_RESULT(sure) 42 | else 43 | CFLAGS="$CFLAGS -g$enableval" 44 | AC_MSG_RESULT(sure ($enableval)) 45 | fi], 46 | [AC_MSG_RESULT(sure)]) 47 | 48 | # Augment CFLAGS for fun. 49 | echo "int main(void){return 1;}" > conftest.$ac_ext 50 | 51 | AC_MSG_CHECKING(if your C compiler wants a hit off the pipe) 52 | save_cflags="$CFLAGS" 53 | CFLAGS="-pipe $CFLAGS" 54 | if (eval $ac_link) 2>/dev/null; then 55 | AC_MSG_RESULT(sure does) 56 | else 57 | AC_MSG_RESULT(no) 58 | CFLAGS="$save_cflags" 59 | fi 60 | 61 | AC_MSG_CHECKING(if your C compiler has a link-time optimizer) 62 | if test x$GCC = xyes; then 63 | save_cflags="$CFLAGS" 64 | CFLAGS="-flto $CFLAGS" 65 | if (eval $ac_link) 2>/dev/null; then 66 | AC_MSG_RESULT(sure does) 67 | else 68 | AC_MSG_RESULT(no) 69 | CFLAGS="$save_cflags" 70 | fi 71 | else 72 | AC_MSG_RESULT(skipped) 73 | fi 74 | 75 | AC_ARG_ENABLE(warnings, AS_HELP_STRING([--enable-warnings], 76 | [turn on lots of compile-time warnings, 77 | these are only useful for development]), 78 | [if test "x$enableval" = "xyes" ; then 79 | AC_MSG_CHECKING(if your C compiler has gcc-like --extra-warnings) 80 | save_cflags="$CFLAGS" 81 | CFLAGS="$CFLAGS -fdiagnostics-show-option --all-warnings --extra-warnings" 82 | if (eval $ac_link) 2>/dev/null; then 83 | AC_MSG_RESULT(yes) 84 | else 85 | AC_MSG_RESULT(no) 86 | CFLAGS="$save_cflags" 87 | fi 88 | 89 | AC_MSG_CHECKING(if your C compiler has clang-like -Weverything) 90 | save_cflags="$CFLAGS" 91 | CFLAGS="$CFLAGS -Weverything" 92 | if (eval $ac_link) 2>/dev/null; then 93 | AC_MSG_RESULT(yes) 94 | else 95 | AC_MSG_RESULT(no) 96 | CFLAGS="$save_cflags" 97 | fi 98 | fi]) 99 | 100 | rm -f conftest.$ac_objext conftest.$ac_ext 101 | 102 | 103 | 104 | # Check for zlib. 105 | AC_CHECK_LIB(z, deflate,, [ 106 | cat < 0) 28 | elem.removeChild( elem.childNodes.item(0) ); 29 | } 30 | 31 | function setClass(elem, c) { 32 | elem.setAttribute("class", c); 33 | elem.setAttribute("className", c); /* for MSIE */ 34 | } 35 | 36 | function setStyle(elem, s) { 37 | elem.setAttribute("style", s); 38 | elem.style.cssText = s; /* for MSIE */ 39 | } 40 | 41 | function makeElemClass(e, c) { 42 | var r = document.createElement(e); 43 | setClass(r, c); 44 | return r; 45 | } 46 | 47 | function makeClear() { 48 | var r = document.createElement("div"); 49 | setStyle(r, "clear:both"); 50 | return r; 51 | } 52 | 53 | function thousands(n) { 54 | var s = String(n); 55 | var out = ""; 56 | while (s.length > 3) { 57 | out = "," + s.substr(s.length - 3, 3) + out; 58 | s = s.substr(0, s.length - 3); 59 | } 60 | return s+out; 61 | } 62 | 63 | function fkbps(bps) { 64 | bps /= 1024; 65 | return bps.toFixed(1); 66 | } 67 | 68 | function kbps(bps) { 69 | bps /= 1024; 70 | if (bps < 1) return bps.toPrecision(2); 71 | else return bps.toFixed(1); 72 | } 73 | 74 | function min(a,b) { return (ab)?a:b; } 76 | 77 | var xh, autoreload=false; 78 | 79 | function graphs_init() { 80 | var gr = document.getElementById("graphs"); 81 | 82 | /* update message */ 83 | var msg = document.createElement("div"); 84 | msg.appendChild(document.createTextNode("Graphs are being loaded...")); 85 | msg.appendChild(document.createElement("br")); 86 | msg.appendChild(document.createElement("br")); 87 | killChildren(gr); 88 | gr.appendChild(msg); 89 | graphs.msg = msg; 90 | 91 | for (var i=0; i4G? */ 188 | if (b_total > total_max) 189 | total_max = b_total; 190 | data.push( [b_pos, b_in, b_out] ); 191 | } 192 | 193 | var igraph = makeElemClass("div", "graph"); // inner graph 194 | setStyle(igraph, 195 | "width:"+graph_width+"px; "+ 196 | "height:"+graph_height+"px; "+ 197 | "position:relative;"); 198 | 199 | var nbars = data.length; 200 | var b_width = (graph_width - bar_gap * (nbars-1)) / nbars; 201 | var next_xofs = 0; 202 | 203 | var min_i = 0, min_o = 0, 204 | max_i = 0, max_o = 0, 205 | tot_i = 0, tot_o = 0; 206 | 207 | for (var i=0; i0) { if (min_i == 0) min_i = b_i; else min_i = min(min_i, b_i); } 213 | max_i = max(max_i, b_i); 214 | tot_i += b_i; 215 | 216 | if (b_o>0) { if (min_o == 0) min_o = b_o; else min_o = min(min_o, b_o); } 217 | max_o = max(max_o, b_o); 218 | tot_o += b_o; 219 | 220 | var xofs = next_xofs; 221 | 222 | next_xofs = Math.round((b_width + bar_gap) * (i+1)); 223 | var curr_w = next_xofs - xofs - bar_gap; 224 | 225 | var h_i = Math.round( b_i * graph_height / total_max ); 226 | var h_o = Math.round( b_o * graph_height / total_max ); 227 | 228 | var label = b_p+": "+ 229 | thousands(b_i)+" bytes in, "+ 230 | thousands(b_o)+" bytes out | "+ 231 | kbps(b_i/bar_secs)+" KB/s in, "+ 232 | kbps(b_o/bar_secs)+" KB/s out"; 233 | 234 | addBar(igraph, label, "bar_in", curr_w, h_i, xofs, 0); 235 | addBar(igraph, label, "bar_out", curr_w, h_o, xofs, h_i); 236 | } 237 | 238 | function legendRow(dir_str, minb, avgb, maxb) { 239 | function makeTD(c, str) { 240 | var r = makeElemClass("td", c); 241 | r.appendChild(document.createTextNode(str)); 242 | return r; 243 | } 244 | function addToRow(row, type_str, bytes, trail) { 245 | row.appendChild( makeTD("type", type_str) ); 246 | row.appendChild( makeTD("rate", fkbps(bytes/bar_secs)+" KB/s"+trail) ); 247 | } 248 | var row = document.createElement("tr"); 249 | row.appendChild( makeTD("dir", dir_str) ); 250 | var cell = makeElemClass("td", "swatch"); 251 | var swatch = makeElemClass("div", "bar_"+dir_str); 252 | setStyle(swatch, "width:6px; height:6px;"); 253 | cell.appendChild(swatch); 254 | row.appendChild(cell); 255 | addToRow(row, "min:", minb, ","); 256 | addToRow(row, "avg:", avgb, ","); 257 | addToRow(row, "max:", maxb, ""); 258 | return row; 259 | } 260 | 261 | var glegend = makeElemClass("div", "legend"); 262 | var avg_i = tot_i / nbars, 263 | avg_o = tot_o / nbars; 264 | var tbl = document.createElement("table"); 265 | var tb = document.createElement("tbody"); /* for MSIE */ 266 | tb.appendChild( legendRow("in", min_i, avg_i, max_i) ); 267 | tb.appendChild( legendRow("out", min_o, avg_o, max_o) ); 268 | tbl.appendChild(tb); 269 | glegend.appendChild(tbl); 270 | setStyle(glegend, "width:"+graph_width+"px;"); 271 | 272 | var gtitle = makeElemClass("div", "graphtitle"); 273 | setStyle(gtitle, "width:"+graph_width+"px;"); 274 | gtitle.appendChild(document.createTextNode(title)); 275 | 276 | killChildren(graph); 277 | graph.appendChild(igraph); 278 | graph.appendChild(glegend); 279 | graph.appendChild(gtitle); 280 | } 281 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | v3.0.719 (28 December 2014) 2 | - Implement tracking of remote ports: shows which ports the host 3 | is making outgoing connections to. Long time feature request. 4 | - Bugfix: when the capture interface goes down, exit instead of 5 | busy-looping forever. 6 | - Fix "clock error" due to machine reboot. 7 | - SIGUSR1 now resets the time and bytes reported on the graphs 8 | page. 9 | - Account for all IP protocols. 10 | - Change the default ports_max to only twice the default 11 | ports_keep. 12 | 13 | v3.0.718 (25 January 2014) 14 | - (SECURITY!) Don't chroot() by default. The user must specify 15 | a --chroot dir for this to happen now. 16 | - Bring back the "--base /path" functionality. 17 | - Add explicit warning about graphs being blank if we can't get 18 | local IPs on an interface. 19 | - Don't crash in timer_stop() if monotonic time stops or goes 20 | backwards. 21 | - Lots of internal cleanups. 22 | - Use time_t instead of "long" for time. This is more correct 23 | and should fix darkstat on OpenBSD 5.5 on 32-bit systems. 24 | 25 | v3.0.717 (14 August 2013) 26 | - (OS X only) Work around lack of clock_gettime(). 27 | - Fix crash due to str_appendf() not understanding %ld. 28 | 29 | v3.0.716 (8 August 2013) 30 | - Implement support for multiple capture interfaces. 31 | - Support multiple local IPs on an interface. 32 | - Only error out if we fail to create all HTTP sockets. 33 | In particular, this helps on IPv6-incapable platforms. 34 | - Use monotonic time over wall time where appropriate. 35 | - Portability fixes for NetBSD and OpenBSD. 36 | 37 | v3.0.715 (January 2012) 38 | - Compatibility fixes for Hurd and Solaris. 39 | - Use link-time optimization and automake-like silent rules. 40 | - Support systems without ifaddrs.h again. 41 | - Continuing fixes for IPv6 support. 42 | - Only update lastseen time for sender, not recipient. 43 | - Implement --local-only: accounting for hosts on the local net. 44 | - Make failure to bind() a socket non-fatal. 45 | - Make failure to get local IP non-fatal. 46 | - Fall back to gethostbyaddr() if getnameinfo() fails. 47 | - Fix detection of IPv4 multicast addresses. 48 | - Fix decoding on OpenBSD DLT_NULL interfaces (e.g. gif(4)) 49 | 50 | v3.0.714 (June 2011) 51 | - IPv6 support! Big ups to Mats Erik Andersson who did most 52 | of this work! 53 | - Allow sort on last-seen, thanks to Dirk Koopman. 54 | - Support multiple bind addresses. 55 | - Add --disable-debug configure flag, thanks to Malte S. Stretz. 56 | - Make it possible to export the database without resetting it: 57 | by sending SIGUSR2. 58 | - Web: Use relative URLs, so darkstat works properly 59 | behind mod_proxy, thanks to Malte S. Stretz. 60 | 61 | v3.0.713 (March 2010) 62 | - Don't require --verbose for pcap_stats. 63 | - Survive interface going down on Linux. 64 | - Support DLT_RAW, implemented by Anton S. Ustyuzhanin. 65 | - Skip accounting for hosts or ports if their max 66 | is set to zero. 67 | - Implement --hexdump for troubleshooting. 68 | - Web: Implement --no-lastseen 69 | - Implement --snaplen manual override. 70 | - Fix snaplen problem on recent (1-2 years?) Linux kernels. 71 | - Implement --syslog 72 | - Implement --wait as a NetworkManager workaround. 73 | 74 | (there were no releases made in 2009) 75 | 76 | v3.0.712 (November 2008) 77 | - Web: Add --no-macs option to hide mac addresses. 78 | Thanks Dennis! 79 | - Web: Make tables prettier. 80 | - Host detail view now triggers a DNS lookup. 81 | - Manpage tweaks, also move from section 1 to section 8. 82 | - Track and show how long ago a host was last seen. 83 | Suggested by: Prof A Olowofoyeku (The African Chief) 84 | - Show pcap_stats (like number of packets dropped) in the web 85 | interface and also upon exit. 86 | 87 | v3.0.711 (August 2008) 88 | - Split --debug into --verbose and --no-daemon 89 | - Include launchd config and instructions for running darkstat 90 | on Mac OS X. Contributed by Damien Clauzel. 91 | - Implement PPPoE decoding on ethernet iface. (--pppoe) 92 | - Web: Add automatic reload button. Thanks Dennis! 93 | - Web: Add a graph legend with min/avg/max. 94 | - Web: Remove hashtable stats pages. 95 | 96 | v3.0.708 (May 2008) 97 | 98 | - Implement limiting of number of ports tracked per host, 99 | configurable on the commandline (--ports-max) 100 | - Optionally don't track high ports (--highest-port) 101 | Thanks Dennis! 102 | - Fix rare use-after-free resulting from hosts table reduction. 103 | - Make hosts limit configurable (--hosts-max) 104 | - Option to read from capfile as alternative to live capture 105 | (really only useful for development, benchmarking) 106 | - Add the sniffed interface name to HTML reports. 107 | Thanks Chris! 108 | 109 | v3.0.707 (Sep 2007) 110 | 111 | - Fix silly bug in formatting hex. 112 | - Check for pcap.h in include/pcap/ for old RedHat-a-likes. 113 | - New commandline parser. 114 | - To stay in foreground, pass --debug instead of -d. 115 | - We can now reset all statistics at runtime (send SIGUSR1) 116 | - Make chroot dir configurable on cmdline (--chroot) 117 | - Make privdrop user configurable on cmdline (--user) 118 | - Implement daylog (brings back a v2 feature) 119 | - Import and export hosts and graphs, this brings back a fairly 120 | major v2 feature. Big ups to Ben for doing a lot of the 121 | design and implementation of this feature! 122 | Note that the v3 database format is, by design, incompatible 123 | with the v2 format. 124 | - Report average KB/s in and out on graphs. 125 | Thanks to Damian Lozinski for suggestion and first cut at the 126 | implementation. 127 | - Fix graph rotation when the delay between rotations is big 128 | enough to clear an entire graph. 129 | - Make ip_proto 8 bits wide, to match the IP header. 130 | - Implement pidfile functionality for people who prefer to 131 | handle daemons in this manner. 132 | 133 | v3.0.619 (Apr 2007) 134 | 135 | - Decode DLT_PPP and DLT_PPP_SERIAL on NetBSD, 136 | patch courtesy of Bartosz Kuzma. 137 | - Don't use pcap_setnonblock(), with help from Colin Phipps. 138 | - Reduce the number of syscalls made. 139 | - Answer FAQ about graph axes / labels / scale. 140 | - Fix build on OpenBSD (thanks Chris!) and Solaris. 141 | - Commandline arg (-n) to disable promiscuous mode when 142 | sniffing, thanks to Chris Kuethe for the implementation. 143 | - Commandline arg (-r) to disable DNS resolver. 144 | - Track and report per-host last seen MAC address. 145 | - Move FAQ into manpage. 146 | - Implement display of start time and running time. 147 | - Web: implement sorting the hosts table by in/out/total. 148 | - Web: implement paging through the hosts table. 149 | - Web: implement full view of hosts table. 150 | - Don't die if the capture interface loses its IP address. 151 | - Make daemonize (previously -d) the default, and make -D the 152 | argument to suppress it. 153 | - Commandline arg (-l) to graph traffic entering/leaving the 154 | local network as opposed to just the local IP. v2 had this. 155 | - Allow configure-time override of CHROOT_DIR and PRIVDROP_USER. 156 | - Web: new color scheme. 157 | 158 | v3.0.540 (Aug 2006) 159 | 160 | - Fix build against old libpcap (thanks Claudio) 161 | - Fix build on AIX (thanks Andreas) 162 | - Fix build warnings on NetBSD (thanks Bartosz) 163 | - Deny writes to BPF socket (thanks Can) 164 | - Reverse-resolve IPs less aggressively. 165 | - Free up the DNS queue as we process it. 166 | - Fix dns_reply silliness. 167 | - Web: tweak the look of the top bar. 168 | - Web: update total packets and bytes as part of graph update. 169 | - Decode DLT_LINUX_SLL (ippp0 on Linux), 170 | patch courtesy of Ingo Bressler 171 | 172 | v3.0.524 (Jul 2006) 173 | 174 | - Fix build on NetBSD. 175 | - Fix shutdown on Linux. 176 | - Performance improvements. 177 | - Free the mallocs. 178 | - Work around BPF being immediate on Linux. 179 | This improves performance. 180 | - Drop privileges when we don't need them. Chroot. Generally 181 | be more paranoid. Thanks to Chris Kuethe for patches and 182 | inspiration. 183 | - Daemonize. (run in the background) 184 | - Graphs: Make the entire bar have the same label (instead of 185 | different labels for in/out), add thousands separators for 186 | legibility, include the position/index (i.e. day 22) 187 | - Instead of reducing the hosts_db based on time, do it based on 188 | its size. 189 | - Implement somewhat better handling of time moving backwards - 190 | we assume that real time is monotonic and just renumber the 191 | graph bars. (time is hard) 192 | - Greatly improve IPC with the DNS child, make it more efficient 193 | and much more robust. 194 | - Decode DLT_PPP_ETHER (pppoe0 on OpenBSD), patch courtesy of 195 | Claudio Leite. 196 | 197 | v3.0.471 (Jun 2006) 198 | 199 | First public release of darkstat 3. Almost a complete rewrite 200 | since v2.6. Architecture much improved, better portability and 201 | stability. Approximate feature parity with v2, missing 202 | loading/saving DB. 203 | 204 | v2.6 (Nov 2003) 205 | 206 | End of the line for darkstat 2. 207 | 208 | vim:set noet ts=8 sts=8 sw=8 tw=72: 209 | -------------------------------------------------------------------------------- /str.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2012 Emil Mikulic. 3 | * 4 | * str.c: string buffer with pool-based reallocation 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include "conv.h" 20 | #include "err.h" 21 | #include "str.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include /* for uint32_t on Linux and OS X */ 27 | #include 28 | 29 | #define INITIAL_LEN 1024 30 | 31 | struct str { 32 | char *buf; 33 | size_t len, pool; 34 | }; 35 | 36 | struct str * 37 | str_make(void) 38 | { 39 | struct str *s = xmalloc(sizeof(*s)); 40 | s->len = 0; 41 | s->pool = INITIAL_LEN; 42 | s->buf = xmalloc(s->pool); 43 | return (s); 44 | } 45 | 46 | void 47 | str_free(struct str *s) 48 | { 49 | free(s->buf); 50 | free(s); 51 | } 52 | 53 | /* 54 | * Extract struct str into buffer and length, freeing the struct in the 55 | * process. 56 | */ 57 | void 58 | str_extract(struct str *s, size_t *len, char **str) 59 | { 60 | *len = s->len; 61 | *str = s->buf; 62 | free(s); 63 | } 64 | 65 | void 66 | str_appendn(struct str *buf, const char *s, const size_t len) 67 | { 68 | if (buf->pool < buf->len + len) { 69 | /* pool has dried up */ 70 | while (buf->pool < buf->len + len) 71 | buf->pool *= 2; 72 | buf->buf = xrealloc(buf->buf, buf->pool); 73 | } 74 | memcpy(buf->buf + buf->len, s, len); 75 | buf->len += len; 76 | } 77 | 78 | void 79 | str_appendstr(struct str *buf, const struct str *s) 80 | { 81 | str_appendn(buf, s->buf, s->len); 82 | } 83 | 84 | #ifndef str_append 85 | void 86 | str_append(struct str *buf, const char *s) 87 | { 88 | str_appendn(buf, s, strlen(s)); 89 | } 90 | #endif 91 | 92 | /* 93 | * Apparently, some wacky locales use periods, or another character that isn't 94 | * a comma, to separate thousands. If you are afflicted by such a locale, 95 | * change this macro: 96 | */ 97 | #define COMMA ',' 98 | 99 | /* 2^32 = 4,294,967,296 (10 digits, 13 chars) */ 100 | #define I32_MAXLEN 13 101 | 102 | /* 2^64 = 18,446,744,073,709,551,616 (20 digits, 26 chars) */ 103 | #define I64_MAXLEN 26 104 | 105 | static void 106 | str_append_u32(struct str *s, const uint32_t i, const int mod_sep) 107 | { 108 | char out[I32_MAXLEN]; 109 | int pos; 110 | unsigned int len; 111 | uint32_t rem, next; 112 | 113 | if (i == 0) { 114 | str_append(s, "0"); 115 | return; 116 | } 117 | 118 | pos = sizeof(out)-1; 119 | len = 0; 120 | rem = i; 121 | 122 | while (rem > 0) { 123 | assert(pos >= 0); 124 | next = rem / 10; 125 | rem = rem - next * 10; 126 | assert(rem < 10); 127 | out[pos] = '0' + rem; 128 | pos--; 129 | len++; 130 | rem = next; 131 | if (mod_sep && (rem > 0) && (len > 0) && (len % 3 == 0)) { 132 | out[pos] = COMMA; 133 | pos--; 134 | } 135 | } 136 | str_appendn(s, out+pos+1, sizeof(out)-1-pos); 137 | } 138 | 139 | static void 140 | str_append_i32(struct str *s, int32_t i, const int mod_sep) 141 | { 142 | if (i < 0) { 143 | str_append(s, "-"); 144 | i = -i; 145 | } 146 | str_append_u32(s, (uint32_t)i, mod_sep); 147 | } 148 | 149 | static void 150 | str_append_u64(struct str *s, const uint64_t i, const int mod_sep) 151 | { 152 | char out[I64_MAXLEN]; 153 | int pos; 154 | unsigned int len; 155 | uint64_t rem, next; 156 | uint32_t rem32, next32; 157 | 158 | if (i == 0) { 159 | str_append(s, "0"); 160 | return; 161 | } 162 | 163 | pos = sizeof(out)-1; 164 | len = 0; 165 | rem = i; 166 | 167 | while (rem >= 4294967295U) { 168 | assert(pos >= 0); 169 | next = rem / 10; 170 | rem = rem - next * 10; 171 | assert(rem < 10); 172 | out[pos] = '0' + rem; 173 | pos--; 174 | len++; 175 | rem = next; 176 | if (mod_sep && (rem > 0) && (len > 0) && (len % 3 == 0)) { 177 | out[pos] = COMMA; 178 | pos--; 179 | } 180 | } 181 | 182 | /* 183 | * Stick to 32-bit math when we can as it's faster on 32-bit platforms. 184 | * FIXME: a tunable way to switch this off? 185 | */ 186 | rem32 = (uint32_t)rem; 187 | while (rem32 > 0) { 188 | assert(pos >= 0); 189 | next32 = rem32 / 10; 190 | rem32 = rem32 - next32 * 10; 191 | assert(rem32 < 10); 192 | out[pos] = '0' + rem32; 193 | pos--; 194 | len++; 195 | rem32 = next32; 196 | if (mod_sep && (rem32 > 0) && (len > 0) && (len % 3 == 0)) { 197 | out[pos] = COMMA; 198 | pos--; 199 | } 200 | } 201 | str_appendn(s, out+pos+1, sizeof(out)-1-pos); 202 | } 203 | 204 | static void 205 | str_append_i64(struct str *s, int64_t i, const int mod_sep) 206 | { 207 | if (i < 0) { 208 | str_append(s, "-"); 209 | i = -i; 210 | } 211 | str_append_u64(s, (uint64_t)i, mod_sep); 212 | } 213 | 214 | static void 215 | str_append_hex8(struct str *s, const uint8_t b) 216 | { 217 | char out[2]; 218 | static const char hexset[] = "0123456789abcdef"; 219 | 220 | out[0] = hexset[ ((b >> 4) & 15) ]; 221 | out[1] = hexset[ (b & 15) ]; 222 | str_appendn(s, out, 2); 223 | } 224 | 225 | /* accepted formats: %s %d %u %x 226 | * accepted modifiers: q and ' 227 | * 228 | * %x is equivalent to %02x and expects a uint8_t 229 | */ 230 | void str_vappendf(struct str *s, const char *format, va_list va) { 231 | size_t pos, len; 232 | len = strlen(format); 233 | 234 | for (pos=0; pos 0) 242 | str_appendn(s, format+span_start, span_len); 243 | 244 | if (format[pos] == '%') { 245 | int mod_quad = 0, mod_sep = 0; 246 | char *arg_str; 247 | FORMAT: 248 | pos++; 249 | switch (format[pos]) { 250 | case '%': 251 | str_append(s, "%"); 252 | break; 253 | case 'q': 254 | mod_quad = 1; 255 | goto FORMAT; 256 | case '\'': 257 | mod_sep = 1; 258 | goto FORMAT; 259 | case 's': 260 | arg_str = va_arg(va, char*); 261 | str_append(s, arg_str); 262 | /* str_append can be a macro! passing it va_arg can result in 263 | * va_arg being called twice 264 | */ 265 | break; 266 | case 'd': 267 | if (mod_quad) 268 | str_append_i64(s, va_arg(va, int64_t), mod_sep); 269 | else 270 | str_append_i32(s, (int32_t)va_arg(va, int), mod_sep); 271 | break; 272 | case 'u': 273 | if (mod_quad) 274 | str_append_u64(s, va_arg(va, uint64_t), mod_sep); 275 | else 276 | str_append_u32(s, (uint32_t)va_arg(va, unsigned int), mod_sep); 277 | break; 278 | case 'x': 279 | str_append_hex8(s, (uint8_t)va_arg(va, int)); 280 | break; 281 | default: 282 | errx(1, "format string is \"%s\", unknown format '%c' at %u", 283 | format, format[pos], (unsigned int)pos); 284 | } 285 | } 286 | } 287 | } 288 | 289 | void 290 | str_appendf(struct str *s, const char *format, ...) 291 | { 292 | va_list va; 293 | va_start(va, format); 294 | str_vappendf(s, format, va); 295 | va_end(va); 296 | } 297 | 298 | size_t 299 | xvasprintf(char **result, const char *format, va_list va) 300 | { 301 | size_t len; 302 | struct str *s = str_make(); 303 | str_vappendf(s, format, va); 304 | str_appendn(s, "", 1); /* "" still contains \0 */ 305 | str_extract(s, &len, result); 306 | return (len-1); 307 | } 308 | 309 | size_t 310 | xasprintf(char **result, const char *format, ...) 311 | { 312 | va_list va; 313 | size_t ret; 314 | va_start(va, format); 315 | ret = xvasprintf(result, format, va); 316 | va_end(va); 317 | return (ret); 318 | } 319 | 320 | /* 321 | * Format a length of time in seconds to "n days, n hrs, n mins, n secs". 322 | * Returns a newly allocated str. 323 | */ 324 | struct str * 325 | length_of_time(const time_t t) 326 | { 327 | struct str *buf = str_make(); 328 | int secs = t % 60; 329 | int mins = (t / 60) % 60; 330 | int hours = (t / 3600) % 24; 331 | int days = t / 86400; 332 | 333 | int show_zeroes = 0; 334 | 335 | if (days > 0) { 336 | str_appendf(buf, "%d %s", days, (days==1)?"day":"days"); 337 | show_zeroes = 1; 338 | } 339 | 340 | if (show_zeroes || (hours > 0)) { 341 | if (show_zeroes) str_append(buf, ", "); 342 | str_appendf(buf, "%d %s", hours, (hours==1)?"hr":"hrs"); 343 | show_zeroes = 1; 344 | } 345 | 346 | if (show_zeroes || (mins > 0)) { 347 | if (show_zeroes) str_append(buf, ", "); 348 | str_appendf(buf, "%d %s", mins, (mins==1)?"min":"mins"); 349 | show_zeroes = 1; 350 | } 351 | 352 | if (show_zeroes) str_append(buf, ", "); 353 | str_appendf(buf, "%d %s", secs, (secs==1)?"sec":"secs"); 354 | 355 | return buf; 356 | } 357 | 358 | ssize_t str_write(const struct str * const buf, const int fd) { 359 | return write(fd, buf->buf, buf->len); 360 | } 361 | 362 | size_t str_len(const struct str * const buf) { 363 | return buf->len; 364 | } 365 | 366 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 367 | -------------------------------------------------------------------------------- /acct.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2012 Emil Mikulic. 3 | * 4 | * acct.c: traffic accounting 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include "acct.h" 20 | #include "decode.h" 21 | #include "conv.h" 22 | #include "daylog.h" 23 | #include "err.h" 24 | #include "hosts_db.h" 25 | #include "localip.h" 26 | #include "now.h" 27 | #include "opt.h" 28 | 29 | #define __FAVOR_BSD 30 | #include 31 | #include 32 | #include 33 | #include /* for isdigit */ 34 | #include /* for gai_strerror */ 35 | #include /* for free */ 36 | #include /* for memcpy */ 37 | 38 | uint64_t acct_total_packets = 0, acct_total_bytes = 0; 39 | 40 | static int using_localnet4 = 0, using_localnet6 = 0; 41 | static struct addr localnet4, localmask4, localnet6, localmask6; 42 | 43 | /* Parse the net/mask specification into two IPs or die trying. */ 44 | void 45 | acct_init_localnet(const char *spec) 46 | { 47 | char **tokens; 48 | unsigned int num_tokens; 49 | int isnum, j, ret; 50 | int pfxlen, octets, remainder; 51 | struct addr localnet, localmask; 52 | 53 | tokens = split('/', spec, &num_tokens); 54 | if (num_tokens != 2) 55 | errx(1, "expecting network/netmask, got \"%s\"", spec); 56 | 57 | if ((ret = str_to_addr(tokens[0], &localnet)) != 0) 58 | errx(1, "couldn't parse \"%s\": %s", tokens[0], gai_strerror(ret)); 59 | 60 | /* Detect a purely numeric argument. */ 61 | isnum = 0; 62 | { 63 | const char *p = tokens[1]; 64 | while (*p != '\0') { 65 | if (isdigit(*p)) { 66 | isnum = 1; 67 | ++p; 68 | continue; 69 | } else { 70 | isnum = 0; 71 | break; 72 | } 73 | } 74 | } 75 | 76 | if (!isnum) { 77 | if ((ret = str_to_addr(tokens[1], &localmask)) != 0) 78 | errx(1, "couldn't parse \"%s\": %s", tokens[1], gai_strerror(ret)); 79 | if (localmask.family != localnet.family) 80 | errx(1, "family mismatch between net and mask"); 81 | } else { 82 | uint8_t frac, *p; 83 | char *endptr; 84 | 85 | localmask.family = localnet.family; 86 | 87 | /* Compute the prefix length. */ 88 | pfxlen = (unsigned int)strtol(tokens[1], &endptr, 10); 89 | 90 | if ((pfxlen < 0) || 91 | ((localnet.family == IPv6) && (pfxlen > 128)) || 92 | ((localnet.family == IPv4) && (pfxlen > 32)) || 93 | (tokens[1][0] == '\0') || 94 | (*endptr != '\0')) 95 | errx(1, "invalid network prefix length \"%s\"", tokens[1]); 96 | 97 | /* Construct the network mask. */ 98 | octets = pfxlen / 8; 99 | remainder = pfxlen % 8; 100 | p = (localnet.family == IPv6) ? (localmask.ip.v6.s6_addr) 101 | : ((uint8_t *) &(localmask.ip.v4)); 102 | 103 | if (localnet.family == IPv6) 104 | memset(p, 0, 16); 105 | else 106 | memset(p, 0, 4); 107 | 108 | for (j = 0; j < octets; ++j) 109 | p[j] = 0xff; 110 | 111 | frac = (uint8_t)(0xff << (8 - remainder)); 112 | if (frac) 113 | p[j] = frac; /* Have contribution for next position. */ 114 | } 115 | 116 | free(tokens[0]); 117 | free(tokens[1]); 118 | free(tokens); 119 | 120 | /* Register the correct netmask and calculate the correct net. */ 121 | addr_mask(&localnet, &localmask); 122 | if (localnet.family == IPv6) { 123 | using_localnet6 = 1; 124 | localnet6 = localnet; 125 | localmask6 = localmask; 126 | } else { 127 | using_localnet4 = 1; 128 | localnet4 = localnet; 129 | localmask4 = localmask; 130 | } 131 | 132 | verbosef("local network address: %s", addr_to_str(&localnet)); 133 | verbosef(" local network mask: %s", addr_to_str(&localmask)); 134 | } 135 | 136 | static int addr_is_local(const struct addr * const a, 137 | const struct local_ips *local_ips) { 138 | if (is_localip(a, local_ips)) 139 | return 1; 140 | if (a->family == IPv4 && using_localnet4) { 141 | if (addr_inside(a, &localnet4, &localmask4)) 142 | return 1; 143 | } else if (a->family == IPv6 && using_localnet6) { 144 | if (addr_inside(a, &localnet6, &localmask6)) 145 | return 1; 146 | } 147 | return 0; 148 | } 149 | 150 | /* Account for the given packet summary. */ 151 | void acct_for(const struct pktsummary * const sm, 152 | const struct local_ips * const local_ips) { 153 | struct bucket *hs = NULL; // Source host. 154 | struct bucket *hd = NULL; // Dest host. 155 | int dir_in, dir_out; 156 | 157 | #if 0 /* WANT_CHATTY? */ 158 | printf("%15s > ", addr_to_str(&sm->src)); 159 | printf("%15s ", addr_to_str(&sm->dst)); 160 | printf("len %4d proto %2d", sm->len, sm->proto); 161 | 162 | if (sm->proto == IPPROTO_TCP || sm->proto == IPPROTO_UDP) 163 | printf(" port %5d : %5d", sm->src_port, sm->dst_port); 164 | if (sm->proto == IPPROTO_TCP) 165 | printf(" %s%s%s%s%s%s", 166 | (sm->tcp_flags & TH_FIN)?"F":"", 167 | (sm->tcp_flags & TH_SYN)?"S":"", 168 | (sm->tcp_flags & TH_RST)?"R":"", 169 | (sm->tcp_flags & TH_PUSH)?"P":"", 170 | (sm->tcp_flags & TH_ACK)?"A":"", 171 | (sm->tcp_flags & TH_URG)?"U":"" 172 | ); 173 | printf("\n"); 174 | #endif 175 | 176 | /* Totals. */ 177 | acct_total_packets++; 178 | acct_total_bytes += sm->len; 179 | 180 | /* Graphs. */ 181 | dir_out = addr_is_local(&sm->src, local_ips); 182 | dir_in = addr_is_local(&sm->dst, local_ips); 183 | 184 | /* Traffic staying within the network isn't counted. */ 185 | if (dir_out && !dir_in) { 186 | daylog_acct((uint64_t)sm->len, GRAPH_OUT); 187 | graph_acct((uint64_t)sm->len, GRAPH_OUT); 188 | } 189 | if (dir_in && !dir_out) { 190 | daylog_acct((uint64_t)sm->len, GRAPH_IN); 191 | graph_acct((uint64_t)sm->len, GRAPH_IN); 192 | } 193 | 194 | if (opt_hosts_max == 0) return; /* skip per-host accounting */ 195 | 196 | /* Hosts. */ 197 | hosts_db_reduce(); 198 | if (!opt_want_local_only || dir_out) { 199 | hs = host_get(&(sm->src)); 200 | hs->out += sm->len; 201 | hs->total += sm->len; 202 | memcpy(hs->u.host.mac_addr, sm->src_mac, sizeof(sm->src_mac)); 203 | hs->u.host.last_seen_mono = now_mono(); 204 | } 205 | 206 | if (!opt_want_local_only || dir_in) { 207 | hd = host_get(&(sm->dst)); 208 | hd->in += sm->len; 209 | hd->total += sm->len; 210 | memcpy(hd->u.host.mac_addr, sm->dst_mac, sizeof(sm->dst_mac)); 211 | /* 212 | * Don't update recipient's last seen time, we don't know that 213 | * they received successfully. 214 | */ 215 | } 216 | 217 | /* Protocols. */ 218 | if (sm->proto != IPPROTO_INVALID) { 219 | if (hs) { 220 | struct bucket *ps = host_get_ip_proto(hs, sm->proto); 221 | ps->out += sm->len; 222 | ps->total += sm->len; 223 | } 224 | if (hd) { 225 | struct bucket *pd = host_get_ip_proto(hd, sm->proto); 226 | pd->in += sm->len; 227 | pd->total += sm->len; 228 | } 229 | } 230 | 231 | if (opt_ports_max == 0) return; /* skip ports accounting */ 232 | 233 | /* Ports. */ 234 | switch (sm->proto) { 235 | case IPPROTO_TCP: 236 | // Local ports on host. 237 | if ((sm->src_port <= opt_highest_port) && hs) { 238 | struct bucket *ps = host_get_port_tcp(hs, sm->src_port); 239 | ps->out += sm->len; 240 | ps->total += sm->len; 241 | } 242 | if ((sm->dst_port <= opt_highest_port) && hd) { 243 | struct bucket *pd = host_get_port_tcp(hd, sm->dst_port); 244 | pd->in += sm->len; 245 | pd->total += sm->len; 246 | if (sm->tcp_flags == TH_SYN) 247 | pd->u.port_tcp.syn++; 248 | } 249 | 250 | // Remote ports. 251 | if ((sm->src_port <= opt_highest_port) && hd) { 252 | struct bucket *pdr = host_get_port_tcp_remote(hd, sm->src_port); 253 | pdr->out += sm->len; 254 | pdr->total += sm->len; 255 | } 256 | if ((sm->dst_port <= opt_highest_port) && hs) { 257 | struct bucket *psr = host_get_port_tcp_remote(hs, sm->dst_port); 258 | psr->in += sm->len; 259 | psr->total += sm->len; 260 | if (sm->tcp_flags == TH_SYN) 261 | psr->u.port_tcp.syn++; 262 | } 263 | break; 264 | 265 | case IPPROTO_UDP: 266 | // Local ports on host. 267 | if ((sm->src_port <= opt_highest_port) && hs) { 268 | struct bucket *ps = host_get_port_udp(hs, sm->src_port); 269 | ps->out += sm->len; 270 | ps->total += sm->len; 271 | } 272 | if ((sm->dst_port <= opt_highest_port) && hd) { 273 | struct bucket *pd = host_get_port_udp(hd, sm->dst_port); 274 | pd->in += sm->len; 275 | pd->total += sm->len; 276 | } 277 | 278 | // Remote ports. 279 | if ((sm->src_port <= opt_highest_port) && hd) { 280 | struct bucket *pdr = host_get_port_udp_remote(hd, sm->src_port); 281 | pdr->out += sm->len; 282 | pdr->total += sm->len; 283 | } 284 | if ((sm->dst_port <= opt_highest_port) && hs) { 285 | struct bucket *psr = host_get_port_udp_remote(hs, sm->dst_port); 286 | psr->in += sm->len; 287 | psr->total += sm->len; 288 | } 289 | break; 290 | 291 | case IPPROTO_INVALID: 292 | /* proto decoding failed, don't complain in accounting */ 293 | break; 294 | } 295 | } 296 | 297 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 298 | -------------------------------------------------------------------------------- /db.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * 3 | * db.c: load and save in-memory database from/to file 4 | * copyright (c) 2007-2012 Ben Stewart, Emil Mikulic. 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #define _GNU_SOURCE 1 /* for O_NOFOLLOW in Linux */ 11 | 12 | #include 13 | #include /* for ntohs() and friends */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "err.h" 20 | #include "hosts_db.h" 21 | #include "graph_db.h" 22 | #include "db.h" 23 | 24 | static const unsigned char export_file_header[] = {0xDA, 0x31, 0x41, 0x59}; 25 | static const unsigned char export_tag_hosts_ver1[] = {0xDA, 'H', 'S', 0x01}; 26 | static const unsigned char export_tag_graph_ver1[] = {0xDA, 'G', 'R', 0x01}; 27 | 28 | #ifndef swap64 29 | static uint64_t swap64(uint64_t _x) { 30 | /* this is __bswap64 from: 31 | * $FreeBSD: src/sys/i386/include/endian.h,v 1.41$ 32 | */ 33 | return ((_x >> 56) | ((_x >> 40) & 0xff00) | ((_x >> 24) & 0xff0000) | 34 | ((_x >> 8) & 0xff000000) | ((_x << 8) & ((uint64_t)0xff << 32)) | 35 | ((_x << 24) & ((uint64_t)0xff << 40)) | 36 | ((_x << 40) & ((uint64_t)0xff << 48)) | ((_x << 56))); 37 | } 38 | #endif 39 | 40 | #define ntoh64 hton64 41 | static uint64_t hton64(const uint64_t ho) { 42 | if (ntohs(0x1234) == 0x1234) 43 | return ho; 44 | else 45 | return swap64(ho); 46 | } 47 | 48 | void 49 | test_64order(void) 50 | { 51 | static const char str[] = { 0x79,0x74,0x69,0x63,0x6b,0x72,0x65,0x6a }; 52 | uint64_t no, ho; 53 | 54 | assert(sizeof(no) == 8); 55 | memcpy(&no, str, 8); 56 | ho = ntoh64(no); 57 | assert(ho == 8751735851613054314ULL); 58 | assert(hton64(ntoh64(no)) == no); 59 | } 60 | 61 | /* --------------------------------------------------------------------------- 62 | * Read-from-file helpers. They all return 0 on failure, and 1 on success. 63 | */ 64 | 65 | unsigned int 66 | xtell(const int fd) 67 | { 68 | off_t ofs = lseek(fd, 0, SEEK_CUR); 69 | if (ofs == -1) 70 | err(1, "lseek(0, SEEK_CUR) failed"); 71 | return (unsigned int)ofs; 72 | } 73 | 74 | /* Read bytes from , warn() and return 0 on failure, 75 | * or return 1 for success. 76 | */ 77 | int 78 | readn(const int fd, void *dest, const size_t len) 79 | { 80 | ssize_t numread; 81 | 82 | numread = read(fd, dest, len); 83 | if (numread == (ssize_t)len) return 1; 84 | 85 | if (numread == -1) 86 | warn("at pos %u: couldn't read %d bytes", xtell(fd), (int)len); 87 | else 88 | warnx("at pos %u: tried to read %d bytes, got %d", 89 | xtell(fd), (int)len, (int)numread); 90 | return 0; 91 | } 92 | 93 | /* Read a byte. */ 94 | int 95 | read8(const int fd, uint8_t *dest) 96 | { 97 | assert(sizeof(*dest) == 1); 98 | return readn(fd, dest, sizeof(*dest)); 99 | } 100 | 101 | /* Read a byte and compare it to the expected data. 102 | * Returns 0 on failure or mismatch, 1 on success. 103 | */ 104 | int 105 | expect8(const int fd, uint8_t expecting) 106 | { 107 | uint8_t tmp; 108 | 109 | assert(sizeof(tmp) == 1); 110 | if (!readn(fd, &tmp, sizeof(tmp))) return 0; 111 | if (tmp == expecting) return 1; 112 | 113 | warnx("at pos %u: expecting 0x%02x, got 0x%02x", 114 | xtell(fd)-1, expecting, tmp); 115 | return 0; 116 | } 117 | 118 | /* Read a network order uint16_t from a file 119 | * and store it in host order in memory. 120 | */ 121 | int 122 | read16(const int fd, uint16_t *dest) 123 | { 124 | uint16_t tmp; 125 | 126 | assert(sizeof(tmp) == 2); 127 | if (!read(fd, &tmp, sizeof(tmp))) return 0; 128 | *dest = ntohs(tmp); 129 | return 1; 130 | } 131 | 132 | /* Read a network order uint32_t from a file 133 | * and store it in host order in memory. 134 | */ 135 | int 136 | read32(const int fd, uint32_t *dest) 137 | { 138 | uint32_t tmp; 139 | 140 | assert(sizeof(tmp) == 4); 141 | if (!read(fd, &tmp, sizeof(tmp))) return 0; 142 | *dest = ntohl(tmp); 143 | return 1; 144 | } 145 | 146 | /* Read an IPv4 addr from a file. This is for backward compatibility with 147 | * host records version 1 and 2. 148 | */ 149 | int 150 | readaddr_ipv4(const int fd, struct addr *dest) 151 | { 152 | dest->family = IPv4; 153 | return readn(fd, &(dest->ip.v4), sizeof(dest->ip.v4)); 154 | } 155 | 156 | /* Read a struct addr from a file. Addresses are always stored in network 157 | * order, both in the file and in the host's memory (FIXME: is that right?) 158 | */ 159 | int 160 | readaddr(const int fd, struct addr *dest) 161 | { 162 | unsigned char family; 163 | 164 | if (!read8(fd, &family)) 165 | return 0; 166 | 167 | if (family == 4) { 168 | dest->family = IPv4; 169 | return readn(fd, &(dest->ip.v4), sizeof(dest->ip.v4)); 170 | } 171 | else if (family == 6) { 172 | dest->family = IPv6; 173 | return readn(fd, dest->ip.v6.s6_addr, sizeof(dest->ip.v6.s6_addr)); 174 | } 175 | else 176 | return 0; /* no address family I ever heard of */ 177 | } 178 | 179 | /* Read a network order uint64_t from a file 180 | * and store it in host order in memory. 181 | */ 182 | int 183 | read64(const int fd, uint64_t *dest) 184 | { 185 | uint64_t tmp; 186 | 187 | assert(sizeof(tmp) == 8); 188 | if (!read(fd, &tmp, sizeof(tmp))) return 0; 189 | *dest = ntoh64(tmp); 190 | return 1; 191 | } 192 | 193 | /* --------------------------------------------------------------------------- 194 | * Write-to-file helpers. They all return 0 on failure, and 1 on success. 195 | */ 196 | 197 | /* Write bytes to , warn() and return 0 on failure, 198 | * or return 1 for success. 199 | */ 200 | int 201 | writen(const int fd, const void *dest, const size_t len) 202 | { 203 | ssize_t numwr; 204 | 205 | numwr = write(fd, dest, len); 206 | if (numwr == (ssize_t)len) return 1; 207 | 208 | if (numwr == -1) 209 | warn("couldn't write %d bytes", (int)len); 210 | else 211 | warnx("tried to write %d bytes but wrote %d", 212 | (int)len, (int)numwr); 213 | return 0; 214 | } 215 | 216 | int 217 | write8(const int fd, const uint8_t i) 218 | { 219 | assert(sizeof(i) == 1); 220 | return writen(fd, &i, sizeof(i)); 221 | } 222 | 223 | /* Given a uint16_t in host order, write it to a file in network order. 224 | */ 225 | int 226 | write16(const int fd, const uint16_t i) 227 | { 228 | uint16_t tmp = htons(i); 229 | assert(sizeof(tmp) == 2); 230 | return writen(fd, &tmp, sizeof(tmp)); 231 | } 232 | 233 | /* Given a uint32_t in host order, write it to a file in network order. 234 | */ 235 | int 236 | write32(const int fd, const uint32_t i) 237 | { 238 | uint32_t tmp = htonl(i); 239 | assert(sizeof(tmp) == 4); 240 | return writen(fd, &tmp, sizeof(tmp)); 241 | } 242 | 243 | /* Given a uint64_t in host order, write it to a file in network order. 244 | */ 245 | int 246 | write64(const int fd, const uint64_t i) 247 | { 248 | uint64_t tmp = hton64(i); 249 | assert(sizeof(tmp) == 8); 250 | return writen(fd, &tmp, sizeof(tmp)); 251 | } 252 | 253 | 254 | /* Write the active address part in a struct addr to a file. 255 | * Addresses are always stored in network order, both in the file and 256 | * in the host's memory (FIXME: is that right?) 257 | */ 258 | int 259 | writeaddr(const int fd, const struct addr *const a) 260 | { 261 | if (!write8(fd, a->family)) 262 | return 0; 263 | 264 | if (a->family == IPv4) 265 | return writen(fd, &(a->ip.v4), sizeof(a->ip.v4)); 266 | else { 267 | assert(a->family == IPv6); 268 | return writen(fd, a->ip.v6.s6_addr, sizeof(a->ip.v6.s6_addr)); 269 | } 270 | } 271 | 272 | /* --------------------------------------------------------------------------- 273 | * db import/export code follows. 274 | */ 275 | 276 | /* Check that the global file header is correct / supported. */ 277 | int 278 | read_file_header(const int fd, const uint8_t expected[4]) 279 | { 280 | uint8_t got[4]; 281 | 282 | if (!readn(fd, got, sizeof(got))) return 0; 283 | 284 | /* Check the header data */ 285 | if (memcmp(got, expected, sizeof(got)) != 0) { 286 | warnx("bad header: " 287 | "expecting %02x%02x%02x%02x, got %02x%02x%02x%02x", 288 | expected[0], expected[1], expected[2], expected[3], 289 | got[0], got[1], got[2], got[3]); 290 | return 0; 291 | } 292 | return 1; 293 | } 294 | 295 | /* Returns 0 on failure, 1 on success. */ 296 | static int 297 | db_import_from_fd(const int fd) 298 | { 299 | if (!read_file_header(fd, export_file_header)) return 0; 300 | if (!read_file_header(fd, export_tag_hosts_ver1)) return 0; 301 | if (!hosts_db_import(fd)) return 0; 302 | if (!read_file_header(fd, export_tag_graph_ver1)) return 0; 303 | if (!graph_import(fd)) return 0; 304 | return 1; 305 | } 306 | 307 | void 308 | db_import(const char *filename) 309 | { 310 | int fd = open(filename, O_RDONLY | O_NOFOLLOW); 311 | if (fd == -1) { 312 | warn("can't import from \"%s\"", filename); 313 | return; 314 | } 315 | if (!db_import_from_fd(fd)) { 316 | warnx("import failed"); 317 | /* don't stay in an inconsistent state: */ 318 | hosts_db_reset(); 319 | graph_reset(); 320 | } 321 | close(fd); 322 | } 323 | 324 | /* Returns 0 on failure, 1 on success. */ 325 | static int 326 | db_export_to_fd(const int fd) 327 | { 328 | if (!writen(fd, export_file_header, sizeof(export_file_header))) 329 | return 0; 330 | if (!writen(fd, export_tag_hosts_ver1, sizeof(export_tag_hosts_ver1))) 331 | return 0; 332 | if (!hosts_db_export(fd)) 333 | return 0; 334 | if (!writen(fd, export_tag_graph_ver1, sizeof(export_tag_graph_ver1))) 335 | return 0; 336 | if (!graph_export(fd)) 337 | return 0; 338 | return 1; 339 | } 340 | 341 | void 342 | db_export(const char *filename) 343 | { 344 | int fd = open(filename, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, 0600); 345 | if (fd == -1) { 346 | warn("can't export to \"%s\"", filename); 347 | return; 348 | } 349 | verbosef("exporting db to file \"%s\"", filename); 350 | if (!db_export_to_fd(fd)) 351 | warnx("export failed"); 352 | else 353 | verbosef("export successful"); 354 | 355 | /* FIXME: should write to another filename and use the rename() syscall to 356 | * atomically update the output file on success 357 | */ 358 | close(fd); 359 | } 360 | 361 | /* vim:set ts=3 sw=3 tw=78 et: */ 362 | -------------------------------------------------------------------------------- /contrib/darkstat_export: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2013 MediaMobil Communication GmbH 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # This script converts a binary .db file into a .csv file. 16 | # The .db file was generated by darkstat with the --export option. 17 | # The .csv file shall be read by any spreadsheet application. 18 | SCRIPTNAME=$( basename $0) 19 | if test -z "$( type -P awk )" ; then 20 | echo "${SCRIPTNAME}: missing AWK interpreter, at least not found in PATH" 21 | echo "${SCRIPTNAME}: every POSIX compliant OS has one; add the location to PATH" 22 | exit 1 23 | fi 24 | if test -z "$( type -P od )" ; then 25 | echo "${SCRIPTNAME}: missing od file dump tool, at least not found in PATH" 26 | echo "${SCRIPTNAME}: every POSIX compliant OS has one; add the location to PATH" 27 | exit 1 28 | fi 29 | if test $# -ne 1; then 30 | echo "${SCRIPTNAME}: missing parameter; need file name of .db file" 31 | exit 1 32 | fi 33 | DBFILENAME=$1 34 | if test -r ${DBFILENAME}; then 35 | echo ${SCRIPTNAME}: Found file ${DBFILENAME} 36 | else 37 | echo ${SCRIPTNAME}: file ${DBFILENAME} does not exist 38 | exit 1 39 | fi 40 | CSVFILENAME=${DBFILENAME%%.*}.csv 41 | echo ${SCRIPTNAME}: Writing output into ${CSVFILENAME} 42 | 43 | # The spec of the .db export format exists for different versions: 44 | # http://unix4lyfe.org/gitweb/darkstat/blob/0a152e51f5d9c1771308caa7135d363a722aee18:/export-format.txt 45 | # http://git.msquadrat.de/darkstat.git/blob_plain/master:/export-format.txt 46 | # http://phil.lavin.me.uk/downloads/parse.phps 47 | # Only file format version 1 is supported by us. 48 | # Obviously, darkstat itself distinguishes 3 different host format versions. 49 | # Only host format version 2 is supported by us. 50 | # The darkstat database file is converted from binary format 51 | # to ASCII by the standard Unix command od. 52 | 53 | # Some things don't work correctly yet. 54 | # Probably because there is no DNS server configured in our embedded device 55 | # that produces .db files within OpenWRT. 56 | # - host name contains nonsense at constant length 5 57 | # - "last seen" timing information contains always 0 58 | # - we read the graphics section of the file but ignore it 59 | 60 | # Let the od tool convert each binary byte into several textual formats. 61 | # The AWK script reads all variants and later picks the format it needs. 62 | od -Ad -v -tx1 -tu1 -ta -w1 < ${DBFILENAME} | 63 | awk ' 64 | NF==2 { addr = 0 + $1; hex[addr] = $2; next } 65 | NF==1 && addr in dec { ascii[addr]=$1; next } 66 | NF==1 && ! (addr in dec) { dec[addr]=$1; next } 67 | # Now all variants of the bytes are available in certain arrays. 68 | # The array indices cover the range 0 .. addr. 69 | 70 | function read_bytes(array, address, count, retval, c) { 71 | retval="" 72 | for (c=0; c 0) 90 | print reason 91 | if (terminate != 0) { 92 | # Any remaining bytes in the file shall be dumped. 93 | for (i=ai; i<=addr; i++) 94 | print i, hex[i], ascii[i] 95 | exit(retval) 96 | } 97 | } 98 | function readIPsection() { 99 | ip_protos_data=read_bytes(ascii, ai, 1) 100 | if (ip_protos_data != "P") 101 | quit("expected ip_protos_data P, found " ip_protos_data, 1, 1) 102 | ai += 1 103 | ip_proto_count=read_number(ai, 1) 104 | ai += 1 105 | for (pi=0; pi 1) { 205 | last_seen=read_number(ai, 4) 206 | # This value is always 0 in our files. 207 | ai += 4 208 | } 209 | mac_address=hex[ai+0] ":" hex[ai+1] ":" hex[ai+2] ":" hex[ai+3] ":" hex[ai+4] ":" hex[ai+5] 210 | ai += 6 211 | # Weird stuff: the host name should be read. 212 | # But there are only 5 bytes of nonsense. 213 | # The first byte should be the length counter, but it isnt. 214 | # The last byte is in fact a 0 byte. 215 | # Probably caused by the missing DNS server. 216 | # ignore 5 bytes with nonsense 217 | nonsense=read_text(ai, 5) 218 | ai += 5 219 | host_bytes_in=read_number(ai, 8) 220 | ai += 8 221 | host_bytes_out=read_number(ai, 8) 222 | ai += 8 223 | readIPsection() 224 | readTCPsection() 225 | readUDPsection() 226 | } else { 227 | quit("host format supported only in version 02: " host_version, 1, 1) 228 | #address_familiy=read_bytes(hex, ai, 1) 229 | #print "address familiy = " address_familiy 230 | } 231 | printf("\"%s\";\"%s\";%d;%d;%s;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%s;%s\n", 232 | ip_address, mac_address, host_bytes_in, host_bytes_out, 233 | IPprotos, ip_proto_in, ip_proto_out, 234 | tcp_proto_count, tcp_proto_in, tcp_proto_out, 235 | udp_proto_count, udp_proto_in, udp_proto_out, 236 | ssh_in, ssh_out, rdp_in, rdp_out, 237 | TCPports, UDPports) 238 | } 239 | section_header=read_bytes(hex, ai, 3) 240 | if (section_header != "da4752") 241 | quit("section header da4752 expected: " section_header, 1, 1) 242 | ai += 3 243 | db_version=read_bytes(hex, ai, 1) 244 | if (db_version != "01") 245 | quit("file format supported only in version 01", 1, 1) 246 | ai += 1 247 | last_time=read_number(ai, 8) 248 | ai += 8 249 | readGraphsection("60 seconds") 250 | readGraphsection("60 minutes") 251 | readGraphsection("24 hours") 252 | readGraphsection("31 days") 253 | # The complete file has been parsed, no bytes should be left over. 254 | # Terminate with return value 0 if the byte numbers match. 255 | quit("", (addr != ai+1) ?0:1, addr != ai+1) 256 | } 257 | ' > ${CSVFILENAME} 258 | -------------------------------------------------------------------------------- /install-sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # install - install a program, script, or datafile 3 | 4 | scriptversion=2005-05-14.22 5 | 6 | # This originates from X11R5 (mit/util/scripts/install.sh), which was 7 | # later released in X11R6 (xc/config/util/install.sh) with the 8 | # following copyright and license. 9 | # 10 | # Copyright (C) 1994 X Consortium 11 | # 12 | # Permission is hereby granted, free of charge, to any person obtaining a copy 13 | # of this software and associated documentation files (the "Software"), to 14 | # deal in the Software without restriction, including without limitation the 15 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 16 | # sell copies of the Software, and to permit persons to whom the Software is 17 | # furnished to do so, subject to the following conditions: 18 | # 19 | # The above copyright notice and this permission notice shall be included in 20 | # all copies or substantial portions of the Software. 21 | # 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 26 | # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- 27 | # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | # 29 | # Except as contained in this notice, the name of the X Consortium shall not 30 | # be used in advertising or otherwise to promote the sale, use or other deal- 31 | # ings in this Software without prior written authorization from the X Consor- 32 | # tium. 33 | # 34 | # 35 | # FSF changes to this file are in the public domain. 36 | # 37 | # Calling this script install-sh is preferred over install.sh, to prevent 38 | # `make' implicit rules from creating a file called install from it 39 | # when there is no Makefile. 40 | # 41 | # This script is compatible with the BSD install script, but was written 42 | # from scratch. It can only install one file at a time, a restriction 43 | # shared with many OS's install programs. 44 | 45 | # set DOITPROG to echo to test this script 46 | 47 | # Don't use :- since 4.3BSD and earlier shells don't like it. 48 | doit="${DOITPROG-}" 49 | 50 | # put in absolute paths if you don't have them in your path; or use env. vars. 51 | 52 | mvprog="${MVPROG-mv}" 53 | cpprog="${CPPROG-cp}" 54 | chmodprog="${CHMODPROG-chmod}" 55 | chownprog="${CHOWNPROG-chown}" 56 | chgrpprog="${CHGRPPROG-chgrp}" 57 | stripprog="${STRIPPROG-strip}" 58 | rmprog="${RMPROG-rm}" 59 | mkdirprog="${MKDIRPROG-mkdir}" 60 | 61 | chmodcmd="$chmodprog 0755" 62 | chowncmd= 63 | chgrpcmd= 64 | stripcmd= 65 | rmcmd="$rmprog -f" 66 | mvcmd="$mvprog" 67 | src= 68 | dst= 69 | dir_arg= 70 | dstarg= 71 | no_target_directory= 72 | 73 | usage="Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE 74 | or: $0 [OPTION]... SRCFILES... DIRECTORY 75 | or: $0 [OPTION]... -t DIRECTORY SRCFILES... 76 | or: $0 [OPTION]... -d DIRECTORIES... 77 | 78 | In the 1st form, copy SRCFILE to DSTFILE. 79 | In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. 80 | In the 4th, create DIRECTORIES. 81 | 82 | Options: 83 | -c (ignored) 84 | -d create directories instead of installing files. 85 | -g GROUP $chgrpprog installed files to GROUP. 86 | -m MODE $chmodprog installed files to MODE. 87 | -o USER $chownprog installed files to USER. 88 | -s $stripprog installed files. 89 | -t DIRECTORY install into DIRECTORY. 90 | -T report an error if DSTFILE is a directory. 91 | --help display this help and exit. 92 | --version display version info and exit. 93 | 94 | Environment variables override the default commands: 95 | CHGRPPROG CHMODPROG CHOWNPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG 96 | " 97 | 98 | while test -n "$1"; do 99 | case $1 in 100 | -c) shift 101 | continue;; 102 | 103 | -d) dir_arg=true 104 | shift 105 | continue;; 106 | 107 | -g) chgrpcmd="$chgrpprog $2" 108 | shift 109 | shift 110 | continue;; 111 | 112 | --help) echo "$usage"; exit $?;; 113 | 114 | -m) chmodcmd="$chmodprog $2" 115 | shift 116 | shift 117 | continue;; 118 | 119 | -o) chowncmd="$chownprog $2" 120 | shift 121 | shift 122 | continue;; 123 | 124 | -s) stripcmd=$stripprog 125 | shift 126 | continue;; 127 | 128 | -t) dstarg=$2 129 | shift 130 | shift 131 | continue;; 132 | 133 | -T) no_target_directory=true 134 | shift 135 | continue;; 136 | 137 | --version) echo "$0 $scriptversion"; exit $?;; 138 | 139 | *) # When -d is used, all remaining arguments are directories to create. 140 | # When -t is used, the destination is already specified. 141 | test -n "$dir_arg$dstarg" && break 142 | # Otherwise, the last argument is the destination. Remove it from $@. 143 | for arg 144 | do 145 | if test -n "$dstarg"; then 146 | # $@ is not empty: it contains at least $arg. 147 | set fnord "$@" "$dstarg" 148 | shift # fnord 149 | fi 150 | shift # arg 151 | dstarg=$arg 152 | done 153 | break;; 154 | esac 155 | done 156 | 157 | if test -z "$1"; then 158 | if test -z "$dir_arg"; then 159 | echo "$0: no input file specified." >&2 160 | exit 1 161 | fi 162 | # It's OK to call `install-sh -d' without argument. 163 | # This can happen when creating conditional directories. 164 | exit 0 165 | fi 166 | 167 | for src 168 | do 169 | # Protect names starting with `-'. 170 | case $src in 171 | -*) src=./$src ;; 172 | esac 173 | 174 | if test -n "$dir_arg"; then 175 | dst=$src 176 | src= 177 | 178 | if test -d "$dst"; then 179 | mkdircmd=: 180 | chmodcmd= 181 | else 182 | mkdircmd=$mkdirprog 183 | fi 184 | else 185 | # Waiting for this to be detected by the "$cpprog $src $dsttmp" command 186 | # might cause directories to be created, which would be especially bad 187 | # if $src (and thus $dsttmp) contains '*'. 188 | if test ! -f "$src" && test ! -d "$src"; then 189 | echo "$0: $src does not exist." >&2 190 | exit 1 191 | fi 192 | 193 | if test -z "$dstarg"; then 194 | echo "$0: no destination specified." >&2 195 | exit 1 196 | fi 197 | 198 | dst=$dstarg 199 | # Protect names starting with `-'. 200 | case $dst in 201 | -*) dst=./$dst ;; 202 | esac 203 | 204 | # If destination is a directory, append the input filename; won't work 205 | # if double slashes aren't ignored. 206 | if test -d "$dst"; then 207 | if test -n "$no_target_directory"; then 208 | echo "$0: $dstarg: Is a directory" >&2 209 | exit 1 210 | fi 211 | dst=$dst/`basename "$src"` 212 | fi 213 | fi 214 | 215 | # This sed command emulates the dirname command. 216 | dstdir=`echo "$dst" | sed -e 's,/*$,,;s,[^/]*$,,;s,/*$,,;s,^$,.,'` 217 | 218 | # Make sure that the destination directory exists. 219 | 220 | # Skip lots of stat calls in the usual case. 221 | if test ! -d "$dstdir"; then 222 | defaultIFS=' 223 | ' 224 | IFS="${IFS-$defaultIFS}" 225 | 226 | oIFS=$IFS 227 | # Some sh's can't handle IFS=/ for some reason. 228 | IFS='%' 229 | set x `echo "$dstdir" | sed -e 's@/@%@g' -e 's@^%@/@'` 230 | shift 231 | IFS=$oIFS 232 | 233 | pathcomp= 234 | 235 | while test $# -ne 0 ; do 236 | pathcomp=$pathcomp$1 237 | shift 238 | if test ! -d "$pathcomp"; then 239 | $mkdirprog "$pathcomp" 240 | # mkdir can fail with a `File exist' error in case several 241 | # install-sh are creating the directory concurrently. This 242 | # is OK. 243 | test -d "$pathcomp" || exit 244 | fi 245 | pathcomp=$pathcomp/ 246 | done 247 | fi 248 | 249 | if test -n "$dir_arg"; then 250 | $doit $mkdircmd "$dst" \ 251 | && { test -z "$chowncmd" || $doit $chowncmd "$dst"; } \ 252 | && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } \ 253 | && { test -z "$stripcmd" || $doit $stripcmd "$dst"; } \ 254 | && { test -z "$chmodcmd" || $doit $chmodcmd "$dst"; } 255 | 256 | else 257 | dstfile=`basename "$dst"` 258 | 259 | # Make a couple of temp file names in the proper directory. 260 | dsttmp=$dstdir/_inst.$$_ 261 | rmtmp=$dstdir/_rm.$$_ 262 | 263 | # Trap to clean up those temp files at exit. 264 | trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 265 | trap '(exit $?); exit' 1 2 13 15 266 | 267 | # Copy the file name to the temp name. 268 | $doit $cpprog "$src" "$dsttmp" && 269 | 270 | # and set any options; do chmod last to preserve setuid bits. 271 | # 272 | # If any of these fail, we abort the whole thing. If we want to 273 | # ignore errors from any of these, just make sure not to ignore 274 | # errors from the above "$doit $cpprog $src $dsttmp" command. 275 | # 276 | { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } \ 277 | && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } \ 278 | && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } \ 279 | && { test -z "$chmodcmd" || $doit $chmodcmd "$dsttmp"; } && 280 | 281 | # Now rename the file to the real destination. 282 | { $doit $mvcmd -f "$dsttmp" "$dstdir/$dstfile" 2>/dev/null \ 283 | || { 284 | # The rename failed, perhaps because mv can't rename something else 285 | # to itself, or perhaps because mv is so ancient that it does not 286 | # support -f. 287 | 288 | # Now remove or move aside any old file at destination location. 289 | # We try this two ways since rm can't unlink itself on some 290 | # systems and the destination file might be busy for other 291 | # reasons. In this case, the final cleanup might fail but the new 292 | # file should still install successfully. 293 | { 294 | if test -f "$dstdir/$dstfile"; then 295 | $doit $rmcmd -f "$dstdir/$dstfile" 2>/dev/null \ 296 | || $doit $mvcmd -f "$dstdir/$dstfile" "$rmtmp" 2>/dev/null \ 297 | || { 298 | echo "$0: cannot unlink or rename $dstdir/$dstfile" >&2 299 | (exit 1); exit 1 300 | } 301 | else 302 | : 303 | fi 304 | } && 305 | 306 | # Now rename the file to the real destination. 307 | $doit $mvcmd "$dsttmp" "$dstdir/$dstfile" 308 | } 309 | } 310 | fi || { (exit 1); exit 1; } 311 | done 312 | 313 | # The final little trick to "correctly" pass the exit status to the exit trap. 314 | { 315 | (exit 0); exit 0 316 | } 317 | 318 | # Local variables: 319 | # eval: (add-hook 'write-file-hooks 'time-stamp) 320 | # time-stamp-start: "scriptversion=" 321 | # time-stamp-format: "%:y-%02m-%02d.%02H" 322 | # time-stamp-end: "$" 323 | # End: 324 | -------------------------------------------------------------------------------- /conv.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * conv.c: convenience functions. 5 | * 6 | * Permission to use, copy, modify, and distribute this file for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include "conv.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include "err.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #define PATH_DEVNULL "/dev/null" 37 | 38 | /* malloc() that exits on failure. */ 39 | void * 40 | xmalloc(const size_t size) 41 | { 42 | void *ptr = malloc(size); 43 | 44 | if (ptr == NULL) 45 | errx(1, "malloc(): out of memory"); 46 | return (ptr); 47 | } 48 | 49 | /* calloc() that exits on failure. */ 50 | void * 51 | xcalloc(const size_t num, const size_t size) 52 | { 53 | void *ptr = calloc(num, size); 54 | 55 | if (ptr == NULL) 56 | errx(1, "calloc(): out of memory"); 57 | return (ptr); 58 | } 59 | 60 | /* realloc() that exits on failure. */ 61 | void * 62 | xrealloc(void *original, const size_t size) 63 | { 64 | void *ptr = realloc(original, size); 65 | 66 | if (ptr == NULL) 67 | errx(1, "realloc(): out of memory"); 68 | return (ptr); 69 | } 70 | 71 | /* strdup() that exits on failure. */ 72 | char * 73 | xstrdup(const char *s) 74 | { 75 | char *tmp = strdup(s); 76 | 77 | if (tmp == NULL) 78 | errx(1, "strdup(): out of memory"); 79 | return (tmp); 80 | } 81 | 82 | /* --------------------------------------------------------------------------- 83 | * Split string out of src with range [left:right-1] 84 | */ 85 | char * 86 | split_string(const char *src, const size_t left, const size_t right) 87 | { 88 | char *dest; 89 | assert(left <= right); 90 | assert(left < strlen(src)); /* [left means must be smaller */ 91 | assert(right <= strlen(src)); /* right) means can be equal or smaller */ 92 | 93 | dest = xmalloc(right - left + 1); 94 | memcpy(dest, src+left, right-left); 95 | dest[right-left] = '\0'; 96 | return (dest); 97 | } 98 | 99 | /* --------------------------------------------------------------------------- 100 | * Uppercasify all characters in a string of given length. 101 | */ 102 | void 103 | strntoupper(char *str, const size_t length) 104 | { 105 | size_t i; 106 | 107 | for (i=0; i 2) 285 | close(fd_null); 286 | } 287 | 288 | /* 289 | * For security, chroot (optionally) and drop privileges. 290 | * Pass a NULL chroot_dir to disable chroot() behaviour. 291 | */ 292 | void privdrop(const char *chroot_dir, const char *privdrop_user) { 293 | struct passwd *pw; 294 | 295 | errno = 0; 296 | pw = getpwnam(privdrop_user); 297 | 298 | if (pw == NULL) { 299 | if (errno == 0) 300 | errx(1, "getpwnam(\"%s\") failed: no such user", privdrop_user); 301 | else 302 | err(1, "getpwnam(\"%s\") failed", privdrop_user); 303 | } 304 | if (chroot_dir == NULL) { 305 | verbosef("no --chroot dir specified, darkstat will not chroot()"); 306 | } else { 307 | tzset(); /* read /etc/localtime before we chroot */ 308 | if (chdir(chroot_dir) == -1) 309 | err(1, "chdir(\"%s\") failed", chroot_dir); 310 | if (chroot(chroot_dir) == -1) 311 | err(1, "chroot(\"%s\") failed", chroot_dir); 312 | verbosef("chrooted into: %s", chroot_dir); 313 | } 314 | { 315 | gid_t list[1]; 316 | list[0] = pw->pw_gid; 317 | if (setgroups(1, list) == -1) 318 | err(1, "setgroups"); 319 | } 320 | if (setgid(pw->pw_gid) == -1) 321 | err(1, "setgid"); 322 | if (setuid(pw->pw_uid) == -1) 323 | err(1, "setuid"); 324 | verbosef("set uid/gid to %d/%d", (int)pw->pw_uid, (int)pw->pw_gid); 325 | } 326 | 327 | /* Make the specified file descriptor non-blocking. */ 328 | void 329 | fd_set_nonblock(const int fd) 330 | { 331 | int flags; 332 | 333 | if ((flags = fcntl(fd, F_GETFL, 0)) == -1) 334 | err(1, "fcntl(fd %d) to get flags", fd); 335 | flags |= O_NONBLOCK; 336 | if (fcntl(fd, F_SETFL, flags) == -1) 337 | err(1, "fcntl(fd %d) to set O_NONBLOCK", fd); 338 | assert( (fcntl(fd, F_GETFL, 0) & O_NONBLOCK ) == O_NONBLOCK ); 339 | } 340 | 341 | /* Make the specified file descriptor blocking. */ 342 | void 343 | fd_set_block(const int fd) 344 | { 345 | int flags; 346 | 347 | if ((flags = fcntl(fd, F_GETFL, 0)) == -1) 348 | err(1, "fcntl(fd %d) to get flags", fd); 349 | flags &= ~O_NONBLOCK; 350 | if (fcntl(fd, F_SETFL, flags) == -1) 351 | err(1, "fcntl(fd %d) to unset O_NONBLOCK", fd); 352 | assert( (fcntl(fd, F_GETFL, 0) & O_NONBLOCK ) == 0 ); 353 | } 354 | 355 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 356 | -------------------------------------------------------------------------------- /dns.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2001-2014 Emil Mikulic. 3 | * 4 | * dns.c: synchronous DNS in a child process. 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #include "cdefs.h" 11 | #include "cap.h" 12 | #include "conv.h" 13 | #include "decode.h" 14 | #include "dns.h" 15 | #include "err.h" 16 | #include "hosts_db.h" 17 | #include "queue.h" 18 | #include "str.h" 19 | #include "tree.h" 20 | #include "bsd.h" /* for setproctitle, strlcpy */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifdef __NetBSD__ 34 | # define gethostbyaddr(addr, len, type) \ 35 | gethostbyaddr((const char *)(addr), len, type) 36 | #endif 37 | 38 | static void dns_main(void) _noreturn_; /* the child process runs this */ 39 | 40 | #define CHILD 0 /* child process uses this socket */ 41 | #define PARENT 1 42 | static int dns_sock[2]; 43 | static pid_t pid = -1; 44 | 45 | struct dns_reply { 46 | struct addr addr; 47 | int error; /* for gai_strerror(), or 0 if no error */ 48 | char name[256]; /* http://tools.ietf.org/html/rfc1034#section-3.1 */ 49 | }; 50 | 51 | void 52 | dns_init(const char *privdrop_user) 53 | { 54 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, dns_sock) == -1) 55 | err(1, "socketpair"); 56 | 57 | pid = fork(); 58 | if (pid == -1) 59 | err(1, "fork"); 60 | 61 | if (pid == 0) { 62 | /* We are the child. */ 63 | privdrop(NULL /* don't chroot */, privdrop_user); 64 | close(dns_sock[PARENT]); 65 | dns_sock[PARENT] = -1; 66 | daemonize_finish(); /* drop our copy of the lifeline! */ 67 | if (signal(SIGUSR1, SIG_IGN) == SIG_ERR) 68 | errx(1, "signal(SIGUSR1, ignore) failed"); 69 | cap_free_args(); 70 | dns_main(); 71 | errx(1, "DNS child fell out of dns_main()"); 72 | } else { 73 | /* We are the parent. */ 74 | close(dns_sock[CHILD]); 75 | dns_sock[CHILD] = -1; 76 | fd_set_nonblock(dns_sock[PARENT]); 77 | verbosef("DNS child has PID %d", pid); 78 | } 79 | } 80 | 81 | void 82 | dns_stop(void) 83 | { 84 | if (pid == -1) 85 | return; /* no child was started */ 86 | close(dns_sock[PARENT]); 87 | if (kill(pid, SIGINT) == -1) 88 | err(1, "kill"); 89 | verbosef("dns_stop() waiting for child"); 90 | if (waitpid(pid, NULL, 0) == -1) 91 | err(1, "waitpid"); 92 | verbosef("dns_stop() done waiting for child"); 93 | } 94 | 95 | struct tree_rec { 96 | RB_ENTRY(tree_rec) ptree; 97 | struct addr ip; 98 | }; 99 | 100 | static int 101 | tree_cmp(struct tree_rec *a, struct tree_rec *b) 102 | { 103 | if (a->ip.family != b->ip.family) 104 | /* Sort IPv4 to the left of IPv6. */ 105 | return ((a->ip.family == IPv4) ? -1 : +1); 106 | 107 | if (a->ip.family == IPv4) 108 | return (memcmp(&a->ip.ip.v4, &b->ip.ip.v4, sizeof(a->ip.ip.v4))); 109 | else { 110 | assert(a->ip.family == IPv6); 111 | return (memcmp(&a->ip.ip.v6, &b->ip.ip.v6, sizeof(a->ip.ip.v6))); 112 | } 113 | } 114 | 115 | static RB_HEAD(tree_t, tree_rec) ip_tree = RB_INITIALIZER(&tree_rec); 116 | RB_GENERATE_STATIC(tree_t, tree_rec, ptree, tree_cmp) 117 | 118 | void 119 | dns_queue(const struct addr *const ipaddr) 120 | { 121 | struct tree_rec *rec; 122 | ssize_t num_w; 123 | 124 | if (pid == -1) 125 | return; /* no child was started - we're not doing any DNS */ 126 | 127 | if ((ipaddr->family != IPv4) && (ipaddr->family != IPv6)) { 128 | verbosef("dns_queue() for unknown family %d", ipaddr->family); 129 | return; 130 | } 131 | 132 | rec = xmalloc(sizeof(*rec)); 133 | memcpy(&rec->ip, ipaddr, sizeof(rec->ip)); 134 | 135 | if (RB_INSERT(tree_t, &ip_tree, rec) != NULL) { 136 | /* Already queued - this happens seldom enough that we don't care about 137 | * the performance hit of needlessly malloc()ing. */ 138 | verbosef("already queued %s", addr_to_str(ipaddr)); 139 | free(rec); 140 | return; 141 | } 142 | 143 | num_w = write(dns_sock[PARENT], ipaddr, sizeof(*ipaddr)); /* won't block */ 144 | if (num_w == 0) 145 | warnx("dns_queue: write: ignoring end of file"); 146 | else if (num_w == -1) 147 | warn("dns_queue: ignoring write error"); 148 | else if (num_w != sizeof(*ipaddr)) 149 | err(1, "dns_queue: wrote %zu instead of %zu", num_w, sizeof(*ipaddr)); 150 | } 151 | 152 | static void 153 | dns_unqueue(const struct addr *const ipaddr) 154 | { 155 | struct tree_rec tmp, *rec; 156 | 157 | memcpy(&tmp.ip, ipaddr, sizeof(tmp.ip)); 158 | if ((rec = RB_FIND(tree_t, &ip_tree, &tmp)) != NULL) { 159 | RB_REMOVE(tree_t, &ip_tree, rec); 160 | free(rec); 161 | } 162 | else 163 | verbosef("couldn't unqueue %s - not in queue!", addr_to_str(ipaddr)); 164 | } 165 | 166 | /* 167 | * Returns non-zero if result waiting, stores IP and name into given pointers 168 | * (name buffer is allocated by dns_poll) 169 | */ 170 | static int 171 | dns_get_result(struct addr *ipaddr, char **name) 172 | { 173 | struct dns_reply reply; 174 | ssize_t numread; 175 | 176 | numread = read(dns_sock[PARENT], &reply, sizeof(reply)); 177 | if (numread == -1) { 178 | if (errno == EAGAIN) 179 | return (0); /* no input waiting */ 180 | else 181 | goto error; 182 | } 183 | if (numread == 0) 184 | goto error; /* EOF */ 185 | if (numread != sizeof(reply)) 186 | errx(1, "dns_get_result read got %zu, expected %zu", 187 | numread, sizeof(reply)); 188 | 189 | /* Return successful reply. */ 190 | memcpy(ipaddr, &reply.addr, sizeof(*ipaddr)); 191 | if (reply.error != 0) { 192 | /* Identify common special cases. */ 193 | const char *type = "none"; 194 | 195 | if (reply.addr.family == IPv6) { 196 | if (IN6_IS_ADDR_LINKLOCAL(&reply.addr.ip.v6)) 197 | type = "link-local"; 198 | else if (IN6_IS_ADDR_SITELOCAL(&reply.addr.ip.v6)) 199 | type = "site-local"; 200 | else if (IN6_IS_ADDR_MULTICAST(&reply.addr.ip.v6)) 201 | type = "multicast"; 202 | } else { 203 | assert(reply.addr.family == IPv4); 204 | if (IN_MULTICAST(htonl(reply.addr.ip.v4))) 205 | type = "multicast"; 206 | } 207 | xasprintf(name, "(%s)", type); 208 | } 209 | else /* Correctly resolved name. */ 210 | *name = xstrdup(reply.name); 211 | 212 | dns_unqueue(&reply.addr); 213 | return (1); 214 | 215 | error: 216 | warn("dns_get_result: ignoring read error"); 217 | /* FIXME: re-align to stream? restart dns child? */ 218 | return (0); 219 | } 220 | 221 | void 222 | dns_poll(void) 223 | { 224 | struct addr ip; 225 | char *name; 226 | 227 | if (pid == -1) 228 | return; /* no child was started - we're not doing any DNS */ 229 | 230 | while (dns_get_result(&ip, &name)) { 231 | /* push into hosts_db */ 232 | struct bucket *b = host_find(&ip); 233 | 234 | if (b == NULL) { 235 | verbosef("resolved %s to %s but it's not in the DB!", 236 | addr_to_str(&ip), name); 237 | return; 238 | } 239 | if (b->u.host.dns != NULL) { 240 | verbosef("resolved %s to %s but it's already in the DB!", 241 | addr_to_str(&ip), name); 242 | return; 243 | } 244 | b->u.host.dns = name; 245 | } 246 | } 247 | 248 | /* ------------------------------------------------------------------------ */ 249 | 250 | struct qitem { 251 | STAILQ_ENTRY(qitem) entries; 252 | struct addr ip; 253 | }; 254 | 255 | static STAILQ_HEAD(qhead, qitem) queue = STAILQ_HEAD_INITIALIZER(queue); 256 | 257 | static void 258 | enqueue(const struct addr *const ip) 259 | { 260 | struct qitem *i; 261 | 262 | i = xmalloc(sizeof(*i)); 263 | memcpy(&i->ip, ip, sizeof(i->ip)); 264 | STAILQ_INSERT_TAIL(&queue, i, entries); 265 | verbosef("DNS: enqueued %s", addr_to_str(ip)); 266 | } 267 | 268 | /* Return non-zero and populate pointer if queue isn't empty. */ 269 | static int 270 | dequeue(struct addr *ip) 271 | { 272 | struct qitem *i; 273 | 274 | i = STAILQ_FIRST(&queue); 275 | if (i == NULL) 276 | return (0); 277 | STAILQ_REMOVE_HEAD(&queue, entries); 278 | memcpy(ip, &i->ip, sizeof(*ip)); 279 | free(i); 280 | verbosef("DNS: dequeued %s", addr_to_str(ip)); 281 | return 1; 282 | } 283 | 284 | static void 285 | xwrite(const int d, const void *buf, const size_t nbytes) 286 | { 287 | ssize_t ret = write(d, buf, nbytes); 288 | 289 | if (ret == -1) 290 | err(1, "write"); 291 | if (ret != (ssize_t)nbytes) 292 | err(1, "wrote %d bytes instead of all %d bytes", (int)ret, (int)nbytes); 293 | } 294 | 295 | static void 296 | dns_main(void) 297 | { 298 | struct addr ip; 299 | 300 | setproctitle("DNS child"); 301 | fd_set_nonblock(dns_sock[CHILD]); 302 | verbosef("DNS child entering main DNS loop"); 303 | for (;;) { 304 | int blocking; 305 | 306 | if (STAILQ_EMPTY(&queue)) { 307 | blocking = 1; 308 | fd_set_block(dns_sock[CHILD]); 309 | verbosef("entering blocking read loop"); 310 | } else { 311 | blocking = 0; 312 | fd_set_nonblock(dns_sock[CHILD]); 313 | verbosef("non-blocking poll"); 314 | } 315 | for (;;) { 316 | /* While we have input to process... */ 317 | ssize_t numread = read(dns_sock[CHILD], &ip, sizeof(ip)); 318 | if (numread == 0) 319 | exit(0); /* end of file, nothing more to do here. */ 320 | if (numread == -1) { 321 | if (!blocking && (errno == EAGAIN)) 322 | break; /* ran out of input */ 323 | /* else error */ 324 | err(1, "DNS: read failed"); 325 | } 326 | if (numread != sizeof(ip)) 327 | err(1, "DNS: read got %zu bytes, expecting %zu", 328 | numread, sizeof(ip)); 329 | enqueue(&ip); 330 | if (blocking) { 331 | /* After one blocking read, become non-blocking so that when we 332 | * run out of input we fall through to queue processing. 333 | */ 334 | blocking = 0; 335 | fd_set_nonblock(dns_sock[CHILD]); 336 | } 337 | } 338 | 339 | /* Process queue. */ 340 | if (dequeue(&ip)) { 341 | struct dns_reply reply; 342 | struct sockaddr_in sin; 343 | struct sockaddr_in6 sin6; 344 | struct hostent *he; 345 | char host[NI_MAXHOST]; 346 | int ret, flags; 347 | 348 | reply.addr = ip; 349 | flags = NI_NAMEREQD; 350 | # ifdef NI_IDN 351 | flags |= NI_IDN; 352 | # endif 353 | switch (ip.family) { 354 | case IPv4: 355 | sin.sin_family = AF_INET; 356 | sin.sin_addr.s_addr = ip.ip.v4; 357 | ret = getnameinfo((struct sockaddr *) &sin, sizeof(sin), 358 | host, sizeof(host), NULL, 0, flags); 359 | if (ret == EAI_FAMILY) { 360 | verbosef("getnameinfo error %s, trying gethostbyname", 361 | gai_strerror(ret)); 362 | he = gethostbyaddr(&sin.sin_addr.s_addr, 363 | sizeof(sin.sin_addr.s_addr), sin.sin_family); 364 | if (he == NULL) { 365 | ret = EAI_FAIL; 366 | verbosef("gethostbyname error %s", hstrerror(h_errno)); 367 | } else { 368 | ret = 0; 369 | strlcpy(host, he->h_name, sizeof(host)); 370 | } 371 | } 372 | break; 373 | case IPv6: 374 | sin6.sin6_family = AF_INET6; 375 | memcpy(&sin6.sin6_addr, &ip.ip.v6, sizeof(sin6.sin6_addr)); 376 | ret = getnameinfo((struct sockaddr *) &sin6, sizeof(sin6), 377 | host, sizeof(host), NULL, 0, flags); 378 | break; 379 | default: 380 | errx(1, "unexpected ip.family = %d", ip.family); 381 | } 382 | 383 | if (ret != 0) { 384 | reply.name[0] = '\0'; 385 | reply.error = ret; 386 | } else { 387 | assert(sizeof(reply.name) > sizeof(char *)); /* not just a ptr */ 388 | strlcpy(reply.name, host, sizeof(reply.name)); 389 | reply.error = 0; 390 | } 391 | fd_set_block(dns_sock[CHILD]); 392 | xwrite(dns_sock[CHILD], &reply, sizeof(reply)); 393 | verbosef("DNS: %s is \"%s\".", addr_to_str(&reply.addr), 394 | (ret == 0) ? reply.name : gai_strerror(ret)); 395 | } 396 | } 397 | } 398 | 399 | /* vim:set ts=3 sw=3 tw=78 expandtab: */ 400 | -------------------------------------------------------------------------------- /graph_db.c: -------------------------------------------------------------------------------- 1 | /* darkstat 3 2 | * copyright (c) 2006-2014 Emil Mikulic. 3 | * 4 | * graph_db.c: round robin database for graph data 5 | * 6 | * You may use, modify and redistribute this file under the terms of the 7 | * GNU General Public License version 2. (see COPYING.GPL) 8 | */ 9 | 10 | #include 11 | 12 | #include "cap.h" 13 | #include "conv.h" 14 | #include "db.h" 15 | #include "acct.h" 16 | #include "err.h" 17 | #include "str.h" 18 | #include "html.h" 19 | #include "graph_db.h" 20 | #include "now.h" 21 | #include "opt.h" 22 | 23 | #include 24 | #include 25 | #include /* for memcpy() */ 26 | #include 27 | 28 | #define GRAPH_WIDTH "320" 29 | #define GRAPH_HEIGHT "200" 30 | 31 | struct graph { 32 | uint64_t *in, *out; 33 | unsigned int offset; /* i.e. seconds start at 0, days start at 1 */ 34 | unsigned int pos, num_bars; 35 | const char *unit; 36 | unsigned int bar_secs; /* one bar represents seconds */ 37 | }; 38 | 39 | static struct graph 40 | graph_secs = {NULL, NULL, 0, 0, 60, "seconds", 1}, 41 | graph_mins = {NULL, NULL, 0, 0, 60, "minutes", 60}, 42 | graph_hrs = {NULL, NULL, 0, 0, 24, "hours", 3600}, 43 | graph_days = {NULL, NULL, 1, 0, 31, "days", 86400}; 44 | 45 | static struct graph *graph_db[] = { 46 | &graph_secs, &graph_mins, &graph_hrs, &graph_days 47 | }; 48 | 49 | static unsigned int graph_db_size = sizeof(graph_db)/sizeof(*graph_db); 50 | static time_t start_mono, start_real, last_real; 51 | 52 | void graph_init(void) { 53 | unsigned int i; 54 | for (i=0; iin = xmalloc(sizeof(uint64_t) * graph_db[i]->num_bars); 56 | graph_db[i]->out = xmalloc(sizeof(uint64_t) * graph_db[i]->num_bars); 57 | } 58 | graph_reset(); 59 | } 60 | 61 | static void zero_graph(struct graph *g) { 62 | memset(g->in, 0, sizeof(uint64_t) * g->num_bars); 63 | memset(g->out, 0, sizeof(uint64_t) * g->num_bars); 64 | } 65 | 66 | void graph_reset(void) { 67 | unsigned int i; 68 | 69 | for (i=0; iin); 87 | free(graph_db[i]->out); 88 | } 89 | } 90 | 91 | void graph_acct(uint64_t amount, enum graph_dir dir) { 92 | unsigned int i; 93 | for (i=0; iin[ graph_db[i]->pos ] += amount; 96 | } else { 97 | assert(dir == GRAPH_OUT); 98 | graph_db[i]->out[ graph_db[i]->pos ] += amount; 99 | } 100 | } 101 | 102 | /* Advance a graph: advance the pos, zeroing out bars as we move. */ 103 | static void advance(struct graph *g, const unsigned int pos) { 104 | if (g->pos == pos) 105 | return; /* didn't need to advance */ 106 | do { 107 | g->pos = (g->pos + 1) % g->num_bars; 108 | g->in[g->pos] = g->out[g->pos] = 0; 109 | } while (g->pos != pos); 110 | } 111 | 112 | /* Rotate a graph: rotate all bars so that the bar at the current pos is moved 113 | * to the newly given pos. 114 | */ 115 | static void rotate(struct graph *g, const unsigned int pos) { 116 | uint64_t *tmp; 117 | unsigned int i, ofs; 118 | size_t size; 119 | 120 | if (pos == g->pos) 121 | return; /* nothing to rotate */ 122 | 123 | size = sizeof(*tmp) * g->num_bars; 124 | tmp = xmalloc(size); 125 | ofs = g->num_bars + pos - g->pos; 126 | 127 | for (i=0; inum_bars; i++) 128 | tmp[ (i+ofs) % g->num_bars ] = g->in[i]; 129 | memcpy(g->in, tmp, size); 130 | 131 | for (i=0; inum_bars; i++) 132 | tmp[ (i+ofs) % g->num_bars ] = g->out[i]; 133 | memcpy(g->out, tmp, size); 134 | 135 | free(tmp); 136 | assert(g->num_bars > 0); 137 | assert(pos == ( (g->pos + ofs) % g->num_bars )); 138 | g->pos = pos; 139 | } 140 | 141 | static void graph_resync(const time_t new_real) { 142 | struct tm *tm; 143 | /* 144 | * If real time went backwards, we assume that the time adjustment should 145 | * only affect display. i.e., if we have: 146 | * 147 | * second 15: 12 bytes 148 | * second 16: 345 bytes 149 | * second 17: <-- current pos 150 | * 151 | * and time goes backwards to second 8, we will shift the graph around to 152 | * get: 153 | * 154 | * second 6: 12 bytes 155 | * second 7: 345 bytes 156 | * second 8: <-- current pos 157 | * 158 | * We don't make any corrections for time being stepped forward, 159 | * it's treated as though there was no traffic during that time. 160 | * 161 | * We rely on graph advancement to happen at the correct real time to 162 | * account for, for example, bandwidth used per day. 163 | */ 164 | assert(new_real < last_real); 165 | 166 | tm = localtime(&new_real); 167 | if (tm->tm_sec == 60) 168 | tm->tm_sec = 59; /* mis-handle leap seconds */ 169 | 170 | rotate(&graph_secs, tm->tm_sec); 171 | rotate(&graph_mins, tm->tm_min); 172 | rotate(&graph_hrs, tm->tm_hour); 173 | rotate(&graph_days, tm->tm_mday - 1); 174 | 175 | last_real = new_real; 176 | } 177 | 178 | void graph_rotate(void) { 179 | time_t t, td; 180 | struct tm *tm; 181 | unsigned int i; 182 | 183 | t = now_real(); 184 | td = t - last_real; 185 | 186 | if (last_real == 0) { 187 | verbosef("first rotate"); 188 | last_real = t; 189 | tm = localtime(&t); 190 | graph_secs.pos = tm->tm_sec; 191 | graph_mins.pos = tm->tm_min; 192 | graph_hrs.pos = tm->tm_hour; 193 | graph_days.pos = tm->tm_mday - 1; 194 | return; 195 | } 196 | 197 | if (t == last_real) 198 | return; /* time has not advanced a full second, don't rotate */ 199 | 200 | if (t < last_real) { 201 | verbosef("graph_db: realtime went backwards! " 202 | "(from %ld to %ld, offset is %ld)", 203 | last_real, t, td); 204 | graph_resync(t); 205 | return; 206 | } 207 | 208 | /* else, normal rotation */ 209 | last_real = t; 210 | tm = localtime(&t); 211 | 212 | /* zero out graphs which have been completely rotated through */ 213 | for (i=0; i= (int)(graph_db[i]->num_bars * graph_db[i]->bar_secs)) 215 | zero_graph(graph_db[i]); 216 | 217 | /* advance the current position, zeroing up to it */ 218 | advance(&graph_secs, tm->tm_sec); 219 | advance(&graph_mins, tm->tm_min); 220 | advance(&graph_hrs, tm->tm_hour); 221 | advance(&graph_days, tm->tm_mday - 1); 222 | } 223 | 224 | /* --------------------------------------------------------------------------- 225 | * Database Import: Grab graphs from a file provided by the caller. 226 | * 227 | * This function will retrieve the data sans the header. We expect the caller 228 | * to have validated the header of the segment, and left the file position at 229 | * the start of the data. 230 | */ 231 | int graph_import(const int fd) { 232 | uint64_t last; 233 | unsigned int i, j; 234 | 235 | if (!read64(fd, &last)) return 0; 236 | last_real = last; 237 | 238 | for (i=0; i= num_bars) { 249 | warn("pos is %u, should be < num_bars which is %u", 250 | (unsigned int)pos, (unsigned int)num_bars); 251 | return 0; 252 | } 253 | 254 | if (graph_db[i]->num_bars != num_bars) { 255 | warn("num_bars is %u, expecting %u", 256 | (unsigned int)num_bars, graph_db[i]->num_bars); 257 | return 0; 258 | } 259 | 260 | graph_db[i]->pos = pos; 261 | for (j=0; jin[j]))) return 0; 263 | if (!read64(fd, &(graph_db[i]->out[j]))) return 0; 264 | } 265 | } 266 | 267 | return 1; 268 | } 269 | 270 | /* --------------------------------------------------------------------------- 271 | * Database Export: Dump hosts_db into a file provided by the caller. 272 | * The caller is responsible for writing out the header first. 273 | */ 274 | int graph_export(const int fd) { 275 | unsigned int i, j; 276 | 277 | if (!write64(fd, (uint64_t)last_real)) return 0; 278 | for (i=0; inum_bars)) return 0; 280 | if (!write8(fd, graph_db[i]->pos)) return 0; 281 | 282 | for (j=0; jnum_bars; j++) { 283 | if (!write64(fd, graph_db[i]->in[j])) return 0; 284 | if (!write64(fd, graph_db[i]->out[j])) return 0; 285 | } 286 | } 287 | return 1; 288 | } 289 | 290 | /* --------------------------------------------------------------------------- 291 | * Web interface: front page! 292 | */ 293 | struct str *html_front_page(void) { 294 | struct str *buf, *rf; 295 | unsigned int i; 296 | char start_when[100]; 297 | time_t d_real, d_mono; 298 | 299 | buf = str_make(); 300 | html_open(buf, "Graphs", /*path_depth=*/0, /*want_graph_js=*/1); 301 | 302 | d_mono = now_mono() - start_mono; 303 | d_real = now_real() - start_real; 304 | str_append(buf, "

\n"); 305 | str_append(buf, "Measuring for "); 306 | rf = length_of_time(d_mono); 307 | str_appendstr(buf, rf); 308 | str_free(rf); 309 | str_append(buf, ""); 310 | if (labs((long)(d_real - d_mono)) > 1) 311 | str_appendf(buf, " (real time is off by %qd sec)", 312 | (qd)(d_real - d_mono)); 313 | 314 | if (strftime(start_when, sizeof(start_when), 315 | "%Y-%m-%d %H:%M:%S %Z%z", localtime(&start_real)) != 0) 316 | str_appendf(buf, ", since %s", start_when); 317 | 318 | str_appendf(buf,".
\n" 319 | "Seen %'qu bytes, " 320 | "in %'qu packets. " 321 | "(%'u captured, " 322 | "%'u dropped)
\n" 323 | "

\n", 324 | (qu)acct_total_bytes, 325 | (qu)acct_total_packets, 326 | cap_pkts_recv, 327 | cap_pkts_drop); 328 | 329 | str_append(buf, 330 | "
\n" 331 | "Graphs require JavaScript.\n" 332 | "\n" 357 | "
\n" 358 | ); 359 | 360 | html_close(buf); 361 | return (buf); 362 | } 363 | 364 | /* --------------------------------------------------------------------------- 365 | * Web interface: graphs.xml 366 | */ 367 | struct str *xml_graphs(void) { 368 | unsigned int i, j; 369 | struct str *buf = str_make(), *rf; 370 | 371 | str_appendf(buf, "\n"); 380 | 381 | for (i=0; i\n", g->unit); 385 | j = g->pos; 386 | do { 387 | j = (j + 1) % g->num_bars; 388 | /* */ 389 | str_appendf(buf, "\n", 390 | g->offset + j, 391 | (qu)g->in[j], 392 | (qu)g->out[j]); 393 | } while (j != g->pos); 394 | str_appendf(buf, "\n", g->unit); 395 | } 396 | str_append(buf, "\n"); 397 | return (buf); 398 | } 399 | 400 | /* vim:set ts=3 sw=3 tw=80 et: */ 401 | --------------------------------------------------------------------------------