1 README for WireGuard
2
3 WireGuard is a thrilling VirtualPrivateNetwork option.
4
5 It uses stateless UDP connections, and looks like a server socket, for
6 example in output of "ss -l", but also "iptables -nvL". It uses today's
7 state-of-the-art algorithms, is extremely simple to setup and maintain, and
8 has really nifty properties. For example, you can load /dev/null as the key
9 to make the VPN unusable, then just put back the correct key and it
10 functions again. You can also create a VPN with a single command line (plus
11 firewall).
12
13 It is possible to create point-to-point connections where the endpoints can
14 communicate only with each other, but on the other hand dedicated "servers"
15 can be used to which all traffic can be forwarded, so that laptops and other
16 end-devices can be boxed into a totally detached environment, having
17 internet access only through (the) VPN(s).
18
19 In all cases you need the kernel option
20
21 CONFIG_WIREGUARD=y
22
23 and generate keys:
24
25 # wg genkey | tee private.key | wg pubkey > public.key
26
27 You should also create a preshared key (may not work otherwise with
28 software before 2021-03-15):
29
30 # wg genpsk
31
32 Nothing magic about the keys, base64 encoded random of the correct
33 length (should do; and except for pubkey, which applies algorithms).
34
35 Simple point-to-point VPN
36
37 Say this is a VPN of two boxes plus broadcast, server on 10.0.0.2,
38 laptop on 10.0.0.1. The laptop has no fixed IP:
39
40 Server.conf:
41 [Interface]
42 PrivateKey = SERVER-PRIKEY
43 ListenPort = SERVER-PORT
44
45 [Peer]
46 PublicKey = LAPTOP-PUBKEY
47 # (Or only 10.0.0.1/32)
48 AllowedIPs = 10.0.0.0/30
49
50 Laptop.conf:
51 [Interface]
52 PrivateKey = LAPTOP-PRIKEY
53 # (Actually unused)
54 ListenPort = LAPTOP-PORT
55
56 [Peer]
57 PublicKey = SERVER-PUBKEY
58 Endpoint = SERVER-IP:SERVER-PORT
59 AllowedIPs = 10.0.0.2/32
60
61 Assuming these are the first interfaces we ever created:
62
63 # wg setconf wg0 Server.conf
64
65 We need some firewall rules. For the case as shown here no forwarding or
66 masquerading is required -- and it is _never_, but on those peers which play
67 a server rule! The Laptop should get away with
68
69 # ip link add dev wg0 type wireguard
70 # ip address add 10.0.0.1/30 dev wg0
71 # iptables -A OUTPUT -o wg0 -j ACCEPT
72 # # not even iptables -A INPUT -i wg0 -j ACCEPT
73 # iptables -A OUTPUT -p udp --dst SRV-IP --dport SRV-PORT -j ACCEPT
74 # ip link set wg0 up
75 # ip route add 10.0.0.1 dev wg0
76
77 That is it (beat me if i am wrong)! Different iptables on the server:
78
79 # iptables -I INPUT -i wg0 -j ACCEPT
80 # # not even iptables -A OUTPUT -o wg0 -j ACCEPT
81 # iptables -A INPUT -p udp --dport SRV-PORT -j ACCEPT
82
83 Finished. You could track the endpoint as they show up, and update the
84 rules with the exact address of the endpoint(s). Like this the last shown
85 rule of the server can apply blacklisting rules. This works easily because
86 once a handshake is completed the defined ListenPort and thus NETFILTER is
87 bypassed (at filter level), and only fewest packets actually show up on
88 --dport SRV-PORT. A working watchdog below.
89
90 End-user having access only via VPN
91
92 In fact this is easy. Of course you can create a wg, then a network
93 namespace, then move the wg to that namespace via "ip link set wg0 netns
94 NSNAME", then add the default route there via "ip -n NSNAME route add
95 default dev wg0", and be done with it. Linux even seems to allow to move
96 the physical hardware to a network namespace, then go the reverse way with
97 the new wg, leaving only wg in the base namespace, and the physical devices
98 boxed somewhere else. Really important differences are:
99
100 - The laptop must change the AllowedIPs of the server [Peer] to
101
102 AllowedIPs = 0.0.0.0/0
103
104 Only like this all the traffic is forwarded to the server.
105
106 - The server now needs forwarding and masquerading enabled:
107
108 # sysctl -w net.ipv4.conf.ETH0.forwarding=1
109 # sysctl -w net.ipv4.conf.WG0.forwarding=1
110 # iptables -A FORWARD -i WG0 -o ETH0 -j ACCEPT
111 # iptables -A FORWARD -o WG0 -i ETH0 -j ACCEPT
112 # iptables -t nat -A POSTROUTING -o ETH0 -j MASQUERADE
113
114 Exchange WG0/ETH0 with your devices. You may want to have a final
115 FORWARDING rule like
116
117 # iptables -A FORWARD -j REJECT --reject-with icmp-proto-unreachable
118
119 You may want to create an additional veth pair that links into the
120 namespace, so that a local DNS proxy like dnsmasq could serve the DNS of
121 that VPN network namespace as well as any other namespace ("interface
122 NSNAME" in dnsmasq.conf). No need to start multiple instances, just share
123 the DNS cache. Of course that single dnsmasq instance could also have
124 a configured upstream that is reached via VPN, maybe just another channel.
125 Luckily "ip link" and "ip netns" names can coexist, so:
126
127 # Placing this in 10.4.0.8/30
128 ip=ip ns=NSNAME 1=10.4.0.9 2=10.4.0.10 p_domain=53
129 ...
130
131 ${ip} link add ${ns} type veth peer name ${ns}_peer
132 ${ip} link set ${ns}_peer netns ${ns}
133
134 ${ip} addr add ${1}/30 dev ${ns}
135 ${ip} link set ${ns} up
136 #${ip} route add ${1} dev ${ns}
137
138 ${ip} -n ${ns} addr add ${2}/30 dev ${ns}_peer broadcast +
139 ${ip} -n ${ns} link set ${ns}_peer up
140 ${ip} -n ${ns} route add ${1} dev ${ns}_peer
141
142 iptables_rule filter INPUT -A -i ${ns} \
143 -p tcp --dport ${p_domain} -j ${ACC}
144 iptables_rule filter INPUT -A -i ${ns} \
145 -p udp --dport ${p_domain} -j ${ACC}
146 iptables_rule filter INPUT -A -i ${ns} -j REJECT
147
148 So now only DNS is allowed from the network namespace to the base namespace,
149 where dnsmasq is listening on "interface NSNAME" (maybe after a restart).
150 All other traffic generated in NSNAME but to 10.4.0.9 is routed through the
151 WireGuard VPN.
152
153 A watchdog
154
155 Driven by cron one can selectively whitelist endpoints without fixed
156 IP addresses, in order to apply strict black listing on those
157
158 # iptables -A INPUT -p udp --dport PORT -j ACCEPT
159
160 rules that are needed (on servers). Here is one idea, it is pretty fresh
161 but working for some time here. Imagine a configuration
162
163 : ${RUNDIR:=/run}
164
165 # (y/empty) Wireguard VPN (ie: look for WG_digit_ADDR settings)?
166 : ${WG:=}
167 # If empty
168 : ${WG_WATCHDOG:=${RUNDIR}/.net-qos-wg-watch}
169 # For wg_watchdog() (aka "$0 watchdog-wg"): persistance data file.
170 # Watchdog only works if non-empty.
171 # -> WG_digit_ADDR='any wg(8) address:LISTEN-PORT'
172 # Ie address+CIDR netmask plus listen port. Whether we create it.
173 ....
174
175 In the following, please substitute ACCEPT for f_m1, and INPUT for i_good.
176 I currently use primitive CONNMARK jumps for all my traffic, instead of
177 using the "fwmark" feature of WireGuard to shortcut that for the VPN.
178
179 wg_watchdog() {
180 [ -n "${WG_WATCHDOG}" ] || {
181 echo >&2 '$WG_WATCHDOG is not set'
182 return 0
183 }
184
185 touch "${WG_WATCHDOG}" "${WG_WATCHDOG}".new "${WG_WATCHDOG}".lck
186 chown root:root "${WG_WATCHDOG}" "${WG_WATCHDOG}".new "${WG_WATCHDOG}".lck
187 chmod 0600 "${WG_WATCHDOG}" "${WG_WATCHDOG}".new "${WG_WATCHDOG}".lck
188
189 if exec 7>"${WG_WATCHDOG}.lck" && flock 7; then :; else
190 echo >&2 'Cannot aquire lock file '${WG_WATCHDOG}.lck
191 return 1
192 fi
193
194 # New list of peers
195 printf '' > "${WG_WATCHDOG}".new
196 wl=
197
198 id=0
199 while :; do
200 eval x=\$WG_${id}_ADDR
201 [ -z "${x}" ] && break
202
203 wg__splita "${x}"
204 dport=${port}
205
206 x=`${wg} show wg${id} endpoints 2>/dev/null`
207 if [ ${?} -eq 0 ]; then
208 x=`echo ${x} | cut -f2 -d' '`
209 if [ "${x}" != '(none)' ]; then
210 wg__splita "${x}"
211 wl=${wl}' '${addr}
212 printf -- "-p udp --src %s --dport %s -j f_m1\n" \
213 "${addr}" "${dport}" >> "${WG_WATCHDOG}".new
214 #--sport ${port}
215 fi
216 fi
217
218 id=$((id + 1))
219 done
220
221 # ..if different to old one, recreate firewall rules
222 if cmp "${WG_WATCHDOG}".new "${WG_WATCHDOG}" >/dev/null 2>&1; then :; else
223 if [ -s "${WG_WATCHDOG}" ]; then
224 while read l; do
225 iptables_rule filter i_good -D ${l}
226 done < "${WG_WATCHDOG}"
227 fi
228
229 # Add new list of peers
230 if [ -n "${wl}" ]; then
231 while read l; do
232 iptables_rule filter i_good -I 1 ${l}
233 done < "${WG_WATCHDOG}".new
234 logger -t /root/bin/net-qos.sh/WG 'whitelist: '${wl}
235 fi
236
237 cp -f "${WG_WATCHDOG}".new "${WG_WATCHDOG}"
238 fi
239
240 exec 7>&-
241 }
242
243 wg__splita() {
244 addr=${1%:*}
245 port=${1##*:}
246 ip6=0
247
248 if [ "${addr}" != "${addr%]*}" ]; then
249 ip6=1
250 addr=${addr%]*}
251 addr=${addr#[*}
252 fi
253
254 if [ "${addr}" != "${addr%/*}" ]; then
255 mask=/${addr#*/}
256 addr=${addr%/*}
257 else
258 mask=/32
259 fi
260 }
261
262 # s-ts-mode
|