gluon-mesh-vpn-wireguard: initial support
This commit is contained in:
parent
b7e8ad33a4
commit
81b728b704
@ -65,6 +65,7 @@ Several Freifunk communities in Germany use Gluon as the foundation of their Fre
|
||||
package/gluon-ebtables-source-filter
|
||||
package/gluon-hoodselector
|
||||
package/gluon-mesh-batman-adv
|
||||
package/gluon-mesh-vpn-wireguard
|
||||
package/gluon-radv-filterd
|
||||
package/gluon-scheduled-domain-switch
|
||||
package/gluon-web-admin
|
||||
|
53
docs/package/gluon-mesh-vpn-wireguard.rst
Executable file
53
docs/package/gluon-mesh-vpn-wireguard.rst
Executable file
@ -0,0 +1,53 @@
|
||||
gluon-mesh-vpn-wireguard
|
||||
========================
|
||||
|
||||
This package allows WireGuard [1] to be used in Gluon. WireGuard establishes
|
||||
VPN connections on OSI layer 3 allowing increased throughput in comparison with
|
||||
fastd for mesh protocols that operate on layer 3 too.
|
||||
|
||||
When starting WireGuard, the system requires some entropy. It is recommended to
|
||||
use haveged to avoid long startup times.
|
||||
|
||||
[1] https://wireguard.io
|
||||
|
||||
site.conf
|
||||
---------
|
||||
This is similar to the fastd-based mesh_vpn structure.
|
||||
|
||||
Example::
|
||||
|
||||
mesh_vpn = {
|
||||
mtu = 1374,
|
||||
wireguard = {
|
||||
enabled = true,
|
||||
groups = {
|
||||
backbone = {
|
||||
limit = 2,
|
||||
peers = {
|
||||
gw02 = {
|
||||
enabled = true,
|
||||
key = 'bog2DzyiC0Os7y1GloEw0afb8bLdZ9SzVQCd44Eock4=',
|
||||
remote = 'gw02.babel.ffm.freifunk.net',
|
||||
broker_port = 40000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Server Side Configuration
|
||||
-------------------------
|
||||
|
||||
* The wireguard private key must be deployed, and the derived Public Key has to be in site.conf
|
||||
* The wg-broker-server script must be running on the server and be listening on
|
||||
the broker_port
|
||||
* The node must be able to reach the server using TCP-Port broker_port and it
|
||||
must be able to communicate with the server using one UDP port between 40000
|
||||
and 41000.
|
||||
|
||||
On dockerhub there is an image klausdieter371/wg-docker integrating the
|
||||
server-side components. Please refer to its documentation to set up the server
|
||||
part. The Code and Documentation are kept here:
|
||||
https://github.com/FreifunkMD/wg-docker
|
||||
|
@ -9,7 +9,7 @@ packages 'web-wizard' \
|
||||
packages 'web-wizard & autoupdater' \
|
||||
'gluon-config-mode-autoupdater'
|
||||
|
||||
packages 'web-wizard & (mesh-vpn-fastd | mesh-vpn-tunneldigger)' \
|
||||
packages 'web-wizard & (mesh-vpn-fastd | mesh-vpn-tunneldigger | mesh-vpn-wireguard)' \
|
||||
'gluon-config-mode-mesh-vpn'
|
||||
|
||||
|
||||
|
@ -40,7 +40,7 @@ elseif has_fastd then
|
||||
elseif has_wireguard then
|
||||
local wireguard_enabled = uci:get_bool("wireguard", "mesh_vpn", "enabled")
|
||||
if wireguard_enabled then
|
||||
local secret = util.trim(util.exec("/usr/bin/gluon-mesh-vpn-wireguard-get-or-create-secret"))
|
||||
local secret = uci:get("wireguard", "mesh_vpn", "secret")
|
||||
pubkey = util.trim(util.exec("/usr/bin/wg pubkey < " .. secret))
|
||||
msg = site_i18n._translate('gluon-config-mode:pubkey')
|
||||
else
|
||||
|
19
package/gluon-mesh-vpn-wireguard/Makefile
Executable file
19
package/gluon-mesh-vpn-wireguard/Makefile
Executable file
@ -0,0 +1,19 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gluon-mesh-vpn-wireguard
|
||||
PKG_VERSION:=3
|
||||
|
||||
include ../gluon.mk
|
||||
|
||||
PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
|
||||
|
||||
define Package/gluon-mesh-vpn-wireguard
|
||||
TITLE:=WireGuard Mesh VPN Support
|
||||
DEPENDS:=+gluon-core +gluon-mesh-vpn-core +kmod-wireguard +jsonfilter +wireguard-tools +micrond +@BUSYBOX_CONFIG_TIMEOUT
|
||||
endef
|
||||
|
||||
define Package/gluon-mesh-vpn-wireguard/description
|
||||
Support layer 3 mesh-vpn connection using wireguard. This can be used by mesh-protocols working on OSI layer 3.
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackageGluon,gluon-mesh-vpn-wireguard))
|
17
package/gluon-mesh-vpn-wireguard/check_site.lua
Normal file
17
package/gluon-mesh-vpn-wireguard/check_site.lua
Normal file
@ -0,0 +1,17 @@
|
||||
local function check_peer(k)
|
||||
need_alphanumeric_key(k)
|
||||
|
||||
need_string_match(in_domain(extend(k, {'key'})), '[%w]+=*')
|
||||
need_string_match(in_domain(extend(k, {'remote'})), '[%w_-.]')
|
||||
need_number(in_domain(extend(k, {'broker_port'})), false)
|
||||
end
|
||||
|
||||
local function check_group(k)
|
||||
need_alphanumeric_key(k)
|
||||
|
||||
need_number(extend(k, {'limit'}), false)
|
||||
need_table(extend(k, {'peers'}), check_peer, false)
|
||||
need_table(extend(k, {'groups'}), check_group, false)
|
||||
end
|
||||
|
||||
need_table({'mesh_vpn', 'wireguard', 'groups'}, check_group)
|
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
touch /etc/config/gluon_mesh_vpn_wireguard
|
||||
uci set gluon_mesh_vpn_wireguard.mesh_vpn="backbone"
|
||||
|
||||
if ! uci get gluon_mesh_vpn_wireguard.mesh_vpn.secret 2>/dev/null| wg pubkey 2>/dev/null; then
|
||||
uci set gluon_mesh_vpn_wireguard.mesh_vpn.secret="generate"
|
||||
fi
|
94
package/gluon-mesh-vpn-wireguard/files/lib/netifd/proto/gluon_wireguard.sh
Executable file
94
package/gluon-mesh-vpn-wireguard/files/lib/netifd/proto/gluon_wireguard.sh
Executable file
@ -0,0 +1,94 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2016-2017 Christof Schulze <christof@christofschulze.com>
|
||||
# Licensed to the public under the Apache License 2.0.
|
||||
|
||||
. /lib/functions.sh
|
||||
. ../netifd-proto.sh
|
||||
init_proto "$@"
|
||||
|
||||
proto_gluon_wireguard_init_config() {
|
||||
no_device=1
|
||||
available=1
|
||||
renew_handler=1
|
||||
}
|
||||
|
||||
proto_gluon_wireguard_renew() {
|
||||
local config="$1"
|
||||
echo "wireguard RENEW: $*"
|
||||
ifdown "$config"
|
||||
ifup "$config"
|
||||
}
|
||||
|
||||
proto_gluon_wireguard_setup() {
|
||||
local config="$1"
|
||||
ifname="$(uci get "network.$config.ifname")" # we need uci here because nodevice=1 means the device is not part of the ubus structure
|
||||
|
||||
local peer_limit=$(gluon-show-site |jsonfilter -e $.mesh_vpn.wireguard.groups.backbone.limit)
|
||||
if [[ $(wg show all latest-handshakes |wc -l) -ge "$peer_limit" ]]; then
|
||||
echo "not establishing another connection, we already have $peer_limit connections." >&2
|
||||
ip link del "$ifname"
|
||||
ifdown "$config"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
flock -n 9
|
||||
|
||||
if [[ $(uci get gluon.mesh_vpn.enabled) -eq 1 ]]; then
|
||||
ip link del "$ifname"
|
||||
ip link add dev "$ifname" type wireguard
|
||||
ip link set mtu "$(gluon-show-site | jsonfilter -e $.mesh_vpn.mtu)" dev "$ifname"
|
||||
ip link set multicast on dev "$ifname"
|
||||
|
||||
mkdir -p /var/gluon/mesh-vpn-wireguard
|
||||
secretfile=/var/gluon/mesh-vpn-wireguard/secret
|
||||
secret=$(gluon-mesh-vpn-wireguard-get-or-create-secret)
|
||||
|
||||
echo "$secret" > "$secretfile"
|
||||
pubkey=$(echo "$secret"| wg pubkey)
|
||||
|
||||
gwname=${config##*_}
|
||||
peer=${gwname%?}
|
||||
|
||||
peer_config=$(gluon-show-site |jsonfilter -e "$.mesh_vpn.wireguard.groups.backbone.peers.$peer")
|
||||
remote=$(jsonfilter -s "$peer_config" -e "$.remote")
|
||||
brokerport=$(jsonfilter -s "$peer_config" -e "$.broker_port")
|
||||
peer_key=$(jsonfilter -s "$peer_config" -e "$.key")
|
||||
remoteport=$(/usr/bin/wg-broker-client "$ifname" "$pubkey" "$remote" "$brokerport")
|
||||
|
||||
if [[ "$remoteport" == "FULL" ]]; then
|
||||
echo "wireguard server $remote is not accepting additional connections. Closing this interface" >&2
|
||||
ip link del "$ifname"
|
||||
exit 1
|
||||
elif [[ "$remoteport" == "ERROR" ]]; then
|
||||
echo "error when setting up wireguard connection for $ifname" >&2
|
||||
ip link del "$ifname"
|
||||
exit 1
|
||||
elif [[ -z "$remoteport" ]]; then
|
||||
echo "error when setting up wireguard connection for $ifname - no response from broker: $remote" >&2
|
||||
ip link del "$ifname"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gluon-wan wg set "$ifname" private-key "$secretfile" peer "$peer_key" endpoint "$remote:$remoteport" allowed-ips ::/0 persistent-keepalive 25
|
||||
|
||||
ip link set dev "$ifname" up
|
||||
ip -6 route add fe80::/64 dev "$ifname" proto kernel metric 256 pref medium table local
|
||||
|
||||
proto_init_update "$ifname" 1
|
||||
proto_send_update "$config"
|
||||
fi
|
||||
) 9>"/var/lock/wireguard_proto_${ifname}.lock" || ifdown "$config"
|
||||
}
|
||||
|
||||
proto_gluon_wireguard_teardown() {
|
||||
local config="$1"
|
||||
echo teardown config: "$config"
|
||||
ifname=$(uci get "network.$config.ifname") # we need uci here because nodevice=1 means the device is not part of the ubus structure
|
||||
|
||||
ip link del "$ifname"
|
||||
}
|
||||
|
||||
[[ -n "$INCLUDE_ONLY" ]] || {
|
||||
add_protocol gluon_wireguard
|
||||
}
|
19
package/gluon-mesh-vpn-wireguard/files/usr/bin/enable-all-wg-interfaces
Executable file
19
package/gluon-mesh-vpn-wireguard/files/usr/bin/enable-all-wg-interfaces
Executable file
@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
|
||||
get_down_wg_backbone_interfaces() {
|
||||
ubus -S call network.interface dump | jsonfilter -e '@.interface[@.up=false && @.proto="gluon_wireguard"].interface'
|
||||
}
|
||||
|
||||
is_wan_up() {
|
||||
ubus -S call network.interface dump | jsonfilter -e '@.interface[@.up=true && @.interface="wan"].up'
|
||||
}
|
||||
|
||||
if is_wan_up >/dev/null; then
|
||||
if [[ $(uci get gluon.mesh_vpn.enabled) == "1" ]]; then
|
||||
for i in $(get_down_wg_backbone_interfaces)
|
||||
do
|
||||
ifup "$i"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
secret=$(uci get gluon_mesh_vpn_wireguard.mesh_vpn.secret)
|
||||
if [[ "$secret" = "generate" ]]; then
|
||||
secret="$(wg genkey)"
|
||||
uci set gluon_mesh_vpn_wireguard.mesh_vpn.secret="$secret"
|
||||
uci commit gluon_mesh_vpn_wireguard
|
||||
fi
|
||||
|
||||
echo "$secret"
|
61
package/gluon-mesh-vpn-wireguard/files/usr/bin/wg-broker-client
Executable file
61
package/gluon-mesh-vpn-wireguard/files/usr/bin/wg-broker-client
Executable file
@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
|
||||
timeout=10
|
||||
run_broker() {
|
||||
local interface="$1"
|
||||
local pubkey="$2"
|
||||
local remote="$3"
|
||||
local brokerport="$4"
|
||||
local port
|
||||
local interval=5
|
||||
|
||||
localtime=$(date +%s)
|
||||
|
||||
# sleeping on stdin keeps the sockets open in nc, allowing us to receive a
|
||||
# reply. Unfortunately this means all requests take $timeout seconds even
|
||||
# if the server is faster
|
||||
peer_reply="$( { echo '{"version":1, "pubkey":"'"$pubkey"'"}'; sleep $timeout; } | gluon-wan timeout $timeout nc "$remote" "$brokerport" | tail -n1)"
|
||||
|
||||
if [[ "x$peer_reply" != "x" ]]; then
|
||||
port=$(jsonfilter -s "$peer_reply" -e "@.port")
|
||||
peer_time=$(jsonfilter -s "$peer_reply" -e "@.time")
|
||||
|
||||
difference=0
|
||||
if [[ $peer_time -gt $localtime ]]; then
|
||||
difference=$((peer_time - localtime))
|
||||
else
|
||||
difference=$((localtime - peer_time))
|
||||
fi
|
||||
|
||||
if [[ "x$peer_time" != "x" && $difference -gt 240 ]]; then
|
||||
# local clock differs a lot from the peer clock.
|
||||
# assuming ntp is working only when a tunnel is established we need to
|
||||
# set the clock to something in the proximity of the correct time.
|
||||
# Let's assume peer_time for now. ntpd will handle the rest
|
||||
formatted_time=$(date -d "@$peer_time" +%Y%m%d%H%M.%S)
|
||||
date -s "$formatted_time" >/dev/null
|
||||
fi
|
||||
|
||||
if [[ -z $port ]]; then
|
||||
error=$(jsonfilter -s "$peer_reply" -e "@.error")
|
||||
if [[ -n $error ]]; then
|
||||
reason=$(jsonfilter -s "$peer_reply" -e "@.error.reason")
|
||||
ecode=$(jsonfilter -s "$peer_reply" -e "@.error.code")
|
||||
echo "received error [$ecode] from host $remote: $reason" >&2
|
||||
|
||||
if [[ "$ecode" == "1" ]]; then
|
||||
echo FULL
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "$port"
|
||||
return 0
|
||||
else
|
||||
echo "Received no reply from peer $remote" >&2
|
||||
echo "ERROR"
|
||||
return 255
|
||||
fi
|
||||
}
|
||||
|
||||
run_broker "$1" "$2" "$3" "$4"
|
40
package/gluon-mesh-vpn-wireguard/files/usr/bin/wgcheck
Executable file
40
package/gluon-mesh-vpn-wireguard/files/usr/bin/wgcheck
Executable file
@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
|
||||
curtime=$(date +%s)
|
||||
|
||||
get_wg_interfaces() {
|
||||
ubus -S call network.interface dump | jsonfilter -e '@.interface[@.up=true && @.proto="gluon_wireguard"].l3_device'
|
||||
}
|
||||
|
||||
get_connection_count() {
|
||||
ubus -S call network.interface dump | jsonfilter -e '@.interface[@.up=true && @.proto="gluon_wireguard" && @].l3_device' | wc -l
|
||||
}
|
||||
|
||||
get_interface_from_ifname() {
|
||||
ubus -S call network.interface dump | jsonfilter -e "@.interface[@.proto=\"gluon_wireguard\" && @.l3_device=\"$1\"].interface"
|
||||
}
|
||||
|
||||
# purge wg interface that have terminated
|
||||
for i in $(get_wg_interfaces)
|
||||
do
|
||||
line=$(wg show "$i" latest-handshakes)
|
||||
if [[ -n "${line}" ]]; then
|
||||
latest=$(echo "${line}"| awk '{print $2}')
|
||||
diff=$((curtime-latest))
|
||||
if [[ $diff -gt 600 ]]; then
|
||||
ifdown "$(get_interface_from_ifname "${i}")"
|
||||
fi
|
||||
else
|
||||
ifdown "$(get_interface_from_ifname "${i}")"
|
||||
fi
|
||||
done
|
||||
|
||||
# in case less than our peer-limit connections is "up", start all wg interfaces that are currently down
|
||||
if [[ "$(uci get gluon.mesh_vpn.enabled)" == "1" ]] &&
|
||||
[[ $(get_connection_count) -lt $(gluon-show-site |jsonfilter -e $.mesh_vpn.wireguard.groups.backbone.limit) ]]; then
|
||||
if [[ $(get_connection_count) -gt 0 ]]; then
|
||||
# it is ok to wait for a backup vpn connection. This sleep spreads the load for the servers
|
||||
sleep "$(awk 'BEGIN{srand();print int(rand()*180)}')"
|
||||
fi
|
||||
/usr/bin/enable-all-wg-interfaces
|
||||
fi
|
@ -0,0 +1 @@
|
||||
*/5 * * * * /usr/bin/wgcheck
|
61
package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/405-wireguard
Executable file
61
package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/405-wireguard
Executable file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/lua
|
||||
local site = require 'gluon.site'
|
||||
local uci = require('simple-uci').cursor()
|
||||
local iputil = require 'gluon.iputil'
|
||||
local sysconfig = require 'gluon.sysconfig'
|
||||
|
||||
local add_groups
|
||||
|
||||
local function generate_section_name(peer)
|
||||
return "mesh_vpn_wg_" .. peer
|
||||
end
|
||||
|
||||
local function generate_wg_iface_name(peer)
|
||||
return "mesh-vpn-" .. peer
|
||||
end
|
||||
|
||||
local function add_peer(name, count)
|
||||
local ip = iputil.mac_to_ip("fe80::/64", sysconfig.primary_mac, 0x00, count)
|
||||
|
||||
uci:section('network', 'interface', generate_section_name(name) .. 'm', {
|
||||
type = 'wireguard',
|
||||
ifname = generate_wg_iface_name(count),
|
||||
proto = 'gluon_mesh',
|
||||
})
|
||||
|
||||
uci:section('network', 'interface', generate_section_name(name) .. 's', {
|
||||
ip6addr = ip,
|
||||
ifname = generate_wg_iface_name(count),
|
||||
proto = 'static',
|
||||
})
|
||||
|
||||
uci:section('network', 'interface', generate_section_name(name) .. 'w', {
|
||||
ifname = generate_wg_iface_name(count),
|
||||
proto = 'gluon_wireguard',
|
||||
})
|
||||
end
|
||||
|
||||
local function add_group(name, config)
|
||||
local count=0
|
||||
if config.peers then
|
||||
for peername, _ in pairs(config.peers) do
|
||||
add_peer(peername, count)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
add_groups(name, config.groups)
|
||||
end
|
||||
|
||||
-- declared local above
|
||||
function add_groups(prefix, groups)
|
||||
if groups then
|
||||
for name, group in pairs(groups) do
|
||||
add_group(prefix .. '_' .. name, group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
add_groups('mesh_vpn', site.mesh_vpn.wireguard.groups())
|
||||
|
||||
uci:save('network')
|
@ -18,6 +18,13 @@
|
||||
pubkey = nil
|
||||
end
|
||||
end
|
||||
local wg_meshvpn_enabled = uci:get_bool("wireguard", "mesh_vpn_backbone", "enabled")
|
||||
if wg_meshvpn_enabled then
|
||||
pubkey = util.trim(util.exec('/usr/bin/wg pubkey < $(uci get wireguard.mesh_vpn.secret)'))
|
||||
if pubkey == '' then
|
||||
pubkey = nil
|
||||
end
|
||||
end
|
||||
|
||||
local values = {
|
||||
{ _('Hostname'), pretty_hostname.get(uci) },
|
||||
|
Loading…
Reference in New Issue
Block a user