]> git.meshlink.io Git - meshlink/blobdiff - src/discovery.c
Never automatically try to bind to ports >= 32768.
[meshlink] / src / discovery.c
index 439bcbef479a1e6053ab2109bee4f9650098934f..032b559996f3e1a5adc9a01d6f6e24cbdd5e1da4 100644 (file)
@@ -1,7 +1,33 @@
+/*
+  discovery.c -- local network discovery
+  Copyright (C) 2014-2021 Guus Sliepen <guus@meshlink.io>
+
+  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
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along
+  with this program; if not, write to the Free Software Foundation, Inc.,
+  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
 #define __APPLE_USE_RFC_3542
 #include "system.h"
 
-#if defined(__APPLE__) || defined(__unix) && !defined(__linux)
+#if defined(__APPLE__)
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFArray.h>
+#include <CoreFoundation/CFString.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#elif defined(__unix) && !defined(__linux)
 #include <net/if.h>
 #include <net/route.h>
 #include <netinet/in.h>
@@ -49,7 +75,7 @@ static const sockaddr_t mdns_address_ipv4 = {
 
 static const sockaddr_t mdns_address_ipv6 = {
        .in6.sin6_family = AF_INET6,
-       .in6.sin6_addr.s6_addr[0x0] = 0xfd,
+       .in6.sin6_addr.s6_addr[0x0] = 0xff,
        .in6.sin6_addr.s6_addr[0x1] = 0x02,
        .in6.sin6_addr.s6_addr[0xf] = 0xfb,
        .in6.sin6_port = 0xe914,
@@ -59,6 +85,7 @@ typedef struct discovery_address {
        int index;
        bool up;
        sockaddr_t address;
+       time_t last_response_sent;
 } discovery_address_t;
 
 static int iface_compare(const void *va, const void *vb) {
@@ -79,6 +106,19 @@ static int address_compare(const void *va, const void *vb) {
 }
 
 static void send_mdns_packet_ipv4(meshlink_handle_t *mesh, int fd, int index, const sockaddr_t *src, const sockaddr_t *dest, void *data, size_t len) {
+#ifdef IP_MULTICAST_IF
+       struct ip_mreqn mreq = {
+               .imr_address = src->in.sin_addr,
+               .imr_ifindex = index,
+       };
+
+       if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq)) != 0) {
+               logger(mesh, MESHLINK_ERROR, "Could not set outgoing multicast interface on IPv4 socket");
+               return;
+       }
+
+#endif
+
 #ifdef IP_PKTINFO
        struct iovec iov  = {
                .iov_base = data,
@@ -95,6 +135,8 @@ static void send_mdns_packet_ipv4(meshlink_handle_t *mesh, int fd, int index, co
                struct cmsghdr align;
        } u;
 
+       memset(&u, 0, sizeof(u));
+
        struct msghdr msg = {
                .msg_name = (struct sockaddr *) &dest->sa,
                .msg_namelen = SALEN(dest->sa),
@@ -104,7 +146,6 @@ static void send_mdns_packet_ipv4(meshlink_handle_t *mesh, int fd, int index, co
                .msg_controllen = sizeof(u.buf),
        };
 
-
        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_level = IPPROTO_IP;
        cmsg->cmsg_type = IP_PKTINFO;
@@ -127,6 +168,15 @@ static void send_mdns_packet_ipv4(meshlink_handle_t *mesh, int fd, int index, co
 }
 
 static void send_mdns_packet_ipv6(meshlink_handle_t *mesh, int fd, int index, const sockaddr_t *src, const sockaddr_t *dest, void *data, size_t len) {
+#ifdef IPV6_MULTICAST_IF
+
+       if(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)) != 0) {
+               logger(mesh, MESHLINK_ERROR, "Could not set outgoing multicast interface on IPv6 socket");
+               return;
+       }
+
+#endif
+
 #ifdef IPV6_PKTINFO
        struct iovec iov  = {
                .iov_base = data,
@@ -175,49 +225,29 @@ static void send_mdns_packet_ipv6(meshlink_handle_t *mesh, int fd, int index, co
        }
 }
 
-static void send_mdns_packet(meshlink_handle_t *mesh, const discovery_address_t *addr) {
+static void send_mdns_packet(meshlink_handle_t *mesh, discovery_address_t *addr, bool response) {
        // Configure the socket to send the packet to the right interface
-       int fd;
-       uint8_t data[1024];
-       char *fingerprint = meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self);
-       const char *keys[] = {MESHLINK_MDNS_NAME_KEY, MESHLINK_MDNS_FINGERPRINT_KEY};
-       const char *values[] = {mesh->name, fingerprint};
-       size_t size = prepare_packet(data, sizeof data, fingerprint, mesh->appname, "tcp", atoi(mesh->myport), 2, keys, values, false);
-       free(fingerprint);
+       uint8_t msg[1024];
+       size_t msg_size;
+
+       if(response) {
+               char *fingerprint = meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self);
+               static const char *keys[] = {MESHLINK_MDNS_NAME_KEY, MESHLINK_MDNS_FINGERPRINT_KEY};
+               const char *values[] = {mesh->name, fingerprint};
+               msg_size = prepare_response(msg, sizeof msg, fingerprint, mesh->appname, "tcp", atoi(mesh->myport), 2, keys, values);
+               free(fingerprint);
+               addr->last_response_sent = mesh->loop.now.tv_sec;
+       } else {
+               msg_size = prepare_request(msg, sizeof msg, mesh->appname, "tcp");
+       }
 
        switch(addr->address.sa.sa_family) {
        case AF_INET:
-               fd = mesh->discovery.sockets[0].fd;
-#ifdef IP_MULTICAST_IF
-               {
-                       struct ip_mreqn mreq = {
-                               .imr_address = addr->address.in.sin_addr,
-                               .imr_ifindex = addr->index,
-                       };
-
-                       if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq)) != 0) {
-                               logger(mesh, MESHLINK_ERROR, "Could not set outgoing multicast interface on IPv4 socket");
-                               return;
-                       }
-               }
-
-#endif
-
-               send_mdns_packet_ipv4(mesh, fd, addr->index, &addr->address, &mdns_address_ipv4, data, size);
+               send_mdns_packet_ipv4(mesh, mesh->discovery.sockets[0].fd, addr->index, &addr->address, &mdns_address_ipv4, msg, msg_size);
                break;
 
        case AF_INET6:
-               fd = mesh->discovery.sockets[1].fd;
-#ifdef IPV6_MULTICAST_IF
-
-               if(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &addr->index, sizeof(addr->index)) != 0) {
-                       logger(mesh, MESHLINK_ERROR, "Could not set outgoing multicast interface on IPv6 socket");
-                       return;
-               }
-
-#endif
-
-               send_mdns_packet_ipv6(mesh, fd, addr->index, &addr->address, &mdns_address_ipv6, data, size);
+               send_mdns_packet_ipv6(mesh, mesh->discovery.sockets[1].fd, addr->index, &addr->address, &mdns_address_ipv6, msg, msg_size);
                break;
 
        default:
@@ -233,7 +263,32 @@ static void mdns_io_handler(event_loop_t *loop, void *data, int flags) {
        sockaddr_t sa;
        socklen_t sl = sizeof(sa);
 
+#ifdef IP_PKTINFO
+       struct iovec iov  = {
+               .iov_base = buf,
+               .iov_len = sizeof(buf),
+       };
+
+       union {
+               char buf[1024];
+               struct cmsghdr align;
+       } u;
+
+       struct msghdr msg = {
+               .msg_name = &sa,
+               .msg_namelen = sl,
+               .msg_iov = &iov,
+               .msg_iovlen = 1,
+               .msg_control = u.buf,
+               .msg_controllen = sizeof(u.buf),
+       };
+
+       ssize_t len = recvmsg(io->fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL);
+
+       sl = msg.msg_namelen;
+#else
        ssize_t len = recvfrom(io->fd, buf, sizeof(buf), MSG_DONTWAIT, &sa.sa, &sl);
+#endif
 
        if(len == -1) {
                if(!sockwouldblock(errno)) {
@@ -248,9 +303,8 @@ static void mdns_io_handler(event_loop_t *loop, void *data, int flags) {
        uint16_t port = 0;
        const char *keys[2] = {MESHLINK_MDNS_NAME_KEY, MESHLINK_MDNS_FINGERPRINT_KEY};
        char *values[2] = {NULL, NULL};
-       bool response;
 
-       if(parse_packet(buf, len, &name, mesh->appname, "tcp", &port, 2, keys, values, &response)) {
+       if(parse_response(buf, len, &name, mesh->appname, "tcp", &port, 2, keys, values)) {
                node_t *n = (node_t *)meshlink_get_node(mesh, values[0]);
 
                if(n) {
@@ -258,15 +312,6 @@ static void mdns_io_handler(event_loop_t *loop, void *data, int flags) {
                                logger(mesh, MESHLINK_INFO, "Node %s discovered on the local network.\n", n->name);
                        }
 
-                       if(!response && n != mesh->self) {
-                               // Send a unicast response back
-                               char *fingerprint = meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self);
-                               const char *response_values[] = {mesh->name, fingerprint};
-                               size_t size = prepare_packet(buf, sizeof(buf), fingerprint, mesh->appname, "tcp", atoi(mesh->myport), 2, keys, response_values, true);
-                               sendto(io->fd, buf, size, MSG_DONTWAIT | MSG_NOSIGNAL, &sa.sa, sl);
-                               free(fingerprint);
-                       }
-
                        switch(sa.sa.sa_family) {
                        case AF_INET:
                                sa.in.sin_port = htons(port);
@@ -306,6 +351,50 @@ static void mdns_io_handler(event_loop_t *loop, void *data, int flags) {
                                }
                        }
                }
+       } else if(parse_request(buf, len, mesh->appname, "tcp")) {
+               char *fingerprint = meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self);
+               const char *response_values[] = {mesh->name, fingerprint};
+               size_t size = prepare_response(buf, sizeof(buf), fingerprint, mesh->appname, "tcp", atoi(mesh->myport), 2, keys, response_values);
+
+               int index = -1;
+               int family = AF_UNSPEC;
+
+               for(struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; cmptr = CMSG_NXTHDR(&msg, cmptr)) {
+#ifdef IP_PKTINFO
+
+                       if(cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) {
+                               struct in_pktinfo *pktinfo = (struct in_pktinfo *) CMSG_DATA(cmptr);
+                               index = pktinfo->ipi_ifindex;
+                               family = AF_INET;
+                               break;
+                       } else
+#endif
+#ifdef IPV6_PKTINFO
+                               if(cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == IPV6_PKTINFO) {
+                                       struct in6_pktinfo *pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmptr);
+                                       index = pktinfo->ipi6_ifindex;
+                                       family = AF_INET6;
+                                       break;
+                               }
+
+#endif
+               }
+
+               if(index) {
+                       // Send a multicast response back using all addresses matching the interface
+                       for(int i = 0; i < mesh->discovery.address_count; i++) {
+                               discovery_address_t *p = &mesh->discovery.addresses[i];
+
+                               if(p->index == index && p->address.sa.sa_family == family) {
+                                       send_mdns_packet(mesh, p, true);
+                               }
+                       }
+               } else {
+                       // Send a unicast response back
+                       sendto(io->fd, buf, size, MSG_DONTWAIT | MSG_NOSIGNAL, &sa.sa, sl);
+               }
+
+               free(fingerprint);
        }
 
        free(name);
@@ -331,6 +420,7 @@ static void iface_up(meshlink_handle_t *mesh, int index) {
                .imr_multiaddr = mdns_address_ipv4.in.sin_addr,
                .imr_ifindex = index,
        };
+
        setsockopt(mesh->discovery.sockets[0].fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq4, sizeof(mreq4));
        setsockopt(mesh->discovery.sockets[0].fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq4, sizeof(mreq4));
 
@@ -338,20 +428,21 @@ static void iface_up(meshlink_handle_t *mesh, int index) {
                .ipv6mr_multiaddr = mdns_address_ipv6.in6.sin6_addr,
                .ipv6mr_interface = index,
        };
+
        setsockopt(mesh->discovery.sockets[1].fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6));
        setsockopt(mesh->discovery.sockets[1].fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6));
 
        // Send an announcement for all addresses associated with this interface
        for(int i = 0; i < mesh->discovery.address_count; i++) {
                if(mesh->discovery.addresses[i].index == index) {
-                       send_mdns_packet(mesh, &mesh->discovery.addresses[i]);
+                       send_mdns_packet(mesh, &mesh->discovery.addresses[i], false);
                }
        }
 
        handle_network_change(mesh, true);
 }
 
-static void iface_down(meshlink_handle_t *const mesh, int index) {
+static void iface_down(meshlink_handle_t *mesh, int index) {
        int *p = bsearch(&index, mesh->discovery.ifaces, mesh->discovery.iface_count, sizeof(*p), iface_compare);
 
        if(!p) {
@@ -390,7 +481,7 @@ static void addr_add(meshlink_handle_t *mesh, const discovery_address_t *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]);
+               send_mdns_packet(mesh, &mesh->discovery.addresses[mesh->discovery.address_count - 1], false);
        }
 
        qsort(mesh->discovery.addresses, mesh->discovery.address_count, sizeof(*p), address_compare);
@@ -406,6 +497,110 @@ static void addr_del(meshlink_handle_t *mesh, const discovery_address_t *addr) {
        memmove(p, p + 1, (mesh->discovery.addresses + --mesh->discovery.address_count - p) * sizeof(*p));
 }
 
+void scan_ifaddrs(meshlink_handle_t *mesh) {
+#ifdef HAVE_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) {
+                       addr_add(mesh, &addr);
+               }
+       }
+
+       freeifaddrs(ifa);
+#else
+       (void)mesh;
+#endif
+}
+
 #if defined(__linux)
 static void netlink_getlink(int fd) {
        static const struct {
@@ -519,7 +714,6 @@ static void netlink_parse(meshlink_handle_t *mesh, const void *data, size_t len)
 static void netlink_io_handler(event_loop_t *loop, void *data, int flags) {
        (void)flags;
        (void)data;
-       static time_t prev_update;
        meshlink_handle_t *mesh = loop->data;
 
        struct {
@@ -552,13 +746,75 @@ static void netlink_io_handler(event_loop_t *loop, void *data, int flags) {
                } else {
                        netlink_parse(mesh, &msg, result);
 
-                       if(loop->now.tv_sec > prev_update + 5) {
-                               prev_update = loop->now.tv_sec;
-                               handle_network_change(mesh, 1);
+                       if(loop->now.tv_sec > mesh->discovery.last_update + 5) {
+                               mesh->discovery.last_update = loop->now.tv_sec;
+                               handle_network_change(mesh, true);
                        }
                }
        }
 }
+#elif defined(__APPLE__)
+static void reachability_change_callback(SCNetworkReachabilityRef reachability, SCNetworkReachabilityFlags flags, void *info) {
+       (void)reachability;
+       (void)flags;
+
+       meshlink_handle_t *mesh = info;
+
+       pthread_mutex_lock(&mesh->mutex);
+
+       scan_ifaddrs(mesh);
+
+       if(mesh->loop.now.tv_sec > mesh->discovery.last_update + 5) {
+               logger(mesh, MESHLINK_INFO, "Network change detected");
+               mesh->discovery.last_update = mesh->loop.now.tv_sec;
+               handle_network_change(mesh, true);
+       }
+
+       pthread_mutex_unlock(&mesh->mutex);
+}
+
+static void *network_change_handler(void *arg) {
+       meshlink_handle_t *mesh = arg;
+
+       mesh->discovery.runloop = CFRunLoopGetCurrent();
+
+       SCNetworkReachabilityRef reach_v4 = SCNetworkReachabilityCreateWithName(NULL, "93.184.216.34");
+       SCNetworkReachabilityRef reach_v6 = SCNetworkReachabilityCreateWithName(NULL, "2606:2800:220:1:248:1893:25c8:1946");
+
+       SCNetworkReachabilityContext context;
+       memset(&context, 0, sizeof(context));
+       context.info = mesh;
+
+       if(reach_v4) {
+               SCNetworkReachabilitySetCallback(reach_v4, reachability_change_callback, &context);
+               SCNetworkReachabilityScheduleWithRunLoop(reach_v4, mesh->discovery.runloop, kCFRunLoopDefaultMode);
+       } else {
+               logger(mesh, MESHLINK_ERROR, "Could not create IPv4 network reachability watcher");
+       }
+
+       if(reach_v6) {
+               SCNetworkReachabilitySetCallback(reach_v6, reachability_change_callback, &context);
+               SCNetworkReachabilityScheduleWithRunLoop(reach_v6, mesh->discovery.runloop, kCFRunLoopDefaultMode);
+       } else {
+               logger(mesh, MESHLINK_ERROR, "Could not create IPv6 network reachability watcher");
+       }
+
+       CFRunLoopRun();
+
+       mesh->discovery.runloop = NULL;
+
+       if(reach_v4) {
+               SCNetworkReachabilityUnscheduleFromRunLoop(reach_v4, mesh->discovery.runloop, kCFRunLoopDefaultMode);
+               CFRelease(reach_v4);
+       }
+
+       if(reach_v6) {
+               SCNetworkReachabilityUnscheduleFromRunLoop(reach_v6, mesh->discovery.runloop, kCFRunLoopDefaultMode);
+               CFRelease(reach_v6);
+       }
+
+       return NULL;
+}
 #elif defined(RTM_NEWADDR)
 static void pfroute_parse_iface(meshlink_handle_t *mesh, const struct rt_msghdr *rtm) {
        const struct if_msghdr *ifm = (const struct if_msghdr *)rtm;
@@ -672,44 +928,56 @@ bool discovery_start(meshlink_handle_t *mesh) {
 
        if(fd == -1) {
                logger(mesh, MESHLINK_ERROR, "Error creating IPv4 socket: %s", strerror(errno));
-       }
+       } else {
 
-       sockaddr_t sa4 = {
-               .in.sin_family = AF_INET,
-               .in.sin_port = ntohs(5353),
-       };
-       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
-       setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
-       setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one8, sizeof(one8));
-       setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl8, sizeof(ttl8));
+               sockaddr_t sa4 = {
+                       .in.sin_family = AF_INET,
+                       .in.sin_port = ntohs(5353),
+               };
+               setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+#ifdef SO_REUSEPORT
+               setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
+#endif
+#ifdef IP_PKTINFO
+               setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+#endif
+               setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one8, sizeof(one8));
+               setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl8, sizeof(ttl8));
 
-       if(bind(fd, &sa4.sa, SALEN(sa4.sa)) == -1) {
-               logger(mesh, MESHLINK_ERROR, "Error binding to IPv4 multicast socket: %s", strerror(errno));
-       } else {
-               io_add(&mesh->loop, &mesh->discovery.sockets[0], mdns_io_handler, &mesh->discovery.sockets[0], fd, IO_READ);
+               if(bind(fd, &sa4.sa, SALEN(sa4.sa)) == -1) {
+                       logger(mesh, MESHLINK_ERROR, "Error binding to IPv4 multicast socket: %s", strerror(errno));
+               } else {
+                       io_add(&mesh->loop, &mesh->discovery.sockets[0], mdns_io_handler, &mesh->discovery.sockets[0], fd, IO_READ);
+               }
        }
 
-       sockaddr_t sa6 = {
-               .in6.sin6_family = AF_INET6,
-               .in6.sin6_port = ntohs(5353),
-       };
        fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
 
        if(fd == -1) {
                logger(mesh, MESHLINK_ERROR, "Error creating IPv6 socket: %s", strerror(errno));
-       }
+       } else {
+               sockaddr_t sa6 = {
+                       .in6.sin6_family = AF_INET6,
+                       .in6.sin6_port = ntohs(5353),
+               };
 
-       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
-       setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
-       setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
-       setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one));
-       setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl));
-       setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
+               setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+#ifdef SO_REUSEPORT
+               setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
+#endif
+#ifdef IPV6_RECVPKTINFO
+               setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
+#endif
+               setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
+               setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one));
+               setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl));
+               setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
 
-       if(bind(fd, &sa6.sa, SALEN(sa6.sa)) == -1) {
-               logger(mesh, MESHLINK_ERROR, "Error binding to IPv4 multicast socket: %s", strerror(errno));
-       } else {
-               io_add(&mesh->loop, &mesh->discovery.sockets[1], mdns_io_handler, &mesh->discovery.sockets[1], fd, IO_READ);
+               if(bind(fd, &sa6.sa, SALEN(sa6.sa)) == -1) {
+                       logger(mesh, MESHLINK_ERROR, "Error binding to IPv6 multicast socket: %s", strerror(errno));
+               } else {
+                       io_add(&mesh->loop, &mesh->discovery.sockets[1], mdns_io_handler, &mesh->discovery.sockets[1], fd, IO_READ);
+               }
        }
 
 #if defined(__linux)
@@ -726,11 +994,17 @@ bool discovery_start(meshlink_handle_t *mesh) {
                        netlink_getlink(sock);
                } else {
                        logger(mesh, MESHLINK_WARNING, "Could not bind AF_NETLINK socket: %s", strerror(errno));
+                       scan_ifaddrs(mesh);
                }
        } else {
                logger(mesh, MESHLINK_WARNING, "Could not open AF_NETLINK socket: %s", strerror(errno));
+               scan_ifaddrs(mesh);
        }
 
+#elif defined(__APPLE__)
+       pthread_create(&mesh->discovery.thread, NULL, network_change_handler, mesh);
+       // TODO: Do we need to wait for the thread to start succesfully?
+       scan_ifaddrs(mesh);
 #elif defined(RTM_NEWADDR)
        int sock = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
 
@@ -740,51 +1014,7 @@ bool discovery_start(meshlink_handle_t *mesh) {
                logger(mesh, MESHLINK_WARNING, "Could not open PF_ROUTE socket: %s", strerror(errno));
        }
 
-       struct ifaddrs *ifa = NULL;
-
-       if(getifaddrs(&ifa) == -1) {
-               logger(mesh, MESHLINK_ERROR, "Could not get list of interface addresses: %s", strerror(errno));
-               return true;
-       }
-
-       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);
-               }
-
-               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) {
-                       addr_add(mesh, &addr);
-               }
-       }
-
-       freeifaddrs(ifa);
-
+       scan_ifaddrs(mesh);
 #endif
 
        return true;
@@ -802,6 +1032,15 @@ void discovery_stop(meshlink_handle_t *mesh) {
        mesh->discovery.iface_count = 0;
        mesh->discovery.address_count = 0;
 
+#if defined(__APPLE__)
+
+       if(mesh->discovery.runloop) {
+               CFRunLoopStop(mesh->discovery.runloop);
+               pthread_join(mesh->discovery.thread, NULL);
+       }
+
+#endif
+
        if(mesh->discovery.pfroute_io.cb) {
                close(mesh->discovery.pfroute_io.fd);
                io_del(&mesh->loop, &mesh->discovery.pfroute_io);
@@ -818,7 +1057,7 @@ void discovery_stop(meshlink_handle_t *mesh) {
 void discovery_refresh(meshlink_handle_t *mesh) {
        for(int i = 0; i < mesh->discovery.address_count; i++) {
                if(mesh->discovery.addresses[i].up) {
-                       send_mdns_packet(mesh, &mesh->discovery.addresses[i]);
+                       send_mdns_packet(mesh, &mesh->discovery.addresses[i], false);
                }
        }
 }