diff --git a/src/statd/python/yanger/yanger.py b/src/statd/python/yanger/yanger.py index 9e8afee8..c3521b34 100755 --- a/src/statd/python/yanger/yanger.py +++ b/src/statd/python/yanger/yanger.py @@ -837,6 +837,26 @@ def add_vlans_to_bridge(brname, iface_out, mc_status): insert(iface_out, "infix-interfaces:bridge", "vlans", "vlan", vlans) +def get_iface_data(ifname): + iface_out = {} + + add_ip_link(ifname, iface_out) + add_ip_addr(ifname, iface_out) + + if 'type' in iface_out and iface_out['type'] == "infix-if-type:ethernet": + add_ethtool_groups(ifname, iface_out) + add_ethtool_std(ifname, iface_out) + + if 'type' in iface_out and iface_out['type'] == "infix-if-type:bridge": + # Fail silent, multicast snooping may not be enabled on bridge + mc_status = run_json_cmd(['mctl', '-p', 'show', 'igmp', 'json'], + "igmp-status.json", default={}, + check=False) + + add_vlans_to_bridge(ifname, iface_out, mc_status) + add_mdb_to_bridge(ifname, iface_out, mc_status) + + return iface_out def main(): global TESTPATH @@ -867,38 +887,20 @@ def main(): logger.addHandler(log) if args.model == 'ietf-interfaces': - # For now, we handle each interface separately, as this is how it's - # currently implemented in sysrepo. I.e sysrepo will subscribe to - # each individual interface and query it for YANG data. - - if not args.param: - print("usage: yanger ietf-interfaces -p INTERFACE", file=sys.stderr) - sys.exit(1) - yang_data = { "ietf-interfaces:interfaces": { - "interface": [{}] + "interface": [] } } - ifname = args.param - iface_out = yang_data['ietf-interfaces:interfaces']['interface'][0] - - add_ip_link(ifname, iface_out) - add_ip_addr(ifname, iface_out) - - if 'type' in iface_out and iface_out['type'] == "infix-if-type:ethernet": - add_ethtool_groups(ifname, iface_out) - add_ethtool_std(ifname, iface_out) - - if 'type' in iface_out and iface_out['type'] == "infix-if-type:bridge": - # Fail silent, multicast snooping may not be enabled on bridge - mc_status = run_json_cmd(['mctl', '-p', 'show', 'igmp', 'json'], - "igmp-status.json", default={}, - check=False) - - add_vlans_to_bridge(ifname, iface_out, mc_status) - add_mdb_to_bridge(ifname, iface_out, mc_status) + if args.param: + iface_data = get_iface_data(args.param) + yang_data['ietf-interfaces:interfaces']['interface'].append(iface_data) + else: + ifnames = os.listdir('/sys/class/net/') + for ifname in ifnames: + iface_data = get_iface_data(ifname) + yang_data['ietf-interfaces:interfaces']['interface'].append(iface_data) elif args.model == 'ietf-routing': yang_data = { diff --git a/src/statd/statd.c b/src/statd/statd.c index af9030a3..7a24b147 100644 --- a/src/statd/statd.c +++ b/src/statd/statd.c @@ -31,9 +31,6 @@ #define MFD_NOEXEC_SEAL 0x0008U #endif -#define SOCK_RMEM_SIZE 1000000 /* Arbitrary chosen, default = 212992 */ -#define NL_BUF_SIZE 4096 /* Arbitrary chosen */ - #define XPATH_MAX PATH_MAX #define XPATH_IFACE_BASE "/ietf-interfaces:interfaces" #define XPATH_ROUTING_BASE "/ietf-routing:routing/control-plane-protocols/control-plane-protocol" @@ -44,11 +41,7 @@ TAILQ_HEAD(sub_head, sub); -/* This should, with some modifications, be able to hold other subscription - * types, not only interfaces. - */ struct sub { - char name[IFNAMSIZ + 3]; struct ev_io watcher; sr_subscription_ctx_t *sr_sub; @@ -56,80 +49,12 @@ struct sub { entries; }; -struct netlink { - int sd; - struct ev_io watcher; -}; - struct statd { - struct netlink nl; - struct ev_loop *ev_loop; struct sub_head subs; sr_session_ctx_t *sr_ses; + struct ev_loop *ev_loop; }; -static void set_sock_rcvbuf(int sd, int size) -{ - if (setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) < 0) { - perror("setsockopt"); - return; - } - DEBUG("Socket receive buffer size increased to: %d bytes", size); -} - -static int nl_sock_init(void) -{ - struct sockaddr_nl addr; - int sock; - - sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (sock < 0) { - ERROR("Error, creating netlink socket: %s", strerror(errno)); - return -1; - } - - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - addr.nl_groups = RTMGRP_LINK; - - if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - ERROR("Error, binding netlink socket: %s", strerror(errno)); - close(sock); - return -1; - } - - return sock; -} - -static struct sub *sub_find(struct sub_head *subs, const char *name) -{ - struct sub *sub; - - TAILQ_FOREACH(sub, subs, entries) { - if (strcmp(sub->name, name) == 0) - return sub; - } - - return NULL; -} - -static void sub_delete(struct ev_loop *loop, struct sub_head *subs, struct sub *sub) -{ - TAILQ_REMOVE(subs, sub, entries); - ev_io_stop(loop, &sub->watcher); - sr_unsubscribe(sub->sr_sub); - free(sub); -} - -static json_t *json_get_ip_link(void) -{ - char cmd[512] = {}; /* Size is arbitrary */ - - snprintf(cmd, sizeof(cmd), "ip -s -d -j link show 2>/dev/null"); - - return json_get_output(cmd); -} - /* * The 'fail' parameter is true for most calls to this function, except * when reading ethtool data (below). @@ -153,12 +78,12 @@ static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent return SR_ERR_SYS; } - if (!strcmp(model, "ietf-interfaces")) { + if ((strcmp(model, "ietf-interfaces") == 0) && arg) { yanger_args[2] = "-p"; yanger_args[3] = (char *)arg; } - fd = memfd_create("my_temp_file", MFD_CLOEXEC | MFD_NOEXEC_SEAL); + fd = memfd_create("yanger_tmpfile", MFD_CLOEXEC | MFD_NOEXEC_SEAL); if (fd == -1) { ERROR("Error, unable to create memfd"); return SR_ERR_SYS; @@ -201,18 +126,50 @@ static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent return err; } -static int sr_ifaces_cb(sr_session_ctx_t *session, uint32_t, const char *path, - const char *, const char *, uint32_t, - struct lyd_node **parent, void *priv) +static char *xpath_extract(const char *xpath, const char *key) { - struct sub *sub = priv; + char *res = NULL; + const char *ptr; + const char *end; + + /* (also checks if key exist) */ + ptr = strstr(xpath, key); + if (!ptr) + return NULL; + + ptr += strlen(key); + + end = strchr(ptr, '\''); + if (!end) { + ERROR("Can't find end quote for %s (sanity check)", key); + return NULL; + } + + if ((end - ptr) >= XPATH_MAX) { + ERROR("Value for %s is to long (sanity check)", key); + return NULL; + } + + res = calloc((end - ptr) + 1, sizeof(char)); + if (!res) + return NULL; + + strncpy(res, ptr, end - ptr); + res[end - ptr] = '\0'; + + return res; +} + +static int sr_iface_cb(sr_session_ctx_t *session, uint32_t, const char *model, + const char *, const char *xpath, uint32_t, + struct lyd_node **parent, __attribute__((unused)) void *priv) +{ + char *ifname = NULL; const struct ly_ctx *ctx; sr_conn_ctx_t *con; - char *ifname; int err; - ifname = &sub->name[3]; - DEBUG("Incoming query for xpath: %s, ifname %s", path, ifname); + DEBUG("Incoming interface query for xpath: %s", xpath); con = sr_session_get_connection(session); if (!con) { @@ -226,13 +183,37 @@ static int sr_ifaces_cb(sr_session_ctx_t *session, uint32_t, const char *path, return SR_ERR_INTERNAL; } - /* - * If ethtool fails, it just means this interface doesn't have - * counters (in JSON format). Possibly because this board + - * driver combo is not 100% supported yet. This is fine, no - * need to break the operational datastore though. - */ - err = ly_add_yanger_data(ctx, parent, "ietf-interfaces", ifname, false); + ifname = xpath_extract(xpath, "[name='"); + err = ly_add_yanger_data(ctx, parent, model, ifname, true); + if (err) + ERROR("Error adding interface yanger data"); + + sr_release_context(con); + + return SR_ERR_OK; +} + +static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *model, + const char *path, const char *, uint32_t, + struct lyd_node **parent, __attribute__((unused)) void *priv) +{ + const struct ly_ctx *ctx; + sr_conn_ctx_t *con; + sr_error_t err; + + con = sr_session_get_connection(session); + if (!con) { + ERROR("Error, getting sr connection"); + return SR_ERR_INTERNAL; + } + + ctx = sr_acquire_context(con); + if (!ctx) { + ERROR("Error, acquiring context"); + return SR_ERR_INTERNAL; + } + + err = ly_add_yanger_data(ctx, parent, model, NULL, true); if (err) ERROR("Error adding yanger data"); @@ -272,36 +253,6 @@ static int sr_ospf_cb(sr_session_ctx_t *session, uint32_t, const char *, return err; } -static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *model, - const char *path, const char *, uint32_t, - struct lyd_node **parent, __attribute__((unused)) void *priv) -{ - const struct ly_ctx *ctx; - sr_conn_ctx_t *con; - sr_error_t err; - - DEBUG("Incoming query for xpath: %s", path); - - con = sr_session_get_connection(session); - if (!con) { - ERROR("Error, getting sr connection"); - return SR_ERR_INTERNAL; - } - - ctx = sr_acquire_context(con); - if (!ctx) { - ERROR("Error, acquiring context"); - return SR_ERR_INTERNAL; - } - - err = ly_add_yanger_data(ctx, parent, model, NULL, true); - if (err) - ERROR("Error adding yanger data"); - - sr_release_context(con); - - return err; -} static void sigint_cb(struct ev_loop *loop, struct ev_signal *, int) { @@ -320,29 +271,17 @@ static void sr_event_cb(struct ev_loop *, struct ev_io *w, int) sr_subscription_process_events(sub->sr_sub, NULL, NULL); } -static int subscribe(struct statd *statd, char *model, char *xpath, const char *name, +static int subscribe(struct statd *statd, char *model, char *xpath, int (*cb)(sr_session_ctx_t *session, uint32_t, const char *, const char *, - const char *, uint32_t, struct lyd_node **parent, void *priv)) + const char *, uint32_t, struct lyd_node **parent, void *priv)) { struct sub *sub; int sr_ev_pipe; sr_error_t err; - sub = sub_find(&statd->subs, name); - if (sub) { - DEBUG("%s already subscribed", name); - return SR_ERR_OK; - } sub = malloc(sizeof(struct sub)); memset(sub, 0, sizeof(struct sub)); - if (strlen(name) >= sizeof(sub->name)) { - ERROR("Subscriber name is to long"); - free(sub); - return SR_ERR_INTERNAL; - } - snprintf(sub->name, sizeof(sub->name), "%s", name); - DEBUG("Subscribe to events for \"%s\"", xpath); err = sr_oper_get_subscribe(statd->sr_ses, model, xpath, cb, sub, SR_SUBSCR_DEFAULT | SR_SUBSCR_NO_THREAD | SR_SUBSCR_DONE_ONLY, @@ -370,29 +309,12 @@ static int subscribe(struct statd *statd, char *model, char *xpath, const char * return SR_ERR_OK; } -static int sub_to_routes(struct statd *statd) +static void sub_delete(struct ev_loop *loop, struct sub_head *subs, struct sub *sub) { - return subscribe(statd, "ietf-routing", XPATH_ROUTING_TABLE, "routes", sr_generic_cb); -} - -static int sub_to_iface(struct statd *statd, const char *ifname) -{ - char path[XPATH_MAX] = {}; - char name[IFNAMSIZ + 3]; - snprintf(name, sizeof(name), "if-%s", ifname); - - /** - * Skip internal interfaces (such as dsa0) - * - * NOTE: this is a good solution but it might be to slow if a lot of - * new interfaces pops up at the same time. We don't want to delay - * processing the netlink messages to much. - */ - if (ip_link_check_group(ifname, "internal") == 1) - return SR_ERR_OK; - - snprintf(path, sizeof(path), "%s/interface[name='%s']", XPATH_IFACE_BASE, ifname); - return subscribe(statd, "ietf-interfaces", path, name, sr_ifaces_cb); + TAILQ_REMOVE(subs, sub, entries); + ev_io_stop(loop, &sub->watcher); + sr_unsubscribe(sub->sr_sub); + free(sub); } static void unsub_to_all(struct statd *statd) @@ -401,136 +323,30 @@ static void unsub_to_all(struct statd *statd) while (!TAILQ_EMPTY(&statd->subs)) { sub = TAILQ_FIRST(&statd->subs); - DEBUG("Unsubscribe from \"%s\" (all)", sub->name); sub_delete(statd->ev_loop, &statd->subs, sub); } } -static int unsub_to_name(struct statd *statd, char *name) +static int subscribe_to_all(struct statd *statd) { - struct sub *sub; + DEBUG("Attempting to subscribe to all"); - sub = sub_find(&statd->subs, name); - if (!sub) { - ERROR("Error, can't find indentity to delete (%s)", name); + if (subscribe(statd, "ietf-routing", XPATH_ROUTING_TABLE, sr_generic_cb)) + return SR_ERR_INTERNAL; + if (subscribe(statd, "ietf-interfaces", XPATH_IFACE_BASE, sr_iface_cb)) + return SR_ERR_INTERNAL; + if (subscribe(statd, "ietf-routing", XPATH_ROUTING_OSPF, sr_ospf_cb)) + return SR_ERR_INTERNAL; + if (subscribe(statd, "ietf-hardware", XPATH_HARDWARE_BASE, sr_generic_cb)) return SR_ERR_INTERNAL; - } - DEBUG("Unsubscribe from \"%s\"", sub->name); - sub_delete(statd->ev_loop, &statd->subs, sub); - - return SR_ERR_OK; -} -static int unsub_to_iface(struct statd *statd, char *ifname) -{ - char name[IFNAMSIZ + 3]; - snprintf(name, sizeof(name), "if-%s", ifname); - - return unsub_to_name(statd, name); -} - -static int nl_process_msg(struct nlmsghdr *nlh, struct statd *statd) -{ - struct ifinfomsg *iface; - struct rtattr *attr; - int attr_len; - - iface = NLMSG_DATA(nlh); - attr = IFLA_RTA(iface); - attr_len = IFLA_PAYLOAD(nlh); - - for (; RTA_OK(attr, attr_len); attr = RTA_NEXT(attr, attr_len)) { - if (attr->rta_type == IFLA_IFNAME) { - char *ifname = (char *)RTA_DATA(attr); - - if (nlh->nlmsg_type == RTM_NEWLINK) - return sub_to_iface(statd, ifname); - else if (nlh->nlmsg_type == RTM_DELLINK) - return unsub_to_iface(statd, ifname); - else - return SR_ERR_INTERNAL; - } - } - - /* Ignore nl messages with no interface name */ - return SR_ERR_OK; -} - -static void nl_event_cb(struct ev_loop *, struct ev_io *w, int) -{ - struct statd *statd = (struct statd *)w->data; - char buf[NL_BUF_SIZE]; - struct nlmsghdr *nlh; - int err; - int len; - - len = recv(statd->nl.sd, buf, sizeof(buf) - 1, 0); - if (len < 0) { - ERROR("Error, netlink recv failed: %s", strerror(errno)); - close(statd->nl.sd); - /* NOTE: This is likely caused by a full kernel buffer, which - * means we can't trust our list. So we exit hard and let finit - * respawn us to handle this. - */ - exit(EXIT_FAILURE); - } - - /* nl_process_msg() expects NULL terminated buffer */ - buf[len] = 0; - - for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { - err = nl_process_msg(nlh, statd); - if (err) - ERROR("Error, processing netlink message: %s", sr_strerror(err)); - } -} - -static int sub_to_ifaces(struct statd *statd) -{ - json_t *j_iface; - json_t *j_root; - size_t i; - - j_root = json_get_ip_link(); - if (!j_root) { - ERROR("Error, parsing ip-link JSON"); - return SR_ERR_SYS; - } - - json_array_foreach(j_root, i, j_iface) { - json_t *j_ifname; - int err; - j_ifname = json_object_get(j_iface, "ifname"); - if (!json_is_string(j_ifname)) { - ERROR("Got unexpected JSON type for 'ifname'"); - continue; - } - err = sub_to_iface(statd, json_string_value(j_ifname)); - if (err) { - ERROR("Unable to subscribe to %s", json_string_value(j_ifname)); - continue; - } - } - json_decref(j_root); - - return SR_ERR_OK; -} - -static int sub_to_hardware(struct statd *statd) -{ - return subscribe(statd, "ietf-hardware", XPATH_HARDWARE_BASE, "hardware", sr_generic_cb); -} - -static int sub_to_ospf(struct statd *statd) -{ - return subscribe(statd, "ietf-routing", XPATH_ROUTING_OSPF, "ospf", sr_ospf_cb); -} - #ifdef CONTAINERS -static int sub_to_container(struct statd *statd) -{ - return subscribe(statd, "infix-containers", XPATH_CONTAIN_BASE, "container", sr_generic_cb); -} + if (subscribe(statd, "infix-containers", XPATH_CONTAIN_BASE, sr_generic_cb)) + return SR_ERR_INTERNAL; #endif + INFO("Successfully subscribed to all models"); + + return SR_ERR_OK; +} int main(int argc, char *argv[]) { @@ -552,15 +368,8 @@ int main(int argc, char *argv[]) TAILQ_INIT(&statd.subs); statd.ev_loop = EV_DEFAULT; - statd.nl.sd = nl_sock_init(); - if (statd.nl.sd < 0) { - ERROR("Error, opening netlink socket"); - return EXIT_FAILURE; - } INFO("Status daemon starting"); - set_sock_rcvbuf(statd.nl.sd, SOCK_RMEM_SIZE); - err = sr_connect(SR_CONN_DEFAULT, &sr_conn); if (err) { ERROR("Error, connecting to sysrepo: %s", sr_strerror(err)); @@ -576,43 +385,12 @@ int main(int argc, char *argv[]) } DEBUG("Session started (%p)", statd.sr_ses); - DEBUG("Attempting to register existing interfaces"); - err = sub_to_ifaces(&statd); + err = subscribe_to_all(&statd); if (err) { - ERROR("Error, registering existing interfaces"); sr_disconnect(sr_conn); return EXIT_FAILURE; } - err = sub_to_routes(&statd); - if (err) { - ERROR("Error register for IPv4 routes"); - sr_disconnect(sr_conn); - return EXIT_FAILURE; - } - err = sub_to_ospf(&statd); - if (err) { - ERROR("Error register for OSPF"); - sr_disconnect(sr_conn); - return EXIT_FAILURE; - } - - err = sub_to_hardware(&statd); - if (err) { - ERROR("Error register for hardware status"); - sr_disconnect(sr_conn); - return EXIT_FAILURE; - } - -#ifdef CONTAINERS - err = sub_to_container(&statd); - if (err) { - ERROR("Error registering infix-container status"); - sr_disconnect(sr_conn); - return EXIT_FAILURE; - } -#endif - ev_signal_init(&sigint_watcher, sigint_cb, SIGINT); sigint_watcher.data = &statd; ev_signal_start(statd.ev_loop, &sigint_watcher); @@ -621,10 +399,6 @@ int main(int argc, char *argv[]) sigusr1_watcher.data = &statd; ev_signal_start(statd.ev_loop, &sigusr1_watcher); - ev_io_init(&statd.nl.watcher, nl_event_cb, statd.nl.sd, EV_READ); - statd.nl.watcher.data = &statd; - ev_io_start(statd.ev_loop, &statd.nl.watcher); - INFO("Status daemon entering main event loop"); ev_run(statd.ev_loop, 0);