statd: Include new yangerd backend for operational

This commit is contained in:
Mattias Walström
2026-06-24 15:53:02 +02:00
parent 442d7428b6
commit 01fc6aaa05
4 changed files with 316 additions and 109 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ DISTCLEANFILES = *~ *.d
ACLOCAL_AMFLAGS = -I m4
sbin_PROGRAMS = statd
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h avahi.c avahi.h
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h avahi.c avahi.h yangerd.c yangerd.h
statd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE
statd_CFLAGS = -W -Wall -Wextra
statd_CFLAGS += $(jansson_CFLAGS) $(libyang_CFLAGS) $(sysrepo_CFLAGS)
+41 -108
View File
@@ -21,23 +21,16 @@
#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"
#include "avahi.h"
#include "yangerd.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"
@@ -74,98 +67,61 @@ struct statd {
struct mdns_ctx mdns; /* mDNS neighbor monitor */
};
static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent,
char *yanger_args[])
static int ly_add_yangerd_data(const struct ly_ctx *ctx, struct lyd_node **parent,
const char *path)
{
FILE *stream;
char *json = NULL;
size_t len = 0;
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);
err = yangerd_query(path, &json, &len);
if (err) {
ERROR("Error, running yanger");
fclose(stream);
free(json);
ERROR("yangerd: query failed for %s", path);
return SR_ERR_SYS;
}
fflush(stream);
if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
ERROR("Error, unable reset stream (seek)");
fclose(stream);
return SR_ERR_SYS;
}
err = lyd_parse_data_fd(ctx, fd, LYD_JSON, LYD_PARSE_ONLY, 0, parent);
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));
fclose(stream);
/* Note: fclose() already closes the underlying fd from fdopen() */
free(json);
return err;
}
static char *xpath_extract(const char *xpath, const char *key)
static const char *xpath_to_yangerd_path(const char *xpath, char *buf, size_t bufsz)
{
char *res = NULL;
const char *ptr;
const char *end;
const char *start, *slash;
size_t len;
/* (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 (!xpath || !*xpath || !strcmp(xpath, "*") || !strcmp(xpath, "/*")) {
buf[0] = '\0';
return buf;
}
if ((end - ptr) >= XPATH_MAX) {
ERROR("Value for %s is to long (sanity check)", key);
return NULL;
}
start = xpath;
if (*start == '/')
start++;
res = calloc((end - ptr) + 1, sizeof(char));
if (!res)
return NULL;
slash = strchr(start, '/');
len = slash ? (size_t)(slash - start) : strlen(start);
strncpy(res, ptr, end - ptr);
res[end - ptr] = '\0';
if (len >= bufsz)
len = bufsz - 1;
return res;
memcpy(buf, start, len);
buf[len] = '\0';
return buf;
}
static int sr_iface_cb(sr_session_ctx_t *session, uint32_t, const char *model,
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)
{
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;
@@ -184,29 +140,20 @@ static int sr_iface_cb(sr_session_ctx_t *session, uint32_t, const char *model,
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);
err = ly_add_yangerd_data(ctx, parent, "ietf-interfaces:interfaces");
if (err)
ERROR("Error adding interface yanger data");
ERROR("Error adding interface data");
sr_release_context(con);
return SR_ERR_OK;
}
static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *model,
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, __attribute__((unused)) void *priv)
{
char *yanger_args[5] = {
YANGER_BINPATH,
(char *)model,
NULL
};
char yangerd_path[XPATH_MAX];
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
sr_error_t err;
@@ -225,9 +172,10 @@ static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *model,
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
xpath_to_yangerd_path(xpath, yangerd_path, sizeof(yangerd_path));
err = ly_add_yangerd_data(ctx, parent, yangerd_path);
if (err)
ERROR("Error adding yanger data");
ERROR("Error adding data for %s", yangerd_path);
sr_release_context(con);
@@ -238,11 +186,6 @@ 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;
@@ -261,9 +204,9 @@ static int sr_ospf_cb(sr_session_ctx_t *session, uint32_t, const char *,
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
err = ly_add_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding yanger data");
ERROR("Error adding OSPF data");
sr_release_context(con);
@@ -274,11 +217,6 @@ 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;
@@ -297,9 +235,9 @@ static int sr_rip_cb(sr_session_ctx_t *session, uint32_t, const char *,
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
err = ly_add_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding yanger data");
ERROR("Error adding RIP data");
sr_release_context(con);
@@ -310,11 +248,6 @@ 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;
@@ -333,9 +266,9 @@ static int sr_bfd_cb(sr_session_ctx_t *session, uint32_t, const char *,
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
err = ly_add_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding yanger data");
ERROR("Error adding BFD data");
sr_release_context(con);
+246
View File
@@ -0,0 +1,246 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <jansson.h>
#include <srx/common.h>
#include "yangerd.h"
static const char *yangerd_socket_path(void)
{
const char *env;
env = getenv("YANGERD_SOCKET");
if (env && *env)
return env;
return YANGERD_SOCKET_DEFAULT;
}
static int yangerd_connect(void)
{
struct sockaddr_un addr = { .sun_family = AF_UNIX };
struct timeval tv = { .tv_sec = YANGERD_TIMEOUT_SEC };
const char *path;
int fd;
path = yangerd_socket_path();
if (strlen(path) >= sizeof(addr.sun_path)) {
ERROR("yangerd socket path too long: %s", path);
return -1;
}
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd < 0) {
ERROR("yangerd: socket(): %s", strerror(errno));
return -1;
}
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
DEBUG("yangerd: connect(%s): %s", path, strerror(errno));
close(fd);
return -1;
}
return fd;
}
static int yangerd_write_all(int fd, const void *buf, size_t len)
{
const unsigned char *p = buf;
while (len > 0) {
ssize_t n = write(fd, p, len);
if (n < 0) {
if (errno == EINTR)
continue;
return -1;
}
p += n;
len -= n;
}
return 0;
}
static int yangerd_read_all(int fd, void *buf, size_t len)
{
unsigned char *p = buf;
while (len > 0) {
ssize_t n = read(fd, p, len);
if (n < 0) {
if (errno == EINTR)
continue;
return -1;
}
if (n == 0) {
errno = ECONNRESET;
return -1;
}
p += n;
len -= n;
}
return 0;
}
static int yangerd_send_request(int fd, const char *path)
{
json_t *req;
char *json_str;
size_t json_len;
unsigned char hdr[5];
int rc = -1;
req = json_pack("{s:s, s:s}", "method", "get", "path", path);
if (!req)
return -1;
json_str = json_dumps(req, JSON_COMPACT);
json_decref(req);
if (!json_str)
return -1;
json_len = strlen(json_str);
if (json_len > YANGERD_MAX_PAYLOAD) {
free(json_str);
return -1;
}
hdr[0] = YANGERD_PROTO_VERSION;
hdr[1] = (json_len >> 24) & 0xff;
hdr[2] = (json_len >> 16) & 0xff;
hdr[3] = (json_len >> 8) & 0xff;
hdr[4] = (json_len >> 0) & 0xff;
if (yangerd_write_all(fd, hdr, sizeof(hdr)) < 0)
goto out;
if (yangerd_write_all(fd, json_str, json_len) < 0)
goto out;
rc = 0;
out:
free(json_str);
return rc;
}
static int yangerd_recv_response(int fd, char **buf, size_t *len)
{
unsigned char hdr[5];
uint32_t payload_len;
json_error_t jerr;
json_t *resp;
json_t *status;
json_t *data;
char *body;
char *data_str;
*buf = NULL;
*len = 0;
if (yangerd_read_all(fd, hdr, sizeof(hdr)) < 0)
return -1;
if (hdr[0] != YANGERD_PROTO_VERSION) {
ERROR("yangerd: protocol version mismatch: got %u, want %u",
hdr[0], YANGERD_PROTO_VERSION);
return -1;
}
payload_len = ((uint32_t)hdr[1] << 24) |
((uint32_t)hdr[2] << 16) |
((uint32_t)hdr[3] << 8) |
((uint32_t)hdr[4]);
if (payload_len > YANGERD_MAX_PAYLOAD) {
ERROR("yangerd: payload too large: %u", payload_len);
return -1;
}
body = malloc(payload_len + 1);
if (!body)
return -1;
if (yangerd_read_all(fd, body, payload_len) < 0) {
free(body);
return -1;
}
body[payload_len] = '\0';
resp = json_loads(body, 0, &jerr);
free(body);
if (!resp) {
ERROR("yangerd: invalid response JSON: %s", jerr.text);
return -1;
}
status = json_object_get(resp, "status");
if (!json_is_string(status) || strcmp(json_string_value(status), "ok")) {
json_t *msg = json_object_get(resp, "message");
ERROR("yangerd: request failed: %s",
json_is_string(msg) ? json_string_value(msg) : "unknown");
json_decref(resp);
return -1;
}
data = json_object_get(resp, "data");
if (!data || json_is_null(data)) {
json_decref(resp);
*buf = strdup("{}");
*len = 2;
return 0;
}
data_str = json_dumps(data, JSON_COMPACT);
json_decref(resp);
if (!data_str)
return -1;
*buf = data_str;
*len = strlen(data_str);
return 0;
}
int yangerd_query(const char *path, char **buf, size_t *len)
{
int fd;
int rc;
*buf = NULL;
*len = 0;
fd = yangerd_connect();
if (fd < 0)
return -1;
if (yangerd_send_request(fd, path) < 0) {
ERROR("yangerd: failed sending request for %s", path);
close(fd);
return -1;
}
rc = yangerd_recv_response(fd, buf, len);
if (rc < 0)
ERROR("yangerd: failed reading response for %s", path);
close(fd);
return rc;
}
+28
View File
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef STATD_YANGERD_H_
#define STATD_YANGERD_H_
#include <stddef.h>
#define YANGERD_SOCKET_DEFAULT "/run/yangerd.sock"
#define YANGERD_TIMEOUT_SEC 5
#define YANGERD_MAX_PAYLOAD (4 << 20) /* 4 MiB, matches Go side */
#define YANGERD_PROTO_VERSION 0x01
/**
* yangerd_query() - Query yangerd daemon for operational YANG data
* @path: YANG model path, e.g. "ietf-interfaces:interfaces"
* @buf: Output pointer to malloc'd JSON string (caller must free)
* @len: Output length of JSON data
*
* Connects to the yangerd Unix socket, sends a "get" request for @path,
* reads the framed response, and extracts the "data" field as a JSON
* string. The socket path defaults to %YANGERD_SOCKET_DEFAULT but can
* be overridden with the YANGERD_SOCKET environment variable.
*
* Return: 0 on success, -1 on error (buf is set to NULL).
*/
int yangerd_query(const char *path, char **buf, size_t *len);
#endif