+static void addr_add(meshlink_handle_t *mesh, const discovery_address_t *addr) {
+ discovery_address_t *p = bsearch(addr, mesh->discovery.addresses, mesh->discovery.address_count, sizeof(*p), address_compare);
+
+ if(p) {
+ return;
+ }
+
+ bool up = bsearch(&addr->index, mesh->discovery.ifaces, mesh->discovery.iface_count, sizeof(int), iface_compare);
+
+ mesh->discovery.addresses = xrealloc(mesh->discovery.addresses, ++mesh->discovery.address_count * sizeof(*p));
+ mesh->discovery.addresses[mesh->discovery.address_count - 1] = *addr;
+ mesh->discovery.addresses[mesh->discovery.address_count - 1].up = up;
+
+ if(up) {
+ send_mdns_packet(mesh, &mesh->discovery.addresses[mesh->discovery.address_count - 1]);
+ }
+
+ qsort(mesh->discovery.addresses, mesh->discovery.address_count, sizeof(*p), address_compare);
+}
+
+static void addr_del(meshlink_handle_t *mesh, const discovery_address_t *addr) {
+ discovery_address_t *p = bsearch(addr, mesh->discovery.addresses, mesh->discovery.address_count, sizeof(*p), address_compare);
+
+ if(!p) {
+ return;
+ }
+
+ memmove(p, p + 1, (mesh->discovery.addresses + --mesh->discovery.address_count - p) * sizeof(*p));
+}
+
+void scan_ifaddrs(meshlink_handle_t *mesh) {
+#ifdef HAVE_GETIFADDRS
+ logger(mesh, MESHLINK_WARNING, "Calling getifaddrs()");
+ struct ifaddrs *ifa = NULL;
+
+ if(getifaddrs(&ifa) == -1) {
+ logger(mesh, MESHLINK_ERROR, "Could not get list of interface addresses: %s", strerror(errno));
+ return;
+ }
+
+ // Check for interfaces being removed
+ for(int i = 0; i < mesh->discovery.iface_count;) {
+ bool found = false;
+
+ for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) {
+ if(!ifap->ifa_name) {
+ continue;
+ }
+
+ int index = if_nametoindex(ifap->ifa_name);
+
+ if(mesh->discovery.ifaces[i] == index) {
+ found = true;
+ break;
+ }
+ }
+
+ if(!found) {
+ iface_down(mesh, mesh->discovery.ifaces[i]);
+ } else {
+ i++;
+ }
+ }
+
+ // Check for addresses being removed
+ for(int i = 0; i < mesh->discovery.address_count;) {
+ discovery_address_t *p = &mesh->discovery.addresses[i];
+ bool found = false;
+
+ for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) {
+ if(!ifap->ifa_name || !ifap->ifa_addr) {
+ continue;
+ }
+
+ int index = if_nametoindex(ifap->ifa_name);
+
+ if(p->index == index && sockaddrcmp_noport(&p->address, (sockaddr_t *)ifap->ifa_addr) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if(!found) {
+ (void)addr_del;
+ memmove(p, p + 1, (mesh->discovery.addresses + --mesh->discovery.address_count - p) * sizeof(*p));
+ } else {
+ i++;
+ }
+ }
+
+ // Check for interfaces state changes and addresses going up
+ for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) {
+ if(!ifap->ifa_name) {
+ continue;
+ }
+
+ int index = if_nametoindex(ifap->ifa_name);
+
+ if(ifap->ifa_flags & IFF_UP && ifap->ifa_flags & IFF_MULTICAST && !(ifap->ifa_flags & IFF_LOOPBACK)) {
+ iface_up(mesh, index);
+ } else {
+ iface_down(mesh, index);
+ }
+
+ if(!ifap->ifa_addr) {
+ continue;
+ }
+
+ discovery_address_t addr = {
+ .index = index,
+ };
+
+ sockaddr_t *sa = (sockaddr_t *)ifap->ifa_addr;
+
+ if(sa->sa.sa_family == AF_INET) {
+ memcpy(&addr.address.in, &sa->in, sizeof(sa->in));
+ addr.address.in.sin_port = ntohs(5353);
+ } else if(sa->sa.sa_family == AF_INET6) {
+ memcpy(&addr.address.in6, &sa->in6, sizeof(sa->in6));
+ addr.address.in6.sin6_port = ntohs(5353);
+ } else {
+ addr.address.sa.sa_family = AF_UNKNOWN;
+ }
+
+ if(addr.address.sa.sa_family != AF_UNKNOWN) {
+ char *host = NULL;
+ sockaddr2str(sa, &host, NULL);
+ logger(mesh, MESHLINK_WARNING, "iface %d (%s) address %s", index, ifap->ifa_name, host);
+ free(host);
+ }
+
+ if(addr.address.sa.sa_family != AF_UNKNOWN) {
+ addr_add(mesh, &addr);
+ }
+ }
+
+ freeifaddrs(ifa);
+#else
+ logger(mesh, MESHLINK_ERROR, "getifaddrs() not supported");
+#endif
+}
+
+#if defined(__linux)
+static void netlink_getlink(int fd) {
+ static const struct {
+ struct nlmsghdr nlm;
+ struct ifinfomsg ifi;
+ } msg = {
+ .nlm.nlmsg_len = NLMSG_LENGTH(sizeof(msg.ifi)),
+ .nlm.nlmsg_type = RTM_GETLINK,
+ .nlm.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+ .nlm.nlmsg_seq = 1,
+ .ifi.ifi_family = AF_UNSPEC,
+ };
+ send(fd, &msg, msg.nlm.nlmsg_len, 0);
+}
+
+static void netlink_getaddr(int fd) {
+ static const struct {
+ struct nlmsghdr nlm;
+ struct ifaddrmsg ifa;
+ } msg = {
+ .nlm.nlmsg_len = NLMSG_LENGTH(sizeof(msg.ifa)),
+ .nlm.nlmsg_type = RTM_GETADDR,
+ .nlm.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+ .nlm.nlmsg_seq = 2,
+ .ifa.ifa_family = AF_UNSPEC,
+ };
+ send(fd, &msg, msg.nlm.nlmsg_len, 0);
+}
+
+static void netlink_parse_link(meshlink_handle_t *mesh, const struct nlmsghdr *nlm) {
+ const struct ifinfomsg *ifi = (const struct ifinfomsg *)(nlm + 1);
+
+ if(ifi->ifi_flags & IFF_UP && ifi->ifi_flags & IFF_MULTICAST) {
+ iface_up(mesh, ifi->ifi_index);
+ } else {
+ iface_down(mesh, ifi->ifi_index);
+ }
+}
+
+static void netlink_parse_addr(meshlink_handle_t *mesh, const struct nlmsghdr *nlm) {
+ const struct ifaddrmsg *ifa = (const struct ifaddrmsg *)(nlm + 1);
+ const uint8_t *ptr = (const uint8_t *)(ifa + 1);
+ size_t len = nlm->nlmsg_len - (ptr - (const uint8_t *)nlm);
+
+ while(len >= sizeof(struct rtattr)) {
+ const struct rtattr *rta = (const struct rtattr *)ptr;
+
+ if(rta->rta_len <= 0 || rta->rta_len > len) {
+ break;
+ }
+
+ if(rta->rta_type == IFA_ADDRESS) {
+ discovery_address_t addr = {
+ .index = ifa->ifa_index,
+ };
+
+ if(rta->rta_len == 8) {
+ addr.address.sa.sa_family = AF_INET;
+ memcpy(&addr.address.in.sin_addr, ptr + 4, 4);
+ addr.address.in.sin_port = ntohs(5353);
+ } else if(rta->rta_len == 20) {
+ addr.address.sa.sa_family = AF_INET6;
+ memcpy(&addr.address.in6.sin6_addr, ptr + 4, 16);
+ addr.address.in6.sin6_port = ntohs(5353);
+ addr.address.in6.sin6_scope_id = ifa->ifa_index;
+ } else {
+ addr.address.sa.sa_family = AF_UNKNOWN;
+ }
+
+ if(addr.address.sa.sa_family != AF_UNKNOWN) {
+ if(nlm->nlmsg_type == RTM_NEWADDR) {
+ addr_add(mesh, &addr);
+ } else {
+ addr_del(mesh, &addr);
+ }
+ }
+ }
+
+ unsigned short rta_len = (rta->rta_len + 3) & ~3;
+ ptr += rta_len;
+ len -= rta_len;
+ }
+}
+
+static void netlink_parse(meshlink_handle_t *mesh, const void *data, size_t len) {
+ const uint8_t *ptr = data;
+
+ while(len >= sizeof(struct nlmsghdr)) {
+ const struct nlmsghdr *nlm = (const struct nlmsghdr *)ptr;
+
+ if(nlm->nlmsg_len > len) {
+ break;
+ }
+
+ switch(nlm->nlmsg_type) {
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ netlink_parse_link(mesh, nlm);
+ break;
+
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ netlink_parse_addr(mesh, nlm);
+ }
+
+ ptr += nlm->nlmsg_len;
+ len -= nlm->nlmsg_len;
+ }
+}
+
+static void netlink_io_handler(event_loop_t *loop, void *data, int flags) {
+ (void)flags;
+ (void)data;
+ meshlink_handle_t *mesh = loop->data;
+
+ struct {
+ struct nlmsghdr nlm;
+ char data[16384];
+ } msg;
+
+ while(true) {
+ ssize_t result = recv(mesh->discovery.pfroute_io.fd, &msg, sizeof(msg), MSG_DONTWAIT);
+
+ if(result <= 0) {
+ if(result == 0 || errno == EAGAIN || errno == EINTR) {
+ break;
+ }
+
+ logger(mesh, MESHLINK_ERROR, "Reading from Netlink socket failed: %s\n", strerror(errno));
+ io_set(loop, &mesh->discovery.pfroute_io, 0);
+ }
+
+ if((size_t)result < sizeof(msg.nlm)) {
+ logger(mesh, MESHLINK_ERROR, "Invalid Netlink message\n");
+ break;
+ }
+
+ if(msg.nlm.nlmsg_type == NLMSG_DONE) {
+ if(msg.nlm.nlmsg_seq == 1) {
+ // We just got the result of GETLINK, now send GETADDR.
+ netlink_getaddr(mesh->discovery.pfroute_io.fd);
+ }
+ } else {
+ netlink_parse(mesh, &msg, result);
+
+ if(loop->now.tv_sec > mesh->discovery.last_update + 5) {
+ mesh->discovery.last_update = loop->now.tv_sec;
+ handle_network_change(mesh, 1);
+ }
+ }
+ }
+}
+#elif defined(__APPLE__)
+static void network_change_callback(SCDynamicStoreRef store, CFArrayRef keys, void *info) {
+ (void)store;
+ (void)keys;
+
+ meshlink_handle_t *mesh = info;
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ logger(mesh, MESHLINK_ERROR, "Network change detected!");
+ scan_ifaddrs(mesh);
+
+ if(mesh->loop.now.tv_sec > mesh->discovery.last_update + 5) {
+ mesh->discovery.last_update = mesh->loop.now.tv_sec;
+ handle_network_change(mesh, 1);
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+static void *network_change_handler(void *arg) {