include $(TOPDIR)/rules.mk
PKG_NAME:=netifd
-PKG_RELEASE:=3
+PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL=$(PROJECT_GIT)/project/netifd.git
-PKG_SOURCE_DATE:=2025-10-20
-PKG_SOURCE_VERSION:=777f5942fa7d6245f6ad29daa1daecc400344d37
-PKG_MIRROR_HASH:=7fc56f436faa1a5b05a147febed52c6f58c479125a38c9737736b352cd8d4409
+PKG_SOURCE_DATE:=2026-02-15
+PKG_SOURCE_VERSION:=c6122254eb7003377b67a6ad14d284b69725bbee
+PKG_MIRROR_HASH:=feb62dba4dfecba1e3e758e5e4e822e9776b4104ee133dccfc3feb7ae6c5182d
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
PKG_LICENSE:=GPL-2.0
import * as uci from "uci";
import * as uloop from "uloop";
-import * as ubus from "ubus";
+import * as libubus from "ubus";
import { access, dirname } from "fs";
function ex_handler(e)
}
uloop.guard(ex_handler);
-ubus.guard(ex_handler);
+libubus.guard(ex_handler);
+let ubus = netifd.ubus = libubus.connect();
let wireless;
+let proto_mod;
function uci_ctx()
{
{
let ctx = uci_ctx();
- if (wireless)
- wireless.config_init(ctx);
+ for (let mod in [ wireless, proto_mod ]) {
+ try {
+ mod?.config_init(ctx);
+ } catch (e) {
+ netifd.log(netifd.L_WARNING, `${e}\n${e.stacktrace[0].context}`);
+ }
+ }
}
function config_start()
} else {
netifd.log(netifd.L_WARNING, `Wireless module not found\n`);
}
+
+const proto_module = dirname(sourcepath()) + "/proto.uc";
+if (access(proto_module, "r")) {
+ try {
+ proto_mod = loadfile(proto_module)();
+ } catch (e) {
+ netifd.log(netifd.L_WARNING, `Error loading proto module: ${e}\n${e.stacktrace[0].context}\n`);
+ }
+}
--- /dev/null
+#!/usr/bin/env ucode
+
+import * as libubus from "ubus";
+
+let script_path = ARGV[0];
+let proto_name = ARGV[1];
+let action = ARGV[2];
+let iface_name = ARGV[3];
+let config_json = ARGV[4];
+let device = ARGV[5];
+
+let config;
+try {
+ let blob = json(config_json);
+ let inner = blob?._ucode_config;
+ config = inner ? json(inner) : blob;
+} catch (e) {
+ warn(`Failed to parse config JSON: ${e}\n${e.stacktrace[0].context}\n`);
+ exit(1);
+}
+
+let ubus = libubus.connect();
+if (!ubus) {
+ warn(`Failed to connect to ubus\n`);
+ exit(1);
+}
+
+let notify_path = `network.interface.${iface_name}`;
+
+function proto_notify(data)
+{
+ return ubus.call(notify_path, "notify_proto", data);
+}
+
+let proto = {
+ iface: iface_name,
+ proto: proto_name,
+ config,
+ device,
+
+ notify: proto_notify,
+
+ update_link: function(up, data) {
+ let msg = { action: 0, "link-up": up, ...(data ?? {}) };
+ return proto_notify(msg);
+ },
+
+ run_command: function(argv, env) {
+ let msg = { action: 1, command: argv };
+ if (env)
+ msg.env = env;
+ return proto_notify(msg);
+ },
+
+ kill_command: function(signal) {
+ return proto_notify({ action: 2, signal: signal ?? 15 });
+ },
+
+ error: function(errors) {
+ return proto_notify({ action: 3, error: errors });
+ },
+
+ block_restart: function() {
+ return proto_notify({ action: 4 });
+ },
+
+ set_available: function(available) {
+ return proto_notify({ action: 5, available });
+ },
+
+ add_host_dependency: function(host, ifname) {
+ let msg = { action: 6 };
+ if (host)
+ msg.host = host;
+ if (ifname)
+ msg.ifname = ifname;
+ return proto_notify(msg);
+ },
+
+ setup_failed: function() {
+ return proto_notify({ action: 7 });
+ },
+};
+
+let handlers = {};
+
+let netifd_stub = {
+ add_proto: function(handler) {
+ if (handler?.name)
+ handlers[handler.name] = handler;
+ },
+};
+
+try {
+ include(script_path, { netifd: netifd_stub });
+} catch (e) {
+ warn(`Failed to load proto handler script '${script_path}': ${e}\n${e.stacktrace[0].context}\n`);
+ exit(1);
+}
+
+let handler = handlers[proto_name];
+if (!handler) {
+ warn(`No handler found for protocol '${proto_name}'\n`);
+ exit(1);
+}
+
+if (!handler[action]) {
+ warn(`Handler '${proto_name}' has no '${action}' function\n`);
+ exit(1);
+}
+
+handler[action](proto);
--- /dev/null
+import { sorted_json } from "./utils.uc";
+import { dirname, glob } from "fs";
+
+let ctx;
+
+function proto_config_load(config_fn, section_name)
+{
+ if (!ctx)
+ return null;
+
+ let section_data = ctx.get_all("network", section_name);
+ if (!section_data)
+ return null;
+
+ let config_obj = {
+ iface: section_name,
+ section: section_name,
+ data: section_data,
+ uci: ctx,
+ };
+
+ let result;
+ if (config_fn)
+ result = config_fn(config_obj);
+ else
+ result = section_data;
+
+ return sorted_json(result);
+}
+
+netifd.cb.proto_config_load = proto_config_load;
+
+let base = dirname(sourcepath());
+for (let script in glob(base + "/proto/*.uc")) {
+ try {
+ loadfile(script)();
+ } catch (e) {
+ netifd.log(netifd.L_WARNING,
+ `Error loading proto handler ${script}: ${e}\n${e.stacktrace[0].context}\n`);
+ }
+}
+
+function config_init(uci)
+{
+ ctx = uci;
+ if (!ctx.load("network"))
+ netifd.log(netifd.L_WARNING, `Failed to load network config\n`);
+}
+
+return {
+ config_init,
+};
return ret;
};
+export function sorted_json(value) {
+ let t = type(value);
+
+ if (t == "object") {
+ let parts = [];
+ for (let key in sort(keys(value)))
+ push(parts, sprintf("%J", key) + ":" + sorted_json(value[key]));
+ return "{" + join(",", parts) + "}";
+ }
+
+ if (t == "array") {
+ let parts = [];
+ for (let item in value)
+ push(parts, sorted_json(item));
+ return "[" + join(",", parts) + "]";
+ }
+
+ return sprintf("%J", value);
+};
+
export function is_equal(val1, val2) {
let t1 = type(val1);