X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=main.c;h=38624d03e082abcf17c6c94855b761b28d1de6c0;hb=732b02c54457bfaccaac77c9431fb8aee13233f0;hp=fd2e8789148735853ac4c954fda0466d5a78687d;hpb=ea00f37d541ba2c75a4a33c5d37c9ae15dac5ab7;p=netmon.git diff --git a/main.c b/main.c index fd2e878..38624d0 100644 --- a/main.c +++ b/main.c @@ -1,50 +1,96 @@ /* netmon - a simple network connectivity monitor -Copyright © 2008 Mikko Rasa, Mikkosoft Productions +Copyright © 2008-2016 Mikko Rasa, Mikkosoft Productions 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 + +typedef unsigned long long Time; +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 +{ + 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 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 *target_name = "google.com"; - struct hostent *target_host; - in_addr_t target; - struct pollfd pfd; - uint16_t seq = 1; - uint16_t pending = 0; - unsigned lost = 0; - unsigned streak = 0; - unsigned max_streak = 0; - unsigned next_stats = 0; + Monitor monitor; + Pinger pinger; int o; - int no_daemon = 0; - int verbose = 0; - unsigned stats_interval = 1800; - unsigned trigger_count = 900; + char *endp; + Time stats_interval = 1800000000ULL; + Time trigger_delay = 60000000ULL; + Time trigger_interval = 600000000ULL; const char *trigger_cmd = NULL; - unsigned next_trigger; - pid_t child_pid = 0; + + no_daemon = 0; + verbose = 0; + monitor.interface_name = NULL; + pinger.target_name = NULL; /* Parse options */ - while((o = getopt(argc, argv, "fvi:t:c:"))!=-1) + while((o = getopt(argc, argv, "fvs:t:c:i:p:"))!=-1) switch(o) { case 'f': @@ -53,160 +99,364 @@ int main(int argc, char **argv) case 'v': verbose = 1; break; - case 'i': - stats_interval = strtoul(optarg, NULL, 10); + case 's': + stats_interval = strtoul(optarg, NULL, 10)*1000000; break; case 't': - trigger_count = strtoul(optarg, NULL, 10); + trigger_delay = strtoul(optarg, &endp, 10)*1000000; + if(*endp==',') + trigger_interval = strtoul(endp+1, NULL, 10)*1000000; + else + trigger_interval = trigger_delay; break; case 'c': trigger_cmd = optarg; break; + case 'i': + monitor.interface_name = strdup(optarg); + break; + case 'p': + pinger.target_name = strdup(optarg); + break; } - /* Take target hostname from commandline if specified */ - if(optindh_addrtype!=AF_INET) - { - fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address"); - return 1; - } - target = ntohl(*(in_addr_t *)*target_host->h_addr_list); - - if(verbose) - printf("Target is %s\n", inet_ntoa(htonl(target))); - - /* Miscellaneous initialization */ - pid = getpid(); - sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP); - if(sock==-1) - { - perror("socket"); - return 1; - } - - pfd.fd = sock; - pfd.events = POLLIN; + if(pinger.target_name) + pinger_init(&pinger); if(!no_daemon && daemon(1, 0)==-1) perror("daemon"); openlog("netmon", 0, LOG_LOCAL7); - next_trigger = trigger_count; + Time next_stats = current_time()+stats_interval; + Time next_trigger = trigger_delay; + pid_t child_pid = 0; while(1) { - struct timeval tv; - int res; - - gettimeofday(&tv, NULL); + Time time = current_time(); - /* Send stats to syslog every stats_interval seconds */ - if(tv.tv_sec>next_stats) + if(monitor.interface_name) { - if(next_stats) + monitor_check(&monitor, time); + + if(monitor.connection_status) + next_trigger = trigger_delay; + else if(time>=monitor.last_receive+next_trigger) { if(no_daemon) - printf("Lost %d, max streak %d\n", lost, max_streak); + printf("Running %s\n", trigger_cmd); else - syslog(LOG_INFO, "Lost %d, max streak %d", lost, max_streak); - lost = 0; - max_streak = 0; + syslog(LOG_INFO, "Running %s", trigger_cmd); + child_pid = run_command(trigger_cmd); + next_trigger += trigger_interval; } - next_stats = tv.tv_sec+stats_interval; } + if(pinger.target_name) + { + 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; + } + } + + /* Reap any finished child process */ if(child_pid) { if(waitpid(child_pid, NULL, WNOHANG)==child_pid) child_pid = 0; } + } - res = poll(&pfd, 1, 1000); - if(res>0) + closelog(); + + return 0; +} + +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; + } + + monitor->pcap = pcap_open_live(monitor->interface_name, 64, 0, 10, err); + if(!monitor->pcap) + { + fprintf(stderr, "pcap_findalldevs: %s\n", err); + return -1; + } + + if(pcap_setnonblock(monitor->pcap, 1, err)==-1) + { + fprintf(stderr, "pcap_findalldevs: %s\n", err); + return -1; + } + + 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; + } + + pcap_freealldevs(devs); + + 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(monitor->last_transmit>monitor->last_receive) { - 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)); + if(monitor->connection_status && timestamp>=monitor->last_receive+10000000) + { + 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"); + } + } + else if(!monitor->connection_status) + { + Time duration = timestamp-monitor->connection_down_time; + monitor->connection_status = 1; + if(no_daemon) + printf("Connection is up (was down for %lld seconds)\n", duration/1000000); else + syslog(LOG_INFO, "Connection is up (was down for %lld seconds)", duration/1000000); + } + } +} + +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); + + 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; + + for(unsigned i=0; in_addresses; ++i) + if(monitor->addresses[i].address.ss_family==AF_INET) { - 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.s_addr)); - if(icmp->un.echo.sequence==pending) - { - pending = 0; - streak = 0; - next_trigger = trigger_count; - } - else if(verbose) - printf("Sequence %d, expected %d\n", icmp->un.echo.sequence, pending); - } - } + int s = get_inet_scope(ip->ip_src.s_addr, &monitor->addresses[i]); + if(s>src_scope) + src_scope = s; + + 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; + + s = get_inet6_scope(&ip6->ip6_dst, &monitor->addresses[i]); + if(s>dst_scope) + dst_scope = s; + } + } + + 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; +} + +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) + { + diff &= ((struct sockaddr_in6 *)&local_addr->mask)->sin6_addr.s6_addr[i]; + if(diff) + return 0; + else + result = 1; } + } + + 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; +} + +void pinger_check(Pinger *pinger, Time time) +{ + 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 { - /* Poll timed out, check for lost ping and send more */ - if(pending) + struct ip *ip = (struct ip *)data; + if(ip->ip_p==IPPROTO_ICMP) { - if(verbose) - printf("Lost ping\n"); - ++lost; - ++streak; - if(streak>max_streak) - max_streak = streak; - if(streak>=next_trigger && trigger_cmd && !child_pid) + struct icmp *icmp = (struct icmp *)(ip+1); + if(icmp->icmp_type==ICMP_ECHOREPLY && icmp->icmp_id==pinger->id) { - if(no_daemon) - printf("Running %s\n", trigger_cmd); - else - syslog(LOG_INFO, "Running %s\n", trigger_cmd); - child_pid = run_command(trigger_cmd); - next_trigger += trigger_count; + /* 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); + } + + if(icmp->icmp_seq==pinger->pending) + pinger->pending = 0; + else if(verbose) + printf("Sequence %d, expected %d\n", icmp->icmp_seq, pinger->pending); } } - send_ping(target, seq); - pending = seq; - if(!++seq) - ++seq; } } - closelog(); + if(time>=pinger->next) + { + if(pinger->pending) + { + ++pinger->lost; + if(verbose) + printf("Lost ping\n"); + } - return 0; + 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); @@ -214,30 +464,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 = htonl(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) @@ -250,7 +497,7 @@ pid_t run_command(const char *cmd) const char *argv[2]; argv[0] = cmd; argv[1] = 0; - execv(cmd, argv); + execv(cmd, (char *const *)argv); exit(127); } else