mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-01 13:23:01 +02:00
Collect more data from FRR to the OSPF and BFD operational datastore. Refactor CLI commands to use the YANG datastore instead of vtysh. Additionally, the BFD command(s) have been moved to the top level of admin-exec, which is where vtysh has them. Finally, preparing for future OSPFv3 support, all commands have been given a new context under 'show ip ospf'. Deprecation warnings have been added to the existing 'show ospf' commands. Also, for consistency, all commands names in plural have been changed to their singular form: routes -> route, interfaes -> interface, etc. This is what vtysh, Cisco, and others do as well. Fixes #1190 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
471 lines
11 KiB
C
471 lines
11 KiB
C
/* SPDX-License-Identifier: BSD-3-Clause */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sysrepo.h>
|
|
#include <ev.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <asm/types.h>
|
|
#include <sys/socket.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include <libite/lite.h>
|
|
#include <jansson.h>
|
|
#include <ctype.h>
|
|
#include <linux/if.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/mman.h>
|
|
|
|
#include <srx/common.h>
|
|
#include <srx/helpers.h>
|
|
#include <srx/lyx.h>
|
|
#include <srx/systemv.h>
|
|
|
|
#include "shared.h"
|
|
|
|
/* New kernel feature, not in sys/mman.h yet */
|
|
#ifndef MFD_NOEXEC_SEAL
|
|
#define MFD_NOEXEC_SEAL 0x0008U
|
|
#endif
|
|
|
|
#define YANGER_BINPATH YANGER_DIR"/yanger"
|
|
#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"
|
|
#define XPATH_ROUTING_TABLE "/ietf-routing:routing/ribs"
|
|
#define XPATH_HARDWARE_BASE "/ietf-hardware:hardware"
|
|
#define XPATH_SYSTEM_BASE "/ietf-system"
|
|
#define XPATH_ROUTING_OSPF XPATH_ROUTING_BASE "/ospf"
|
|
#define XPATH_ROUTING_BFD XPATH_ROUTING_BASE "/bfd"
|
|
#define XPATH_CONTAIN_BASE "/infix-containers:containers"
|
|
#define XPATH_DHCP_SERVER_BASE "/infix-dhcp-server:dhcp-server"
|
|
#define XPATH_LLDP_BASE "/ieee802-dot1ab-lldp:lldp"
|
|
#define XPATH_FIREWALL_BASE "/infix-firewall:firewall"
|
|
|
|
TAILQ_HEAD(sub_head, sub);
|
|
|
|
struct sub {
|
|
struct ev_io watcher;
|
|
sr_subscription_ctx_t *sr_sub;
|
|
|
|
TAILQ_ENTRY(sub)
|
|
entries;
|
|
};
|
|
|
|
struct statd {
|
|
struct sub_head subs;
|
|
sr_session_ctx_t *sr_ses;
|
|
struct ev_loop *ev_loop;
|
|
};
|
|
|
|
static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent,
|
|
char *yanger_args[])
|
|
{
|
|
FILE *stream;
|
|
int err;
|
|
int fd;
|
|
|
|
fd = memfd_create("yanger_tmpfile", MFD_CLOEXEC | MFD_NOEXEC_SEAL);
|
|
if (fd == -1) {
|
|
ERROR("Error, unable to create memfd");
|
|
return SR_ERR_SYS;
|
|
}
|
|
|
|
/* Wrap the file descriptor in a FILE stream for fwrite */
|
|
stream = fdopen(fd, "w+");
|
|
if (stream == NULL) {
|
|
ERROR("Error, unable to fdopen memfd");
|
|
close(fd);
|
|
return SR_ERR_SYS;
|
|
}
|
|
|
|
err = fsystemv(yanger_args, NULL, stream, NULL);
|
|
if (err) {
|
|
ERROR("Error, running yanger");
|
|
fclose(stream);
|
|
close(fd);
|
|
return SR_ERR_SYS;
|
|
}
|
|
|
|
fflush(stream);
|
|
|
|
if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
|
|
ERROR("Error, unable reset stream (seek)");
|
|
fclose(stream);
|
|
close(fd);
|
|
return SR_ERR_SYS;
|
|
}
|
|
|
|
err = lyd_parse_data_fd(ctx, fd, LYD_JSON, LYD_PARSE_ONLY, 0, parent);
|
|
if (err)
|
|
ERROR("Error, parsing yanger data (%d)", err);
|
|
|
|
fclose(stream);
|
|
close(fd);
|
|
|
|
return err;
|
|
}
|
|
|
|
static char *xpath_extract(const char *xpath, const char *key)
|
|
{
|
|
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 *yanger_args[5] = {
|
|
YANGER_BINPATH,
|
|
(char *)model,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
char *ifname = NULL;
|
|
const struct ly_ctx *ctx;
|
|
sr_conn_ctx_t *con;
|
|
int err;
|
|
|
|
DEBUG("Incoming interface query for xpath: %s", xpath);
|
|
|
|
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;
|
|
}
|
|
|
|
ifname = xpath_extract(xpath, "[name='");
|
|
if (ifname) {
|
|
yanger_args[2] = "-p";
|
|
yanger_args[3] = ifname;
|
|
}
|
|
err = ly_add_yanger_data(ctx, parent, yanger_args);
|
|
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 *, const char *xpath, uint32_t,
|
|
struct lyd_node **parent, __attribute__((unused)) void *priv)
|
|
{
|
|
char *yanger_args[5] = {
|
|
YANGER_BINPATH,
|
|
(char *)model,
|
|
NULL
|
|
};
|
|
const struct ly_ctx *ctx;
|
|
sr_conn_ctx_t *con;
|
|
sr_error_t err;
|
|
|
|
DEBUG("Incoming generic query for xpath: %s", xpath);
|
|
|
|
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, yanger_args);
|
|
if (err)
|
|
ERROR("Error adding yanger data");
|
|
|
|
sr_release_context(con);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int sr_ospf_cb(sr_session_ctx_t *session, uint32_t, const char *,
|
|
const char *, const char *xpath, uint32_t,
|
|
struct lyd_node **parent, __attribute__((unused)) void *priv)
|
|
{
|
|
char *yanger_args[5] = {
|
|
YANGER_BINPATH,
|
|
"ietf-ospf",
|
|
NULL
|
|
};
|
|
const struct ly_ctx *ctx;
|
|
sr_conn_ctx_t *con;
|
|
sr_error_t err;
|
|
|
|
DEBUG("Incoming ospf query for xpath: %s", xpath);
|
|
|
|
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, yanger_args);
|
|
if (err)
|
|
ERROR("Error adding yanger data");
|
|
|
|
sr_release_context(con);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int sr_bfd_cb(sr_session_ctx_t *session, uint32_t, const char *,
|
|
const char *, const char *xpath, uint32_t,
|
|
struct lyd_node **parent, __attribute__((unused)) void *priv)
|
|
{
|
|
char *yanger_args[5] = {
|
|
YANGER_BINPATH,
|
|
"ietf-bfd-ip-sh",
|
|
NULL
|
|
};
|
|
const struct ly_ctx *ctx;
|
|
sr_conn_ctx_t *con;
|
|
sr_error_t err;
|
|
|
|
DEBUG("Incoming BFD query for xpath: %s", xpath);
|
|
|
|
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, yanger_args);
|
|
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)
|
|
{
|
|
ev_break(loop, EVBREAK_ALL);
|
|
}
|
|
|
|
static void sigusr1_cb(struct ev_loop *, struct ev_signal *, int)
|
|
{
|
|
debug ^= 1;
|
|
}
|
|
|
|
static void sr_event_cb(struct ev_loop *, struct ev_io *w, int)
|
|
{
|
|
struct sub *sub = (struct sub *)w->data;
|
|
|
|
sr_subscription_process_events(sub->sr_sub, NULL, NULL);
|
|
}
|
|
|
|
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))
|
|
{
|
|
struct sub *sub;
|
|
int sr_ev_pipe;
|
|
sr_error_t err;
|
|
|
|
sub = malloc(sizeof(struct sub));
|
|
memset(sub, 0, sizeof(struct sub));
|
|
|
|
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,
|
|
&sub->sr_sub);
|
|
if (err) {
|
|
ERROR("Error, subscribing to path \"%s\": %s", xpath, sr_strerror(err));
|
|
free(sub);
|
|
return err;
|
|
}
|
|
|
|
err = sr_get_event_pipe(sub->sr_sub, &sr_ev_pipe);
|
|
if (err) {
|
|
ERROR("Error, getting sysrepo event pipe: %s", sr_strerror(err));
|
|
sr_unsubscribe(sub->sr_sub);
|
|
free(sub);
|
|
return err;
|
|
}
|
|
|
|
TAILQ_INSERT_TAIL(&statd->subs, sub, entries);
|
|
|
|
ev_io_init(&sub->watcher, sr_event_cb, sr_ev_pipe, EV_READ);
|
|
sub->watcher.data = sub;
|
|
ev_io_start(statd->ev_loop, &sub->watcher);
|
|
|
|
return SR_ERR_OK;
|
|
}
|
|
|
|
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 void unsub_to_all(struct statd *statd)
|
|
{
|
|
struct sub *sub;
|
|
|
|
while (!TAILQ_EMPTY(&statd->subs)) {
|
|
sub = TAILQ_FIRST(&statd->subs);
|
|
sub_delete(statd->ev_loop, &statd->subs, sub);
|
|
}
|
|
}
|
|
|
|
static int subscribe_to_all(struct statd *statd)
|
|
{
|
|
DEBUG("Attempting to subscribe to all");
|
|
|
|
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-routing", XPATH_ROUTING_BFD, sr_bfd_cb))
|
|
return SR_ERR_INTERNAL;
|
|
if (subscribe(statd, "ietf-hardware", XPATH_HARDWARE_BASE, sr_generic_cb))
|
|
return SR_ERR_INTERNAL;
|
|
if (subscribe(statd, "ietf-system", XPATH_SYSTEM_BASE":system", sr_generic_cb))
|
|
return SR_ERR_INTERNAL;
|
|
if (subscribe(statd, "ietf-system", XPATH_SYSTEM_BASE":system-state", sr_generic_cb))
|
|
return SR_ERR_INTERNAL;
|
|
if (subscribe(statd, "ieee802-dot1ab-lldp", XPATH_LLDP_BASE, sr_generic_cb))
|
|
return SR_ERR_INTERNAL;
|
|
#ifdef CONTAINERS
|
|
if (subscribe(statd, "infix-containers", XPATH_CONTAIN_BASE, sr_generic_cb))
|
|
return SR_ERR_INTERNAL;
|
|
#endif
|
|
if (subscribe(statd, "infix-dhcp-server", XPATH_DHCP_SERVER_BASE, sr_generic_cb))
|
|
return SR_ERR_INTERNAL;
|
|
if (subscribe(statd, "infix-firewall", XPATH_FIREWALL_BASE, sr_generic_cb))
|
|
return SR_ERR_INTERNAL;
|
|
|
|
INFO("Successfully subscribed to all models");
|
|
return SR_ERR_OK;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct ev_signal sigint_watcher, sigusr1_watcher;
|
|
int log_opts = LOG_PID | LOG_NDELAY;
|
|
struct statd statd = {};
|
|
sr_conn_ctx_t *sr_conn;
|
|
const char *env;
|
|
int err;
|
|
|
|
env = getenv("DEBUG");
|
|
if (env || (argc > 1 && !strcmp(argv[1], "-d"))) {
|
|
log_opts |= LOG_PERROR;
|
|
debug = 1;
|
|
}
|
|
|
|
openlog("statd", log_opts, LOG_DAEMON);
|
|
|
|
TAILQ_INIT(&statd.subs);
|
|
statd.ev_loop = EV_DEFAULT;
|
|
|
|
INFO("Status daemon starting");
|
|
|
|
err = sr_connect(SR_CONN_DEFAULT, &sr_conn);
|
|
if (err) {
|
|
ERROR("Error, connecting to sysrepo: %s", sr_strerror(err));
|
|
return EXIT_FAILURE;
|
|
}
|
|
DEBUG("Connected to sysrepo");
|
|
|
|
err = sr_session_start(sr_conn, SR_DS_OPERATIONAL, &statd.sr_ses);
|
|
if (err) {
|
|
ERROR("Error, start sysrepo session: %s", sr_strerror(err));
|
|
sr_disconnect(sr_conn);
|
|
return EXIT_FAILURE;
|
|
}
|
|
DEBUG("Session started (%p)", statd.sr_ses);
|
|
|
|
err = subscribe_to_all(&statd);
|
|
if (err) {
|
|
sr_disconnect(sr_conn);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
ev_signal_init(&sigint_watcher, sigint_cb, SIGINT);
|
|
sigint_watcher.data = &statd;
|
|
ev_signal_start(statd.ev_loop, &sigint_watcher);
|
|
|
|
ev_signal_init(&sigusr1_watcher, sigusr1_cb, SIGUSR1);
|
|
sigusr1_watcher.data = &statd;
|
|
ev_signal_start(statd.ev_loop, &sigusr1_watcher);
|
|
|
|
/* Signal readiness to Finit */
|
|
pidfile(NULL);
|
|
|
|
INFO("Status daemon entering main event loop");
|
|
ev_run(statd.ev_loop, 0);
|
|
|
|
/* We should never get here during normal operation */
|
|
INFO("Status daemon shutting down");
|
|
unsub_to_all(&statd);
|
|
sr_session_stop(statd.sr_ses);
|
|
sr_disconnect(sr_conn);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|