commit 58e0844b7c2805cb42e7a6d896693f8e2584158a
parent 6d253db83e32a58928c3cf15a2daa1ef638e9a9b
Author: finwo <finwo@pm.me>
Date: Thu, 17 Sep 2026 22:38:03 +0200
Add basic ifupdown functionality to unosd
Diffstat:
11 files changed, 1215 insertions(+), 40 deletions(-)
diff --git a/src/cli/unosc.c b/src/cli/unosc.c
@@ -0,0 +1,97 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#define UNOS_IPC_PATH "/run/unosd.sock"
+
+int main(int argc, char *argv[]) {
+ const char *prog = basename(argv[0]);
+ const char *cmd = NULL;
+ int cmd_argc = 0;
+ char **cmd_argv = NULL;
+ int sock;
+ struct sockaddr_un addr;
+ char buf[4096];
+
+ // Determine command from argv[0] or argv[1]
+ if (!strcmp(prog, "unosc")) {
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <ifup|ifdown|ifquery|ifreload> [args...]\n", prog);
+ fprintf(stderr, " or: ifup/ifdown/ifquery/ifreload as symlinks to unosc\n");
+ return 1;
+ }
+ cmd = argv[1];
+ cmd_argc = argc - 2;
+ cmd_argv = argv + 2;
+ } else {
+ // Called as ifup, ifdown, etc. via symlink
+ cmd = prog;
+ cmd_argc = argc - 1;
+ cmd_argv = argv + 1;
+ }
+
+ sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (sock < 0) {
+ perror("socket");
+ return 1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, UNOS_IPC_PATH, sizeof(addr.sun_path)-1);
+
+ if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
+ fprintf(stderr, "unosc: connect %s failed: %s\n", UNOS_IPC_PATH, strerror(errno));
+ fprintf(stderr, "Is unosd running?\n");
+ close(sock);
+ return 1;
+ }
+
+ // Send command: "cmd [args...]\n"
+ {
+ char out[4096];
+ int len = 0;
+ len += snprintf(out+len, sizeof(out)-len, "%s", cmd);
+ for (int i = 0; i < cmd_argc; i++) {
+ len += snprintf(out+len, sizeof(out)-len, " %s", cmd_argv[i]);
+ }
+ len += snprintf(out+len, sizeof(out)-len, "\n");
+ if (write(sock, out, len) != len) {
+ perror("write");
+ close(sock);
+ return 1;
+ }
+ }
+
+ // Read response and pipe to stdout
+ // Protocol: server sends lines, first line is OK/ERR, but for ifquery the dump is the response
+ // We just pipe everything to stdout, and exit 0 on OK, 1 on ERR
+ int saw_ok = 0;
+ int saw_err = 0;
+ FILE *f = fdopen(sock, "r");
+ if (!f) {
+ perror("fdopen");
+ close(sock);
+ return 1;
+ }
+ while (fgets(buf, sizeof(buf), f)) {
+ // Check for OK/ERR prefix on first line? Just print and detect
+ if (!saw_ok && !saw_err) {
+ if (!strncmp(buf, "OK", 2)) saw_ok = 1;
+ else if (!strncmp(buf, "ERR", 3)) saw_err = 1;
+ }
+ fputs(buf, stdout);
+ // For non-ifquery, the response is just OK/ERR line
+ // For ifquery, there may be multiple lines plus OK
+ }
+ fclose(f);
+ // If we saw ERR, exit 1
+ if (saw_err) return 1;
+ return 0;
+}
diff --git a/src/config/ifaces.c b/src/config/ifaces.c
@@ -0,0 +1,332 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#include "finwo/cnfparse.h"
+
+#include "util/config.h"
+
+#include "ifaces.h"
+
+static struct unos_iface *ifaces = NULL;
+static struct unos_iface *ifaces_tail = NULL;
+
+struct unos_iface * ifaces_list(void) {
+ return ifaces;
+}
+
+struct unos_iface * ifaces_find(const char *name) {
+ struct unos_iface *cur;
+ for (cur = ifaces; cur; cur = cur->next) {
+ if (!strcmp(cur->name, name)) return cur;
+ }
+ return NULL;
+}
+
+void ifaces_set_auto(const char *name, int flag) {
+ struct unos_iface *cur = ifaces_find(name);
+ if (cur) {
+ cur->auto_flag = flag;
+ return;
+ }
+ // Create placeholder for auto without iface block yet (common in Debian: auto comes before iface)
+ cur = calloc(1, sizeof(*cur));
+ cur->name = strdup(name);
+ cur->vlan_id = -1;
+ cur->auto_flag = flag;
+ if (ifaces_tail) {
+ ifaces_tail->next = cur;
+ } else {
+ ifaces = cur;
+ }
+ ifaces_tail = cur;
+}
+
+void ifaces_free(void) {
+ struct unos_iface *cur = ifaces;
+ struct unos_iface *next;
+ struct iface_addr *a, *anext;
+ while (cur) {
+ next = cur->next;
+ free(cur->name);
+ free(cur->method);
+ free(cur->family);
+ free(cur->gateway);
+ free(cur->broadcast);
+ free(cur->hwaddress);
+ free(cur->vlan_raw_device);
+ free(cur->pre_up);
+ free(cur->post_up);
+ free(cur->pre_down);
+ free(cur->post_down);
+ for (a = cur->addrs; a; a = anext) {
+ anext = a->next;
+ free(a->address);
+ free(a->netmask);
+ free(a);
+ }
+ free(cur);
+ cur = next;
+ }
+ ifaces = NULL;
+ ifaces_tail = NULL;
+}
+
+static void iface_add_addr(struct unos_iface *iface, const char *addr, const char *netmask) {
+ struct iface_addr *a = calloc(1, sizeof(*a));
+ a->address = strdup(addr);
+ if (netmask) a->netmask = strdup(netmask);
+ a->next = NULL;
+ // append
+ if (!iface->addrs) {
+ iface->addrs = a;
+ } else {
+ struct iface_addr *cur = iface->addrs;
+ while (cur->next) cur = cur->next;
+ cur->next = a;
+ }
+}
+
+// auto <if> [<if> ...]
+struct cnf_directive * cfg_parse_auto(FILE *fd, struct cnf_directive *dir, void *user) {
+ (void)fd; (void)user;
+ int i;
+ for (i = 0; i < (int)dir->argc; i++) {
+ ifaces_set_auto(dir->argv[i], 1);
+ }
+ cnf_directive_free(dir);
+ return NULL;
+}
+
+// iface <name> [inet|inet6 <method>] -- cumulus style `iface <name>` preferred (no inet)
+struct cnf_directive * cfg_parse_iface(FILE *fd, struct cnf_directive *dir, void *user) {
+ struct unos_iface *iface;
+ (void)user;
+
+ if (dir->argc < 1 || dir->argc > 3) {
+ fprintf(stderr, "`iface` directive: expected `iface <name>` or `iface <name> inet <method>`\n");
+ exit(1);
+ }
+
+ const char *name = dir->argv[0];
+ const char *family = NULL;
+ const char *method = NULL;
+
+ if (dir->argc == 1) {
+ // Cumulus style: iface <name> -- defaults to static, no family
+ method = "static";
+ } else if (dir->argc == 3) {
+ // Traditional: iface <name> inet static
+ family = dir->argv[1];
+ method = dir->argv[2];
+ if (strcasecmp(family, "inet") && strcasecmp(family, "inet6")) {
+ fprintf(stderr, "`iface %s` unknown family: %s (expected inet/inet6)\n", name, family);
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "`iface` directive: expected `iface <name>` or `iface <name> inet <method>`\n");
+ exit(1);
+ }
+
+ // Find existing placeholder from auto, or create new
+ iface = ifaces_find(name);
+ int was_placeholder = 0;
+ if (iface && !iface->method && !iface->family && !iface->addrs) {
+ // placeholder from auto -- reuse
+ was_placeholder = 1;
+ } else if (iface) {
+ // Duplicate iface block -- for now we merge? Cumulus allows multiple iface stanzas for same if
+ // but we treat as error to keep simple, unless it's same name with different family
+ // Allow merging: if family/method differ, we keep as separate? For now, error on exact duplicate
+ // Check if same iface already has addrs/method -- if so, create new entry with same name but as separate node
+ // To keep diff-apply simple, we allow multiple entries with same name as separate list nodes
+ // So we will create a new node even if name exists, unless it was placeholder
+ was_placeholder = 0;
+ }
+
+ if (iface && was_placeholder) {
+ // reuse placeholder
+ free(iface->method);
+ free(iface->family);
+ iface->method = method ? strdup(method) : NULL;
+ iface->family = family ? strdup(family) : NULL;
+ // keep auto_flag
+ } else {
+ // create new
+ iface = calloc(1, sizeof(*iface));
+ iface->name = strdup(name);
+ iface->method = method ? strdup(method) : NULL;
+ iface->family = family ? strdup(family) : NULL;
+ iface->vlan_id = -1;
+ // check if auto was previously set for this name via placeholder? Already handled, but if not placeholder, check if any placeholder had auto
+ // Actually ifaces_set_auto may have created placeholder with auto, we already handled.
+ // If no placeholder, auto_flag stays 0 until auto directive is parsed (order may be auto before or after iface)
+ // So we need to check if there was a prior auto for this name that wasn't yet linked -- but our placeholder logic already covers.
+ if (ifaces_tail) {
+ ifaces_tail->next = iface;
+ } else {
+ ifaces = iface;
+ }
+ ifaces_tail = iface;
+ }
+
+ // Handle vlan dot notation: swp1.100 -> raw_device swp1, vlan_id 100
+ char *dot = strrchr(iface->name, '.');
+ if (dot && !iface->vlan_raw_device) {
+ char *end = NULL;
+ long vid = strtol(dot+1, &end, 10);
+ if (end != dot+1 && *end == '\0' && vid >= 1 && vid <= 4094) {
+ iface->vlan_id = (int)vid;
+ // Only set raw_device if not already set via explicit vlan-raw-device
+ // Keep as is, will be resolved later if needed
+ }
+ }
+
+ for(;;) {
+ if (dir) cnf_directive_free(dir);
+ dir = cnf_directive_read(fd);
+ if (!dir) break;
+
+ if(0) {}
+ else if (!strcasecmp("address", dir->name)) {
+ if (dir->argc < 1 || dir->argc > 2) {
+ fprintf(stderr, "`address` expects 1 or 2 args (address + optional netmask)\n");
+ exit(1);
+ }
+ const char *addr = dir->argv[0];
+ const char *nm = dir->argc > 1 ? dir->argv[1] : NULL;
+ iface_add_addr(iface, addr, nm);
+ }
+ else if (!strcasecmp("netmask", dir->name)) {
+ if (dir->argc != 1) {
+ fprintf(stderr, "`netmask` expects 1 arg\n");
+ exit(1);
+ }
+ // Attach netmask to last address without netmask
+ struct iface_addr *a = iface->addrs;
+ if (!a) {
+ fprintf(stderr, "`netmask` without preceding `address`\n");
+ exit(1);
+ }
+ while (a->next) a = a->next;
+ free(a->netmask);
+ a->netmask = strdup(dir->argv[0]);
+ }
+ else if (!strcasecmp("broadcast", dir->name)) {
+ if (dir->argc != 1) { fprintf(stderr, "`broadcast` expects 1 arg\n"); exit(1); }
+ free(iface->broadcast);
+ iface->broadcast = strdup(dir->argv[0]);
+ }
+ else if (!strcasecmp("gateway", dir->name)) {
+ if (dir->argc != 1) { fprintf(stderr, "`gateway` expects 1 arg\n"); exit(1); }
+ free(iface->gateway);
+ iface->gateway = strdup(dir->argv[0]);
+ }
+ else if (!strcasecmp("mtu", dir->name)) {
+ if (dir->argc != 1) { fprintf(stderr, "`mtu` expects 1 arg\n"); exit(1); }
+ iface->mtu = atoi(dir->argv[0]);
+ }
+ else if (!strcasecmp("hwaddress", dir->name) || !strcasecmp("hw-address", dir->name)) {
+ if (dir->argc != 1) { fprintf(stderr, "`hwaddress` expects 1 arg\n"); exit(1); }
+ free(iface->hwaddress);
+ iface->hwaddress = strdup(dir->argv[0]);
+ }
+ else if (!strcasecmp("vlan-raw-device", dir->name) || !strcasecmp("vlan_raw_device", dir->name)) {
+ if (dir->argc != 1) { fprintf(stderr, "`vlan-raw-device` expects 1 arg\n"); exit(1); }
+ free(iface->vlan_raw_device);
+ iface->vlan_raw_device = strdup(dir->argv[0]);
+ }
+ else if (!strcasecmp("vlan-id", dir->name) || !strcasecmp("vlan_id", dir->name)) {
+ if (dir->argc != 1) { fprintf(stderr, "`vlan-id` expects 1 arg\n"); exit(1); }
+ iface->vlan_id = atoi(dir->argv[0]);
+ }
+ else if (!strcasecmp("pre-up", dir->name) || !strcasecmp("pre_up", dir->name)) {
+ if (dir->argc < 1) { fprintf(stderr, "`pre-up` expects at least 1 arg\n"); exit(1); }
+ // Join argv with spaces
+ size_t len = 0;
+ for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1;
+ char *cmd = calloc(1, len+1);
+ for (int i=0;i<(int)dir->argc;i++) {
+ strcat(cmd, dir->argv[i]);
+ if (i+1 < (int)dir->argc) strcat(cmd, " ");
+ }
+ free(iface->pre_up);
+ iface->pre_up = cmd;
+ }
+ else if (!strcasecmp("post-up", dir->name) || !strcasecmp("post_up", dir->name)) {
+ if (dir->argc < 1) { fprintf(stderr, "`post-up` expects at least 1 arg\n"); exit(1); }
+ size_t len = 0;
+ for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1;
+ char *cmd = calloc(1, len+1);
+ for (int i=0;i<(int)dir->argc;i++) {
+ strcat(cmd, dir->argv[i]);
+ if (i+1 < (int)dir->argc) strcat(cmd, " ");
+ }
+ free(iface->post_up);
+ iface->post_up = cmd;
+ }
+ else if (!strcasecmp("pre-down", dir->name) || !strcasecmp("pre_down", dir->name)) {
+ if (dir->argc < 1) { fprintf(stderr, "`pre-down` expects at least 1 arg\n"); exit(1); }
+ size_t len = 0;
+ for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1;
+ char *cmd = calloc(1, len+1);
+ for (int i=0;i<(int)dir->argc;i++) {
+ strcat(cmd, dir->argv[i]);
+ if (i+1 < (int)dir->argc) strcat(cmd, " ");
+ }
+ free(iface->pre_down);
+ iface->pre_down = cmd;
+ }
+ else if (!strcasecmp("post-down", dir->name) || !strcasecmp("post_down", dir->name)) {
+ if (dir->argc < 1) { fprintf(stderr, "`post-down` expects at least 1 arg\n"); exit(1); }
+ size_t len = 0;
+ for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1;
+ char *cmd = calloc(1, len+1);
+ for (int i=0;i<(int)dir->argc;i++) {
+ strcat(cmd, dir->argv[i]);
+ if (i+1 < (int)dir->argc) strcat(cmd, " ");
+ }
+ free(iface->post_down);
+ iface->post_down = cmd;
+ }
+ else if (!strcasecmp("up", dir->name)) {
+ // alias for post-up
+ if (dir->argc < 1) { fprintf(stderr, "`up` expects at least 1 arg\n"); exit(1); }
+ size_t len = 0;
+ for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1;
+ char *cmd = calloc(1, len+1);
+ for (int i=0;i<(int)dir->argc;i++) {
+ strcat(cmd, dir->argv[i]);
+ if (i+1 < (int)dir->argc) strcat(cmd, " ");
+ }
+ free(iface->post_up);
+ iface->post_up = cmd;
+ }
+ else if (!strcasecmp("down", dir->name)) {
+ if (dir->argc < 1) { fprintf(stderr, "`down` expects at least 1 arg\n"); exit(1); }
+ size_t len = 0;
+ for (int i=0;i<(int)dir->argc;i++) len += strlen(dir->argv[i])+1;
+ char *cmd = calloc(1, len+1);
+ for (int i=0;i<(int)dir->argc;i++) {
+ strcat(cmd, dir->argv[i]);
+ if (i+1 < (int)dir->argc) strcat(cmd, " ");
+ }
+ free(iface->pre_down);
+ iface->pre_down = cmd;
+ }
+ else {
+ break;
+ }
+ }
+
+ return dir;
+}
+
+void ifaces_register(void) {
+ struct cfg_ns *ns = cfg_ns_get("interfaces");
+ cfg_register_directive(ns, "auto", cfg_parse_auto);
+ cfg_register_directive(ns, "allow-auto", cfg_parse_auto);
+ cfg_register_directive(ns, "allow-hotplug", cfg_parse_auto);
+ cfg_register_directive(ns, "iface", cfg_parse_iface);
+}
diff --git a/src/config/ifaces.h b/src/config/ifaces.h
@@ -0,0 +1,47 @@
+#ifndef __UNOS_CONFIG_IFACES_H__
+#define __UNOS_CONFIG_IFACES_H__
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "finwo/cnfparse.h"
+
+// Network interface configuration from /etc/network/interfaces
+// IPv6 is first-class: address may be v4 or v6, gateway may be v4 or v6.
+
+struct iface_addr {
+ char *address; // "192.168.1.1/24" or "2001:db8::1/64"
+ char *netmask; // v4 only, optional
+ struct iface_addr *next;
+};
+
+struct unos_iface {
+ char *name; // e.g., "eth0", "swp1.100" -- cumulus style `iface <name>` preferred
+ char *method; // "static", "dhcp", "loopback", "manual" -- optional, defaults to "static" when `iface <name>` used without `inet`
+ char *family; // "inet", "inet6" -- optional, NULL when cumulus style
+ struct iface_addr *addrs;
+ char *gateway;
+ char *broadcast;
+ int mtu; // 0 = unset
+ char *hwaddress;
+ char *vlan_raw_device;
+ int vlan_id; // -1 = unset, 1..4094
+ char *pre_up;
+ char *post_up;
+ char *pre_down;
+ char *post_down;
+ int auto_flag; // 1 if listed in auto
+ struct unos_iface *next;
+};
+
+void ifaces_register(void);
+
+struct cnf_directive * cfg_parse_auto(FILE *fd, struct cnf_directive *dir, void *user);
+struct cnf_directive * cfg_parse_iface(FILE *fd, struct cnf_directive *dir, void *user);
+
+struct unos_iface * ifaces_list(void);
+struct unos_iface * ifaces_find(const char *name);
+void ifaces_free(void);
+void ifaces_set_auto(const char *name, int flag);
+
+#endif // __UNOS_CONFIG_IFACES_H__
diff --git a/src/config/ports.c b/src/config/ports.c
@@ -166,5 +166,6 @@ struct cnf_directive * cfg_parse_port(FILE *fd, struct cnf_directive *dir, void
}
void ports_register(void) {
- cfg_register_directive("port", cfg_parse_port);
+ struct cfg_ns *ns = cfg_ns_get("ports");
+ cfg_register_directive(ns, "port", cfg_parse_port);
}
diff --git a/src/ipc.c b/src/ipc.c
@@ -0,0 +1,446 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "rxi/log.h"
+
+#include "util/config.h"
+#include "config/ifaces.h"
+#include "ipc.h"
+#include "netlink/netlink.h"
+
+static int ipc_sock = -1;
+
+int ipc_fd(void) {
+ return ipc_sock;
+}
+
+int ipc_init(void) {
+ struct sockaddr_un addr;
+ // Ensure /run exists (tmpfs mount in rcS, but early boot may not have it)
+ mkdir("/run", 0755);
+ unlink(UNOS_IPC_PATH);
+
+ ipc_sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (ipc_sock < 0) {
+ log_error("ipc: socket failed: %s", strerror(errno));
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, UNOS_IPC_PATH, sizeof(addr.sun_path)-1);
+
+ if (bind(ipc_sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
+ log_error("ipc: bind %s failed: %s", UNOS_IPC_PATH, strerror(errno));
+ close(ipc_sock);
+ ipc_sock = -1;
+ return -1;
+ }
+
+ if (chmod(UNOS_IPC_PATH, 0600) != 0) {
+ log_warn("ipc: chmod %s failed: %s", UNOS_IPC_PATH, strerror(errno));
+ }
+
+ if (listen(ipc_sock, 8) != 0) {
+ log_error("ipc: listen failed: %s", strerror(errno));
+ close(ipc_sock);
+ ipc_sock = -1;
+ unlink(UNOS_IPC_PATH);
+ return -1;
+ }
+
+ log_info("ipc: listening on %s", UNOS_IPC_PATH);
+ return 0;
+}
+
+void ipc_fini(void) {
+ if (ipc_sock >= 0) {
+ close(ipc_sock);
+ ipc_sock = -1;
+ }
+ unlink(UNOS_IPC_PATH);
+}
+
+// Helpers to send response
+static void ipc_send_ok(FILE *out, const char *msg) {
+ if (msg) fprintf(out, "OK %s\n", msg);
+ else fprintf(out, "OK\n");
+}
+
+static void ipc_send_err(FILE *out, const char *msg) {
+ fprintf(out, "ERR %s\n", msg ? msg : "unknown error");
+}
+
+// Forward declarations for handlers (implemented below, using netlink helpers)
+static int handle_ifup(FILE *out, const char *ifname, uid_t uid);
+static int handle_ifdown(FILE *out, const char *ifname, uid_t uid);
+static int handle_ifreload(FILE *out, uid_t uid);
+static int handle_ifquery(FILE *out, const char *ifname);
+
+// IPC namespace handlers -- these are registered with cfg_ns_get("ipc")
+// They are invoked via cfg_parse on the connected socket's FILE*
+static struct cnf_directive *cfg_handle_ifup(FILE *fd, struct cnf_directive *dir, void *user) {
+ FILE *out = (FILE*)user;
+ (void)fd;
+ log_info("ipc: cfg_handle_ifup argc=%d", dir->argc);
+ uid_t uid = 0;
+ extern uid_t ipc_current_uid;
+ uid = ipc_current_uid;
+ if (uid != 0) {
+ ipc_send_err(out, "permission denied: ifup requires root");
+ cnf_directive_free(dir);
+ return NULL;
+ }
+ if (dir->argc != 1) {
+ ipc_send_err(out, "ifup expects 1 arg: <ifname>");
+ cnf_directive_free(dir);
+ return NULL;
+ }
+ log_info("ipc: cfg_handle_ifup %s uid=%d", dir->argv[0], uid);
+ if (handle_ifup(out, dir->argv[0], uid) == 0) ipc_send_ok(out, NULL);
+ else log_info("ipc: handle_ifup %s failed", dir->argv[0]);
+ cnf_directive_free(dir);
+ return NULL;
+}
+
+static struct cnf_directive *cfg_handle_ifdown(FILE *fd, struct cnf_directive *dir, void *user) {
+ FILE *out = (FILE*)user;
+ (void)fd;
+ extern uid_t ipc_current_uid;
+ uid_t uid = ipc_current_uid;
+ if (uid != 0) {
+ ipc_send_err(out, "permission denied: ifdown requires root");
+ cnf_directive_free(dir);
+ return NULL;
+ }
+ if (dir->argc != 1) {
+ ipc_send_err(out, "ifdown expects 1 arg: <ifname>");
+ cnf_directive_free(dir);
+ return NULL;
+ }
+ if (handle_ifdown(out, dir->argv[0], uid) == 0) ipc_send_ok(out, NULL);
+ cnf_directive_free(dir);
+ return NULL;
+}
+
+static struct cnf_directive *cfg_handle_ifreload(FILE *fd, struct cnf_directive *dir, void *user) {
+ FILE *out = (FILE*)user;
+ (void)fd;
+ extern uid_t ipc_current_uid;
+ uid_t uid = ipc_current_uid;
+ if (uid != 0) {
+ ipc_send_err(out, "permission denied: ifreload requires root");
+ cnf_directive_free(dir);
+ return NULL;
+ }
+ if (dir->argc != 0) {
+ ipc_send_err(out, "ifreload expects no args");
+ cnf_directive_free(dir);
+ return NULL;
+ }
+ if (handle_ifreload(out, uid) == 0) ipc_send_ok(out, NULL);
+ cnf_directive_free(dir);
+ return NULL;
+}
+
+static struct cnf_directive *cfg_handle_ifquery(FILE *fd, struct cnf_directive *dir, void *user) {
+ FILE *out = (FILE*)user;
+ (void)fd;
+ log_info("ipc: ifquery argc=%d", dir->argc);
+ const char *ifname = NULL;
+ if (dir->argc > 1) {
+ ipc_send_err(out, "ifquery expects 0 or 1 arg");
+ cnf_directive_free(dir);
+ return NULL;
+ }
+ if (dir->argc == 1) ifname = dir->argv[0];
+ log_info("ipc: ifquery %s", ifname ? ifname : "(all)");
+ handle_ifquery(out, ifname);
+ cnf_directive_free(dir);
+ return NULL;
+}
+
+// Global for current uid (single-threaded, one client at a time)
+uid_t ipc_current_uid = 0;
+
+int ipc_handle(void) {
+ log_info("ipc: handle called");
+ int cfd;
+ struct sockaddr_un addr;
+ socklen_t alen = sizeof(addr);
+ struct ucred cred;
+ socklen_t clen = sizeof(cred);
+ FILE *fin = NULL;
+ FILE *fout = NULL;
+ int fd_dup;
+
+ cfd = accept4(ipc_sock, (struct sockaddr*)&addr, &alen, SOCK_CLOEXEC | SOCK_NONBLOCK);
+ if (cfd < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return 0;
+ log_error("ipc: accept failed: %s", strerror(errno));
+ return -1;
+ }
+
+ // Get peer credentials
+ if (getsockopt(cfd, SOL_SOCKET, SO_PEERCRED, &cred, &clen) == 0) {
+ ipc_current_uid = cred.uid;
+ } else {
+ ipc_current_uid = 99999;
+ }
+
+ // Use fdopen for reading and writing -- need separate FILE* for read and write to handle duplex?
+ // We'll use one FILE* for reading commands, and the fd directly for writing responses.
+ // Simpler: use fdopen for reading, and use fdopen dup for writing.
+ fd_dup = dup(cfd);
+ if (fd_dup < 0) {
+ close(cfd);
+ return -1;
+ }
+ fin = fdopen(cfd, "r");
+ fout = fdopen(fd_dup, "w");
+ if (!fin || !fout) {
+ if (fin) fclose(fin); else close(cfd);
+ if (fout) fclose(fout); else close(fd_dup);
+ return -1;
+ }
+
+ // Set line buffering
+ setvbuf(fout, NULL, _IONBF, 0);
+
+ // Parse one or more commands from this connection (each line is a command)
+ // Use cfg_parse on the ipc namespace
+ struct cfg_ns *ns = cfg_ns_get("ipc");
+ // cfg_parse will read directives from fin and dispatch to handlers, which write to fout
+ // It handles multiple lines until EOF.
+ cfg_parse(ns, NULL, fin, fout);
+
+ // Ensure OK is sent if handler didn't already? Our handlers send OK/ERR themselves.
+ // Flush and close
+ fflush(fout);
+ fclose(fin); // also closes cfd
+ fclose(fout);
+ ipc_current_uid = 0;
+ return 0;
+}
+
+// Register IPC commands
+void ipc_register(void) {
+ struct cfg_ns *ns = cfg_ns_get("ipc");
+ cfg_register_directive(ns, "ifup", cfg_handle_ifup);
+ cfg_register_directive(ns, "ifdown", cfg_handle_ifdown);
+ cfg_register_directive(ns, "ifreload", cfg_handle_ifreload);
+ cfg_register_directive(ns, "ifquery", cfg_handle_ifquery);
+}
+
+// Stubs for netlink helpers (to be implemented in netlink.c or here)
+// For now, provide minimal implementations that just log and return OK
+// The real implementation will be wired in the next commit (Wire netlink apply)
+
+#include <sys/wait.h>
+
+static int run_hook(const char *cmd) {
+ if (!cmd || !*cmd) return 0;
+ log_info("ipc: running hook: %s", cmd);
+ int rc = system(cmd);
+ if (rc != 0) {
+ log_warn("hook `%s` failed with %d", cmd, rc);
+ return -1;
+ }
+ return 0;
+}
+
+static int handle_ifup(FILE *out, const char *ifname, uid_t uid) {
+ (void)uid;
+ struct unos_iface *iface = ifaces_find(ifname);
+ if (!iface) {
+ log_info("ipc: ifup %s (not in interfaces, just bringing link up)", ifname);
+ char cmd[512];
+ snprintf(cmd, sizeof(cmd), "ip link set %s up 2>&1", ifname);
+ int rc = system(cmd);
+ if (rc != 0) {
+ fprintf(out, "ERR ifup %s failed\n", ifname);
+ return -1;
+ }
+ return 0;
+ }
+ log_info("ipc: ifup %s", ifname);
+ if (iface->pre_up && run_hook(iface->pre_up) != 0) {
+ fprintf(out, "ERR pre-up hook failed for %s\n", ifname);
+ return -1;
+ }
+ // VLAN handling: if iface is vlan, ensure raw device exists and create vlan link
+ // For now, just bring link up and add addresses via netlink helpers (to be implemented)
+ // Placeholder: use system ip commands via netlink abstraction
+ // TODO: wire real netlink RTM_NEWLINK/NEWADDR
+ char cmd[512];
+ int rc;
+ // Bring link up
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link set %s up", ifname);
+ rc = system(cmd);
+ log_info("ipc: ifup %s link up rc=%d cmd='%s'", ifname, rc, cmd);
+ if (rc != 0) { fprintf(out, "ERR ifup %s: link up failed (%d)\n", ifname, rc); return -1; }
+ // Add addresses
+ struct iface_addr *a;
+ for (a = iface->addrs; a; a = a->next) {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip addr add %s dev %s 2>&1", a->address, ifname);
+ rc = system(cmd);
+ log_info("ipc: ifup %s addr %s rc=%d", ifname, a->address, rc);
+ // File exists is not fatal (addr already present)
+ if (rc != 0 && rc != 256) { /* 256 = File exists from ip */ }
+ }
+ if (iface->gateway) {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip route add default via %s 2>&1 || true", iface->gateway);
+ rc = system(cmd);
+ log_info("ipc: ifup %s gateway %s rc=%d", ifname, iface->gateway, rc);
+ }
+ if (iface->mtu) {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link set %s mtu %d", ifname, iface->mtu);
+ rc = system(cmd);
+ log_info("ipc: ifup %s mtu %d rc=%d", ifname, iface->mtu, rc);
+ if (rc != 0) { fprintf(out, "ERR ifup %s: mtu failed\n", ifname); return -1; }
+ }
+ if (iface->hwaddress) {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link set %s address %s", ifname, iface->hwaddress);
+ rc = system(cmd);
+ log_info("ipc: ifup %s hwaddress %s rc=%d", ifname, iface->hwaddress, rc);
+ if (rc != 0) { fprintf(out, "ERR ifup %s: hwaddress failed\n", ifname); return -1; }
+ }
+ if (iface->post_up && run_hook(iface->post_up) != 0) {
+ fprintf(out, "ERR post-up hook failed for %s\n", ifname);
+ return -1;
+ }
+ return 0;
+}
+
+static int handle_ifdown(FILE *out, const char *ifname, uid_t uid) {
+ (void)uid;
+ struct unos_iface *iface = ifaces_find(ifname);
+ if (!iface) {
+ log_info("ipc: ifdown %s (not in interfaces, just bringing link down)", ifname);
+ char cmd[512];
+ snprintf(cmd, sizeof(cmd), "ip link set %s down 2>&1", ifname);
+ int rc = system(cmd);
+ if (rc != 0) {
+ fprintf(out, "ERR ifdown %s failed\n", ifname);
+ return -1;
+ }
+ return 0;
+ }
+ log_info("ipc: ifdown %s", ifname);
+ if (iface->pre_down && run_hook(iface->pre_down) != 0) {
+ fprintf(out, "ERR pre-down hook failed for %s\n", ifname);
+ return -1;
+ }
+ char cmd[512];
+ int rc2;
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link set %s down", ifname);
+ rc2 = system(cmd);
+ log_info("ipc: ifdown %s rc=%d", ifname, rc2);
+ if (rc2 != 0) { fprintf(out, "ERR ifdown %s failed (%d)\n", ifname, rc2); return -1; }
+ // Flush addresses (keep for now)
+ if (iface->post_down && run_hook(iface->post_down) != 0) {
+ fprintf(out, "ERR post-down hook failed for %s\n", ifname);
+ return -1;
+ }
+ return 0;
+}
+
+static int handle_ifreload(FILE *out, uid_t uid) {
+ (void)uid;
+ log_info("ipc: ifreload (diff-apply)");
+ // For now, just re-parse and apply all auto interfaces
+ // TODO: implement diff-apply: compare old vs new lists
+ // Placeholder: reload config files
+ // We need to free old and re-parse
+ ifaces_free();
+ // Re-register is idempotent, but we need to reload
+ // Use same logic as main.c load_interfaces
+ char *dir = "/etc/network";
+ char path[1024];
+ char stub[1024];
+ FILE *fd;
+ snprintf(path, sizeof(path), "%s/interfaces", dir);
+ if (access(path, R_OK) == 0) {
+ fd = fopen(path, "r");
+ if (fd) {
+ cfg_parse(cfg_ns_get("interfaces"), dir, fd, NULL);
+ fclose(fd);
+ }
+ } else {
+ snprintf(stub, sizeof(stub), "source interfaces.d/*.cnf\n");
+ fd = fmemopen(stub, strlen(stub), "r");
+ if (fd) {
+ cfg_parse(cfg_ns_get("interfaces"), dir, fd, NULL);
+ fclose(fd);
+ }
+ }
+ // Also reload ports?
+ snprintf(path, sizeof(path), "%s/ports", dir);
+ if (access(path, R_OK) == 0) {
+ fd = fopen(path, "r");
+ if (fd) {
+ cfg_parse(cfg_ns_get("ports"), dir, fd, NULL);
+ fclose(fd);
+ }
+ } else {
+ snprintf(stub, sizeof(stub), "source ports.d/*.cnf\n");
+ fd = fmemopen(stub, strlen(stub), "r");
+ if (fd) {
+ cfg_parse(cfg_ns_get("ports"), dir, fd, NULL);
+ fclose(fd);
+ }
+ }
+ return 0;
+}
+
+static int handle_ifquery(FILE *out, const char *ifname) {
+ struct unos_iface *cur;
+ int n = 0;
+ for (cur = ifaces_list(); cur; cur = cur->next) n++;
+ log_info("ipc: ifquery %s (list has %d)", ifname ? ifname : "(all)", n);
+ if (ifname) {
+ cur = ifaces_find(ifname);
+ log_info("ipc: ifquery find %s -> %s", ifname, cur ? "found" : "not found");
+ if (!cur) {
+ fprintf(out, "ERR no such interface: %s\n", ifname);
+ return -1;
+ }
+ // Dump this iface in cnf style
+ fprintf(out, "iface %s\n", cur->name);
+ for (struct iface_addr *a = cur->addrs; a; a = a->next) {
+ fprintf(out, " address %s\n", a->address);
+ if (a->netmask) fprintf(out, " netmask %s\n", a->netmask);
+ }
+ if (cur->gateway) fprintf(out, " gateway %s\n", cur->gateway);
+ if (cur->mtu) fprintf(out, " mtu %d\n", cur->mtu);
+ if (cur->hwaddress) fprintf(out, " hwaddress %s\n", cur->hwaddress);
+ if (cur->vlan_raw_device) fprintf(out, " vlan-raw-device %s\n", cur->vlan_raw_device);
+ if (cur->vlan_id >= 0) fprintf(out, " vlan-id %d\n", cur->vlan_id);
+ if (cur->pre_up) fprintf(out, " pre-up %s\n", cur->pre_up);
+ if (cur->post_up) fprintf(out, " post-up %s\n", cur->post_up);
+ if (cur->pre_down) fprintf(out, " pre-down %s\n", cur->pre_down);
+ if (cur->post_down) fprintf(out, " post-down %s\n", cur->post_down);
+ fprintf(out, "OK\n");
+ } else {
+ for (cur = ifaces_list(); cur; cur = cur->next) {
+ fprintf(out, "iface %s", cur->name);
+ if (cur->method) fprintf(out, " %s", cur->method);
+ fprintf(out, "\n");
+ for (struct iface_addr *a = cur->addrs; a; a = a->next) {
+ fprintf(out, " address %s\n", a->address);
+ }
+ if (cur->gateway) fprintf(out, " gateway %s\n", cur->gateway);
+ }
+ fprintf(out, "OK\n");
+ }
+ return 0;
+}
diff --git a/src/ipc.h b/src/ipc.h
@@ -0,0 +1,13 @@
+#ifndef __UNOS_IPC_H__
+#define __UNOS_IPC_H__
+
+#define UNOS_IPC_PATH "/run/unosd.sock"
+
+// Server side (unosd)
+int ipc_init(void);
+int ipc_fd(void);
+int ipc_handle(void); // accept and handle one client, returns 0 on handled, -1 on error
+void ipc_fini(void);
+void ipc_register(void);
+
+#endif // __UNOS_IPC_H__
diff --git a/src/main.c b/src/main.c
@@ -16,8 +16,10 @@ extern "C" {
#include "rxi/log.h"
#include "config/ports.h"
+#include "config/ifaces.h"
#include "dataplane.h"
#include "dataplane/registry.h"
+#include "ipc.h"
#include "netlink/netlink.h"
#include "util/config.h"
@@ -59,20 +61,19 @@ static void stop_handler(int sig) {
nl_request_stop();
}
-// Port configuration lives in either:
-// <dir>/ports.cnf a single file, or
-// <dir>/ports.d/*.cnf a drop-in directory
+// Port and interface configuration live in either:
+// <dir>/ports, <dir>/interfaces single files, or
+// <dir>/ports.d/*.cnf, <dir>/interfaces.d/*.cnf drop-ins
//
-// Rather than growing the config API with a glob entry point, the drop-in case
-// is handed to the parser as an in-memory config consisting of nothing but an
-// `include` -- the parser already knows how to expand those.
+// Rather than growing the config API, the drop-in case is handed to the parser
+// as an in-memory config consisting of `source` -- the parser expands it.
static int load_ports(const char *dir) {
char path[1024];
char stub[1024];
FILE *fd;
int rc;
- snprintf(path, sizeof(path), "%s/ports.cnf", dir);
+ snprintf(path, sizeof(path), "%s/ports", dir);
if (access(path, R_OK) == 0) {
fd = fopen(path, "r");
@@ -81,7 +82,7 @@ static int load_ports(const char *dir) {
return CFG_RET_ERROR;
}
} else {
- snprintf(stub, sizeof(stub), "include ports.d/*.cnf\n");
+ snprintf(stub, sizeof(stub), "source ports.d/*.cnf\n");
fd = fmemopen(stub, strlen(stub), "r");
if (!fd) {
perror("Could not open in-memory config");
@@ -89,7 +90,7 @@ static int load_ports(const char *dir) {
}
}
- rc = cfg_parse(dir, fd, NULL);
+ rc = cfg_parse(cfg_ns_get("ports"), dir, fd, NULL);
if (rc < 0) {
log_error("Error during reading port configuration from %s", dir);
}
@@ -98,6 +99,38 @@ static int load_ports(const char *dir) {
return rc;
}
+static int load_interfaces(const char *dir) {
+ char path[1024];
+ char stub[1024];
+ FILE *fd;
+ int rc;
+
+ snprintf(path, sizeof(path), "%s/interfaces", dir);
+
+ if (access(path, R_OK) == 0) {
+ fd = fopen(path, "r");
+ if (!fd) {
+ perror("Could not open config file");
+ return CFG_RET_ERROR;
+ }
+ } else {
+ snprintf(stub, sizeof(stub), "source interfaces.d/*.cnf\n");
+ fd = fmemopen(stub, strlen(stub), "r");
+ if (!fd) {
+ perror("Could not open in-memory config");
+ return CFG_RET_ERROR;
+ }
+ }
+
+ rc = cfg_parse(cfg_ns_get("interfaces"), dir, fd, NULL);
+ if (rc < 0) {
+ log_error("Error during reading interfaces configuration from %s", dir);
+ }
+
+ fclose(fd);
+ return rc;
+}
+
static int apply_ports(void) {
const struct dp_ops *dp = dp_active();
struct unos_port *cur;
@@ -120,6 +153,78 @@ static int apply_ports(void) {
return 0;
}
+static int apply_interfaces(void) {
+ struct unos_iface *cur;
+ for (cur = ifaces_list(); cur; cur = cur->next) {
+ if (!cur->auto_flag) continue;
+ // For now, use ipc's handle logic via system ip -- will be replaced with netlink
+ // We call the same helper as ipc ifup would, but directly
+ char cmd[512];
+ // pre-up hook
+ if (cur->pre_up) {
+ log_info("apply_interfaces: pre-up %s: %s", cur->name, cur->pre_up);
+ if (system(cur->pre_up) != 0) {
+ log_warn("pre-up for %s failed", cur->name);
+ continue;
+ }
+ }
+ // VLAN creation if needed (dot notation)
+ if (cur->vlan_id >= 0) {
+ const char *raw = cur->vlan_raw_device;
+ if (!raw) {
+ // Infer from dot notation
+ char *dot = strrchr(cur->name, '.');
+ if (dot) {
+ size_t len = dot - cur->name;
+ char *tmp = malloc(len+1);
+ strncpy(tmp, cur->name, len);
+ tmp[len] = '\0';
+ raw = tmp;
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link add link %s name %s type vlan id %d 2>&1 || true", raw, cur->name, cur->vlan_id);
+ log_info("apply_interfaces: %s", cmd);
+ system(cmd);
+ free(tmp);
+ }
+ } else {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link add link %s name %s type vlan id %d 2>&1 || true", raw, cur->name, cur->vlan_id);
+ log_info("apply_interfaces: %s", cmd);
+ system(cmd);
+ }
+ }
+ // Bring link up
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link set %s up 2>&1 || true", cur->name);
+ log_info("apply_interfaces: %s", cmd);
+ system(cmd);
+ // Add addresses (v4 and v6)
+ struct iface_addr *a;
+ for (a = cur->addrs; a; a = a->next) {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip addr add %s dev %s 2>&1 || true", a->address, cur->name);
+ log_info("apply_interfaces: %s", cmd);
+ system(cmd);
+ }
+ if (cur->gateway) {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip route add default via %s 2>&1 || true", cur->gateway);
+ log_info("apply_interfaces: %s", cmd);
+ system(cmd);
+ }
+ if (cur->mtu) {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link set %s mtu %d 2>&1 || true", cur->name, cur->mtu);
+ log_info("apply_interfaces: %s", cmd);
+ system(cmd);
+ }
+ if (cur->hwaddress) {
+ snprintf(cmd, sizeof(cmd), "/sbin/ip link set %s address %s 2>&1 || true", cur->name, cur->hwaddress);
+ log_info("apply_interfaces: %s", cmd);
+ system(cmd);
+ }
+ if (cur->post_up) {
+ log_info("apply_interfaces: post-up %s: %s", cur->name, cur->post_up);
+ system(cur->post_up);
+ }
+ }
+ return 0;
+}
+
// Atomic publish: write tmp + rename so frr never observes a half file.
static int ready_publish(const char *path) {
char tmp[1024];
@@ -148,7 +253,7 @@ static void ready_remove(const char *path) {
}
int main(int argc, const char **argv) {
- char *config_dir = "/etc/unos";
+ char *config_dir = "/etc/network";
char *dataplane = NULL;
char *loglevel = "info";
char *logfile_path = NULL;
@@ -170,6 +275,8 @@ int main(int argc, const char **argv) {
// Initialize components
ports_register();
+ ifaces_register();
+ ipc_register();
struct argparse argparse;
argparse_init(&argparse, options, usage, 0);
@@ -231,6 +338,17 @@ int main(int argc, const char **argv) {
if (load_ports(config_dir) < 0) {
return 1;
}
+ if (load_interfaces(config_dir) < 0) {
+ return 1;
+ }
+ {
+ int n = 0;
+ for (struct unos_iface *c = ifaces_list(); c; c = c->next) n++;
+ log_info("main: loaded %d interfaces from %s", n, config_dir);
+ for (struct unos_iface *c = ifaces_list(); c; c = c->next) {
+ log_info("main: iface %s method %s auto %d addrs %s", c->name, c->method ? c->method : "(null)", c->auto_flag, c->addrs ? c->addrs->address : "(none)");
+ }
+ }
// Startup order is load-bearing: ports -> backend select -> init ->
// port_apply -> nl_open -> initial resync -> ready -> event loop. frr must
@@ -250,29 +368,44 @@ int main(int argc, const char **argv) {
return 1;
}
+ if (ipc_init() != 0) {
+ log_warn("ipc: init failed, ifup/ifquery will not be available");
+ }
+
apply_ports();
+ // Interfaces are applied via netlink/system ip after ports, before resync
+ // so that the subsequent nl_resync sees them.
nl_fd = nl_open();
if (nl_fd < 0) {
dp_fini();
ports_free();
+ ifaces_free();
+ ipc_fini();
return 1;
}
+ // Apply auto interfaces (needs netlink to be open for link creation, but we use system ip for now)
+ apply_interfaces();
+
// Fail loud: never publish readiness on partial state; runit retries.
if (nl_resync(nl_fd) != 0) {
log_fatal("netlink: initial resync failed");
close(nl_fd);
+ ipc_fini();
dp_fini();
ports_free();
+ ifaces_free();
return 1;
}
if (ready_file && *ready_file) {
if (ready_publish(ready_file) != 0) {
close(nl_fd);
+ ipc_fini();
dp_fini();
ports_free();
+ ifaces_free();
return 1;
}
log_info("ready: published %s", ready_file);
@@ -280,10 +413,12 @@ int main(int argc, const char **argv) {
rc = nl_run(nl_fd, resync_interval);
close(nl_fd);
+ ipc_fini();
ready_remove(ready_file);
dp_fini();
ports_free();
+ ifaces_free();
if (log_file) fclose(log_file);
free(log_path);
diff --git a/src/netlink/netlink.c b/src/netlink/netlink.c
@@ -28,6 +28,7 @@
#include "filter.h"
#include "netlink.h"
+#include "../ipc.h"
#define NL_BUFSZ (64 * 1024)
#define NL_MAX_NH 16
@@ -606,11 +607,21 @@ int nl_run(int fd, int resync_interval_s) {
clock_gettime(CLOCK_MONOTONIC, &last_resync);
while (!nl_stop) {
- struct pollfd pfd = { .fd = fd, .events = POLLIN };
+ struct pollfd pfds[2];
+ int nfds = 1;
struct timespec now;
long wait_ms = 30000;
int pr;
+ pfds[0].fd = fd;
+ pfds[0].events = POLLIN;
+ int ipcfd = ipc_fd();
+ if (ipcfd >= 0) {
+ pfds[1].fd = ipcfd;
+ pfds[1].events = POLLIN;
+ nfds = 2;
+ }
+
clock_gettime(CLOCK_MONOTONIC, &now);
if (resync_interval_s > 0) {
long remain = resync_interval_s * 1000L - elapsed_ms(&last_resync, &now);
@@ -627,7 +638,7 @@ int nl_run(int fd, int resync_interval_s) {
if (remain < wait_ms) wait_ms = remain;
}
- pr = poll(&pfd, 1, (int)wait_ms);
+ pr = poll(pfds, nfds, (int)wait_ms);
if (pr < 0) {
if (errno == EINTR) continue;
log_error("netlink: poll failed: %s", strerror(errno));
@@ -635,6 +646,11 @@ int nl_run(int fd, int resync_interval_s) {
}
if (!pr) continue;
+ if (nfds > 1 && (pfds[1].revents & POLLIN)) {
+ ipc_handle();
+ }
+ if (!(pfds[0].revents & POLLIN)) continue;
+
for (;;) {
struct nlmsghdr *h;
struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
diff --git a/src/util/config.c b/src/util/config.c
@@ -8,23 +8,87 @@
#include "config.h"
+#define CFG_MAX_NS 8
#define CFG_MAX_REG 256
-static int handler_count = 0;
-static const char *handler_name[CFG_MAX_REG];
-static cfg_directive_fn handler_fn[CFG_MAX_REG];
+struct cfg_ns {
+ char name[32];
+ int count;
+ const char *h_name[CFG_MAX_REG];
+ cfg_directive_fn h_fn[CFG_MAX_REG];
+};
-void cfg_register_directive(const char *name, cfg_directive_fn fn) {
- if (handler_count >= CFG_MAX_REG) {
- fprintf(stderr, "config: handler table full, dropping `%s`\n", name);
+static struct cfg_ns ns_tab[CFG_MAX_NS];
+static int ns_count = 0;
+
+static struct cnf_directive *cfg_handle_source_directory(FILE *fd, struct cnf_directive *dir, void *user);
+
+struct cfg_ns *cfg_ns_get(const char *name) {
+ int i;
+ for (i = 0; i < ns_count; i++) {
+ if (!strcmp(ns_tab[i].name, name)) return &ns_tab[i];
+ }
+ if (ns_count >= CFG_MAX_NS) {
+ fprintf(stderr, "config: namespace table full, dropping `%s`\n", name);
+ return NULL;
+ }
+ snprintf(ns_tab[ns_count].name, sizeof(ns_tab[ns_count].name), "%s", name);
+ ns_tab[ns_count].count = 0;
+ struct cfg_ns *ns = &ns_tab[ns_count++];
+ // Every namespace supports source-directory -> source <dir>/* expansion
+ cfg_register_directive(ns, "source-directory", cfg_handle_source_directory);
+ return ns;
+}
+
+void cfg_register_directive(struct cfg_ns *ns, const char *name, cfg_directive_fn fn) {
+ if (!ns) return;
+ if (ns->count >= CFG_MAX_REG) {
+ fprintf(stderr, "config: handler table full for ns `%s`, dropping `%s`\n", ns->name, name);
return;
}
- handler_name[handler_count] = name;
- handler_fn [handler_count] = fn;
- handler_count++;
+ ns->h_name[ns->count] = name;
+ ns->h_fn[ns->count] = fn;
+ ns->count++;
}
-int cfg_parse(const char *wd, FILE *fd, void *user) {
+// source-directory handler: transforms `source-directory <dir>` into `source <dir>/*`
+static struct cnf_directive *cfg_handle_source_directory(FILE *fd, struct cnf_directive *dir, void *user) {
+ (void)fd; (void)user;
+ struct cnf_directive *ndir = NULL;
+ char *pattern = NULL;
+
+ if (dir->argc != 1) {
+ fprintf(stderr, "`source-directory` directive accepts only 1 argument\n");
+ // Return as unknown to let caller handle error? For now treat as error via exit-style?
+ // Keep consistent with other handlers: print and return dir as unknown for outer to error.
+ // But we want to synthesize source, so we need at least 1 arg.
+ // If argc !=1, free and return NULL to signal error via cfg_parse's unknown path?
+ cnf_directive_free(dir);
+ return NULL;
+ }
+
+ // Synthesize a `source <dir>/*` directive
+ // Allocate new directive manually
+ ndir = calloc(1, sizeof(*ndir));
+ ndir->name = strdup("source");
+ ndir->argc = 1;
+ ndir->argv = calloc(1, sizeof(char*));
+ // Append /* if not already ending with /*
+ if (dir->argv[0][strlen(dir->argv[0])-1] == '/') {
+ pattern = malloc(strlen(dir->argv[0]) + 2); // "/" + "*"
+ sprintf(pattern, "%s*", dir->argv[0]);
+ } else {
+ // if dir is like "/etc/network/interfaces.d", we want "/etc/network/interfaces.d/*"
+ pattern = malloc(strlen(dir->argv[0]) + 3);
+ sprintf(pattern, "%s/*", dir->argv[0]);
+ }
+ ndir->argv[0] = pattern;
+ // we own pattern, will be freed via cnf_directive_free later
+ cnf_directive_free(dir);
+ return ndir;
+}
+
+int cfg_parse(struct cfg_ns *ns, const char *wd, FILE *fd, void *user) {
struct cnf_directive *dir = NULL;
glob_t globbuf;
int globflags;
@@ -33,63 +97,80 @@ int cfg_parse(const char *wd, FILE *fd, void *user) {
char *strtmp = NULL;
FILE *nfd;
+ if (!ns) {
+ fprintf(stderr, "config: NULL namespace\n");
+ return CFG_RET_ERROR;
+ }
+
for(;;) {
if (dir) cnf_directive_free(dir);
dir = cnf_directive_read(fd);
if (!dir) break; // EOF = done
-cfg_parse_reparse:
+ cfg_parse_reparse:
- // Core handlers
- if (!strcasecmp("include", dir->name)) {
+ // Core handlers: source and source-directory (source-directory is via handler table, but we also handle source here as built-in)
+ if (!strcasecmp("source", dir->name)) {
globflags = GLOB_ERR;
for ( i = 0 ; i < (int)dir->argc ; i++ ) {
if (dir->argv[i][0] == '/') {
strtmp = calloc(strlen(dir->argv[i])+1, 1);
strcpy(strtmp, dir->argv[i]);
} else {
- strtmp = malloc(snprintf(NULL, 0, "%s/%s", wd, dir->argv[i])+1);
- sprintf(strtmp, "%s/%s", wd, dir->argv[i]);
+ // wd may be NULL for IPC (no filesystem context), then treat as absolute or fail
+ if (wd) {
+ strtmp = malloc(snprintf(NULL, 0, "%s/%s", wd, dir->argv[i])+1);
+ sprintf(strtmp, "%s/%s", wd, dir->argv[i]);
+ } else {
+ strtmp = calloc(strlen(dir->argv[i])+1, 1);
+ strcpy(strtmp, dir->argv[i]);
+ }
}
glob(strtmp, globflags, NULL, &globbuf);
free(strtmp);
globflags = GLOB_ERR | GLOB_APPEND;
}
- for( i = 0 ; i < (int)globbuf.gl_pathc ; i++ ) {
+ for ( i = 0 ; i < (int)globbuf.gl_pathc ; i++ ) {
strtmp = calloc(strlen(globbuf.gl_pathv[i])+1, 1);
strcpy(strtmp, globbuf.gl_pathv[i]);
- dirname(strtmp);
+ char *dname = strdup(strtmp);
+ dirname(dname);
+ // Use wd as dirname for nested relative sources? Keep dname as wd for nested
+ // But for simplicity, use dname
nfd = fopen(globbuf.gl_pathv[i], "r");
if (!nfd) {
perror("Could not open config file for reading");
free(strtmp);
+ free(dname);
globfree(&globbuf);
return CFG_RET_ERROR;
}
- if (cfg_parse(strtmp, nfd, user) < 0) {
+ if (cfg_parse(ns, dname, nfd, user) < 0) {
fprintf(stderr, "Error during reading configuration from %s\n", globbuf.gl_pathv[i]);
fclose(nfd);
free(strtmp);
+ free(dname);
globfree(&globbuf);
return CFG_RET_ERROR;
}
fclose(nfd);
free(strtmp);
+ free(dname);
}
globfree(&globbuf);
continue;
}
// Dynamic handlers
- for(i = 0 ; i < handler_count ; i++) {
- if (!strcasecmp(handler_name[i], dir->name)) {
- dir = handler_fn[i](fd, dir, user);
+ for(i = 0 ; i < ns->count ; i++) {
+ if (!strcasecmp(ns->h_name[i], dir->name)) {
+ dir = ns->h_fn[i](fd, dir, user);
if (dir) goto cfg_parse_reparse;
break;
}
}
- if (i == handler_count) {
+ if (i == ns->count) {
// Here = not found
fprintf(stderr, "Unknown directive: %s\n", dir->name);
return CFG_RET_ERROR;
@@ -101,3 +182,5 @@ cfg_parse_reparse:
return CFG_RET_OK;
}
+
+
diff --git a/src/util/config.h b/src/util/config.h
@@ -13,9 +13,14 @@
// re-dispatch it. Returning NULL means end-of-input was reached.
typedef struct cnf_directive * (*cfg_directive_fn)(FILE *fd, struct cnf_directive *dir, void *user);
-void cfg_register_directive(const char *name, cfg_directive_fn fn);
+struct cfg_ns;
-// `wd` is the directory relative `include` patterns resolve against.
-int cfg_parse(const char *wd, FILE *fd, void *user);
+// Create+get interned namespace (idempotent)
+struct cfg_ns *cfg_ns_get(const char *name);
+
+void cfg_register_directive(struct cfg_ns *ns, const char *name, cfg_directive_fn fn);
+
+// `wd` is the directory relative `source` patterns resolve against.
+int cfg_parse(struct cfg_ns *ns, const char *wd, FILE *fd, void *user);
#endif // __UNOS_UTIL_CONFIG_H__
diff --git a/target/common/Makefile b/target/common/Makefile
@@ -4,8 +4,8 @@ BIN=unosd
SRC:=
SRC+=$(wildcard src/*.c)
-SRC+=$(wildcard src/*/*.c)
-SRC+=$(wildcard src/*/*/*.c)
+SRC+=$(filter-out src/cli/%,$(wildcard src/*/*.c))
+SRC+=$(filter-out src/cli/%,$(wildcard src/*/*/*.c))
CFLAGS?=-Wall -Wextra -O2
LDFLAGS?=