diff --git a/package/python-statd/Config.in b/package/python-statd/Config.in index 09b609b4..440eff0b 100644 --- a/package/python-statd/Config.in +++ b/package/python-statd/Config.in @@ -1,5 +1,6 @@ config BR2_PACKAGE_PYTHON_STATD bool "python-statd" select BR2_PACKAGE_HOST_PYTHON3 + select BR2_PACKAGE_DBUS_PYTHON help Python helpers for statd. diff --git a/package/python-statd/python-statd.mk b/package/python-statd/python-statd.mk index 2ca3c582..f8508244 100644 --- a/package/python-statd/python-statd.mk +++ b/package/python-statd/python-statd.mk @@ -3,7 +3,7 @@ PYTHON_STATD_SITE_METHOD = local PYTHON_STATD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/statd/python PYTHON_STATD_LICENSE = BSD-3-Clause PYTHON_STATD_LICENSE_FILES = LICENSE -PYTHON_STATD_DEPENDENCIES = host-python3 python3 host-python-poetry-core +PYTHON_STATD_DEPENDENCIES = host-python3 python3 host-python-poetry-core dbus-python PYTHON_STATD_SETUP_TYPE = pep517 # poetry define PYTHON_STATD_MOVE_BINARIES @@ -11,6 +11,7 @@ define PYTHON_STATD_MOVE_BINARIES mv $(TARGET_DIR)/usr/bin/yanger $(TARGET_DIR)/usr/libexec/statd/ mv $(TARGET_DIR)/usr/bin/cli-pretty $(TARGET_DIR)/usr/libexec/statd/ mv $(TARGET_DIR)/usr/bin/ospf-status $(TARGET_DIR)/usr/libexec/statd/ + mv $(TARGET_DIR)/usr/bin/dhcp-server-status $(TARGET_DIR)/usr/libexec/statd/ endef PYTHON_STATD_POST_INSTALL_TARGET_HOOKS += PYTHON_STATD_MOVE_BINARIES $(eval $(python-package)) diff --git a/patches/dnsmasq/2.90/0000-relay-agent-info.patch b/patches/dnsmasq/2.90/0000-relay-agent-info.patch new file mode 100644 index 00000000..f661c0a6 --- /dev/null +++ b/patches/dnsmasq/2.90/0000-relay-agent-info.patch @@ -0,0 +1,190 @@ +commit 9ae2b2a466871d936ab79b15c95ba85cbc7d5cd6 +Author: Stefan Schlosser +Date: Fri Aug 23 10:52:17 2024 +0200 + + dnsmasq: add support for option 82 + + This patch adds support to insert Option 82 Relay Agent Information (see RFC3046) + into relayed DHCP requests. + + When relay has been turned on with --dhcp-relay, the suboptions circuit-id and remote-id + can be configured using directives --dhcp-circuitid and --dhcp-remoteid with a maximum of + 4 bytes denoted in hex format: + + dhcp-circuitid=set:enterprise,00:11:22:33 + dhcp-remoteid=set:enterprise,cc:dd:ee:ff + + If circuit-id is not set, dnsmasq will use the interface index instead. + + These options usually provide a way to map requests to --dhcp-host and --dhcp-range directives + but dhcp-proxy function uses them already for similar purposes. + + The existing option space will be used (no re-negotiation). If it doesn't fit to a packet a warning + + "Not enough space to add relay agent information" + + will be dropped. + + Signed-off-by: Stefan Schlosser + +diff --git dnsmasq-2.90/src/dhcp.c dnsmasq-2.90/src/dhcp.c +index b65facd..4c399eb 100644 +--- dnsmasq-2.90/src/dhcp.c ++++ dnsmasq-2.90/src/dhcp.c +@@ -1117,7 +1117,10 @@ static int relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz) + /* plug in our address */ + mess->giaddr.s_addr = relay->local.addr4.s_addr; + } +- ++ ++ /* add relay agent information */ ++ add_relay_agent_info(relay, mess, sz); ++ + to.sa.sa_family = AF_INET; + to.in.sin_addr = relay->server.addr4; + to.in.sin_port = htons(relay->port); +diff --git dnsmasq-2.90/src/dnsmasq.h dnsmasq-2.90/src/dnsmasq.h +index e455c3f..73cb94e 100644 +--- dnsmasq-2.90/src/dnsmasq.h ++++ dnsmasq-2.90/src/dnsmasq.h +@@ -1902,3 +1902,6 @@ int add_update_server(int flags, + const char *interface, + const char *domain, + union all_addr *local_addr); ++ ++ ++int add_relay_agent_info(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz); +diff --git dnsmasq-2.90/src/rfc2131.c dnsmasq-2.90/src/rfc2131.c +index 68834ea..05a4faa 100644 +--- dnsmasq-2.90/src/rfc2131.c ++++ dnsmasq-2.90/src/rfc2131.c +@@ -2812,4 +2812,129 @@ static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid) + } + } + ++struct relay_info { ++ struct { ++ unsigned char *data; ++ int len; ++ } circuit; ++ struct { ++ unsigned char *data; ++ int len; ++ } remote; ++}; ++ ++static inline int subopt_add(unsigned char *ptr, size_t size, ++ int subopt, unsigned char *buf, int len) ++{ ++ if ((len + 2) > size) ++ { ++ my_syslog(MS_DHCP | LOG_WARNING, "No space for relay sub option %d", subopt); ++ return 0; ++ } ++ ++ *(ptr++) = subopt; ++ *(ptr++) = len; ++ memcpy(ptr, buf, len); ++ ++ return (len + 2); ++} ++ ++static int option_add(struct dhcp_packet *mess, size_t sz, ++ struct relay_info *info) ++{ ++ unsigned char *start = &mess->options[0] + sizeof(u32); ++ unsigned char *end = (unsigned char *)mess + sz; ++ unsigned char *optend = NULL; ++ unsigned char opt[64], *sub, *p; ++ int size, len = 0; ++ int ret = -1; ++ ++ sub = &opt[2]; ++ size = sizeof(opt) - 2; ++ ++ if (info->circuit.len > 0) ++ len += subopt_add(sub+len, size-len, ++ SUBOPT_CIRCUIT_ID, ++ info->circuit.data, ++ info->circuit.len); ++ ++ if (info->remote.len > 0) ++ len += subopt_add(sub+len, size-len, ++ SUBOPT_REMOTE_ID, ++ info->remote.data, ++ info->remote.len); ++ ++ if (len == 0) ++ return 0; /* Nothing to add */ ++ ++ opt[0] = OPTION_AGENT_ID; ++ opt[1] = len; ++ len += 2; ++ ++ /* find option end */ ++ optend = option_find1(start, end, OPTION_END, 1); ++ if (!optend) ++ return -1; ++ ++ *optend = 0; ++ ++ p = dhcp_skip_opts(start); ++ if ((p + len + 1) >= end) ++ { ++ my_syslog(MS_DHCP | LOG_WARNING, "Not enough space to add relay agent information"); ++ goto out; ++ } ++ ++ memcpy(p, opt, len); ++ optend = p + len; ++ ++ ret = 0; ++out: ++ *optend = OPTION_END; ++ return ret; ++} ++ ++int add_relay_agent_info(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz) ++{ ++ struct dhcp_vendor *vendor; ++ struct relay_info info; ++ int ifindex; ++ ++ if (option_find(mess, sz, OPTION_AGENT_ID, 1)) ++ return 0; /* don't alter any existing relay agent info */ ++ ++ /* lookup circuit-id and remote-id */ ++ info.circuit.len = info.remote.len = 0; ++ ++ for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) ++ { ++ if (vendor->match_type == MATCH_CIRCUIT) ++ { ++ if (info.circuit.len == 0) ++ { ++ info.circuit.data = vendor->data; ++ info.circuit.len = vendor->len; ++ } ++ } ++ else if (vendor->match_type == MATCH_REMOTE) ++ { ++ if (info.remote.len == 0) ++ { ++ info.remote.data = vendor->data; ++ info.remote.len = vendor->len; ++ } ++ } ++ } ++ ++ /* use interface index as circuit-id if not specified */ ++ if (info.circuit.len == 0) ++ { ++ ifindex = htonl(relay->iface_index); ++ info.circuit.data = &ifindex; ++ info.circuit.len = sizeof(ifindex); ++ } ++ ++ return option_add(mess, sz, &info); ++} ++ + #endif /* HAVE_DHCP */ diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am index 601340bb..b4b76c2c 100644 --- a/src/confd/src/Makefile.am +++ b/src/confd/src/Makefile.am @@ -41,7 +41,8 @@ confd_plugin_la_SOURCES = \ ietf-syslog.c \ ietf-factory-default.c \ ietf-routing.c \ - infix-dhcp.c \ + infix-dhcp-client.c \ + infix-dhcp-server.c \ infix-factory.c \ infix-meta.c \ infix-services.c \ diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 05577161..ccd2d681 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -146,7 +146,10 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv) rc = infix_containers_init(&confd); if (rc) goto err; - rc = infix_dhcp_init(&confd); + rc = infix_dhcp_client_init(&confd); + if (rc) + goto err; + rc = infix_dhcp_server_init(&confd); if (rc) goto err; rc = infix_factory_init(&confd); diff --git a/src/confd/src/core.h b/src/confd/src/core.h index 14c048ca..fa3fd170 100644 --- a/src/confd/src/core.h +++ b/src/confd/src/core.h @@ -221,8 +221,11 @@ static inline void infix_containers_pre_hook(sr_session_ctx_t *session, struct c static inline void infix_containers_post_hook(sr_session_ctx_t *session, struct confd *confd) {} #endif -/* infix-dhcp.c */ -int infix_dhcp_init(struct confd *confd); +/* infix-dhcp-client.c */ +int infix_dhcp_client_init(struct confd *confd); + +/* infix-dhcp-server.c */ +int infix_dhcp_server_init(struct confd *confd); /* ietf-factory-default */ int ietf_factory_default_init(struct confd *confd); diff --git a/src/confd/src/infix-dhcp.c b/src/confd/src/infix-dhcp-client.c similarity index 95% rename from src/confd/src/infix-dhcp.c rename to src/confd/src/infix-dhcp-client.c index 431a468c..4f6ee22f 100644 --- a/src/confd/src/infix-dhcp.c +++ b/src/confd/src/infix-dhcp-client.c @@ -224,7 +224,7 @@ static void add(const char *ifname, struct lyd_node *cfg) os_name_version(vendor, sizeof(vendor)); - fp = fopenf("w", "/etc/finit.d/available/dhcp-%s.conf", ifname); + fp = fopenf("w", "/etc/finit.d/available/dhcp-client-%s.conf", ifname); if (!fp) { ERROR("failed creating DHCP client %s: %s", ifname, strerror(errno)); goto err; @@ -232,8 +232,8 @@ static void add(const char *ifname, struct lyd_node *cfg) fprintf(fp, "# Generated by Infix confd\n"); fprintf(fp, "metric=%s\n", metric); - fprintf(fp, "service name:dhcp :%s \\\n" - " [2345] udhcpc -f -p /run/dhcp-%s.pid -t 10 -T 3 -A 10 %s -S -R \\\n" + fprintf(fp, "service name:dhcp-client :%s \\\n" + " [2345] udhcpc -f -p /run/dhcp-client-%s.pid -t 10 -T 3 -A 10 %s -S -R \\\n" " -o %s \\\n" " -i %s %s %s \\\n" " -- DHCP client @%s\n", @@ -243,14 +243,14 @@ static void add(const char *ifname, struct lyd_node *cfg) fclose(fp); action = "enable"; err: - systemf("initctl -bfqn %s dhcp-%s", action, ifname); + systemf("initctl -bfqn %s dhcp-client-%s", action, ifname); if (options) free(options); } static void del(const char *ifname) { - systemf("initctl -bfq delete dhcp-%s", ifname); + systemf("initctl -bfq delete dhcp-client-%s", ifname); } static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module, @@ -396,7 +396,7 @@ static int cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module, return SR_ERR_OK; } -int infix_dhcp_init(struct confd *confd) +int infix_dhcp_client_init(struct confd *confd) { int rc; diff --git a/src/confd/src/infix-dhcp-server.c b/src/confd/src/infix-dhcp-server.c new file mode 100644 index 00000000..7afa0d57 --- /dev/null +++ b/src/confd/src/infix-dhcp-server.c @@ -0,0 +1,609 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include + +#include +#include +#include + +#include "core.h" +#include + +#define MODULE "infix-dhcp-server" +#define ROOT_XPATH "/infix-dhcp-server:" +#define CFG_XPATH ROOT_XPATH "dhcp-server" + +#define DBUS_NAME_DNSMASQ "uk.org.thekelleys.dnsmasq" + +#define DEFAULT_LEASETIME 300 /* seconds */ +#define MAX_LEASE_COUNT 1024 /* max. number of leases */ +#define MAX_RELAY_SERVER 2 /* max. number of relay servers */ + + +struct option { + TAILQ_ENTRY(option) list; + int num; + char name[32]; +}; +TAILQ_HEAD(options, option); + +static struct options known_options; + + +static char * strip(char *line) +{ + char *p; + + while (isspace(*line)) + line++; + + p = line + strlen(line) - 1; + while (p >= line) { + if (isspace(*p)) + *(p--) = '\0'; + else + break; + } + + return line; +} + +static int ip_address_is_valid (const char *ifname, const char *str) +{ + struct ifaddrs *ifaddr, *ifa; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct in_addr addr4; + struct in6_addr addr6; + int fam, ret = 0; + + if (inet_pton(AF_INET, str, &addr4) == 1) + fam = AF_INET; + else if (inet_pton(AF_INET6, str, &addr6) == 1) + fam = AF_INET6; + else + return 0; + + if (getifaddrs(&ifaddr) < 0) + return 0; + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr || + strcmp(ifa->ifa_name, ifname) != 0 || + ifa->ifa_addr->sa_family != fam) + continue; + + if (fam == AF_INET) { + sin = (struct sockaddr_in *) ifa->ifa_addr; + if (sin->sin_addr.s_addr == addr4.s_addr) { + ret = 1; + break; + } + } else if (fam == AF_INET6) { + sin6 = (struct sockaddr_in6 *) ifa->ifa_addr; + if (memcmp(sin6->sin6_addr.s6_addr, + addr6.s6_addr, 16) == 0) { + ret = 1; + break; + } + } + } + freeifaddrs(ifaddr); + + return ret; +} + +static void add_known_option(char *line) +{ + char *p, *name, *num; + struct option *opt; + + if (!isdigit(line[0])) + return; + + p = strchr(line, ' '); + if (!p) + return; + + *p = '\0'; + num = line; + name = p + 1; + + opt = malloc(sizeof(struct option)); + if (opt) { + opt->num = atoi(strip(num)); + strncpy(opt->name, strip(name), sizeof(opt->name)); + TAILQ_INSERT_TAIL(&known_options, opt, list); + } +} + +static int load_known_options(void) +{ + char line[256]; + FILE *pp; + + TAILQ_INIT(&known_options); + + /* + * From dnsmasq.8: + * + * --help dhcp will display known DHCPv4 configuration options + * --help dhcp6 will display DHCPv6 options + */ + pp = popen("dnsmasq --help dhcp", "r"); + if (!pp) { + ERROR("Unable to retrieve known options"); + return -1; + } + while (!feof(pp)) { + if (fgets(line, sizeof(line), pp)) + add_known_option(strip(line)); + } + pclose(pp); + + return 0; +} + +static struct option * get_known_option(const char *name) +{ + struct option *opt; + int num = -1; + + if (isdigit(name[0])) + num = atoi(name); + + opt = TAILQ_FIRST(&known_options); + while (opt) { + if (num > 0) { + if (opt->num == num) + return opt; + } else { + if (strcmp(opt->name, name) == 0) + return opt; + } + opt = TAILQ_NEXT(opt, list); + } + + return NULL; +} + +/* configure dnsmasq options */ +static int configure_options(FILE *fp, struct lyd_node *cfg) +{ + struct lyd_node *option; + struct option *opt; + const char *value, *name; + + LYX_LIST_FOR_EACH(lyd_child(cfg), option, "option") { + value = lydx_get_cattr(option, "value"); + name = lydx_get_cattr(option, "name"); + + if (!value || !name) + continue; + + opt = get_known_option(name); + if (!opt) { + ERROR("Unknown option %s", name); + return -1; + } + + fprintf(fp, "dhcp-option=%d,%s\n", opt->num, value); + } + + return 0; +} + +static int configure_host(FILE *fp, struct lyd_node *host, int leasetime) +{ + struct lyd_node *node; + const char *ip, *str, *pfx; + size_t i; + struct { + const char *node; + const char *prefix; + } entries[] = { + { "mac-address", NULL }, + { "hostname", NULL }, + { "client-identifier", "id:" } + }; + + node = lydx_get_child(host, "ip-address"); + if (!node) + return -1; + ip = lyd_get_value(node); + if (!ip || !*ip) + return -1; + + for (i = 0; i < sizeof(entries)/sizeof(entries[0]); i++) { + node = lydx_get_child(host, entries[i].node); + if (!node) + continue; + + str = lyd_get_value(node); + if (str && *str) { + pfx = entries[i].prefix; + + fprintf(fp, "dhcp-host=%s%s,%s,%d\n", + pfx ? pfx : "", + ip, str, leasetime); + return 0; + } + } + + return -1; +} + +static int configure_pool(FILE *fp, struct lyd_node *pool, int leasetime) +{ + struct lyd_node *node, *host; + const char *str, *first, *last; + + if ((str = lydx_get_cattr(pool, "pool-name"))) + fprintf(fp, "# pool %s\n", str); + + if ((str = lydx_get_cattr(pool, "lease-time"))) + leasetime = atoi(str); + + if ((node = lydx_get_child(pool, "first-ip-address"))) + first = lyd_get_value(node); + else + return -1; + + if ((node = lydx_get_child(pool, "last-ip-address"))) + last = lyd_get_value(node); + else + return -1; + + fprintf(fp, "dhcp-range=%s,%s,%d\n", first, last, leasetime); + + LYX_LIST_FOR_EACH(lyd_child(pool), host, "static-allocation") { + if (configure_host(fp, host, leasetime)) + return -1; + } + + return 0; +} + +static int configure_server(FILE *fp, const char *ifname, struct lyd_node *cfg) +{ + struct lyd_node *pool; + int leasetime = DEFAULT_LEASETIME; + const char *str; + + if (configure_options(fp, cfg)) + return -1; + + if ((str = lydx_get_cattr(cfg, "lease-time"))) + leasetime = atoi(str); + + LYX_LIST_FOR_EACH(lyd_child(cfg), pool, "ip-pool") { + fprintf(fp, "\n"); + if (configure_pool(fp, pool, leasetime)) + return -1; + } + + return 0; +} + +static int configure_relay_info (FILE *fp, struct lyd_node *cfg, const char *id) +{ + struct lyd_node *node; + const char *str, *p; + int count = 0; + + /* + * Here we configure identifiers for DHCP option 82 (Relay Agent Information) + * which get inserted into relayed DHCP requests (see RFC3046). + * The DHCP option space is quite limited, thus we only allow 4 bytes for each + * identifier. + * + * If circuit-id is not set, dnsmasq will use the interface index instead. + * + * (see patches/dnsmasq/2.90/0000-relay-agent-info.patch) + */ + + node = lydx_get_child(cfg, id); + if (!node) + return 0; + + str = lyd_get_value(node); + for (p = str; p && *p; p++) + if (*p == ':') count++; + + if (count == 0) + return 0; + + if (count > 3) { + ERROR("Overflow in %s (max. 4 bytes allowed)", id); + return -1; + } + + fprintf(fp, "dhcp-%s=set:enterprise,%s\n", + (strcmp(id, "circuit-id") == 0) ? "circuitid" : "remoteid", + str); + + return 0; +} + +static int configure_relay(FILE *fp, const char *ifname, struct lyd_node *cfg) +{ + struct lyd_node *node; + const char *addr, *str; + int count = 0; + + if ((node = lydx_get_child(cfg, "address"))) + addr = lyd_get_value(node); + + if (!addr || !*addr) { + ERROR("Need relay address"); + return -1; + } + if (!ip_address_is_valid(ifname, addr)) { + ERROR("Invalid relay address"); + return -1; + } + + LYX_LIST_FOR_EACH(lyd_child(cfg), node, "server") { + if (count >= MAX_RELAY_SERVER) { + ERROR("Too many relay servers configured"); + return -1; + } + if ((str = lydx_get_cattr(node, "ip-address"))) { + fprintf(fp, "dhcp-relay=%s,%s\n", addr, str); + count++; + } + } + + if (count == 0) { + ERROR("No relay server configured"); + return -1; + } + + if (configure_relay_info(fp, cfg, "circuit-id") < 0 || + configure_relay_info(fp, cfg, "remote-id") < 0) + return -1; + + return 0; +} + +static int configure_dnsmasq(const char *ifname, struct lyd_node *cfg) +{ + struct lyd_node *node; + const char *mode = NULL; + const char *addr; + FILE *fp = NULL; + int ret; + + fp = fopenf("w", "/var/run/dnsmasq-%s.conf", ifname); + if (!fp) { + ERROR("Failed to create dnsmasq conf for %s: %s", ifname, strerror(errno)); + return -1; + } + + fprintf(fp, "# Generated by Infix confd\n"); + fprintf(fp, "pid-file=/var/run/dnsmasq-%s.pid\n", ifname); + fprintf(fp, "enable-dbus=%s.%s\n", DBUS_NAME_DNSMASQ, ifname); + fprintf(fp, "bind-interfaces\n"); + fprintf(fp, "interface=%s\n", ifname); + fprintf(fp, "except-interface=lo\n"); + fprintf(fp, "dhcp-leasefile=/var/run/dnsmasq-%s.leases\n", ifname); + fprintf(fp, "dhcp-lease-max=%d\n", MAX_LEASE_COUNT); + + if (debug) + fprintf(fp, "log-dhcp\n"); + + if ((node = lydx_get_child(cfg, "address"))) { + addr = lyd_get_value(node); + if (!ip_address_is_valid(ifname, addr)) { + ERROR("Invalid server address"); + return -1; + } + fprintf(fp, "listen-address=%s\n", addr); + } + + if (!lydx_is_enabled(cfg, "dns-enabled")) { + fprintf(fp, "port=0\n"); + fprintf(fp, "no-poll\n"); + } + fprintf(fp, "\n"); + + if ((node = lydx_get_child(cfg, "mode"))) + mode = lyd_get_value(node); + + if (mode && strcmp(mode, "relay") == 0) { + INFO("Configuring DHCP relay on %s", ifname); + ret = configure_relay(fp, ifname, + lydx_get_child(cfg, "relay")); + } else { + INFO("Configuring DHCP server on %s", ifname); + ret = configure_server(fp, ifname, + lydx_get_child(cfg, "server")); + } + + fclose(fp); + + if (ret != 0) + fremove("/var/run/dnsmasq-%s.conf", ifname); + + return ret; +} + +static int configure_finit(const char *ifname) +{ + FILE *fp; + + fp = fopenf("w", "/etc/finit.d/available/dhcp-server-%s.conf", ifname); + if (!fp) { + ERROR("Failed to create finit conf for %s: %s", ifname, strerror(errno)); + return -1; + } + + fprintf(fp, "# Generated by Infix confd\n"); + fprintf(fp, "service name:dhcp-server :%s \\\n" + " [2345] dnsmasq -k -u root -C /var/run/dnsmasq-%s.conf \\\n" + " -- DHCP server @%s\n", + ifname, ifname, ifname, ifname); + fclose(fp); + + return 0; +} + +static int configure_dbus(const char *ifname) +{ + FILE *fp; + + if (fexistf("/etc/dbus-1/system.d/dnsmasq-%s.conf", ifname)) { + return 0; + } + fp = fopenf("w", "/etc/dbus-1/system.d/dnsmasq-%s.conf", ifname); + if (!fp) { + ERROR("Failed to create dbus conf for %s: %s", ifname, strerror(errno)); + return -1; + } + fprintf(fp, "\n"); + fprintf(fp, "\n"); + fprintf(fp, " \n"); + fprintf(fp, " \n", DBUS_NAME_DNSMASQ, ifname); + fprintf(fp, " \n", DBUS_NAME_DNSMASQ, ifname); + fprintf(fp, " \n"); + fprintf(fp, " \n"); + fprintf(fp, " \n", DBUS_NAME_DNSMASQ, ifname); + fprintf(fp, " \n", DBUS_NAME_DNSMASQ, ifname); + fprintf(fp, " \n"); + fprintf(fp, "\n"); + fclose(fp); + + return 0; +} + +static void add(const char *ifname, struct lyd_node *cfg) +{ + const char *action = "disable"; + + if (configure_dnsmasq(ifname, cfg)) + goto out; + + if (configure_finit(ifname)) + goto out; + + if (configure_dbus(ifname)) + goto out; + + action = "enable"; +out: + systemf("initctl -bfqn %s dhcp-server-%s", action, ifname); +} + +static void del(const char *ifname) +{ + systemf("initctl -bfq delete dhcp-server-%s", ifname); +} + +static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module, + const char *xpath, sr_event_t event, unsigned request_id, void *_confd) +{ + struct lyd_node *global, *diff, *cifs, *difs, *cif, *dif; + const char *ifname, *cifname; + sr_error_t err = 0; + sr_data_t *cfg; + int enabled = 0; + + switch (event) { + case SR_EV_DONE: + break; + case SR_EV_CHANGE: + case SR_EV_ABORT: + default: + return SR_ERR_OK; + } + + err = sr_get_data(session, CFG_XPATH "//.", 0, 0, 0, &cfg); + if (err || !cfg) + goto err_abandon; + + err = srx_get_diff(session, &diff); + if (err) + goto err_release_data; + + global = lydx_get_descendant(cfg->tree, "dhcp-server", NULL); + enabled = lydx_is_enabled(global, "enabled"); + + cifs = lydx_get_descendant(cfg->tree, "dhcp-server", "server-if", NULL); + difs = lydx_get_descendant(diff, "dhcp-server", "server-if", NULL); + + /* find the modified one, delete or recreate only that */ + LYX_LIST_FOR_EACH(difs, dif, "server-if") { + ifname = lydx_get_cattr(dif, "if-name"); + if (!ifname) + continue; + + if (lydx_get_op(dif) == LYDX_OP_DELETE) { + del(ifname); + continue; + } + + LYX_LIST_FOR_EACH(cifs, cif, "server-if") { + cifname = lydx_get_cattr(cif, "if-name"); + if (!cifname || strcmp(ifname, cifname)) + continue; + + if (!enabled || !lydx_is_enabled(cif, "enabled")) + del(ifname); + else + add(ifname, cif); + break; + } + } + + if (!enabled) { + LYX_LIST_FOR_EACH(cifs, cif, "server-if") { + ifname = lydx_get_cattr(cif, "if-name"); + if (ifname) { + INFO("DHCP server globally disabled, stopping server on %s", ifname); + del(ifname); + } + } + } + + lyd_free_tree(diff); +err_release_data: + sr_release_data(cfg); +err_abandon: + return err; +} + +static int cleanstats(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath, + const sr_val_t *input, const size_t input_cnt, sr_event_t event, + unsigned request_id, sr_val_t **output, size_t *output_cnt, void *priv) +{ + int rc; + + if (input_cnt < 1) { + ERROR("Not enough input parameters"); + return SR_ERR_SYS; + } + + rc = systemf("/usr/libexec/statd/dhcp-server-status --clean %s", + input[0].data.string_val); + + return (rc == 0) ? SR_ERR_OK : SR_ERR_SYS; +} + +int infix_dhcp_server_init(struct confd *confd) +{ + int rc; + + rc = load_known_options(); + if (rc) + goto fail; + + REGISTER_CHANGE(confd->session, MODULE, CFG_XPATH, 0, change, confd, &confd->sub); + REGISTER_RPC(confd->session, ROOT_XPATH "clean-statistics", cleanstats, NULL, &confd->sub); + + return SR_ERR_OK; +fail: + ERROR("init failed: %s", sr_strerror(rc)); + return rc; +} diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 93a67cdb..19567ee7 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -31,6 +31,7 @@ MODULES=( "ieee802-dot1ab-lldp@2022-03-15.yang" "infix-lldp@2025-01-08.yang" "infix-dhcp-client@2024-09-20.yang" + "infix-dhcp-server@2024-08-18.yang" "infix-meta@2024-10-18.yang" "infix-system@2024-11-27.yang" "infix-services@2024-12-03.yang" diff --git a/src/confd/yang/infix-dhcp-client.yang b/src/confd/yang/infix-dhcp-client.yang index 9d59be45..f5749f93 100644 --- a/src/confd/yang/infix-dhcp-client.yang +++ b/src/confd/yang/infix-dhcp-client.yang @@ -37,7 +37,7 @@ module infix-dhcp-client { description "This type is used for selecting route preference (distance)."; } - typedef dhcp-options { + typedef dhcp-client-options { type union { type string; type enumeration { @@ -140,7 +140,7 @@ module infix-dhcp-client { client to only get IP address and default route, set this to: 'subnet router'"; leaf name { - type dhcp-options; + type dhcp-client-options; description "DHCP option to request from, or inform server of."; } leaf value { diff --git a/src/confd/yang/infix-dhcp-server@2024-08-18.yang b/src/confd/yang/infix-dhcp-server@2024-08-18.yang index e0d77b7b..a0071b4b 100644 --- a/src/confd/yang/infix-dhcp-server@2024-08-18.yang +++ b/src/confd/yang/infix-dhcp-server@2024-08-18.yang @@ -9,6 +9,9 @@ module infix-dhcp-server { import ietf-interfaces { prefix if; } + import ietf-ip { + prefix ip; + } import ietf-yang-types { prefix yang; } @@ -18,185 +21,308 @@ module infix-dhcp-server { description "YANG model for configuring a DHCPv4 server"; revision 2024-08-18 { - description "Initial revision adapted for Infix from DHCPv6 model."; - reference "internal"; + description "Initial revision."; + reference "rfc2131 rfc7950"; } - typedef dhcp-options { + typedef dhcp-server-options { type union { type string; type enumeration { - enum log-server { - value 7; - description "Name or IPv address of remote syslog server."; - } enum router { value 3; - description "Default route(s)"; + description "Default router."; } - enum dns { + enum dns-server { value 6; - description "DNS server"; + description "DNS server."; } - enum hostname { - value 12; - description "Hostname"; - } - enum domain { + enum domain-name { value 15; - description "Domain name"; + description "Domain name."; } - enum ntpsrv { + enum mtu { + value 26; + description "Interface MTU."; + } + enum ntp-server { value 42; - description "NTP server"; + description "NTP server."; } - enum search { - value 119; - description "Domain search list"; + enum netbios-ns { + value 44; + description "NETBIOS name server."; } - enum staticroutes { - value 121; - description "Classless static routes"; + enum netbios-nodetype { + value 46; + description "NETBIOS node type."; + } + enum netbios-scope { + value 47; + description "NETBIOS scope."; } } } - description "Supported DHCP client request options"; - } - - typedef dhcp-lease-time { - type uint32 { - range "120..max"; - } - units "seconds"; - default "3600"; // 1 hour - description "The lease time in seconds, with a minimum of 2 minutes (120 seconds)."; - } - - typedef infinite-lease-time { - type union { - type dhcp-lease-time; - type enumeration { - enum infinite { - description "Indicates an infinite lease time."; - } - } - } - description "The lease time for a static binding, with an option for infinite lease time."; + description "Supported DHCP server options."; } container dhcp-server { - description "Top-level container for DHCP server configuration."; + description "DHCP server configuration."; - leaf enable { - description "Enable or disable the DHCP server."; + leaf enabled { type boolean; + default "true"; + description "Globally enables the DHCP server function."; } - list option { - key "name"; - description "List of global DHCP options to be applied as defaults for all pools."; + list server-if { + key "if-name"; + description "List of interfaces configuring a DHCP server."; - leaf name { - description "DHCP option name."; - type dhcp-options; - } - - leaf value { - description "Option value."; - type string; - } - } - - list pool { - key "name"; - description "List of address pools managed by the DHCP server."; - - leaf name { - description "The name of the address pool."; - type string; - } - - leaf start-address { - description "The start address of the DHCP address pool."; - type inet:ipv4-address; - } - - leaf end-address { - description "The end address of the DHCP address pool."; - type inet:ipv4-address; - } - - leaf subnet-mask { - description "Subnet mask provided to clients in this pool."; - type inet:ipv4-address; - } - - leaf lease-time { - description "Lease time for DHCP addresses."; - type dhcp-lease-time; - } - - leaf interface { - description "Optional interface to which this pool is bound."; + leaf if-name { type if:interface-ref; + mandatory true; + description "Name of the interface."; + } + leaf enabled { + type boolean; + default "true"; + description "Enable DHCP server for this interface."; + } + leaf dns-enabled { + type boolean; + default "true"; + description "Enable DNS service."; + } + leaf mode { + description "Operating mode of the DHCP server."; + default "server"; + + type enumeration { + enum server { + description "DHCP server operates as server."; + } + enum relay { + description "DHCP server operates as relay."; + } + } } - list option { - key "name"; - description "List of DHCP options specific to this pool."; + container server { + leaf address { + type inet:ip-address; + description "IP address of the server to listen on (optional). + If not set, the server will listen to all local + addresses configured on the interface."; + } + leaf lease-time { + type uint32 { + range "180..31536000"; + } + units "seconds"; + default 3600; + description "Default network address lease time assigned to DHCP clients."; + } + list option { + key "name"; + description "List of DHCP options to offer."; - leaf name { - description "DHCP option name."; - type dhcp-options; + leaf name { + type dhcp-server-options; + description "DHCP option to offer."; + } + leaf value { + type string; + description "Optional value, only used for non-flag options. + Example: option:dns, value:1.2.3.4 + option:vendor, value:01:02:03:04:05:06:07:08:09:0a + option:domain, value:example.com"; + } } - leaf value { - description "Option value."; - type string; + list ip-pool { + key "pool-name"; + description "IP pool configuration."; + + leaf pool-name { + type string { + length "1..64"; + } + description "Name of the IP pool."; + } + leaf lease-time { + type uint32 { + range "180..31536000"; + } + units "seconds"; + default 3600; + description "Network address lease time assigned to DHCP clients."; + } + leaf first-ip-address { + type inet:ip-address; + mandatory "true"; + description "First IP address of the pool."; + } + leaf last-ip-address { + type inet:ip-address; + mandatory "true"; + description "Last IP address of the pool."; + } + list static-allocation { + key "ip-address"; + description "List of host using static address allocation."; + + leaf ip-address { + type inet:ip-address; + description "IP address of the host."; + } + choice identifier { + description "The identifier for the host."; + + case mac-address { + leaf mac-address { + type yang:mac-address; + description "Client MAC address of the host."; + } + } + case hostname { + leaf hostname { + type yang:mac-address; + description "Client hostname (DHCP option 12) of the host."; + } + } + case client-identifier { + leaf client-identifier { + type yang:mac-address; + description "Client identifier (DHCP option 61) of the host."; + } + } + } + } + } + container lease { + description "Lease information."; + config "false"; + + leaf host-count { + type uint32; + description "Number of host leases."; + } + list host { + description "List of active host leases."; + key ip-address; + + leaf ip-address { + type inet:ip-address; + description "IP address of the host."; + } + leaf hardware-address { + type string; + description "MAC address of the host."; + } + leaf hostname { + type string; + description "Name of the host."; + } + leaf expires { + type yang:date-and-time; + description "Time and date when the lease expires."; + } + leaf client-identifier { + type string; + description "Client identifier of the host."; + } + } + } + } + + container relay { + leaf address { + type inet:ip-address; + description "IP address of the server to listen on. + If not set, the server will listen to all local + addresses configured on the interface."; + } + list server { + key "ip-address"; + description "List of addresses to which the server will relay to."; + + leaf ip-address { + type inet:ip-address; + description "IP address of the relay server."; + } + } + leaf circuit-id { + type yang:hex-string { + length "2..11"; + } + description "Relay Agent Circuit Identifier + to be inserted as DHCP option 82 when relaying requests to the DHCP server. + If not set, the interface index will be used."; + } + leaf remote-id { + type yang:hex-string { + length "2..11"; + } + description "Relay Agent Remote Identifier + to be inserted as DHCP option 82 when relaying requests to the DHCP server."; + } + } + + container packet-statistics { + description "DHCP packet statistics."; + config "false"; + + container sent { + description "The packets sent by the server."; + + leaf offer-count { + type uint32; + description "Total number of DHCPOFFER packets."; + } + leaf ack-count { + type uint32; + description "Total number of DHCPACK packets."; + } + leaf nack-count { + type uint32; + description "Total number of DHCPNAK packets."; + } + } + container received { + description "The packets sent by the client."; + + leaf decline-count { + type uint32; + description "Total number of DHCPDECLINE packets."; + } + leaf discover-count { + type uint32; + description "Total number of DHCPDISCOVER packets."; + } + leaf request-count { + type uint32; + description "Total number of DHCPREQUEST packets."; + } + leaf release-count { + type uint32; + description "Total number of DHCPRELEASE packets."; + } + leaf inform-count { + type uint32; + description "Total number of DHCPINFORM packets."; + } } } } - - list host { - key "identifier"; - description "List of static bindings based on MAC address, client identifier, or other identifiers."; - - leaf identifier { - description "The MAC address, clientid, or other identifier of the client."; - type union { - type yang:mac-address; - type string; // clientid or other identifiers - } - } - - leaf ip-address { - description "The IPv4 address to assign to the client."; - type inet:ipv4-address; - } - - leaf hostname { - description "Optional hostname to assign with the lease."; - type string; - } - - leaf lease-time { - description "Specific lease time for this static binding, including the option for infinite lease time."; - type infinite-lease-time; - } - - list option { - key "name"; - description "List of DHCP options specific to this static host."; - - leaf name { - description "DHCP option name."; - type dhcp-options; - } - - leaf value { - description "Option value."; - type string; - } + } + rpc clean-statistics { + description "Clear packet statistics."; + input { + leaf if-name { + type if:interface-ref; + mandatory true; + description "Name of the interface."; } } } diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index 7191cbe4..caf75792 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -223,6 +223,13 @@ + + + + /infix-dhcp-server:clean-statistics + + + @@ -296,6 +303,21 @@ + + + sysrepocfg -f json -X -d operational -m infix-dhcp-server | \ + /usr/libexec/statd/cli-pretty "show-dhcp-server" + + + + + sysrepocfg -f json -X -d operational -m infix-dhcp-server | \ + jq -C . + + + + + diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 25e5e92e..7fca71d0 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -57,6 +57,12 @@ class PadSoftware: state = 10 version = 23 +class PadDhcpServer: + iface = 7 + ip = 17 + mac = 19 + host = 21 + exp = 7 class PadUsbPort: title = 30 @@ -350,6 +356,34 @@ class STPPortID: ) return f"{prio:1x}.{pid:03x}" +class DhcpServer: + def __init__(self, data): + self.data = data + self.iface = get_json_data('', self.data, 'if-name') + self.leases = [] + now = datetime.now(timezone.utc) + for lease in get_json_data([], self.data, 'server', 'lease', 'host'): + dt = datetime.strptime(lease["expires"], "%Y-%m-%dT%H:%M:%S%z") + exp = (dt - now).total_seconds() + self.leases.append({ + "ip": lease["ip-address"], + "mac": lease["hardware-address"], + "host": lease["hostname"], + "exp": int(exp) + }) + + def print(self): + for lease in self.leases: + ip = lease["ip"] + mac = lease["mac"] + exp = "%ds" % lease["exp"] + host = lease["host"][:20] + row = f"{self.iface:<{PadDhcpServer.iface}}" + row += f"{ip:<{PadDhcpServer.ip}}" + row += f"{mac:<{PadDhcpServer.mac}}" + row += f"{host:<{PadDhcpServer.host}}" + row += f"{exp:<{PadDhcpServer.exp}}" + print(row) class Iface: def __init__(self, data): @@ -1038,6 +1072,23 @@ def show_ntp(json): row += f"{source['poll']:>{PadNtpSource.poll}}" print(row) +def show_dhcp_server(json): + if not json.get("infix-dhcp-server:dhcp-server"): + print("DHCP server not enabled.") + return + + hdr = (f"{'IFACE':<{PadDhcpServer.iface}}" + f"{'IP':<{PadDhcpServer.ip}}" + f"{'MAC':<{PadDhcpServer.mac}}" + f"{'HOSTNAME':<{PadDhcpServer.host}}" + f"{'EXPIRES':<{PadDhcpServer.exp}}") + print(Decore.invert(hdr)) + + servers = get_json_data({}, json, "infix-dhcp-server:dhcp-server", "server-if") + for s in servers: + server = DhcpServer(s) + server.print() + def main(): global UNIT_TEST @@ -1074,6 +1125,8 @@ def main(): parser_show_boot_order = subparsers.add_parser('show-boot-order', help='Show NTP sources') + parser_show_routing_table = subparsers.add_parser('show-dhcp-server', help='Show DHCP server') + args = parser.parse_args() UNIT_TEST = args.test @@ -1091,6 +1144,8 @@ def main(): show_hardware(json_data) elif args.command == "show-ntp": show_ntp(json_data) + elif args.command == "show-dhcp-server": + show_dhcp_server(json_data) else: print(f"Error, unknown command '{args.command}'") sys.exit(1) diff --git a/src/statd/python/dhcp_server_status/__init__.py b/src/statd/python/dhcp_server_status/__init__.py new file mode 100644 index 00000000..b2567bee --- /dev/null +++ b/src/statd/python/dhcp_server_status/__init__.py @@ -0,0 +1,4 @@ +from .dhcp_server_status import main + +if __name__ == "__main__": + main() diff --git a/src/statd/python/dhcp_server_status/dhcp_server_status.py b/src/statd/python/dhcp_server_status/dhcp_server_status.py new file mode 100755 index 00000000..2c61353e --- /dev/null +++ b/src/statd/python/dhcp_server_status/dhcp_server_status.py @@ -0,0 +1,83 @@ +#!/usr/bin/python3 +# +# This script is used to query dnsmasq daemons via dbus and +# fills/cleans the infix-dhcp-server packet statistics. +# + +import argparse +import json +import dbus +import re + + +DBUS_NAME = "org.freedesktop.DBus" +DBUS_OBJECT = "/org/freedesktop/DBus" +DBUS_IFACE = "org.freedesktop.DBus" + +DNSMASQ_NAME = "uk.org.thekelleys.dnsmasq" +DNSMASQ_IFACE = "uk.org.thekelleys.dnsmasq" +DNSMASQ_OBJECT = "/uk/org/thekelleys/dnsmasq" + + +def get_servers(bus): + try: + remote_object = bus.get_object(DBUS_NAME, DBUS_OBJECT) + iface = dbus.Interface(remote_object, DBUS_IFACE) + except dbus.DBusException: + return [] + + servers = [] + for name in iface.ListNames(): + r = re.search(r"%s.(\w+)$" % DNSMASQ_NAME, name) + if r: + server = { + "ifc": r.group(1), + "name": str(name) + } + servers.append(server) + + return servers + + +def get_iface(bus, name): + try: + remote_object = bus.get_object(name, DNSMASQ_OBJECT) + iface = dbus.Interface(remote_object, DNSMASQ_IFACE) + except dbus.DBusException: + return None + finally: + return iface + + +def main(): + bus = dbus.SystemBus() + servers = get_servers(bus) + + parser = argparse.ArgumentParser(prog='dhcp-server-status') + parser.add_argument("-c", "--clean", help="DHCP server interface") + args = parser.parse_args() + + if args.clean: + for server in servers: + if server["ifc"] != args.clean: + continue + iface = get_iface(bus, server["name"]) + if not iface: + continue + print("Cleaning metrics for DHCP server on %s" % server["ifc"]) + iface.ClearMetrics() + else: + data = [] + for server in servers: + iface = get_iface(bus, server["name"]) + if not iface: + continue + data.append({ + "if-name": server["ifc"], + "metrics": iface.GetMetrics() + }) + print(json.dumps(data)) + + +if __name__ == "__main__": + main() diff --git a/src/statd/python/pyproject.toml b/src/statd/python/pyproject.toml index 8f33838a..dc4e0e45 100644 --- a/src/statd/python/pyproject.toml +++ b/src/statd/python/pyproject.toml @@ -6,7 +6,8 @@ license = "MIT" packages = [ { include = "yanger" }, { include = "cli_pretty" }, - { include = "ospf_status" } + { include = "ospf_status" }, + { include = "dhcp_server_status" } ] authors = [ "KernelKit developers" @@ -21,3 +22,4 @@ build-backend = "poetry.core.masonry.api" yanger = "yanger.__main__:main" cli-pretty = "cli_pretty:main" ospf-status = "ospf_status:main" +dhcp-server-status = "dhcp_server_status:main" diff --git a/src/statd/python/yanger/__main__.py b/src/statd/python/yanger/__main__.py index 6e6c8d16..8c898c29 100644 --- a/src/statd/python/yanger/__main__.py +++ b/src/statd/python/yanger/__main__.py @@ -70,6 +70,9 @@ def main(): elif args.model == 'infix-containers': from . import infix_containers yang_data = infix_containers.operational() + elif args.model == 'infix-dhcp-server': + from . import infix_dhcp_server + yang_data = infix_dhcp_server.operational() elif args.model == 'ietf-system': from . import ietf_system yang_data = ietf_system.operational() diff --git a/src/statd/python/yanger/infix_dhcp_server.py b/src/statd/python/yanger/infix_dhcp_server.py new file mode 100644 index 00000000..41c22f0b --- /dev/null +++ b/src/statd/python/yanger/infix_dhcp_server.py @@ -0,0 +1,79 @@ +from datetime import datetime +from .host import HOST + + +def lease_info(ifname): + """Populate DHCP leases table""" + leases = f"/var/run/dnsmasq-{ifname}.leases" + hosts = [] + try: + with open(leases, 'r', encoding='utf-8') as fd: + for line in fd: + tokens = line.strip().split(" ") + if len(tokens) != 5: + continue + + host = { + "ip-address": tokens[2], + "hardware-address": tokens[1], + } + dt = datetime.utcfromtimestamp(int(tokens[0])) + host["expires"] = dt.isoformat() + "+00:00" + + if tokens[3] != '*': + host["hostname"] = tokens[3] + if tokens[4] != '*': + host["client-identifier"] = tokens[4] + + hosts.append(host) + except (IOError, OSError, ValueError): + pass + + return { + "host-count": len(hosts), + "host": hosts + } + + +def status(servers): + """Populate DHCP server status""" + + data = HOST.run_json(['/usr/libexec/statd/dhcp-server-status'], default=[]) + if data == []: + return + + for entry in data: + metrics = entry["metrics"] + + servers.append({ + "if-name": entry["if-name"], + "packet-statistics": { + "sent": { + "offer-count": metrics["dhcp_offer"], + "ack-count": metrics["dhcp_ack"], + "nak-count": metrics["dhcp_nak"] + }, + "received": { + "decline-count": metrics["dhcp_decline"], + "discover-count": metrics["dhcp_discover"], + "request-count": metrics["dhcp_request"], + "release-count": metrics["dhcp_release"], + "inform-count": metrics["dhcp_inform"] + } + }, + "server": { + "lease": lease_info(entry["if-name"]) + } + }) + + +def operational(): + """Return operational status for DHCP server""" + out = { + "infix-dhcp-server:dhcp-server": { + "server-if": [] + } + } + status(out['infix-dhcp-server:dhcp-server']['server-if']) + + return out diff --git a/src/statd/statd.c b/src/statd/statd.c index 3c194e6c..062ffeaf 100644 --- a/src/statd/statd.c +++ b/src/statd/statd.c @@ -40,6 +40,7 @@ #define XPATH_SYSTEM_BASE "/ietf-system:system-state" #define XPATH_ROUTING_OSPF XPATH_ROUTING_BASE "/ospf" #define XPATH_CONTAIN_BASE "/infix-containers:containers" +#define XPATH_DHCP_SERVER_BASE "/infix-dhcp-server:dhcp-server" TAILQ_HEAD(sub_head, sub); @@ -347,8 +348,10 @@ static int subscribe_to_all(struct statd *statd) if (subscribe(statd, "infix-containers", XPATH_CONTAIN_BASE, sr_generic_cb)) return SR_ERR_INTERNAL; #endif - INFO("Successfully subscribed to all models"); + if (subscribe(statd, "infix-dhcp-server", XPATH_DHCP_SERVER_BASE, sr_generic_cb)) + return SR_ERR_INTERNAL; + INFO("Successfully subscribed to all models"); return SR_ERR_OK; }