-
Notifications
You must be signed in to change notification settings - Fork 57
/
run
318 lines (276 loc) · 11.7 KB
/
run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#!/bin/bash
[ -n "$DEBUG" ] && set -o xtrace
# Check and/or set default options
# Should be 0/1
[[ "$EXIT_ON_FATAL" =~ ^[0-1]$ ]] || EXIT_ON_FATAL=0
[[ "$FIREWALL" =~ ^[0-1]$ ]] || FIREWALL=1
[[ "$PORT_FILE_CLEANUP" =~ ^[0-1]$ ]] || PORT_FILE_CLEANUP=0
[[ "$PORT_FORWARDING" =~ ^[0-1]$ ]] || PORT_FORWARDING=0
[[ "$PORT_PERSIST" =~ ^[0-1]$ ]] || PORT_PERSIST=0
[[ "$PORT_FATAL" =~ ^[0-1]$ ]] || PORT_FATAL=0
# Should be a positive integer
[[ "$KEEPALIVE" =~ ^[0-9]+$ ]] || KEEPALIVE=0
[[ "$META_PORT" =~ ^[0-9]+$ ]] || export META_PORT=443
# Maybe also check the following. They are all blank by default.
# LOCAL_NETWORK=
# PIA_CN=
# PIA_IP=
# PIA_PORT=
# PORT_FILE=
# QDISC=
# VPNDNS=
# MTU=
configdir="/pia"
tokenfile="$configdir/.token"
pf_persistfile="$configdir/portsig.json"
# Run custom scripts at the appropriate time if present
# We also run custom commands specified by the PRE_UP, POST_UP, PRE_DOWN, and POST_DOWN env vars at the same time
custom_scriptdir="/pia/scripts"
pre_up_script="$custom_scriptdir/pre-up.sh"
post_up_script="$custom_scriptdir/post-up.sh"
pre_down_script="$custom_scriptdir/pre-down.sh"
post_down_script="$custom_scriptdir/post-down.sh"
sharedir="/pia-shared"
# Set env var PORT_FILE to override where the forwarded port number is dumped
# Might need to handle setting file ownership/permissions too
portfile="${PORT_FILE:-$sharedir/port.dat}"
pia_cacrt="/rsa_4096.crt"
wg_conf="/etc/wireguard/wg0.conf"
firewall_init () {
# Block everything by default
ip6tables -P OUTPUT DROP &> /dev/null
ip6tables -P INPUT DROP &> /dev/null
ip6tables -P FORWARD DROP &> /dev/null
iptables -P OUTPUT DROP &> /dev/null
iptables -P INPUT DROP &> /dev/null
iptables -P FORWARD DROP &> /dev/null
# Allow loopback traffic and input for established connections
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# We also need to temporarily allow the following:
# DNS queries
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
# HTTPS to download the server list and access API for generating auth token
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
# API access to register the public WireGuard key
iptables -A OUTPUT -p tcp --dport 1337 -j ACCEPT
# Non-default API port if set
[ "$META_PORT" -ne 443 ] && iptables -A OUTPUT -p tcp --dport "$META_PORT" -j ACCEPT
}
# Alpine 3.19 changed the default iptables backend to iptables-nft
# Check that the host supports this and revert to iptables-legacy if needed
nftables_setup () {
# Run an iptables command to see if things are working
iptables -L &> /dev/null && return
# If not, change to legacy
echo "$(date): Falling back to iptables-legacy"
ln -sf /sbin/xtables-legacy-multi /sbin/iptables
ln -sf /sbin/xtables-legacy-multi /sbin/iptables-save
ln -sf /sbin/xtables-legacy-multi /sbin/iptables-restore
ln -sf /sbin/xtables-legacy-multi /sbin/ip6tables
ln -sf /sbin/xtables-legacy-multi /sbin/ip6tables-save
ln -sf /sbin/xtables-legacy-multi /sbin/ip6tables-restore
}
# Handle shutdown behavior
finish () {
[ -x "$pre_down_script" ] && run_command "$pre_down_script"
[ -n "$PRE_DOWN" ] && run_command "$PRE_DOWN"
[ $PORT_FORWARDING -eq 1 ] && pkill -f 'pf.sh'
echo "$(date): Shutting down WireGuard"
# Remove forwarded port number dump file if requested
[ $PORT_FILE_CLEANUP -eq 1 ] && [ -w "$portfile" ] && rm "$portfile"
wg-quick down wg0
[ -x "$post_down_script" ] && run_command "$post_down_script"
[ -n "$POST_DOWN" ] && run_command "$POST_DOWN"
exit 0
}
trap finish SIGTERM SIGINT SIGQUIT
# All done. Sleep and wait for termination.
now_sleep () {
if [ $PORT_FORWARDING -eq 1 ] && [ $PORT_FATAL -eq 1 ]; then
wait $pf_pid
if [ $? -ne 0 ];then
echo "$(date): Port forwarding script failed"
fatal_error
fi
echo "$(date): Port forwarding script closed"
fi
sleep infinity &
wait $!
}
# An error with no recovery logic occured. Either go to sleep or exit.
fatal_error () {
echo "$(date): Fatal error"
[ -n "$FATAL_SCRIPT" ] && run_command "$FATAL_SCRIPT"
[ $EXIT_ON_FATAL -eq 1 ] && exit 1
sleep infinity &
wait $!
}
run_command () {
echo "$(date): Running: $1"
eval "$1"
}
gen_wgconf () {
/scripts/wg-gen.sh -l "$1" -t "$tokenfile" -o "$wg_conf" -k "/RegionsListPubKey.pem" -d "$VPNDNS" -m "$MTU" -c "$pia_cacrt"
return $?
}
# Get a new auth token
# Unsure how long an auth token will remain valid
get_auth_token () {
[ -r "$USER_FILE" ] && echo "$(date): Reading username from $USER_FILE" && USER=$(<"$USER_FILE")
[ -r "$PASS_FILE" ] && echo "$(date): Reading password from $PASS_FILE" && PASS=$(<"$PASS_FILE")
[ -z "$PASS" ] && echo "$(date): PIA password not set. Unable to retrieve new auth token." && fatal_error
[ -z "$USER" ] && echo "$(date): PIA username not set. Unable to retrieve new auth token." && fatal_error
echo "$(date): Generating auth token"
local token
if ! token=$(/scripts/pia-auth.sh -u "$USER" -p "$PASS" -n "$META_CN" -i "$META_IP" -o "$META_PORT" -c "$pia_cacrt"); then
echo "$(date): Failed to acquire new auth token" && fatal_error
fi
echo "$token" > "$tokenfile"
chmod 600 "$tokenfile"
}
nftables_setup
[ -x "$pre_up_script" ] && run_command "$pre_up_script"
[ -n "$PRE_UP" ] && run_command "$PRE_UP"
[ $FIREWALL -eq 1 ] && firewall_init
# Remove previous forwarded port number dump file if requested and present
[ $PORT_FILE_CLEANUP -eq 1 ] && [ -w "$portfile" ] && rm "$portfile"
# LOC is ignored and may be blank if ip/cn/port override vars or a dedicated ip are used
[ -n "$PIA_CN" ] && [ -n "$PIA_IP" ] && [ -n "$PIA_PORT" ] && LOC="manual"
[ -n "$PIA_DIP_TOKEN" ] && LOC="dip"
# No LOC or specific ip/port/cn supplied
[ -z "$LOC" ] && /scripts/wg-gen.sh -a && fatal_error
[ ! -r "$tokenfile" ] && get_auth_token
# Generate wg0.conf
# LOC can be a single location id, or a space or comma separated list
# Multiple location ids are used as fallback if the initial registration fails
gen_success=0
for location in ${LOC//,/ }; do
gen_wgconf "$location"
result=$?
if [ "$result" -eq 2 ]; then
# Reauth and retry if auth failed
# An auth error implies that the location id is valid and the endpoint responsive
rm "$tokenfile"
get_auth_token
gen_wgconf "$location" || fatal_error
elif [ "$result" -eq 3 ]; then
# Location not found
echo "$(date): Location $location not found"
continue
elif [ "$result" -eq 4 ]; then
# Registration failed
echo "$(date): Registration failed"
continue
elif [ "$result" -ne 0 ]; then
echo "$(date): Failed to generate WireGuard config"
fatal_error
fi
gen_success=1
break
done
if [ "$gen_success" -eq 0 ]; then
echo "$(date): Failed to generate WireGuard config for the selected location/s: $LOC"
fatal_error
fi
# Add PersistentKeepalive if KEEPALIVE is set
[ $KEEPALIVE -gt 0 ] && echo "PersistentKeepalive = $KEEPALIVE" >> "$wg_conf"
# Bring up Wireguard interface
echo "$(date): Bringing up WireGuard interface wg0"
wg-quick up wg0 || fatal_error
# Print out wg interface info
echo
wg
echo
echo "$(date): WireGuard successfully started"
# Show a warning if src_valid_mark=1 needs setting, otherwise incoming packets will be dropped
effective_rp_filter="$(sysctl -n net.ipv4.conf.all.rp_filter)"
[ "$(sysctl -n net.ipv4.conf.default.rp_filter)" -gt "$effective_rp_filter" ] && effective_rp_filter="$(sysctl -n net.ipv4.conf.default.rp_filter)"
[ "$effective_rp_filter" -eq 1 ] && [ "$(sysctl -n net.ipv4.conf.all.src_valid_mark)" -ne 1 ] && \
echo "$(date): Warning: Container requires net.ipv4.conf.all.src_valid_mark=1 sysctl to be set when rp_filter is set to strict. See the README for more info."
# Add qdisc to wg0 if requested
# eg: QDISC=cake bandwidth 20Mbit
[ -n "$QDISC" ] && echo "$(date): Adding qdisc to wg0: $QDISC" && tc qdisc add root dev wg0 $QDISC && tc -statistics qdisc show dev wg0
if [ $FIREWALL -eq 1 ]; then
# Remove temporary rules
iptables -D OUTPUT -p udp --dport 53 -j ACCEPT
iptables -D OUTPUT -p tcp --dport 443 -j ACCEPT
iptables -D OUTPUT -p tcp --dport 1337 -j ACCEPT
[ "$META_PORT" -ne 443 ] && iptables -D OUTPUT -p tcp --dport "$META_PORT" -j ACCEPT
# Allow docker network input/output
for iface in /sys/class/net/*; do
iface="${iface##*/}"
[[ "$iface" = @(lo|wg0) ]] && continue
docker_network="$(ip -o addr show dev "$iface"|
awk '$3 == "inet" {print $4}')"
[ -z "$docker_network" ] && continue
echo "$(date): Allowing network access to $docker_network on $iface"
iptables -A OUTPUT -o "$iface" --destination "$docker_network" -j ACCEPT
iptables -A INPUT -i "$iface" --source "$docker_network" -j ACCEPT
done
# Allow WG stuff
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -I OUTPUT -m mark --mark "$(wg show wg0 fwmark)" -j ACCEPT
echo "$(date): Firewall enabled: Blocking non-WireGuard traffic"
fi
# Set env var LOCAL_NETWORK=192.168.1.0/24 to allow LAN input/output
# Accept comma separated as well as space separated list
if [ -n "$LOCAL_NETWORK" ]; then
iface=$(ip route show default | awk '/default/ {print $5}')
gaddr=$(ip route show default | awk '/default/ {print $3}')
[ -z "$VPNDNS" ] && pia_dns=$(grep 'DNS = ' "$wg_conf" | sed 's/DNS = \(.*\)/\1/')
for range in ${LOCAL_NETWORK//,/ }; do
if [ -n "$pia_dns" ]; then
grepcidr "$range" <(echo "$pia_dns") >/dev/null && \
echo "$(date): Warning: LOCAL_NETWORK range $range overlaps with PIA's default dns servers ($pia_dns)" && \
echo "$(date): Consider setting custom dns servers using the VPNDNS env var if there are name resolution issues"
fi
if [ $FIREWALL -eq 1 ]; then
echo "$(date): Allowing network access to $range on $iface"
iptables -A OUTPUT -o "$iface" --destination "$range" -j ACCEPT
iptables -A INPUT -i "$iface" --source "$range" -j ACCEPT
fi
echo "$(date): Adding route to $range"
ip route add "$range" via "$gaddr"
done
fi
# Nat+forward traffic from a specific interface if requested
# eg. FWD_IFACE=eth1
if [ -n "$FWD_IFACE" ]; then
iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
iptables -A FORWARD -i wg0 -o "$FWD_IFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i "$FWD_IFACE" -o wg0 -j ACCEPT
echo "$(date): Forwarding traffic from $FWD_IFACE to VPN"
fi
# Setup port forwarding if requested and available
pf_api_ip=$(grep '#pf api' "$wg_conf"| sed 's/#pf api ip: \(.*\)/\1/')
pf_cn=$(grep '#cn: ' "$wg_conf"| sed 's/#cn: \(.*\)/\1/')
if [ $PORT_FORWARDING -eq 1 ] && [ -n "$pf_api_ip" ]; then
echo "$(date): Starting port forward script"
# Try to use a persistent port if requested
if [ $PORT_PERSIST -eq 1 ]; then
/scripts/pf.sh -t "$tokenfile" -i "$pf_api_ip" -n "$pf_cn" -p "$portfile" -c "$pia_cacrt" -s "/scripts/pf_success.sh" -r "$pf_persistfile" -f wg0 &
else
/scripts/pf.sh -t "$tokenfile" -i "$pf_api_ip" -n "$pf_cn" -p "$portfile" -c "$pia_cacrt" -s "/scripts/pf_success.sh" -f wg0 &
fi
pf_pid=$!
elif [ $PORT_FORWARDING -eq 1 ] && [ -z "$pf_api_ip" ]; then
echo "$(date): Warning: Port forwarding is unavailable on this server. Try a different location."
fi
[ -x "$post_up_script" ] && run_command "$post_up_script"
[ -n "$POST_UP" ] && run_command "$POST_UP"
# Workaround a NAT bug when using Wireguard behind a particular Asus router by regularly changing the local port
# Set env var CYCLE_PORTS to a space-separated list of ports to cycle through
# Eg: CYCLE_PORTS=50001 50002 50003
# Optionally set CYCLE_INTERVAL to number of seconds to use each port for. Defaults to 180 (3mins)
if [ -n "$CYCLE_PORTS" ]; then
echo "$(date): Changing Wireguard's local port every ${CYCLE_INTERVAL:-180}s"
while true; do
for port in $CYCLE_PORTS; do
wg set wg0 listen-port "$port"
sleep "${CYCLE_INTERVAL:-180}" & wait $!
done
done
fi
now_sleep