-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathvpn-firewall.sh
More file actions
302 lines (253 loc) · 11.1 KB
/
vpn-firewall.sh
File metadata and controls
302 lines (253 loc) · 11.1 KB
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
#!/bin/sh
# Made by Jack'lul <jacklul.github.io>
#
# Prevent other end of the VPN connection from accessing the router or LAN
#
#jas-update=vpn-firewall.sh
#shellcheck shell=ash
#shellcheck disable=SC2155
#shellcheck source=./common.sh
readonly common_script="$(dirname "$0")/common.sh"
if [ -f "$common_script" ]; then . "$common_script"; else { echo "$common_script not found" >&2; exit 1; } fi
VPN_INTERFACES="" # VPN interfaces to affect, separated by spaces, empty means auto detect, to find VPN interfaces run 'jas vpn-firewall identify'
ALLOW_PORTS_INPUT="" # allow connections on these ports in the INPUT chain, in format 'tcp=80 udp=5000-6000 1050' (not specifying protocol means tcp+udp), separated by spaces
ALLOW_PORTS_FORWARD="" # same as ALLOW_PORTS_INPUT but for FORWARD chain
EXECUTE_COMMAND="" # execute a command after firewall rules are applied or removed (receives arguments: $1 = action - add/remove)
RUN_EVERY_MINUTE= # verify that the rules are still set (true/false), empty means false when service-event script is available but otherwise true
RETRY_ON_ERROR=false # retry setting the rules on error (once per run)
load_script_config
readonly CHAIN_INPUT="jas-${script_name}-input"
readonly CHAIN_FORWARD="jas-${script_name}-forward"
iptables_chain() {
local _iptables="$1"
[ -z "$_iptables" ] && _iptables="iptables"
local _extra _has_error _chain _allow_ports _chain_type
case "$3" in
"INPUT")
_chain="$CHAIN_INPUT"
_allow_ports="$ALLOW_PORTS_INPUT"
[ -n "$3" ] && _extra="-d $3"
_chain_type="input"
;;
"FORWARD")
_chain="$CHAIN_FORWARD"
_allow_ports="$ALLOW_PORTS_FORWARD"
[ -n "$3" ] && _extra="! -d $3"
_chain_type="forward"
;;
esac
case "$2" in
"add")
$_iptables -N "$_chain"
local _port _proto
#shellcheck disable=SC2086
for _port in $_allow_ports; do
_proto=""
if echo "$_port" | grep -Fq "="; then
_proto="$(echo "$_port" | cut -d '=' -f 1 2> /dev/null)"
_port="$(echo "$_port" | cut -d '=' -f 2 2> /dev/null)"
fi
if echo "$_port" | grep -Fq ":"; then # Port range
if [ -z "$_proto" ]; then
$_iptables -A "$_chain" -p tcp --match multiport --dports "$_port" $_extra -j RETURN || _has_error=1
$_iptables -A "$_chain" -p udp --match multiport --dports "$_port" $_extra -j RETURN || _has_error=1
else
$_iptables -A "$_chain" -p "$_proto" --match multiport --dports "$_port" $_extra -j RETURN || _has_error=1
fi
else
if [ -z "$_proto" ]; then
$_iptables -A "$_chain" -p tcp --dport "$_port" $_extra -j RETURN || _has_error=1
$_iptables -A "$_chain" -p udp --dport "$_port" $_extra -j RETURN || _has_error=1
else
$_iptables -A "$_chain" -p "$_proto" --dport "$_port" $_extra -j RETURN || _has_error=1
fi
fi
done
[ -n "$_allow_ports" ] && logecho "Allowed ports in the $_chain_type chain: $(echo "$_allow_ports" | awk '{$1=$1};1')" alert
$_iptables -A "$_chain" -j DROP || _has_error=1
;;
"remove")
$_iptables -F "$_chain"
$_iptables -X "$_chain" || _has_error=1
;;
esac
[ -z "$_has_error" ] && return 0 || return 1
}
iptables_rule() {
local _iptables="$1"
[ -z "$_iptables" ] && _iptables="iptables"
local _interface="$4"
local _num="$5"
local _has_error _action
case "$2" in
"add")
_action="-I"
;;
"remove")
_action="-D"
;;
esac
local _chain _target_chain _chain_wgs _chain_ovpn
case "$3" in
"INPUT")
_chain="INPUT"
_target_chain="$CHAIN_INPUT"
_chain_wgs="WGCI"
_chain_ovpn="OVPNCI"
;;
"FORWARD")
_chain="FORWARD"
_target_chain="$CHAIN_FORWARD"
_chain_wgs="WGCF"
_chain_ovpn="OVPNCF"
;;
esac
if echo "$_interface" | grep -Fq 'wgs'; then
$_iptables "$_action" "$_chain_wgs" -i "$_interface" -j "$_target_chain" \
${iptables_comment:+-m comment --comment "$iptables_comment"} \
|| _has_error=1
elif echo "$_interface" | grep -q 'tun1\|tap1'; then
$_iptables "$_action" "$_chain_ovpn" -i "$_interface" -j "$_target_chain" \
${iptables_comment:+-m comment --comment "$iptables_comment"} \
|| _has_error=1
elif [ -n "$_num" ]; then
$_iptables "$_action" "$_chain" "$_num" -i "$_interface" -j "$_target_chain" \
${iptables_comment:+-m comment --comment "$iptables_comment"} \
|| _has_error=1
else
$_iptables "$_action" "$_chain" -i "$_interface" -j "$_target_chain" \
${iptables_comment:+-m comment --comment "$iptables_comment"} \
|| _has_error=1
fi
[ -z "$_has_error" ] && return 0 || return 1
}
firewall_rules() {
if [ -z "$VPN_INTERFACES" ]; then
local _vpnc_profiles="$(get_vpnc_clientlist | awk -F '>' '{print $6, $2}' | grep "^1")" # get only active ones
if echo "$_vpnc_profiles" | grep -Fq "WireGuard"; then
VPN_INTERFACES="$VPN_INTERFACES wgc+"
fi
if echo "$_vpnc_profiles" | grep -Fq "OpenVPN"; then
VPN_INTERFACES="$VPN_INTERFACES tun1+"
fi
[ -z "$VPN_INTERFACES" ] && return # silently exit
fi
# If xt_comment module is not available, disable comments to avoid errors and continue working without them
modprobe xt_comment 2> /dev/null && iptables_comment="jas-$script_name" || iptables_comment=""
lockfile lockwait
local _for_iptables="iptables"
[ "$(nvram get ipv6_service)" != "disabled" ] && _for_iptables="$_for_iptables ip6tables"
local _iptables _rules_action _rules_error _router_ip _input_start _forward_start _vpn_interface
for _iptables in $_for_iptables; do
if [ "$_iptables" = "ip6tables" ]; then
_router_ip="$(nvram get ipv6_rtr_addr)"
else
_router_ip="$(nvram get lan_ipaddr)"
fi
[ -z "$_router_ip" ] && continue
_rules_action=
_rules_error=
case "$1" in
"add")
if ! $_iptables -nL "$CHAIN_INPUT" > /dev/null 2>&1; then
if iptables_chain "$_iptables" add INPUT; then
_input_start="$($_iptables -nvL INPUT --line-numbers | grep -E "WGSI .* all" | tail -1 | awk '{print $1}')"
if [ -n "$_input_start" ]; then
for _vpn_interface in $VPN_INTERFACES; do
iptables_rule "$_iptables" add INPUT "$_vpn_interface" "$_input_start" \
&& _rules_action=1 || _rules_error=1
done
else
logecho "Unable to find the 'target WGSI' rule in the INPUT filter chain" error
_rules_error=1
fi
else
_rules_error=1
fi
fi
if ! $_iptables -nL "$CHAIN_FORWARD" > /dev/null 2>&1; then
if iptables_chain "$_iptables" add FORWARD; then
_forward_start="$($_iptables -nvL FORWARD --line-numbers | grep -E "WGSF .* all" | tail -1 | awk '{print $1}')"
if [ -n "$_forward_start" ]; then
for _vpn_interface in $VPN_INTERFACES; do
iptables_rule "$_iptables" add FORWARD "$_vpn_interface" "$_forward_start" \
&& _rules_action=1 || _rules_error=1
done
else
logecho "Unable to find the 'target WGSF' rule in the FORWARD filter chain" error
_rules_error=1
fi
else
_rules_error=1
fi
fi
;;
"remove")
remove_iptables_rules_by_comment "$_iptables" "filter" && _rules_action=-1
if $_iptables -nL "$CHAIN_INPUT" > /dev/null 2>&1; then
iptables_chain "$_iptables" remove INPUT || _rules_error=1
fi
if $_iptables -nL "$CHAIN_FORWARD" > /dev/null 2>&1; then
iptables_chain "$_iptables" remove FORWARD || _rules_error=1
fi
;;
esac
done
[ "$_rules_error" = 1 ] && logecho "Errors detected while modifying firewall rules ($1)" error
if [ -n "$_rules_action" ]; then
if [ "$_rules_action" = 1 ]; then
logecho "Blocking connections coming from VPN interfaces: $(echo "$VPN_INTERFACES" | awk '{$1=$1};1')" alert
else
logecho "Stopped blocking connections coming from VPN interfaces: $(echo "$VPN_INTERFACES" | awk '{$1=$1};1')" alert
fi
fi
[ -n "$EXECUTE_COMMAND" ] && [ -n "$_rules_action" ] && eval "$EXECUTE_COMMAND $1"
lockfile unlock
[ -z "$_rules_error" ] && return 0 || return 1
}
case "$1" in
"run")
firewall_rules add || { [ "$RETRY_ON_ERROR" = true ] && firewall_rules add; }
;;
"identify")
printf "%-10s %-7s %-20s\n" "Interface" "Active" "Description"
printf "%-10s %-7s %-20s\n" "---------" "------" "--------------------"
IFS="$(printf '\n\b')"
for entry in $(get_vpnc_clientlist); do
desc="$(echo "$entry" | awk -F '>' '{print $1}')"
type="$(echo "$entry" | awk -F '>' '{print $2}')"
id="$(echo "$entry" | awk -F '>' '{print $3}')"
active="$(echo "$entry" | awk -F '>' '{print $6}')"
[ "$active" = "1" ] && active=yes || active=no
if [ "$type" = "WireGuard" ]; then
interface="wgs${id}"
elif [ "$type" = "OpenVPN" ]; then
interface="$(nvram get "vpn_client${id}_if")1${id}"
else
continue
fi
printf "%-10s %-7s %-50s\n" "$interface" "$active" "$desc"
done
;;
"start")
firewall_rules add
# Set value of empty RUN_EVERY_MINUTE depending on situation
execute_script_basename "service-event.sh" check && service_event_active=true
[ -z "$RUN_EVERY_MINUTE" ] && [ -z "$service_event_active" ] && RUN_EVERY_MINUTE=true
if [ "$RUN_EVERY_MINUTE" = true ]; then
crontab_entry add "*/1 * * * * $script_path run"
fi
;;
"stop")
crontab_entry delete
firewall_rules remove
;;
"restart")
sh "$script_path" stop
sh "$script_path" start
;;
*)
echo "Usage: $0 run|start|stop|restart|identify"
exit 1
;;
esac