Files
usteer/remote.c
Jan Braun e4ea2045eb usteer: add support for IPv6 remote exchange
This adds optional support for message exchange using IPv6 multicast
messaging. This has the ability for routers and switches between nodes
to route traffic between usteer nodes multicast-aware.

By default, IPv4 is used. IPv6 can be enabled by configuring the ipv6
option to 1.

Signed-off-by: Jan Braun <keksgesicht23@gmail.com>
[squash commits - adapt usock usage]
Signed-off-by: David Bauer <mail@david-bauer.net>
2021-09-21 14:58:41 +02:00

739 lines
17 KiB
C

/*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <libubox/vlist.h>
#include <libubox/avl-cmp.h>
#include <libubox/usock.h>
#include "usteer.h"
#include "remote.h"
#include "node.h"
static uint32_t local_id;
static struct uloop_fd remote_fd;
static struct uloop_timeout remote_timer;
static struct uloop_timeout reload_timer;
static struct blob_buf buf;
static uint32_t msg_seq;
struct interface {
struct vlist_node node;
int ifindex;
};
static void
interfaces_update_cb(struct vlist_tree *tree,
struct vlist_node *node_new,
struct vlist_node *node_old);
static int remote_host_cmp(const void *k1, const void *k2, void *ptr)
{
unsigned long v1 = (unsigned long) k1;
unsigned long v2 = (unsigned long) k2;
return v2 - v1;
}
static VLIST_TREE(interfaces, avl_strcmp, interfaces_update_cb, true, true);
LIST_HEAD(remote_nodes);
AVL_TREE(remote_hosts, remote_host_cmp, false, NULL);
static const char *
interface_name(struct interface *iface)
{
return iface->node.avl.key;
}
static void
interface_check(struct interface *iface)
{
iface->ifindex = if_nametoindex(interface_name(iface));
uloop_timeout_set(&reload_timer, 1);
}
static void
interface_init(struct interface *iface)
{
interface_check(iface);
}
static void
interface_free(struct interface *iface)
{
avl_delete(&interfaces.avl, &iface->node.avl);
free(iface);
}
static void
interfaces_update_cb(struct vlist_tree *tree,
struct vlist_node *node_new,
struct vlist_node *node_old)
{
struct interface *iface;
if (node_new && node_old) {
iface = container_of(node_new, struct interface, node);
free(iface);
iface = container_of(node_old, struct interface, node);
interface_check(iface);
} else if (node_old) {
iface = container_of(node_old, struct interface, node);
interface_free(iface);
} else {
iface = container_of(node_new, struct interface, node);
interface_init(iface);
}
}
void usteer_interface_add(const char *name)
{
struct interface *iface;
char *name_buf;
iface = calloc_a(sizeof(*iface), &name_buf, strlen(name) + 1);
strcpy(name_buf, name);
vlist_add(&interfaces, &iface->node, name_buf);
}
void config_set_interfaces(struct blob_attr *data)
{
struct blob_attr *cur;
int rem;
if (!data)
return;
if (!blobmsg_check_attr_list(data, BLOBMSG_TYPE_STRING))
return;
vlist_update(&interfaces);
blobmsg_for_each_attr(cur, data, rem) {
usteer_interface_add(blobmsg_data(cur));
}
vlist_flush(&interfaces);
}
void config_get_interfaces(struct blob_buf *buf)
{
struct interface *iface;
void *c;
c = blobmsg_open_array(buf, "interfaces");
vlist_for_each_element(&interfaces, iface, node) {
blobmsg_add_string(buf, NULL, interface_name(iface));
}
blobmsg_close_array(buf, c);
}
static void
interface_add_station(struct usteer_remote_node *node, struct blob_attr *data)
{
struct sta *sta;
struct sta_info *si;
struct apmsg_sta msg;
bool create;
if (!parse_apmsg_sta(&msg, data)) {
MSG(DEBUG, "Cannot parse station in message\n");
return;
}
if (msg.timeout <= 0) {
MSG(DEBUG, "Refuse to add an already expired station entry\n");
return;
}
sta = usteer_sta_get(msg.addr, true);
if (!sta)
return;
si = usteer_sta_info_get(sta, &node->node, &create);
if (!si)
return;
si->connected = msg.connected;
si->signal = msg.signal;
si->seen = current_time - msg.seen;
usteer_sta_info_update_timeout(si, msg.timeout);
}
static void
remote_node_free(struct usteer_remote_node *node)
{
struct usteer_remote_host *host = node->host;
list_del(&node->list);
list_del(&node->host_list);
usteer_sta_node_cleanup(&node->node);
free(node);
if (!list_empty(&host->nodes))
return;
avl_delete(&remote_hosts, &host->avl);
free(host);
}
static struct usteer_remote_host *
interface_get_host(const char *addr, unsigned long id)
{
struct usteer_remote_host *host;
host = avl_find_element(&remote_hosts, (void *)id, host, avl);
if (host)
goto out;
host = calloc(1, sizeof(*host));
host->avl.key = (void *)id;
INIT_LIST_HEAD(&host->nodes);
avl_insert(&remote_hosts, &host->avl);
out:
if (host->addr && strcmp(host->addr, addr) != 0)
return host;
free(host->addr);
host->addr = strdup(addr);
return host;
}
static struct usteer_remote_node *
interface_get_node(struct usteer_remote_host *host, const char *name)
{
struct usteer_remote_node *node;
int addr_len = strlen(host->addr);
char *buf;
list_for_each_entry(node, &host->nodes, host_list)
if (!strcmp(node->name, name))
return node;
node = calloc_a(sizeof(*node), &buf, addr_len + 1 + strlen(name) + 1);
node->node.type = NODE_TYPE_REMOTE;
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);
list_add_tail(&node->list, &remote_nodes);
list_add_tail(&node->host_list, &host->nodes);
return node;
}
static void
interface_add_node(struct usteer_remote_host *host, struct blob_attr *data)
{
struct usteer_remote_node *node;
struct apmsg_node msg;
struct blob_attr *cur;
int rem;
if (!parse_apmsg_node(&msg, data)) {
MSG(DEBUG, "Cannot parse node in message\n");
return;
}
node = interface_get_node(host, msg.name);
node->check = 0;
node->node.freq = msg.freq;
node->node.n_assoc = msg.n_assoc;
node->node.max_assoc = msg.max_assoc;
node->node.noise = msg.noise;
node->node.load = msg.load;
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);
blob_for_each_attr(cur, msg.stations, rem)
interface_add_station(node, cur);
}
static void
interface_recv_msg(struct interface *iface, char *addr_str, void *buf, int len)
{
struct usteer_remote_host *host;
struct blob_attr *data = buf;
struct apmsg msg;
struct blob_attr *cur;
int rem;
if (blob_pad_len(data) != len) {
MSG(DEBUG, "Invalid message length (header: %d, real: %d)\n", blob_pad_len(data), len);
return;
}
if (!parse_apmsg(&msg, data)) {
MSG(DEBUG, "Missing fields in message\n");
return;
}
if (msg.id == local_id)
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);
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 struct interface *
interface_find_by_ifindex(int index)
{
struct interface *iface;
vlist_for_each_element(&interfaces, iface, node) {
if (iface->ifindex == index)
return iface;
}
return NULL;
}
static void
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)
};
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;
int len;
do {
struct in_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 != IP_PKTINFO)
continue;
pkti = (struct in_pktinfo *) CMSG_DATA(cmsg);
}
if (!pkti) {
MSG(DEBUG, "Received packet without ifindex\n");
continue;
}
iface = interface_find_by_ifindex(pkti->ipi_ifindex);
if (!iface) {
MSG(DEBUG, "Received packet from unconfigured interface %d\n", pkti->ipi_ifindex);
continue;
}
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_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;
static struct iovec iov;
static struct msghdr m = {
.msg_name = (struct sockaddr *) &a,
.msg_namelen = sizeof(a),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsg_data,
.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)),
};
struct in_pktinfo *pkti;
struct cmsghdr *cmsg;
a.sin_family = AF_INET;
a.sin_port = htons(APMGR_PORT);
a.sin_addr.s_addr = ~0;
memset(cmsg_data, 0, sizeof(cmsg_data));
cmsg = CMSG_FIRSTHDR(&m);
cmsg->cmsg_len = m.msg_controllen;
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
pkti = (struct in_pktinfo *) CMSG_DATA(cmsg);
pkti->ipi_ifindex = iface->ifindex;
iov.iov_base = data;
iov.iov_len = blob_pad_len(data);
if (sendmsg(remote_fd.fd, &m, 0) < 0)
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;
void *c;
c = blob_nest_start(&buf, 0);
blob_put(&buf, APMSG_STA_ADDR, sta->sta->addr, 6);
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_TIMEOUT, config.local_sta_timeout - seen);
blob_nest_end(&buf, c);
}
static void usteer_send_node(struct usteer_node *node, struct sta_info *sta)
{
void *c, *s, *r;
c = blob_nest_start(&buf, 0);
blob_put_string(&buf, APMSG_NODE_NAME, usteer_node_name(node));
blob_put_string(&buf, APMSG_NODE_SSID, node->ssid);
blob_put_int32(&buf, APMSG_NODE_FREQ, node->freq);
blob_put_int32(&buf, APMSG_NODE_NOISE, node->noise);
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);
if (node->rrm_nr) {
r = blob_nest_start(&buf, APMSG_NODE_RRM_NR);
blobmsg_add_field(&buf, BLOBMSG_TYPE_ARRAY, "",
blobmsg_data(node->rrm_nr),
blobmsg_data_len(node->rrm_nr));
blob_nest_end(&buf, r);
}
if (node->node_info)
blob_put(&buf, APMSG_NODE_NODE_INFO,
blob_data(node->node_info),
blob_len(node->node_info));
s = blob_nest_start(&buf, APMSG_NODE_STATIONS);
if (sta) {
usteer_send_sta_info(sta);
} else {
list_for_each_entry(sta, &node->sta_info, node_list)
usteer_send_sta_info(sta);
}
blob_nest_end(&buf, s);
blob_nest_end(&buf, c);
}
static void
usteer_check_timeout(void)
{
struct usteer_remote_node *node, *tmp;
int timeout = config.remote_node_timeout;
list_for_each_entry_safe(node, tmp, &remote_nodes, list) {
if (node->check++ > timeout)
remote_node_free(node);
}
}
static void *
usteer_update_init(void)
{
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);
}
static void
usteer_update_send(void *c)
{
struct interface *iface;
blob_nest_end(&buf, c);
vlist_for_each_element(&interfaces, iface, node)
interface_send_msg(iface, buf.head);
}
void
usteer_send_sta_update(struct sta_info *si)
{
void *c = usteer_update_init();
usteer_send_node(si->node, si);
usteer_update_send(c);
}
static void
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);
usteer_update_send(c);
usteer_check_timeout();
}
static int
usteer_init_local_id(void)
{
FILE *f;
f = fopen("/dev/urandom", "r");
if (!f) {
perror("fopen(/dev/urandom)");
return -1;
}
if (fread(&local_id, sizeof(local_id), 1, f) < 1)
return -1;
fclose(f);
return 0;
}
static int usteer_create_v4_socket() {
int yes = 1;
int 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 - 1;
}
if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0)
perror("setsockopt(IP_PKTINFO)");
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) < 0)
perror("setsockopt(SO_BROADCAST)");
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);
}
int usteer_interface_init(void)
{
if (usteer_init_local_id())
return -1;
remote_timer.cb = usteer_send_update_timer;
remote_timer.cb(&remote_timer);
reload_timer.cb = usteer_reload_timer;
reload_timer.cb(&reload_timer);
return 0;
}