mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 12:33:02 +02:00
544 lines
13 KiB
C
544 lines
13 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 <time.h>
|
|
#include <sys/stat.h>
|
|
#include <pthread.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"
|
|
#include "journal.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_RIP XPATH_ROUTING_BASE "/rip"
|
|
#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"
|
|
#define XPATH_NTP_BASE "/ietf-ntp:ntp"
|
|
|
|
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; /* Provider session with callbacks */
|
|
sr_session_ctx_t *sr_query_ses; /* Consumer session for queries */
|
|
sr_conn_ctx_t *sr_conn; /* Connection (owns YANG context) */
|
|
struct ev_loop *ev_loop;
|
|
struct journal_ctx journal; /* Journal thread context */
|
|
};
|
|
|
|
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_rip_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-rip",
|
|
NULL
|
|
};
|
|
const struct ly_ctx *ctx;
|
|
sr_conn_ctx_t *con;
|
|
sr_error_t err;
|
|
|
|
DEBUG("Incoming rip 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_RIP, sr_rip_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;
|
|
if (subscribe(statd, "ietf-ntp", XPATH_NTP_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 = {};
|
|
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, &statd.sr_conn);
|
|
if (err) {
|
|
ERROR("Error, connecting to sysrepo: %s", sr_strerror(err));
|
|
return EXIT_FAILURE;
|
|
}
|
|
DEBUG("Connected to sysrepo");
|
|
|
|
/* Session 1: Provider with operational callbacks */
|
|
err = sr_session_start(statd.sr_conn, SR_DS_OPERATIONAL, &statd.sr_ses);
|
|
if (err) {
|
|
ERROR("Error, start provider session: %s", sr_strerror(err));
|
|
sr_disconnect(statd.sr_conn);
|
|
return EXIT_FAILURE;
|
|
}
|
|
DEBUG("Provider session started (%p)", statd.sr_ses);
|
|
|
|
/* Session 2: Consumer for querying operational data */
|
|
err = sr_session_start(statd.sr_conn, SR_DS_OPERATIONAL, &statd.sr_query_ses);
|
|
if (err) {
|
|
ERROR("Error, start query session: %s", sr_strerror(err));
|
|
sr_session_stop(statd.sr_ses);
|
|
sr_disconnect(statd.sr_conn);
|
|
return EXIT_FAILURE;
|
|
}
|
|
DEBUG("Query session started (%p)", statd.sr_query_ses);
|
|
|
|
err = subscribe_to_all(&statd);
|
|
if (err) {
|
|
sr_session_stop(statd.sr_query_ses);
|
|
sr_session_stop(statd.sr_ses);
|
|
sr_disconnect(statd.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);
|
|
|
|
err = journal_start(&statd.journal, statd.sr_query_ses);
|
|
if (err) {
|
|
sr_session_stop(statd.sr_query_ses);
|
|
sr_session_stop(statd.sr_ses);
|
|
sr_disconnect(statd.sr_conn);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* 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");
|
|
|
|
journal_stop(&statd.journal);
|
|
|
|
unsub_to_all(&statd);
|
|
sr_session_stop(statd.sr_query_ses);
|
|
sr_session_stop(statd.sr_ses);
|
|
sr_disconnect(statd.sr_conn);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|