* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
+#define _GNU_SOURCE
+
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
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");
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_events.target++;
+ break;
+ }
+ }
+ }
+
usteer_sta_info_update_timeout(si, msg.timeout);
}
list_del(&node->list);
list_del(&node->host_list);
usteer_sta_node_cleanup(&node->node);
+ usteer_measurement_report_node_cleanup(&node->node);
free(node);
if (!list_empty(&host->nodes))
return;
avl_delete(&remote_hosts, &host->avl);
+ free(host->addr);
free(host);
}
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);
node = calloc_a(sizeof(*node), &buf, addr_len + 1 + strlen(name) + 1);
node->node.type = NODE_TYPE_REMOTE;
+ node->node.created = current_time;
sprintf(buf, "%s#%s", host->addr, name);
node->node.avl.key = buf;
node->name = buf + addr_len + 1;
node->host = host;
INIT_LIST_HEAD(&node->node.sta_info);
+ INIT_LIST_HEAD(&node->node.measurements);
list_add_tail(&node->list, &remote_nodes);
list_add_tail(&node->host_list, &host->nodes);
node = interface_get_node(host, msg.name);
node->check = 0;
node->node.freq = msg.freq;
+ node->node.channel = msg.channel;
+ node->node.op_class = msg.op_class;
node->node.n_assoc = msg.n_assoc;
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);
}
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;
int rem;
+ if (config.local_mode)
+ return;
+
if (blob_pad_len(data) != len) {
MSG(DEBUG, "Invalid message length (header: %d, real: %d)\n", blob_pad_len(data), len);
return;
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);
blob_for_each_attr(cur, msg.nodes, rem)
interface_add_node(host, cur);
}
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)
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_send_msg(struct interface *iface, struct blob_attr *data)
+
+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_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;
struct cmsghdr *cmsg;
a.sin_family = AF_INET;
- a.sin_port = htons(16720);
+ a.sin_port = htons(APMGR_PORT);
a.sin_addr.s_addr = ~0;
memset(cmsg_data, 0, sizeof(cmsg_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);
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);
}
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_int32(&buf, APMSG_NODE_OP_CLASS, node->op_class);
+ blob_put_int32(&buf, APMSG_NODE_CHANNEL, node->channel);
+ 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, "",
usteer_check_timeout(void)
{
struct usteer_remote_node *node, *tmp;
- int timeout = config.remote_node_timeout / config.remote_update_interval;
+ int timeout = config.remote_node_timeout;
list_for_each_entry_safe(node, tmp, &remote_nodes, list) {
- if (node->check++ > timeout)
+ if (config.local_mode || node->check++ > timeout)
remote_node_free(node);
}
}
blob_buf_init(&buf, 0);
blob_put_int32(&buf, APMSG_ID, local_id);
blob_put_int32(&buf, APMSG_SEQ, ++msg_seq);
+ if (host_info_blob)
+ blob_put(&buf, APMSG_HOST_INFO,
+ blob_data(host_info_blob),
+ blob_len(host_info_blob));
return blob_nest_start(&buf, APMSG_NODES);
}
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 (!config.local_mode &&
+ (!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();
}
return -1;
}
- if (fread(&local_id, sizeof(local_id), 1, f) < 1)
+ if (fread(&local_id, sizeof(local_id), 1, f) < 1) {
+ fclose(f);
return -1;
+ }
fclose(f);
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)
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);
}