ubus: prioritize neighbor reports on bss transition
[project/usteer.git] / remote.c
index eeb063214a32bd97daea731036ec5303325007fe..4887cc023ddda9507927d82faa31b31914222ffd 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -17,6 +17,8 @@
  *   Copyright (C) 2020 John Crispin <john@phrozen.org> 
  */
 
+#define _GNU_SOURCE
+
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -153,9 +155,11 @@ static void
 interface_add_station(struct usteer_remote_node *node, struct blob_attr *data)
 {
        struct sta *sta;
-       struct sta_info *si;
+       struct sta_info *si, *local_si;
        struct apmsg_sta msg;
+       struct usteer_node *local_node;
        bool create;
+       bool connect_change;
 
        if (!parse_apmsg_sta(&msg, data)) {
                MSG(DEBUG, "Cannot parse station in message\n");
@@ -175,9 +179,26 @@ interface_add_station(struct usteer_remote_node *node, struct blob_attr *data)
        if (!si)
                return;
 
+       connect_change = si->connected != msg.connected;
        si->connected = msg.connected;
        si->signal = msg.signal;
        si->seen = current_time - msg.seen;
+       si->last_connected = current_time - msg.last_connected;
+
+       /* Check if client roamed to this foreign node */
+       if ((connect_change || create) && si->connected == STA_CONNECTED) {
+               for_each_local_node(local_node) {
+                       local_si = usteer_sta_info_get(sta, local_node, NULL);
+                       if (!local_si)
+                               continue;
+
+                       if (current_time - local_si->last_connected < config.roam_process_timeout) {
+                               node->node.roam_destination++;
+                               break;
+                       }
+               }
+       }
+
        usteer_sta_info_update_timeout(si, msg.timeout);
 }
 
@@ -195,6 +216,7 @@ remote_node_free(struct usteer_remote_node *node)
                return;
 
        avl_delete(&remote_hosts, &host->avl);
+       free(host->addr);
        free(host);
 }
 
@@ -213,7 +235,7 @@ interface_get_host(const char *addr, unsigned long id)
        avl_insert(&remote_hosts, &host->avl);
 
 out:
-       if (host->addr && strcmp(host->addr, addr) != 0)
+       if (host->addr && !strcmp(host->addr, addr))
                return host;
 
        free(host->addr);
@@ -268,6 +290,9 @@ interface_add_node(struct usteer_remote_host *host, struct blob_attr *data)
        node->node.max_assoc = msg.max_assoc;
        node->node.noise = msg.noise;
        node->node.load = msg.load;
+
+       memcpy(node->node.bssid, msg.bssid, sizeof(node->node.bssid));
+
        snprintf(node->node.ssid, sizeof(node->node.ssid), "%s", msg.ssid);
        usteer_node_set_blob(&node->node.rrm_nr, msg.rrm_nr);
        usteer_node_set_blob(&node->node.node_info, msg.node_info);
@@ -277,10 +302,9 @@ interface_add_node(struct usteer_remote_host *host, struct blob_attr *data)
 }
 
 static void
-interface_recv_msg(struct interface *iface, struct in_addr *addr, void *buf, int len)
+interface_recv_msg(struct interface *iface, char *addr_str, void *buf, int len)
 {
        struct usteer_remote_host *host;
-       char addr_str[INET_ADDRSTRLEN];
        struct blob_attr *data = buf;
        struct apmsg msg;
        struct blob_attr *cur;
@@ -302,8 +326,6 @@ interface_recv_msg(struct interface *iface, struct in_addr *addr, void *buf, int
        MSG(NETWORK, "Received message on %s (id=%08x->%08x seq=%d len=%d)\n",
                interface_name(iface), msg.id, local_id, msg.seq, len);
 
-       inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str));
-
        host = interface_get_host(addr_str, msg.id);
        usteer_node_set_blob(&host->host_info, msg.host_info);
 
@@ -325,11 +347,12 @@ interface_find_by_ifindex(int index)
 }
 
 static void
