Merge pull request #5070 from blocktrron/pr-luci-wifi6
[project/luci.git] / libs / rpcd-mod-luci / src / luci.c
index f1ec0e5bec896b5c5c474f49894496c2b407d029..258d2ec86b58a5fd23a74a5425b2c716f2034754 100644 (file)
@@ -1150,10 +1150,48 @@ rpc_luci_get_wireless_devices(struct ubus_context *ctx,
 struct host_hint {
        struct avl_node avl;
        char *hostname;
-       struct in_addr ip;
-       struct in6_addr ip6;
+       struct avl_tree ipaddrs;
+       struct avl_tree ip6addrs;
+};
+
+/* used to ignore priority with avl_find_element */
+#define HOST_HINT_PRIO_IGNORE        -1
+
+/* higher (larger) priority addresses are listed first */
+#define HOST_HINT_PRIO_NL            10 /* neighbor table */
+#define HOST_HINT_PRIO_ETHER         50 /* /etc/ethers */
+#define HOST_HINT_PRIO_LEASEFILE    100 /* dhcp leasefile */
+#define HOST_HINT_PRIO_RRDNS        100 /* rrdns */
+#define HOST_HINT_PRIO_IFADDRS      200 /* getifaddrs() */
+#define HOST_HINT_PRIO_STATIC_LEASE 200 /* uci static leases */
+
+struct host_hint_addr {
+       struct avl_node avl;
+       int af;
+       int prio;
+       union {
+               struct in_addr in;
+               struct in6_addr in6;
+       } addr;
 };
 
+static int
+host_hint_addr_avl_cmp(const void *k1, const void *k2, void *ptr)
+{
+       struct host_hint_addr *a1 = (struct host_hint_addr *)k1;
+       struct host_hint_addr *a2 = (struct host_hint_addr *)k2;
+
+       if (a1->prio != a2->prio &&
+           a1->prio != HOST_HINT_PRIO_IGNORE &&
+           a2->prio != HOST_HINT_PRIO_IGNORE)
+               return a1->prio < a2->prio ? 1 : -1;
+
+       if (a1->af != a2->af)
+               return a1->af < a2->af ? -1 : 1;
+
+       return memcmp(&a1->addr, &a2->addr, sizeof(a1->addr));
+}
+
 static int
 nl_cb_done(struct nl_msg *msg, void *arg)
 {
@@ -1189,12 +1227,69 @@ rpc_luci_get_host_hint(struct reply_context *rctx, struct ether_addr *ea)
                        return NULL;
 
                hint->avl.key = strcpy(p, mac);
+               avl_init(&hint->ipaddrs, host_hint_addr_avl_cmp, false, NULL);
+               avl_init(&hint->ip6addrs, host_hint_addr_avl_cmp, false, NULL);
                avl_insert(&rctx->avl, &hint->avl);
        }
 
        return hint;
 }
 
+static void
+rpc_luci_add_host_hint_addr(struct host_hint *hint, int af, int prio, void *addr)
+{
+       struct host_hint_addr e, *a;
+       struct avl_tree *addrs = af == AF_INET ? &hint->ipaddrs : &hint->ip6addrs;
+
+       if (!addr)
+               return;
+
+       memset(&e, 0, sizeof(e));
+       e.af = af;
+       /* ignore prio when comparing against existing addresses */
+       e.prio = HOST_HINT_PRIO_IGNORE;
+
+       if (af == AF_INET)
+               memcpy(&e.addr.in, (struct in_addr *)addr, sizeof(e.addr.in));
+       else
+               memcpy(&e.addr.in6, (struct in6_addr *)addr, sizeof(e.addr.in6));
+
+       a = avl_find_element(addrs, &e, a, avl);
+
+       if (a) {
+               /* update prio of existing address if higher */
+               if (prio <= a->prio)
+                       return;
+
+               avl_delete(addrs, &a->avl);
+               a->prio = prio;
+               avl_insert(addrs, &a->avl);
+               return;
+       }
+
+       a = calloc(1, sizeof(*a));
+
+       if (!a)
+               return;
+
+       memcpy(a, &e, sizeof(*a));
+       a->prio = prio;
+       a->avl.key = a;
+       avl_insert(addrs, &a->avl);
+}
+
+static void
+rpc_luci_add_host_hint_ipaddr(struct host_hint *hint, int prio, struct in_addr *addr)
+{
+       return rpc_luci_add_host_hint_addr(hint, AF_INET, prio, (void *)addr);
+}
+
+static void
+rpc_luci_add_host_hint_ip6addr(struct host_hint *hint, int prio, struct in6_addr *addr)
+{
+       return rpc_luci_add_host_hint_addr(hint, AF_INET6, prio, (void *)addr);
+}
+
 static int nl_cb_dump_neigh(struct nl_msg *msg, void *arg)
 {
        struct reply_context *rctx = arg;
@@ -1228,9 +1323,9 @@ static int nl_cb_dump_neigh(struct nl_msg *msg, void *arg)
                return NL_SKIP;
 
        if (nd->ndm_family == AF_INET)
-               hint->ip = *(struct in_addr *)dst;
+               rpc_luci_add_host_hint_ipaddr(hint, HOST_HINT_PRIO_NL, (struct in_addr *)dst);
        else
-               hint->ip6 = *(struct in6_addr *)dst;
+               rpc_luci_add_host_hint_ip6addr(hint, HOST_HINT_PRIO_NL, (struct in6_addr *)dst);
 
        return NL_SKIP;
 }
