Skip to content

Commit

Permalink
ffac-wg-registration: add wireguard packages (freifunkMUC#35)
Browse files Browse the repository at this point in the history
* ffac-wg-registration: add wireguard packages
- adds ffac-wg-registration - which communicates with a broker
- adds ffac-mesh-vpn-wireguard-openwrt19 - which provides upstream compatible wg functionality for older devices

* ffac-mesh-vpn-wireguard-openwrt19: fix public_key

* set coorect key generation
maurerle authored Jun 1, 2023
1 parent add7485 commit 89aa96a
Showing 14 changed files with 413 additions and 0 deletions.
18 changes: 18 additions & 0 deletions ffac-mesh-vpn-wireguard-openwrt19/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2023 Florian Maurer (FFAC), Annika Wickert (FFMUC)
# SPDX-License-Identifier: GPL-2.0-or-later
include $(TOPDIR)/rules.mk

PKG_NAME:=ffac-mesh-vpn-wireguard-openwrt19
PKG_VERSION:=1
PKG_RELEASE:=1

PKG_LICENSE:=GPL-2.0-or-later

include $(TOPDIR)/../package/gluon.mk

define Package/$(PKG_NAME)
TITLE:=Support for connecting meshes via wireguard
DEPENDS:=+gluon-mesh-vpn-core +micrond +kmod-wireguard +wireguard-tools +ip-full
endef

$(eval $(call BuildPackageGluon,$(PKG_NAME)))
71 changes: 71 additions & 0 deletions ffac-mesh-vpn-wireguard-openwrt19/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# ffac-mesh-vpn-wireguard-openwrt19

You can use this package for connecting with wireguard to a upstream gluon-mesh-vpn-wireguard compatible network.
When upgrading to the upstream version of mesh-vpn-wireguard, the same wireguard privatekey is used.

A special thanks to Annika Wickert @awlx who first developed ffmuc-mesh-vpn-wireguard-vxlan

This version is compatible with Gluon v2021.1.x and later, but it is highly advertised to use gluon-mesh-vpn-wireguard on v2022.1.x and later.

You should use something like the following in the site.conf:

**Note that the peers are not named in contrast to the upstream package version**

```
mesh_vpn = {
mtu = 1400,
wireguard = {
enabled = '1',
iface = 'mesh-vpn',
broker = 'wg-broker.freifunk-aachen.de/api/add_key',
peers = {
{
public_key ='N9uF5Gg1B5AqWrE9IuvDgzmQePhqhb8Em/HrRpAdnlY=',
endpoint ='ffkwsn01.freifunk-koenigswinter.de:30020',
link_address = 'fe80::f000:22ff:fe12:01',
},
{
public_key ='liatbdT62FbPiDPHKBqXVzrEo6hc5oO5tmEKDMhMTlU=',
endpoint ='ffkwsn02.freifunk-koenigswinter.de:30020',
link_address = 'fe80::f000:22ff:fe12:02',
},
{
public_key ='xakSGG39D1v90j3Z9eVWzojh6nDbnsVUc/RByVdcKB0=',
endpoint ='ffkwsn03.freifunk-koenigswinter.de:30020',
link_address = 'fe80::f000:22ff:fe12:07',
},
},
},
```

And you should include the package in the site.mk of course!

### Dependencies

This relies on a broker which accepts post requests like `{'node_name': 'name', 'public_key': 'my_wg_pubkey'}`.
The broker adds the publickey to the WireGuard supernodes so that they accept the WireGuard key which is transmitted during connection.

### How it works

When `checkuplink` gets called (which happens every minute via cronjob), it checks if the gateway connection is still alive by calling `wget` and connecting to `wireguard.peer.peer_[number].link_address`. If this address replies we also start a `batctl ping` to the same address. If both checks succeed the connection just stays alive.

If one of the checks above bails out with an error the reconnect cycle is started. Which means `checkuplink` registers itself with `wireguard.broker` by sending the WireGuard public_key over either http or https (depending on the device support). After the key was sent the script tries to randomely connect to one of the `wireguard.peer`. This script prefers to establish connections over IPv6 and falls back to IPv4 only if there is no IPv6 default route.

### Interesting Links

- [FFAC Broker](https://github.com/ffac/ff-supernode/blob/main/playbooks/roles/ff.wgbroker/templates/broker.py)
- [FFMUC: Half a year with WireGuard](https://www.slideshare.net/AnnikaWickert/ffmuc-half-a-year-with-wireguard)
- [FFMUC: WireGuard Firmware (German)](https://ffmuc.net/freifunkmuc/2020/12/03/wireguard-firmware/)
- [FFMUC: Statistics](https://stats.ffmuc.net)

### Upstream

This package is compatible with the upstream support for wireguard added in Gluon.
In contrast to FFMUC it uses:

- udp6zerocsumtx and udp6zerocsumrx
- 4789
- different domain_seed_bytes calculation
- public_key instead of publickey in site
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/sh

if { set -C; 2>/dev/null >/var/lock/checkuplink.lock; }; then
trap "rm -f /var/lock/checkuplink.lock" EXIT
else
echo "Lock file exists... exiting"
exit
fi

interface_linklocal() {
# We generate a predictable v6 address
#local macaddr="$(echo $(uci get network.wg_mesh.private_key | wg pubkey) |md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')"
local macaddr="$(printf "%s" "$(uci get network.wg_mesh.private_key | wg pubkey)"|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')"
local oldIFS="$IFS"; IFS=':'; set -- $macaddr; IFS="$oldIFS"
# shellcheck disable=SC2086
echo "fe80::$1$2:$3ff:fe$4:$5$6"
}

clean_port() {
echo "$(echo $1 | sed -r 's/:[0-9]+$|\[|\]//g')"
}

check_address_family() {
local peer_endpoint="$1"
local gateway="$(clean_port $peer_endpoint)"
# Check if we have a default route for v6 if not fallback to v4
defgw=$(ip -6 route show table 1 | grep 'default via')
if [ "$?" -eq "0" ]; then
local ipv6="$(gluon-wan nslookup $gateway | grep 'Address [0-9]' | egrep -o '([a-f0-9:]+:+)+[a-f0-9]+')"
echo [$ipv6]$(echo $peer_endpoint | egrep -oe :[0-9]+$)
else
local ipv4="$(gluon-wan nslookup $gateway | grep 'Address [0-9]' | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b")"
echo $ipv4$(echo $peer_endpoint | egrep -oe :[0-9]+$)
fi

}

# Do we already have a private-key? If not generate one
temp=$(uci get network.wg_mesh.private_key)
if [ "$?" -ne "0" ]; then
uci set network.wg_mesh=interface
uci set network.wg_mesh.private_key=$(wg genkey)
uci commit network
fi

# Is wireguard enabled?
if [ "$(uci get wireguard.mesh_vpn.enabled)" == "true" ] || [ "$(uci get wireguard.mesh_vpn.enabled)" == "1" ]; then

#We assume we are not connected by default
CONNECTED=0

MESH_VPN_IFACE=$(uci get wireguard.mesh_vpn.iface)

# Check connectivity to supernode
wget http://[$(wg | grep fe80 | awk '{split($3,A,"/")};{print A[1]}')%$MESH_VPN_IFACE]/ --timeout=5 -O/dev/null -q
if [ "$?" -eq "0" ]; then
GWMAC=$(batctl gwl | grep \* | awk '{print $2}')
batctl ping -c 5 $GWMAC &> /dev/null
if [ "$?" -eq "0" ]; then
CONNECTED=1
fi
fi

# If we don't have a connection we try to connect
if [ "$CONNECTED" -ne "1" ]; then
logger -t checkuplink "Reconnecting ..."
NTP_SERVER=$(uci get system.ntp.server)
gluon-wan /usr/sbin/ntpd -n -N -S /usr/sbin/ntpd-hotplug -p $NTP_SERVER -q

# TODO use upstream config schema: uci show wireguard | grep =peer | sed "s/[^_]*_\([^=]*\).*/\1/"
# Get the number of configured peers and randomly select one
NUMBER_OF_PEERS=$(uci -q show wireguard | egrep -ce peer_[0-9]+.endpoint)
PEER="$(awk -v min=1 -v max=$NUMBER_OF_PEERS 'BEGIN{srand(); print int(min+rand()*(max-min+1))}')"
PEER_PUBLICKEY="$(uci get wireguard.peer_$PEER.public_key)"

logger -t checkuplink "Selected peer $PEER"

endpoint="$(check_address_family "$(uci get wireguard.peer_$PEER.endpoint)")"

logger -t checkuplink "Connecting to $endpoint"

# Delete Interfaces
ip link set nomaster dev mesh-vpn &> /dev/null
ip link delete dev mesh-vpn &> /dev/null
ip link del $MESH_VPN_IFACE &> /dev/null
PUBLICKEY=$(uci get network.wg_mesh.private_key | wg pubkey)

# Push public key to broker, test for https and use if supported
wget -q https://[::1]
if [ $? -eq 1 ]; then
PROTO=http
else
PROTO=https
fi

NODENAME=$(uci get system.@system[0].hostname)
BROKER=$(uci get wireguard.mesh_vpn.broker)
gluon-wan wget -q -O- --post-data='{"node_name": "'"$NODENAME"'","public_key": "'"$PUBLICKEY"'"}' $PROTO://$BROKER

# Bring up the wireguard interface
ip link add dev $MESH_VPN_IFACE type wireguard
wg set $MESH_VPN_IFACE fwmark 1
uci get network.wg_mesh.private_key | wg set $MESH_VPN_IFACE private-key /proc/self/fd/0
ip link set up dev $MESH_VPN_IFACE

# Add link-address and Peer
ip address add "$(interface_linklocal "$MESH_VPN_IFACE")"/64 dev $MESH_VPN_IFACE
if [ "$endpoint" == "" ]; then
endpoint=$(uci get wireguard.peer_$PEER.endpoint)
fi
gluon-wan wg set $MESH_VPN_IFACE peer $(uci get wireguard.peer_$PEER.public_key) persistent-keepalive 25 allowed-ips fe80::1/128 endpoint $endpoint

# We need to allow incoming vxlan traffic on mesh iface
sleep 10
ip6tables -I INPUT 1 -i $MESH_VPN_IFACE -m udp -p udp --dport 4789 -j ACCEPT
logger -t checkuplink "vxlan link $(interface_linklocal "$MESH_VPN_IFACE")"
# Bring up VXLAN
ip link add mesh-vpn type vxlan id "$(lua -e 'print(tonumber(require("gluon.util").domain_seed_bytes("gluon-mesh-vxlan", 3), 16))')" local $(interface_linklocal "$MESH_VPN_IFACE") remote fe80::1 dstport 4789 dev $MESH_VPN_IFACE udp6zerocsumtx udp6zerocsumrx
ip link set up dev mesh-vpn

sleep 5
# If we have a BATMAN_V env we need to correct the throughput value now
batctl hardif mesh-vpn throughput_override 1000mbit;
fi
fi
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* * * * * sleep $(awk 'BEGIN{srand();print int(rand()*40)}') && /lib/gluon/gluon-mesh-wireguard-vxlan/checkuplink
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/lua

local site = require 'gluon.site'
local uci = require("simple-uci").cursor()

local wg_enabled = uci:get_bool('wireguard', 'mesh_vpn', 'enabled') or false

-- Clean up previous configuration
uci:delete_all('wireguard', 'peer', function(peer)
return peer.preserve ~= '1'
end)
-- Clean up previous configuration
uci:delete_all('wireguard', 'wireguard', function(peer)
return peer.preserve ~= '1'
end)

local mesh_enabled = uci:get_bool('gluon', 'mesh_vpn', 'enabled') -- default
or uci:get_bool('fastd', 'mesh_vpn', 'enabled') --migration
or wg_enabled -- specific config

uci:section("wireguard", "wireguard", "mesh_vpn", {
iface = site.mesh_vpn.wireguard.iface(),
limit = site.mesh_vpn.wireguard.limit(),
broker = site.mesh_vpn.wireguard.broker(),
enabled = mesh_enabled,
})

for name, peer in pairs(site.mesh_vpn.wireguard.peers()) do
uci:section("wireguard", "peer", "peer_" .. name, {
enabled = true,
endpoint = peer.endpoint,
public_key = peer.public_key,
})
end

uci:save('wireguard')
uci:save('gluon')
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
local uci = require('simple-uci').cursor()

local site = require 'gluon.site'
local util = require 'gluon.util'
local vpn_core = require 'gluon.mesh-vpn'

local M = {}

function M.public_key()
return util.trim(util.exec('/usr/bin/wg show wg_mesh_vpn public-key'))
end

function M.enable(val)
uci:set('wireguard', 'mesh_vpn', 'enabled', val)
uci:save('wireguard')
end

function M.active()
return site.mesh_vpn.wireguard() ~= nil
end

function M.set_limit(ingress_limit, egress_limit)
uci:delete('simple-tc', 'mesh_vpn')
if ingress_limit ~= nil and egress_limit ~= nil then
uci:section('simple-tc', 'interface', 'mesh_vpn', {
ifname = vpn_core.get_interface(),
enabled = true,
limit_egress = egress_limit,
limit_ingress = ingress_limit,
})
end

uci:save('simple-tc')
end

return M
24 changes: 24 additions & 0 deletions ffac-wg-registration/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
BSD 2-Clause License

Copyright (c) 2023, Florian Maurer (FFAC)

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18 changes: 18 additions & 0 deletions ffac-wg-registration/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2023 Florian Maurer (FFAC)
# SPDX-License-Identifier: BSD-2-Clause
include $(TOPDIR)/rules.mk

PKG_NAME:=ffac-wg-registration
PKG_VERSION:=1
PKG_RELEASE:=1

PKG_LICENSE:=BSD-2-Clause

include $(TOPDIR)/../package/gluon.mk

define Package/$(PKG_NAME)
TITLE:=Support for connecting meshes via wireguard
DEPENDS:=+gluon-mesh-vpn-core +micrond +kmod-wireguard +wireguard-tools +ip-full
endef

$(eval $(call BuildPackageGluon,$(PKG_NAME)))
34 changes: 34 additions & 0 deletions ffac-wg-registration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ffac-wg-registration

You can use this package to publish the wg publickey to the server.

You should use something like the following in the site.conf:


```
mesh_vpn = {
enabled = true,
wireguard = {
broker = 'wg-broker.freifunk-aachen.de/api/add_key',
peers = {
{
public_key ='N9uF5Gg1B5AqWrE9IuvDgzmQePhqhb8Em/HrRpAdnlY=',
endpoint ='01.wg-node.freifunk-aachen.de:51819',
},
{
public_key ='liatbdT62FbPiDPHKBqXVzrEo6hc5oO5tmEKDMhMTlU=',
endpoint ='01.wg-node.freifunk-aachen.de:51820',
},
},
mtu = 1400,
},
```
And you should include the package in the site.mk of course!

### Dependencies

This relies on a broker which accepts post requests like `{'node_name': 'name', 'public_key': 'my_wg_pubkey'}`.
The broker programms the gateway to accept the WireGuard key which is transmitted during connection.

An example broker can be found here: https://gist.github.com/maurerle/2e4a434a882f524d1304bd82c7307984
25 changes: 25 additions & 0 deletions ffac-wg-registration/files/lib/gluon/upgrade/400-wg-registration
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/lua
local uci = require('simple-uci').cursor()

function file_exists(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
end

os.remove('/etc/config/wireguard')

local file = io.open('/etc/config/wireguard', 'w')
file:close()

-- Is multidomain support activated?
local domain = uci:get('gluon', 'core', 'domain')
local datei
if domain and file_exists('/lib/gluon/domains/' .. domain .. '.json') then
datei = '/lib/gluon/domains/' .. domain .. '.json'
else
datei = '/lib/gluon/site.json'
end
uci:section('wireguard', 'wireguard', 'mesh_vpn', {
broker = io.popen('jsonfilter -i ' .. datei .. ' -e "$.mesh_vpn.wireguard.broker"'):read("*l")
})
uci:commit('wireguard')
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh

if [ "$(uci get gluon.mesh_vpn.enabled)" == "true" ] || [ "$(uci get gluon.mesh_vpn.enabled)" == "1" ]; then
# check if registration has been done since last boot
if [ ! -f /tmp/WG_REGISTRATION_SUCCESSFUL ]; then
# Push public key to broker, test for https and use if supported
wget -q https://[::1]
if [ $? -eq 1 ]; then
PROTO=http
else
PROTO=https
fi
PUBLICKEY=$(uci get network.wg_mesh.private_key | wg pubkey)
NODENAME=$(uci get system.@system[0].hostname)
BROKER=$(uci get wireguard.mesh_vpn.broker)
logger -t wg-registration "Post $NODENAME and $PUBLICKEY to $PROTO://$BROKER"
gluon-wan wget -q -O- --post-data='{"node_name": "'"$NODENAME"'","public_key": "'"$PUBLICKEY"'"}' $PROTO://$BROKER
if [ $? -eq 0 ]; then
touch /tmp/WG_REGISTRATION_SUCCESSFUL
logger -t wg-registration "successfully registered wg publickey"
fi
fi
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* * * * * sleep $(awk 'BEGIN{srand();print int(rand()*40)}') && /lib/gluon/wg-registration/registration.sh

0 comments on commit 89aa96a

Please sign in to comment.