X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=main.c;h=00b26de58677ed0e6f844bc89710e0916af3980a;hb=ec0ee88d13987e2953b7c532c2e8e1e5506967ac;hp=472c83c82dc48d210139555bf7534d0e69745269;hpb=bf1a831e4606ffb150e5ad961360b74dab54dfdb;p=netmon.git diff --git a/main.c b/main.c index 472c83c..00b26de 100644 --- a/main.c +++ b/main.c @@ -6,56 +6,101 @@ Distributed under the GPL #include #include -#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include +#include #include -#include -#include #include -#include -#include -#include -#include +#include + +typedef unsigned long long Time; -unsigned long long read_number(const char *); +typedef struct +{ + struct sockaddr_storage address; + struct sockaddr_storage mask; +} Address; + +typedef struct +{ + char *interface_name; + pcap_t *pcap; + unsigned n_addresses; + Address *addresses; + Time last_receive; + Time last_transmit; + int connection_status; + Time connection_down_time; +} Monitor; + +typedef struct +{ + Monitor *monitor; + Time delay; + Time interval; + const char *command; + Time next; + pid_t child_pid; +} Trigger; + +typedef struct +{ + const char *target_name; + struct sockaddr_in target_addr; + int socket; + struct pollfd pfd; + Time next; + uint16_t id; + uint16_t seq; + uint16_t pending; + unsigned count; + unsigned lost; +} Pinger; + +Time current_time(); +int monitor_init(Monitor *); +void monitor_check(Monitor *, Time); +void capture_handler(uint8_t *, const struct pcap_pkthdr *, const uint8_t *); +int trigger_init(Trigger *); +void trigger_check(Trigger *, Time); +int get_inet_scope(uint32_t, const Address *); +int get_inet6_scope(const struct in6_addr *, const Address *); +int pinger_init(Pinger *); +void pinger_check(Pinger *, Time); unsigned checksum(const char *, unsigned); -void send_ping(in_addr_t, uint16_t); +void send_ping(Pinger *); pid_t run_command(const char *); -int sock; -int pid; +int no_daemon; +int verbose; int main(int argc, char **argv) { - const char *ping_target_name = NULL; - in_addr_t ping_target = 0; - const char *interface = NULL; - struct pollfd pfd; - unsigned next_ping = 0; - uint16_t seq = 1; - uint16_t pending = 0; - unsigned ping_count = 0; - unsigned lost = 0; - unsigned next_stats = 0; + Monitor monitor; + Trigger trigger; + Pinger pinger; int o; char *endp; - int no_daemon = 0; - int verbose = 0; - unsigned stats_interval = 1800; - unsigned trigger_delay = 60; - unsigned trigger_interval = 600; - const char *trigger_cmd = NULL; - unsigned next_trigger; - pid_t child_pid = 0; - unsigned long long rx_packets = 0; - unsigned long long tx_packets = 0; - unsigned last_receive = 0; - unsigned last_transmit = 0; - int connection_status = 1; - unsigned connection_down_time; + Time stats_interval = 1800000000ULL; + + no_daemon = 0; + verbose = 0; + monitor.interface_name = NULL; + trigger.monitor = &monitor; + trigger.delay = 60000000ULL; + trigger.interval = 600000000ULL; + pinger.target_name = NULL; /* Parse options */ while((o = getopt(argc, argv, "fvs:t:c:i:p:"))!=-1) @@ -68,235 +113,382 @@ int main(int argc, char **argv) verbose = 1; break; case 's': - stats_interval = strtoul(optarg, NULL, 10); + stats_interval = strtoul(optarg, NULL, 10)*1000000; break; case 't': - trigger_delay = strtoul(optarg, &endp, 10); + trigger.delay = strtoul(optarg, &endp, 10)*1000000; if(*endp==',') - trigger_interval = strtoul(endp+1, NULL, 10); + trigger.interval = strtoul(endp+1, NULL, 10)*1000000; else - trigger_interval = trigger_delay; + trigger.interval = trigger.delay; break; case 'c': - trigger_cmd = optarg; + trigger.command = optarg; break; case 'i': - interface = optarg; + monitor.interface_name = strdup(optarg); break; case 'p': - ping_target_name = optarg; + pinger.target_name = strdup(optarg); break; } - if(ping_target_name) + if(monitor.interface_name) { - struct hostent *ping_target_host; + monitor_init(&monitor); + + if(trigger.command) + trigger_init(&trigger); + } + + if(pinger.target_name) + pinger_init(&pinger); - ping_target_host = gethostbyname(ping_target_name); - if(!ping_target_host) + if(!no_daemon && daemon(1, 0)==-1) + perror("daemon"); + + openlog("netmon", 0, LOG_LOCAL7); + + Time next_stats = current_time()+stats_interval; + while(1) + { + Time time = current_time(); + + if(monitor.interface_name) { - herror("gethostbyname"); - return 1; + monitor_check(&monitor, time); + + if(trigger.command) + trigger_check(&trigger, time); } - if(ping_target_host->h_addrtype!=AF_INET) + if(pinger.target_name) { - fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address"); - return 1; + pinger_check(&pinger, time); + + if(time>=next_stats) + { + if(pinger.count) + { + float loss_ratio = (float)pinger.lost/pinger.count; + if(no_daemon) + printf("Packet loss: %.2f%%\n", loss_ratio*100); + else + syslog(LOG_INFO, "Packet loss: %.2f%%", loss_ratio*100); + } + + pinger.count = 0; + pinger.lost = 0; + next_stats += stats_interval; + } } - ping_target = *(in_addr_t *)*ping_target_host->h_addr_list; + } + + closelog(); + + return 0; +} - if(verbose) - printf("Ping target is %s\n", inet_ntoa(*(struct in_addr *)*ping_target_host->h_addr_list)); +Time current_time() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec*1000000ULL+tv.tv_usec; +} + +int monitor_init(Monitor *monitor) +{ + char err[PCAP_ERRBUF_SIZE]; + pcap_if_t *devs; + if(pcap_findalldevs(&devs, err)==-1) + { + fprintf(stderr, "pcap_findalldevs: %s\n", err); + return -1; } - /* Miscellaneous initialization */ - pid = getpid(); - sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP); - if(sock==-1) + monitor->pcap = pcap_open_live(monitor->interface_name, 64, 0, 10, err); + if(!monitor->pcap) { - perror("socket"); - return 1; + fprintf(stderr, "pcap_findalldevs: %s\n", err); + return -1; } - pfd.fd = sock; - pfd.events = POLLIN; + if(pcap_setnonblock(monitor->pcap, 1, err)==-1) + { + fprintf(stderr, "pcap_findalldevs: %s\n", err); + return -1; + } - if(!no_daemon && daemon(1, 0)==-1) - perror("daemon"); + monitor->n_addresses = 0; + monitor->addresses = NULL; + for(pcap_if_t *d=devs; d; d=d->next) + for(pcap_addr_t *a=d->addresses; a; a=a->next) + if(a->addr->sa_family==AF_INET || a->addr->sa_family==AF_INET6) + { + monitor->addresses = (Address *)realloc(monitor->addresses, (monitor->n_addresses+1)*sizeof(Address)); + unsigned size = (a->addr->sa_family==AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + memcpy(&monitor->addresses[monitor->n_addresses].address, a->addr, size); + memcpy(&monitor->addresses[monitor->n_addresses].mask, a->netmask, size); + ++monitor->n_addresses; + } - openlog("netmon", 0, LOG_LOCAL7); + pcap_freealldevs(devs); - next_trigger = trigger_delay; - while(1) - { - struct timeval tv; - int res; - char fnbuf[256]; - unsigned long long count; - - gettimeofday(&tv, NULL); - - /* Read network statistics */ - snprintf(fnbuf, sizeof(fnbuf), "/sys/class/net/%s/statistics/rx_packets", interface); - count = read_number(fnbuf); - if(count>rx_packets) - last_receive = tv.tv_sec; - rx_packets = count; - - snprintf(fnbuf, sizeof(fnbuf), "/sys/class/net/%s/statistics/tx_packets", interface); - count = read_number(fnbuf); - if(count>tx_packets) - last_transmit = tv.tv_sec; - tx_packets = count; + monitor->last_receive = 0; + monitor->last_transmit = 0; + monitor->connection_status = 1; + return 0; +} + +void monitor_check(Monitor *monitor, Time timestamp) +{ + pcap_dispatch(monitor->pcap, -1, &capture_handler, (uint8_t *)monitor); + + if(monitor->last_transmit && monitor->last_receive) + { /* If packets have been transmitted more recently than received, there might be a problem */ - if(last_transmit>last_receive) + if(monitor->last_transmit>monitor->last_receive) { - if(connection_status && tv.tv_sec>=last_receive+10) + if(monitor->connection_status && timestamp>=monitor->last_receive+10000000) { - connection_status = 0; - connection_down_time = last_receive; + monitor->connection_status = 0; + monitor->connection_down_time = monitor->last_receive; if(no_daemon) printf("Connection is down\n"); else syslog(LOG_INFO, "Connection is down"); } - - if(trigger_cmd && tv.tv_sec>last_receive+next_trigger) - { - if(no_daemon) - printf("Running %s\n", trigger_cmd); - else - syslog(LOG_INFO, "Running %s", trigger_cmd); - child_pid = run_command(trigger_cmd); - next_trigger += trigger_interval; - } } - else if(!connection_status) + else if(!monitor->connection_status) { - unsigned duration = tv.tv_sec-connection_down_time; - connection_status = 1; + Time duration = timestamp-monitor->connection_down_time; + monitor->connection_status = 1; if(no_daemon) - printf("Connection is up (was down for %d seconds)\n", duration); + printf("Connection is up (was down for %lld seconds)\n", duration/1000000); else - syslog(LOG_INFO, "Connection is up (was down for %d seconds)", duration); - - next_trigger = trigger_delay; + syslog(LOG_INFO, "Connection is up (was down for %lld seconds)", duration/1000000); } + } +} - if(ping_target) - { - /* Send ping packets to monitor packet loss */ - if(tv.tv_sec>=next_ping) - { - if(pending) - { - ++lost; - if(verbose) - printf("Lost ping\n"); - } +void capture_handler(uint8_t *user, const struct pcap_pkthdr *header, const uint8_t *data) +{ + Monitor *monitor = (Monitor *)user; + const struct ether_header *eth = (const struct ether_header *)(data); - send_ping(ping_target, seq); - pending = seq++; - if(!seq) - ++seq; - ++ping_count; - next_ping = tv.tv_sec+1; - } + int src_scope = 0; + int dst_scope = 0; + int proto = ntohs(eth->ether_type); + if(proto==ETHERTYPE_IP) + { + const struct ip *ip = (const struct ip *)(eth+1); + if(ntohl(ip->ip_dst.s_addr)>>28==14) + dst_scope = 3; - if(tv.tv_sec>=next_stats) + for(unsigned i=0; in_addresses; ++i) + if(monitor->addresses[i].address.ss_family==AF_INET) { - if(lost) - { - float loss_ratio = (float)lost/ping_count; - if(no_daemon) - printf("Packet loss: %.2f%%\n", loss_ratio*100); - else - syslog(LOG_INFO, "Packet loss: %.2f%%", loss_ratio*100); + int s = get_inet_scope(ip->ip_src.s_addr, &monitor->addresses[i]); + if(s>src_scope) + src_scope = s; - ping_count = 0; - lost = 0; - } + s = get_inet_scope(ip->ip_dst.s_addr, &monitor->addresses[i]); + if(s>dst_scope) + dst_scope = s; + } + } + else if(proto==ETHERTYPE_IPV6) + { + const struct ip6_hdr *ip6 = (const struct ip6_hdr *)(eth+1); + for(unsigned i=0; in_addresses; ++i) + if(monitor->addresses[i].address.ss_family==AF_INET6) + { + int s = get_inet6_scope(&ip6->ip6_src, &monitor->addresses[i]); + if(s>src_scope) + src_scope = s; - next_stats = tv.tv_sec+stats_interval; + s = get_inet6_scope(&ip6->ip6_dst, &monitor->addresses[i]); + if(s>dst_scope) + dst_scope = s; } - } + } - /* Reap any finished child process */ - if(child_pid) - { - if(waitpid(child_pid, NULL, WNOHANG)==child_pid) - child_pid = 0; - } + Time timestamp = header->ts.tv_sec*1000000ULL+header->ts.tv_usec; + if(src_scope==0 && dst_scope>0) + monitor->last_receive = timestamp; + else if(src_scope>0 && dst_scope==0) + monitor->last_transmit = timestamp; +} - res = poll(&pfd, 1, 1000); - if(res>0) +int trigger_init(Trigger *trigger) +{ + trigger->next = trigger->delay; + trigger->child_pid = 0; + + return 0; +} + +void trigger_check(Trigger *trigger, Time time) +{ + Monitor *monitor = trigger->monitor; + + if(monitor->connection_status) + trigger->next = trigger->delay; + else if(time>=monitor->last_receive+trigger->next) + { + if(no_daemon) + printf("Running %s\n", trigger->command); + else + syslog(LOG_INFO, "Running %s", trigger->command); + trigger->child_pid = run_command(trigger->command); + trigger->next += trigger->interval; + } + + /* Reap any finished child process */ + if(trigger->child_pid) + { + if(waitpid(trigger->child_pid, NULL, WNOHANG)==trigger->child_pid) + trigger->child_pid = 0; + } +} + +int get_inet_scope(uint32_t addr, const Address *local_addr) +{ + uint32_t diff = addr^((struct sockaddr_in *)&local_addr->address)->sin_addr.s_addr; + if(!diff) + return 2; + else if(!(diff&((struct sockaddr_in *)&local_addr->mask)->sin_addr.s_addr)) + return 1; + else + return 0; +} + +int get_inet6_scope(const struct in6_addr *addr, const Address *local_addr) +{ + int result = 2; + for(unsigned i=0; i<16; ++i) + { + uint8_t diff = addr->s6_addr[i]^((struct sockaddr_in6 *)&local_addr->address)->sin6_addr.s6_addr[i]; + if(diff) { - struct sockaddr_in addr; - socklen_t alen = sizeof(addr); - char data[1500]; - int len; - struct iphdr *ip; - struct icmphdr *icmp; - - /* Receive a packet */ - len = recvfrom(sock, data, sizeof(data), 0, (struct sockaddr *)&addr, &alen); - if(len==-1) - fprintf(stderr, "recvfrom error: %s\n", strerror(errno)); + diff &= ((struct sockaddr_in6 *)&local_addr->mask)->sin6_addr.s6_addr[i]; + if(diff) + return 0; else - { - ip = (struct iphdr *)data; - if(ip->protocol==IPPROTO_ICMP) - { - icmp = (struct icmphdr *)(ip+1); - if(icmp->type==ICMP_ECHOREPLY && icmp->un.echo.id==pid) - { - /* It's an ICMP echo reply and ours, process it */ - if(verbose) - printf("Ping reply from %s\n", inet_ntoa(addr.sin_addr)); - if(icmp->un.echo.sequence==pending) - pending = 0; - else if(verbose) - printf("Sequence %d, expected %d\n", icmp->un.echo.sequence, pending); - } - } - } + result = 1; } } - closelog(); + return result; +} + +int pinger_init(Pinger *pinger) +{ + if(!pinger->target_name) + return -1; + + struct hostent *host; + + host = gethostbyname(pinger->target_name); + if(!host) + { + herror("gethostbyname"); + return -1; + } + + if(host->h_addrtype!=AF_INET) + { + fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address"); + return -1; + } + + pinger->target_addr.sin_family = AF_INET; + pinger->target_addr.sin_addr = *(struct in_addr *)host->h_addr_list[0]; + + if(verbose) + { + char buf[64]; + inet_ntop(AF_INET, &pinger->target_addr.sin_addr, buf, sizeof(buf)); + printf("Ping target is %s\n", buf); + } + + pinger->socket = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP); + if(pinger->socket==-1) + { + perror("socket"); + return -1; + } + + pinger->next = 0; + pinger->id = getpid(); + pinger->seq = 1; + pinger->pending = 0; + pinger->count = 0; + pinger->lost = 0; + + pinger->pfd.fd = pinger->socket; + pinger->pfd.events = POLLIN; return 0; } -unsigned long long read_number(const char *fn) +void pinger_check(Pinger *pinger, Time time) { - char buf[64]; - int fd; - int len; - - fd = open(fn, O_RDONLY); - if(fd<0) - return 0; + if(poll(&pinger->pfd, 1, 10)>0) + { + struct sockaddr_in addr; + socklen_t alen = sizeof(addr); + + /* Receive a packet */ + char data[1500]; + int len = recvfrom(pinger->socket, data, sizeof(data), 0, (struct sockaddr *)&addr, &alen); + if(len==-1) + fprintf(stderr, "recvfrom error: %s\n", strerror(errno)); + else + { + struct ip *ip = (struct ip *)data; + if(ip->ip_p==IPPROTO_ICMP) + { + struct icmp *icmp = (struct icmp *)(ip+1); + if(icmp->icmp_type==ICMP_ECHOREPLY && icmp->icmp_id==pinger->id) + { + /* It's an ICMP echo reply and ours, process it */ + if(verbose) + { + char buf[64]; + inet_ntop(AF_INET, &addr.sin_addr, buf, sizeof(buf)); + printf("Ping reply from %s\n", buf); + } - len = read(fd, buf, sizeof(buf)); - close(fd); - if(len<0) - return 0; + if(icmp->icmp_seq==pinger->pending) + pinger->pending = 0; + else if(verbose) + printf("Sequence %d, expected %d\n", icmp->icmp_seq, pinger->pending); + } + } + } + } - buf[len] = 0; + if(time>=pinger->next) + { + if(pinger->pending) + { + ++pinger->lost; + if(verbose) + printf("Lost ping\n"); + } - return strtoull(buf, NULL, 10); + send_ping(pinger); + pinger->next = time+1000000; + } } unsigned checksum(const char *data, unsigned len) { unsigned sum = 0; - unsigned i; - - for(i=0; i0xFFFF) sum = (sum>>16)+(sum&0xFFFF); @@ -304,30 +496,27 @@ unsigned checksum(const char *data, unsigned len) return ~sum; } -void send_ping(in_addr_t target, uint16_t seq) +void send_ping(Pinger *pinger) { char data[64]; - unsigned i; - struct icmphdr *hdr; - struct sockaddr_in addr; - - for(i=0; itype = ICMP_ECHO; - hdr->code = 0; - hdr->checksum = 0; - hdr->un.echo.id = pid; - hdr->un.echo.sequence = seq; - hdr->checksum = checksum(data, sizeof(data)); + struct icmp *hdr = (struct icmp *)data; + hdr->icmp_type = ICMP_ECHO; + hdr->icmp_code = 0; + hdr->icmp_cksum = 0; + hdr->icmp_id = pinger->id; + hdr->icmp_seq = pinger->seq; + hdr->icmp_cksum = checksum(data, sizeof(data)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = target; - addr.sin_port = 0; - - if(sendto(sock, data, sizeof(data), 0, (struct sockaddr *)&addr, sizeof(addr))==-1) + if(sendto(pinger->socket, data, sizeof(data), 0, (struct sockaddr *)&pinger->target_addr, sizeof(struct sockaddr_in))==-1) fprintf(stderr, "sendto error: %s\n", strerror(errno)); + + pinger->pending = pinger->seq++; + if(!pinger->seq) + ++pinger->seq; + ++pinger->count; } pid_t run_command(const char *cmd)