mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-06 15:43:02 +02:00
confd: reduce dhcp-server to minimal scope
- dnsmasq:
- allow listening to all interfaces again to serve DHCP leases
- disable most of the default DHCP options, keep broadcast and
netmask because when dnsmasq can infer these they are better
than breaking networking for clients -- note, they can still
be overridden from global/subnet/host scope -- and for relay
setups they need to be supplied anyway
- confd:
- drop per-interface dnsmasq, although practical for many use-cases,
having a per-subnet focused server means improved integration with
future stand-alone dhcp relay setup
- implement suggested, simplified, yang model
Fixes #703
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -3,16 +3,19 @@
|
||||
# and similar events feed servers and configuration to dnsmasq.
|
||||
domain-needed
|
||||
|
||||
# Only listen to loopback (local system)
|
||||
interface=lo
|
||||
bind-dynamic
|
||||
#listen-address=127.0.0.1,::1
|
||||
|
||||
# Allow configuration and cache clear over D-Bus
|
||||
enable-dbus
|
||||
|
||||
# Disable the following dnsmasq default DHCP options
|
||||
#dhcp-option=option:netmask
|
||||
#dhcp-option=28 # option:broadcast
|
||||
#dhcp-option=option:domain-name
|
||||
dhcp-option=option:router
|
||||
dhcp-option=option:dns-server
|
||||
dhcp-option=12 # option:hostname
|
||||
|
||||
# Generated by openresolv
|
||||
resolv-file=/var/lib/misc/resolv.conf
|
||||
|
||||
# Include all files in a directory which end in .conf
|
||||
conf-dir=/etc/dnsmasq.d/,*.conf
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ 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))
|
||||
|
||||
@@ -1 +1 @@
|
||||
service [S12345] <pid/syslogd> dnsmasq -k -u root -- DHCP/DNS proxy
|
||||
service [S12345] <!pid/syslogd> dnsmasq -k -u root -- DHCP/DNS proxy
|
||||
|
||||
+328
-391
@@ -10,28 +10,31 @@
|
||||
#include "core.h"
|
||||
#include <ifaddrs.h>
|
||||
|
||||
#define MODULE "infix-dhcp-server"
|
||||
#define ROOT_XPATH "/infix-dhcp-server:"
|
||||
#define CFG_XPATH ROOT_XPATH "dhcp-server"
|
||||
#define MODULE "infix-dhcp-server"
|
||||
#define ROOT_XPATH "/infix-dhcp-server:"
|
||||
#define CFG_XPATH ROOT_XPATH "dhcp-server"
|
||||
|
||||
#define DNSMASQ_GLOBAL_OPTS "/etc/dnsmasq.d/global-opts.conf"
|
||||
#define DNSMASQ_SUBNET_FMT "/etc/dnsmasq.d/%s.conf"
|
||||
#define DNSMASQ_LEASES "/var/lib/misc/dnsmasq.leases"
|
||||
#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 */
|
||||
#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_ENTRY(option) list;
|
||||
int num;
|
||||
char name[32];
|
||||
};
|
||||
TAILQ_HEAD(options, option);
|
||||
|
||||
static struct options known_options;
|
||||
|
||||
|
||||
static char * strip(char *line)
|
||||
static char *strip(char *line)
|
||||
{
|
||||
char *p;
|
||||
|
||||
@@ -49,49 +52,6 @@ static char * strip(char *line)
|
||||
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)
|
||||
{
|
||||
@@ -117,34 +77,7 @@ static void add_known_option(char *line)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
static struct option *get_known_option(const char *name)
|
||||
{
|
||||
struct option *opt;
|
||||
int num = -1;
|
||||
@@ -167,348 +100,302 @@ static struct option * get_known_option(const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* configure dnsmasq options */
|
||||
static int configure_options(FILE *fp, struct lyd_node *cfg)
|
||||
static int load_known_options(void)
|
||||
{
|
||||
struct lyd_node *option;
|
||||
struct option *opt;
|
||||
const char *value, *name;
|
||||
char line[256];
|
||||
FILE *pp;
|
||||
|
||||
LYX_LIST_FOR_EACH(lyd_child(cfg), option, "option") {
|
||||
value = lydx_get_cattr(option, "value");
|
||||
name = lydx_get_cattr(option, "name");
|
||||
TAILQ_INIT(&known_options);
|
||||
|
||||
if (!value || !name)
|
||||
continue;
|
||||
/*
|
||||
* 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 dnsmasq DHCP options");
|
||||
return -1;
|
||||
}
|
||||
while (!feof(pp)) {
|
||||
if (fgets(line, sizeof(line), pp))
|
||||
add_known_option(strip(line));
|
||||
}
|
||||
pclose(pp);
|
||||
|
||||
opt = get_known_option(name);
|
||||
if (!opt) {
|
||||
ERROR("Unknown option %s", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fprintf(fp, "dhcp-option=%d,%s\n", opt->num, value);
|
||||
if (!get_known_option("hostname")) {
|
||||
snprintf(line, sizeof(line), "12 hostname\n");
|
||||
add_known_option(strip(line));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int configure_host(FILE *fp, struct lyd_node *host, int leasetime)
|
||||
static const char *subnet_tag(const char *subnet)
|
||||
{
|
||||
unsigned int a, b, c, d, m;
|
||||
static char tag[32];
|
||||
|
||||
sscanf(subnet, "%u.%u.%u.%u/%u", &a, &b, &c, &d, &m);
|
||||
|
||||
if (m == 8) /* Class A */
|
||||
snprintf(tag, sizeof(tag), "net-%u", a);
|
||||
else if (m == 16) /* Class B */
|
||||
snprintf(tag, sizeof(tag), "net-%u-%u", a, b);
|
||||
else if (m == 24) /* Class C */
|
||||
snprintf(tag, sizeof(tag), "net-%u-%u-%u", a, b, c);
|
||||
else {
|
||||
/* Non-classful, use full format */
|
||||
snprintf(tag, sizeof(tag), "net-%u-%u-%u-%u-%u",
|
||||
a, b, c, d, m);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
static const char *host_tag(const char *subnet, const char *addr)
|
||||
{
|
||||
unsigned int a, b, c, d;
|
||||
static char tag[128];
|
||||
|
||||
sscanf(addr, "%u.%u.%u.%u", &a, &b, &c, &d);
|
||||
|
||||
if (!subnet)
|
||||
snprintf(tag, sizeof(tag), "host-%u-%u-%u-%u", a, b, c, d);
|
||||
else
|
||||
snprintf(tag, sizeof(tag), "%s-host-%u-%u-%u-%u", subnet, a, b, c, d);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
/* configure dnsmasq options */
|
||||
static int configure_options(FILE *fp, struct lyd_node *cfg, const char *tag)
|
||||
{
|
||||
struct lyd_node *option;
|
||||
|
||||
LYX_LIST_FOR_EACH(lyd_child(cfg), option, "option") {
|
||||
const char *id = lydx_get_cattr(option, "id");
|
||||
struct lyd_node *suboption;
|
||||
const struct option *opt;
|
||||
const char *val;
|
||||
|
||||
opt = get_known_option(id);
|
||||
if (!opt) {
|
||||
ERROR("Unknown option %s", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* val is validated by the yang model for each of the various
|
||||
* attributes 'address', 'name', and 'string'. in the future
|
||||
* we may add 'value' as well, for hexadecimal data
|
||||
*/
|
||||
val = lydx_get_cattr(option, "name")
|
||||
?: lydx_get_cattr(option, "string")
|
||||
?: NULL;
|
||||
if (!val) {
|
||||
val = lydx_get_cattr(option, "address");
|
||||
if (val && !strcmp(val, "auto"))
|
||||
val = "0.0.0.0";
|
||||
}
|
||||
|
||||
if (val) {
|
||||
fprintf(fp, "dhcp-option=%s%s%s%d,%s\n",
|
||||
tag ? "tag:" : "", tag ?: "",
|
||||
tag ? "," : "", opt->num, val);
|
||||
} else if ((suboption = lydx_get_descendant(option, "option", "static-route", NULL))) {
|
||||
struct lyd_node *net;
|
||||
|
||||
LYX_LIST_FOR_EACH(suboption, net, "static-route") {
|
||||
fprintf(fp, "dhcp-option=%s%s%s%d,%s,%s\n",
|
||||
tag ? "tag:" : "",
|
||||
tag ?: "", tag ? "," : "", opt->num,
|
||||
lydx_get_cattr(net, "destination"),
|
||||
lydx_get_cattr(net, "next-hop"));
|
||||
}
|
||||
} else {
|
||||
ERROR("Unknown value to option %s", id);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *host_match(struct lyd_node *match, const char **id)
|
||||
{
|
||||
struct lyd_node *node;
|
||||
const char *ip, *str, *pfx;
|
||||
size_t i;
|
||||
struct {
|
||||
const char *node;
|
||||
const char *key;
|
||||
const char *prefix;
|
||||
} entries[] = {
|
||||
{ "mac-address", NULL },
|
||||
{ "hostname", NULL },
|
||||
{ "client-identifier", "id:" }
|
||||
} choice[] = {
|
||||
{ "mac-address", NULL },
|
||||
{ "hostname", NULL },
|
||||
{ "client-id", "id:" }
|
||||
};
|
||||
|
||||
node = lydx_get_child(host, "ip-address");
|
||||
if (!node)
|
||||
return -1;
|
||||
ip = lyd_get_value(node);
|
||||
if (!ip || !*ip)
|
||||
return -1;
|
||||
if (!match)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < sizeof(entries)/sizeof(entries[0]); i++) {
|
||||
node = lydx_get_child(host, entries[i].node);
|
||||
for (size_t i = 0; i < NELEMS(choice); i++) {
|
||||
struct lyd_node *node;
|
||||
|
||||
node = lydx_get_child(match, choice[i].key);
|
||||
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;
|
||||
}
|
||||
*id = choice[i].prefix;
|
||||
return lyd_get_value(node);
|
||||
}
|
||||
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int configure_pool(FILE *fp, struct lyd_node *pool, int leasetime)
|
||||
/* the address is a key in the host node, so is guaranteed to be set */
|
||||
static int configure_host(FILE *fp, struct lyd_node *host, const char *subnet)
|
||||
{
|
||||
struct lyd_node *node, *host;
|
||||
const char *str, *first, *last;
|
||||
const char *ip = lydx_get_cattr(host, "address");
|
||||
const char *tag = host_tag(NULL, ip);
|
||||
const char *match, *id, *name;
|
||||
|
||||
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
|
||||
match = host_match(lydx_get_child(host, "match"), &id);
|
||||
if (!match)
|
||||
return -1;
|
||||
|
||||
if ((node = lydx_get_child(pool, "last-ip-address")))
|
||||
last = lyd_get_value(node);
|
||||
else
|
||||
fprintf(fp, "\n# Host specific options\n");
|
||||
if (configure_options(fp, host, tag))
|
||||
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;
|
||||
name = lydx_get_cattr(host, "hostname");
|
||||
|
||||
/*
|
||||
* 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)
|
||||
* set host-specific tag, allow options from that tag,
|
||||
* also allow options from subnet
|
||||
*/
|
||||
|
||||
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);
|
||||
fprintf(fp, "\n# Host %s specific options\n", ip);
|
||||
fprintf(fp, "dhcp-host=%s%s,set:%s,set:%s,%s,%s%s%s\n",
|
||||
id ? id : "", match, tag, subnet, ip,
|
||||
name ?: "", name ? "," : "",
|
||||
lydx_get_cattr(host, "lease-time"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int configure_relay(FILE *fp, const char *ifname, struct lyd_node *cfg)
|
||||
static void add(const char *subnet, struct lyd_node *cfg)
|
||||
{
|
||||
const char *tag = subnet_tag(subnet);
|
||||
const char *val, *ifname;
|
||||
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;
|
||||
int rc = 0;
|
||||
|
||||
fp = fopenf("w", "/var/run/dnsmasq-%s.conf", ifname);
|
||||
fp = fopenf("w", DNSMASQ_SUBNET_FMT, tag);
|
||||
if (!fp) {
|
||||
ERROR("Failed to create dnsmasq conf for %s: %s", ifname, strerror(errno));
|
||||
return -1;
|
||||
ERROR("Failed creating dnsmasq conf for %s: %s", subnet, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
val = lydx_get_cattr(cfg, "description");
|
||||
fprintf(fp, "# Subnet %s%s%s\n", subnet, val ? " - " : "", val ?: "");
|
||||
|
||||
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);
|
||||
fprintf(fp, "\n# Common options for this subnet\n");
|
||||
rc = configure_options(fp, cfg, tag);
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
LYX_LIST_FOR_EACH(lyd_child(cfg), node, "host") {
|
||||
if ((rc = configure_host(fp, node, tag)))
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!lydx_is_enabled(cfg, "dns-enabled")) {
|
||||
fprintf(fp, "port=0\n");
|
||||
fprintf(fp, "no-poll\n");
|
||||
/* Optional, may be used to limit scope of subnet */
|
||||
ifname = lydx_get_cattr(cfg, "if-name");
|
||||
|
||||
if ((node = lydx_get_child(cfg, "pool"))) {
|
||||
const char *start, *end;
|
||||
|
||||
start = lydx_get_cattr(node, "start-address");
|
||||
end = lydx_get_cattr(node, "end-address");
|
||||
|
||||
fprintf(fp, "\n# Subnet pool %s - %s\n", start, end);
|
||||
fprintf(fp, "dhcp-range=%s%sset:%s,%s,%s,%s\n",
|
||||
ifname ?: "", ifname ? "," : "", tag,
|
||||
start, end, lydx_get_cattr(node, "lease-time"));
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
|
||||
if ((node = lydx_get_child(cfg, "mode")))
|
||||
mode = lyd_get_value(node);
|
||||
err:
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
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"));
|
||||
static void del(const char *subnet, struct lyd_node *cfg)
|
||||
{
|
||||
struct in_addr subnet_addr;
|
||||
FILE *fp, *next;
|
||||
int prefix_len;
|
||||
char line[512];
|
||||
|
||||
systemf("initctl -nbq stop dnsmasq");
|
||||
fremove(DNSMASQ_SUBNET_FMT, subnet_tag(subnet));
|
||||
|
||||
/* Parse subnet/prefix */
|
||||
if (sscanf(subnet, "%15[^/]/%d", line, &prefix_len) != 2)
|
||||
goto parse_err;
|
||||
if (inet_pton(AF_INET, line, &subnet_addr) != 1) {
|
||||
parse_err:
|
||||
ERRNO("Failed parsing DHCP server subnet %s for deletion", subnet);
|
||||
return;
|
||||
}
|
||||
|
||||
fp = fopen(DNSMASQ_LEASES, "r");
|
||||
if (!fp)
|
||||
return; /* Nothing to do here */
|
||||
|
||||
/* Create temp file for new leases */
|
||||
next = fopen(DNSMASQ_LEASES"+", "w");
|
||||
if (!next) {
|
||||
ERRNO("Failed creating new leases file %s", DNSMASQ_LEASES"+");
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Copy non-matching leases */
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
char mac[18], ip[16], name[64];
|
||||
struct in_addr lease_addr;
|
||||
unsigned int lease_time;
|
||||
uint32_t subnet_mask;
|
||||
|
||||
if (sscanf(line, "%u %17s %15s %63s", &lease_time, mac, ip, name) < 3)
|
||||
continue;
|
||||
|
||||
/* Check if IP is in subnet */
|
||||
if (inet_pton(AF_INET, ip, &lease_addr) != 1)
|
||||
continue;
|
||||
|
||||
subnet_mask = htonl(~((1UL << (32 - prefix_len)) - 1));
|
||||
if ((lease_addr.s_addr & subnet_mask) == (subnet_addr.s_addr & subnet_mask))
|
||||
continue; /* Skip matching lease */
|
||||
|
||||
fputs(line, next);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
fclose(next);
|
||||
|
||||
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 <net/%s/running> \\\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, "<!DOCTYPE busconfig PUBLIC\n");
|
||||
fprintf(fp, " \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\"\n");
|
||||
fprintf(fp, " \"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">\n");
|
||||
fprintf(fp, "<busconfig>\n");
|
||||
fprintf(fp, " <policy user=\"root\">\n");
|
||||
fprintf(fp, " <allow own=\"%s.%s\"/>\n", DBUS_NAME_DNSMASQ, ifname);
|
||||
fprintf(fp, " <allow send_destination=\"%s.%s\"/>\n", DBUS_NAME_DNSMASQ, ifname);
|
||||
fprintf(fp, " </policy>\n");
|
||||
fprintf(fp, " <policy context=\"default\">\n");
|
||||
fprintf(fp, " <deny own=\"%s.%s\"/>\n", DBUS_NAME_DNSMASQ, ifname);
|
||||
fprintf(fp, " <deny send_destination=\"%s.%s\"/>\n", DBUS_NAME_DNSMASQ, ifname);
|
||||
fprintf(fp, " </policy>\n");
|
||||
fprintf(fp, "</busconfig>\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);
|
||||
/* Replace old leases file */
|
||||
rename(DNSMASQ_LEASES"+", DNSMASQ_LEASES);
|
||||
}
|
||||
|
||||
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;
|
||||
int enabled = 0, added = 0, deleted = 0;
|
||||
sr_error_t err = 0;
|
||||
sr_data_t *cfg;
|
||||
int enabled = 0;
|
||||
|
||||
switch (event) {
|
||||
case SR_EV_DONE:
|
||||
@@ -519,7 +406,7 @@ static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
err = sr_get_data(session, CFG_XPATH "//.", 0, 0, 0, &cfg);
|
||||
err = sr_get_data(session, "//.", 0, 0, 0, &cfg);
|
||||
if (err || !cfg)
|
||||
goto err_abandon;
|
||||
|
||||
@@ -530,65 +417,114 @@ static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module
|
||||
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);
|
||||
cifs = lydx_get_descendant(cfg->tree, "dhcp-server", "subnet", NULL);
|
||||
difs = lydx_get_descendant(diff, "dhcp-server", "subnet", 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;
|
||||
LYX_LIST_FOR_EACH(difs, dif, "subnet") {
|
||||
const char *subnet = lydx_get_cattr(dif, "subnet");
|
||||
|
||||
if (lydx_get_op(dif) == LYDX_OP_DELETE) {
|
||||
del(ifname);
|
||||
del(subnet, dif), deleted++;
|
||||
continue;
|
||||
}
|
||||
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "server-if") {
|
||||
cifname = lydx_get_cattr(cif, "if-name");
|
||||
if (!cifname || strcmp(ifname, cifname))
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "subnet") {
|
||||
const char *cnet = lydx_get_cattr(cif, "subnet");
|
||||
|
||||
if (strcmp(subnet, cnet))
|
||||
continue;
|
||||
|
||||
if (!enabled || !lydx_is_enabled(cif, "enabled"))
|
||||
del(ifname);
|
||||
del(subnet, cif), deleted++;
|
||||
else
|
||||
add(ifname, cif);
|
||||
add(subnet, cif), added++;
|
||||
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);
|
||||
}
|
||||
if (enabled) {
|
||||
struct lyd_node *node;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(DNSMASQ_GLOBAL_OPTS, "w");
|
||||
if (!fp)
|
||||
goto err_done;
|
||||
|
||||
node = lydx_get_xpathf(cfg->tree, "/ietf-system:system/hostname");
|
||||
if (node) {
|
||||
const char *hostname = lyd_get_value(node);
|
||||
const char *ptr = hostname ? strchr(hostname, '.') : NULL;
|
||||
|
||||
if (ptr)
|
||||
fprintf(fp, "domain = %s\n", ++ptr);
|
||||
}
|
||||
|
||||
err = configure_options(fp, global, NULL);
|
||||
fclose(fp);
|
||||
if (err)
|
||||
goto err_done;
|
||||
} else {
|
||||
system("initctl -nbq stop dnsmasq"), deleted++;
|
||||
erase(DNSMASQ_LEASES);
|
||||
erase(DNSMASQ_GLOBAL_OPTS);
|
||||
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "subnet") {
|
||||
const char *subnet = lydx_get_cattr(cif, "subnet");
|
||||
|
||||
INFO("DHCP server globally disabled, stopping server on %s", subnet);
|
||||
del(subnet, cif);
|
||||
}
|
||||
}
|
||||
|
||||
err_done:
|
||||
if (deleted)
|
||||
system("initctl -nbq restart dnsmasq");
|
||||
else
|
||||
system("initctl -nbq touch dnsmasq");
|
||||
|
||||
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)
|
||||
static int cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
|
||||
const char *path, sr_event_t event, unsigned request_id, void *priv)
|
||||
{
|
||||
int rc;
|
||||
const char *fmt = "/infix-dhcp-server:dhcp-server/option[id='%s']/address";
|
||||
sr_val_t inferred = { .type = SR_STRING_T };
|
||||
const char *opt[] = {
|
||||
"router",
|
||||
"dns-server",
|
||||
};
|
||||
size_t i, cnt = 0;
|
||||
|
||||
if (input_cnt < 1) {
|
||||
ERROR("Not enough input parameters");
|
||||
return SR_ERR_SYS;
|
||||
if (event != SR_EV_UPDATE && event != SR_EV_CHANGE)
|
||||
return 0;
|
||||
|
||||
if (srx_nitems(session, &cnt, "/infix-dhcp-server:dhcp-server/option") || cnt)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < NELEMS(opt); i++) {
|
||||
inferred.data.string_val = "auto";
|
||||
srx_set_item(session, &inferred, 0, fmt, opt[i]);
|
||||
}
|
||||
|
||||
rc = systemf("/usr/libexec/statd/dhcp-server-status --clean %s",
|
||||
input[0].data.string_val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (rc == 0) ? SR_ERR_OK : SR_ERR_SYS;
|
||||
static int clear_stats(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)
|
||||
{
|
||||
if (systemf("dbus-send --system --dest=uk.org.thekelleys.dnsmasq "
|
||||
"/uk/org/thekelleys/dnsmasq uk.org.thekelleys.dnsmasq.ClearMetrics"))
|
||||
return SR_ERR_SYS;
|
||||
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
int infix_dhcp_server_init(struct confd *confd)
|
||||
@@ -600,7 +536,8 @@ int infix_dhcp_server_init(struct confd *confd)
|
||||
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);
|
||||
REGISTER_CHANGE(confd->cand, MODULE, CFG_XPATH"//.", SR_SUBSCR_UPDATE, cand, confd, &confd->sub);
|
||||
REGISTER_RPC(confd->session, CFG_XPATH "/statistics/clear", clear_stats, NULL, &confd->sub);
|
||||
|
||||
return SR_ERR_OK;
|
||||
fail:
|
||||
|
||||
@@ -31,7 +31,7 @@ MODULES=(
|
||||
"ieee802-dot1ab-lldp@2022-03-15.yang"
|
||||
"infix-lldp@2025-01-08.yang"
|
||||
"infix-dhcp-client@2025-01-20.yang"
|
||||
"infix-dhcp-server@2024-08-18.yang"
|
||||
"infix-dhcp-server@2025-01-13.yang"
|
||||
"infix-meta@2024-10-18.yang"
|
||||
"infix-system@2024-11-27.yang"
|
||||
"infix-services@2024-12-03.yang"
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
module infix-dhcp-server {
|
||||
yang-version 1.1;
|
||||
namespace "urn:infix:params:xml:ns:yang:infix-dhcp-server";
|
||||
prefix dhc4-srv;
|
||||
|
||||
import ietf-inet-types {
|
||||
prefix inet;
|
||||
}
|
||||
import ietf-interfaces {
|
||||
prefix if;
|
||||
}
|
||||
import ietf-yang-types {
|
||||
prefix yang;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "YANG model for configuring a DHCPv4 server";
|
||||
|
||||
revision 2025-01-13 {
|
||||
description "Second revision based on core team review.";
|
||||
reference "internal";
|
||||
}
|
||||
revision 2024-08-18 {
|
||||
description "Initial revision adapted for Infix from DHCPv6 model.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
typedef octet-string {
|
||||
description "A generic string or a hex value if prefixed with 'id:'.";
|
||||
type union {
|
||||
type string {
|
||||
pattern 'id:([0-9a-fA-F]{2}:)*[0-9a-fA-F]{2}';
|
||||
}
|
||||
type string;
|
||||
}
|
||||
}
|
||||
|
||||
typedef dhcp-server-options {
|
||||
description "Supported DHCP server options.";
|
||||
type union {
|
||||
type uint8 {
|
||||
range "1..254";
|
||||
}
|
||||
type enumeration {
|
||||
enum netmask {
|
||||
value 1;
|
||||
description "IP subnet mask as per RFC 950
|
||||
|
||||
Note: this option is always sent using a netmask derived from an
|
||||
interface matching the given subnet, even if no configuraiton
|
||||
exists in global/subnet/host scope. Except in the case when the
|
||||
server respods to a relayed request, in which case the subnet will
|
||||
not match any local interface.";
|
||||
}
|
||||
enum router {
|
||||
value 3;
|
||||
description "Default route(s)";
|
||||
}
|
||||
enum dns-server {
|
||||
value 6;
|
||||
description "DNS server";
|
||||
}
|
||||
enum log-server {
|
||||
value 7;
|
||||
description "Name or IP address of remote syslog server";
|
||||
}
|
||||
enum hostname {
|
||||
value 12;
|
||||
description "Hostname";
|
||||
}
|
||||
enum domain {
|
||||
value 15;
|
||||
description "Domain name";
|
||||
}
|
||||
enum broadcast {
|
||||
value 28;
|
||||
description "IP broadcast address as per RFC 1122
|
||||
|
||||
Note: this option is always sent using a broadcast address from
|
||||
an interface matching the given subnet, even if no configuraiton
|
||||
exists in global/subnet/host scope. Except in the case when the
|
||||
server respods to a relayed request, in which case the subnet will
|
||||
not match any local interface.";
|
||||
}
|
||||
enum ntp-server {
|
||||
value 42;
|
||||
description "NTP server";
|
||||
}
|
||||
enum domain-search {
|
||||
value 119;
|
||||
description "Domain search list";
|
||||
}
|
||||
enum classless-static-route {
|
||||
value 121;
|
||||
description "Classless static routes";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef dhcp-lease-time {
|
||||
description "The lease time in seconds, with a minimum of 2 minutes (120 seconds).
|
||||
For static host entries, the value 'infinite' may be used.";
|
||||
type union {
|
||||
type uint32 {
|
||||
range "120..max";
|
||||
}
|
||||
type enumeration {
|
||||
enum infinite {
|
||||
description "Infinite lease time for static host entries.";
|
||||
}
|
||||
}
|
||||
}
|
||||
units "seconds";
|
||||
default "3600";
|
||||
}
|
||||
|
||||
grouping dhcp-option-group {
|
||||
description "Generic list structure for DHCP options.";
|
||||
|
||||
list option {
|
||||
description "List of DHCP options.";
|
||||
key "id";
|
||||
|
||||
leaf id {
|
||||
description "DHCP option ID or number.";
|
||||
type dhcp-server-options;
|
||||
}
|
||||
|
||||
choice value {
|
||||
case address-opt {
|
||||
when "id = 'router' or id = 'dns-server' or id = 'log-server' or id = 'ntp-server' or id = 'netmask' or id = 'broadcast'";
|
||||
leaf address {
|
||||
description "IP address, or 'auto'.";
|
||||
type union {
|
||||
type inet:ipv4-address;
|
||||
type enumeration {
|
||||
enum auto {
|
||||
description "Use IP address of the DHCP server.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case name-opt {
|
||||
when "id = 'hostname' or id = 'domain' or id = 'search'";
|
||||
leaf name {
|
||||
description "Internet name or FQDN.";
|
||||
type inet:domain-name;
|
||||
}
|
||||
}
|
||||
|
||||
case classless-route-opt {
|
||||
/* Support Microsoft's Classless Static Route option as well */
|
||||
when "id = 'classless-static-route' or id = 121 or id = 249";
|
||||
list static-route {
|
||||
key "destination";
|
||||
leaf destination {
|
||||
description "Destination network prefix.";
|
||||
type inet:ipv4-prefix;
|
||||
}
|
||||
leaf next-hop {
|
||||
description "Next-hop IP address.";
|
||||
type inet:ipv4-address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case default-opt {
|
||||
leaf string {
|
||||
description "Generic string value.";
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container dhcp-server {
|
||||
description "DHCPv4 server configuration.";
|
||||
|
||||
leaf enabled {
|
||||
description "Globally enable or disable the DHCP server.";
|
||||
type boolean;
|
||||
default true;
|
||||
}
|
||||
|
||||
uses dhcp-option-group {
|
||||
refine "option" {
|
||||
description "Global DHCP options used as defaults for pools and static host leases.";
|
||||
}
|
||||
}
|
||||
|
||||
list subnet {
|
||||
description "Subnet specific settings, including static host entries.";
|
||||
key "subnet";
|
||||
|
||||
leaf subnet {
|
||||
description "Subnet to serve DHCP leases from.";
|
||||
type inet:ipv4-prefix;
|
||||
}
|
||||
|
||||
leaf description {
|
||||
description "Additional information about this subnet (e.g., purpose, location, or notes).";
|
||||
type string;
|
||||
}
|
||||
|
||||
leaf enabled {
|
||||
description "Enable or disable DHCP server on this subnet.";
|
||||
type boolean;
|
||||
default true;
|
||||
}
|
||||
|
||||
leaf if-name {
|
||||
description "Optional interface to bind this subnet to.";
|
||||
type if:interface-ref;
|
||||
}
|
||||
|
||||
uses dhcp-option-group {
|
||||
refine "option" {
|
||||
description "List of DHCP options specific to this subnet.";
|
||||
}
|
||||
}
|
||||
|
||||
container pool {
|
||||
description "IP address pool for this subnet.";
|
||||
|
||||
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 lease-time {
|
||||
description "Lease time for DHCP addresses.";
|
||||
type dhcp-lease-time;
|
||||
}
|
||||
}
|
||||
|
||||
list host {
|
||||
description "List of static host entries.";
|
||||
key "address";
|
||||
|
||||
leaf address {
|
||||
description "IPv4 address to assign to the client.";
|
||||
type inet:ipv4-address;
|
||||
mandatory true;
|
||||
}
|
||||
|
||||
leaf description {
|
||||
description "Additional information about this entry, e.g., owner's name, equipment details, or location.";
|
||||
type string;
|
||||
}
|
||||
|
||||
container match {
|
||||
description "Match rule for this lease.";
|
||||
choice match {
|
||||
mandatory true;
|
||||
|
||||
case mac-address {
|
||||
leaf mac-address {
|
||||
description "Match on client MAC address";
|
||||
type yang:mac-address;
|
||||
}
|
||||
}
|
||||
|
||||
case hostname {
|
||||
leaf hostname {
|
||||
description "Match on client hostname, DHCP option 12.";
|
||||
type string;
|
||||
}
|
||||
}
|
||||
|
||||
case client-id {
|
||||
leaf client-id {
|
||||
description "Match on client-id (string 'abc' or hex 'id:c0:ff:ee', DHCP option 61.";
|
||||
type union {
|
||||
type yang:mac-address;
|
||||
type octet-string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* case option82 here ... */
|
||||
}
|
||||
/* More advanced match rules, e.g., option82 + client-id are not supported. */
|
||||
}
|
||||
|
||||
leaf hostname {
|
||||
description "Optional hostname to assign with the lease.
|
||||
|
||||
Please note, Linux systems only allow 64 character hostnames.";
|
||||
type inet:domain-name;
|
||||
}
|
||||
|
||||
leaf lease-time {
|
||||
description "Lease time, or 'infinite'.";
|
||||
type dhcp-lease-time;
|
||||
}
|
||||
|
||||
uses dhcp-option-group {
|
||||
refine "option" {
|
||||
description "List of DHCP options specific to this static host.
|
||||
|
||||
By default, global and subnet options are inherited.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container leases {
|
||||
description "Lease information.";
|
||||
config false;
|
||||
|
||||
list lease {
|
||||
description "List of active host leases.";
|
||||
key address;
|
||||
|
||||
leaf address {
|
||||
description "IP address of the host.";
|
||||
type inet:ip-address;
|
||||
}
|
||||
|
||||
leaf phys-address {
|
||||
description "Physical (MAC) address of the host.";
|
||||
type yang:mac-address;
|
||||
}
|
||||
|
||||
leaf hostname {
|
||||
description "Name of the host, if one was provided.";
|
||||
type string;
|
||||
}
|
||||
|
||||
leaf expires {
|
||||
description "Time and date when the lease expires, or 'never'.
|
||||
|
||||
The value 'never' is used for infinite lease time, i.e., no expiration time.";
|
||||
type union {
|
||||
type yang:date-and-time;
|
||||
type enumeration {
|
||||
enum never;
|
||||
}
|
||||
}
|
||||
}
|
||||
leaf client-id {
|
||||
description "Client identifier of the host, if one was provided.";
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container statistics {
|
||||
description "DHCP packet statistics.";
|
||||
config false;
|
||||
|
||||
container sent {
|
||||
description "The packets sent by the server.";
|
||||
|
||||
leaf offer-count {
|
||||
type yang:counter32;
|
||||
description "Total number of DHCPOFFER packets.";
|
||||
}
|
||||
leaf ack-count {
|
||||
type yang:counter32;
|
||||
description "Total number of DHCPACK packets.";
|
||||
}
|
||||
leaf nak-count {
|
||||
type yang:counter32;
|
||||
description "Total number of DHCPNAK packets.";
|
||||
}
|
||||
}
|
||||
|
||||
container received {
|
||||
description "The packets sent by the client.";
|
||||
|
||||
leaf decline-count {
|
||||
type yang:counter32;
|
||||
description "Total number of DHCPDECLINE packets.";
|
||||
}
|
||||
leaf discover-count {
|
||||
type yang:counter32;
|
||||
description "Total number of DHCPDISCOVER packets.";
|
||||
}
|
||||
leaf request-count {
|
||||
type yang:counter32;
|
||||
description "Total number of DHCPREQUEST packets.";
|
||||
}
|
||||
leaf release-count {
|
||||
type yang:counter32;
|
||||
description "Total number of DHCPRELEASE packets.";
|
||||
}
|
||||
leaf inform-count {
|
||||
type yang:counter32;
|
||||
description "Total number of DHCPINFORM packets.";
|
||||
}
|
||||
}
|
||||
|
||||
action clear {
|
||||
description "Clear packet statistics.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
module infix-dhcp-server {
|
||||
yang-version 1.1;
|
||||
namespace "urn:infix:params:xml:ns:yang:infix-dhcp-server";
|
||||
prefix dhc4-srv;
|
||||
|
||||
import ietf-inet-types {
|
||||
prefix inet;
|
||||
}
|
||||
import ietf-interfaces {
|
||||
prefix if;
|
||||
}
|
||||
import ietf-ip {
|
||||
prefix ip;
|
||||
}
|
||||
import ietf-yang-types {
|
||||
prefix yang;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "YANG model for configuring a DHCPv4 server";
|
||||
|
||||
revision 2024-08-18 {
|
||||
description "Initial revision.";
|
||||
reference "rfc2131 rfc7950";
|
||||
}
|
||||
|
||||
typedef dhcp-server-options {
|
||||
type union {
|
||||
type string;
|
||||
type enumeration {
|
||||
enum router {
|
||||
value 3;
|
||||
description "Default router.";
|
||||
}
|
||||
enum dns-server {
|
||||
value 6;
|
||||
description "DNS server.";
|
||||
}
|
||||
enum domain-name {
|
||||
value 15;
|
||||
description "Domain name.";
|
||||
}
|
||||
enum mtu {
|
||||
value 26;
|
||||
description "Interface MTU.";
|
||||
}
|
||||
enum ntp-server {
|
||||
value 42;
|
||||
description "NTP server.";
|
||||
}
|
||||
enum netbios-ns {
|
||||
value 44;
|
||||
description "NETBIOS name server.";
|
||||
}
|
||||
enum netbios-nodetype {
|
||||
value 46;
|
||||
description "NETBIOS node type.";
|
||||
}
|
||||
enum netbios-scope {
|
||||
value 47;
|
||||
description "NETBIOS scope.";
|
||||
}
|
||||
}
|
||||
}
|
||||
description "Supported DHCP server options.";
|
||||
}
|
||||
|
||||
container dhcp-server {
|
||||
description "DHCP server configuration.";
|
||||
|
||||
leaf enabled {
|
||||
type boolean;
|
||||
default "true";
|
||||
description "Globally enables the DHCP server function.";
|
||||
}
|
||||
|
||||
list server-if {
|
||||
key "if-name";
|
||||
description "List of interfaces configuring a DHCP server.";
|
||||
|
||||
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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rpc clean-statistics {
|
||||
description "Clear packet statistics.";
|
||||
input {
|
||||
leaf if-name {
|
||||
type if:interface-ref;
|
||||
mandatory true;
|
||||
description "Name of the interface.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
infix-dhcp-server.yang
|
||||
@@ -224,9 +224,8 @@
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="dhcp-server" help="DHCP server tools" mode="switch">
|
||||
<COMMAND name="clean-statistics" help="Clean DHCP server packet statistics">
|
||||
<PARAM name="if-name" ptype="/IFACES" help="DHCP server interface."/>
|
||||
<ACTION sym="srp_rpc@sysrepo">/infix-dhcp-server:clean-statistics</ACTION>
|
||||
<COMMAND name="clear-statistics" help="Clear DHCP server statistics">
|
||||
<ACTION sym="srp_rpc@sysrepo">/infix-dhcp-server:dhcp-server/statistics/clear</ACTION>
|
||||
</COMMAND>
|
||||
</COMMAND>
|
||||
|
||||
@@ -308,13 +307,22 @@
|
||||
sysrepocfg -f json -X -d operational -m infix-dhcp-server | \
|
||||
/usr/libexec/statd/cli-pretty "show-dhcp-server"
|
||||
</ACTION>
|
||||
|
||||
<SWITCH name="optional" min="0">
|
||||
<COMMAND name="detailed" help="Detailed (full) output">
|
||||
<ACTION sym="script">
|
||||
sysrepocfg -f json -X -d operational -m infix-dhcp-server | \
|
||||
jq -C .
|
||||
</ACTION>
|
||||
<COMMAND name="statistics" help="Show DHCP server statistics">
|
||||
<ACTION sym="script">
|
||||
sysrepocfg -f json -X -d operational -m infix-dhcp-server | \
|
||||
/usr/libexec/statd/cli-pretty "show-dhcp-server" -s
|
||||
</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="detailed" help="Detailed (full operational) output">
|
||||
<ACTION sym="script">
|
||||
sysrepocfg -f json -X -d operational -m infix-dhcp-server | \
|
||||
jq -C .
|
||||
</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
</SWITCH>
|
||||
</COMMAND>
|
||||
|
||||
|
||||
@@ -58,11 +58,11 @@ class PadSoftware:
|
||||
version = 23
|
||||
|
||||
class PadDhcpServer:
|
||||
iface = 7
|
||||
ip = 17
|
||||
mac = 19
|
||||
host = 21
|
||||
exp = 7
|
||||
cid = 19
|
||||
exp = 10
|
||||
|
||||
class PadUsbPort:
|
||||
title = 30
|
||||
@@ -359,32 +359,78 @@ class STPPortID:
|
||||
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()
|
||||
for lease in get_json_data([], self.data, 'leases', 'lease'):
|
||||
if lease["expires"] == "never":
|
||||
exp = " never"
|
||||
else:
|
||||
dt = datetime.strptime(lease['expires'], '%Y-%m-%dT%H:%M:%S%z')
|
||||
seconds = int((dt - now).total_seconds())
|
||||
exp = f" {self.format_duration(seconds)}"
|
||||
self.leases.append({
|
||||
"ip": lease["ip-address"],
|
||||
"mac": lease["hardware-address"],
|
||||
"ip": lease["address"],
|
||||
"mac": lease["phys-address"],
|
||||
"cid": lease["client-id"],
|
||||
"host": lease["hostname"],
|
||||
"exp": int(exp)
|
||||
"exp": exp
|
||||
})
|
||||
stats = get_json_data([], self.data, 'statistics')
|
||||
self.offers = stats["sent"]["offer-count"]
|
||||
self.acks = stats["sent"]["ack-count"]
|
||||
self.naks = stats["sent"]["nak-count"]
|
||||
self.declines = stats["received"]["decline-count"]
|
||||
self.discovers = stats["received"]["discover-count"]
|
||||
self.requests = stats["received"]["request-count"]
|
||||
self.releases = stats["received"]["release-count"]
|
||||
self.informs = stats["received"]["inform-count"]
|
||||
|
||||
def format_duration(self, seconds):
|
||||
"""Convert seconds to DDdHHhMMmSSs format, omitting zero values"""
|
||||
if seconds < 0:
|
||||
return "expired"
|
||||
|
||||
days, remainder = divmod(seconds, 86400)
|
||||
hours, remainder = divmod(remainder, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
|
||||
parts = []
|
||||
if days:
|
||||
parts.append(f"{days}d")
|
||||
if hours:
|
||||
parts.append(f"{hours}h")
|
||||
if minutes:
|
||||
parts.append(f"{minutes}m")
|
||||
if seconds or not parts:
|
||||
parts.append(f"{seconds}s")
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
def print(self):
|
||||
for lease in self.leases:
|
||||
ip = lease["ip"]
|
||||
mac = lease["mac"]
|
||||
exp = "%ds" % lease["exp"]
|
||||
ip = lease["ip"]
|
||||
mac = lease["mac"]
|
||||
cid = lease["cid"]
|
||||
exp = lease['exp']
|
||||
host = lease["host"][:20]
|
||||
row = f"{self.iface:<{PadDhcpServer.iface}}"
|
||||
row += f"{ip:<{PadDhcpServer.ip}}"
|
||||
row = f"{ip:<{PadDhcpServer.ip}}"
|
||||
row += f"{mac:<{PadDhcpServer.mac}}"
|
||||
row += f"{host:<{PadDhcpServer.host}}"
|
||||
row += f"{exp:<{PadDhcpServer.exp}}"
|
||||
row += f"{cid:<{PadDhcpServer.cid}}"
|
||||
row += f"{exp:>{PadDhcpServer.exp - 1}}"
|
||||
print(row)
|
||||
|
||||
def print_stats(self):
|
||||
print(f"{'DHCP offers sent':<{32}}: {self.offers}")
|
||||
print(f"{'DHCP ACK messages sent':<{32}}: {self.acks}")
|
||||
print(f"{'DHCP NAK messages sent':<{32}}: {self.naks}")
|
||||
print(f"{'DHCP decline messages received':<{32}}: {self.declines}")
|
||||
print(f"{'DHCP discover messages received':<{32}}: {self.discovers}")
|
||||
print(f"{'DHCP request messages received':<{32}}: {self.requests}")
|
||||
print(f"{'DHCP release messages received':<{32}}: {self.discovers}")
|
||||
print(f"{'DHCP inform messages received':<{32}}: {self.discovers}")
|
||||
|
||||
|
||||
class Iface:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
@@ -1072,21 +1118,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"):
|
||||
def show_dhcp_server(json, stats):
|
||||
data = json.get("infix-dhcp-server:dhcp-server")
|
||||
if not data:
|
||||
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))
|
||||
server = DhcpServer(data)
|
||||
|
||||
servers = get_json_data({}, json, "infix-dhcp-server:dhcp-server", "server-if")
|
||||
for s in servers:
|
||||
server = DhcpServer(s)
|
||||
if stats:
|
||||
server.print_stats()
|
||||
else:
|
||||
hdr = (f"{'IP ADDRESS':<{PadDhcpServer.ip}}"
|
||||
f"{'MAC':<{PadDhcpServer.mac}}"
|
||||
f"{'HOSTNAME':<{PadDhcpServer.host}}"
|
||||
f"{'CLIENT ID':<{PadDhcpServer.cid}}"
|
||||
f"{'EXPIRES':>{PadDhcpServer.exp}}")
|
||||
print(Decore.invert(hdr))
|
||||
server.print()
|
||||
|
||||
def main():
|
||||
@@ -1125,7 +1173,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')
|
||||
parser_dhcp_srv = subparsers.add_parser('show-dhcp-server', help='Show DHCP server')
|
||||
parser_dhcp_srv.add_argument("-s", "--stats", action="store_true", help="Show server statistics")
|
||||
|
||||
args = parser.parse_args()
|
||||
UNIT_TEST = args.test
|
||||
@@ -1145,7 +1194,7 @@ def main():
|
||||
elif args.command == "show-ntp":
|
||||
show_ntp(json_data)
|
||||
elif args.command == "show-dhcp-server":
|
||||
show_dhcp_server(json_data)
|
||||
show_dhcp_server(json_data, args.stats)
|
||||
else:
|
||||
print(f"Error, unknown command '{args.command}'")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -1,82 +1,37 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# This script is used to query dnsmasq daemons via dbus and
|
||||
# fills/cleans the infix-dhcp-server packet statistics.
|
||||
#
|
||||
"""
|
||||
This script is used to query a single dnsmasq daemon via dbus
|
||||
and fills/clears 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")
|
||||
parser.add_argument("-c", "--clear", action="store_true",
|
||||
help="Clear DHCP server metrics")
|
||||
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))
|
||||
try:
|
||||
bus = dbus.SystemBus()
|
||||
dnsmasq = dbus.Interface(bus.get_object(DNSMASQ_NAME, DNSMASQ_OBJECT),
|
||||
DNSMASQ_IFACE)
|
||||
|
||||
if args.clear:
|
||||
print("Clearing metrics for DHCP server")
|
||||
dnsmasq.ClearMetrics()
|
||||
else:
|
||||
print(json.dumps({"metrics": dnsmasq.GetMetrics()}))
|
||||
|
||||
except dbus.DBusException as e:
|
||||
print(f"Error: Unable to connect to dnsmasq via D-Bus: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,8 +6,7 @@ license = "MIT"
|
||||
packages = [
|
||||
{ include = "yanger" },
|
||||
{ include = "cli_pretty" },
|
||||
{ include = "ospf_status" },
|
||||
{ include = "dhcp_server_status" }
|
||||
{ include = "ospf_status" }
|
||||
]
|
||||
authors = [
|
||||
"KernelKit developers"
|
||||
@@ -22,4 +21,3 @@ 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"
|
||||
|
||||
Regular → Executable
+81
-59
@@ -1,79 +1,101 @@
|
||||
from datetime import datetime
|
||||
from .host import HOST
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Collect operational data for infix-dhcp-server.yang from dnsmasq
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import dbus
|
||||
|
||||
|
||||
def lease_info(ifname):
|
||||
def leases(leases_file):
|
||||
"""Populate DHCP leases table"""
|
||||
leases = f"/var/run/dnsmasq-{ifname}.leases"
|
||||
hosts = []
|
||||
table = []
|
||||
try:
|
||||
with open(leases, 'r', encoding='utf-8') as fd:
|
||||
with open(leases_file, '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],
|
||||
# Handle infinite lease time as specified in RFC 2131
|
||||
if tokens[0] == "0":
|
||||
expires = "never"
|
||||
else:
|
||||
dt = datetime.fromtimestamp(int(tokens[0]),
|
||||
tz=timezone.utc)
|
||||
expires = dt.isoformat() + "+00:00"
|
||||
|
||||
row = {
|
||||
"expires": expires,
|
||||
"address": tokens[2],
|
||||
"phys-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]
|
||||
row["hostname"] = tokens[3]
|
||||
else:
|
||||
row["hostname"] = ""
|
||||
|
||||
hosts.append(host)
|
||||
if tokens[4] != '*':
|
||||
row["client-id"] = tokens[4]
|
||||
else:
|
||||
row["client-id"] = ""
|
||||
|
||||
table.append(row)
|
||||
except (IOError, OSError, ValueError):
|
||||
pass
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def statistics():
|
||||
"""Fetch DHCP server metrics over D-Bus"""
|
||||
try:
|
||||
bus = dbus.SystemBus()
|
||||
obj = bus.get_object("uk.org.thekelleys.dnsmasq",
|
||||
"/uk/org/thekelleys/dnsmasq")
|
||||
srv = dbus.Interface(obj, "uk.org.thekelleys.dnsmasq")
|
||||
|
||||
metrics = srv.GetMetrics()
|
||||
except dbus.exceptions.DBusException:
|
||||
metrics = {
|
||||
"dhcp_offer": 0,
|
||||
"dhcp_ack": 0,
|
||||
"dhcp_nak": 0,
|
||||
"dhcp_decline": 0,
|
||||
"dhcp_discover": 0,
|
||||
"dhcp_request": 0,
|
||||
"dhcp_release": 0,
|
||||
"dhcp_inform": 0
|
||||
}
|
||||
|
||||
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": []
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
status(out['infix-dhcp-server:dhcp-server']['server-if'])
|
||||
|
||||
return out
|
||||
|
||||
def operational(leases_file="/var/lib/misc/dnsmasq.leases"):
|
||||
"""Return operational status for DHCP server"""
|
||||
return {
|
||||
"infix-dhcp-server:dhcp-server": {
|
||||
"statistics": statistics(),
|
||||
"leases": {
|
||||
"lease": leases(leases_file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(json.dumps(operational(leases_file="mock.leases")))
|
||||
|
||||
Reference in New Issue
Block a user