Merge pull request #20007 from dhewg/prometheus-node-exporter-ucode

prometheus-node-exporter-ucode: add new package
This commit is contained in:
Etienne Champetier 2023-10-15 11:00:44 -04:00 committed by GitHub
commit 86df457120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 3056 additions and 0 deletions

View File

@ -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))

View File

@ -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"));

View File

@ -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]);
}

View File

@ -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"));

View File

@ -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]);

View File

@ -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]);

View File

@ -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]);
}

View File

@ -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);
}

View File

@ -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]);
}

View File

@ -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);

View File

@ -0,0 +1 @@
gauge("node_time_seconds")(null, time());

View File

@ -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);

View File

@ -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'

View File

@ -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]);

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);
});

View File

@ -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"]);
}
}
}

View File

@ -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"]);
}
}

View File

@ -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"
}

View File

@ -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)),
});
}
%}

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/bin/ucode -T /usr/share/ucode/node-exporter/metrics.uc $*

View File

@ -0,0 +1,3 @@
#!/bin/sh
prometheus-node-exporter-ucode time

View File

@ -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))

View File

@ -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)

View File

@ -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
};

File diff suppressed because it is too large Load Diff

View File

@ -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