gluon/package/gluon-mesh-babel/src/respondd.c
2023-01-09 22:34:00 +01:00

525 lines
14 KiB
C

/* SPDX-FileCopyrightText: 2016, Matthias Schiffer <mschiffer@universe-factory.net> */
/* SPDX-License-Identifier: BSD-2-Clause */
#include <respondd.h>
#include <json-c/json.h>
#include <libgluonutil.h>
#include <uci.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <errno.h>
#include <libbabelhelper/babelhelper.h>
#include <libubox/blobmsg_json.h>
#include <libubus.h>
#define SOCKET_INPUT_BUFFER_SIZE 255
#define PROTOLEN 32
#define UBUS_TIMEOUT 30000
static struct babelhelper_ctx bhelper_ctx = {};
static bool get_linklocal_address(const char *ifname, char lladdr[INET6_ADDRSTRLEN]) {
struct ifaddrs *ifaddr, *ifa;
bool ret = false;
if (getifaddrs(&ifaddr) == -1) {
perror("getifaddrs");
return false;
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr)
continue;
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
if (strcmp(ifname, ifa->ifa_name) != 0)
continue;
const struct in6_addr *address = &((const struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
if (!IN6_IS_ADDR_LINKLOCAL(address))
continue;
if (!inet_ntop(AF_INET6, address, lladdr, INET6_ADDRSTRLEN)) {
perror("inet_ntop");
continue;
}
ret = true;
break;
}
freeifaddrs(ifaddr);
return ret;
}
static char* get_line_from_run(const char* command) {
FILE *fp;
char *line = NULL;
size_t len = 0;
fp = popen(command, "r");
if (fp != NULL) {
ssize_t r = getline(&line, &len, fp);
if (r >= 0) {
len = strlen(line);
if (len && line[len-1] == '\n')
line[len-1] = 0;
}
else {
free(line);
line = NULL;
}
pclose(fp);
}
return line;
}
static struct json_object * get_addresses(void) {
char *primarymac = gluonutil_get_sysconfig("primary_mac");
char *address = malloc(INET6_ADDRSTRLEN+1);
char node_prefix_str[INET6_ADDRSTRLEN+1];
struct in6_addr node_prefix = {};
struct json_object *retval = json_object_new_array();
if (!gluonutil_get_node_prefix6(&node_prefix)) {
fprintf(stderr, "get_addresses: could not obtain mesh-prefix from site.conf. Not adding addresses to json data\n");
goto free;
}
if (inet_ntop(AF_INET6, &(node_prefix.s6_addr), node_prefix_str, INET6_ADDRSTRLEN) == NULL) {
fprintf(stderr, "get_addresses: could not convert mesh-prefix from site.conf to string\n");
goto free;
}
char *prefix_addresspart = strndup(node_prefix_str, INET6_ADDRSTRLEN);
if (! babelhelper_generateip_str(address, primarymac, prefix_addresspart) ) {
fprintf(stderr, "IP-address could not be generated by babelhelper");
}
free(prefix_addresspart);
json_object_array_add(retval, json_object_new_string(address));
free:
free(address);
free(primarymac);
return retval;
}
static void mesh_add_if(const char *ifname, struct json_object *wireless,
struct json_object *tunnel, struct json_object *other) {
char str_ip[INET6_ADDRSTRLEN];
if (!get_linklocal_address(ifname, str_ip))
return;
struct json_object *address = json_object_new_string(str_ip);
/* In case of VLAN and bridge interfaces, we want the lower interface
* to determine the interface type (but not for the interface address) */
char lowername[IF_NAMESIZE];
gluonutil_get_interface_lower(lowername, ifname);
switch(gluonutil_get_interface_type(lowername)) {
case GLUONUTIL_INTERFACE_TYPE_WIRELESS:
json_object_array_add(wireless, address);
break;
case GLUONUTIL_INTERFACE_TYPE_TUNNEL:
json_object_array_add(tunnel, address);
break;
default:
json_object_array_add(other, address);
}
}
static bool handle_neighbour(char **data, void *obj) {
if (data[NEIGHBOUR]) {
struct json_object *neigh = json_object_new_object();
if (data[RXCOST])
json_object_object_add(neigh, "rxcost", json_object_new_int(atoi(data[RXCOST])));
if (data[TXCOST])
json_object_object_add(neigh, "txcost", json_object_new_int(atoi(data[TXCOST])));
if (data[COST])
json_object_object_add(neigh, "cost", json_object_new_int(atoi(data[COST])));
if (data[REACH])
json_object_object_add(neigh, "reachability", json_object_new_double(strtod(data[REACH], NULL)));
if (!data[IF])
return true;
struct json_object *nif;
if (!json_object_object_get_ex(obj, data[IF], &nif)) {
char str_ip[INET6_ADDRSTRLEN];
nif = json_object_new_object();
if (get_linklocal_address(data[IF], str_ip))
json_object_object_add(nif, "ll-addr", json_object_new_string(str_ip));
json_object_object_add(nif, "protocol", json_object_new_string("babel"));
json_object_object_add(obj, data[IF], nif);
json_object_object_add(nif, "neighbours", json_object_new_object());
}
struct json_object *neighborcollector;
json_object_object_get_ex(nif, "neighbours", &neighborcollector);
json_object_object_add(neighborcollector, data[ADDRESS], neigh);
}
return true;
}
static struct json_object * get_babel_neighbours(void) {
struct json_object *neighbours;
neighbours = json_object_new_object();
if (!neighbours)
return NULL;
babelhelper_readbabeldata(&bhelper_ctx, "dump", (void*)neighbours, handle_neighbour);
return(neighbours);
}
static void blobmsg_handle_list(struct blob_attr *attr, int len, bool array, struct json_object *wireless, struct json_object *tunnel, struct json_object *other);
static void blobmsg_handle_element(struct blob_attr *attr, bool head, char **ifname, char **proto, struct json_object *wireless, struct json_object *tunnel, struct json_object *other) {
void *data;
if (!blobmsg_check_attr(attr, false))
return;
data = blobmsg_data(attr);
switch (blob_id(attr)) {
case BLOBMSG_TYPE_STRING:
if (!strncmp(blobmsg_name(attr), "device", 6)) {
free(*ifname);
*ifname = strndup(data, IF_NAMESIZE);
} else if (!strncmp(blobmsg_name(attr), "proto", 5)) {
free(*proto);
*proto = strndup(data, PROTOLEN);
}
return;
case BLOBMSG_TYPE_ARRAY:
blobmsg_handle_list(data, blobmsg_data_len(attr), true, wireless, tunnel, other);
return;
case BLOBMSG_TYPE_TABLE:
blobmsg_handle_list(data, blobmsg_data_len(attr), false, wireless, tunnel, other);
}
}
static void blobmsg_handle_list(struct blob_attr *attr, int len, bool array, struct json_object *wireless, struct json_object *tunnel, struct json_object *other) {
struct blob_attr *pos;
int rem = len;
char *ifname = NULL;
char *proto = NULL;
__blob_for_each_attr(pos, attr, rem) {
blobmsg_handle_element(pos, array, &ifname, &proto, wireless, tunnel, other);
}
if (ifname && proto) {
if (!strncmp(proto, "gluon_mesh", 10)) {
mesh_add_if(ifname, wireless, tunnel, other);
}
}
free(ifname);
free(proto);
}
static void add_if_not_empty(struct json_object *obj, const char *key, struct json_object *val) {
if (json_object_array_length(val))
json_object_object_add(obj, key, val);
else
json_object_put(val);
}
static void receive_call_result_data(struct ubus_request *req, int type, struct blob_attr *msg) {
struct json_object *ret = json_object_new_object();
struct json_object *wireless = json_object_new_array();
struct json_object *tunnel = json_object_new_array();
struct json_object *other = json_object_new_array();
if (!ret || !wireless || !tunnel || !other) {
json_object_put(wireless);
json_object_put(tunnel);
json_object_put(other);
json_object_put(ret);
return;
}
if (!msg) {
printf("empty message\n");
return;
}
blobmsg_handle_list(blobmsg_data(msg), blobmsg_data_len(msg), false, wireless, tunnel, other);
add_if_not_empty(ret, "wireless", wireless);
add_if_not_empty(ret, "tunnel", tunnel);
add_if_not_empty(ret, "other", other);
*((struct json_object**)(req->priv)) = ret;
}
static struct json_object * get_mesh_ifs() {
struct ubus_context *ubus_ctx;
struct json_object *ret = NULL;
struct blob_buf b = {};
unsigned int id=8;
ubus_ctx = ubus_connect(NULL);
if (!ubus_ctx) {
fprintf(stderr,"could not connect to ubus, not providing mesh-data\n");
goto end;
}
blob_buf_init(&b, 0);
ubus_lookup_id(ubus_ctx, "network.interface", &id);
int uret = ubus_invoke(ubus_ctx, id, "dump", b.head, receive_call_result_data, &ret, UBUS_TIMEOUT);
if (uret > 0)
fprintf(stderr, "ubus command failed: %s\n", ubus_strerror(uret));
else if (uret == -2)
fprintf(stderr, "invalid call, exiting\n");
blob_buf_free(&b);
end:
ubus_free(ubus_ctx);
return ret;
}
static struct json_object * get_mesh(void) {
struct json_object *ret = json_object_new_object();
struct json_object *interfaces = NULL;
interfaces = json_object_new_object();
json_object_object_add(interfaces, "interfaces", get_mesh_ifs());
json_object_object_add(ret, "babel", interfaces);
return ret;
}
static struct json_object * get_babeld_version(void) {
char *version = get_line_from_run("exec babeld -V 2>&1");
struct json_object *ret = gluonutil_wrap_string(version);
free(version);
return ret;
}
static struct json_object * respondd_provider_nodeinfo(void) {
bhelper_ctx.debug=false;
struct json_object *ret = json_object_new_object();
struct json_object *network = json_object_new_object();
json_object_object_add(network, "addresses", get_addresses());
json_object_object_add(network, "mesh", get_mesh());
json_object_object_add(ret, "network", network);
struct json_object *software = json_object_new_object();
struct json_object *software_babeld = json_object_new_object();
json_object_object_add(software_babeld, "version", get_babeld_version());
json_object_object_add(software, "babeld", software_babeld);
json_object_object_add(ret, "software", software);
return ret;
}
static struct json_object * read_number(const char *ifname, const char *stat) {
const char *format = "/sys/class/net/%s/statistics/%s";
struct json_object *ret = NULL;
int64_t i;
char path[strlen(format) + strlen(ifname) + strlen(stat) + 1];
snprintf(path, sizeof(path), format, ifname, stat);
FILE *f = fopen(path, "r");
if (!f)
return NULL;
if (fscanf(f, "%"SCNd64, &i) == 1)
ret = json_object_new_int64(i);
fclose(f);
return ret;
}
static struct json_object * get_traffic(void) {
const char *ifname = "br-client";
struct json_object *ret = NULL;
struct json_object *rx = json_object_new_object();
struct json_object *tx = json_object_new_object();
json_object_object_add(rx, "packets", read_number(ifname, "rx_packets"));
json_object_object_add(rx, "bytes", read_number(ifname, "rx_bytes"));
json_object_object_add(rx, "dropped", read_number(ifname, "rx_dropped"));
json_object_object_add(tx, "packets", read_number(ifname, "tx_packets"));
json_object_object_add(tx, "dropped", read_number(ifname, "tx_dropped"));
json_object_object_add(tx, "bytes", read_number(ifname, "tx_bytes"));
ret = json_object_new_object();
json_object_object_add(ret, "rx", rx);
json_object_object_add(ret, "tx", tx);
return ret;
}
static bool handle_route_addgw_nexthop(char **data, void *arg) {
struct json_object *obj = (struct json_object*) arg;
if (data[PREFIX] && data[FROM] && data[VIA] && data[IF]) {
if ( (! strncmp(data[PREFIX], "::/0", 4) ) && ( ! strncmp(data[FROM], "::/0", 4) ) ) {
int gw_nexthoplen=strlen(data[VIA]) + strlen(data[IF])+2;
char gw_nexthop[gw_nexthoplen];
snprintf(gw_nexthop, gw_nexthoplen , "%s%%%s", data[VIA], data[IF]);
json_object_object_add(obj, "gateway_nexthop", json_object_new_string(gw_nexthop));
}
}
return true;
}
static int json_parse_get_clients(json_object * object) {
if (object) {
json_object_object_foreach(object, key, val) {
if (! strncmp("clients", key, 7)) {
return(json_object_get_int(val));
}
}
}
return(-1);
}
static int ask_l3roamd_for_client_count() {
struct sockaddr_un addr;
const char *socket_path = "/var/run/l3roamd.sock";
int fd;
int clients = -1;
char *buf = NULL;
int already_read = 0;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
fprintf(stderr, "could not setup l3roamd-control-socket\n");
return(-1);
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
fprintf(stderr, "connect error\n");
return(-1);
}
if (write(fd,"get_clients\n",12) != 12) {
perror("could not send command to l3roamd socket: get_clients");
goto end;
}
int rc = 0;
do {
char *buf_tmp = realloc(buf, already_read + SOCKET_INPUT_BUFFER_SIZE + 1);
if (buf_tmp == NULL) {
fprintf(stderr, "could not allocate memory for buffer\n");
goto end;
}
buf = buf_tmp;
rc = read(fd, &buf[already_read], SOCKET_INPUT_BUFFER_SIZE);
already_read+=rc;
if (rc < 0) {
perror("error on read in ask_l3roamd_for_client_count():");
goto end;
}
buf[already_read]='\0';
} while (rc == SOCKET_INPUT_BUFFER_SIZE);
json_object * jobj = json_tokener_parse(buf);
clients = json_parse_get_clients(jobj);
json_object_put(jobj);
end:
free(buf);
close(fd);
return clients;
}
static struct json_object * get_clients(void) {
struct json_object *ret = json_object_new_object();
int total = ask_l3roamd_for_client_count();
if (total >= 0)
json_object_object_add(ret, "total", json_object_new_int(total));
return ret;
}
static struct json_object * respondd_provider_statistics(void) {
struct json_object *ret = json_object_new_object();
json_object_object_add(ret, "clients", get_clients());
json_object_object_add(ret, "traffic", get_traffic());
babelhelper_readbabeldata(&bhelper_ctx, "dump", (void*)ret, handle_route_addgw_nexthop );
return ret;
}
static struct json_object * respondd_provider_neighbours(void) {
struct json_object *ret = json_object_new_object();
struct json_object *babel = get_babel_neighbours();
if (babel)
json_object_object_add(ret, "babel", babel);
return ret;
}
const struct respondd_provider_info respondd_providers[] = {
{"nodeinfo", respondd_provider_nodeinfo},
{"statistics", respondd_provider_statistics},
{"neighbours", respondd_provider_neighbours},
{}
};