blob: 1962ed529eab7c99a9ba753be13356021a46ffa7 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001#!/bin/sh /etc/rc.common
2# Copyright 2017-2020 Stan Grishin (stangri@melmac.net)
3# shellcheck disable=SC2039,SC1091,SC2018,SC2019,SC3043,SC3057,SC3060
4PKG_VERSION='dev-test'
5
6# sysctl net.ipv4.conf.default.rp_filter=1
7# sysctl net.ipv4.conf.all.rp_filter=1
8
9# shellcheck disable=SC2034
10START=94
11# shellcheck disable=SC2034
12USE_PROCD=1
13
14if type extra_command 1>/dev/null 2>&1; then
15 extra_command 'support' "Generates output required to troubleshoot routing issues
16 Use '-d' option for more detailed output
17 Use '-p' option to automatically upload data under VPR paste.ee account
18 WARNING: while paste.ee uploads are unlisted, they are still publicly available
19 List domain names after options to include their lookup in report"
20 extra_command 'version' 'Show version information'
21 extra_command 'reload_interface' 'Reload specific interface only'
22else
23# shellcheck disable=SC2034
24 EXTRA_COMMANDS='support version'
25# shellcheck disable=SC2034
26 EXTRA_HELP=" support Generates output required to troubleshoot routing issues
27 Use '-d' option for more detailed output
28 Use '-p' option to automatically upload data under VPR paste.ee account
29 WARNING: while paste.ee uploads are unlisted, they are still publicly available
30 List domain names after options to include their lookup in report"
31fi
32
33readonly packageName='vpn-policy-routing'
34readonly serviceName="$packageName $PKG_VERSION"
35readonly PIDFile="/var/run/${packageName}.pid"
36readonly jsonFile="/var/run/${packageName}.json"
37readonly dnsmasqFile="/var/dnsmasq.d/${packageName}"
38readonly sharedMemoryOutput="/dev/shm/$packageName-output"
39readonly _OK_='\033[0;32m\xe2\x9c\x93\033[0m'
40readonly _FAIL_='\033[0;31m\xe2\x9c\x97\033[0m'
41readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m'
42readonly __FAIL__='\033[0;31m[\xe2\x9c\x97]\033[0m'
43readonly _ERROR_='\033[0;31mERROR\033[0m'
44readonly _WARNING_='\033[0;33mWARNING\033[0m'
45
46gatewaySummary=''; errorSummary=''; warningSummary='';
47serviceEnabled=''; verbosity=''; strictMode='';
48wanTableID=''; wanMark=''; fwMask='';
49ipv6Enabled=''; srcIpset=''; destIpset=''; resolverIpset='';
50wanIface4=''; wanIface6=''; ifaceMark=''; ifaceTableID='';
51ifAll=''; ifSupported=''; ignoredIfaces=''; supportedIfaces=''; icmpIface='';
52wanGW4=''; wanGW6=''; bootTimeout=''; insertOption='';
53webuiChainColumn=''; webuiShowIgnore=''; dnsmasqIpsetSupported='';
54procdReloadDelay='';
55usedChainsList='PREROUTING'
56ipsetSupported='true'
57configLoaded='false'
58
59version() { echo "$PKG_VERSION"; }
60output_ok() { output 1 "$_OK_"; output 2 "$__OK__\\n"; }
61output_okn() { output 1 "$_OK_\\n"; output 2 "$__OK__\\n"; }
62output_fail() { s=1; output 1 "$_FAIL_"; output 2 "$__FAIL__\\n"; }
63output_failn() { output 1 "$_FAIL_\\n"; output 2 "$__FAIL__\\n"; }
64str_replace() { printf "%b" "$1" | sed -e "s/$(printf "%b" "$2")/$(printf "%b" "$3")/g"; }
65str_replace() { echo "${1//$2/$3}"; }
66str_contains() { [ -n "$2" ] && [ "${1//$2}" != "$1" ]; }
67str_contains_word() { echo "$1" | grep -q -w "$2"; }
68str_to_lower() { echo "$1" | tr 'A-Z' 'a-z'; }
69str_extras_to_underscore() { echo "$1" | tr '[\. ~`!@#$%^&*()\+/,<>?//;:]' '_'; }
70str_extras_to_space() { echo "$1" | tr ';{}' ' '; }
71
72output() {
73# Can take a single parameter (text) to be output at any verbosity
74# Or target verbosity level and text to be output at specifc verbosity
75 local msg memmsg logmsg
76 if [ $# -ne 1 ]; then
77 if [ $((verbosity & $1)) -gt 0 ] || [ "$verbosity" = "$1" ]; then shift; else return 0; fi
78 fi
79 [ -t 1 ] && printf "%b" "$1"
80 msg="${1//$serviceName /service }";
81 if [ "$(printf "%b" "$msg" | wc -l)" -gt 0 ]; then
82 [ -s "$sharedMemoryOutput" ] && memmsg="$(cat "$sharedMemoryOutput")"
83 logmsg="$(printf "%b" "${memmsg}${msg}" | sed 's/\x1b\[[0-9;]*m//g')"
84 logger -t "${packageName:-service} [$$]" "$(printf "%b" "$logmsg")"
85 rm -f "$sharedMemoryOutput"
86 else
87 printf "%b" "$msg" >> "$sharedMemoryOutput"
88 fi
89}
90is_present() { command -v "$1" >/dev/null 2>&1; }
91is_installed() { [ -s "/usr/lib/opkg/info/${1}.control" ]; }
92is_variant_installed() { [ "$(echo /usr/lib/opkg/info/"${1}"*.control)" != "/usr/lib/opkg/info/${1}*.control" ]; }
93
94build_ifAll() { ifAll="${ifAll}${1} "; }
95build_ifSupported() { is_supported_interface "$1" && ifSupported="${ifSupported}${1} "; }
96vpr_find_iface() {
97 local iface i param="$2"
98 [ "$param" = 'wan6' ] || param='wan'
99 "network_find_${param}" iface
100 is_tunnel "$iface" && unset iface
101 if [ -z "$iface" ]; then
102 for i in $ifAll; do
103 if "is_${param}" "$i"; then break; else unset i; fi
104 done
105 fi
106 eval "$1"='${iface:-$i}'
107}
108vpr_get_gateway() {
109 local iface="$2" dev="$3" gw
110 network_get_gateway gw "$iface"
111 if [ -z "$gw" ] || [ "$gw" = '0.0.0.0' ]; then
112 gw="$(ip -4 a list dev "$dev" 2>/dev/null | grep inet | awk '{print $2}' | awk -F "/" '{print $1}')"
113 fi
114 eval "$1"='$gw'
115}
116vpr_get_gateway6() {
117 local iface="$2" dev="$3" gw
118 network_get_gateway6 gw "$iface"
119 if [ -z "$gw" ] || [ "$gw" = '::/0' ] || [ "$gw" = '::0/0' ] || [ "$gw" = '::' ]; then
120 gw="$(ip -6 a list dev "$dev" 2>/dev/null | grep inet6 | awk '{print $2}')"
121 fi
122 eval "$1"='$gw'
123}
124is_l2tp() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:4}" = "l2tp" ]; }
125is_oc() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:11}" = "openconnect" ]; }
126is_ovpn() { local dev i; for i in ifname device; do [ -z "$dev" ] && dev="$(uci -q get "network.${1}.${i}")"; done; [ "${dev:0:3}" = "tun" ] || [ "${dev:0:3}" = "tap" ] || [ -f "/sys/devices/virtual/net/${dev}/tun_flags" ]; }
127is_pptp() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:4}" = "pptp" ]; }
128is_tor() { [ "$(str_to_lower "$1")" = "tor" ]; }
129is_tor_running() {
130 local ret=0
131 if [ -s "/etc/tor/torrc" ]; then
132 json_load "$(ubus call service list "{ 'name': 'tor' }")"
133 json_select 'tor'; json_select 'instances'; json_select 'instance1';
134 json_get_var ret 'running'; json_cleanup
135 fi
136 if [ "$ret" = "0" ]; then return 1; else return 0; fi
137}
138is_wg() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:9}" = "wireguard" ]; }
139is_tunnel() { is_l2tp "$1" || is_oc "$1" || is_ovpn "$1" || is_pptp "$1" || is_tor "$1" || is_wg "$1"; }
140is_wan() { [ "$1" = "$wanIface4" ] || { [ "${1##wan}" != "$1" ] && [ "${1##wan6}" = "$1" ]; } || [ "${1%%wan}" != "$1" ]; }
141is_wan6() { [ -n "$wanIface6" ] && [ "$1" = "$wanIface6" ] || [ "${1/#wan6}" != "$1" ] || [ "${1/%wan6}" != "$1" ]; }
142is_ignored_interface() { str_contains_word "$ignoredIfaces" "$1"; }
143is_supported_interface() { str_contains_word "$supportedIfaces" "$1" || { ! is_ignored_interface "$1" && { is_wan "$1" || is_wan6 "$1" || is_tunnel "$1"; }; }; }
144is_mac_address() { expr "$1" : '[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]$' >/dev/null; }
145is_ipv4() { expr "$1" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; }
146is_ipv6() { ! is_mac_address "$1" && str_contains "$1" ":"; }
147is_family_mismatch() { ( is_netmask "${1//!}" && is_ipv6 "${2//!}" ) || ( is_ipv6 "${1//!}" && is_netmask "${2//!}" ); }
148is_ipv6_link_local() { [ "${1:0:4}" = "fe80" ]; }
149is_ipv6_unique_local() { [ "${1:0:2}" = "fc" ] || [ "${1:0:2}" = "fd" ]; }
150is_ipv6_global() { [ "${1:0:4}" = "2001" ]; }
151# is_ipv6_global() { is_ipv6 "$1" && ! is_ipv6_link_local "$1" && ! is_ipv6_link_local "$1"; }
152is_netmask() { local ip="${1%/*}"; [ "$ip" != "$1" ] && is_ipv4 "$ip"; }
153is_domain() { str_contains "$1" '[a-zA-Z]'; }
154is_phys_dev() { [ "${1:0:1}" = "@" ] && ip l show | grep -E -q "^\\d+\\W+${1:1}"; }
155is_turris() { /bin/ubus -S call system board | /bin/grep 'Turris' | /bin/grep -q '15.05'; }
156is_chaos_calmer() { ubus -S call system board | grep -q 'Chaos Calmer'; }
157dnsmasq_kill() { killall -q -s HUP dnsmasq; }
158dnsmasq_restart() { output 3 'Restarting DNSMASQ '; if /etc/init.d/dnsmasq restart >/dev/null 2>&1; then output_okn; else output_failn; fi; }
159is_default_dev() { [ "$1" = "$(ip -4 r | grep -m1 'dev' | grep -Eso 'dev [^ ]*' | awk '{print $2}')" ]; }
160is_supported_iface_dev() {
161 for n in $ifSupported; do
162 if [ "$1" = "$(uci -q get "network.${n}.ifname" || echo "$n")" ] || \
163 [ "$1" = "$(uci -q get "network.${n}.device" || echo "$n")" ] || \
164 [ "$1" = "$(uci -q get "network.${n}.proto")-${n}" ] ; then return 0; fi
165 done
166 return 1
167}
168is_supported_protocol () { grep -o '^[^#]*' /etc/protocols | grep -w -v '0' | grep . | awk '{print $1}' | grep -q "$1"; }
169append_chains_targets() {
170 local chain iface name
171 config_get name "$1" 'name' 'blank'
172 config_get chain "$1" 'chain' 'PREROUTING'
173 config_get iface "$1" 'interface'
174 if ! str_contains_word "$usedChainsList" "$chain"; then
175 usedChainsList="$usedChainsList $chain"
176 if [ "$chain" != 'PREROUTING' ] && [ "$webuiChainColumn" != '1' ]; then
177 warningSummary="${warningSummary}$_WARNING_: Chain '$chain' is used by a policy '$name', but a WebUI setting to show chains column (webui_chain_column) is disabled!\\n"
178 fi
179 fi
180 if [ "$iface" = 'ignore' ] && ! str_contains_word "$supportedIfaces" 'ignore'; then
181 supportedIfaces="$supportedIfaces ignore"
182 if [ "$webuiShowIgnore" != '1' ]; then
183 warningSummary="${warningSummary}$_WARNING_: The 'ignore' target is used by a policy '$name', but a WebUI setting to show 'ignore' target (webui_show_ignore_target) is disabled!\\n"
184 fi
185 fi
186}
187
188load_package_config() {
189 [ "$configLoaded" = 'false' ] || return 0
190
191 config_load "$packageName"
192 config_get_bool serviceEnabled 'config' 'enabled' 0
193 config_get_bool strictMode 'config' 'strict_enforcement' 1
194 config_get_bool ipv6Enabled 'config' 'ipv6_enabled' 0
195 config_get_bool srcIpset 'config' 'src_ipset' 0
196 config_get_bool destIpset 'config' 'dest_ipset' 0
197 config_get resolverIpset 'config' 'resolver_ipset' 'dnsmasq.ipset'
198 config_get verbosity 'config' 'verbosity' '2'
199 config_get wanTableID 'config' 'wan_tid' '201'
200 config_get wanMark 'config' 'wan_mark' '0x010000'
201 config_get fwMask 'config' 'fw_mask' '0xff0000'
202 config_get icmpIface 'config' 'icmp_interface'
203 config_get ignoredIfaces 'config' 'ignored_interface'
204 config_get supportedIfaces 'config' 'supported_interface'
205 config_get bootTimeout 'config' 'boot_timeout' '30'
206 config_get insertOption 'config' 'iptables_rule_option' 'append'
207 config_get procdReloadDelay 'config' 'procd_reload_delay' '0'
208 config_get_bool webuiChainColumn 'config' 'webui_chain_column' '0'
209 config_get_bool webuiShowIgnore 'config' 'webui_show_ignore_target' '0'
210 config_foreach append_chains_targets 'policy'
211
212 if [ -z "${verbosity##*[!0-9]*}" ] || [ "$verbosity" -lt 0 ] || [ "$verbosity" -gt 2 ]; then
213 verbosity=2
214 fi
215
216 . /lib/functions/network.sh
217 . /usr/share/libubox/jshn.sh
218 mkdir -p "${PIDFile%/*}"
219 mkdir -p "${jsonFile%/*}"
220 mkdir -p "${dnsmasqFile%/*}"
221
222 if [ -n "$icmpIface" ] && ! str_contains_word "$usedChainsList" 'OUTPUT'; then
223 usedChainsList="$usedChainsList OUTPUT"
224 fi
225
226 case $insertOption in
227 insert|-i|-I) insertOption='-I';;
228 append|-a|-A|*) insertOption='-A';;
229 esac
230
231 [ "$resolverIpset" = 'dnsmasq.ipset' ] && dnsmasqIpsetSupported='true'
232 if dnsmasq -v 2>/dev/null | grep -q 'no-ipset' || ! dnsmasq -v 2>/dev/null | grep -q -w 'ipset'; then
233 unset dnsmasqIpsetSupported
234 if [ -n "$dnsmasqIpsetSupported" ]; then
235 errorSummary="${errorSummary}${_ERROR_}: Resolver ipset support (dnsmasq.ipset) is enabled in $packageName, but DNSMASQ ipsets are not supported on this system!\\n"
236 fi
237 fi
238 if ! ipset help hash:net >/dev/null 2>&1; then
239 unset ipsetSupported
240 if [ -n "$dnsmasqIpsetSupported" ]; then
241 errorSummary="${errorSummary}${_ERROR_}: DNSMASQ ipsets are supported, but ipset is either not installed or installed ipset does not support 'hash:net' type!\\n"
242 unset dnsmasqIpsetSupported
243 fi
244 if [ "$destIpset" -ne 0 ]; then
245 errorSummary="${errorSummary}${_ERROR_}: Destination ipset support is enabled in $packageName, but ipset is either not installed or installed ipset does not support 'hash:net' type!\\n"
246 destIpset=0
247 fi
248 if [ "$srcIpset" -ne 0 ]; then
249 errorSummary="${errorSummary}${_ERROR_}: Source ipset support is enabled in $packageName, but ipset is either not installed or installed ipset does not support 'hash:net' type!\\n"
250 srcIpset=0
251 fi
252 fi
253 if ! ipset help hash:mac >/dev/null 2>&1; then
254 if [ "$srcIpset" -ne 0 ]; then
255 errorSummary="${errorSummary}${_ERROR_}: Source ipset support is enabled in $packageName, but ipset is either not installed or installed ipset does not support 'hash:mac' type!\\n"
256 srcIpset=0
257 fi
258 fi
259
260 configLoaded='true'
261}
262
263is_enabled() {
264 load_package_config
265 if [ "$serviceEnabled" -eq 0 ]; then
266 if [ "$1" = 'on_start' ]; then
267 errorSummary="${errorSummary}${_ERROR_}: ${packageName} is currently disabled.\\n"
268 errorSummary="${errorSummary}Enable ${packageName} from WebUI or run the following commands:\\n"
269 errorSummary="${errorSummary}uci set $packageName.config.enabled='1'; uci commit $packageName;\\n"
270 fi
271 return 1
272 fi
273}
274
275load_network() {
276 if [ -z "$ifAll" ]; then
277 config_load 'network'
278 config_foreach build_ifAll 'interface'
279 fi
280 vpr_find_iface wanIface4 'wan'
281 [ "$ipv6Enabled" -ne 0 ] && vpr_find_iface wanIface6 'wan6'
282 [ -n "$wanIface4" ] && network_get_gateway wanGW4 "$wanIface4"
283 [ -n "$wanIface6" ] && network_get_gateway6 wanGW6 "$wanIface6"
284 wanGW="${wanGW4:-$wanGW6}"
285 unset ifSupported
286 config_load 'network'
287 config_foreach build_ifSupported 'interface'
288}
289
290is_wan_up() {
291 local sleepCount=1
292 load_network
293 while [ -z "$wanGW" ] ; do
294 load_network
295 if [ $((sleepCount)) -gt $((bootTimeout)) ] || [ -n "$wanGW" ]; then break; fi
296 output "$serviceName waiting for wan gateway...\\n"
297 sleep 1
298 network_flush_cache
299 sleepCount=$((sleepCount+1))
300 done
301 if [ -n "$wanGW" ]; then
302 return 0
303 else
304 errorSummary="${errorSummary}${_ERROR_}: ${serviceName} failed to discover WAN gateway!\\n"
305 return 1
306 fi
307}
308
309ipt_cleanup() {
310 local i
311 for i in PREROUTING FORWARD INPUT OUTPUT; do
312 while iptables -t mangle -D $i -m mark --mark 0x0/0xff0000 -j VPR_${i} >/dev/null 2>&1; do : ; done
313 done
314 for i in PREROUTING FORWARD INPUT OUTPUT; do
315 while iptables -t mangle -D $i -j VPR_${i} >/dev/null 2>&1; do : ; done
316 done
317}
318
319# shellcheck disable=SC2086
320ipt() {
321 local d failFlagIpv4=1 failFlagIpv6=1
322 for d in "${*//-A/-D}" "${*//-I/-D}" "${*//-N/-F}" "${*//-N/-X}"; do
323 [ "$d" != "$*" ] && { iptables $d >/dev/null 2>&1; ip6tables $d >/dev/null 2>&1; }
324 done
325
326 d="$*"; iptables $d >/dev/null 2>&1 && failFlagIpv4=0;
327 if [ "$ipv6Enabled" -gt 0 ]; then ip6tables $d >/dev/null 2>&1 && failFlagIpv6=0; fi
328
329 [ "$failFlagIpv4" -eq 0 ] || [ "$failFlagIpv6" -eq 0 ]
330}
331
332# shellcheck disable=SC2086
333ips() {
334 local command="$1" ipset="${2//-/_}" param="$3" comment="$4" appendix failFlag=0
335 if str_contains "$ipset" '_ip'; then
336 ipset="${ipset//_ip}"; appendix='_ip';
337 elif str_contains "$ipset" '_mac'; then
338 ipset="${ipset//_mac}"; appendix='_mac';
339 fi
340
341 case "$command" in
342 add_dnsmasq)
343 [ "$resolverIpset" = "dnsmasq.ipset" ] || return 1
344 if [ -z "$dnsmasqIpsetSupported" ]; then
345 warningSummary="${warningSummary}${_WARNING_}: The 'resolver_ipset' is set to 'dnsmasq.ipset', but DNSMASQ ipsets are not supported on this system!\\n"
346 failFlag=1
347 elif [ "$ipv6Enabled" -ne 0 ]; then
348 echo "ipset=/${param}/${ipset},${ipset}6 # $comment" >> "$dnsmasqFile" || failFlag=1
349 else
350 echo "ipset=/${param}/${ipset} # $comment" >> "$dnsmasqFile" || failFlag=1
351 fi
352 ;;
353 add)
354 if [ -z "$appendix" ] && [ "$destIpset" -eq 0 ]; then return 1; fi
355 if [ -n "$appendix" ] && [ "$srcIpset" -eq 0 ]; then return 1; fi
356 if [ "$ipv6Enabled" -ne 0 ] && [ "$appendix" != "_mac" ]; then
357 ipset -q -! $command "${ipset}6${appendix}" $param comment "$comment" || failFlag=1
358 fi
359 ipset -q -! $command "${ipset}${appendix}" $param comment "$comment" || failFlag=1
360 ;;
361 create)
362 if [ "$ipv6Enabled" -ne 0 ] && [ "$appendix" != "_mac" ]; then
363 ipset -q -! "$command" "${ipset}6${appendix}" $param family inet6 || failFlag=1
364 fi
365 ipset -q -! "$command" "${ipset}${appendix}" $param || failFlag=1
366 ;;
367 destroy|flush)
368 ipset -q -! "$command" "${ipset}6${appendix}" 2>/dev/null || failFlag=1
369 ipset -q -! "$command" "${ipset}${appendix}" 2>/dev/null || failFlag=1
370 return 0
371 ;;
372 esac
373 return $failFlag
374}
375
376insert_tor_policy() {
377 local comment="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" proto chain
378 proto="$(str_to_lower "$7")"
379 chain="${8:-PREROUTING}"
380 if [ -n "${laddr}${lport}${rport}" ]; then
381 processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'src_addr', 'src_port' and 'dest_port' for policy '$comment'\\n"
382 fi
383 if [ -n "$proto" ] && [ "$proto" != "all" ]; then
384 processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'proto' or set 'proto' to 'all' for policy '$comment'\\n"
385 fi
386 if [ "$chain" != "PREROUTING" ]; then
387 processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '$comment'\\n"
388 fi
389 ips 'add' "${iface}" "$raddr" "${comment}: $raddr" || processPolicyError="${processPolicyError}${_ERROR_}: ipset 'add' $iface $raddr\\n"
390 return 0
391}
392
393insert_policy() {
394 local comment="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" proto chain
395 local mark param i valueNeg value dest ipInsertOption="-A"
396 proto="$(str_to_lower "$7")"
397 chain="${8:-PREROUTING}"
398 mark=$(eval echo "\$mark_${iface//-/_}")
399 if [ "$ipv6Enabled" -eq 0 ] && ( is_ipv6 "$laddr" || is_ipv6 "$raddr" ); then
400 processPolicyError="${processPolicyError}${_ERROR_}: Skipping IPv6 policy '$comment' as IPv6 support is disabled\\n"
401 return 1
402 fi
403
404 if [ -n "$mark" ]; then
405 dest="-g VPR_MARK${mark}"
406 elif [ "$iface" = "ignore" ]; then
407 dest="-j RETURN"
408 else
409 processPolicyError="${processPolicyError}${_ERROR_}: Unknown fw_mark for ${iface}\\n"
410 return 0
411 fi
412
413 if [ -z "$proto" ]; then
414 if [ -n "$lport" ] || [ -n "$rport" ]; then
415 proto='tcp udp'
416 else
417 proto='all'
418 fi
419 fi
420
421 if is_family_mismatch "$laddr" "$raddr"; then
422 processPolicyError="${processPolicyError}${_ERROR_}: Mismatched IP family between '$laddr' and '$raddr' in policy '$comment'\\n"
423 return 0
424 fi
425
426 for i in $proto; do
427 if [ "$i" = 'all' ]; then
428 param="-t mangle ${ipInsertOption} VPR_${chain} $dest"
429 elif ! is_supported_protocol "$i"; then
430 processPolicyError="${processPolicyError}${_ERROR_}: Unknown protocol '$i' in policy '$comment'\\n"
431 return 0
432 else
433 param="-t mangle ${ipInsertOption} VPR_${chain} $dest -p $i"
434 fi
435
436 if [ -n "$laddr" ]; then
437 if [ "${laddr:0:1}" = "!" ]; then
438 valueNeg='!'; value="${laddr:1}"
439 else
440 unset valueNeg; value="$laddr";
441 fi
442 if is_phys_dev "$value"; then
443 param="$param $valueNeg -m physdev --physdev-in ${value:1}"
444 elif is_mac_address "$value"; then
445 param="$param -m mac $valueNeg --mac-source $value"
446 else
447 param="$param $valueNeg -s $value"
448 fi
449 fi
450
451 if [ -n "$lport" ]; then
452 if [ "${lport:0:1}" = "!" ]; then
453 valueNeg='!'; value="${lport:1}"
454 else
455 unset valueNeg; value="$lport";
456 fi
457 param="$param -m multiport $valueNeg --sport ${value//-/:}"
458 fi
459
460 if [ -n "$raddr" ]; then
461 if [ "${raddr:0:1}" = "!" ]; then
462 valueNeg='!'; value="${raddr:1}"
463 else
464 unset valueNeg; value="$raddr";
465 fi
466 param="$param $valueNeg -d $value"
467 fi
468
469 if [ -n "$rport" ]; then
470 if [ "${rport:0:1}" = "!" ]; then
471 valueNeg='!'; value="${rport:1}"
472 else
473 unset valueNeg; value="$rport";
474 fi
475 param="$param -m multiport $valueNeg --dport ${value//-/:}"
476 fi
477
478 [ -n "$comment" ] && param="$param -m comment --comment $(str_extras_to_underscore "$comment")"
479 ipt "$param" || processPolicyError="${processPolicyError}${_ERROR_}: iptables $param\\n"
480 done
481 return 0
482}
483
484r_process_policy(){
485 local comment="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" proto="$7" chain="$8" resolved_laddr resolved_raddr i ipsFailFlag
486 if str_contains "$laddr" '[ ;\{\}]'; then
487 for i in $(str_extras_to_space "$laddr"); do [ -n "$i" ] && r_process_policy "$comment" "$iface" "$i" "$lport" "$raddr" "$rport" "$proto" "$chain"; done
488 return 0
489 elif str_contains "$lport" '[ ;\{\}]'; then
490 for i in $(str_extras_to_space "$lport"); do [ -n "$i" ] && r_process_policy "$comment" "$iface" "$laddr" "$i" "$raddr" "$rport" "$proto" "$chain"; done
491 return 0
492 elif str_contains "$raddr" '[ ;\{\}]'; then
493 for i in $(str_extras_to_space "$raddr"); do [ -n "$i" ] && r_process_policy "$comment" "$iface" "$laddr" "$lport" "$i" "$rport" "$proto" "$chain"; done
494 return 0
495 elif str_contains "$rport" '[ ;\{\}]'; then
496 for i in $(str_extras_to_space "$rport"); do [ -n "$i" ] && r_process_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$i" "$proto" "$chain"; done
497 return 0
498 fi
499
500 # start non-recursive processing
501 # process TOR, netmask, physical device and mac-address separately, so we don't send them to resolveip
502 if is_tor "$iface"; then
503 insert_tor_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport" "$proto" "$chain"
504 elif is_phys_dev "$laddr"; then
505 insert_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport" "$proto" "$chain"
506 elif [ -n "$laddr" ] && [ -z "${lport}${raddr}${rport}" ] && [ "$chain" = 'PREROUTING' ]; then
507 if is_mac_address "$laddr"; then
508 if [ -n "$proto" ] && [ "$proto" != 'all' ] && [ "$srcIpset" -ne 0 ]; then
509 processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'proto' or set 'proto' to 'all' for policy: '$comment', mac-address: '$laddr'\\n"
510 fi
511 ips 'add' "${iface}_mac" "$laddr" "${comment}: $laddr" || ipsFailFlag=1
512 else
513 if [ -n "$proto" ] && [ "$proto" != "all" ] && [ "$srcIpset" -ne 0 ]; then
514 processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'proto' or set 'proto' to 'all' for policy: '$comment', source: '$laddr'\\n"
515 fi
516 ips 'add' "${iface}_ip" "$laddr" "${comment}: $laddr" || ipsFailFlag=1
517 fi
518 elif [ -n "$raddr" ] && [ -z "${laddr}${lport}${rport}" ] && [ "$chain" = 'PREROUTING' ]; then
519 if [ -n "$proto" ] && [ "$proto" != 'all' ]; then
520 processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'proto' or set 'proto' to 'all' for policy: '$comment', destination: '$raddr'\\n"
521 fi
522 if is_domain "$raddr"; then
523 ips 'add_dnsmasq' "${iface}" "$raddr" "${comment}" || ipsFailFlag=1
524 else
525 ips 'add' "${iface}" "$raddr" "${comment}: $raddr" || ipsFailFlag=1
526 fi
527 else
528 ipsFailFlag=1
529 fi
530 [ -n "$ipsFailFlag" ] || return 0;
531 if is_mac_address "$laddr"; then
532 insert_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport" "$proto" "$chain"
533 elif is_netmask "$laddr" || is_netmask "$raddr"; then
534 insert_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport" "$proto" "$chain"
535 else
536 [ -n "$laddr" ] && resolved_laddr="$(resolveip "$laddr")"
537 [ -n "$raddr" ] && resolved_raddr="$(resolveip "$raddr")"
538 if [ -n "$resolved_laddr" ] && [ "$resolved_laddr" != "$laddr" ]; then
539 for i in $resolved_laddr; do [ -n "$i" ] && r_process_policy "$comment $laddr" "$iface" "$i" "$lport" "$raddr" "$rport" "$proto" "$chain"; done
540 elif [ -n "$resolved_raddr" ] && [ "$resolved_raddr" != "$raddr" ]; then
541 for i in $resolved_raddr; do [ -n "$i" ] && r_process_policy "$comment $raddr" "$iface" "$laddr" "$lport" "$i" "$rport" "$proto" "$chain"; done
542 else
543 insert_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport" "$proto" "$chain"
544 fi
545 fi
546}
547
548process_policy(){
549 local name comment iface laddr lport raddr rport param mark processPolicyError processPolicyWarning proto chain enabled
550 config_get comment "$1" 'comment'
551 config_get name "$1" 'name' 'blank'
552 config_get iface "$1" 'interface'
553 config_get laddr "$1" 'src_addr'
554 config_get lport "$1" 'src_port'
555 config_get raddr "$1" 'dest_addr'
556 config_get rport "$1" 'dest_port'
557 config_get proto "$1" 'proto'
558 config_get chain "$1" 'chain' 'PREROUTING'
559 config_get_bool enabled "$1" 'enabled' 1
560
561 [ "$enabled" -gt 0 ] || return 0
562 proto="$(str_to_lower "$proto")"
563 [ "$proto" = 'auto' ] && unset proto
564
565 comment="${comment:-$name}"
566 output 2 "Routing '$comment' via $iface "
567
568 if [ -z "$comment" ]; then
569 errorSummary="${errorSummary}${_ERROR_}: Policy name is empty\\n"
570 output_fail; return 1;
571 fi
572 if [ -z "${laddr}${lport}${raddr}${rport}" ]; then
573 errorSummary="${errorSummary}${_ERROR_}: Policy '$comment' missing all IPs/ports\\n"
574 output_fail; return 1;
575 fi
576 if [ -z "$iface" ]; then
577 errorSummary="${errorSummary}${_ERROR_}: Policy '$comment' has no assigned interface\\n"
578 output_fail; return 1;
579 fi
580 if ! is_supported_interface "$iface"; then
581 errorSummary="${errorSummary}${_ERROR_}: Policy '$comment' has unknown interface: '${iface}'\\n"
582 output_fail; return 1;
583 fi
584
585 lport="${lport// / }"; lport="${lport// /,}"; lport="${lport//,\!/ !}";
586 rport="${rport// / }"; rport="${rport// /,}"; rport="${rport//,\!/ !}";
587 r_process_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport" "$proto" "$chain"
588 if [ -n "$processPolicyWarning" ]; then
589 warningSummary="${warningSummary}${processPolicyWarning}\\n"
590 fi
591 if [ -n "$processPolicyError" ]; then
592 output_fail
593 errorSummary="${errorSummary}${processPolicyError}\\n"
594 else
595 output_ok
596 fi
597}
598
599table_destroy(){
600 local tid="$1" iface="$2" mark="$3"
601 if [ -n "$tid" ] && [ -n "$iface" ] && [ -n "$mark" ]; then
602 ipt -t mangle -F "VPR_MARK${mark}"
603 ipt -t mangle -X "VPR_MARK${mark}"
604 ip -4 rule del fwmark "$mark" table "$tid" >/dev/null 2>&1
605 ip -6 rule del fwmark "$mark" table "$tid" >/dev/null 2>&1
606 ip -4 rule del table "$tid" >/dev/null 2>&1
607 ip -6 rule del table "$tid" >/dev/null 2>&1
608 ip -4 route flush table "$tid" >/dev/null 2>&1
609 ip -6 route flush table "$tid" >/dev/null 2>&1
610 ips 'flush' "${iface}"; ips 'destroy' "${iface}";
611 ips 'flush' "${iface}_ip"; ips 'destroy' "${iface}_ip";
612 ips 'flush' "${iface}_mac"; ips 'destroy' "${iface}_mac";
613 ip -4 route flush cache
614 ip -6 route flush cache
615 sed -i "/$iface/d" /etc/iproute2/rt_tables
616 return 0
617 else
618 return 1
619 fi
620}
621
622# shellcheck disable=SC2086
623table_create(){
624 local tid="$1" mark="$2" iface="$3" gw4="$4" dev="$5" gw6="$6" dev6="$7" match="$8" dscp s=0 i ipv4_error=0 ipv6_error=1
625
626 if [ -z "$tid" ] || [ -z "$mark" ] || [ -z "$iface" ]; then
627 return 1
628 fi
629
630 table_destroy "$tid" "$iface" "$mark"
631
632 if [ -n "$gw4" ] || [ "$strictMode" -ne 0 ]; then
633 echo "$tid" "$iface" >> /etc/iproute2/rt_tables
634 if [ -z "$gw4" ]; then
635 ip -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1
636 else
637 ip -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1
638 fi
639# ip -4 route list table main | grep -v 'br-lan' | while read -r i; do
640 ip -4 route list table main | while read -r i; do
641 idev="$(echo "$i" | grep -Eso 'dev [^ ]*' | awk '{print $2}')"
642 if ! is_supported_iface_dev "$idev"; then
643 ip -4 route add $i table "$tid" >/dev/null 2>&1 || ipv4_error=1
644 fi
645 done
646 ip -4 route flush cache || ipv4_error=1
647 ip -4 rule add fwmark "${mark}/${fwMask}" table "$tid" || ipv4_error=1
648 ipt -t mangle -N "VPR_MARK${mark}" || ipv4_error=1
649 ipt -t mangle -A "VPR_MARK${mark}" -j MARK --set-xmark "${mark}/${fwMask}" || ipv4_error=1
650 ipt -t mangle -A "VPR_MARK${mark}" -j RETURN || ipv4_error=1
651 fi
652
653 if [ "$ipv6Enabled" -ne 0 ]; then
654 ipv6_error=0
655 if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strictMode" -ne 0 ]; then
656 if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then
657 ip -6 route add unreachable default table "$tid" || ipv6_error=1
658 else
659 ip -6 route list table main | grep " dev $dev6 " | while read -r i; do
660 ip -6 route add $i table "$tid" >/dev/null 2>&1 || ipv6_error=1
661 done
662 fi
663 ip -6 route flush cache || ipv6_error=1
664 ip -6 rule add fwmark "${mark}/${fwMask}" table "$tid" || ipv6_error=1
665 fi
666 fi
667
668 if [ $ipv4_error -eq 0 ] || [ $ipv6_error -eq 0 ]; then
669 dscp="$(uci -q get "${packageName}".config."${iface}"_dscp)"
670 if [ "${dscp:-0}" -ge 1 ] && [ "${dscp:-0}" -le 63 ]; then
671 ipt -t mangle -I VPR_PREROUTING -m dscp --dscp "${dscp}" -g "VPR_MARK${mark}" || s=1
672 fi
673 if [ -n "$ipsetSupported" ] && { [ -n "$dnsmasqIpsetSupported" ] || [ "$destIpset" -ne 0 ]; }; then
674 if ips 'create' "${iface}" 'hash:net comment' && ips 'flush' "${iface}"; then
675 for i in $usedChainsList; do
676 ipt -t mangle -I VPR_${i} -m set --match-set "${iface}" dst -g "VPR_MARK${mark}" || s=1
677 if [ "$ipv6Enabled" -ne 0 ]; then ipt -t mangle -I VPR_${i} -m set --match-set "${iface}6" dst -g "VPR_MARK${mark}" || s=1; fi
678 done
679 else
680 s=1
681 fi
682 fi
683 if [ -n "$ipsetSupported" ] && [ "$srcIpset" -ne 0 ]; then
684 if ips 'create' "${iface}_ip" 'hash:net comment' && ips 'flush' "${iface}_ip"; then
685 ipt -t mangle -I VPR_PREROUTING -m set --match-set "${iface}_ip" src -g "VPR_MARK${mark}" || s=1
686 if [ "$ipv6Enabled" -ne 0 ]; then ipt -t mangle -I VPR_PREROUTING -m set --match-set "${iface}6_ip" src -g "VPR_MARK${mark}" || s=1; fi
687 else
688 s=1
689 fi
690 if ips 'create' "${iface}_mac" 'hash:mac comment' && ips 'flush' "${iface}_mac"; then
691 ipt -t mangle -I VPR_PREROUTING -m set --match-set "${iface}_mac" src -g "VPR_MARK${mark}" || s=1
692 else
693 s=1
694 fi
695 fi
696 if [ "$iface" = "$icmpIface" ]; then
697 ipt -t mangle -I VPR_OUTPUT -p icmp -g "VPR_MARK${mark}" || s=1
698 fi
699 else
700 s=1
701 fi
702
703 return $s
704}
705
706table_reload() {
707 local tid="$1" mark="$2" iface="$3" gw4="$4" dev="$5" gw6="$6" dev6="$7" match="$8" dscp s=0 i ipv4_error=0 ipv6_error=1
708
709 if [ -z "$tid" ] || [ -z "$mark" ] || [ -z "$iface" ]; then
710 return 1
711 fi
712
713 ip -4 route del default table "$tid" >/dev/null 2>&1
714 if [ -n "$gw4" ] || [ "$strictMode" -ne 0 ]; then
715 if [ -z "$gw4" ]; then
716 ip -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1
717 else
718 ip -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1
719 fi
720 ip -4 route flush cache || ipv4_error=1
721 ip -4 rule del fwmark "${mark}/${fwMask}" table "$tid" >/dev/null 2>&1
722 ip -4 rule add fwmark "${mark}/${fwMask}" table "$tid" || ipv4_error=1
723 fi
724
725 if [ "$ipv6Enabled" -ne 0 ]; then
726 ip -6 route del default table "$tid" >/dev/null 2>&1
727 ipv6_error=0
728 if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strictMode" -ne 0 ]; then
729 if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then
730 ip -6 route add unreachable default table "$tid" || ipv6_error=1
731 else
732 ip -6 route list table main | grep " dev $dev6 " | while read -r i; do
733 ip -6 route add "$i" table "$tid" >/dev/null 2>&1 || ipv6_error=1
734 done
735 fi
736 ip -6 route flush cache || ipv6_error=1
737 ip -6 rule del fwmark "${mark}/${fwMask}" table "$tid" >/dev/null 2>&1
738 ip -6 rule add fwmark "${mark}/${fwMask}" table "$tid" || ipv6_error=1
739 fi
740 fi
741
742 if [ $ipv4_error -eq 0 ] || [ $ipv6_error -eq 0 ]; then
743 dscp="$(uci -q get "${packageName}".config."${iface}"_dscp)"
744 if [ "${dscp:-0}" -ge 1 ] && [ "${dscp:-0}" -le 63 ]; then
745 ipt -t mangle -I VPR_PREROUTING -m dscp --dscp "${dscp}" -g "VPR_MARK${mark}" || s=1
746 fi
747 if [ "$iface" = "$icmpIface" ]; then
748 ipt -t mangle -I VPR_OUTPUT -p icmp -g "VPR_MARK${mark}" || s=1
749 fi
750 else
751 s=1
752 fi
753
754 return $s
755}
756
757process_interface(){
758 local gw4 gw6 dev dev6 s=0 dscp iface="$1" action="$2" match="$3" displayText
759
760 is_supported_interface "$iface" || return 0
761 is_wan6 "$iface" && return 0
762 [ $((ifaceMark)) -gt $((fwMask)) ] && return 1
763
764 network_get_device dev "$iface"
765 [ -z "$dev" ] && config_get dev "$iface" 'ifname'
766 [ -z "$dev" ] && config_get dev "$iface" 'device'
767 if is_wan "$iface" && [ -n "$wanIface6" ]; then
768 network_get_device dev6 "$wanIface6"
769 [ -z "$dev6" ] && config_get dev6 "$wanIface6" 'ifname'
770 [ -z "$dev6" ] && config_get dev6 "$wanIface6" 'device'
771 fi
772 [ -z "$dev6" ] && dev6="$dev"
773
774 [ -z "$ifaceTableID" ] && ifaceTableID="$wanTableID"; [ -z "$ifaceMark" ] && ifaceMark="$wanMark";
775
776 case "$action" in
777 destroy)
778 table_destroy "${ifaceTableID}" "${iface}" "${ifaceMark}"
779 ifaceTableID="$((ifaceTableID + 1))"; ifaceMark="$(printf '0x%06x' $((ifaceMark + wanMark)))";
780 ;;
781 create)
782 eval "mark_${iface//-/_}"='$ifaceMark'
783 eval "tid_${iface//-/_}"='$ifaceTableID'
784 if [ -z "$match" ]; then
785 table_destroy "$ifaceTableID" "$iface"
786 fi
787 vpr_get_gateway gw4 "$iface" "$dev"
788 vpr_get_gateway6 gw6 "$iface" "$dev6"
789 if [ "$iface" = "$dev" ]; then
790 displayText="${iface}/${gw4:-0.0.0.0}"
791 else
792 displayText="${iface}/${dev}/${gw4:-0.0.0.0}"
793 fi
794 [ "$ipv6Enabled" -ne 0 ] && displayText="${displayText}/${gw6:-::/0}"
795 if [ -z "$match" ]; then
796 output 2 "Creating table '$displayText' "
797 is_default_dev "$dev" && displayText="${displayText} ${__OK__}"
798 if table_create "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$match"; then
799 gatewaySummary="${gatewaySummary}${displayText}\\n"
800 output_ok
801 else
802 errorSummary="${errorSummary}${_ERROR_}: Failed to set up '$displayText'\\n"
803 output_fail
804 fi
805 elif [ "$iface" = "$match" ]; then
806 output 2 "Reloading table '$displayText' "
807 is_default_dev "$dev" && displayText="${displayText} ${__OK__}"
808 if table_reload "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$match"; then
809 gatewaySummary="${gatewaySummary}${displayText}\\n"
810 output_ok
811 else
812 errorSummary="${errorSummary}${_ERROR_}: Failed to reload '$displayText'\\n"
813 output_fail
814 fi
815 else
816 is_default_dev "$dev" && displayText="${displayText} ${__OK__}"
817 gatewaySummary="${gatewaySummary}${displayText}\\n"
818 fi
819 ifaceTableID="$((ifaceTableID + 1))"; ifaceMark="$(printf '0x%06x' $((ifaceMark + wanMark)))";
820 ;;
821 esac
822 return $s
823}
824
825process_tor_interface(){
826 local s=0 iface="$1" action="$2" displayText
827 case "$action" in
828 destroy)
829 for i in PREROUTING FORWARD INPUT OUTPUT; do
830 ipt -t nat -D "${i}" -m mark --mark "0x0/${fwMask}" -j "VPR_${i}"
831 ipt -t nat -F "VPR_${i}"; ipt -t nat -X "VPR_${i}";
832 done
833 ;;
834 create)
835 output 2 "Creating TOR redirects "
836 dnsPort="$(grep -m1 DNSPort /etc/tor/torrc | awk -F: '{print $2}')"
837 transPort="$(grep -m1 TransPort /etc/tor/torrc | awk -F: '{print $2}')"
838 dnsPort="${dnsPort:-9053}"; transPort="${transPort:-9040}";
839 for i in $usedChainsList; do
840 ipt -t nat -N "VPR_${i}"
841 ipt -t nat "$insertOption" "$i" -m mark --mark "0x0/${fwMask}" -j "VPR_${i}"
842 done
843 if ips 'create' "${iface}" 'hash:net comment' && ips 'flush' "${iface}"; then
844 for i in $usedChainsList; do
845 ipt -t nat -I "VPR_${i}" -p udp -m udp --dport 53 -m set --match-set "${iface}" dst -j REDIRECT --to-ports "$dnsPort" -m comment --comment "TorDNS-UDP" || s=1
846 ipt -t nat -I "VPR_${i}" -p tcp -m tcp --dport 80 -m set --match-set "${iface}" dst -j REDIRECT --to-ports "$transPort" -m comment --comment "TorHTTP-TCP" || s=1
847 ipt -t nat -I "VPR_${i}" -p udp -m udp --dport 80 -m set --match-set "${iface}" dst -j REDIRECT --to-ports "$transPort" -m comment --comment "TorHTTP-UDP" || s=1
848 ipt -t nat -I "VPR_${i}" -p tcp -m tcp --dport 443 -m set --match-set "${iface}" dst -j REDIRECT --to-ports "$transPort" -m comment --comment "TorHTTPS-TCP" || s=1
849 ipt -t nat -I "VPR_${i}" -p udp -m udp --dport 443 -m set --match-set "${iface}" dst -j REDIRECT --to-ports "$transPort" -m comment --comment "TorHTTPS-UDP" || s=1
850 done
851 else
852 s=1
853 fi
854 displayText="${iface}/53->${dnsPort}/80,443->${transPort}"
855 if [ "$s" -eq "0" ]; then
856 gatewaySummary="${gatewaySummary}${displayText}\\n"
857 output_ok
858 else
859 errorSummary="${errorSummary}${_ERROR_}: Failed to set up '$displayText'\\n"
860 output_fail
861 fi
862 ;;
863 esac
864 return $s
865}
866
867convert_config(){
868 local i src_ipset dest_ipset resolver_ipset
869 [ -s "/etc/config/${packageName}" ] || return 0
870 grep -q "ignored_interfaces" "/etc/config/${packageName}" && sed -i 's/ignored_interfaces/ignored_interface/g' "/etc/config/${packageName}"
871 grep -q "supported_interfaces" "/etc/config/${packageName}" && sed -i 's/supported_interfaces/supported_interface/g' "/etc/config/${packageName}"
872 grep -q "local_addresses" "/etc/config/${packageName}" && sed -i 's/local_addresses/local_address/g' "/etc/config/${packageName}"
873 grep -q "local_ports" "/etc/config/${packageName}" && sed -i 's/local_ports/local_port/g' "/etc/config/${packageName}"
874 grep -q "remote_addresses" "/etc/config/${packageName}" && sed -i 's/remote_addresses/remote_address/g' "/etc/config/${packageName}"
875 grep -q "remote_ports" "/etc/config/${packageName}" && sed -i 's/remote_ports/remote_port/g' "/etc/config/${packageName}"
876 grep -q "ipset_enabled" "/etc/config/${packageName}" && sed -i 's/ipset_enabled/dest_ipset/g' "/etc/config/${packageName}"
877 grep -q "dnsmasq_enabled" "/etc/config/${packageName}" && sed -i 's/dnsmasq_enabled/resolver_ipset/g' "/etc/config/${packageName}"
878 grep -q "enable_control" "/etc/config/${packageName}" && sed -i 's/enable_control/webui_enable_column/g' "/etc/config/${packageName}"
879 grep -q "proto_control" "/etc/config/${packageName}" && sed -i 's/proto_control/webui_protocol_column/g' "/etc/config/${packageName}"
880 grep -q "chain_control" "/etc/config/${packageName}" && sed -i 's/chain_control/webui_chain_column/g' "/etc/config/${packageName}"
881 grep -q "sort_control" "/etc/config/${packageName}" && sed -i 's/sort_control/webui_sorting/g' "/etc/config/${packageName}"
882 grep -q "local_address" "/etc/config/${packageName}" && sed -i 's/local_address/src_addr/g' "/etc/config/${packageName}"
883 grep -q "local_port" "/etc/config/${packageName}" && sed -i 's/local_port/src_port/g' "/etc/config/${packageName}"
884 grep -q "remote_address" "/etc/config/${packageName}" && sed -i 's/remote_address/dest_addr/g' "/etc/config/${packageName}"
885 grep -q "remote_port" "/etc/config/${packageName}" && sed -i 's/remote_port/dest_port/g' "/etc/config/${packageName}"
886 grep -q "local_ipset" "/etc/config/${packageName}" && sed -i 's/local_ipset/src_ipset/g' "/etc/config/${packageName}"
887 grep -q "remote_ipset" "/etc/config/${packageName}" && sed -i 's/remote_ipset/dest_ipset/g' "/etc/config/${packageName}"
888 dest_ipset="$(uci -q get $packageName.config.dest_ipset)"
889 src_ipset="$(uci -q get $packageName.config.src_ipset)"
890 resolver_ipset="$(uci -q get $packageName.config.resolver_ipset)"
891
892 if [ -n "$dest_ipset" ] && [ "$dest_ipset" != "0" ] && [ "$dest_ipset" != "1" ]; then
893 uci set "$packageName".config.dest_ipset='0'
894 if [ -z "$resolver_ipset" ]; then
895 uci set "$packageName".config.resolver_ipset='dnsmasq.ipset'
896 fi
897 uci commit "$packageName"
898 fi
899 if [ -n "$src_ipset" ] && [ "$src_ipset" != "0" ] && [ "$src_ipset" != "1" ]; then
900 uci set "$packageName".config.src_ipset='1'
901 uci commit "$packageName"
902 fi
903 if [ -z "$(uci -q get $packageName.config.webui_supported_protocol)" ]; then
904 uci add_list "$packageName".config.webui_supported_protocol='tcp'
905 uci add_list "$packageName".config.webui_supported_protocol='udp'
906 uci add_list "$packageName".config.webui_supported_protocol='tcp udp'
907 uci add_list "$packageName".config.webui_supported_protocol='icmp'
908 uci add_list "$packageName".config.webui_supported_protocol='all'
909 uci commit "$packageName"
910 fi
911 for i in append_local_rules append_src_rules \
912 append_remote_rules append_dest_rules; do
913 if [ -n "$(uci -q get $packageName.config.$i)" ]; then
914 warningSummary="${warningSummary}$_WARNING_: $i setting is not supported in ${serviceName}.\\n"
915 fi
916 done
917 for i in udp_proto_enabled forward_chain_enabled input_chain_enabled \
918 output_chain_enabled iprule_enabled; do
919 if [ "$(uci -q get $packageName.config.$i)" = "1" ]; then
920 warningSummary="${warningSummary}$_WARNING_: $i setting is not supported in ${serviceName}.\\n"
921 fi
922 done
923}
924
925check_config(){ local en; config_get_bool en "$1" 'enabled' 1; [ "$en" -gt 0 ] && _cfg_enabled=0; }
926is_config_enabled(){
927 local cfg="$1" _cfg_enabled=1
928 [ -n "$1" ] || return 1
929 config_load "$packageName"
930 config_foreach check_config "$cfg"
931 return "$_cfg_enabled"
932}
933
934process_user_file(){
935 local path enabled shellBin="${SHELL:-/bin/ash}"
936 config_get_bool enabled "$1" 'enabled' 1
937 config_get path "$1" 'path'
938 [ "$enabled" -gt 0 ] || return 0
939 if [ ! -s "$path" ]; then
940 errorSummary="${errorSummary}${_ERROR_}: Custom user file '$path' not found or empty!\\n"
941 output_fail
942 return 1
943 fi
944 if ! $shellBin -n "$path"; then
945 errorSummary="${errorSummary}${_ERROR_}: Syntax error in custom user file '$path'!\\n"
946 output_fail
947 return 1
948 fi
949 output 2 "Running $path "
950# shellcheck disable=SC1090
951 if ! . "$path"; then
952 errorSummary="${errorSummary}${_ERROR_}: Error running custom user file '$path'!\\n"
953 if grep -q -w 'curl' "$path" && ! is_present 'curl'; then
954 errorSummary="${errorSummary}${_ERROR_}: Use of 'curl' is detected in custom user file '$path', but 'curl' isn't installed!\\n"
955 errorSummary="${errorSummary}${_ERROR_}: If 'curl' is needed, install it with 'opkg update; opkg install curl;' command in CLI.\\n"
956 fi
957 output_fail
958 return 1
959 else
960 output_ok
961 return 0
962 fi
963}
964
965boot() { rc_procd start_service && rc_procd service_triggers; }
966
967start_service() {
968 local dnsmasqStoredHash dnsmasqNewHash i modprobeStatus=0 reloadedIface="$1"
969 convert_config
970 is_enabled 'on_start' || return 1
971 is_wan_up || return 1
972
973 iptables -t 'mangle' --list 'VPR_PREROUTING' >/dev/null 2>&1 || unset reloadedIface
974 [ -n "$(tmpfs get gateway)" ] || unset reloadedIface
975
976 if [ -s "$dnsmasqFile" ]; then
977 dnsmasqStoredHash="$(md5sum $dnsmasqFile | awk '{ print $1; }')"
978 rm -f "$dnsmasqFile"
979 fi
980
981 for i in xt_set ip_set ip_set_hash_ip; do
982 modprobe "$i" >/dev/null 2>/dev/null || modprobeStatus=$((modprobeStatus + 1))
983 done
984
985 if [ "$modprobeStatus" -gt 0 ] && ! is_chaos_calmer; then
986 errorSummary="${errorSummary}${_ERROR_}: Failed to load kernel modules\\n"
987 fi
988
989 if [ -z "$reloadedIface" ]; then
990 for i in $usedChainsList; do
991 ipt -t mangle -N "VPR_${i}"
992 ipt -t mangle "$insertOption" "$i" -m mark --mark "0x0/${fwMask}" -j "VPR_${i}"
993 done
994 fi
995
996 if [ -z "$reloadedIface" ]; then
997 output 1 'Processing Interfaces '
998 config_load 'network'; config_foreach process_interface 'interface' 'create';
999 process_tor_interface 'tor' 'destroy'; is_tor_running && process_tor_interface 'tor' 'create';
1000 output 1 '\n'
1001 if is_config_enabled 'policy'; then
1002 output 1 'Processing Policies '
1003 config_load "$packageName"; config_foreach process_policy 'policy' "$reloadedIface";
1004 output 1 '\n'
1005 fi
1006 if is_config_enabled 'include'; then
1007 output 1 'Processing User File(s) '
1008 config_load "$packageName"; config_foreach process_user_file 'include';
1009 output 1 '\n'
1010 fi
1011 else
1012 output 1 "Reloading Interface: $reloadedIface "
1013 config_load 'network'; config_foreach process_interface 'interface' 'create' "$reloadedIface";
1014 output 1 '\n'
1015 fi
1016
1017 if [ -s "$dnsmasqFile" ]; then
1018 dnsmasqNewHash="$(md5sum $dnsmasqFile | awk '{ print $1; }')"
1019 fi
1020 [ "$dnsmasqNewHash" != "$dnsmasqStoredHash" ] && dnsmasq_restart
1021
1022 if [ -z "$gatewaySummary" ]; then
1023 errorSummary="${errorSummary}${_ERROR_}: failed to set up any gateway!\\n"
1024 fi
1025 procd_open_instance "main"
1026 procd_set_param command /bin/true
1027 procd_set_param stdout 1
1028 procd_set_param stderr 1
1029 procd_open_data
1030 json_add_array 'status'
1031 json_add_object ''
1032 [ -n "$gatewaySummary" ] && json_add_string gateway "$gatewaySummary"
1033 [ -n "$errorSummary" ] && json_add_string error "$errorSummary"
1034 [ -n "$warningSummary" ] && json_add_string warning "$warningSummary"
1035 if [ "$strictMode" -ne 0 ] && str_contains "$gatewaySummary" '0.0.0.0'; then
1036 json_add_string mode "strict"
1037 fi
1038 json_close_object
1039 json_close_array
1040 procd_close_data
1041 procd_close_instance
1042}
1043
1044tmpfs() {
1045 local action="$1" param="$2" value="$3"
1046# shellcheck disable=SC2034
1047 local gateway error warning mode i
1048 if [ -s "$jsonFile" ]; then
1049 json_load_file "$jsonFile" 2>/dev/null
1050 json_select 'status' 2>/dev/null
1051 for i in gateway error warning mode; do
1052 json_get_var $i "$i" 2>/dev/null
1053 done
1054 fi
1055 case "$action" in
1056 get)
1057 printf "%b" "$(eval echo "\$$param")"; return;;
1058 add)
1059 eval "$param"='$(eval echo "\$$param")${value}';;
1060 del)
1061 case "$param" in
1062 all)
1063 unset gateway error warning mode;;
1064 *)
1065 unset "$param";;
1066 esac
1067 ;;
1068 set)
1069 eval "$param"='$value';;
1070 esac
1071 json_init
1072 json_add_object 'status'
1073 json_add_string version "$PKG_VERSION"
1074 for i in gateway error warning mode; do
1075 json_add_string "$i" "$(eval echo "\$$i")"
1076 done
1077 json_close_object
1078 json_dump > "$jsonFile"
1079 sync
1080}
1081
1082service_started() {
1083 tmpfs set 'gateway' "$gatewaySummary"
1084 tmpfs set 'error' "$errorSummary"
1085 tmpfs set 'warning' "$warningSummary"
1086 if [ "$strictMode" -ne 0 ] && str_contains "$gatewaySummary" '0.0.0.0'; then
1087 tmpfs set 'mode' 'strict'
1088 fi
1089 [ -n "$gatewaySummary" ] && output "$serviceName started with gateways:\\n${gatewaySummary}"
1090 [ -n "$errorSummary" ] && output "${errorSummary}"
1091 [ -n "$warningSummary" ] && output "${warningSummary}"
1092 if [ -n "$errorSummary" ]; then
1093 return 2
1094 elif [ -n "$warningSummary" ]; then
1095 return 1
1096 else
1097 return 0
1098 fi
1099}
1100
1101stop_service() {
1102 local i
1103 iptables -t mangle -L | grep -q VPR_PREROUTING || return 0
1104 load_package_config
1105 for i in PREROUTING FORWARD INPUT OUTPUT; do
1106 ipt -t mangle -D "${i}" -m mark --mark "0x0/${fwMask}" -j "VPR_${i}"
1107 ipt -t mangle -F "VPR_${i}"; ipt -t mangle -X "VPR_${i}";
1108 done
1109 config_load 'network'; config_foreach process_interface 'interface' 'destroy';
1110 process_tor_interface 'tor' 'destroy'
1111 unset ifaceTableID; unset ifaceMark;
1112 if [ -s "$dnsmasqFile" ]; then
1113 rm -f "$dnsmasqFile"
1114 dnsmasq_restart
1115 fi
1116 if [ "$serviceEnabled" -ne 0 ]; then
1117 output "$serviceName stopped "; output_okn;
1118 fi
1119}
1120
1121reload_interface() { rc_procd start_service "$1"; }
1122
1123service_triggers() {
1124 local n
1125 is_enabled || return 1
1126
1127 if [ "$procdReloadDelay" -gt 0 ] && [ "$procdReloadDelay" -lt 100 ]; then
1128# shellcheck disable=SC2034
1129 PROCD_RELOAD_DELAY=$(( procdReloadDelay * 1000 ))
1130 fi
1131
1132 procd_open_validate
1133 validate_config
1134 validate_policy
1135 validate_include
1136 procd_close_validate
1137
1138 procd_open_trigger
1139 procd_add_reload_trigger 'openvpn'
1140 if type procd_add_service_trigger 1>/dev/null 2>&1; then
1141 procd_add_service_trigger "service.restart" "firewall" /etc/init.d/${packageName} reload
1142 fi
1143 procd_add_config_trigger "config.change" "${packageName}" /etc/init.d/${packageName} reload
1144 for n in $ifSupported; do
1145 procd_add_interface_trigger "interface.*" "$n" /etc/init.d/${packageName} reload_interface "$n"
1146 done
1147 procd_close_trigger
1148
1149 output 3 "$serviceName monitoring interfaces: $ifSupported"; output_okn;
1150}
1151
1152status_service() { support "$@"; }
1153support() {
1154 local dist vers out id s param status set_d set_p tableCount i=0 dev dev6 j
1155 readonly _SEPARATOR_='============================================================'
1156 is_enabled
1157
1158 json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version
1159 if [ -n "$wanIface4" ]; then
1160 network_get_gateway wanGW4 "$wanIface4"
1161 [ -z "$dev" ] && dev="$(uci -q get network."${wanIface4}".ifname)"
1162 [ -z "$dev" ] && dev="$(uci -q get network."${wanIface4}".device)"
1163 fi
1164 if [ -n "$wanIface6" ]; then
1165 [ -z "$dev6" ] && dev6="$(uci -q get network."${wanIface6}".ifname)"
1166 [ -z "$dev6" ] && dev6="$(uci -q get network."${wanIface6}".device)"
1167 wanGW6=$(ip -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}')
1168 [ "$wanGW6" = "default" ] && wanGW6=$(ip -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}')
1169 fi
1170 while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done
1171 [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support"
1172 status="$serviceName running on $dist $vers."
1173 [ -n "$wanIface4" ] && status="$status WAN (IPv4): ${wanIface4}/${dev}/${wanGW4:-0.0.0.0}."
1174 [ -n "$wanIface6" ] && status="$status WAN (IPv6): ${wanIface6}/${dev6}/${wanGW6:-::/0}."
1175 {
1176 echo "$status"
1177 echo "$_SEPARATOR_"
1178 dnsmasq --version 2>/dev/null | sed '/^$/,$d'
1179 if [ -n "$1" ]; then
1180 echo "$_SEPARATOR_"
1181 echo "Resolving domains"
1182 for i in $1; do
1183 echo "$i: $(resolveip "$i" | tr '\n' ' ')"
1184 done
1185 fi
1186
1187 echo "$_SEPARATOR_"
1188 echo "Routes/IP Rules"
1189 tableCount=$(ip rule list | grep -c 'fwmark') || tableCount=0
1190 if [ -n "$set_d" ]; then route; else route | grep '^default'; fi
1191 if [ -n "$set_d" ]; then ip rule list; fi
1192 i=0; while [ $i -lt $tableCount ]; do
1193 echo ""
1194 echo "IPv4 Table $((wanTableID + i)): $(ip -4 route show table $((wanTableID + i)))"
1195 echo "IPv4 Table $((wanTableID + i)) Rules:"
1196 ip -4 rule list table "$((wanTableID + i))"
1197 i=$((i + 1))
1198 done
1199
1200 if [ "$ipv6Enabled" -ne 0 ]; then
1201 i=0; while [ $i -lt $tableCount ]; do
1202 ip -6 route show table $((wanTableID + i)) | while read -r param; do
1203 echo "IPv6 Table $((wanTableID + i)): $param"
1204 done
1205 i=$((i + 1))
1206 done
1207 fi
1208
1209 for j in Mangle NAT; do
1210 if [ -z "$set_d" ]; then
1211 for i in $usedChainsList; do
1212 if iptables -v -t "$(str_to_lower $j)" -S "VPR_${i}" 1>/dev/null 2>&1; then
1213 echo "$_SEPARATOR_"
1214 echo "$j IP Table: $i"
1215 iptables -v -t "$(str_to_lower $j)" -S "VPR_${i}"
1216 if [ "$ipv6Enabled" -ne 0 ]; then
1217 echo "$_SEPARATOR_"
1218 echo "$j IPv6 Table: $i"
1219 ip6tables -v -t "$(str_to_lower $j)" -S "VPR_${i}"
1220 fi
1221 fi
1222 done
1223 else
1224 echo "$_SEPARATOR_"
1225 echo "$j IP Table"
1226 iptables -L -t "$(str_to_lower $j)"
1227 if [ "$ipv6Enabled" -ne 0 ]; then
1228 echo "$_SEPARATOR_"
1229 echo "$j IPv6 Table"
1230 ip6tables -L -t "$(str_to_lower $j)"
1231 fi
1232 fi
1233 i=0; ifaceMark="$wanMark";
1234 while [ $i -lt $tableCount ]; do
1235 if iptables -v -t "$(str_to_lower $j)" -S "VPR_MARK${ifaceMark}" 1>/dev/null 2>&1; then
1236 echo "$_SEPARATOR_"
1237 echo "$j IP Table MARK Chain: VPR_MARK${ifaceMark}"
1238 iptables -v -t "$(str_to_lower $j)" -S "VPR_MARK${ifaceMark}"
1239 ifaceMark="$(printf '0x%06x' $((ifaceMark + wanMark)))";
1240 fi
1241 i=$((i + 1))
1242 done
1243 done
1244
1245 echo "$_SEPARATOR_"
1246 echo "Current ipsets"
1247 ipset save
1248 if [ -s "$dnsmasqFile" ]; then
1249 echo "$_SEPARATOR_"
1250 echo "DNSMASQ ipsets"
1251 cat "$dnsmasqFile"
1252 fi
1253 echo "$_SEPARATOR_"
1254 } | tee -a /var/${packageName}-support
1255 if [ -n "$set_p" ]; then
1256 printf "%b" "Pasting to paste.ee... "
1257 if is_present 'curl' && is_variant_installed 'libopenssl' && is_installed 'ca-bundle'; then
1258 json_init; json_add_string "description" "${packageName}-support"
1259 json_add_array "sections"; json_add_object '0'
1260 json_add_string "name" "$(uci -q get system.@system[0].hostname)"
1261 json_add_string "contents" "$(cat /var/${packageName}-support)"
1262 json_close_object; json_close_array; payload=$(json_dump)
1263 out=$(curl -s -k "https://api.paste.ee/v1/pastes" -X "POST" -H "Content-Type: application/json" -H "X-Auth-Token:uVOJt6pNqjcEWu7qiuUuuxWQafpHhwMvNEBviRV2B" -d "$payload")
1264 json_load "$out"; json_get_var id id; json_get_var s success
1265 [ "$s" = "1" ] && printf "%b" "https://paste.ee/p/$id $__OK__\\n" || printf "%b" "$__FAIL__\\n"
1266 [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support"
1267 else
1268 printf "%b" "$__FAIL__\\n"
1269 printf "%b" "$_ERROR_: curl, libopenssl or ca-bundle were not found!\\nRun 'opkg update; opkg install curl libopenssl ca-bundle' to install them.\\n"
1270 fi
1271 else
1272 printf "%b" "Your support details have been logged to '/var/${packageName}-support'. $__OK__\\n"
1273 fi
1274}
1275
1276# shellcheck disable=SC2120
1277validate_config() {
1278 uci_validate_section "${packageName}" config "${1}" \
1279 'enabled:bool:0' \
1280 'strict_enforcement:bool:1' \
1281 'ipv6_enabled:bool:0' \
1282 'src_ipset:bool:0' \
1283 'dest_ipset:bool:0' \
1284 'resolver_ipset::or("", "none", "dnsmasq.ipset")' \
1285 'verbosity:range(0,2):1' \
1286 'wan_tid:integer:201' \
1287 'wan_fw_mark:hex(8)' \
1288 'fw_mask:hex(8)' \
1289 'icmp_interface:string' \
1290 'ignored_interface:list(string)' \
1291 'supported_interface:list(string)' \
1292 'boot_timeout:integer:30' \
1293 'iptables_rule_option:or("", "append", "insert")' \
1294 'procd_reload_delay:integer:0' \
1295 'webui_enable_column:bool:0' \
1296 'webui_protocol_column:bool:0' \
1297 'webui_supported_protocol:list(string)' \
1298 'webui_chain_column:bool:0' \
1299 'webui_sorting:bool:1' \
1300 'webui_show_ignore_target:bool:0'
1301}
1302
1303# shellcheck disable=SC2120
1304validate_policy() {
1305 uci_validate_section "${packageName}" policy "${1}" \
1306 'name:string' \
1307 'enabled:bool:0' \
1308 'interface:network' \
1309 'proto:or(string)' \
1310 'chain:or("", "PREROUTING", "FORWARD", "INPUT", "OUTPUT")' \
1311 'src_addr:list(neg(or(host,network,macaddr)))' \
1312 'src_port:list(neg(or(portrange, string)))' \
1313 'dest_addr:list(neg(host))' \
1314 'dest_port:list(neg(or(portrange, string)))'
1315}
1316
1317# shellcheck disable=SC2120
1318validate_include() {
1319 uci_validate_section "${packageName}" include "${1}" \
1320 'path:string' \
1321 'enabled:bool:0'
1322}