aboutsummaryrefslogtreecommitdiffstats
path: root/pacredir.c
diff options
context:
space:
mode:
Diffstat (limited to 'pacredir.c')
-rw-r--r--pacredir.c909
1 files changed, 619 insertions, 290 deletions
diff --git a/pacredir.c b/pacredir.c
index 4bfb16c..570799f 100644
--- a/pacredir.c
+++ b/pacredir.c
@@ -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;
- case AVAHI_BROWSER_REMOVE:
- host = get_fqdn(name, domain);
+ 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;
+ 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, "Service %s on host %s disappeared\n",
- type, host);
+ write_log(stdout, "Host %s is on an ignored interface.\n", canonical);
+ goto finish_service;
+ }
- remove_host(host, protocol, type);
+ if (match < DNS_SRV_TXT_MATCH_ALL) {
+ if (verbose > 0)
+ write_log(stdout, "Host %s does not match distribution and/or architecture.\n", canonical);
+ goto finish_service;
+ }
- free(host);
+ /* add the peer to our struct */
+ add_host(canonical, port, 1);
- break;
+ goto finish_service;
- case AVAHI_BROWSER_ALL_FOR_NOW:
- case AVAHI_BROWSER_CACHE_EXHAUSTED:
- break;
+parse_failure_service:
+ write_log(stderr, "Parse failure for service: %s\n", strerror(-r));
+
+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,6 +1022,7 @@ 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.
@@ -685,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);
@@ -699,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);
@@ -754,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,
@@ -785,21 +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 */
+ /* register SIG{INT,KILL,TERM} signal callbacks */
struct sigaction act = { 0 };
act.sa_handler = sig_callback;
- sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGINT, &act, NULL);
sigaction(SIGKILL, &act, NULL);
- sigaction(SIGINT, &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...");
@@ -817,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!");