-interface_recv(struct uloop_fd *u, unsigned int events)
+interface_recv_v4(struct uloop_fd *u, unsigned int events)
 {
        static char buf[APMGR_BUFLEN];
        static char cmsg_buf[( CMSG_SPACE(sizeof(struct in_pktinfo)) + sizeof(int)) + 1];
        static struct sockaddr_in sin;
+       char addr_str[INET_ADDRSTRLEN];
        static struct iovec iov = {
                .iov_base = buf,
                .iov_len = sizeof(buf)
@@ -381,11 +404,80 @@ interface_recv(struct uloop_fd *u, unsigned int events)
                        continue;
                }
 
-               interface_recv_msg(iface, &sin.sin_addr, buf, len);
+               inet_ntop(AF_INET, &sin.sin_addr, addr_str, sizeof(addr_str));
+
+               interface_recv_msg(iface, addr_str, buf, len);
+       } while (1);
+}
+
+
+static void interface_recv_v6(struct uloop_fd *u, unsigned int events){
+       static char buf[APMGR_BUFLEN];
+       static char cmsg_buf[( CMSG_SPACE(sizeof(struct in6_pktinfo)) + sizeof(int)) + 1];
+       static struct sockaddr_in6 sin;
+       static struct iovec iov = {
+               .iov_base = buf,
+               .iov_len = sizeof(buf)
+       };
+       static struct msghdr msg = {
+               .msg_name = &sin,
+               .msg_namelen = sizeof(sin),
+               .msg_iov = &iov,
+               .msg_iovlen = 1,
+               .msg_control = cmsg_buf,
+               .msg_controllen = sizeof(cmsg_buf),
+       };
+       struct cmsghdr *cmsg;
+       char addr_str[INET6_ADDRSTRLEN];
+       int len;
+
+       do {
+               struct in6_pktinfo *pkti = NULL;
+               struct interface *iface;
+
+               len = recvmsg(u->fd, &msg, 0);
+               if (len < 0) {
+                       switch (errno) {
+                       case EAGAIN:
+                               return;
+                       case EINTR:
+                               continue;
+                       default:
+                               perror("recvmsg");
+                               uloop_fd_delete(u);
+                               return;
+                       }
+               }
+
+               for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+                       if (cmsg->cmsg_type != IPV6_PKTINFO)
+                               continue;
+
+                       pkti = (struct in6_pktinfo *) CMSG_DATA(cmsg);
+               }
+
+               if (!pkti) {
+                       MSG(DEBUG, "Received packet without ifindex\n");
+                       continue;
+               }
+
+               iface = interface_find_by_ifindex(pkti->ipi6_ifindex);
+               if (!iface) {
+                       MSG(DEBUG, "Received packet from unconfigured interface %d\n", pkti->ipi6_ifindex);
+                       continue;
+               }
+
+               inet_ntop(AF_INET6, &sin.sin6_addr, addr_str, sizeof(addr_str));
+               if (sin.sin6_addr.s6_addr[0] == 0) {
+                       /* IPv4 mapped address. Ignore. */
+                       continue;
+               }
+
+               interface_recv_msg(iface, addr_str, buf, len);
        } while (1);
 }
 
-static void interface_send_msg(struct interface *iface, struct blob_attr *data)
+static void interface_send_msg_v4(struct interface *iface, struct blob_attr *data)
 {
        static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in_pktinfo)) / sizeof(size_t)) + 1];
        static struct sockaddr_in a;
@@ -421,9 +513,32 @@ static void interface_send_msg(struct interface *iface, struct blob_attr *data)
                perror("sendmsg");
 }
 
+
+static void interface_send_msg_v6(struct interface *iface, struct blob_attr *data) {
+       static struct sockaddr_in6 groupSock = {};
+
+       groupSock.sin6_family = AF_INET6;
+       inet_pton(AF_INET6, APMGR_V6_MCAST_GROUP, &groupSock.sin6_addr);
+       groupSock.sin6_port = htons(APMGR_PORT);
+
+       setsockopt(remote_fd.fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface->ifindex, sizeof(iface->ifindex));
+
+       if (sendto(remote_fd.fd, data, blob_pad_len(data), 0, (const struct sockaddr *)&groupSock, sizeof(groupSock)) < 0)
+               perror("sendmsg");
+}
+
+static void interface_send_msg(struct interface *iface, struct blob_attr *data){
+       if (config.ipv6) {
+               interface_send_msg_v6(iface, data);
+       } else {
+               interface_send_msg_v4(iface, data);
+       }
+}
+
 static void usteer_send_sta_info(struct sta_info *sta)
 {
        int seen = current_time - sta->seen;
+       int last_connected = !!sta->connected ? 0 : current_time - sta->last_connected;
        void *c;
 
        c = blob_nest_start(&buf, 0);
@@ -431,6 +546,7 @@ static void usteer_send_sta_info(struct sta_info *sta)
        blob_put_int8(&buf, APMSG_STA_CONNECTED, !!sta->connected);
        blob_put_int32(&buf, APMSG_STA_SIGNAL, sta->signal);
        blob_put_int32(&buf, APMSG_STA_SEEN, seen);
+       blob_put_int32(&buf, APMSG_STA_LAST_CONNECTED, last_connected);
        blob_put_int32(&buf, APMSG_STA_TIMEOUT, config.local_sta_timeout - seen);
        blob_nest_end(&buf, c);
 }
