diff options
Diffstat (limited to 'pacredir.c')
-rw-r--r-- | pacredir.c | 917 |
1 files changed, 625 insertions, 292 deletions
@@ -1,5 +1,5 @@ /* - * (C) 2013-2024 by Christian Hesse <mail@eworm.de> + * (C) 2013-2025 by Christian Hesse <mail@eworm.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,251 +32,454 @@ const static struct option options_long[] = { struct hosts * hosts = NULL; struct ignore_interfaces * ignore_interfaces = NULL; int max_threads = 0; -static AvahiSimplePoll *simple_poll = NULL; -uint8_t verbose = 0; +uint8_t quit = 0, update = 0, verbose = 0; unsigned int count_redirect = 0, count_not_found = 0; /*** write_log ***/ -int write_log(FILE *stream, const char *format, ...) { +static int write_log(FILE *stream, const char *format, ...) { va_list args; - va_start(args, format); + va_start(args, format); vfprintf(stream, format, args); + va_end(args); fflush(stream); return EXIT_SUCCESS; } -/*** get_fqdn ***/ -char * get_fqdn(const char * hostname, const char * domainname) { - char * name; - - name = malloc(strlen(hostname) + strlen(domainname) + 2 /* '.' and null char */); - sprintf(name, "%s.%s", hostname, domainname); - - return name; -} - /*** get_url ***/ -char * get_url(const char * hostname, AvahiProtocol proto, const char * address, const uint16_t port, const uint8_t dbfile, const char * uri) { - const char * host, * dir; +static char * get_url(const char * hostname, const uint16_t port, const uint8_t dbfile, const char * uri) { + const char * dir; char * url; - host = *address ? address : hostname; - dir = dbfile ? "db" : "pkg"; - - url = malloc(10 /* static chars of an url & null char */ - + strlen(host) + url = malloc(11 /* static chars of an url & null char */ + + strlen(hostname) + 5 /* max strlen of decimal 16bit value */ - + 2 /* square brackets for IPv6 address */ - + 4 /* extra dir */ + + strlen(dir) + strlen(uri)); - if (*address != 0 && proto == AVAHI_PROTO_INET6) - sprintf(url, "http://[%s]:%d/%s/%s", address, port, dir, uri); - else - sprintf(url, "http://%s:%d/%s/%s", host, port, dir, uri); + sprintf(url, "http://%s:%d/%s/%s", hostname, port, dir, uri); return url; } -/*** add_host ***/ -int add_host(const char * host, AvahiProtocol proto, const char * address, const uint16_t port, const char * type) { - struct hosts * tmphosts = hosts; - struct request request; +/*** update_interfaces ***/ +static void update_interfaces(void) { + struct ignore_interfaces *ignore_interfaces_ptr = ignore_interfaces; - while (tmphosts->host != NULL) { - if (strcmp(tmphosts->host, host) == 0 && tmphosts->proto == proto) { - /* host already exists */ - if (verbose > 0) - write_log(stdout, "Updating service %s (port %d) on host %s (%s)\n", - type, port, host, avahi_proto_to_string(proto)); - goto update; - } - tmphosts = tmphosts->next; + while (ignore_interfaces_ptr->interface != NULL) { + ignore_interfaces_ptr->ifindex = if_nametoindex(ignore_interfaces_ptr->interface); + ignore_interfaces_ptr = ignore_interfaces_ptr->next; } +} - /* host not found, adding a new one */ - if (verbose > 0) - write_log(stdout, "Adding host %s (%s) with service %s (port %d)\n", - host, avahi_proto_to_string(proto), type, port); - - tmphosts->host = strdup(host); - tmphosts->proto = AVAHI_PROTO_UNSPEC; - *tmphosts->address = 0; +/*** get_name ***/ +static size_t get_name(const uint8_t* rr_ptr, char* name) { + uint8_t dot = 0; + char *name_ptr = name; - tmphosts->port = 0; - tmphosts->online = 0; - tmphosts->badtime = 0; - tmphosts->badcount = 0; + for (;;) { + if (*rr_ptr == 0) + return (strlen(name) + 2); + if (dot) + *(name_ptr++) = '.'; + else + dot++; + memcpy(name_ptr, rr_ptr + 1, *rr_ptr + 1); + name_ptr += *rr_ptr; + rr_ptr += *rr_ptr + 1; + } +} - tmphosts->next = malloc(sizeof(struct hosts)); - tmphosts->next->host = NULL; - tmphosts->next->next = NULL; +/*** process_reply_record ***/ +static char* process_reply_record(const void *rr, size_t sz) { + uint16_t class, type, rdlength; + const uint8_t *rr_ptr = rr; + char *name; + uint32_t ttl; -update: - tmphosts->proto = proto; - if (address != NULL) - memcpy(tmphosts->address, address, AVAHI_ADDRESS_STR_MAX); + rr_ptr += strlen((char*)rr_ptr) + 1; + memcpy(&type, rr_ptr, sizeof(uint16_t)); + rr_ptr += sizeof(uint16_t); + memcpy(&class, rr_ptr, sizeof(uint16_t)); + rr_ptr += sizeof(uint16_t); + memcpy(&ttl, rr_ptr, sizeof(uint32_t)); + rr_ptr += sizeof(uint32_t); + memcpy(&rdlength, rr_ptr, sizeof(uint16_t)); + rr_ptr += sizeof(uint16_t); + assert(be16toh(type) == DNS_TYPE_PTR); + assert(be16toh(class) == DNS_CLASS_IN); - tmphosts->online = 1; - tmphosts->port = port; + name = malloc(strlen((char*)rr_ptr) + 1); + rr_ptr += get_name(rr_ptr, name); - /* do a first request and let get_http_code() set the bad status */ - request.host = tmphosts; - request.url = get_url(request.host->host, request.host->proto, request.host->address, request.host->port, 0, ""); - request.http_code = 0; - request.last_modified = 0; - get_http_code(&request); - free(request.url); + assert(rr_ptr == (const uint8_t*) rr + sz); - return EXIT_SUCCESS; + return name; } -/*** remove_host ***/ -int remove_host(const char * host, AvahiProtocol proto, const char * type) { - struct hosts * tmphosts = hosts; +/*** update_hosts ***/ +static void update_hosts(void) { + struct if_nameindex *if_nidxs, *intf; + struct hosts *hosts_ptr = hosts; + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply = NULL; + sd_bus *bus = NULL; + int r, sock; - while (tmphosts->host != NULL) { - if (strcmp(tmphosts->host, host) == 0 && tmphosts->proto == proto) { - if (verbose > 0) - write_log(stdout, "Marking service %s on host %s (%s) offline\n", - type, host, avahi_proto_to_string(proto)); - tmphosts->online = 0; + /* set 'present' to 0, so we later know which hosts were available, and which were not */ + while (hosts_ptr->host != NULL) { + hosts_ptr->present = 0; + hosts_ptr = hosts_ptr->next; + } + + r = sd_bus_open_system(&bus); + if (r < 0) { + write_log(stderr, "Failed to open system bus: %s\n", strerror(-r)); + goto fast_finish; + } + + r = sd_bus_call_method(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", "ResolveRecord", &error, + &reply, "isqqt", 0 /* any */, PACSERVE "." MDNS_DOMAIN, + DNS_CLASS_IN, DNS_TYPE_PTR, SD_RESOLVED_NO_SYNTHESIZE|SD_RESOLVED_NO_ZONE); + if (r < 0) { + if (verbose > 0) + write_log(stderr, "Failed to resolve record: %s\n", error.message); + sd_bus_error_free(&error); + goto finish; + } + + /* On empty cache systemd-resolved returns just one record on first query, + happened above. Similar delays are seen for new hosts. + Wait a moment, call again for full update, then goto finish. + TODO: Drop when continuous mDNS querying becomes available! */ + usleep(250000); + + if ((if_nidxs = if_nameindex()) == NULL) { + write_log(stderr, "Failed to get list of interfaces.\n"); + goto finish; + } + + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + write_log(stderr, "Failed to open control socket.\n"); + goto finish; + } + + for (intf = if_nidxs; intf->if_index != 0 || intf->if_name != NULL; intf++) { + struct ifreq ifr; + struct ignore_interfaces *ignore_interfaces_ptr = ignore_interfaces; + uint8_t ignore = 0; + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, intf->if_name, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = 0; + + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { + write_log(stderr, "Failed to get flags for interface %s.\n", intf->if_name); + continue; + } + + if ((ifr.ifr_flags & IFF_UP) == 0) + continue; + + if ((ifr.ifr_flags & IFF_LOOPBACK) > 0) + continue; + + if ((ifr.ifr_flags & IFF_RUNNING) == 0) + continue; + + while (ignore_interfaces_ptr->interface != NULL) { + if (ignore_interfaces_ptr->ifindex == intf->if_index) { + ignore++; + break; + } + + ignore_interfaces_ptr = ignore_interfaces_ptr->next; + } + + if (!ignore) + update_hosts_on_interface(bus, intf->if_index); + + if (quit) break; + } + + close(sock); + if_freenameindex(if_nidxs); + +finish: + /* mark hosts offline that did not show up in query */ + hosts_ptr = hosts; + while (hosts_ptr->host != NULL) { + if (hosts_ptr->mdns == 1 && hosts_ptr->online == 1 && hosts_ptr->present == 0) { + if (verbose > 0) + write_log(stdout, "Marking host %s offline\n", hosts_ptr->host); + hosts_ptr->online = 0; } - tmphosts = tmphosts->next; + hosts_ptr = hosts_ptr->next; } - return EXIT_SUCCESS; +fast_finish: + sd_bus_message_unref(reply); + sd_bus_flush_close_unref(bus); } -/*** resolve_callback *** - * Called whenever a service has been resolved successfully or timed out */ -static void resolve_callback(AvahiServiceResolver *r, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiResolverEvent event, - const char *name, - const char *type, - const char *domain, - const char *host, - const AvahiAddress *address, - uint16_t port, - AvahiStringList *txt, - AvahiLookupResultFlags flags, - void* userdata) { - char ipaddress[AVAHI_ADDRESS_STR_MAX]; - char intname[IFNAMSIZ]; +/*** update_hosts_on_interface ***/ +static void update_hosts_on_interface(sd_bus *bus, const unsigned int if_index) { + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply_record = NULL; + uint64_t flags; + int r; - assert(r); + r = sd_bus_call_method(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", "ResolveRecord", &error, + &reply_record, "isqqt", if_index, PACSERVE "." MDNS_DOMAIN, + DNS_CLASS_IN, DNS_TYPE_PTR, SD_RESOLVED_NO_SYNTHESIZE|SD_RESOLVED_NO_ZONE); + if (r < 0) { + if (verbose > 0) + write_log(stderr, "Failed to resolve record: %s\n", error.message); + sd_bus_error_free(&error); + goto finish; + } - if_indextoname(interface, intname); + r = sd_bus_message_enter_container(reply_record, 'a', "(iqqay)"); + if (r < 0) + goto parse_failure_record; - switch (event) { - case AVAHI_RESOLVER_FAILURE: - write_log(stderr, "Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", - name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + for (;;) { + int ifindex; + uint16_t class, type, port; + const void *data; + size_t length; + char *peer; + const char *canonical, *discard; + uint8_t match = 0, ignore = 0; + + r = sd_bus_message_enter_container(reply_record, 'r', "iqqay"); + if (r < 0) + goto parse_failure_record; + if (r == 0) /* Reached end of array */ break; + r = sd_bus_message_read(reply_record, "iqq", &ifindex, &class, &type); + if (r < 0) + goto parse_failure_record; + r = sd_bus_message_read_array(reply_record, 'y', &data, &length); + if (r < 0) + goto parse_failure_record; + r = sd_bus_message_exit_container(reply_record); + if (r < 0) + goto parse_failure_record; - case AVAHI_RESOLVER_FOUND: - avahi_address_snprint(ipaddress, AVAHI_ADDRESS_STR_MAX, address); + /* process the data received */ + peer = process_reply_record(data, length); + sd_bus_message *reply_service = NULL; + /* service START */ + r = sd_bus_call_method(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", "ResolveService", &error, + &reply_service, "isssit", 0 /* any */, "", "", peer, AF_UNSPEC, UINT64_C(0)); + if (r < 0) { if (verbose > 0) - write_log(stdout, "Found service %s on host %s (%s) on interface %s\n", - type, host, ipaddress, intname); + write_log(stderr, "Failed to resolve service '%s': %s\n", peer, error.message); + sd_bus_error_free(&error); + goto finish_service; + } - add_host(host, protocol, ipaddress, PORT_PACSERVE, type); - break; - } + r = sd_bus_message_enter_container(reply_service, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + goto parse_failure_service; - avahi_service_resolver_free(r); -} + for (;;) { + uint16_t priority, weight; + const char *hostname; -/*** browse_callback *** - * Called whenever a new services becomes available on the LAN or is removed from the LAN */ -static void browse_callback(AvahiServiceBrowser *b, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiBrowserEvent event, - const char *name, - const char *type, - const char *domain, - AvahiLookupResultFlags flags, - void* userdata) { - char * host; - char intname[IFNAMSIZ]; - struct ignore_interfaces * tmp_ignore_interfaces = ignore_interfaces; - AvahiClient * c; + r = sd_bus_message_enter_container(reply_service, 'r', "qqqsa(iiay)s"); + if (r < 0) + goto parse_failure_service; + if (r == 0) /* Reached end of array */ + break; + r = sd_bus_message_read(reply_service, "qqqs", &priority, &weight, &port, &hostname); + if (r < 0) + goto parse_failure_service; - assert(b); + r = sd_bus_message_enter_container(reply_service, 'a', "(iiay)"); + if (r < 0) + goto parse_failure_service; - c = userdata; - if_indextoname(interface, intname); + for (;;) { + int ifindex, family; + const void *data; + size_t length; - switch (event) { - case AVAHI_BROWSER_FAILURE: - write_log(stderr, "Failed to browse: %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); - avahi_simple_poll_quit(simple_poll); - return; + r = sd_bus_message_enter_container(reply_service, 'r', "iiay"); + if (r < 0) + goto parse_failure_service; + if (r == 0) /* Reached end of array */ + break; + r = sd_bus_message_read(reply_service, "ii", &ifindex, &family); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_read_array(reply_service, 'y', &data, &length); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; + } + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; - case AVAHI_BROWSER_NEW: - host = get_fqdn(name, domain); + r = sd_bus_message_read(reply_service, "s", &canonical); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; + } - if (flags & AVAHI_LOOKUP_RESULT_LOCAL) - goto out; + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_enter_container(reply_service, 'a', "ay"); + if (r < 0) + goto parse_failure_service; - /* check whether to ignore the interface */ - while (tmp_ignore_interfaces->next != NULL) { - if (strcmp(intname, tmp_ignore_interfaces->interface) == 0) { - if (verbose > 0) - write_log(stdout, "Ignoring service %s on host %s on interface %s\n", - type, host, intname); - goto out; - } - tmp_ignore_interfaces = tmp_ignore_interfaces->next; - } + for(;;) { + const void *txt_data; + size_t txt_len; - if ((avahi_service_resolver_new(c, interface, protocol, name, type, domain, protocol, 0, resolve_callback, c)) == NULL) - write_log(stderr, "Failed to create resolver for service '%s' of type '%s' in domain '%s': %s\n", - name, type, domain, avahi_strerror(avahi_client_errno(c))); -out: - free(host); + r = sd_bus_message_read_array(reply_service, 'y', &txt_data, &txt_len); + if (r < 0) + goto parse_failure_service; + if (r == 0) /* Reached end of array */ + break; - break; + /* does the TXT data match our architecture (arch) or distribution (id)? */ + if (strncmp((char*)txt_data, "arch=" ARCH, txt_len) == 0) + match |= DNS_SRV_TXT_MATCH_ARCH; + if (strncmp((char*)txt_data, "id=" ID, txt_len) == 0) + match |= DNS_SRV_TXT_MATCH_ID; + } + + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; + + r = sd_bus_message_read(reply_service, "s", &discard); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_read(reply_service, "s", &discard); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_read(reply_service, "s", &discard); + if (r < 0) + goto parse_failure_service; - case AVAHI_BROWSER_REMOVE: - host = get_fqdn(name, domain); + r = sd_bus_message_read(reply_service, "t", &flags); + if (r < 0) + goto parse_failure_service; + + if (ignore > 0) { + if (verbose > 0) + write_log(stdout, "Host %s is on an ignored interface.\n", canonical); + goto finish_service; + } + if (match < DNS_SRV_TXT_MATCH_ALL) { if (verbose > 0) - write_log(stdout, "Service %s on host %s disappeared\n", - type, host); + write_log(stdout, "Host %s does not match distribution and/or architecture.\n", canonical); + goto finish_service; + } - remove_host(host, protocol, type); + /* add the peer to our struct */ + add_host(canonical, port, 1); - free(host); + goto finish_service; - break; +parse_failure_service: + write_log(stderr, "Parse failure for service: %s\n", strerror(-r)); - case AVAHI_BROWSER_ALL_FOR_NOW: - case AVAHI_BROWSER_CACHE_EXHAUSTED: - break; +finish_service: + free(peer); + sd_bus_message_unref(reply_service); } + + r = sd_bus_message_exit_container(reply_record); + if (r < 0) + goto parse_failure_record; + r = sd_bus_message_read(reply_record, "t", &flags); + if (r < 0) + goto parse_failure_record; + + goto finish; + +parse_failure_record: + write_log(stderr, "Parse failure for record: %s\n", strerror(-r)); + +finish: + sd_bus_message_unref(reply_record); } -/*** client_callback ***/ -static void client_callback(AvahiClient *c, - AvahiClientState state, - void * userdata) { - assert(c); +/*** add_host ***/ +static int add_host(const char * host, const uint16_t port, const uint8_t mdns) { + struct hosts * hosts_ptr = hosts; - if (state == AVAHI_CLIENT_FAILURE) { - write_log(stderr, "Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c))); - avahi_simple_poll_quit(simple_poll); + while (hosts_ptr->host != NULL) { + if (strcmp(hosts_ptr->host, host) == 0) { + /* host already exists */ + if (verbose > 0) + write_log(stdout, "Updating host %s with port %d\n", + host, port); + goto update; + } + hosts_ptr = hosts_ptr->next; } + + /* host not found, adding a new one */ + if (verbose > 0) + write_log(stdout, "Adding host %s with port %d\n", + host, port); + + hosts_ptr->host = strdup(host); + hosts_ptr->mdns = mdns; + hosts_ptr->badtime = 0; + hosts_ptr->badcount = 0; + hosts_ptr->finds = 0; + + hosts_ptr->next = malloc(sizeof(struct hosts)); + hosts_ptr->next->host = NULL; + hosts_ptr->next->next = NULL; + +update: + hosts_ptr->port = port; + hosts_ptr->online = 1; + hosts_ptr->present = 1; + + return EXIT_SUCCESS; } +/*** remove_host ***/ +/* currently unused, but could become important if continuous + mDNS querying becomes available again on day... +static int remove_host(const char * host) { + struct hosts * hosts_ptr = hosts; + + while (hosts_ptr->host != NULL) { + if (strcmp(hosts_ptr->host, host) == 0) { + if (verbose > 0) + write_log(stdout, "Marking host %s offline\n", host); + hosts_ptr->online = 0; + break; + } + hosts_ptr = hosts_ptr->next; + } + + return EXIT_SUCCESS; +} */ + /*** get_http_code ***/ static void * get_http_code(void * data) { struct request * request = (struct request *)data; @@ -299,9 +502,9 @@ static void * get_http_code(void * data) { curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); /* ask for filetime */ curl_easy_setopt(curl, CURLOPT_FILETIME, 1L); - /* set connection timeout to 5 seconds + /* set connection timeout to 2 seconds * if the host needs longer we do not want to use it anyway ;) */ - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 2L); /* time out if connection is established but transfer rate is low * this should make curl finish after a maximum of 8 seconds */ curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L); @@ -352,9 +555,101 @@ static void * get_http_code(void * data) { return NULL; } +/* append_string */ +static char * append_string(char * string, const char *format, ...) { + va_list args; + size_t string_len = 0, append_len; + + if (string != NULL) + string_len = strlen(string); + + va_start(args, format); + append_len = vsnprintf(NULL, 0, format, args) + 1; + va_end(args); + + string = realloc(string, string_len + append_len); + + va_start(args, format); + vsnprintf(string + string_len, append_len, format, args); + va_end(args); + + return string; +} +/*** status_page ***/ +static char * status_page(void) { + struct ignore_interfaces * ignore_interfaces_ptr = ignore_interfaces; + struct hosts * hosts_ptr = hosts; + char *page = NULL, *overall = CIRCLE_BLUE; + char hostname[HOST_NAME_MAX]; + struct timeval tv; + + /* initialize struct timeval */ + gettimeofday(&tv, NULL); + + if (count_redirect + count_not_found) + switch (count_redirect * 4 / (count_redirect + count_not_found)) { + case 0: + overall = CIRCLE_RED; + break; + case 1: + overall = CIRCLE_ORANGE; + break; + case 2: + overall = CIRCLE_YELLOW; + break; + default: + overall = CIRCLE_GREEN; + break; + } + + gethostname(hostname, HOST_NAME_MAX); + page = append_string(page, STATUS_HEAD, hostname, count_redirect, count_not_found, overall); + + page = append_string(page, STATUS_INT_HEAD); + if (ignore_interfaces_ptr->interface == NULL) + page = append_string(page, STATUS_INT_NONE); + while (ignore_interfaces_ptr->interface != NULL) { + if (ignore_interfaces_ptr->ifindex > 0) + /* write_log(stdout, STATUS_INT_ONE, + ignore_interfaces_ptr->interface, ignore_interfaces_ptr->ifindex); */ + page = append_string(page, STATUS_INT_ONE, + ignore_interfaces_ptr->interface, ignore_interfaces_ptr->ifindex); + else + page = append_string(page, STATUS_INT_ONE_NA, + ignore_interfaces_ptr->interface); + + ignore_interfaces_ptr = ignore_interfaces_ptr->next; + } + page = append_string(page, STATUS_INT_FOOT); + + page = append_string(page, STATUS_HOST_HEAD); + if (hosts_ptr->host == NULL) + page = append_string(page, STATUS_HOST_NONE); + while (hosts_ptr->host != NULL) { + time_t badtime = hosts_ptr->badtime + hosts_ptr->badcount * BADTIME; + uint8_t bad = hosts_ptr->badcount && badtime > tv.tv_sec ? 1 : 0; + + page = append_string(page, STATUS_HOST_ONE, + (hosts_ptr->mdns && !hosts_ptr->online) || bad ? " class=\"grey\"" : "", + hosts_ptr->host, hosts_ptr->port, + hosts_ptr->mdns ? (hosts_ptr->online ? CIRCLE_GREEN : CIRCLE_RED) : CIRCLE_BLUE, + hosts_ptr->mdns ? (hosts_ptr->online ? "online" : "offline") : "static", + hosts_ptr->finds ? CIRCLE_GREEN : CIRCLE_BLUE, hosts_ptr->finds, + bad ? CIRCLE_RED : CIRCLE_BLUE, + hosts_ptr->badcount); + + hosts_ptr = hosts_ptr->next; + } + page = append_string(page, STATUS_HOST_FOOT); + + page = append_string(page, STATUS_FOOT); + + return page; +} + /*** ahc_echo *** * called whenever a http request is received */ -static mhd_result ahc_echo(void * cls, +static enum MHD_Result ahc_echo(void * cls, struct MHD_Connection * connection, const char * uri, const char * method, @@ -365,7 +660,7 @@ static mhd_result ahc_echo(void * cls, static int dummy; struct MHD_Response * response; int ret; - struct hosts * tmphosts = hosts; + struct hosts * hosts_ptr = hosts; char * url = NULL, * page = NULL; const char * basename, * host = NULL; @@ -379,13 +674,33 @@ static mhd_result ahc_echo(void * cls, pthread_t * tid = NULL; struct request ** requests = NULL; struct request * request = NULL; - long http_code = 0; + long http_code = MHD_HTTP_NOT_FOUND; double time_total = INFINITY; char ctime[26]; /* initialize struct timeval */ gettimeofday(&tv, NULL); + /* give status page */ + if (strcmp(uri, "/") == 0) { + http_code = MHD_HTTP_OK; + page = status_page(); + goto response; + } + + /* give favicon */ + if (strcmp(uri, "/favicon.png") == 0) { + http_code = MHD_HTTP_OK; + goto response; + } + + /* give a simple ok response for monitoring */ + if (strcmp(uri, "/check") == 0) { + http_code = MHD_HTTP_OK; + page = strdup("OK"); + goto response; + } + /* we want the filename, not the path */ basename = uri; while (strstr(basename, "/") != NULL) @@ -409,15 +724,6 @@ static mhd_result ahc_echo(void * cls, /* clear context pointer */ *ptr = NULL; - /* redirect to website if no file given */ - if (*basename == 0) { - http_code = MHD_HTTP_OK; - /* duplicate string so we can free it later */ - url = strdup(WEBSITE); - host = basename = "project site"; - goto response; - } - /* process db file request (*.db and *.files) */ if ((strlen(basename) > 3 && strcmp(basename + strlen(basename) - 3, ".db") == 0) || (strlen(basename) > 6 && strcmp(basename + strlen(basename) - 6, ".files") == 0)) { @@ -434,16 +740,15 @@ static mhd_result ahc_echo(void * cls, } /* try to find a peer with most recent file */ - while (tmphosts->host != NULL) { - struct hosts * host = tmphosts; - time_t badtime = host->badtime + host->badcount * BADTIME; + while (hosts_ptr->host != NULL) { + time_t badtime = hosts_ptr->badtime + hosts_ptr->badcount * BADTIME; /* skip host if offline or had a bad request within last BADTIME seconds */ - if (host->online == 0) { + if (hosts_ptr->online == 0) { if (verbose > 0) - write_log(stdout, "Service %s on host %s is offline, skipping\n", - PACSERVE, tmphosts->host); - tmphosts = tmphosts->next; + write_log(stdout, "Host %s is offline, skipping\n", + hosts_ptr->host); + hosts_ptr = hosts_ptr->next; continue; } else if (badtime > tv.tv_sec) { if (verbose > 0) { @@ -451,10 +756,10 @@ static mhd_result ahc_echo(void * cls, ctime_r(&badtime, ctime); ctime[strlen(ctime) - 1] = '\0'; - write_log(stdout, "Service %s on host %s is marked bad until %s, skipping\n", - PACSERVE, tmphosts->host, ctime); + write_log(stdout, "Host %s is marked bad until %s, skipping\n", + hosts_ptr->host, ctime); } - tmphosts = tmphosts->next; + hosts_ptr = hosts_ptr->next; continue; } @@ -481,18 +786,18 @@ static mhd_result ahc_echo(void * cls, request = requests[req_count]; /* prepare request struct */ - request->host = tmphosts; - request->url = get_url(request->host->host, request->host->proto, request->host->address, request->host->port, dbfile, basename); + request->host = hosts_ptr; + request->url = get_url(request->host->host, request->host->port, dbfile, basename); request->http_code = 0; request->last_modified = 0; if (verbose > 0) - write_log(stdout, "Trying %s: %s\n", request->host, request->url); + write_log(stdout, "Trying %s: %s\n", request->host->host, request->url); if ((error = pthread_create(&tid[req_count], NULL, get_http_code, (void *)request)) != 0) write_log(stderr, "Could not run thread number %d, errno %d\n", req_count, error); - tmphosts = tmphosts->next; + hosts_ptr = hosts_ptr->next; } /* try to find a suitable response */ @@ -527,11 +832,12 @@ static mhd_result ahc_echo(void * cls, request->time_total < time_total))) || /* for packages try to guess the fastest peer */ (dbfile == 0 && request->time_total < time_total))) { + request->host->finds++; if (url != NULL) free(url); url = request->url; host = request->host->host; - http_code = MHD_HTTP_OK; + http_code = MHD_HTTP_TEMPORARY_REDIRECT; last_modified = request->last_modified; time_total = request->time_total; } else @@ -541,22 +847,34 @@ static mhd_result ahc_echo(void * cls, /* increase counters before reponse label, do not count redirects to project page */ - if (http_code == MHD_HTTP_OK) + if (http_code == MHD_HTTP_TEMPORARY_REDIRECT) count_redirect++; else count_not_found++; response: /* give response */ - if (http_code == MHD_HTTP_OK) { + if (http_code == MHD_HTTP_TEMPORARY_REDIRECT) { write_log(stdout, "Redirecting to %s: %s\n", host, url); page = malloc(strlen(PAGE307) + strlen(url) + strlen(basename) + 1); sprintf(page, PAGE307, url, basename); response = MHD_create_response_from_buffer(strlen(page), (void*) page, MHD_RESPMEM_MUST_FREE); ret = MHD_add_response_header(response, "Location", url); - ret = MHD_queue_response(connection, MHD_HTTP_TEMPORARY_REDIRECT, response); free(url); - } else { + } else if (http_code == MHD_HTTP_OK) { + if (page != NULL) { + write_log(stdout, "Sending status page.\n"); + response = MHD_create_response_from_buffer(strlen(page), (void*) page, MHD_RESPMEM_MUST_FREE); + ret = MHD_add_response_header(response, "Content-Type", "text/html"); + } else { + write_log(stdout, "Sending favicon.\n"); + response = MHD_create_response_from_buffer(sizeof(favicon), favicon, MHD_RESPMEM_PERSISTENT); + ret = MHD_add_response_header(response, "ETag", FAVICON_SHA1); + ret = MHD_add_response_header(response, "Last-Modified", FAVICON_DATE); + ret = MHD_add_response_header(response, "Cache-Control", "max-age=86400"); + ret = MHD_add_response_header(response, "Content-Type", "image/png"); + } + } else { /* MHD_HTTP_NOT_FOUND */ if (req_count < 0) write_log(stdout, "Currently no peers are available to check for %s.\n", basename); @@ -570,9 +888,10 @@ response: page = malloc(strlen(PAGE404) + strlen(basename) + 1); sprintf(page, PAGE404, basename); response = MHD_create_response_from_buffer(strlen(page), (void*) page, MHD_RESPMEM_MUST_FREE); - ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); } + ret = MHD_add_response_header(response, "Server", PROGNAME " v" VERSION " " ID "/" ARCH); + ret = MHD_queue_response(connection, http_code, response); MHD_destroy_response(response); /* report counts to systemd */ @@ -588,23 +907,62 @@ response: } /*** sig_callback ***/ -void sig_callback(int signal) { +static void sig_callback(int signal) { write_log(stdout, "Received signal '%s', quitting.\n", strsignal(signal)); - avahi_simple_poll_quit(simple_poll); + quit++; } /*** sighup_callback ***/ -void sighup_callback(int signal) { - struct hosts * tmphosts = hosts; +static void sighup_callback(int signal) { + struct hosts * hosts_ptr = hosts; - write_log(stdout, "Received SIGHUP, resetting bad status for hosts.\n"); + write_log(stdout, "Received signal '%s', resetting bad counts, updating interfaces and hosts.\n", + strsignal(signal)); - while (tmphosts->host != NULL) { - tmphosts->badtime = 0; - tmphosts->badcount = 0; - tmphosts = tmphosts->next; + while (hosts_ptr->host != NULL) { + hosts_ptr->badtime = 0; + hosts_ptr->badcount = 0; + hosts_ptr = hosts_ptr->next; } + + update++; +} + +/*** sigusr_callback ***/ +static void sigusr_callback(int signal) { + struct ignore_interfaces * ignore_interfaces_ptr = ignore_interfaces; + struct hosts * hosts_ptr = hosts; + + write_log(stdout, "Received signal '%s', dumping state.\n", strsignal(signal)); + + write_log(stdout, "Ignored interfaces:\n"); + if (ignore_interfaces_ptr->interface == NULL) + write_log(stdout, " (none)\n"); + while (ignore_interfaces_ptr->interface != NULL) { + if (ignore_interfaces_ptr->ifindex > 0) + write_log(stdout, " -> %s (link %d)\n", + ignore_interfaces_ptr->interface, ignore_interfaces_ptr->ifindex); + else + write_log(stdout, " -> %s (N/A)\n", ignore_interfaces_ptr->interface); + + ignore_interfaces_ptr = ignore_interfaces_ptr->next; + } + + write_log(stdout, "Known hosts:\n"); + if (hosts_ptr->host == NULL) + write_log(stdout, " (none)\n"); + while (hosts_ptr->host != NULL) { + write_log(stdout, " -> %s (%s, %s, port: %d, finds: %d, bad: %d)\n", + hosts_ptr->host, hosts_ptr->mdns ? "mdns" : "static", + hosts_ptr->online ? "online" : "offline", hosts_ptr->port, + hosts_ptr->finds, hosts_ptr->badcount); + + hosts_ptr = hosts_ptr->next; + } + + write_log(stdout, "%d redirects, %d not found.\n", + count_redirect, count_not_found); } /*** main ***/ @@ -612,14 +970,11 @@ int main(int argc, char ** argv) { dictionary * ini; const char * inistring; char * values, * value; - int8_t use_proto = AVAHI_PROTO_UNSPEC; uint16_t port; - struct ignore_interfaces * tmp_ignore_interfaces; - AvahiClient *client = NULL; - AvahiServiceBrowser *pacserve = NULL; - int error, i, ret = 1; + struct ignore_interfaces * ignore_interfaces_ptr; + int i, ret = 1, sleepsec = 0; struct MHD_Daemon * mhd; - struct hosts * tmphosts; + struct hosts * hosts_ptr; struct sockaddr_in address; unsigned int version = 0, help = 0; @@ -642,10 +997,7 @@ int main(int argc, char ** argv) { if (verbose > 0) write_log(stdout, "%s: " PROGNAME " v" VERSION " " ID "/" ARCH -#if REPRODUCIBLE == 0 - " (compiled: " __DATE__ ", " __TIME__ ")" -#endif - "\n", argv[0]); + " (built: " __DATE__ ", " __TIME__ ")\n", argv[0]); if (help > 0) write_log(stdout, "usage: %s [-h] [-v] [-V]\n", argv[0]); @@ -670,12 +1022,15 @@ int main(int argc, char ** argv) { ignore_interfaces = malloc(sizeof(struct ignore_interfaces)); ignore_interfaces->interface = NULL; + ignore_interfaces->ifindex = 0; ignore_interfaces->next = NULL; /* Probing for static pacserve hosts takes some time. * Receiving a SIGHUP at this time could kill us. So register signal * SIGHUP here before probing. */ - signal(SIGHUP, sighup_callback); + struct sigaction act_hup = { 0 }; + act_hup.sa_handler = sighup_callback; + sigaction(SIGHUP, &act_hup, NULL); /* parse config file */ if ((ini = iniparser_load(CONFIGFILE)) == NULL) { @@ -683,7 +1038,6 @@ int main(int argc, char ** argv) { /* continue anyway, there is nothing essential in the config file */ } else { int ini_verbose; - const char * tmp; /* extra verbosity from config */ ini_verbose = iniparser_getint(ini, "general:verbose", 0); @@ -697,52 +1051,36 @@ int main(int argc, char ** argv) { /* store interfaces to ignore */ if ((inistring = iniparser_getstring(ini, "general:ignore interfaces", NULL)) != NULL) { values = strdup(inistring); - tmp_ignore_interfaces = ignore_interfaces; + ignore_interfaces_ptr = ignore_interfaces; value = strtok(values, DELIMITER); while (value != NULL) { if (verbose > 0) write_log(stdout, "Ignoring interface: %s\n", value); - tmp_ignore_interfaces->interface = strdup(value); - tmp_ignore_interfaces->next = malloc(sizeof(struct ignore_interfaces)); - tmp_ignore_interfaces = tmp_ignore_interfaces->next; + ignore_interfaces_ptr->interface = strdup(value); + ignore_interfaces_ptr->next = malloc(sizeof(struct ignore_interfaces)); + ignore_interfaces_ptr = ignore_interfaces_ptr->next; value = strtok(NULL, DELIMITER); } - tmp_ignore_interfaces->interface = NULL; - tmp_ignore_interfaces->next = NULL; + ignore_interfaces_ptr->interface = NULL; + ignore_interfaces_ptr->next = NULL; free(values); } - /* configure protocols to use */ - if ((tmp = iniparser_getstring(ini, "general:protocol", NULL)) != NULL) { - switch(tmp[strlen(tmp) - 1]) { - case '4': - if (verbose > 0) - write_log(stdout, "Using IPv4 only\n"); - use_proto = AVAHI_PROTO_INET; - break; - case '6': - if (verbose > 0) - write_log(stdout, "Using IPv6 only\n"); - use_proto = AVAHI_PROTO_INET6; - break; - } - } - /* add static pacserve hosts */ if ((inistring = iniparser_getstring(ini, "general:pacserve hosts", NULL)) != NULL) { values = strdup(inistring); value = strtok(values, DELIMITER); while (value != NULL) { if (verbose > 0) - write_log(stdout, "Adding static pacserve host: %s\n", value); + write_log(stdout, "Adding static host: %s\n", value); if (strchr(value, ':') != NULL) { port = atoi(strchr(value, ':') + 1); *strchr(value, ':') = 0; } else port = PORT_PACSERVE; - add_host(value, AVAHI_PROTO_UNSPEC, NULL, port, PACSERVE); + add_host(value, port, 0); value = strtok(NULL, DELIMITER); } free(values); @@ -752,29 +1090,10 @@ int main(int argc, char ** argv) { iniparser_freedict(ini); } - /* allocate main loop object */ - if ((simple_poll = avahi_simple_poll_new()) == NULL) { - write_log(stderr, "Failed to create simple poll object.\n"); - goto fail; - } - - /* allocate a new client */ - if ((client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, client_callback, NULL, &error)) == NULL) { - write_log(stderr, "Failed to create client: %s\n", avahi_strerror(error)); - goto fail; - } - - /* create the service browser for PACSERVE */ - if ((pacserve = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, - use_proto, PACSERVE, NULL, 0, browse_callback, client)) == NULL) { - write_log(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client))); - goto fail; - } - /* prepare struct to make microhttpd listen on localhost only */ address.sin_family = AF_INET; address.sin_port = htons(PORT_PACREDIR); - address.sin_addr.s_addr = htonl(0x7f000001); + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* start http server */ if ((mhd = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_TCP_FASTOPEN, PORT_PACREDIR, @@ -783,19 +1102,40 @@ int main(int argc, char ** argv) { goto fail; } + if (verbose > 0) + write_log(stdout, "Listening on port %d\n", PORT_PACREDIR); + /* initialize curl */ curl_global_init(CURL_GLOBAL_ALL); - /* register SIG{TERM,KILL,INT} signal callbacks */ - signal(SIGTERM, sig_callback); - signal(SIGKILL, sig_callback); - signal(SIGINT, sig_callback); + /* register SIG{INT,KILL,TERM} signal callbacks */ + struct sigaction act = { 0 }; + act.sa_handler = sig_callback; + sigaction(SIGINT, &act, NULL); + sigaction(SIGKILL, &act, NULL); + sigaction(SIGTERM, &act, NULL); + + /* register SIGUSR[12] signal callbacks */ + struct sigaction act_usr = { 0 }; + act_usr.sa_handler = sigusr_callback; + sigaction(SIGUSR1, &act_usr, NULL); + sigaction(SIGUSR2, &act_usr, NULL); /* report ready to systemd */ sd_notify(0, "READY=1\nSTATUS=Waiting for requests to redirect..."); - /* run the main loop */ - avahi_simple_poll_loop(simple_poll); + /* main loop */ + while (quit == 0) { + sleepsec = sleep(sleepsec); + + if (sleepsec > 0 && update == 0) + continue; + + update_interfaces(); + update_hosts(); + update = 0; + sleepsec = 60; + } /* report stopping to systemd */ sd_notify(0, "STOPPING=1\nSTATUS=Stopping..."); @@ -813,26 +1153,19 @@ fail: /* Cleanup things */ while (hosts->host != NULL) { free(hosts->host); - tmphosts = hosts->next; + hosts_ptr = hosts->next; free(hosts); - hosts = tmphosts; + hosts = hosts_ptr; } + free(hosts); while (ignore_interfaces->interface != NULL) { free(ignore_interfaces->interface); - tmp_ignore_interfaces = ignore_interfaces->next; + ignore_interfaces_ptr = ignore_interfaces->next; free(ignore_interfaces); - ignore_interfaces = tmp_ignore_interfaces; + ignore_interfaces = ignore_interfaces_ptr; } - - if (pacserve) - avahi_service_browser_free(pacserve); - - if (client) - avahi_client_free(client); - - if (simple_poll) - avahi_simple_poll_free(simple_poll); + free(ignore_interfaces); sd_notify(0, "STATUS=Stopped. Bye!"); |