@@ -1313,8 +1408,7 @@ rpc_luci_get_host_hints_ether(struct reply_context *rctx)
                        continue;
 
                if (inet_pton(AF_INET, p, &in) == 1) {
-                       if (hint->ip.s_addr == 0)
-                               hint->ip = in;
+                       rpc_luci_add_host_hint_ipaddr(hint, HOST_HINT_PRIO_ETHER, &in);
                }
                else if (*p && !hint->hostname) {
                        hint->hostname = strdup(p);
@@ -1330,13 +1424,13 @@ rpc_luci_get_host_hints_uci(struct reply_context *rctx)
        struct uci_ptr ptr = { .package = "dhcp" };
        struct uci_context *uci = NULL;
        struct uci_package *pkg = NULL;
-       struct in6_addr empty = {};
        struct lease_entry *lease;
        struct host_hint *hint;
        struct uci_element *e, *l;
        struct uci_section *s;
        struct in_addr in;
        char *p, *n;
+       int i;
 
        uci = uci_alloc_context();
 
@@ -1394,8 +1488,8 @@ rpc_luci_get_host_hints_uci(struct reply_context *rctx)
                                if (!hint)
                                        continue;
 
-                               if (hint->ip.s_addr == 0 && in.s_addr != 0)
-                                       hint->ip = in;
+                               if (in.s_addr != 0)
+                                       rpc_luci_add_host_hint_ipaddr(hint, HOST_HINT_PRIO_STATIC_LEASE, &in);
 
                                if (n && !hint->hostname)
                                        hint->hostname = strdup(n);
@@ -1408,8 +1502,8 @@ rpc_luci_get_host_hints_uci(struct reply_context *rctx)
                                if (!hint)
                                        continue;
 
-                               if (hint->ip.s_addr == 0 && in.s_addr != 0)
-                                       hint->ip = in;
+                               if (in.s_addr != 0)
+                                       rpc_luci_add_host_hint_ipaddr(hint, HOST_HINT_PRIO_STATIC_LEASE, &in);
 
                                if (n && !hint->hostname)
                                        hint->hostname = strdup(n);
@@ -1425,11 +1519,12 @@ rpc_luci_get_host_hints_uci(struct reply_context *rctx)
                if (!hint)
                        continue;
 
-               if (lease->af == AF_INET && lease->n_addr && hint->ip.s_addr == 0)
-                       hint->ip = lease->addr[0].in;
-               else if (lease->af == AF_INET6 && lease->n_addr &&
-                        !memcmp(&hint->ip6, &empty, sizeof(empty)))
-                       hint->ip6 = lease->addr[0].in6;
+               for (i = 0; i < lease->n_addr; i++) {
+                       if (lease->af == AF_INET)
+                               rpc_luci_add_host_hint_ipaddr(hint, HOST_HINT_PRIO_LEASEFILE, &lease->addr[i].in);
+                       else if (lease->af == AF_INET6)
+                               rpc_luci_add_host_hint_ip6addr(hint, HOST_HINT_PRIO_LEASEFILE, &lease->addr[i].in6);
+               }
 
                if (lease->hostname && !hint->hostname)
                        hint->hostname = strdup(lease->hostname);
@@ -1508,12 +1603,11 @@ rpc_luci_get_host_hints_ifaddrs(struct reply_context *rctx)
                        hint = rpc_luci_get_host_hint(rctx, &device->ea);
 
                        if (hint) {
-                               if (hint->ip.s_addr == 0 && device->in.s_addr != 0)
-                                       hint->ip = device->in;
+                               if (device->in.s_addr != 0)
+                                       rpc_luci_add_host_hint_ipaddr(hint, HOST_HINT_PRIO_IFADDRS, &device->in);
 
-                               if (memcmp(&hint->ip6, &empty_in6, sizeof(empty_in6)) == 0 &&
-                                   memcmp(&device->in6, &empty_in6, sizeof(empty_in6)) != 0)
-                                       hint->ip6 = device->in6;
+                               if (memcmp(&device->in6, &empty_in6, sizeof(empty_in6)) != 0)
+                                       rpc_luci_add_host_hint_ip6addr(hint, HOST_HINT_PRIO_IFADDRS, &device->in6);
                        }
                }
 
