3fda210f85
Also make babel match batman-adv and only emit the wireless/tunnel/other fields when they are non-empty. Fixes: #1783
547 lines
15 KiB
C
547 lines
15 KiB
C
/*
|
|
Copyright (c) 2016, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
#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},
|
|
{}
|
|
};
|