Просмотр исходного кода

wireguard-tools: rewrite proto handler in ucode

This fixes automatic config reload on peer changes

Signed-off-by: Felix Fietkau <[email protected]>
Felix Fietkau 5 дней назад
Родитель
Сommit
41bc454602

+ 1 - 1
package/network/utils/wireguard-tools/Makefile

@@ -57,7 +57,7 @@ define Package/wireguard-tools/install
 	$(INSTALL_BIN) $(PKG_BUILD_DIR)/src/wg $(1)/usr/bin/
 	$(INSTALL_BIN) ./files/wireguard_watchdog $(1)/usr/bin/
 	$(INSTALL_DIR) $(1)/lib/netifd/proto/
-	$(INSTALL_BIN) ./files/wireguard.sh $(1)/lib/netifd/proto/
+	$(INSTALL_DATA) ./files/wireguard.uc $(1)/lib/netifd/proto/
 endef
 
 $(eval $(call BuildPackage,wireguard-tools))

+ 0 - 169
package/network/utils/wireguard-tools/files/wireguard.sh

@@ -1,169 +0,0 @@
-#!/bin/sh
-# Copyright 2016-2017 Dan Luedtke <[email protected]>
-# Licensed to the public under the Apache License 2.0.
-# shellcheck disable=SC2317
-
-WG=/usr/bin/wg
-if [ ! -x $WG ]; then
-	logger -t "wireguard" "error: missing wireguard-tools (${WG})"
-	exit 0
-fi
-
-[ -n "$INCLUDE_ONLY" ] || {
-	. /lib/functions.sh
-	. ../netifd-proto.sh
-	init_proto "$@"
-}
-
-proto_wireguard_init_config() {
-	renew_handler=1
-	peer_detect=1
-
-	proto_config_add_string "private_key"
-	proto_config_add_int "listen_port"
-	proto_config_add_int "mtu"
-	proto_config_add_string "fwmark"
-	proto_config_add_string "addresses"
-
-	available=1
-	no_proto_task=1
-}
-
-ensure_key_is_generated() {
-	local private_key
-	private_key="$(uci get network."$1".private_key)"
-
-	if [ "$private_key" = "generate" ] || [ -z "$private_key" ]; then
-		private_key="$("${WG}" genkey)"
-		uci -q set network."$1".private_key="$private_key" && \
-			uci -q commit network
-	fi
-}
-
-proto_wireguard_setup() {
-	local config="$1"
-
-	local private_key listen_port mtu fwmark addresses ip6prefix nohostroute tunlink
-	ensure_key_is_generated "${config}"
-
-	config_load network
-	config_get private_key "${config}" "private_key"
-	config_get listen_port "${config}" "listen_port"
-	config_get addresses "${config}" "addresses"
-	config_get mtu "${config}" "mtu"
-	config_get fwmark "${config}" "fwmark"
-	config_get ip6prefix "${config}" "ip6prefix"
-	config_get nohostroute "${config}" "nohostroute"
-	config_get tunlink "${config}" "tunlink"
-
-	# Add the link only if it didn't already exist
-	ip -br link show "${config}" >/dev/null 2>&1 || ip link add dev "${config}" type wireguard
-
-	[ -n "${mtu}" ] && ip link set mtu "${mtu}" dev "${config}"
-
-	proto_init_update "${config}" 1
-
-	# Build WireGuard configuration entirely in memory
-	local wg_config="[Interface]\n"
-	wg_config="${wg_config}PrivateKey=${private_key}\n"
-	[ -n "${listen_port}" ]	&& wg_config="${wg_config}ListenPort=${listen_port}\n"
-	[ -n "${fwmark}" ]		&& wg_config="${wg_config}FwMark=${fwmark}\n"
-
-	# Collect peer configs into wg_config as well
-	local peer_config
-	peer_config=""
-	proto_wireguard_setup_peer_collect() {
-		local section="$1"
-		local peer_block
-
-		config_get_bool route_allowed_ips "$section" "route_allowed_ips" 0
-		config_get_bool disabled "$section" "disabled" 0
-		[ "$disabled" = 1 ] && return;
-		config_get peer_key "$section" "public_key"
-		config_get peer_eph "$section" "endpoint_host"
-		config_get peer_port "$section" "endpoint_port" "51820"
-		config_get peer_a_ips "$section" "allowed_ips"
-		config_get peer_p_ka "$section" "persistent_keepalive"
-		config_get peer_psk "$section" "preshared_key"
-
-
-		[ "${peer_eph##*:}" != "$peer_eph" ] && peer_eph="[$peer_eph]"
-		peer_port=${peer_port:-51820}
-
-		peer_block="\n[Peer]\n"
-		[ -n "${peer_key}" ]	&& peer_block="${peer_block}PublicKey=${peer_key}\n"
-		[ -n "${peer_psk}" ]	&& peer_block="${peer_block}PresharedKey=${peer_psk}\n"
-		[ -n "${peer_eph}" ]	&& peer_block="${peer_block}Endpoint=${peer_eph}${peer_port:+:$peer_port}\n"
-		[ -n "${peer_a_ips}" ]	&& peer_block="${peer_block}AllowedIPs=${peer_a_ips// /, }\n"
-		[ -n "${peer_p_ka}" ]	&& peer_block="${peer_block}PersistentKeepalive=${peer_p_ka}\n"
-
-		[ -n "$peer_key" ] && peer_config="$peer_config$peer_block\n"
-		if [ $route_allowed_ips -ne 0 ]; then
-			for allowed_ip in $peer_a_ips; do
-				case "${allowed_ip}" in
-					*:*/*) proto_add_ipv6_route "${allowed_ip%%/*}" "${allowed_ip##*/}" ;;
-					*.*/*) proto_add_ipv4_route "${allowed_ip%%/*}" "${allowed_ip##*/}" ;;
-					*:*) proto_add_ipv6_route "${allowed_ip%%/*}" "128" ;;
-					*.*) proto_add_ipv4_route "${allowed_ip%%/*}" "32" ;;
-				esac
-			done
-		fi
-
-	}
-
-	config_foreach proto_wireguard_setup_peer_collect "wireguard_${config}"
-
-	# Combine interface + peer config into one variable
-	wg_config="${wg_config}${peer_config}"
-
-	# Apply configuration directly using wg syncconf via stdin
-	printf "%b" "$wg_config" | ${WG} syncconf "${config}" /dev/stdin
-	local WG_RETURN=$?
-
-	if [ ${WG_RETURN} -ne 0 ]; then
-		echo "Could not sync WireGuard configuration"
-		sleep 5
-		proto_setup_failed "${config}"
-		exit 1
-	fi
-
-	# Assign addresses
-	for address in ${addresses}; do
-		case "${address}" in
-			*:*/*) proto_add_ipv6_address "${address%%/*}" "${address##*/}" ;;
-			*.*/*) proto_add_ipv4_address "${address%%/*}" "${address##*/}" ;;
-			*:*)   proto_add_ipv6_address "${address%%/*}" "128" ;;
-			*.*)   proto_add_ipv4_address "${address%%/*}" "32" ;;
-		esac
-	done
-
-	for prefix in ${ip6prefix}; do
-		proto_add_ipv6_prefix "$prefix"
-	done
-
-	# Endpoint dependency tracking
-	if [ "${nohostroute}" != "1" ]; then
-		wg show "${config}" endpoints | \
-		sed -E 's/\[?([0-9.:a-f]+)\]?:([0-9]+)/\1 \2/' | \
-		while IFS=$'\t ' read -r key address port; do
-			[ -n "${port}" ] || continue
-			proto_add_host_dependency "${config}" "${address}" "${tunlink}"
-		done
-	fi
-
-	proto_send_update "${config}"
-}
-
-proto_wireguard_renew() {
-	local interface="$1"
-	proto_wireguard_setup "$interface"
-}
-
-proto_wireguard_teardown() {
-	local config="$1"
-	ip link del dev "${config}" >/dev/null 2>&1
-}
-
-[ -n "$INCLUDE_ONLY" ] || {
-	add_protocol wireguard
-}

