From 281adb45b6afe256831ddd9bea304a3378a08ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 31 Mar 2026 11:07:10 +0200 Subject: [PATCH] statd: Include new yangerd backend for operational --- src/statd/Makefile.am | 2 +- src/statd/statd.c | 149 +++++++------------------ src/statd/yangerd.c | 246 ++++++++++++++++++++++++++++++++++++++++++ src/statd/yangerd.h | 28 +++++ 4 files changed, 316 insertions(+), 109 deletions(-) create mode 100644 src/statd/yangerd.c create mode 100644 src/statd/yangerd.h diff --git a/src/statd/Makefile.am b/src/statd/Makefile.am index 727583da..2e6d58d5 100644 --- a/src/statd/Makefile.am +++ b/src/statd/Makefile.am @@ -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) diff --git a/src/statd/statd.c b/src/statd/statd.c index dac05583..77db0b5c 100644 --- a/src/statd/statd.c +++ b/src/statd/statd.c @@ -21,23 +21,16 @@ #include #include #include -#include #include #include #include -#include #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); diff --git a/src/statd/yangerd.c b/src/statd/yangerd.c new file mode 100644 index 00000000..328dff5e --- /dev/null +++ b/src/statd/yangerd.c @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; +} diff --git a/src/statd/yangerd.h b/src/statd/yangerd.h new file mode 100644 index 00000000..e1421a29 --- /dev/null +++ b/src/statd/yangerd.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef STATD_YANGERD_H_ +#define STATD_YANGERD_H_ + +#include + +#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