forked from ports/contrib
263 lines
8.7 KiB
Plaintext
263 lines
8.7 KiB
Plaintext
README for WireGuard
|
|
|
|
WireGuard is a thrilling VirtualPrivateNetwork option.
|
|
|
|
It uses stateless UDP connections, and looks like a server socket, for
|
|
example in output of "ss -l", but also "iptables -nvL". It uses today's
|
|
state-of-the-art algorithms, is extremely simple to setup and maintain, and
|
|
has really nifty properties. For example, you can load /dev/null as the key
|
|
to make the VPN unusable, then just put back the correct key and it
|
|
functions again. You can also create a VPN with a single command line (plus
|
|
firewall).
|
|
|
|
It is possible to create point-to-point connections where the endpoints can
|
|
communicate only with each other, but on the other hand dedicated "servers"
|
|
can be used to which all traffic can be forwarded, so that laptops and other
|
|
end-devices can be boxed into a totally detached environment, having
|
|
internet access only through (the) VPN(s).
|
|
|
|
In all cases you need the kernel option
|
|
|
|
CONFIG_WIREGUARD=y
|
|
|
|
and generate keys:
|
|
|
|
# wg genkey | tee private.key | wg pubkey > public.key
|
|
|
|
You should also create a preshared key (may not work otherwise with
|
|
software before 2021-03-15):
|
|
|
|
# wg genpsk
|
|
|
|
Nothing magic about the keys, base64 encoded random of the correct
|
|
length (should do; and except for pubkey, which applies algorithms).
|
|
|
|
Simple point-to-point VPN
|
|
|
|
Say this is a VPN of two boxes plus broadcast, server on 10.0.0.2,
|
|
laptop on 10.0.0.1. The laptop has no fixed IP:
|
|
|
|
Server.conf:
|
|
[Interface]
|
|
PrivateKey = SERVER-PRIKEY
|
|
ListenPort = SERVER-PORT
|
|
|
|
[Peer]
|
|
PublicKey = LAPTOP-PUBKEY
|
|
# (Or only 10.0.0.1/32)
|
|
AllowedIPs = 10.0.0.0/30
|
|
|
|
Laptop.conf:
|
|
[Interface]
|
|
PrivateKey = LAPTOP-PRIKEY
|
|
# (Actually unused)
|
|
ListenPort = LAPTOP-PORT
|
|
|
|
[Peer]
|
|
PublicKey = SERVER-PUBKEY
|
|
Endpoint = SERVER-IP:SERVER-PORT
|
|
AllowedIPs = 10.0.0.2/32
|
|
|
|
Assuming these are the first interfaces we ever created:
|
|
|
|
# wg setconf wg0 Server.conf
|
|
|
|
We need some firewall rules. For the case as shown here no forwarding or
|
|
masquerading is required -- and it is _never_, but on those peers which play
|
|
a server rule! The Laptop should get away with
|
|
|
|
# ip link add dev wg0 type wireguard
|
|
# ip address add 10.0.0.1/30 dev wg0
|
|
# iptables -A OUTPUT -o wg0 -j ACCEPT
|
|
# # not even iptables -A INPUT -i wg0 -j ACCEPT
|
|
# iptables -A OUTPUT -p udp --dst SRV-IP --dport SRV-PORT -j ACCEPT
|
|
# ip link set wg0 up
|
|
# ip route add 10.0.0.1 dev wg0
|
|
|
|
That is it (beat me if i am wrong)! Different iptables on the server:
|
|
|
|
# iptables -I INPUT -i wg0 -j ACCEPT
|
|
# # not even iptables -A OUTPUT -o wg0 -j ACCEPT
|
|
# iptables -A INPUT -p udp --dport SRV-PORT -j ACCEPT
|
|
|
|
Finished. You could track the endpoint as they show up, and update the
|
|
rules with the exact address of the endpoint(s). Like this the last shown
|
|
rule of the server can apply blacklisting rules. This works easily because
|
|
once a handshake is completed the defined ListenPort and thus NETFILTER is
|
|
bypassed (at filter level), and only fewest packets actually show up on
|
|
--dport SRV-PORT. A working watchdog below.
|
|
|
|
End-user having access only via VPN
|
|
|
|
In fact this is easy. Of course you can create a wg, then a network
|
|
namespace, then move the wg to that namespace via "ip link set wg0 netns
|
|
NSNAME", then add the default route there via "ip -n NSNAME route add
|
|
default dev wg0", and be done with it. Linux even seems to allow to move
|
|
the physical hardware to a network namespace, then go the reverse way with
|
|
the new wg, leaving only wg in the base namespace, and the physical devices
|
|
boxed somewhere else. Really important differences are:
|
|
|
|
- The laptop must change the AllowedIPs of the server [Peer] to
|
|
|
|
AllowedIPs = 0.0.0.0/0
|
|
|
|
Only like this all the traffic is forwarded to the server.
|
|
|
|
- The server now needs forwarding and masquerading enabled:
|
|
|
|
# sysctl -w net.ipv4.conf.ETH0.forwarding=1
|
|
# sysctl -w net.ipv4.conf.WG0.forwarding=1
|
|
# iptables -A FORWARD -i WG0 -o ETH0 -j ACCEPT
|
|
# iptables -A FORWARD -o WG0 -i ETH0 -j ACCEPT
|
|
# iptables -t nat -A POSTROUTING -o ETH0 -j MASQUERADE
|
|
|
|
Exchange WG0/ETH0 with your devices. You may want to have a final
|
|
FORWARDING rule like
|
|
|
|
# iptables -A FORWARD -j REJECT --reject-with icmp-proto-unreachable
|
|
|
|
You may want to create an additional veth pair that links into the
|
|
namespace, so that a local DNS proxy like dnsmasq could serve the DNS of
|
|
that VPN network namespace as well as any other namespace ("interface
|
|
NSNAME" in dnsmasq.conf). No need to start multiple instances, just share
|
|
the DNS cache. Of course that single dnsmasq instance could also have
|
|
a configured upstream that is reached via VPN, maybe just another channel.
|
|
Luckily "ip link" and "ip netns" names can coexist, so:
|
|
|
|
# Placing this in 10.4.0.8/30
|
|
ip=ip ns=NSNAME 1=10.4.0.9 2=10.4.0.10 p_domain=53
|
|
...
|
|
|
|
${ip} link add ${ns} type veth peer name ${ns}_peer
|
|
${ip} link set ${ns}_peer netns ${ns}
|
|
|
|
${ip} addr add ${1}/30 dev ${ns}
|
|
${ip} link set ${ns} up
|
|
#${ip} route add ${1} dev ${ns}
|
|
|
|
${ip} -n ${ns} addr add ${2}/30 dev ${ns}_peer broadcast +
|
|
${ip} -n ${ns} link set ${ns}_peer up
|
|
${ip} -n ${ns} route add ${1} dev ${ns}_peer
|
|
|
|
iptables_rule filter INPUT -A -i ${ns} \
|
|
-p tcp --dport ${p_domain} -j ${ACC}
|
|
iptables_rule filter INPUT -A -i ${ns} \
|
|
-p udp --dport ${p_domain} -j ${ACC}
|
|
iptables_rule filter INPUT -A -i ${ns} -j REJECT
|
|
|
|
So now only DNS is allowed from the network namespace to the base namespace,
|
|
where dnsmasq is listening on "interface NSNAME" (maybe after a restart).
|
|
All other traffic generated in NSNAME but to 10.4.0.9 is routed through the
|
|
WireGuard VPN.
|
|
|
|
A watchdog
|
|
|
|
Driven by cron one can selectively whitelist endpoints without fixed
|
|
IP addresses, in order to apply strict black listing on those
|
|
|
|
# iptables -A INPUT -p udp --dport PORT -j ACCEPT
|
|
|
|
rules that are needed (on servers). Here is one idea, it is pretty fresh
|
|
but working for some time here. Imagine a configuration
|
|
|
|
: ${RUNDIR:=/run}
|
|
|
|
# (y/empty) Wireguard VPN (ie: look for WG_digit_ADDR settings)?
|
|
: ${WG:=}
|
|
# If empty
|
|
: ${WG_WATCHDOG:=${RUNDIR}/.net-qos-wg-watch}
|
|
# For wg_watchdog() (aka "$0 watchdog-wg"): persistance data file.
|
|
# Watchdog only works if non-empty.
|
|
# -> WG_digit_ADDR='any wg(8) address:LISTEN-PORT'
|
|
# Ie address+CIDR netmask plus listen port. Whether we create it.
|
|
....
|
|
|
|
In the following, please substitute ACCEPT for f_m1, and INPUT for i_good.
|
|
I currently use primitive CONNMARK jumps for all my traffic, instead of
|
|
using the "fwmark" feature of WireGuard to shortcut that for the VPN.
|
|
|
|
wg_watchdog() {
|
|
[ -n "${WG_WATCHDOG}" ] || {
|
|
echo >&2 '$WG_WATCHDOG is not set'
|
|
return 0
|
|
}
|
|
|
|
touch "${WG_WATCHDOG}" "${WG_WATCHDOG}".new "${WG_WATCHDOG}".lck
|
|
chown root:root "${WG_WATCHDOG}" "${WG_WATCHDOG}".new "${WG_WATCHDOG}".lck
|
|
chmod 0600 "${WG_WATCHDOG}" "${WG_WATCHDOG}".new "${WG_WATCHDOG}".lck
|
|
|
|
if exec 7>"${WG_WATCHDOG}.lck" && flock 7; then :; else
|
|
echo >&2 'Cannot aquire lock file '${WG_WATCHDOG}.lck
|
|
return 1
|
|
fi
|
|
|
|
# New list of peers
|
|
printf '' > "${WG_WATCHDOG}".new
|
|
wl=
|
|
|
|
id=0
|
|
while :; do
|
|
eval x=\$WG_${id}_ADDR
|
|
[ -z "${x}" ] && break
|
|
|
|
wg__splita "${x}"
|
|
dport=${port}
|
|
|
|
x=`${wg} show wg${id} endpoints 2>/dev/null`
|
|
if [ ${?} -eq 0 ]; then
|
|
x=`echo ${x} | cut -f2 -d' '`
|
|
if [ "${x}" != '(none)' ]; then
|
|
wg__splita "${x}"
|
|
wl=${wl}' '${addr}
|
|
printf -- "-p udp --src %s --dport %s -j f_m1\n" \
|
|
"${addr}" "${dport}" >> "${WG_WATCHDOG}".new
|
|
#--sport ${port}
|
|
fi
|
|
fi
|
|
|
|
id=$((id + 1))
|
|
done
|
|
|
|
# ..if different to old one, recreate firewall rules
|
|
if cmp "${WG_WATCHDOG}".new "${WG_WATCHDOG}" >/dev/null 2>&1; then :; else
|
|
if [ -s "${WG_WATCHDOG}" ]; then
|
|
while read l; do
|
|
iptables_rule filter i_good -D ${l}
|
|
done < "${WG_WATCHDOG}"
|
|
fi
|
|
|
|
# Add new list of peers
|
|
if [ -n "${wl}" ]; then
|
|
while read l; do
|
|
iptables_rule filter i_good -I 1 ${l}
|
|
done < "${WG_WATCHDOG}".new
|
|
logger -t /root/bin/net-qos.sh/WG 'whitelist: '${wl}
|
|
fi
|
|
|
|
cp -f "${WG_WATCHDOG}".new "${WG_WATCHDOG}"
|
|
fi
|
|
|
|
exec 7>&-
|
|
}
|
|
|
|
wg__splita() {
|
|
addr=${1%:*}
|
|
port=${1##*:}
|
|
ip6=0
|
|
|
|
if [ "${addr}" != "${addr%]*}" ]; then
|
|
ip6=1
|
|
addr=${addr%]*}
|
|
addr=${addr#[*}
|
|
fi
|
|
|
|
if [ "${addr}" != "${addr%/*}" ]; then
|
|
mask=/${addr#*/}
|
|
addr=${addr%/*}
|
|
else
|
|
mask=/32
|
|
fi
|
|
}
|
|
|
|
# s-ts-mode
|