Merge pull request #20007 from dhewg/prometheus-node-exporter-ucode
authorEtienne Champetier <champetier.etienne@gmail.com>
Sun, 15 Oct 2023 15:00:44 +0000 (11:00 -0400)
committerGitHub <noreply@github.com>
Sun, 15 Oct 2023 15:00:44 +0000 (11:00 -0400)
prometheus-node-exporter-ucode: add new package

30 files changed:
utils/prometheus-node-exporter-ucode/Makefile [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/conntrack.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/cpu.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/entropy.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/filefd.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/loadavg.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/meminfo.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/netclass.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/netdev.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/selinux.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/time.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/base/uname.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/config [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/extra/netstat.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/extra/wifi.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/init [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/metrics.uc [new file with mode: 0644]
utils/prometheus-node-exporter-ucode/files/run.sh [new file with mode: 0755]
utils/prometheus-node-exporter-ucode/test.sh [new file with mode: 0755]
utils/rpcd-mod-wireguard/Makefile [new file with mode: 0644]
utils/rpcd-mod-wireguard/src/CMakeLists.txt [new file with mode: 0644]
utils/rpcd-mod-wireguard/src/api.c [new file with mode: 0644]
utils/rpcd-mod-wireguard/src/wireguard.c [new file with mode: 0644]
utils/rpcd-mod-wireguard/src/wireguard.h [new file with mode: 0644]

diff --git a/utils/prometheus-node-exporter-ucode/Makefile b/utils/prometheus-node-exporter-ucode/Makefile
new file mode 100644 (file)
index 0000000..3d712c1
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright (C) 2013-2017 OpenWrt.org
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=prometheus-node-exporter-ucode
+PKG_VERSION:=2022.12.02
+PKG_RELEASE:=1
+
+PKG_MAINTAINER:=Andre Heider <a.heider@gmail.com>
+PKG_LICENSE:=Apache-2.0
+
+include $(INCLUDE_DIR)/package.mk
+
+Build/Compile=
+
+define Package/$(PKG_NAME)/Default
+  SECTION:=utils
+  CATEGORY:=Utilities
+  TITLE:=Prometheus node exporter
+  PKGARCH:=all
+endef
+
+define Package/$(PKG_NAME)
+  $(call Package/$(PKG_NAME)/Default)
+  DEPENDS:=+uhttpd +uhttpd-mod-ucode +rpcd +ucode-mod-fs +ucode-mod-ubus
+endef
+
+define Package/$(PKG_NAME)/install
+       $(INSTALL_DIR) $(1)/etc/config
+       $(INSTALL_CONF) ./files/config $(1)/etc/config/$(PKG_NAME)
+       $(INSTALL_DIR) $(1)/etc/init.d
+       $(INSTALL_BIN) ./files/init $(1)/etc/init.d/$(PKG_NAME)
+       $(INSTALL_DIR) $(1)/usr/share/ucode/node-exporter/lib
+       $(INSTALL_DATA) ./files/metrics.uc $(1)/usr/share/ucode/node-exporter/
+       $(INSTALL_DATA) ./files/base/*.uc $(1)/usr/share/ucode/node-exporter/lib/
+       $(INSTALL_DIR) $(1)/usr/bin
+       $(INSTALL_BIN) ./files/run.sh $(1)/usr/bin/$(PKG_NAME)
+endef
+
+define Package/$(PKG_NAME)/conffiles
+/etc/config/$(PKG_NAME)
+endef
+
+define Package/$(PKG_NAME)/description
+  Provides node metrics as Prometheus scraping endpoint.
+
+  This service is a lightweight rewrite in ucode of the offical Prometheus node_exporter.
+endef
+
+$(eval $(call BuildPackage,prometheus-node-exporter-ucode))
+
+define Collector
+  define Package/$(PKG_NAME)-$(1)
+    $$(call Package/$(PKG_NAME)/Default)
+    TITLE+= ($(2))
+    DEPENDS:=$(PKG_NAME) $(3)
+  endef
+
+  define Package/$(PKG_NAME)-$(1)/install
+       $$(INSTALL_DIR) $$(1)/usr/share/ucode/node-exporter/lib
+       $$(INSTALL_DATA) ./files/extra/$(1).uc $$(1)/usr/share/ucode/node-exporter/lib/
+  endef
+
+  $$(eval $$(call BuildPackage,$(PKG_NAME)-$(1)))
+endef
+
+$(eval $(call Collector,dnsmasq,Dnsmasq collector,@dnsmasq))
+$(eval $(call Collector,ltq-dsl,Lantiq/Intel/MaxLinear DSL collector,@ltq-dsl-app))
+$(eval $(call Collector,netstat,netstat collector,))
+$(eval $(call Collector,openwrt,OpenWrt collector,))
+$(eval $(call Collector,snmp6,snmp6 collector,))
+$(eval $(call Collector,uci_dhcp_host,UCI DHCP host collector,))
+$(eval $(call Collector,wifi,Wi-Fi collector,+ucode-mod-nl80211))
+$(eval $(call Collector,wireguard,Wireguard collector,+rpcd-mod-wireguard))
diff --git a/utils/prometheus-node-exporter-ucode/files/base/conntrack.uc b/utils/prometheus-node-exporter-ucode/files/base/conntrack.uc
new file mode 100644 (file)
index 0000000..b3ee3f3
--- /dev/null
@@ -0,0 +1,4 @@
+gauge("node_nf_conntrack_entries")
+       (null, oneline("/proc/sys/net/netfilter/nf_conntrack_count"));
+gauge("node_nf_conntrack_entries_limit")
+       (null, oneline("/proc/sys/net/netfilter/nf_conntrack_max"));
diff --git a/utils/prometheus-node-exporter-ucode/files/base/cpu.uc b/utils/prometheus-node-exporter-ucode/files/base/cpu.uc
new file mode 100644 (file)
index 0000000..574655d
--- /dev/null
@@ -0,0 +1,44 @@
+let f = fs.open("/proc/stat");
+
+if (!f)
+       return false;
+
+const desc = [
+       null,
+       "user",
+       "nice",
+       "system",
+       "idle",
+       "iowait",
+       "irq",
+       "softirq",
+       "steal",
+       "guest",
+       "guest_nice",
+];
+const m_cpu = counter("node_cpu_seconds_total");
+
+let line;
+while (line = nextline(f)) {
+       const x = wsplit(line);
+
+       if (length(x) < 2)
+               continue;
+
+       if (match(x[0], /^cpu\d+/)) {
+               const count = min(length(x), length(desc));
+               for (let i = 1; i < count; i++)
+                       m_cpu({ cpu: x[0], mode: desc[i] }, x[i] / 100.0);
+       } else if (x[0] == "intr")
+               counter("node_intr_total")(null, x[1]);
+       else if (x[0] == "ctxt")
+               counter("node_context_switches_total")(null, x[1]);
+       else if (x[0] == "btime")
+               gauge("node_boot_time_seconds")(null, x[1]);
+       else if (x[0] == "processes")
+               counter("node_forks_total")(null, x[1]);
+       else if (x[0] == "procs_running")
+               gauge("node_procs_running_total")(null, x[1]);
+       else if (x[0] == "procs_blocked")
+               gauge("node_procs_blocked_total")(null, x[1]);
+}
diff --git a/utils/prometheus-node-exporter-ucode/files/base/entropy.uc b/utils/prometheus-node-exporter-ucode/files/base/entropy.uc
new file mode 100644 (file)
index 0000000..2df4426
--- /dev/null
@@ -0,0 +1,4 @@
+gauge("node_entropy_available_bits")
+       (null, oneline("/proc/sys/kernel/random/entropy_avail"));
+gauge("node_entropy_pool_size_bits")
+       (null, oneline("/proc/sys/kernel/random/poolsize"));
diff --git a/utils/prometheus-node-exporter-ucode/files/base/filefd.uc b/utils/prometheus-node-exporter-ucode/files/base/filefd.uc
new file mode 100644 (file)
index 0000000..359cffd
--- /dev/null
@@ -0,0 +1,7 @@
+const x = wsplit(oneline("/proc/sys/fs/file-nr"));
+
+if (length(x) < 3)
+       return false;
+
+gauge("node_filefd_allocated")(null, x[0]);
+gauge("node_filefd_maximum")(null, x[2]);
diff --git a/utils/prometheus-node-exporter-ucode/files/base/loadavg.uc b/utils/prometheus-node-exporter-ucode/files/base/loadavg.uc
new file mode 100644 (file)
index 0000000..ba67daf
--- /dev/null
@@ -0,0 +1,8 @@
+const x = wsplit(oneline("/proc/loadavg"));
+
+if (length(x) < 3)
+       return false;
+
+gauge("node_load1")(null, x[0]);
+gauge("node_load5")(null, x[1]);
+gauge("node_load15")(null, x[2]);
diff --git a/utils/prometheus-node-exporter-ucode/files/base/meminfo.uc b/utils/prometheus-node-exporter-ucode/files/base/meminfo.uc
new file mode 100644 (file)
index 0000000..3cecb12
--- /dev/null
@@ -0,0 +1,24 @@
+let f = fs.open("/proc/meminfo");
+
+if (!f)
+       return false;
+
+let line;
+while (line = nextline(f)) {
+       const x = wsplit(line);
+
+       if (length(x) < 2)
+               continue;
+
+       if (substr(x[0], -1) != ":")
+               continue;
+
+       let name;
+       if (substr(x[0], -2) == "):")
+               name = replace(substr(x[0], 0, -2), "(", "_");
+       else
+               name = substr(x[0], 0, -1);
+
+       gauge(`node_memory_${name}_bytes`)
+               (null, x[2] == "kB" ? x[1] * 1024 : x[1]);
+}
diff --git a/utils/prometheus-node-exporter-ucode/files/base/netclass.uc b/utils/prometheus-node-exporter-ucode/files/base/netclass.uc
new file mode 100644 (file)
index 0000000..10b3cfd
--- /dev/null
@@ -0,0 +1,48 @@
+const root = "/sys/class/net/";
+const devices = fs.lsdir(root);
+
+if (length(devices) < 1)
+       return false;
+
+const m_info = gauge("node_network_info");
+const m_speed = gauge("node_network_speed_bytes");
+const metrics = {
+       addr_assign_type:       gauge("node_network_address_assign_type"),
+       carrier:                gauge("node_network_carrier"),
+       carrier_changes:        counter("node_network_carrier_changes_total"),
+       carrier_down_count:     counter("node_network_carrier_down_changes_total"),
+       carrier_up_count:       counter("node_network_carrier_up_changes_total"),
+       dev_id:                 gauge("node_network_device_id"),
+       dormant:                gauge("node_network_dormant"),
+       flags:                  gauge("node_network_flags"),
+       ifindex:                gauge("node_network_iface_id"),
+       iflink:                 gauge("node_network_iface_link"),
+       link_mode:              gauge("node_network_iface_link_mode"),
+       mtu:                    gauge("node_network_mtu_bytes"),
+       name_assign_type:       gauge("node_network_name_assign_type"),
+       netdev_group:           gauge("node_network_net_dev_group"),
+       type:                   gauge("node_network_protocol_type"),
+       tx_queue_len:           gauge("node_network_transmit_queue_length"),
+};
+
+for (let device in devices) {
+       const devroot = root + device + "/";
+
+       m_info({
+               device,
+               address:        oneline(devroot + "address"),
+               broadcast:      oneline(devroot + "broadcast"),
+               duplex:         oneline(devroot + "duplex"),
+               operstate:      oneline(devroot + "operstate"),
+               ifalias:        oneline(devroot + "ifalias"),
+       }, 1);
+
+       for (let m in metrics) {
+               let line = oneline(devroot + m);
+               metrics[m]({ device }, line);
+       }
+
+       const speed = int(oneline(devroot + "speed"));
+       if (speed > 0)
+                       m_speed({ device }, speed * 1000 * 1000 / 8);
+}
diff --git a/utils/prometheus-node-exporter-ucode/files/base/netdev.uc b/utils/prometheus-node-exporter-ucode/files/base/netdev.uc
new file mode 100644 (file)
index 0000000..f8fc68d
--- /dev/null
@@ -0,0 +1,40 @@
+let f = fs.open("/proc/net/dev");
+
+if (!f)
+       return false;
+
+const m = [
+       null,
+       counter("node_network_receive_bytes_total"),
+       counter("node_network_receive_packets_total"),
+       counter("node_network_receive_errs_total"),
+       counter("node_network_receive_drop_total"),
+       counter("node_network_receive_fifo_total"),
+       counter("node_network_receive_frame_total"),
+       counter("node_network_receive_compressed_total"),
+       counter("node_network_receive_multicast_total"),
+       counter("node_network_transmit_bytes_total"),
+       counter("node_network_transmit_packets_total"),
+       counter("node_network_transmit_errs_total"),
+       counter("node_network_transmit_drop_total"),
+       counter("node_network_transmit_fifo_total"),
+       counter("node_network_transmit_colls_total"),
+       counter("node_network_transmit_carrier_total"),
+       counter("node_network_transmit_compressed_total"),
+];
+
+let line;
+while (line = nextline(f)) {
+       const x = wsplit(ltrim(line), " ");
+
+       if (length(x) < 2)
+               continue;
+
+       if (substr(x[0], -1) != ":")
+               continue;
+
+       const count = min(length(x), length(m));
+       const labels = { device: substr(x[0], 0, -1) };
+       for (let i = 1; i < count; i++)
+               m[i](labels, x[i]);
+}
diff --git a/utils/prometheus-node-exporter-ucode/files/base/selinux.uc b/utils/prometheus-node-exporter-ucode/files/base/selinux.uc
new file mode 100644 (file)
index 0000000..11840a8
--- /dev/null
@@ -0,0 +1,10 @@
+const mode = oneline("/sys/fs/selinux/enforce");
+const enabled = gauge("node_selinux_enabled");
+
+if (mode == null) {
+       enabled(null, 0);
+       return;
+}
+
+enabled(null, 1);
+gauge("node_selinux_current_mode")(null, mode);
diff --git a/utils/prometheus-node-exporter-ucode/files/base/time.uc b/utils/prometheus-node-exporter-ucode/files/base/time.uc
new file mode 100644 (file)
index 0000000..7d13ea8
--- /dev/null
@@ -0,0 +1 @@
+gauge("node_time_seconds")(null, time());
diff --git a/utils/prometheus-node-exporter-ucode/files/base/uname.uc b/utils/prometheus-node-exporter-ucode/files/base/uname.uc
new file mode 100644 (file)
index 0000000..50cb352
--- /dev/null
@@ -0,0 +1,8 @@
+gauge("node_uname_info")({
+       sysname:        oneline("/proc/sys/kernel/ostype"),
+       nodename:       oneline("/proc/sys/kernel/hostname"),
+       release:        oneline("/proc/sys/kernel/osrelease"),
+       version:        oneline("/proc/sys/kernel/version"),
+       machine:        poneline("uname -m"), // TODO lame
+       domainname:     oneline("/proc/sys/kernel/domainname"),
+}, 1);
diff --git a/utils/prometheus-node-exporter-ucode/files/config b/utils/prometheus-node-exporter-ucode/files/config
new file mode 100644 (file)
index 0000000..8741f4a
--- /dev/null
@@ -0,0 +1,7 @@
+config prometheus-node-exporter-ucode 'main'
+       option listen_interface 'loopback'
+       option listen_port '9101'
+       option http_keepalive '70'
+
+config collector 'wifi'
+       option stations '1'
diff --git a/utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc b/utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc
new file mode 100644 (file)
index 0000000..3644b20
--- /dev/null
@@ -0,0 +1,6 @@
+const x = ubus.call("dnsmasq", "metrics");
+if (!x)
+       return false;
+
+for (let i in x)
+       gauge(`dnsmasq_${i}_total`)(null, x[i]);
diff --git a/utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc b/utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc
new file mode 100644 (file)
index 0000000..1644497
--- /dev/null
@@ -0,0 +1,72 @@
+const x = ubus.call("dsl", "metrics");
+
+if (!x)
+       return false;
+
+gauge("dsl_info")({
+       atuc_vendor:            x.atu_c.vendor,
+       atuc_system_vendor:     x.atu_c.system_vendor,
+       chipset:                x.chipset,
+       firmware_version:       x.firmware_version,
+       api_version:            x.api_version,
+       driver_version:         x.driver_version,
+}, 1);
+
+gauge("dsl_line_info")({
+       annex:          x.annex,
+       standard:       x.standard,
+       mode:           x.mode,
+       profile:        x.profile,
+}, 1);
+
+gauge("dsl_up")({ detail: x.state }, x.up);
+gauge("dsl_uptime_seconds")(null, x.uptime);
+
+gauge("dsl_line_attenuation_db")
+       ({ direction: "down" }, x.downstream.latn)
+       ({ direction: "up" }, x.upstream.latn);
+gauge("dsl_signal_attenuation_db")
+       ({ direction: "down" }, x.downstream.satn)
+       ({ direction: "up" }, x.upstream.satn);
+gauge("dsl_signal_to_noise_margin_db")
+       ({ direction: "down" }, x.downstream.snr)
+       ({ direction: "up" }, x.upstream.snr);
+gauge("dsl_aggregated_transmit_power_db")
+       ({ direction: "down" }, x.downstream.actatp)
+       ({ direction: "up" }, x.upstream.actatp);
+
+if (x.downstream.interleave_delay)
+       gauge("dsl_latency_seconds")
+               ({ direction: "down" }, x.downstream.interleave_delay / 1000000.0)
+               ({ direction: "up" }, x.upstream.interleave_delay / 1000000.0);
+gauge("dsl_datarate")
+       ({ direction: "down" }, x.downstream.data_rate)
+       ({ direction: "up" }, x.upstream.data_rate);
+gauge("dsl_max_datarate")
+       ({ direction: "down" }, x.downstream.attndr)
+       ({ direction: "up" }, x.upstream.attndr);
+
+counter("dsl_error_seconds_total")
+       ({ err: "forward error correction", loc: "near" }, x.errors.near.fecs)
+       ({ err: "forward error correction", loc: "far" }, x.errors.far.fecs)
+       ({ err: "errored", loc: "near" }, x.errors.near.es)
+       ({ err: "errored", loc: "far" }, x.errors.far.es)
+       ({ err: "severely errored", loc: "near" }, x.errors.near.ses)
+       ({ err: "severely errored", loc: "far" }, x.errors.far.ses)
+       ({ err: "loss of signal", loc: "near" }, x.errors.near.loss)
+       ({ err: "loss of signal", loc: "far" }, x.errors.far.loss)
+       ({ err: "unavailable", loc: "near" }, x.errors.near.uas)
+       ({ err: "unavailable", loc: "far" }, x.errors.far.uas);
+
+counter("dsl_errors_total")
+       ({ err: "header error code error", loc: "near" }, x.errors.near.hec)
+       ({ err: "header error code error", loc: "far" }, x.errors.far.hec)
+       ({ err: "non pre-emptive crc error", loc: "near" }, x.errors.near.crc_p)
+       ({ err: "non pre-emptive crc error", loc: "far" }, x.errors.far.crc_p)
+       ({ err: "pre-emptive crc error", loc: "near" }, x.errors.near.crcp_p)
+       ({ err: "pre-emptive crc error", loc: "far" }, x.errors.far.crcp_p);
+
+if (x.erb)
+       counter("dsl_erb_total")
+               ({ counter: "sent" }, x.erb.sent)
+               ({ counter: "discarded" }, x.erb.discarded);
diff --git a/utils/prometheus-node-exporter-ucode/files/extra/netstat.uc b/utils/prometheus-node-exporter-ucode/files/extra/netstat.uc
new file mode 100644 (file)
index 0000000..7449305
--- /dev/null
@@ -0,0 +1,30 @@
+function parse(fn) {
+       let f = fs.open(fn);
+
+       if (!f)
+               return false;
+
+       let names, values;
+       while (names = nextline(f), values = nextline(f)) {
+               const name = wsplit(names);
+               const value = wsplit(values);
+
+               if (name[0] != value[0])
+                       continue;
+
+               if (length(name) != length(value))
+                       continue;
+
+               let prefix = substr(name[0], 0, -1);
+               for (let i = 1; i < length(name); i++)
+                       gauge(`node_netstat_${prefix}_${name[i]}`)(null, value[i]);
+       }
+
+       return true;
+}
+
+let n = parse("/proc/net/netstat");
+let s = parse("/proc/net/snmp");
+
+if (!n && !s)
+       return false;
diff --git a/utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc b/utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc
new file mode 100644 (file)
index 0000000..10c15a1
--- /dev/null
@@ -0,0 +1,14 @@
+const x = ubus.call("system", "board");
+
+if (!x)
+       return false;
+
+gauge("node_openwrt_info")({
+       board_name:     x.board_name,
+       id:             x.release.distribution,
+       model:          x.model,
+       release:        x.release.version,
+       revision:       x.release.revision,
+       system:         x.system,
+       target:         x.release.target,
+}, 1);
diff --git a/utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc b/utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc
new file mode 100644 (file)
index 0000000..d440a88
--- /dev/null
@@ -0,0 +1,23 @@
+function parse(fn, device, skipdecl) {
+       let f = fs.open(fn);
+
+       if (!f)
+               return false;
+
+       const labels = { device };
+       let line;
+       while (line = nextline(f)) {
+               const x = wsplit(line);
+
+               if (length(x) < 2)
+                       continue;
+
+               counter(`snmp6_${x[0]}`, null, skipdecl)(labels, x[1]);
+       }
+}
+
+parse("/proc/net/snmp6", "all");
+
+const root = "/proc/net/dev_snmp6/";
+for (let device in fs.lsdir(root))
+       parse(root + device, device, true);
diff --git a/utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc b/utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc
new file mode 100644 (file)
index 0000000..0d55724
--- /dev/null
@@ -0,0 +1,14 @@
+import { cursor } from "uci";
+
+const uci = cursor();
+uci.load("dhcp");
+
+let m = gauge("dhcp_host_info");
+
+uci.foreach('dhcp', `host`, (s) => {
+       m({
+               name: s.name,
+               mac: s.mac,
+               ip: s.ip,
+       }, 1);
+});
diff --git a/utils/prometheus-node-exporter-ucode/files/extra/wifi.uc b/utils/prometheus-node-exporter-ucode/files/extra/wifi.uc
new file mode 100644 (file)
index 0000000..f5f58ef
--- /dev/null
@@ -0,0 +1,118 @@
+import { request, 'const' as wlconst } from 'nl80211';
+
+const x = ubus.call("network.wireless", "status");
+
+if (!x)
+       return false;
+
+const iftypes = [
+       "Unknown",
+       "Ad-Hoc",
+       "Client",
+       "Master",
+       "Master (VLAN)",
+       "WDS",
+       "Monitor",
+       "Mesh Point",
+       "P2P Client",
+       "P2P Go",
+       "P2P Device",
+       "OCB",
+];
+
+let m_radio_info = gauge("wifi_radio_info");
+let m_network_info = gauge("wifi_network_info");
+let m_network_quality = gauge("wifi_network_quality");
+let m_network_bitrate = gauge("wifi_network_bitrate");
+let m_network_noise = gauge("wifi_network_noise_dbm");
+let m_network_signal = gauge("wifi_network_signal_dbm");
+let m_stations_total = counter("wifi_stations_total");
+let m_station_inactive = gauge("wifi_station_inactive_milliseconds");
+let m_station_rx_bytes = counter("wifi_station_receive_bytes_total");
+let m_station_tx_bytes = counter("wifi_station_transmit_bytes_total");
+let m_station_rx_packets = counter("wifi_station_receive_packets_total");
+let m_station_tx_packets = counter("wifi_station_transmit_packets_total");
+let m_station_signal = gauge("wifi_station_signal_dbm");
+let m_station_rx_bitrate = gauge("wifi_station_receive_kilobits_per_second");
+let m_station_tx_bitrate = gauge("wifi_station_transmit_kilobits_per_second");
+let m_station_exp_tp = gauge("wifi_station_expected_throughput_kilobits_per_second");
+
+for (let radio in x) {
+       const rc = x[radio]["config"];
+
+       m_radio_info({
+               radio,
+               htmode: rc["htmode"],
+               channel: rc["channel"],
+               country: rc["country"],
+       } ,1);
+
+       for (let iface in x[radio]["interfaces"]) {
+               const ifname = iface["ifname"];
+               const nc = iface["config"];
+               const wif = request(wlconst.NL80211_CMD_GET_INTERFACE, 0, { dev: ifname });
+
+               if (!wif)
+                       continue;
+
+               m_network_info({
+                       radio,
+                       ifname,
+                       ssid: nc["ssid"] || nc["mesh_id"],
+                       bssid: wif["mac"],
+                       mode: iftypes[wif["iftype"]],
+               }, 1);
+
+               const wsta = request(wlconst.NL80211_CMD_GET_STATION, wlconst.NLM_F_DUMP, { dev: ifname });
+               let signal = 0;
+               let bitrate = 0;
+               const stations = length(wsta) || 0;
+               if (stations) {
+                       for (let sta in wsta) {
+                               signal += sta["sta_info"].signal;
+                               bitrate += sta["sta_info"]["tx_bitrate"].bitrate32;
+                       }
+                       bitrate /= stations * 0.01;
+                       signal /= stations;
+               }
+
+               let labels = { radio, ifname };
+               m_network_bitrate(labels, bitrate || NaN);
+               m_network_signal(labels, signal || NaN);
+               m_network_quality(labels, signal ? 100.0 / 70 * (signal + 110) : NaN);
+
+               const wsur = request(wlconst.NL80211_CMD_GET_SURVEY, wlconst.NLM_F_DUMP, { dev: ifname });
+               let noise = 0;
+               for (let i in wsur) {
+                       if (i["survey_info"]["frequency"] != wif["wiphy_freq"])
+                               continue;
+
+                       noise = i["survey_info"]["noise"];
+                       break;
+               }
+
+               m_network_noise(labels, noise || NaN);
+
+               if (config["stations"] != "1")
+                       continue;
+
+               m_stations_total(labels, stations);
+               if (!stations)
+                       continue;
+
+               for (let sta in wsta) {
+                       labels["mac"] = sta["mac"];
+                       const info = sta["sta_info"];
+
+                       m_station_inactive(labels, info["inactive_time"]);
+                       m_station_rx_bytes(labels, info["rx_bytes64"]);
+                       m_station_tx_bytes(labels, info["tx_bytes64"]);
+                       m_station_rx_packets(labels, info["rx_packets"]);
+                       m_station_tx_packets(labels, info["tx_packets"]);
+                       m_station_signal(labels, info["signal"]);
+                       m_station_rx_bitrate(labels, info["rx_bitrate"]["bitrate32"] * 100);
+                       m_station_tx_bitrate(labels, info["tx_bitrate"]["bitrate32"] * 100);
+                       m_station_exp_tp(labels, info["expected_throughput"]);
+               }
+       }
+}
diff --git a/utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc b/utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc
new file mode 100644 (file)
index 0000000..12ae56a
--- /dev/null
@@ -0,0 +1,49 @@
+import { cursor } from "uci";
+
+const x = ubus.call("wireguard", "status");
+if (!x)
+       return false;
+
+const uci = cursor();
+uci.load("network");
+
+let m_wg_iface_info = gauge("wireguard_interface_info");
+let m_wg_peer_info = gauge("wireguard_peer_info");
+let m_wg_handshake = gauge ("wireguard_latest_handshake_seconds");
+let m_wg_rx = gauge ("wireguard_received_bytes_total");
+let m_wg_tx = gauge ("wireguard_sent_bytes_total");
+
+for (let iface in x) {
+       const wc = x[iface];
+
+       m_wg_iface_info({
+               name: iface,
+               public_key: wc["public_key"],
+               listen_port: wc["listen_port"],
+               fwmark: wc["fwmark"] || NaN,
+       }, 1);
+
+       for (let peer in wc["peers"]) {
+               let description;
+               uci.foreach('network', `wireguard_${iface}`, (s) => {
+                       if (s.public_key == peer)
+                               description = s.description;
+               });
+
+               const pc = wc["peers"][peer];
+
+               m_wg_peer_info({
+                       interface: iface,
+                       public_key: peer,
+                       description,
+                       endpoint: pc["endpoint"],
+                       persistent_keepalive_interval: pc["persistent_keepalive_interval"] || NaN,
+               }, 1);
+
+               const labels = { public_key: peer };
+
+               m_wg_handshake(labels, pc["last_handshake"]);
+               m_wg_rx(labels, pc["rx_bytes"]);
+               m_wg_tx(labels, pc["tx_bytes"]);
+       }
+}
diff --git a/utils/prometheus-node-exporter-ucode/files/init b/utils/prometheus-node-exporter-ucode/files/init
new file mode 100644 (file)
index 0000000..1735236
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2013-2017 OpenWrt.org
+
+START=60
+USE_PROCD=1
+
+_log() {
+       logger -p daemon.info -t prometheus-node-exporter-ucode "$@"
+}
+
+start_service() {
+       . /lib/functions/network.sh
+
+       local interface port bind4 bind6
+
+       config_load prometheus-node-exporter-ucode.main
+       config_get interface "main" listen_interface "loopback"
+       config_get port "main" listen_port 9101
+       config_get keepalive "main" http_keepalive 70
+
+       [ "$interface" = "*" ] || {
+               network_get_ipaddr  bind4 "$interface"
+               network_get_ipaddr6 bind6 "$interface"
+               [ -n "$bind4$bind6" ] || {
+                       _log "defering start until listen interface $interface becomes ready"
+                       return 0
+               }
+       }
+
+       procd_open_instance
+
+       procd_set_param command /usr/sbin/uhttpd -f -c /dev/null -h /dev/null -S -D -o /metrics -O /usr/share/ucode/node-exporter/metrics.uc
+
+       if [ "$interface" = "*" ]; then
+               procd_append_param command -p $port
+       else
+               [ -n "$bind4" ] && procd_append_param command -p $bind4:$port
+               [ -n "$bind6" ] && procd_append_param command -p [$bind6]:$port
+       fi
+       [ $keepalive -gt 0 ] && procd_append_param command -k $keepalive
+
+       procd_add_jail prometheus-node-exporter-ucode log procfs sysfs ubus
+       procd_add_jail_mount "/usr/lib/uhttpd_ucode.so"
+       procd_add_jail_mount "/lib/libubus.so*"
+       procd_add_jail_mount "/lib/libuci.so"
+       procd_add_jail_mount "/usr/lib/ucode"
+       procd_add_jail_mount "/usr/lib/libnl*.so*"
+       procd_add_jail_mount "/usr/share/ucode/node-exporter"
+       procd_add_jail_mount "/etc/config"
+
+       # TODO breaks the dsl collector?
+       #procd_set_param user nobody
+       #procd_set_param group nogroup
+       procd_set_param no_new_privs 1
+
+       procd_set_param stdout 1
+       procd_set_param stderr 1
+       procd_set_param respawn
+
+       procd_close_instance
+}
+
+service_triggers()
+{
+       local interface
+
+       procd_add_reload_trigger "prometheus-node-exporter-ucode"
+
+       config_load prometheus-node-exporter-ucode.main
+       config_get interface "main" listen_interface "loopback"
+
+       [ "$interface" = "*" ] || procd_add_reload_interface_trigger "$interface"
+}
diff --git a/utils/prometheus-node-exporter-ucode/files/metrics.uc b/utils/prometheus-node-exporter-ucode/files/metrics.uc
new file mode 100644 (file)
index 0000000..3dce77a
--- /dev/null
@@ -0,0 +1,227 @@
+{%
+'use strict';
+
+import * as fs from "fs";
+import { connect } from "ubus";
+import { cursor } from "uci";
+
+function debug(...s) {
+       if (global.debug)
+               warn("DEBUG: ", ...s, "\n");
+}
+
+function puts(...s) {
+       return uhttpd.send(...s, "\n");
+}
+
+function govalue(value) {
+       if (value == Infinity)
+               return "+Inf";
+       else if (value == -Infinity)
+               return "-Inf";
+       else if (value != value)
+               return "NaN";
+       else if (type(value) in [ "int", "double" ])
+               return value;
+       else if (type(value) in [ "bool", "string" ])
+               return +value;
+
+       return null;
+}
+
+function metric(name, mtype, help, skipdecl) {
+       let func;
+       let decl = skipdecl == true ? false : true;
+
+       let yield = function(labels, value) {
+               let v = govalue(value);
+
+               if (v == null) {
+                       debug(`skipping metric: unsupported value '${value}' (${name})`);
+                       return func;
+               }
+
+               let labels_str = "";
+               if (length(labels)) {
+                       let sep = "";
+                       let s;
+                       labels_str = "{";
+                       for (let l in labels) {
+                               if (labels[l] == null)
+                                       s = "";
+                               else if (type(labels[l]) == "string") {
+                                       s = labels[l];
+                                       s = replace(labels[l], "\\", "\\\\");
+                                       s = replace(s, "\"", "\\\"");
+                                       s = replace(s, "\n", "\\n");
+                               } else {
+                                       s = govalue(labels[l]);
+
+                                       if (!s)
+                                               continue;
+                               }
+
+                               labels_str += sep + l + "=\"" + s + "\"";
+                               sep = ",";
+                       }
+                       labels_str += "}";
+               }
+
+               if (decl) {
+                       if (help)
+                               puts("# HELP ", name, " ", help);
+                       puts("# TYPE ", name, " ", mtype);
+                       decl = false;
+               }
+
+               puts(name, labels_str, " ", v);
+               return func;
+       };
+
+       func = yield;
+       return func;
+}
+
+function counter(name, help, skipdecl) {
+       return metric(name, "counter", help, skipdecl);
+}
+
+function gauge(name, help, skipdecl) {
+       return metric(name, "gauge", help, skipdecl);
+}
+
+function httpstatus(status) {
+       puts("Status: ", status, "\nContent-Type: text/plain; version=0.0.4; charset=utf-8\n");
+}
+
+function clockdiff(t1, t2) {
+       return (t2[0] - t1[0]) * 1000000000 + t2[1] - t1[1];
+}
+
+let collectors = {};
+
+global.handle_request = function(env) {
+       let scope = {
+               config: null,
+               fs,
+               ubus: connect(),
+               counter,
+               gauge,
+               wsplit: function(line) {
+                       return split(line, /\s+/);
+               },
+               nextline: function(f) {
+                       return rtrim(f.read("line"), "\n");
+               },
+               oneline: function(fn) {
+                       let f = fs.open(fn);
+
+                       if (!f)
+                               return null;
+
+                       return nextline(f);
+               },
+               poneline: function(cmd) {
+                       let f = fs.popen(cmd);
+
+                       if (!f)
+                               return null;
+
+                       return nextline(f);
+               },
+       };
+
+       if (length(collectors) < 1) {
+               httpstatus("404 No Collectors found");
+               return;
+       }
+
+       let cols = [];
+       for (let q in split(env.QUERY_STRING, "&")) {
+               let s = split(q, "=", 2);
+               if (length(s) == 2 && s[0] == "collect") {
+                       if (!(s[1] in collectors)) {
+                               httpstatus(`404 Collector ${s[1]} not found`);
+                               return;
+                       }
+
+                       push(cols, s[1]);
+               }
+       }
+
+       if (length(cols) > 0)
+               cols = uniq(cols);
+       else
+               cols = keys(collectors);
+
+       httpstatus("200 OK");
+
+       let duration = gauge("node_scrape_collector_duration_seconds");
+       let success = gauge("node_scrape_collector_success");
+
+       for (let col in cols) {
+               let ok = false;
+               let t1, t2;
+
+               scope["config"] = collectors[col].config;
+               t1 = clock(true);
+               try {
+                       ok = call(collectors[col].func, null, scope) != false;
+               } catch(e) {
+                       warn(`error running collector '${col}':\n${e.message}\n`);
+               }
+               t2 = clock(true);
+
+               duration({ collector: col }, clockdiff(t1, t2) / 1000000000.0);
+               success({ collector: col }, ok);
+       }
+};
+
+const lib = "/usr/share/ucode/node-exporter/lib";
+const opts = {
+       strict_declarations:    true,
+       raw_mode:               true,
+};
+
+let cols = fs.lsdir(lib, "*.uc");
+for (let col in cols) {
+       let func;
+       let uci = cursor();
+
+       try {
+               func = loadfile(lib + "/" + col, opts);
+       } catch(e) {
+               warn(`error compiling collector '${col}':\n${e.message}\n`);
+               continue;
+       }
+
+       let name = substr(col, 0, -3);
+       let config = uci.get_all("prometheus-node-exporter-ucode", name);
+       if (!config || config[".type"] != "collector")
+               config = {};
+       else {
+               delete config[".anonymous"];
+               delete config[".type"];
+               delete config[".name"];
+       }
+
+       collectors[name] = {
+               func,
+               config,
+       };
+}
+
+warn(`prometheus-node-exporter-ucode now serving requests with ${length(collectors)} collectors\n`);
+
+if (!("uhttpd" in global)) {
+       global.debug = true;
+
+       puts = function(...s) {
+               return print(...s, "\n");
+       };
+
+       handle_request({
+               QUERY_STRING: join("&", map(ARGV, v => "collect=" + v)),
+       });
+}
+%}
diff --git a/utils/prometheus-node-exporter-ucode/files/run.sh b/utils/prometheus-node-exporter-ucode/files/run.sh
new file mode 100755 (executable)
index 0000000..a7cefef
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/bin/ucode -T /usr/share/ucode/node-exporter/metrics.uc $*
diff --git a/utils/prometheus-node-exporter-ucode/test.sh b/utils/prometheus-node-exporter-ucode/test.sh
new file mode 100755 (executable)
index 0000000..e72942b
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+prometheus-node-exporter-ucode time
diff --git a/utils/rpcd-mod-wireguard/Makefile b/utils/rpcd-mod-wireguard/Makefile
new file mode 100644 (file)
index 0000000..9fb7d7e
--- /dev/null
@@ -0,0 +1,31 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=rpcd-mod-wireguard
+PKG_RELEASE=1
+
+PKG_LICENSE:=LGPL-2.1+
+PKG_BUILD_FLAGS:=gc-sections
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/cmake.mk
+
+define Package/rpcd-mod-wireguard
+  SECTION:=libs
+  CATEGORY:=Libraries
+  TITLE:=WireGuard rpcd module
+  DEPENDS:=+rpcd +kmod-wireguard
+  MAINTAINER:=Andre Heider <a.heider@gmail.com>
+endef
+
+define Package/rpcd-mod-wireguard/install
+       $(INSTALL_DIR) $(1)/usr/lib/rpcd
+       $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/rpcd/wireguard.so \
+               $(1)/usr/lib/rpcd/
+endef
+
+define Package/rpcd-mod-wireguard/postinst
+#!/bin/sh
+[ -n "$$IPKG_INSTROOT" ] || /etc/init.d/rpcd reload
+endef
+
+$(eval $(call BuildPackage,rpcd-mod-wireguard))
diff --git a/utils/rpcd-mod-wireguard/src/CMakeLists.txt b/utils/rpcd-mod-wireguard/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..97e06aa
--- /dev/null
@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 2.8.12)
+PROJECT(rpcd-mod-wireguard)
+ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations)
+
+SET(SOURCES wireguard.c api.c)
+
+ADD_LIBRARY(rpcd-mod-wireguard SHARED ${SOURCES})
+
+SET_TARGET_PROPERTIES(rpcd-mod-wireguard PROPERTIES OUTPUT_NAME wireguard PREFIX "")
+INSTALL(TARGETS rpcd-mod-wireguard LIBRARY DESTINATION lib/rpcd)
diff --git a/utils/rpcd-mod-wireguard/src/api.c b/utils/rpcd-mod-wireguard/src/api.c
new file mode 100644 (file)
index 0000000..e80ed53
--- /dev/null
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2023 Andre Heider <a.heider@gmail.com>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
+
+#include <libubus.h>
+
+#include <rpcd/plugin.h>
+
+#include "wireguard.h"
+
+static struct blob_buf buf;
+
+enum {
+       RPC_PK_DEVICE,
+       __RPC_PK_MAX,
+};
+
+static const struct blobmsg_policy rpc_privatekey_policy[__RPC_PK_MAX] = {
+       [RPC_PK_DEVICE] = { .name = "private", .type = BLOBMSG_TYPE_STRING },
+};
+
+static void rpc_wireguard_add_endpoint(const wg_endpoint *endpoint)
+{
+       char host[1025]; // NI_MAXHOST
+       char serv[32]; // NI_MAXSERV
+       char res[sizeof(host) + sizeof(serv) + 4];
+       socklen_t addr_len;
+
+       memset(res, 0, sizeof(res));
+       if (endpoint->addr.sa_family == AF_INET)
+               addr_len = sizeof(struct sockaddr_in);
+       else if (endpoint->addr.sa_family == AF_INET6)
+               addr_len = sizeof(struct sockaddr_in6);
+       else
+               return;
+
+       if (getnameinfo(&endpoint->addr, addr_len, host, sizeof(host), serv, sizeof(serv),
+                       NI_DGRAM | NI_NUMERICHOST | NI_NUMERICSERV))
+               return;
+
+       if (endpoint->addr.sa_family == AF_INET6 && strchr(host, ':'))
+               snprintf(res, sizeof(res), "[%s]:%s", host, serv);
+       else
+               snprintf(res, sizeof(res), "%s:%s", host, serv);
+       res[sizeof(res) - 1] = 0;
+
+       blobmsg_add_string(&buf, "endpoint", res);
+}
+
+static void rpc_wireguard_add_allowedip(const wg_allowedip *allowedip)
+{
+       char res[INET6_ADDRSTRLEN + 4 + 1];
+
+       memset(res, 0, sizeof(res));
+       if (allowedip->family == AF_INET)
+               inet_ntop(AF_INET, &allowedip->ip4, res, INET6_ADDRSTRLEN);
+       else if (allowedip->family == AF_INET6)
+               inet_ntop(AF_INET6, &allowedip->ip6, res, INET6_ADDRSTRLEN);
+       else
+               return;
+
+       if (!res[0])
+               return;
+
+       sprintf(res + strlen(res), "/%u", allowedip->cidr);
+       res[sizeof(res) - 1] = 0;
+
+       blobmsg_add_string(&buf, NULL, res);
+}
+
+static void rpc_wireguard_add_peer(const wg_peer *peer)
+{
+       void *c;
+       struct wg_allowedip *allowedip;
+
+       rpc_wireguard_add_endpoint(&peer->endpoint);
+
+       c = blobmsg_open_array(&buf, "allowed_ips");
+       wg_for_each_allowedip(peer, allowedip)
+               rpc_wireguard_add_allowedip(allowedip);
+       blobmsg_close_array(&buf, c);
+
+       blobmsg_add_u64(&buf, "last_handshake", peer->last_handshake_time.tv_sec);
+       blobmsg_add_u64(&buf, "rx_bytes", peer->rx_bytes);
+       blobmsg_add_u64(&buf, "tx_bytes", peer->tx_bytes);
+       if (peer->persistent_keepalive_interval)
+               blobmsg_add_u16(&buf, "persistent_keepalive_interval", peer->persistent_keepalive_interval);
+}
+
+static void rpc_wireguard_add_device(const wg_device *device)
+{
+       void *c, *d;
+       wg_peer *peer;
+       wg_key_b64_string key;
+
+       blobmsg_add_u32(&buf, "ifindex", device->ifindex);
+
+       if (device->flags & WGDEVICE_HAS_PUBLIC_KEY) {
+               wg_key_to_base64(key, device->public_key);
+               blobmsg_add_string(&buf, "public_key", key);
+       }
+
+       if (device->listen_port)
+               blobmsg_add_u16(&buf, "listen_port", device->listen_port);
+
+       if (device->fwmark)
+               blobmsg_add_u32(&buf, "fwmark", device->fwmark);
+
+       c = blobmsg_open_table(&buf, "peers");
+       wg_for_each_peer(device, peer) {
+               wg_key_to_base64(key, peer->public_key);
+               d = blobmsg_open_table(&buf, key);
+               rpc_wireguard_add_peer(peer);
+               blobmsg_close_table(&buf, d);
+       }
+       blobmsg_close_table(&buf, c);
+}
+
+static int rpc_wireguard_status(struct ubus_context *ctx, struct ubus_object *obj,
+       struct ubus_request_data *req, const char *method, struct blob_attr *msg)
+{
+       void *c;
+       char *device_names, *device_name;
+       size_t len;
+
+       device_names = wg_list_device_names();
+       if (!device_names)
+               return UBUS_STATUS_NOT_FOUND;
+
+       blob_buf_init(&buf, 0);
+
+       wg_for_each_device_name(device_names, device_name, len) {
+               wg_device *device;
+
+               if (wg_get_device(&device, device_name) < 0)
+                       continue;
+
+               c = blobmsg_open_table(&buf, device_name);
+               rpc_wireguard_add_device(device);
+               blobmsg_close_table(&buf, c);
+
+               wg_free_device(device);
+       }
+
+       free(device_names);
+
+       ubus_send_reply(ctx, req, buf.head);
+
+       return UBUS_STATUS_OK;
+}
+
+static int rpc_wireguard_genkey(struct ubus_context *ctx, struct ubus_object *obj,
+       struct ubus_request_data *req, const char *method, struct blob_attr *msg)
+{
+       wg_key private_key, public_key;
+       wg_key_b64_string key;
+
+       wg_generate_private_key(private_key);
+       wg_generate_public_key(public_key, private_key);
+
+       blob_buf_init(&buf, 0);
+       wg_key_to_base64(key, private_key);
+       blobmsg_add_string(&buf, "private", key);
+       wg_key_to_base64(key, public_key);
+       blobmsg_add_string(&buf, "public", key);
+       ubus_send_reply(ctx, req, buf.head);
+
+       return UBUS_STATUS_OK;
+}
+
+static int rpc_wireguard_genpsk(struct ubus_context *ctx, struct ubus_object *obj,
+       struct ubus_request_data *req, const char *method, struct blob_attr *msg)
+{
+       wg_key preshared_key;
+       wg_key_b64_string key;
+
+       wg_generate_preshared_key(preshared_key);
+
+       blob_buf_init(&buf, 0);
+       wg_key_to_base64(key, preshared_key);
+       blobmsg_add_string(&buf, "preshared", key);
+       ubus_send_reply(ctx, req, buf.head);
+
+       return UBUS_STATUS_OK;
+}
+
+static int rpc_wireguard_pubkey(struct ubus_context *ctx, struct ubus_object *obj,
+       struct ubus_request_data *req, const char *method, struct blob_attr *msg)
+{
+       static struct blob_attr *tb[__RPC_PK_MAX];
+       wg_key_b64_string key;
+       wg_key private_key, public_key;
+
+       blobmsg_parse(rpc_privatekey_policy, __RPC_PK_MAX, tb, blob_data(msg), blob_len(msg));
+
+       if (!tb[RPC_PK_DEVICE])
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       if (wg_key_from_base64(private_key, blobmsg_get_string(tb[RPC_PK_DEVICE])))
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       wg_generate_public_key(public_key, private_key);
+       blob_buf_init(&buf, 0);
+       wg_key_to_base64(key, public_key);
+       blobmsg_add_string(&buf, "public", key);
+       ubus_send_reply(ctx, req, buf.head);
+
+       return UBUS_STATUS_OK;
+}
+
+static int rpc_wireguard_api_init(const struct rpc_daemon_ops *ops, struct ubus_context *ctx)
+{
+       static const struct ubus_method wireguard_methods[] = {
+               UBUS_METHOD_NOARG("status", rpc_wireguard_status),
+               UBUS_METHOD_NOARG("genkey", rpc_wireguard_genkey),
+               UBUS_METHOD_NOARG("genpsk", rpc_wireguard_genpsk),
+               UBUS_METHOD("pubkey", rpc_wireguard_pubkey, rpc_privatekey_policy),
+       };
+
+       static struct ubus_object_type wireguard_type =
+               UBUS_OBJECT_TYPE("rpcd-plugin-wireguard", wireguard_methods);
+
+       static struct ubus_object obj = {
+               .name = "wireguard",
+               .type = &wireguard_type,
+               .methods = wireguard_methods,
+               .n_methods = ARRAY_SIZE(wireguard_methods),
+       };
+
+       return ubus_add_object(ctx, &obj);
+}
+
+struct rpc_plugin rpc_plugin = {
+       .init = rpc_wireguard_api_init
+};
diff --git a/utils/rpcd-mod-wireguard/src/wireguard.c b/utils/rpcd-mod-wireguard/src/wireguard.c
new file mode 100644 (file)
index 0000000..4941549
--- /dev/null
@@ -0,0 +1,1755 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ * Copyright (C) 2008-2012 Pablo Neira Ayuso <pablo@netfilter.org>.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/genetlink.h>
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "wireguard.h"
+
+/* wireguard.h netlink uapi: */
+
+#define WG_GENL_NAME "wireguard"
+#define WG_GENL_VERSION 1
+
+enum wg_cmd {
+       WG_CMD_GET_DEVICE,
+       WG_CMD_SET_DEVICE,
+       __WG_CMD_MAX
+};
+
+enum wgdevice_flag {
+       WGDEVICE_F_REPLACE_PEERS = 1U << 0
+};
+enum wgdevice_attribute {
+       WGDEVICE_A_UNSPEC,
+       WGDEVICE_A_IFINDEX,
+       WGDEVICE_A_IFNAME,
+       WGDEVICE_A_PRIVATE_KEY,
+       WGDEVICE_A_PUBLIC_KEY,
+       WGDEVICE_A_FLAGS,
+       WGDEVICE_A_LISTEN_PORT,
+       WGDEVICE_A_FWMARK,
+       WGDEVICE_A_PEERS,
+       __WGDEVICE_A_LAST
+};
+
+enum wgpeer_flag {
+       WGPEER_F_REMOVE_ME = 1U << 0,
+       WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1
+};
+enum wgpeer_attribute {
+       WGPEER_A_UNSPEC,
+       WGPEER_A_PUBLIC_KEY,
+       WGPEER_A_PRESHARED_KEY,
+       WGPEER_A_FLAGS,
+       WGPEER_A_ENDPOINT,
+       WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
+       WGPEER_A_LAST_HANDSHAKE_TIME,
+       WGPEER_A_RX_BYTES,
+       WGPEER_A_TX_BYTES,
+       WGPEER_A_ALLOWEDIPS,
+       WGPEER_A_PROTOCOL_VERSION,
+       __WGPEER_A_LAST
+};
+
+enum wgallowedip_attribute {
+       WGALLOWEDIP_A_UNSPEC,
+       WGALLOWEDIP_A_FAMILY,
+       WGALLOWEDIP_A_IPADDR,
+       WGALLOWEDIP_A_CIDR_MASK,
+       __WGALLOWEDIP_A_LAST
+};
+
+/* libmnl mini library: */
+
+#define MNL_SOCKET_AUTOPID 0
+#define MNL_ALIGNTO 4
+#define MNL_ALIGN(len) (((len)+MNL_ALIGNTO-1) & ~(MNL_ALIGNTO-1))
+#define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr))
+#define MNL_ATTR_HDRLEN MNL_ALIGN(sizeof(struct nlattr))
+
+enum mnl_attr_data_type {
+       MNL_TYPE_UNSPEC,
+       MNL_TYPE_U8,
+       MNL_TYPE_U16,
+       MNL_TYPE_U32,
+       MNL_TYPE_U64,
+       MNL_TYPE_STRING,
+       MNL_TYPE_FLAG,
+       MNL_TYPE_MSECS,
+       MNL_TYPE_NESTED,
+       MNL_TYPE_NESTED_COMPAT,
+       MNL_TYPE_NUL_STRING,
+       MNL_TYPE_BINARY,
+       MNL_TYPE_MAX,
+};
+
+#define mnl_attr_for_each(attr, nlh, offset) \
+       for ((attr) = mnl_nlmsg_get_payload_offset((nlh), (offset)); \
+            mnl_attr_ok((attr), (char *)mnl_nlmsg_get_payload_tail(nlh) - (char *)(attr)); \
+            (attr) = mnl_attr_next(attr))
+
+#define mnl_attr_for_each_nested(attr, nest) \
+       for ((attr) = mnl_attr_get_payload(nest); \
+            mnl_attr_ok((attr), (char *)mnl_attr_get_payload(nest) + mnl_attr_get_payload_len(nest) - (char *)(attr)); \
+            (attr) = mnl_attr_next(attr))
+
+#define mnl_attr_for_each_payload(payload, payload_size) \
+       for ((attr) = (payload); \
+            mnl_attr_ok((attr), (char *)(payload) + payload_size - (char *)(attr)); \
+            (attr) = mnl_attr_next(attr))
+
+#define MNL_CB_ERROR   -1
+#define MNL_CB_STOP    0
+#define MNL_CB_OK      1
+
+typedef int (*mnl_attr_cb_t)(const struct nlattr *attr, void *data);
+typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data);
+
+#ifndef MNL_ARRAY_SIZE
+#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+#endif
+
+static size_t mnl_ideal_socket_buffer_size(void)
+{
+       static size_t size = 0;
+
+       if (size)
+               return size;
+       size = (size_t)sysconf(_SC_PAGESIZE);
+       if (size > 8192)
+               size = 8192;
+       return size;
+}
+
+static size_t mnl_nlmsg_size(size_t len)
+{
+       return len + MNL_NLMSG_HDRLEN;
+}
+
+static struct nlmsghdr *mnl_nlmsg_put_header(void *buf)
+{
+       int len = MNL_ALIGN(sizeof(struct nlmsghdr));
+       struct nlmsghdr *nlh = buf;
+
+       memset(buf, 0, len);
+       nlh->nlmsg_len = len;
+       return nlh;
+}
+
+static void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, size_t size)
+{
+       char *ptr = (char *)nlh + nlh->nlmsg_len;
+       size_t len = MNL_ALIGN(size);
+       nlh->nlmsg_len += len;
+       memset(ptr, 0, len);
+       return ptr;
+}
+
+static void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh)
+{
+       return (void *)nlh + MNL_NLMSG_HDRLEN;
+}
+
+static void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, size_t offset)
+{
+       return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset);
+}
+
+static bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len)
+{
+       return len >= (int)sizeof(struct nlmsghdr) &&
+              nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
+              (int)nlh->nlmsg_len <= len;
+}
+
+static struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, int *len)
+{
+       *len -= MNL_ALIGN(nlh->nlmsg_len);
+       return (struct nlmsghdr *)((void *)nlh + MNL_ALIGN(nlh->nlmsg_len));
+}
+
+static void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh)
+{
+       return (void *)nlh + MNL_ALIGN(nlh->nlmsg_len);
+}
+
+static bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, unsigned int seq)
+{
+       return nlh->nlmsg_seq && seq ? nlh->nlmsg_seq == seq : true;
+}
+
+static bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, unsigned int portid)
+{
+       return nlh->nlmsg_pid && portid ? nlh->nlmsg_pid == portid : true;
+}
+
+static uint16_t mnl_attr_get_type(const struct nlattr *attr)
+{
+       return attr->nla_type & NLA_TYPE_MASK;
+}
+
+static uint16_t mnl_attr_get_payload_len(const struct nlattr *attr)
+{
+       return attr->nla_len - MNL_ATTR_HDRLEN;
+}
+
+static void *mnl_attr_get_payload(const struct nlattr *attr)
+{
+       return (void *)attr + MNL_ATTR_HDRLEN;
+}
+
+static bool mnl_attr_ok(const struct nlattr *attr, int len)
+{
+       return len >= (int)sizeof(struct nlattr) &&
+              attr->nla_len >= sizeof(struct nlattr) &&
+              (int)attr->nla_len <= len;
+}
+
+static struct nlattr *mnl_attr_next(const struct nlattr *attr)
+{
+       return (struct nlattr *)((void *)attr + MNL_ALIGN(attr->nla_len));
+}
+
+static int mnl_attr_type_valid(const struct nlattr *attr, uint16_t max)
+{
+       if (mnl_attr_get_type(attr) > max) {
+               errno = EOPNOTSUPP;
+               return -1;
+       }
+       return 1;
+}
+
+static int __mnl_attr_validate(const struct nlattr *attr,
+                              enum mnl_attr_data_type type, size_t exp_len)
+{
+       uint16_t attr_len = mnl_attr_get_payload_len(attr);
+       const char *attr_data = mnl_attr_get_payload(attr);
+
+       if (attr_len < exp_len) {
+               errno = ERANGE;
+               return -1;
+       }
+       switch(type) {
+       case MNL_TYPE_FLAG:
+               if (attr_len > 0) {
+                       errno = ERANGE;
+                       return -1;
+               }
+               break;
+       case MNL_TYPE_NUL_STRING:
+               if (attr_len == 0) {
+                       errno = ERANGE;
+                       return -1;
+               }
+               if (attr_data[attr_len-1] != '\0') {
+                       errno = EINVAL;
+                       return -1;
+               }
+               break;
+       case MNL_TYPE_STRING:
+               if (attr_len == 0) {
+                       errno = ERANGE;
+                       return -1;
+               }
+               break;
+       case MNL_TYPE_NESTED:
+
+               if (attr_len == 0)
+                       break;
+
+               if (attr_len < MNL_ATTR_HDRLEN) {
+                       errno = ERANGE;
+                       return -1;
+               }
+               break;
+       default:
+
+               break;
+       }
+       if (exp_len && attr_len > exp_len) {
+               errno = ERANGE;
+               return -1;
+       }
+       return 0;
+}
+
+static const size_t mnl_attr_data_type_len[MNL_TYPE_MAX] = {
+       [MNL_TYPE_U8]           = sizeof(uint8_t),
+       [MNL_TYPE_U16]          = sizeof(uint16_t),
+       [MNL_TYPE_U32]          = sizeof(uint32_t),
+       [MNL_TYPE_U64]          = sizeof(uint64_t),
+       [MNL_TYPE_MSECS]        = sizeof(uint64_t),
+};
+
+static int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type)
+{
+       int exp_len;
+
+       if (type >= MNL_TYPE_MAX) {
+               errno = EINVAL;
+               return -1;
+       }
+       exp_len = mnl_attr_data_type_len[type];
+       return __mnl_attr_validate(attr, type, exp_len);
+}
+
+static int mnl_attr_parse(const struct nlmsghdr *nlh, unsigned int offset,
+                         mnl_attr_cb_t cb, void *data)
+{
+       int ret = MNL_CB_OK;
+       const struct nlattr *attr;
+
+       mnl_attr_for_each(attr, nlh, offset)
+               if ((ret = cb(attr, data)) <= MNL_CB_STOP)
+                       return ret;
+       return ret;
+}
+
+static int mnl_attr_parse_nested(const struct nlattr *nested, mnl_attr_cb_t cb,
+                                void *data)
+{
+       int ret = MNL_CB_OK;
+       const struct nlattr *attr;
+
+       mnl_attr_for_each_nested(attr, nested)
+               if ((ret = cb(attr, data)) <= MNL_CB_STOP)
+                       return ret;
+       return ret;
+}
+
+static uint8_t mnl_attr_get_u8(const struct nlattr *attr)
+{
+       return *((uint8_t *)mnl_attr_get_payload(attr));
+}
+
+static uint16_t mnl_attr_get_u16(const struct nlattr *attr)
+{
+       return *((uint16_t *)mnl_attr_get_payload(attr));
+}
+
+static uint32_t mnl_attr_get_u32(const struct nlattr *attr)
+{
+       return *((uint32_t *)mnl_attr_get_payload(attr));
+}
+
+static uint64_t mnl_attr_get_u64(const struct nlattr *attr)
+{
+       uint64_t tmp;
+       memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp));
+       return tmp;
+}
+
+static const char *mnl_attr_get_str(const struct nlattr *attr)
+{
+       return mnl_attr_get_payload(attr);
+}
+
+static void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, size_t len,
+                        const void *data)
+{
+       struct nlattr *attr = mnl_nlmsg_get_payload_tail(nlh);
+       uint16_t payload_len = MNL_ALIGN(sizeof(struct nlattr)) + len;
+       int pad;
+
+       attr->nla_type = type;
+       attr->nla_len = payload_len;
+       memcpy(mnl_attr_get_payload(attr), data, len);
+       nlh->nlmsg_len += MNL_ALIGN(payload_len);
+       pad = MNL_ALIGN(len) - len;
+       if (pad > 0)
+               memset(mnl_attr_get_payload(attr) + len, 0, pad);
+}
+
+static void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, uint16_t data)
+{
+       mnl_attr_put(nlh, type, sizeof(uint16_t), &data);
+}
+
+static void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, uint32_t data)
+{
+       mnl_attr_put(nlh, type, sizeof(uint32_t), &data);
+}
+
+static void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, const char *data)
+{
+       mnl_attr_put(nlh, type, strlen(data)+1, data);
+}
+
+static struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, uint16_t type)
+{
+       struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh);
+
+       start->nla_type = NLA_F_NESTED | type;
+       nlh->nlmsg_len += MNL_ALIGN(sizeof(struct nlattr));
+       return start;
+}
+
+static bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen,
+                              uint16_t type, size_t len, const void *data)
+{
+       if (nlh->nlmsg_len + MNL_ATTR_HDRLEN + MNL_ALIGN(len) > buflen)
+               return false;
+       mnl_attr_put(nlh, type, len, data);
+       return true;
+}
+
+static bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen,
+                                 uint16_t type, uint8_t data)
+{
+       return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data);
+}
+
+static bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen,
+                                  uint16_t type, uint16_t data)
+{
+       return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data);
+}
+
+static bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen,
+                                  uint16_t type, uint32_t data)
+{
+       return mnl_attr_put_check(nlh, buflen, type, sizeof(uint32_t), &data);
+}
+
+static struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, size_t buflen,
+                                               uint16_t type)
+{
+       if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > buflen)
+               return NULL;
+       return mnl_attr_nest_start(nlh, type);
+}
+
+static void mnl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *start)
+{
+       start->nla_len = mnl_nlmsg_get_payload_tail(nlh) - (void *)start;
+}
+
+static void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start)
+{
+       nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start;
+}
+
+static int mnl_cb_noop(__attribute__((unused)) const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
+{
+       return MNL_CB_OK;
+}
+
+static int mnl_cb_error(const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
+{
+       const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+
+       if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {
+               errno = EBADMSG;
+               return MNL_CB_ERROR;
+       }
+
+       if (err->error < 0)
+               errno = -err->error;
+       else
+               errno = err->error;
+
+       return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnl_cb_stop(__attribute__((unused)) const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
+{
+       return MNL_CB_STOP;
+}
+
+static const mnl_cb_t default_cb_array[NLMSG_MIN_TYPE] = {
+       [NLMSG_NOOP]    = mnl_cb_noop,
+       [NLMSG_ERROR]   = mnl_cb_error,
+       [NLMSG_DONE]    = mnl_cb_stop,
+       [NLMSG_OVERRUN] = mnl_cb_noop,
+};
+
+static int __mnl_cb_run(const void *buf, size_t numbytes,
+                       unsigned int seq, unsigned int portid,
+                       mnl_cb_t cb_data, void *data,
+                       const mnl_cb_t *cb_ctl_array,
+                       unsigned int cb_ctl_array_len)
+{
+       int ret = MNL_CB_OK, len = numbytes;
+       const struct nlmsghdr *nlh = buf;
+
+       while (mnl_nlmsg_ok(nlh, len)) {
+
+               if (!mnl_nlmsg_portid_ok(nlh, portid)) {
+                       errno = ESRCH;
+                       return -1;
+               }
+
+               if (!mnl_nlmsg_seq_ok(nlh, seq)) {
+                       errno = EPROTO;
+                       return -1;
+               }
+
+               if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) {
+                       errno = EINTR;
+                       return -1;
+               }
+
+               if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
+                       if (cb_data){
+                               ret = cb_data(nlh, data);
+                               if (ret <= MNL_CB_STOP)
+                                       goto out;
+                       }
+               } else if (nlh->nlmsg_type < cb_ctl_array_len) {
+                       if (cb_ctl_array && cb_ctl_array[nlh->nlmsg_type]) {
+                               ret = cb_ctl_array[nlh->nlmsg_type](nlh, data);
+                               if (ret <= MNL_CB_STOP)
+                                       goto out;
+                       }
+               } else if (default_cb_array[nlh->nlmsg_type]) {
+                       ret = default_cb_array[nlh->nlmsg_type](nlh, data);
+                       if (ret <= MNL_CB_STOP)
+                               goto out;
+               }
+               nlh = mnl_nlmsg_next(nlh, &len);
+       }
+out:
+       return ret;
+}
+
+static int mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq,
+                      unsigned int portid, mnl_cb_t cb_data, void *data,
+                      const mnl_cb_t *cb_ctl_array, unsigned int cb_ctl_array_len)
+{
+       return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data,
+                           cb_ctl_array, cb_ctl_array_len);
+}
+
+static int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq,
+                     unsigned int portid, mnl_cb_t cb_data, void *data)
+{
+       return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, NULL, 0);
+}
+
+struct mnl_socket {
+       int                     fd;
+       struct sockaddr_nl      addr;
+};
+
+static unsigned int mnl_socket_get_portid(const struct mnl_socket *nl)
+{
+       return nl->addr.nl_pid;
+}
+
+static struct mnl_socket *__mnl_socket_open(int bus, int flags)
+{
+       struct mnl_socket *nl;
+
+       nl = calloc(1, sizeof(struct mnl_socket));
+       if (nl == NULL)
+               return NULL;
+
+       nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus);
+       if (nl->fd == -1) {
+               free(nl);
+               return NULL;
+       }
+
+       return nl;
+}
+
+static struct mnl_socket *mnl_socket_open(int bus)
+{
+       return __mnl_socket_open(bus, 0);
+}
+
+static int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid)
+{
+       int ret;
+       socklen_t addr_len;
+
+       nl->addr.nl_family = AF_NETLINK;
+       nl->addr.nl_groups = groups;
+       nl->addr.nl_pid = pid;
+
+       ret = bind(nl->fd, (struct sockaddr *) &nl->addr, sizeof (nl->addr));
+       if (ret < 0)
+               return ret;
+
+       addr_len = sizeof(nl->addr);
+       ret = getsockname(nl->fd, (struct sockaddr *) &nl->addr, &addr_len);
+       if (ret < 0)
+               return ret;
+
+       if (addr_len != sizeof(nl->addr)) {
+               errno = EINVAL;
+               return -1;
+       }
+       if (nl->addr.nl_family != AF_NETLINK) {
+               errno = EINVAL;
+               return -1;
+       }
+       return 0;
+}
+
+static ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *buf,
+                                size_t len)
+{
+       static const struct sockaddr_nl snl = {
+               .nl_family = AF_NETLINK
+       };
+       return sendto(nl->fd, buf, len, 0,
+                     (struct sockaddr *) &snl, sizeof(snl));
+}
+
+static ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf,
+                                  size_t bufsiz)
+{
+       ssize_t ret;
+       struct sockaddr_nl addr;
+       struct iovec iov = {
+               .iov_base       = buf,
+               .iov_len        = bufsiz,
+       };
+       struct msghdr msg = {
+               .msg_name       = &addr,
+               .msg_namelen    = sizeof(struct sockaddr_nl),
+               .msg_iov        = &iov,
+               .msg_iovlen     = 1,
+               .msg_control    = NULL,
+               .msg_controllen = 0,
+               .msg_flags      = 0,
+       };
+       ret = recvmsg(nl->fd, &msg, 0);
+       if (ret == -1)
+               return ret;
+
+       if (msg.msg_flags & MSG_TRUNC) {
+               errno = ENOSPC;
+               return -1;
+       }
+       if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {
+               errno = EINVAL;
+               return -1;
+       }
+       return ret;
+}
+
+static int mnl_socket_close(struct mnl_socket *nl)
+{
+       int ret = close(nl->fd);
+       free(nl);
+       return ret;
+}
+
+/* mnlg mini library: */
+
+struct mnlg_socket {
+       struct mnl_socket *nl;
+       char *buf;
+       uint16_t id;
+       uint8_t version;
+       unsigned int seq;
+       unsigned int portid;
+};
+
+static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+                                          uint16_t flags, uint16_t id,
+                                          uint8_t version)
+{
+       struct nlmsghdr *nlh;
+       struct genlmsghdr *genl;
+
+       nlh = mnl_nlmsg_put_header(nlg->buf);
+       nlh->nlmsg_type = id;
+       nlh->nlmsg_flags = flags;
+       nlg->seq = time(NULL);
+       nlh->nlmsg_seq = nlg->seq;
+
+       genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+       genl->cmd = cmd;
+       genl->version = version;
+
+       return nlh;
+}
+
+static struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+                                        uint16_t flags)
+{
+       return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version);
+}
+
+static int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh)
+{
+       return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);
+}
+
+static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data)
+{
+       (void)nlh;
+       (void)data;
+       return MNL_CB_OK;
+}
+
+static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data)
+{
+       const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+       (void)data;
+
+       if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {
+               errno = EBADMSG;
+               return MNL_CB_ERROR;
+       }
+       /* Netlink subsystems returns the errno value with different signess */
+       if (err->error < 0)
+               errno = -err->error;
+       else
+               errno = err->error;
+
+       return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data)
+{
+       (void)data;
+       if (nlh->nlmsg_flags & NLM_F_MULTI && nlh->nlmsg_len == mnl_nlmsg_size(sizeof(int))) {
+               int error = *(int *)mnl_nlmsg_get_payload(nlh);
+               /* Netlink subsystems returns the errno value with different signess */
+               if (error < 0)
+                       errno = -error;
+               else
+                       errno = error;
+
+               return error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+       }
+       return MNL_CB_STOP;
+}
+
+static const mnl_cb_t mnlg_cb_array[] = {
+       [NLMSG_NOOP]    = mnlg_cb_noop,
+       [NLMSG_ERROR]   = mnlg_cb_error,
+       [NLMSG_DONE]    = mnlg_cb_stop,
+       [NLMSG_OVERRUN] = mnlg_cb_noop,
+};
+
+static int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data)
+{
+       int err;
+
+       do {
+               err = mnl_socket_recvfrom(nlg->nl, nlg->buf,
+                                         mnl_ideal_socket_buffer_size());
+               if (err <= 0)
+                       break;
+               err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,
+                                 data_cb, data, mnlg_cb_array, MNL_ARRAY_SIZE(mnlg_cb_array));
+       } while (err > 0);
+
+       return err;
+}
+
+static int get_family_id_attr_cb(const struct nlattr *attr, void *data)
+{
+       const struct nlattr **tb = data;
+       int type = mnl_attr_get_type(attr);
+
+       if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+               return MNL_CB_ERROR;
+
+       if (type == CTRL_ATTR_FAMILY_ID &&
+           mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
+               return MNL_CB_ERROR;
+       tb[type] = attr;
+       return MNL_CB_OK;
+}
+
+static int get_family_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+       uint16_t *p_id = data;
+       struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };
+
+       mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, tb);
+       if (!tb[CTRL_ATTR_FAMILY_ID])
+               return MNL_CB_ERROR;
+       *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+       return MNL_CB_OK;
+}
+
+static struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version)
+{
+       struct mnlg_socket *nlg;
+       struct nlmsghdr *nlh;
+       int err;
+
+       nlg = malloc(sizeof(*nlg));
+       if (!nlg)
+               return NULL;
+       nlg->id = 0;
+
+       err = -ENOMEM;
+       nlg->buf = malloc(mnl_ideal_socket_buffer_size());
+       if (!nlg->buf)
+               goto err_buf_alloc;
+
+       nlg->nl = mnl_socket_open(NETLINK_GENERIC);
+       if (!nlg->nl) {
+               err = -errno;
+               goto err_mnl_socket_open;
+       }
+
+       if (mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+               err = -errno;
+               goto err_mnl_socket_bind;
+       }
+
+       nlg->portid = mnl_socket_get_portid(nlg->nl);
+
+       nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+                                NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+       mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);
+
+       if (mnlg_socket_send(nlg, nlh) < 0) {
+               err = -errno;
+               goto err_mnlg_socket_send;
+       }
+
+       errno = 0;
+       if (mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id) < 0) {
+               errno = errno == ENOENT ? EPROTONOSUPPORT : errno;
+               err = errno ? -errno : -ENOSYS;
+               goto err_mnlg_socket_recv_run;
+       }
+
+       nlg->version = version;
+       errno = 0;
+       return nlg;
+
+err_mnlg_socket_recv_run:
+err_mnlg_socket_send:
+err_mnl_socket_bind:
+       mnl_socket_close(nlg->nl);
+err_mnl_socket_open:
+       free(nlg->buf);
+err_buf_alloc:
+       free(nlg);
+       errno = -err;
+       return NULL;
+}
+
+static void mnlg_socket_close(struct mnlg_socket *nlg)
+{
+       mnl_socket_close(nlg->nl);
+       free(nlg->buf);
+       free(nlg);
+}
+
+/* wireguard-specific parts: */
+
+struct string_list {
+       char *buffer;
+       size_t len;
+       size_t cap;
+};
+
+static int string_list_add(struct string_list *list, const char *str)
+{
+       size_t len = strlen(str) + 1;
+
+       if (len == 1)
+               return 0;
+
+       if (len >= list->cap - list->len) {
+               char *new_buffer;
+               size_t new_cap = list->cap * 2;
+
+               if (new_cap <  list->len +len + 1)
+                       new_cap = list->len + len + 1;
+               new_buffer = realloc(list->buffer, new_cap);
+               if (!new_buffer)
+                       return -errno;
+               list->buffer = new_buffer;
+               list->cap = new_cap;
+       }
+       memcpy(list->buffer + list->len, str, len);
+       list->len += len;
+       list->buffer[list->len] = '\0';
+       return 0;
+}
+
+struct interface {
+       const char *name;
+       bool is_wireguard;
+};
+
+static int parse_linkinfo(const struct nlattr *attr, void *data)
+{
+       struct interface *interface = data;
+
+       if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr)))
+               interface->is_wireguard = true;
+       return MNL_CB_OK;
+}
+
+static int parse_infomsg(const struct nlattr *attr, void *data)
+{
+       struct interface *interface = data;
+
+       if (mnl_attr_get_type(attr) == IFLA_LINKINFO)
+               return mnl_attr_parse_nested(attr, parse_linkinfo, data);
+       else if (mnl_attr_get_type(attr) == IFLA_IFNAME)
+               interface->name = mnl_attr_get_str(attr);
+       return MNL_CB_OK;
+}
+
+static int read_devices_cb(const struct nlmsghdr *nlh, void *data)
+{
+       struct string_list *list = data;
+       struct interface interface = { 0 };
+       int ret;
+
+       ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, &interface);
+       if (ret != MNL_CB_OK)
+               return ret;
+       if (interface.name && interface.is_wireguard)
+               ret = string_list_add(list, interface.name);
+       if (ret < 0)
+               return ret;
+       if (nlh->nlmsg_type != NLMSG_DONE)
+               return MNL_CB_OK + 1;
+       return MNL_CB_OK;
+}
+
+static int fetch_device_names(struct string_list *list)
+{
+       struct mnl_socket *nl = NULL;
+       char *rtnl_buffer = NULL;
+       size_t message_len;
+       unsigned int portid, seq;
+       ssize_t len;
+       int ret = 0;
+       struct nlmsghdr *nlh;
+       struct ifinfomsg *ifm;
+
+       ret = -ENOMEM;
+       rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1);
+       if (!rtnl_buffer)
+               goto cleanup;
+
+       nl = mnl_socket_open(NETLINK_ROUTE);
+       if (!nl) {
+               ret = -errno;
+               goto cleanup;
+       }
+
+       if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+               ret = -errno;
+               goto cleanup;
+       }
+
+       seq = time(NULL);
+       portid = mnl_socket_get_portid(nl);
+       nlh = mnl_nlmsg_put_header(rtnl_buffer);
+       nlh->nlmsg_type = RTM_GETLINK;
+       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+       nlh->nlmsg_seq = seq;
+       ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+       ifm->ifi_family = AF_UNSPEC;
+       message_len = nlh->nlmsg_len;
+
+       if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {
+               ret = -errno;
+               goto cleanup;
+       }
+
+another:
+       if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, mnl_ideal_socket_buffer_size())) < 0) {
+               ret = -errno;
+               goto cleanup;
+       }
+       if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, list)) < 0) {
+               /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed
+                * during the dump. That's unfortunate, but is pretty common on busy
+                * systems that are adding and removing tunnels all the time. Rather
+                * than retrying, potentially indefinitely, we just work with the
+                * partial results. */
+               if (errno != EINTR) {
+                       ret = -errno;
+                       goto cleanup;
+               }
+       }
+       if (len == MNL_CB_OK + 1)
+               goto another;
+       ret = 0;
+
+cleanup:
+       free(rtnl_buffer);
+       if (nl)
+               mnl_socket_close(nl);
+       return ret;
+}
+
+static int add_del_iface(const char *ifname, bool add)
+{
+       struct mnl_socket *nl = NULL;
+       char *rtnl_buffer;
+       ssize_t len;
+       int ret;
+       struct nlmsghdr *nlh;
+       struct ifinfomsg *ifm;
+       struct nlattr *nest;
+
+       rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1);
+       if (!rtnl_buffer) {
+               ret = -ENOMEM;
+               goto cleanup;
+       }
+
+       nl = mnl_socket_open(NETLINK_ROUTE);
+       if (!nl) {
+               ret = -errno;
+               goto cleanup;
+       }
+
+       if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+               ret = -errno;
+               goto cleanup;
+       }
+
+       nlh = mnl_nlmsg_put_header(rtnl_buffer);
+       nlh->nlmsg_type = add ? RTM_NEWLINK : RTM_DELLINK;
+       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | (add ? NLM_F_CREATE | NLM_F_EXCL : 0);
+       nlh->nlmsg_seq = time(NULL);
+       ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+       ifm->ifi_family = AF_UNSPEC;
+       mnl_attr_put_strz(nlh, IFLA_IFNAME, ifname);
+       nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
+       mnl_attr_put_strz(nlh, IFLA_INFO_KIND, WG_GENL_NAME);
+       mnl_attr_nest_end(nlh, nest);
+
+       if (mnl_socket_sendto(nl, rtnl_buffer, nlh->nlmsg_len) < 0) {
+               ret = -errno;
+               goto cleanup;
+       }
+       if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, mnl_ideal_socket_buffer_size())) < 0) {
+               ret = -errno;
+               goto cleanup;
+       }
+       if (mnl_cb_run(rtnl_buffer, len, nlh->nlmsg_seq, mnl_socket_get_portid(nl), NULL, NULL) < 0) {
+               ret = -errno;
+               goto cleanup;
+       }
+       ret = 0;
+
+cleanup:
+       free(rtnl_buffer);
+       if (nl)
+               mnl_socket_close(nl);
+       return ret;
+}
+
+int wg_set_device(wg_device *dev)
+{
+       int ret = 0;
+       wg_peer *peer = NULL;
+       wg_allowedip *allowedip = NULL;
+       struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest;
+       struct nlmsghdr *nlh;
+       struct mnlg_socket *nlg;
+
+       nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+       if (!nlg)
+               return -errno;
+
+again:
+       nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK);
+       mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name);
+
+       if (!peer) {
+               uint32_t flags = 0;
+
+               if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
+                       mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key);
+               if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+                       mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port);
+               if (dev->flags & WGDEVICE_HAS_FWMARK)
+                       mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark);
+               if (dev->flags & WGDEVICE_REPLACE_PEERS)
+                       flags |= WGDEVICE_F_REPLACE_PEERS;
+               if (flags)
+                       mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags);
+       }
+       if (!dev->first_peer)
+               goto send;
+       peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL;
+       peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS);
+       for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
+               uint32_t flags = 0;
+
+               peer_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0);
+               if (!peer_nest)
+                       goto toobig_peers;
+               if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key))
+                       goto toobig_peers;
+               if (peer->flags & WGPEER_REMOVE_ME)
+                       flags |= WGPEER_F_REMOVE_ME;
+               if (!allowedip) {
+                       if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+                               flags |= WGPEER_F_REPLACE_ALLOWEDIPS;
+                       if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
+                               if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))
+                                       goto toobig_peers;
+                       }
+                       if (peer->endpoint.addr.sa_family == AF_INET) {
+                               if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))
+                                       goto toobig_peers;
+                       } else if (peer->endpoint.addr.sa_family == AF_INET6) {
+                               if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))
+                                       goto toobig_peers;
+                       }
+                       if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
+                               if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
+                                       goto toobig_peers;
+                       }
+               }
+               if (flags) {
+                       if (!mnl_attr_put_u32_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_FLAGS, flags))
+                               goto toobig_peers;
+               }
+               if (peer->first_allowedip) {
+                       if (!allowedip)
+                               allowedip = peer->first_allowedip;
+                       allowedips_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ALLOWEDIPS);
+                       if (!allowedips_nest)
+                               goto toobig_allowedips;
+                       for (; allowedip; allowedip = allowedip->next_allowedip) {
+                               allowedip_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0);
+                               if (!allowedip_nest)
+                                       goto toobig_allowedips;
+                               if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_FAMILY, allowedip->family))
+                                       goto toobig_allowedips;
+                               if (allowedip->family == AF_INET) {
+                                       if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))
+                                               goto toobig_allowedips;
+                               } else if (allowedip->family == AF_INET6) {
+                                       if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
+                                               goto toobig_allowedips;
+                               }
+                               if (!mnl_attr_put_u8_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
+                                       goto toobig_allowedips;
+                               mnl_attr_nest_end(nlh, allowedip_nest);
+                               allowedip_nest = NULL;
+                       }
+                       mnl_attr_nest_end(nlh, allowedips_nest);
+                       allowedips_nest = NULL;
+               }
+
+               mnl_attr_nest_end(nlh, peer_nest);
+               peer_nest = NULL;
+       }
+       mnl_attr_nest_end(nlh, peers_nest);
+       peers_nest = NULL;
+       goto send;
+toobig_allowedips:
+       if (allowedip_nest)
+               mnl_attr_nest_cancel(nlh, allowedip_nest);
+       if (allowedips_nest)
+               mnl_attr_nest_end(nlh, allowedips_nest);
+       mnl_attr_nest_end(nlh, peer_nest);
+       mnl_attr_nest_end(nlh, peers_nest);
+       goto send;
+toobig_peers:
+       if (peer_nest)
+               mnl_attr_nest_cancel(nlh, peer_nest);
+       mnl_attr_nest_end(nlh, peers_nest);
+       goto send;
+send:
+       if (mnlg_socket_send(nlg, nlh) < 0) {
+               ret = -errno;
+               goto out;
+       }
+       errno = 0;
+       if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {
+               ret = errno ? -errno : -EINVAL;
+               goto out;
+       }
+       if (peer)
+               goto again;
+
+out:
+       mnlg_socket_close(nlg);
+       errno = -ret;
+       return ret;
+}
+
+static int parse_allowedip(const struct nlattr *attr, void *data)
+{
+       wg_allowedip *allowedip = data;
+
+       switch (mnl_attr_get_type(attr)) {
+       case WGALLOWEDIP_A_UNSPEC:
+               break;
+       case WGALLOWEDIP_A_FAMILY:
+               if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+                       allowedip->family = mnl_attr_get_u16(attr);
+               break;
+       case WGALLOWEDIP_A_IPADDR:
+               if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4))
+                       memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4));
+               else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6))
+                       memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6));
+               break;
+       case WGALLOWEDIP_A_CIDR_MASK:
+               if (!mnl_attr_validate(attr, MNL_TYPE_U8))
+                       allowedip->cidr = mnl_attr_get_u8(attr);
+               break;
+       }
+
+       return MNL_CB_OK;
+}
+
+static int parse_allowedips(const struct nlattr *attr, void *data)
+{
+       wg_peer *peer = data;
+       wg_allowedip *new_allowedip = calloc(1, sizeof(wg_allowedip));
+       int ret;
+
+       if (!new_allowedip)
+               return MNL_CB_ERROR;
+       if (!peer->first_allowedip)
+               peer->first_allowedip = peer->last_allowedip = new_allowedip;
+       else {
+               peer->last_allowedip->next_allowedip = new_allowedip;
+               peer->last_allowedip = new_allowedip;
+       }
+       ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip);
+       if (!ret)
+               return ret;
+       if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128))) {
+               errno = EAFNOSUPPORT;
+               return MNL_CB_ERROR;
+       }
+       return MNL_CB_OK;
+}
+
+bool wg_key_is_zero(const wg_key key)
+{
+       volatile uint8_t acc = 0;
+       unsigned int i;
+
+       for (i = 0; i < sizeof(wg_key); ++i) {
+               acc |= key[i];
+               __asm__ ("" : "=r" (acc) : "0" (acc));
+       }
+       return 1 & ((acc - 1) >> 8);
+}
+
+static int parse_peer(const struct nlattr *attr, void *data)
+{
+       wg_peer *peer = data;
+
+       switch (mnl_attr_get_type(attr)) {
+       case WGPEER_A_UNSPEC:
+               break;
+       case WGPEER_A_PUBLIC_KEY:
+               if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) {
+                       memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key));
+                       peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+               }
+               break;
+       case WGPEER_A_PRESHARED_KEY:
+               if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) {
+                       memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key));
+                       if (!wg_key_is_zero(peer->preshared_key))
+                               peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+               }
+               break;
+       case WGPEER_A_ENDPOINT: {
+               struct sockaddr *addr;
+
+               if (mnl_attr_get_payload_len(attr) < sizeof(*addr))
+                       break;
+               addr = mnl_attr_get_payload(attr);
+               if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr4))
+                       memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4));
+               else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6))
+                       memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6));
+               break;
+       }
+       case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
+               if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+                       peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);
+               break;
+       case WGPEER_A_LAST_HANDSHAKE_TIME:
+               if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time))
+                       memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time));
+               break;
+       case WGPEER_A_RX_BYTES:
+               if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+                       peer->rx_bytes = mnl_attr_get_u64(attr);
+               break;
+       case WGPEER_A_TX_BYTES:
+               if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+                       peer->tx_bytes = mnl_attr_get_u64(attr);
+               break;
+       case WGPEER_A_ALLOWEDIPS:
+               return mnl_attr_parse_nested(attr, parse_allowedips, peer);
+       }
+
+       return MNL_CB_OK;
+}
+
+static int parse_peers(const struct nlattr *attr, void *data)
+{
+       wg_device *device = data;
+       wg_peer *new_peer = calloc(1, sizeof(wg_peer));
+       int ret;
+
+       if (!new_peer)
+               return MNL_CB_ERROR;
+       if (!device->first_peer)
+               device->first_peer = device->last_peer = new_peer;
+       else {
+               device->last_peer->next_peer = new_peer;
+               device->last_peer = new_peer;
+       }
+       ret = mnl_attr_parse_nested(attr, parse_peer, new_peer);
+       if (!ret)
+               return ret;
+       if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY)) {
+               errno = ENXIO;
+               return MNL_CB_ERROR;
+       }
+       return MNL_CB_OK;
+}
+
+static int parse_device(const struct nlattr *attr, void *data)
+{
+       wg_device *device = data;
+
+       switch (mnl_attr_get_type(attr)) {
+       case WGDEVICE_A_UNSPEC:
+               break;
+       case WGDEVICE_A_IFINDEX:
+               if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+                       device->ifindex = mnl_attr_get_u32(attr);
+               break;
+       case WGDEVICE_A_IFNAME:
+               if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) {
+                       strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1);
+                       device->name[sizeof(device->name) - 1] = '\0';
+               }
+               break;
+       case WGDEVICE_A_PRIVATE_KEY:
+               if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) {
+                       memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key));
+                       device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+               }
+               break;
+       case WGDEVICE_A_PUBLIC_KEY:
+               if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) {
+                       memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key));
+                       device->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+               }
+               break;
+       case WGDEVICE_A_LISTEN_PORT:
+               if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+                       device->listen_port = mnl_attr_get_u16(attr);
+               break;
+       case WGDEVICE_A_FWMARK:
+               if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+                       device->fwmark = mnl_attr_get_u32(attr);
+               break;
+       case WGDEVICE_A_PEERS:
+               return mnl_attr_parse_nested(attr, parse_peers, device);
+       }
+
+       return MNL_CB_OK;
+}
+
+static int read_device_cb(const struct nlmsghdr *nlh, void *data)
+{
+       return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data);
+}
+
+static void coalesce_peers(wg_device *device)
+{
+       wg_peer *old_next_peer, *peer = device->first_peer;
+
+       while (peer && peer->next_peer) {
+               if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(wg_key))) {
+                       peer = peer->next_peer;
+                       continue;
+               }
+               if (!peer->first_allowedip) {
+                       peer->first_allowedip = peer->next_peer->first_allowedip;
+                       peer->last_allowedip = peer->next_peer->last_allowedip;
+               } else {
+                       peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip;
+                       peer->last_allowedip = peer->next_peer->last_allowedip;
+               }
+               old_next_peer = peer->next_peer;
+               peer->next_peer = old_next_peer->next_peer;
+               free(old_next_peer);
+       }
+}
+
+int wg_get_device(wg_device **device, const char *device_name)
+{
+       int ret = 0;
+       struct nlmsghdr *nlh;
+       struct mnlg_socket *nlg;
+
+try_again:
+       *device = calloc(1, sizeof(wg_device));
+       if (!*device)
+               return -errno;
+
+       nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+       if (!nlg) {
+               wg_free_device(*device);
+               *device = NULL;
+               return -errno;
+       }
+
+       nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+       mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, device_name);
+       if (mnlg_socket_send(nlg, nlh) < 0) {
+               ret = -errno;
+               goto out;
+       }
+       errno = 0;
+       if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) {
+               ret = errno ? -errno : -EINVAL;
+               goto out;
+       }
+       coalesce_peers(*device);
+
+out:
+       if (nlg)
+               mnlg_socket_close(nlg);
+       if (ret) {
+               wg_free_device(*device);
+               if (ret == -EINTR)
+                       goto try_again;
+               *device = NULL;
+       }
+       errno = -ret;
+       return ret;
+}
+
+/* first\0second\0third\0forth\0last\0\0 */
+char *wg_list_device_names(void)
+{
+       struct string_list list = { 0 };
+       int ret = fetch_device_names(&list);
+
+       errno = -ret;
+       if (errno) {
+               free(list.buffer);
+               return NULL;
+       }
+       return list.buffer ?: strdup("\0");
+}
+
+int wg_add_device(const char *device_name)
+{
+       return add_del_iface(device_name, true);
+}
+
+int wg_del_device(const char *device_name)
+{
+       return add_del_iface(device_name, false);
+}
+
+void wg_free_device(wg_device *dev)
+{
+       wg_peer *peer, *np;
+       wg_allowedip *allowedip, *na;
+
+       if (!dev)
+               return;
+       for (peer = dev->first_peer, np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) {
+               for (allowedip = peer->first_allowedip, na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL)
+                       free(allowedip);
+               free(peer);
+       }
+       free(dev);
+}
+
+static void encode_base64(char dest[static 4], const uint8_t src[static 3])
+{
+       const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 };
+       unsigned int i;
+
+       for (i = 0; i < 4; ++i)
+               dest[i] = input[i] + 'A'
+                         + (((25 - input[i]) >> 8) & 6)
+                         - (((51 - input[i]) >> 8) & 75)
+                         - (((61 - input[i]) >> 8) & 15)
+                         + (((62 - input[i]) >> 8) & 3);
+
+}
+
+void wg_key_to_base64(wg_key_b64_string base64, const wg_key key)
+{
+       unsigned int i;
+
+       for (i = 0; i < 32 / 3; ++i)
+               encode_base64(&base64[i * 4], &key[i * 3]);
+       encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 });
+       base64[sizeof(wg_key_b64_string) - 2] = '=';
+       base64[sizeof(wg_key_b64_string) - 1] = '\0';
+}
+
+static int decode_base64(const char src[static 4])
+{
+       int val = 0;
+       unsigned int i;
+
+       for (i = 0; i < 4; ++i)
+               val |= (-1
+                           + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))
+                           + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))
+                           + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))
+                           + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)
+                           + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)
+                       ) << (18 - 6 * i);
+       return val;
+}
+
+int wg_key_from_base64(wg_key key, const wg_key_b64_string base64)
+{
+       unsigned int i;
+       int val;
+       volatile uint8_t ret = 0;
+
+       if (strlen(base64) != sizeof(wg_key_b64_string) - 1 || base64[sizeof(wg_key_b64_string) - 2] != '=') {
+               errno = EINVAL;
+               goto out;
+       }
+
+       for (i = 0; i < 32 / 3; ++i) {
+               val = decode_base64(&base64[i * 4]);
+               ret |= (uint32_t)val >> 31;
+               key[i * 3 + 0] = (val >> 16) & 0xff;
+               key[i * 3 + 1] = (val >> 8) & 0xff;
+               key[i * 3 + 2] = val & 0xff;
+       }
+       val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });
+       ret |= ((uint32_t)val >> 31) | (val & 0xff);
+       key[i * 3 + 0] = (val >> 16) & 0xff;
+       key[i * 3 + 1] = (val >> 8) & 0xff;
+       errno = EINVAL & ~((ret - 1) >> 8);
+out:
+       return -errno;
+}
+
+typedef int64_t fe[16];
+
+static __attribute__((noinline)) void memzero_explicit(void *s, size_t count)
+{
+       memset(s, 0, count);
+       __asm__ __volatile__("": :"r"(s) :"memory");
+}
+
+static void carry(fe o)
+{
+       int i;
+
+       for (i = 0; i < 16; ++i) {
+               o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16);
+               o[i] &= 0xffff;
+       }
+}
+
+static void cswap(fe p, fe q, int b)
+{
+       int i;
+       int64_t t, c = ~(b - 1);
+
+       for (i = 0; i < 16; ++i) {
+               t = c & (p[i] ^ q[i]);
+               p[i] ^= t;
+               q[i] ^= t;
+       }
+
+       memzero_explicit(&t, sizeof(t));
+       memzero_explicit(&c, sizeof(c));
+       memzero_explicit(&b, sizeof(b));
+}
+
+static void pack(uint8_t *o, const fe n)
+{
+       int i, j, b;
+       fe m, t;
+
+       memcpy(t, n, sizeof(t));
+       carry(t);
+       carry(t);
+       carry(t);
+       for (j = 0; j < 2; ++j) {
+               m[0] = t[0] - 0xffed;
+               for (i = 1; i < 15; ++i) {
+                       m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
+                       m[i - 1] &= 0xffff;
+               }
+               m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
+               b = (m[15] >> 16) & 1;
+               m[14] &= 0xffff;
+               cswap(t, m, 1 - b);
+       }
+       for (i = 0; i < 16; ++i) {
+               o[2 * i] = t[i] & 0xff;
+               o[2 * i + 1] = t[i] >> 8;
+       }
+
+       memzero_explicit(m, sizeof(m));
+       memzero_explicit(t, sizeof(t));
+       memzero_explicit(&b, sizeof(b));
+}
+
+static void add(fe o, const fe a, const fe b)
+{
+       int i;
+
+       for (i = 0; i < 16; ++i)
+               o[i] = a[i] + b[i];
+}
+
+static void subtract(fe o, const fe a, const fe b)
+{
+       int i;
+
+       for (i = 0; i < 16; ++i)
+               o[i] = a[i] - b[i];
+}
+
+static void multmod(fe o, const fe a, const fe b)
+{
+       int i, j;
+       int64_t t[31] = { 0 };
+
+       for (i = 0; i < 16; ++i) {
+               for (j = 0; j < 16; ++j)
+                       t[i + j] += a[i] * b[j];
+       }
+       for (i = 0; i < 15; ++i)
+               t[i] += 38 * t[i + 16];
+       memcpy(o, t, sizeof(fe));
+       carry(o);
+       carry(o);
+
+       memzero_explicit(t, sizeof(t));
+}
+
+static void invert(fe o, const fe i)
+{
+       fe c;
+       int a;
+
+       memcpy(c, i, sizeof(c));
+       for (a = 253; a >= 0; --a) {
+               multmod(c, c, c);
+               if (a != 2 && a != 4)
+                       multmod(c, c, i);
+       }
+       memcpy(o, c, sizeof(fe));
+
+       memzero_explicit(c, sizeof(c));
+}
+
+static void clamp_key(uint8_t *z)
+{
+       z[31] = (z[31] & 127) | 64;
+       z[0] &= 248;
+}
+
+void wg_generate_public_key(wg_key public_key, const wg_key private_key)
+{
+       int i, r;
+       uint8_t z[32];
+       fe a = { 1 }, b = { 9 }, c = { 0 }, d = { 1 }, e, f;
+
+       memcpy(z, private_key, sizeof(z));
+       clamp_key(z);
+
+       for (i = 254; i >= 0; --i) {
+               r = (z[i >> 3] >> (i & 7)) & 1;
+               cswap(a, b, r);
+               cswap(c, d, r);
+               add(e, a, c);
+               subtract(a, a, c);
+               add(c, b, d);
+               subtract(b, b, d);
+               multmod(d, e, e);
+               multmod(f, a, a);
+               multmod(a, c, a);
+               multmod(c, b, e);
+               add(e, a, c);
+               subtract(a, a, c);
+               multmod(b, a, a);
+               subtract(c, d, f);
+               multmod(a, c, (const fe){ 0xdb41, 1 });
+               add(a, a, d);
+               multmod(c, c, a);
+               multmod(a, d, f);
+               multmod(d, b, (const fe){ 9 });
+               multmod(b, e, e);
+               cswap(a, b, r);
+               cswap(c, d, r);
+       }
+       invert(c, c);
+       multmod(a, a, c);
+       pack(public_key, a);
+
+       memzero_explicit(&r, sizeof(r));
+       memzero_explicit(z, sizeof(z));
+       memzero_explicit(a, sizeof(a));
+       memzero_explicit(b, sizeof(b));
+       memzero_explicit(c, sizeof(c));
+       memzero_explicit(d, sizeof(d));
+       memzero_explicit(e, sizeof(e));
+       memzero_explicit(f, sizeof(f));
+}
+
+void wg_generate_private_key(wg_key private_key)
+{
+       wg_generate_preshared_key(private_key);
+       clamp_key(private_key);
+}
+
+void wg_generate_preshared_key(wg_key preshared_key)
+{
+       ssize_t ret;
+       size_t i;
+       int fd;
+#if defined(__OpenBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)))
+       if (!getentropy(preshared_key, sizeof(wg_key)))
+               return;
+#endif
+#if defined(__NR_getrandom) && defined(__linux__)
+       if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == sizeof(wg_key))
+               return;
+#endif
+       fd = open("/dev/urandom", O_RDONLY);
+       assert(fd >= 0);
+       for (i = 0; i < sizeof(wg_key); i += ret) {
+               ret = read(fd, preshared_key + i, sizeof(wg_key) - i);
+               assert(ret > 0);
+       }
+       close(fd);
+}
diff --git a/utils/rpcd-mod-wireguard/src/wireguard.h b/utils/rpcd-mod-wireguard/src/wireguard.h
new file mode 100644 (file)
index 0000000..328fcb4
--- /dev/null
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#ifndef WIREGUARD_H
+#define WIREGUARD_H
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef uint8_t wg_key[32];
+typedef char wg_key_b64_string[((sizeof(wg_key) + 2) / 3) * 4 + 1];
+
+/* Cross platform __kernel_timespec */
+struct timespec64 {
+       int64_t tv_sec;
+       int64_t tv_nsec;
+};
+
+typedef struct wg_allowedip {
+       uint16_t family;
+       union {
+               struct in_addr ip4;
+               struct in6_addr ip6;
+       };
+       uint8_t cidr;
+       struct wg_allowedip *next_allowedip;
+} wg_allowedip;
+
+enum wg_peer_flags {
+       WGPEER_REMOVE_ME = 1U << 0,
+       WGPEER_REPLACE_ALLOWEDIPS = 1U << 1,
+       WGPEER_HAS_PUBLIC_KEY = 1U << 2,
+       WGPEER_HAS_PRESHARED_KEY = 1U << 3,
+       WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4
+};
+
+typedef union wg_endpoint {
+       struct sockaddr addr;
+       struct sockaddr_in addr4;
+       struct sockaddr_in6 addr6;
+} wg_endpoint;
+
+typedef struct wg_peer {
+       enum wg_peer_flags flags;
+
+       wg_key public_key;
+       wg_key preshared_key;
+
+       wg_endpoint endpoint;
+
+       struct timespec64 last_handshake_time;
+       uint64_t rx_bytes, tx_bytes;
+       uint16_t persistent_keepalive_interval;
+
+       struct wg_allowedip *first_allowedip, *last_allowedip;
+       struct wg_peer *next_peer;
+} wg_peer;
+
+enum wg_device_flags {
+       WGDEVICE_REPLACE_PEERS = 1U << 0,
+       WGDEVICE_HAS_PRIVATE_KEY = 1U << 1,
+       WGDEVICE_HAS_PUBLIC_KEY = 1U << 2,
+       WGDEVICE_HAS_LISTEN_PORT = 1U << 3,
+       WGDEVICE_HAS_FWMARK = 1U << 4
+};
+
+typedef struct wg_device {
+       char name[IFNAMSIZ];
+       uint32_t ifindex;
+
+       enum wg_device_flags flags;
+
+       wg_key public_key;
+       wg_key private_key;
+
+       uint32_t fwmark;
+       uint16_t listen_port;
+
+       struct wg_peer *first_peer, *last_peer;
+} wg_device;
+
+#define wg_for_each_device_name(__names, __name, __len) for ((__name) = (__names), (__len) = 0; ((__len) = strlen(__name)); (__name) += (__len) + 1)
+#define wg_for_each_peer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer)
+#define wg_for_each_allowedip(__peer, __allowedip) for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); (__allowedip) = (__allowedip)->next_allowedip)
+
+int wg_set_device(wg_device *dev);
+int wg_get_device(wg_device **dev, const char *device_name);
+int wg_add_device(const char *device_name);
+int wg_del_device(const char *device_name);
+void wg_free_device(wg_device *dev);
+char *wg_list_device_names(void); /* first\0second\0third\0forth\0last\0\0 */
+void wg_key_to_base64(wg_key_b64_string base64, const wg_key key);
+int wg_key_from_base64(wg_key key, const wg_key_b64_string base64);
+bool wg_key_is_zero(const wg_key key);
+void wg_generate_public_key(wg_key public_key, const wg_key private_key);
+void wg_generate_private_key(wg_key private_key);
+void wg_generate_preshared_key(wg_key preshared_key);
+
+#endif