+ 255 - 0
package/network/utils/wireguard-tools/files/wireguard.uc

@@ -0,0 +1,255 @@
+#!/usr/bin/env ucode
+'use strict';
+
+import * as fs from 'fs';
+
+const WG = '/usr/bin/wg';
+
+function wg_exists() {
+	return fs.access(WG, fs.F_OK);
+}
+
+function ensure_key_is_generated(cursor, section_name) {
+	let private_key = cursor.get('network', section_name, 'private_key');
+
+	if (!private_key || private_key == 'generate') {
+		let proc = fs.popen(`${WG} genkey`);
+		if (!proc)
+			return null;
+
+		let generated_key = rtrim(proc.read('all'));
+		proc.close();
+
+		if (generated_key) {
+			cursor.set('network', section_name, 'private_key', generated_key);
+			cursor.commit('network');
+			return generated_key;
+		}
+	}
+
+	return private_key;
+}
+
+function parse_address(addr) {
+	if (index(addr, ':') >= 0) {
+		if (index(addr, '/') >= 0) {
+			let parts = split(addr, '/');
+			return { family: 6, address: parts[0], mask: int(parts[1]) };
+		}
+		return { family: 6, address: addr, mask: 128 };
+	}
+
+	if (index(addr, '/') >= 0) {
+		let parts = split(addr, '/');
+		return { family: 4, address: parts[0], mask: int(parts[1]) };
+	}
+
+	return { family: 4, address: addr, mask: 32 };
+}
+
+function load_peers(cursor, iface) {
+	let peers = [];
+	let peer_type = sprintf('wireguard_%s', iface);
+
+	cursor.foreach('network', peer_type, (peer_section) => {
+		let disabled = peer_section.disabled;
+		if (disabled == '1')
+			return;
+
+		let route_allowed_ips = peer_section.route_allowed_ips;
+		let peer_key = peer_section.public_key;
+		let peer_eph = peer_section.endpoint_host;
+		let peer_port = peer_section.endpoint_port ?? '51820';
+		let peer_a_ips = peer_section.allowed_ips;
+		let peer_p_ka = peer_section.persistent_keepalive;
+		let peer_psk = peer_section.preshared_key;
+
+		if (!peer_key)
+			return;
+
+		let peer_data = {
+			public_key: peer_key,
+			preshared_key: peer_psk,
+			endpoint_host: peer_eph,
+			endpoint_port: peer_port,
+			allowed_ips: peer_a_ips,
+			persistent_keepalive: peer_p_ka,
+			route_allowed_ips: route_allowed_ips == '1'
+		};
+
+		push(peers, peer_data);
+	});
+
+	return peers;
+}
+
+function proto_setup(proto) {
+	if (!wg_exists()) {
+		warn('WireGuard tools not found at ', WG, '\n');
+		proto.setup_failed();
+		return;
+	}
+
+	let iface = proto.iface;
+	let config = proto.config;
+
+	system(sprintf('ip link add dev %s type wireguard 2>/dev/null || true', iface));
+
+	if (config.mtu)
+		system(sprintf('ip link set mtu %d dev %s', int(config.mtu), iface));
+
+	let wg_config = '[Interface]\n';
+	wg_config += sprintf('PrivateKey=%s\n', config.private_key);
+
+	if (config.listen_port)
+		wg_config += sprintf('ListenPort=%d\n', int(config.listen_port));
+
+	if (config.fwmark)
+		wg_config += sprintf('FwMark=%s\n', config.fwmark);
+
+	let ipv4_routes = [];
+	let ipv6_routes = [];
+
+	for (let peer in config.peers) {
+		wg_config += '\n[Peer]\n';
+		wg_config += sprintf('PublicKey=%s\n', peer.public_key);
+
+		if (peer.preshared_key)
+			wg_config += sprintf('PresharedKey=%s\n', peer.preshared_key);
+
+		if (peer.endpoint_host) {
+			let eph = peer.endpoint_host;
+			if (index(eph, ':') >= 0 && eph[0] != '[')
+				eph = sprintf('[%s]', eph);
+			wg_config += sprintf('Endpoint=%s:%s\n', eph, peer.endpoint_port);
+		}
+
+		if (peer.allowed_ips) {
+			let allowed_list = type(peer.allowed_ips) == 'array' ? peer.allowed_ips : split(peer.allowed_ips, ' ');
+			wg_config += sprintf('AllowedIPs=%s\n', join(', ', allowed_list));
+
+			if (peer.route_allowed_ips) {
+				for (let allowed_ip in allowed_list) {
+					let addr_info = parse_address(allowed_ip);
+					let route = { target: addr_info.address, netmask: '' + addr_info.mask };
+					if (addr_info.family == 6)
+						push(ipv6_routes, route);
+					else
+						push(ipv4_routes, route);
+				}
+			}
+		}
+
+		if (peer.persistent_keepalive)
+			wg_config += sprintf('PersistentKeepalive=%s\n', peer.persistent_keepalive);
+	}
+
+	let wg_proc = fs.popen(sprintf('%s syncconf %s /dev/stdin', WG, iface), 'w');
+	if (!wg_proc) {
+		warn('Failed to run wg syncconf for ', iface, '\n');
+		proto.setup_failed();
+		return;
+	}
+
+	wg_proc.write(wg_config);
+	let wg_result = wg_proc.close();
+
+	if (wg_result != 0) {
+		warn('wg syncconf failed for ', iface, '\n');
+		proto.setup_failed();
+		return;
+	}
+
+	system(sprintf('ip link set up dev %s', iface));
+
+	let ipv4_addrs = [];
+	let ipv6_addrs = [];
+
+	if (config.addresses) {
+		let addr_list = split(config.addresses, ' ');
+		for (let address in addr_list) {
+			let addr_info = parse_address(address);
+			let addr = { ipaddr: addr_info.address, mask: '' + addr_info.mask };
+			if (addr_info.family == 6)
+				push(ipv6_addrs, addr);
+			else
+				push(ipv4_addrs, addr);
+		}
+	}
+
+	let link_data = {
+		ifname: iface
+	};
+
+	if (length(ipv4_addrs) > 0)
+		link_data.ipaddr = ipv4_addrs;
+
+	if (length(ipv6_addrs) > 0)
+		link_data.ip6addr = ipv6_addrs;
+
+	if (length(ipv4_routes) > 0)
+		link_data.routes = ipv4_routes;
+
+	if (length(ipv6_routes) > 0)
+		link_data.routes6 = ipv6_routes;
+
+	if (config.ip6prefix) {
+		let prefix_list = split(config.ip6prefix, ' ');
+		if (length(prefix_list) > 0)
+			link_data.ip6prefix = prefix_list;
+	}
+
+	if (config.nohostroute != '1') {
+		let endpoints_proc = fs.popen(sprintf('%s show %s endpoints', WG, iface));
+		if (endpoints_proc) {
+			let endpoints_data = endpoints_proc.read('all');
+			endpoints_proc.close();
+
+			let endpoint_lines = split(endpoints_data, '\n');
+			for (let line in endpoint_lines) {
+				if (!line)
+					continue;
+
+				let parts = split(rtrim(line), '\t');
+				if (length(parts) < 2)
+					continue;
+
+				let endpoint = parts[1];
+				let addr_match = match(endpoint, regexp('\\[?([0-9.:a-f]+)\\]?:([0-9]+)'));
+				if (addr_match && length(addr_match) > 1)
+					proto.add_host_dependency(addr_match[1], config.tunlink);
+			}
+		}
+	}
+
+	proto.update_link(true, link_data);
+}
+
+function proto_teardown(proto) {
+	let iface = proto.iface;
+	system(sprintf('ip link del dev %s 2>/dev/null', iface));
+	proto.update_link(false);
+}
+
+function proto_renew(proto) {
+	proto_setup(proto);
+}
+
+netifd.add_proto({
+	available: true,
+	no_proto_task: true,
+	'renew-handler': true,
+	name: 'wireguard',
+
+	config: function(ctx) {
+		return {
+			...ctx.data,
+			private_key: ensure_key_is_generated(ctx.uci, ctx.section),
+			peers: load_peers(ctx.uci, ctx.section)
+		};
+	},
+
+	setup: proto_setup,
+	teardown: proto_teardown,
+	renew: proto_renew
+});