@@ -1542,7 +1636,8 @@ rpc_luci_get_host_hints_rrdns_cb(struct ubus_request *req, int type,
 
                        if (inet_pton(AF_INET6, blobmsg_name(cur), &in6) == 1) {
                                avl_for_each_element(&rctx->avl, hint, avl) {
-                                       if (!memcmp(&hint->ip6, &in6, sizeof(in6))) {
+                                       rpc_luci_add_host_hint_ip6addr(hint, HOST_HINT_PRIO_RRDNS, &in6);
+                                       if (!avl_is_empty(&hint->ip6addrs)) {
                                                if (hint->hostname)
                                                        free(hint->hostname);
 
@@ -1553,7 +1648,8 @@ rpc_luci_get_host_hints_rrdns_cb(struct ubus_request *req, int type,
                        }
                        else if (inet_pton(AF_INET, blobmsg_name(cur), &in) == 1) {
                                avl_for_each_element(&rctx->avl, hint, avl) {
-                                       if (!memcmp(&hint->ip, &in, sizeof(in))) {
+                                       rpc_luci_add_host_hint_ipaddr(hint, HOST_HINT_PRIO_RRDNS, &in);
+                                       if (!avl_is_empty(&hint->ipaddrs)) {
                                                if (hint->hostname)
                                                        free(hint->hostname);
 
@@ -1575,6 +1671,7 @@ rpc_luci_get_host_hints_rrdns(struct reply_context *rctx)
        char buf[INET6_ADDRSTRLEN];
        struct blob_buf req = {};
        struct host_hint *hint;
+       struct host_hint_addr *addr;
        int n = 0;
        void *a;
 
@@ -1583,15 +1680,19 @@ rpc_luci_get_host_hints_rrdns(struct reply_context *rctx)
        a = blobmsg_open_array(&req, "addrs");
 
        avl_for_each_element(&rctx->avl, hint, avl) {
-               if (hint->ip.s_addr != 0) {
-                       inet_ntop(AF_INET, &hint->ip, buf, sizeof(buf));
-                       blobmsg_add_string(&req, NULL, buf);
-                       n++;
+               avl_for_each_element(&hint->ipaddrs, addr, avl) {
+                       if (addr->addr.in.s_addr != 0) {
+                               inet_ntop(AF_INET, &addr->addr.in, buf, sizeof(buf));
+                               blobmsg_add_string(&req, NULL, buf);
+                               n++;
+                       }
                }
-               else if (memcmp(&hint->ip6, &empty_in6, sizeof(empty_in6))) {
-                       inet_ntop(AF_INET6, &hint->ip6, buf, sizeof(buf));
-                       blobmsg_add_string(&req, NULL, buf);
-                       n++;
+               avl_for_each_element(&hint->ip6addrs, addr, avl) {
+                       if (memcmp(&addr->addr.in6, &empty_in6, sizeof(empty_in6))) {
+                               inet_ntop(AF_INET6, &addr->addr.in6, buf, sizeof(buf));
+                               blobmsg_add_string(&req, NULL, buf);
+                               n++;
+                       }
                }
        }
 
@@ -1616,24 +1717,40 @@ static int
 rpc_luci_get_host_hints_finish(struct reply_context *rctx)
 {
        struct host_hint *hint, *nexthint;
+       struct host_hint_addr *addr, *nextaddr;
        char buf[INET6_ADDRSTRLEN];
        struct in6_addr in6 = {};
-       struct in_addr in = {};
-       void *o;
+       void *o, *a;
 
        avl_remove_all_elements(&rctx->avl, hint, avl, nexthint) {
                o = blobmsg_open_table(&rctx->blob, hint->avl.key);
 
-               if (memcmp(&hint->ip, &in, sizeof(in))) {
-                       inet_ntop(AF_INET, &hint->ip, buf, sizeof(buf));
-                       blobmsg_add_string(&rctx->blob, "ipv4", buf);
+               a = blobmsg_open_array(&rctx->blob, "ipaddrs");
+
+               avl_remove_all_elements(&hint->ipaddrs, addr, avl, nextaddr) {
+                       if (addr->addr.in.s_addr != 0) {
+                               inet_ntop(AF_INET, &addr->addr.in, buf, sizeof(buf));
+                               blobmsg_add_string(&rctx->blob, NULL, buf);
+                       }
+
+                       free(addr);
                }
 
-               if (memcmp(&hint->ip6, &in6, sizeof(in6))) {
-                       inet_ntop(AF_INET6, &hint->ip6, buf, sizeof(buf));
-                       blobmsg_add_string(&rctx->blob, "ipv6", buf);
+               blobmsg_close_array(&rctx->blob, a);
+
+               a = blobmsg_open_array(&rctx->blob, "ip6addrs");
+
+               avl_remove_all_elements(&hint->ip6addrs, addr, avl, nextaddr) {
+                       if (memcmp(&addr->addr.in6, &in6, sizeof(in6))) {
+                               inet_ntop(AF_INET6, &addr->addr.in6, buf, sizeof(buf));
+                               blobmsg_add_string(&rctx->blob, NULL, buf);
+                       }
+
+                       free(addr);
                }
 
+               blobmsg_close_array(&rctx->blob, a);
+
                if (hint->hostname)
                        blobmsg_add_string(&rctx->blob, "name", hint->hostname);