@@ -448,6 +564,7 @@ static void usteer_send_node(struct usteer_node *node, struct sta_info *sta)
        blob_put_int32(&buf, APMSG_NODE_LOAD, node->load);
        blob_put_int32(&buf, APMSG_NODE_N_ASSOC, node->n_assoc);
        blob_put_int32(&buf, APMSG_NODE_MAX_ASSOC, node->max_assoc);
+       blob_put(&buf, APMSG_NODE_BSSID, node->bssid, sizeof(node->bssid));
        if (node->rrm_nr) {
                r = blob_nest_start(&buf, APMSG_NODE_RRM_NR);
                blobmsg_add_field(&buf, BLOBMSG_TYPE_ARRAY, "",
@@ -526,17 +643,16 @@ usteer_send_update_timer(struct uloop_timeout *t)
        struct usteer_node *node;
        void *c;
 
-       if (avl_is_empty(&local_nodes) && !host_info_blob)
-               return;
-
        usteer_update_time();
        uloop_timeout_set(t, config.remote_update_interval);
 
-       c = usteer_update_init();
-       for_each_local_node(node)
-               usteer_send_node(node, NULL);
+       if (!avl_is_empty(&local_nodes) || host_info_blob) {
+               c = usteer_update_init();
+               for_each_local_node(node)
+                       usteer_send_node(node, NULL);
 
-       usteer_update_send(c);
+               usteer_update_send(c);
+       }
        usteer_check_timeout();
 }
 
@@ -558,23 +674,16 @@ usteer_init_local_id(void)
        return 0;
 }
 
-static void
-usteer_reload_timer(struct uloop_timeout *t)
-{
+static int usteer_create_v4_socket() {
        int yes = 1;
        int fd;
 
-       if (remote_fd.registered) {
-               uloop_fd_delete(&remote_fd);
-               close(remote_fd.fd);
-       }
-
        fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK |
                   USOCK_NUMERIC | USOCK_IPV4ONLY,
                   "0.0.0.0", APMGR_PORT_STR);
        if (fd < 0) {
                perror("usock");
-               return;
+               return - 1;
        }
 
        if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0)
@@ -583,8 +692,61 @@ usteer_reload_timer(struct uloop_timeout *t)
        if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) < 0)
                perror("setsockopt(SO_BROADCAST)");
 
-       remote_fd.fd = fd;
-       remote_fd.cb = interface_recv;
+       return fd;
+}
+
+
+static int usteer_create_v6_socket() {
+       struct interface *iface;
+       struct ipv6_mreq group;
+       int yes = 1;
+       int fd;
+
+       fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK |
+                  USOCK_NUMERIC | USOCK_IPV6ONLY,
+                  "::", APMGR_PORT_STR);
+       if (fd < 0) {
+               perror("usock");
+               return fd;
+       }
+
+       if (!inet_pton(AF_INET6, APMGR_V6_MCAST_GROUP, &group.ipv6mr_multiaddr.s6_addr))
+               perror("inet_pton(AF_INET6)");
+
+       /* Membership has to be added for every interface we listen on. */
+       vlist_for_each_element(&interfaces, iface, node) {
+               group.ipv6mr_interface = iface->ifindex;
+               if(setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&group, sizeof group) < 0)
+                       perror("setsockopt(IPV6_ADD_MEMBERSHIP)");
+       }
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &yes, sizeof(yes)) < 0)
+               perror("setsockopt(IPV6_RECVPKTINFO)");
+
+       if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) < 0)
+               perror("setsockopt(SO_BROADCAST)");
+
+       return fd;
+}
+
+static void usteer_reload_timer(struct uloop_timeout *t) {
+       /* Remove uloop descriptor */
+       if (remote_fd.fd && remote_fd.registered) {
+               uloop_fd_delete(&remote_fd);
+               close(remote_fd.fd);
+       }
+
+       if (config.ipv6) {
+               remote_fd.fd = usteer_create_v6_socket();
+               remote_fd.cb = interface_recv_v6;
+       } else {
+               remote_fd.fd = usteer_create_v4_socket();
+               remote_fd.cb = interface_recv_v4;
+       }
+
+       if (remote_fd.fd < 0)
+               return;
+
        uloop_fd_add(&remote_fd, ULOOP_READ);
 }