Files
infix/src/statd/statd.c
T
Mattias Walström 581d3d52bd statd: Adapt to yanger keys on module:container path
Instead of relying of the runtime xpath, which can be just /system-state
for example, resulting on no yangerd match.
2026-06-24 15:53:03 +02:00

504 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 <srx/common.h>
#include <srx/helpers.h>
#include <srx/lyx.h>
#include "shared.h"
#include "journal.h"
#include "avahi.h"
#include "yangerd.h"
#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"
#define XPATH_PTP_BASE "/ieee1588-ptp-tt:ptp"
TAILQ_HEAD(sub_head, sub);
struct sub {
struct ev_io watcher;
sr_subscription_ctx_t *sr_sub;
char key[XPATH_MAX]; /* yangerd key, derived from the subscription xpath */
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 */
struct mdns_ctx mdns; /* mDNS neighbor monitor */
};
static int ly_add_yangerd_data(const struct ly_ctx *ctx, struct lyd_node **parent,
const char *path)
{
char *json = NULL;
size_t len = 0;
int err;
err = yangerd_query(path, &json, &len);
if (err) {
free(json);
ERROR("yangerd: query failed for %s", path);
return SR_ERR_SYS;
}
NOTE("yangerd: got %zu bytes JSON for %s", len, path);
err = lyd_parse_data_mem(ctx, json, LYD_JSON, LYD_PARSE_ONLY, 0, parent);
if (err)
ERROR("Error, parsing yanger data (%d): %s", err, ly_errmsg(ctx));
free(json);
return err;
}
static const char *xpath_to_yangerd_path(const char *xpath, char *buf, size_t bufsz)
{
const char *start, *slash;
size_t len;
if (!xpath || !*xpath || !strcmp(xpath, "*") || !strcmp(xpath, "/*")) {
buf[0] = '\0';
return buf;
}
start = xpath;
if (*start == '/')
start++;
slash = strchr(start, '/');
len = slash ? (size_t)(slash - start) : strlen(start);
if (len >= bufsz)
len = bufsz - 1;
memcpy(buf, start, len);
buf[len] = '\0';
return buf;
}
static int sr_iface_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)
{
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;
}
err = ly_add_yangerd_data(ctx, parent, "ietf-interfaces:interfaces");
if (err)
ERROR("Error adding interface data (err %d)", err);
sr_release_context(con);
return err ? SR_ERR_INTERNAL : SR_ERR_OK;
}
static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *,
const char *, const char *xpath, uint32_t,
struct lyd_node **parent, void *priv)
{
struct sub *sub = priv;
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
sr_error_t err;
DEBUG("Incoming generic query for xpath: %s -> key %s", xpath, sub->key);
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_yangerd_data(ctx, parent, sub->key);
if (err)
ERROR("Error adding data for %s", sub->key);
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)
{
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_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding OSPF 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)
{
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_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding RIP 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)
{
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_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding BFD 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 sighup_cb(struct ev_loop *, struct ev_signal *w, int)
{
struct statd *statd = w->data;
mdns_ctx_reconnect(&statd->mdns);
}
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));
/*
* Derive the yangerd key from the (static) subscription xpath here,
* once. The generic callback must NOT derive it from the runtime
* request xpath sysrepo hands it -- that is unreliable and yields a
* bare "system-state" for /ietf-system:system-state, which yangerd
* (keyed "ietf-system:system-state") cannot match.
*/
xpath_to_yangerd_path(xpath, sub->key, sizeof(sub->key));
DEBUG("Subscribe to events for \"%s\" (key \"%s\")", xpath, sub->key);
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;
if (subscribe(statd, "ieee1588-ptp-tt", XPATH_PTP_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, sighup_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);
ev_signal_init(&sighup_watcher, sighup_cb, SIGHUP);
sighup_watcher.data = &statd;
ev_signal_start(statd.ev_loop, &sighup_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;
}
if (mdns_ctx_init(&statd.mdns, statd.ev_loop, statd.sr_conn))
INFO("mDNS neighbor monitoring not available");
/* 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");
mdns_ctx_exit(&statd.mdns);
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;
}