Skip to content

Commit

Permalink
ffmuc-gluon-mesh-vpn-wireguard-vxlan: add package (#6)
Browse files Browse the repository at this point in the history
Co-authored-by: Annika Wickert <[email protected]>
Co-authored-by: krombel <[email protected]>
Co-authored-by: Martin Weinelt <[email protected]>
Co-authored-by: lqb <[email protected]>
Co-authored-by: lqb <[email protected]>
Co-authored-by: Julian Labus <[email protected]>
Co-authored-by: Tristan Helmich <[email protected]>
Co-authored-by: goligo <[email protected]>
  • Loading branch information
9 people authored Sep 17, 2021
1 parent 1d7e81b commit 1df9c3d
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 0 deletions.
17 changes: 17 additions & 0 deletions ffmuc-gluon-mesh-vpn-wireguard-vxlan/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
include $(TOPDIR)/rules.mk

PKG_NAME:=ffmuc-gluon-mesh-vpn-wireguard-vxlan
PKG_VERSION:=1
PKG_RELEASE:=1

PKG_MAINTAINER:=Annika Wickert <[email protected]>
PKG_LICENSE:=GPL-2.0-or-later

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

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

$(eval $(call BuildPackageGluon,gluon-mesh-vpn-wireguard-vxlan))
59 changes: 59 additions & 0 deletions ffmuc-gluon-mesh-vpn-wireguard-vxlan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# gluon-mesh-vpn-wireguard-vxlan

You can use this package for connecting with wireguard to the Freifunk Munich network.

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


```
mesh_vpn = {
mtu = 1400,
wireguard = {
enabled = '1',
iface = 'mesh-vpn',
limit = '1', -- actually unused
broker = 'broker.ffmuc.net/api/v1/wg/key/exchange',
peers = {
{
publickey ='N9uF5Gg1B5AqWrE9IuvDgzmQePhqhb8Em/HrRpAdnlY=',
endpoint ='ffkwsn01.freifunk-koenigswinter.de:30020',
link_address = 'fe80::f000:22ff:fe12:01',
},
{
publickey ='liatbdT62FbPiDPHKBqXVzrEo6hc5oO5tmEKDMhMTlU=',
endpoint ='ffkwsn02.freifunk-koenigswinter.de:30020',
link_address = 'fe80::f000:22ff:fe12:02',
},
{
publickey ='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 [wgkex](https://github.com/freifunkMUC/wgkex) the FFMUC wireguard broker running on the configured broker address. The broker programms the gateway to accept the WireGuard key which is transmitted during connection.

For the health-checks a webserver of some kind needs to listen to `HTTP GET` requests on the gateways.

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

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

### Contact

Feel free to ask questions in the [FFMUC chat](https://chat.ffmuc.net).
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/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 wireguard.mesh_vpn.privatekey | wg pubkey) |md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')"
local oldIFS="$IFS"; IFS=':'; set -- $macaddr; IFS="$oldIFS"
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_publickey="$1"
local peer_endpoint="$2"
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 wireguard.mesh_vpn.privatekey)
if [ "$?" -ne "0" ]; then
uci set wireguard.mesh_vpn.privatekey=$(wg genkey)
uci commit wireguard
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

# 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.publickey)"

logger -t checkuplink "Selected peer $PEER"

endpoint="$(check_address_family "$PEER_PUBLICKEY" "$(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 wireguard.mesh_vpn.privatekey | wg pubkey)
SEGMENT=$(uci get gluon.core.domain)

# 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
gluon-wan wget -q -O- --post-data='{"domain": "'"$SEGMENT"'","public_key": "'"$PUBLICKEY"'"}' $PROTO://$(uci get wireguard.broker)

# Bring up the wireguard interface
ip link add dev $MESH_VPN_IFACE type wireguard
wg set $MESH_VPN_IFACE fwmark 1
uci get wireguard.mesh_vpn.privatekey | 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.publickey) persistent-keepalive 25 allowed-ips $(uci get wireguard.peer_$PEER.link_address)/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 8472 -j ACCEPT

# Bring up VXLAN
ip link add mesh-vpn type vxlan id "$(lua -e 'print(tonumber(require("gluon.util").domain_seed_bytes("gluon-mesh-vpn-vxlan", 3), 16))')" local $(interface_linklocal "$MESH_VPN_IFACE") remote $(uci get wireguard.peer_$PEER.link_address) dstport 8472 dev $MESH_VPN_IFACE
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,42 @@
#!/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
local privkey = uci:get("wireguard", "mesh_vpn", "privatekey") or ""

-- 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:set('gluon', 'mesh_vpn', 'enabled', mesh_enabled)

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,
privatekey = privkey,
})

for name, peer in pairs(site.mesh_vpn.wireguard.peers()) do
uci:section("wireguard", "peer", "peer_" .. name, {
enabled = true,
endpoint = peer.endpoint,
publickey = peer.publickey,
link_address = peer.link_address,
})
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

0 comments on commit 1df9c3d

Please sign in to comment.