/* SPDX-FileCopyrightText: 2016, Matthias Schiffer */ /* SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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}, {} };