759 lines
34 KiB
Bash

#!/bin/sh /etc/rc.common
# Copyright 2017-2018 Stan Grishin (stangri@melmac.net)
PKG_VERSION=
export START=94
export USE_PROCD=1
readonly _OK_='\033[0;32m\xe2\x9c\x93\033[0m'
readonly _FAIL_='\033[0;31m\xe2\x9c\x97\033[0m'
readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m'
readonly __FAIL__='\033[0;31m[\xe2\x9c\x97]\033[0m'
readonly __PASS__='\033[0;33m[-]\033[0m'
readonly _ERROR_='\033[0;31mERROR\033[0m'
# readonly readmeURL="https://github.com/openwrt/packages/tree/master/net/vpn-policy-routing/files/README.md"
readonly readmeURL="https://github.com/stangri/openwrt-packages/blob/vpn-policy-routing/net/vpn-policy-routing/files/README.md"
export EXTRA_COMMANDS="status support"
export EXTRA_HELP=" support Generates output required to troubleshoot routing issues
Use '-d' option for more detailed output
Use '-p' option to automatically upload data under VPR paste.ee account
WARNING: while paste.ee uploads are unlisted, they are still publicly available
List domain names after options to include their lookup in report"
readonly packageName="vpn-policy-routing"
readonly serviceName="$packageName $PKG_VERSION"
readonly PID="/var/run/${packageName}.pid"
readonly dnsmasqFile="/var/dnsmasq.d/${packageName}"
create_lock() { [ -e "$PID" ] && return 1; touch "$PID"; }
remove_lock() { [ -e "$PID" ] && rm -f "$PID"; }
trap remove_lock EXIT
output_ok() { case $verbosity in 1) output "$_OK_";; 2) output "$__OK__\\n";; esac; }
output_okn() { case $verbosity in 1) output "$_OK_\\n";; 2) output "$__OK__\\n";; esac; }
output_fail() { s=1; case $verbosity in 1) output "$_FAIL_";; 2) output "$__FAIL__\\n";; esac; }
output_failn() { case $verbosity in 1) output "$_FAIL_\\n";; 2) output "$__FAIL__\\n";; esac; }
output() {
# Can take a single parameter (text) to be output at any verbosity
# Or target verbosity level and text to be output at specifc verbosity
if [ $# -ne 1 ]; then
if [ ! $((verbosity & $1)) -gt 0 ]; then return 0; else shift; fi
fi
[ -t 1 ] && echo -e -n "$1"
local msg=$(echo -n "${1/$serviceName /service }" | sed 's|\\033\[[0-9]\?;\?[0-9]\?[0-9]\?m||g');
if [ "$(echo -e -n "$msg" | wc -l)" -gt 0 ]; then
logger -t "${packageName:-service} [$$]" "$(echo -e -n "${logmsg}${msg}")"
logmsg=''
else
logmsg="${logmsg}${msg}"
fi
}
is_installed(){ [ -n "$(opkg list-installed "$1")" ]; }
export serviceEnabled verbosity strictMode wanTableID wanMark fwMask
export ipv6Enabled ipsetEnabled ipruleEnabled dnsmasqEnabled udpProtoEnabled icmpIface
export forwardChainEnabled=0 inputChainEnabled=0 outputChainEnabled=0 ignoredIfaces="" supportedIfaces=""
export appendLocalPolicy="" appendRemotePolicy=""
export wanIface4 wanIface6 ifaceMark ifaceTableID ifacesListAll ifacesListSupported wanGW4 wanGW6
export ifconfigOutput ip4RouteOutput ip6RouteOutput
list_iface() { ifacesListAll="${ifacesListAll}${1} "; }
list_supported_iface() { is_supported_interface "$1" && ifacesListSupported="${ifacesListSupported}${1} "; }
vpr_find_iface() {
local iface i
config_load 'network'; "network_find_$2" iface;
[ -z "$ifacesListAll" ] && config_foreach list_iface 'interface'
if [ -z "$iface" ] || is_tunnel "$iface"; then
for i in $ifacesListAll; do if [ "${i%$2}" != "$i" ]; then break; else unset i; fi; done;
fi
[ -n "$i" ] && iface="$i";
[ -n "$iface" ] && eval "$1=$iface";
}
is_l2tp() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:4}" = "l2tp" ]; }
is_oc() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:11}" = "openconnect" ]; }
is_ovpn() { local dev; dev=$(uci -q get network."$1".ifname); [ "${dev:0:3}" = "tun" ] || [ "${dev:0:3}" = "tap" ] || [ -f "/sys/devices/virtual/net/${dev}/tun_flags" ]; }
is_pptp() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:4}" = "pptp" ]; }
is_tor() { local dev; dev=$(uci -q get network."$1".ifname); [ "${dev:0:3}" = "tor" ]; }
is_wg() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:9}" = "wireguard" ]; }
is_tunnel() { is_l2tp "$1" || is_oc "$1" || is_ovpn "$1" || is_pptp "$1" || is_tor "$1" || is_wg "$1"; }
is_wan() { [ -n "$wanIface4" ] && [ "$1" = "$wanIface4" ]; }
is_wan6() { [ -n "$wanIface6" ] && [ "$1" = "$wanIface6" ]; }
is_ignored_interface() { [ "${ignoredIfaces/$1}" != "$ignoredIfaces" ]; }
is_supported_interface() { [ "${supportedIfaces/$1}" != "$supportedIfaces" ] || { ! is_ignored_interface "$1" && { is_wan "$1" || is_wan6 "$1" || is_tunnel "$1"; }; }; }
is_ipv4() { expr "$1" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; }
is_ipv6() { [ "${1//:}" != "$1" ]; }
is_netmask() { local ip="${1%/*}"; [ "$ip" != "$1" ] && is_ipv4 "$ip"; }
is_domain() { [ "${1//[a-zA-Z-]}" != "$1" ]; }
is_phys_dev() { [ -n "$1" ] && [ -n "$ifconfigOutput" ] && echo "$ifconfigOutput" | grep -q "^${1}"; }
is_turris() { /bin/ubus -S call system board | /bin/grep 'Turris' | /bin/grep -q '15.05'; }
get_device_default_gw() { echo "$ip4RouteOutput" | grep -Eso -m1 "via ([0-9]{1,3}\\.){3}[0-9]{1,3} dev $1" | awk '{print $2}'; }
get_device_default_gw6() { echo "$ip6RouteOutput" | grep -Eso -m1 "via ([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(/([0-9]){0,2}){0,2} dev $1" | awk '{print $2}' | awk -F "/" '{print $1}'; }
get_device_gw() { echo "$ip4RouteOutput" | grep -Eso -m1 "^([0-9]{1,3}\\.){3}[0-9]{1,3} dev $1" | awk '{print $1}'; }
get_device_gw6() { echo "$ip6RouteOutput" | grep -Eso -m1 "^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(/([0-9]){0,2}){0,2} dev $1" | awk -F "/" '{print $1}'; }
get_tunnel_gw() { echo "$ifconfigOutput" | grep "^${1}" -A 3 | grep 'P-t-P' | awk '{print $3}' | awk -F ":" '{print $2}'; }
get_tunnel_gw6() { local gw6=$(echo "$ifconfigOutput" | grep "^${1}" -A 3 | grep 'inet6 addr:' | awk '{print $3}'); echo ${gw6:+"${gw6%:*}:1"}; }
dnsmasq_kill() { killall -q -HUP dnsmasq; }
dnsmasq_restart() { /etc/init.d/dnsmasq restart >/dev/null 2>&1; }
load_package_config() {
config_load "$packageName"
config_get_bool serviceEnabled 'config' 'enabled' 0
config_get_bool strictMode 'config' 'strict_enforcement' 1
config_get_bool ipv6Enabled 'config' 'ipv6_enabled' 1
config_get_bool ipsetEnabled 'config' 'ipset_enabled' 1
config_get_bool ipruleEnabled 'config' 'iprule_enabled' 0
config_get_bool dnsmasqEnabled 'config' 'dnsmasq_enabled' 0
config_get_bool udpProtoEnabled 'config' 'udp_proto_enabled' 0
config_get_bool forwardChainEnabled 'config' 'forward_chain_enabled' 0
config_get_bool inputChainEnabled 'config' 'input_chain_enabled' 0
config_get_bool outputChainEnabled 'config' 'output_chain_enabled' 0
config_get appendLocalPolicy 'config' 'append_local_rules'
config_get appendRemotePolicy 'config' 'append_remote_rules'
config_get verbosity 'config' 'verbosity' '2'
config_get wanTableID 'config' 'wan_tid' '201'
config_get wanMark 'config' 'wan_mark' '0x010000'
config_get fwMask 'config' 'fw_mask' '0xff0000'
config_get icmpIface 'config' 'icmp_interface'
config_get ignoredIfaces 'config' 'ignored_interface'
config_get supportedIfaces 'config' 'supported_interface'
if [ -z "${verbosity##*[!0-9]*}" ] || [ "$verbosity" -lt 0 ] || [ "$verbosity" -gt 2 ]; then
verbosity=1
fi
. /lib/functions/network.sh
}
is_enabled() {
local sleepCount=1
load_package_config
if [ "$serviceEnabled" -eq 0 ]; then
if [ "$1" = "on_start" ]; then
output "$packageName is currently disabled.\\n"
output "Run the following commands before starting service again:\\n"
output "uci set $packageName.config.enabled='1'; uci commit;\\n"
fi
return 1
fi
if [ "$dnsmasqEnabled" -ne 0 ]; then
if ! dnsmasq --version | grep -q " ipset "; then
output "$_ERROR_: dnsmasq support is enabled in $packageName, but installed dnsmasq does not support ipsets!\\n"
dnsmasqEnabled=0
else
ipsetEnabled=1
fi
fi
while [ -z "$wanGW" ] ; do
vpr_find_iface wanIface4 'wan'
[ "$ipv6Enabled" -ne 0 ] && vpr_find_iface wanIface6 'wan6'
[ -n "$wanIface4" ] && network_get_gateway wanGW4 "$wanIface4"
[ -n "$wanIface6" ] && network_get_gateway6 wanGW6 "$wanIface6"
wanGW="${wanGW4:-$wanGW6}"
if [ $sleepCount -ge 25 ] || [ -n "$wanGW" ]; then break; fi
output "$serviceName waiting for wan gateway...\\n"; sleep 2; network_flush_cache; sleepCount=$((sleepCount+1));
done
if [ -n "$wanGW" ]; then
mkdir -p "${PID%/*}"; mkdir -p "${dnsmasqFile%/*}";
[ -z "$ip4RouteOutput" ] && ip4RouteOutput="$(ip -4 route 2>/dev/null)"
[ -z "$ip6RouteOutput" ] && ip6RouteOutput="$(ip -6 route 2>/dev/null)"
[ -z "$ifconfigOutput" ] && ifconfigOutput="$(ifconfig 2>/dev/null)"
unset ifacesListSupported
config_load 'network'
config_foreach list_supported_iface 'interface'
return 0
else
output "$_ERROR_: $serviceName failed to discover WAN gateway!\\n"
return 1
fi
}
# shellcheck disable=SC2086
ipt() {
local d failFlagIpv4=0 failFlagIpv6=1
d="${*//-A/-D}"; [ "$d" != "$*" ] && { iptables $d >/dev/null 2>&1; ip6tables $d >/dev/null 2>&1; }
d="${*//-I/-D}"; [ "$d" != "$*" ] && { iptables $d >/dev/null 2>&1; ip6tables $d >/dev/null 2>&1; }
d="${*//-N/-F}"; [ "$d" != "$*" ] && { iptables $d >/dev/null 2>&1; ip6tables $d >/dev/null 2>&1; }
d="${*//-N/-X}"; [ "$d" != "$*" ] && { iptables $d >/dev/null 2>&1; ip6tables $d >/dev/null 2>&1; }
d="$*"; iptables $d >/dev/null 2>&1 || failFlagIpv4=1
if [ "$ipv6Enabled" -ne 0 ]; then ip6tables $d >/dev/null 2>&1 && failFlagIpv6=0; fi
if [ "$failFlagIpv4" -eq 0 ] || [ "$failFlagIpv6" -eq 0 ]; then return 0; else return 1; fi
}
# shellcheck disable=SC2086
ips() {
local command="$1" ipset="${2//-/_}" param="$3" comment="$4" failFlagIpv4=0 failFlagIpv6=0
case "$command" in
add_dnsmasq)
[ "$dnsmasqEnabled" -eq 0 ] && return 1
[ "$ipv6Enabled" -ne 0 ] && ipset="${ipset},${ipset}6"
echo "ipset=/${param}/${ipset} # $comment" >> "$dnsmasqFile" || failFlagIpv4=1
if [ "$failFlagIpv4" -eq 0 ] && [ "$failFlagIpv6" -eq 0 ]; then return 0; else return 1; fi
;;
add)
[ "$ipsetEnabled" -eq 0 ] && return 1
ipset -q -! $command "$ipset" $param comment "$comment" || failFlagIpv4=1
[ "$ipv6Enabled" -ne 0 ] && ipset -q -! "$command" "${ipset}6" ${param/"family inet"/"family inet6"} comment "$comment" || failFlagIpv6=1
if [ "$failFlagIpv4" -eq 0 ] || [ "$failFlagIpv6" -eq 0 ]; then return 0; else return 1; fi
;;
create)
[ "$ipsetEnabled" -eq 0 ] && return 0
ipset -q -! "$command" "$ipset" $param || failFlagIpv4=1
if [ "$ipv6Enabled" -ne 0 ]; then ipset -q -! "$command" "${ipset}6" ${param/"family inet"/"family inet6"} || failFlagIpv6=1; fi
if [ "$failFlagIpv4" -eq 0 ] && [ "$failFlagIpv6" -eq 0 ]; then return 0; else return 1; fi
;;
destroy|flush)
[ "$ipsetEnabled" -eq 0 ] && return 0
ipset -q -! "$command" "$ipset" || failFlagIpv4=1
if [ "$ipv6Enabled" -ne 0 ]; then ipset -q -! "$command" "${ipset}6" || failFlagIpv6=1; fi
if [ "$failFlagIpv4" -eq 0 ] && [ "$failFlagIpv6" -eq 0 ]; then return 0; else return 1; fi
;;
esac
}
ipr()
{
local comment="$1" tid=$(eval echo "\$tid_$2") laddr="$3" failFlagIpv4=0 failFlagIpv6=1
[ "$ipruleEnabled" -ne 0 ] || return 1
ip -4 rule del from "$laddr" table "$tid" >/dev/null 2>&1
ip -4 rule add from "$laddr" table "$tid" >/dev/null 2>&1 || failFlagIpv4=1
if [ "$ipv6Enabled" -ne 0 ]; then
ip -6 rule del from "$laddr" table "$tid" >/dev/null 2>&1
ip -6 rule add from "$laddr" table "$tid" >/dev/null 2>&1 && failFlagIpv6=0
fi
if [ "$failFlagIpv4" -eq 0 ] || [ "$failFlagIpv6" -eq 0 ]; then return 0; else return 1; fi
}
insert_tor_policy() {
local comment="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" proto="${7:-tcp}" chain="${8:-PREROUTING}"
local mark=$(eval echo "\$mark_$iface")
[ -z "$mark" ] && processPolicyStatus="${processPolicyStatus}${_ERROR_}: unknown fw_mark for ${iface}!\\n"
param="-t mangle -I VPR_${chain} 1 -j MARK --set-xmark ${mark}/${fwMask}"
[ -n "$laddr" ] && param="$param -s $laddr"
[ -n "$lport" ] && param="$param -p tcp -m multiport --sport ${lport//-/:}"
[ -n "$raddr" ] && param="$param -d $raddr"
[ -n "$rport" ] && param="$param -p $proto -m multiport --dport ${rport//-/:}"
[ -n "$comment" ] && param="$param -m comment --comment $(echo "$comment" | tr '[\. ~`!@#$%^&*()\+/,<>?//;:]' '_')"
# Here be dragons
return 0
}
insert_policy() {
local comment="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" proto="${7:-tcp}" chain="${8:-PREROUTING}"
local mark=$(eval echo "\$mark_$iface")
[ -z "$mark" ] && processPolicyStatus="${processPolicyStatus}${_ERROR_}: unknown fw_mark for ${iface}!\\n"
# param="-t mangle -A VPR_${chain} -j MARK --set-xmark ${mark}/${fwMask}"
param="-t mangle -I VPR_${chain} 1 -j MARK --set-xmark ${mark}/${fwMask}"
[ -n "$laddr" ] && param="$param -s $laddr $appendLocalPolicy"
[ -n "$lport" ] && param="$param -p tcp -m multiport --sport ${lport//-/:}"
[ -n "$raddr" ] && param="$param -d $raddr $appendRemotePolicy"
[ -n "$rport" ] && param="$param -p $proto -m multiport --dport ${rport//-/:}"
[ -n "$comment" ] && param="$param -m comment --comment $(echo "$comment" | tr '[\. ~`!@#$%^&*()\+/,<>?//;:]' '_')"
ipt "$param" || processPolicyStatus="${processPolicyStatus}${_ERROR_}: ipt $param\\n"
if [ "$outputChainEnabled" -ne 0 ] && [ "$chain" = "PREROUTING" ] && [ "$proto" = "tcp" ] && [ -z "$laddr" ]; then
if [ -n "$lport" ] || [ -n "$rport" ]; then
insert_policy "$1" "$2" "$3" "$4" "$5" "$6" "$proto" 'OUTPUT'
fi
fi
if [ "$udpProtoEnabled" -ne 0 ] && [ "$proto" = "tcp" ]; then
if [ -n "$lport" ] || [ -n "$rport" ]; then
insert_policy "$1" "$2" "$3" "$4" "$5" "$6" 'udp' "$chain"
fi
fi
return 0
}
insert_phys_dev_policy() {
local comment="$1" iface="$2" phys_dev="$3" proto="${7:-tcp}" chain="${8:-PREROUTING}" mark
mark=$(eval echo "\$mark_$iface")
[ -z "$mark" ] && processPolicyStatus="${processPolicyStatus}${_ERROR_}: unknown fw_mark for ${iface}!\\n"
param="-t mangle -I VPR_${chain} 1 -j MARK --set-xmark ${mark}/${fwMask}"
[ -n "$phys_dev" ] && param="$param -i $phys_dev"
[ -n "$comment" ] && param="$param -m comment --comment $(echo "$comment" | tr '[\. ~`!@#$%^&*()\+/,<>?//;:]' '_')"
ipt "$param" || processPolicyStatus="${processPolicyStatus}${_ERROR_}: ipt $param\\n"
return 0
}
r_process_policy(){
local comment="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" resolved_laddr resolved_raddr i ipsFailFlag
if [ "${laddr//[ ;\{\}]}" != "$laddr" ]; then
for i in $(echo "$laddr" | tr " ;{}" "\\n"); do [ -n "$i" ] && r_process_policy "$comment" "$iface" "$i" "$lport" "$raddr" "$rport"; done
elif [ "${lport//[ ;\{\}]}" != "$lport" ]; then
for i in $(echo "$lport" | tr " ;{}" "\\n"); do [ -n "$i" ] && r_process_policy "$comment" "$iface" "$laddr" "$i" "$raddr" "$rport"; done
elif [ "${raddr//[ ;\{\}]}" != "$raddr" ]; then
for i in $(echo "$raddr" | tr " ;{}" "\\n"); do [ -n "$i" ] && r_process_policy "$comment" "$iface" "$laddr" "$lport" "$i" "$rport"; done
elif [ "${rport//[ ;\{\}]}" != "$rport" ]; then
for i in $(echo "$rport" | tr " ;{}" "\\n"); do [ -n "$i" ] && r_process_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$i"; done
else
if is_tor "$iface"; then
insert_tor_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport"
elif is_netmask "$laddr" || is_netmask "$raddr"; then
insert_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport"
elif [ -n "$laddr" ] && [ -z "$lport" ] && [ -z "$raddr" ] && [ -z "$rport" ]; then
if is_phys_dev "$laddr"; then
insert_phys_dev_policy "$comment" "$iface" "$laddr"
else
for i in $(resolveip "$laddr"); do
ipr "$comment" "$iface" "$i" || insert_policy "$comment" "$iface" "$i"
done
fi
elif [ -z "$laddr" ] && [ -z "$lport" ] && [ -n "$raddr" ] && [ -z "$rport" ]; then
if is_ipv6 "$raddr"; then
ips "add" "${iface}" "$raddr" "${comment}: $raddr" || ipsFailFlag=1;
elif is_domain "$raddr"; then
if ! ips "add_dnsmasq" "${iface}" "$raddr" "${comment}"; then
for i in $(resolveip "$raddr"); do ips "add" "${iface}" "$raddr" "${comment}: $raddr" || ipsFailFlag=1; done
fi
elif is_ipv4 "$raddr"; then
ips "add" "${iface}" "$raddr" "${comment}: $raddr" || ipsFailFlag=1
else
ipsFailFlag=1
fi
else
ipsFailFlag=1
fi
if [ -n "$ipsFailFlag" ]; then # either ipset above has failed or it's a non-ispet compatible policy
if is_netmask "$laddr" || is_netmask "$raddr"; then
insert_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport"
else
[ -n "$laddr" ] && resolved_laddr="$(resolveip "$laddr")"
[ -n "$raddr" ] && resolved_raddr="$(resolveip "$raddr")"
if [ "$resolved_laddr" != "$laddr" ]; then
for i in $resolved_laddr; do [ -n "$i" ] && r_process_policy "$comment $laddr" "$iface" "$i" "$lport" "$raddr" "$rport"; done
elif [ "$resolved_raddr" != "$raddr" ]; then
for i in $resolved_raddr; do [ -n "$i" ] && r_process_policy "$comment $raddr" "$iface" "$laddr" "$lport" "$i" "$rport"; done
else
insert_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport"
fi
fi
fi
fi
}
process_policy(){
local name comment iface laddr lport raddr rport param mark processPolicyStatus
config_get comment "$1" 'comment'
config_get name "$1" 'name' 'blank'
config_get iface "$1" 'interface'
config_get laddr "$1" 'local_addresses'
config_get lport "$1" 'local_ports'
config_get raddr "$1" 'remote_addresses'
config_get rport "$1" 'remote_ports'
comment="${comment:-$name}"
output 2 "Routing '$comment' via $iface "
if [ -z "$comment" ]; then
errorStatusSummary="${errorStatusSummary}${_ERROR_}: policy name is empty!\\n"
output_fail; return 1;
fi
if [ -z "$laddr" ] && [ -z "$lport" ] && [ -z "$raddr" ] && [ -z "$rport" ]; then
errorStatusSummary="${errorStatusSummary}${_ERROR_}: policy '$comment' missing all IPs/ports!\\n"
output_fail; return 1;
fi
if ! is_supported_interface "$iface"; then
errorStatusSummary="${errorStatusSummary}${_ERROR_}: policy '$comment' has an unknown interface: ${iface}!\\n"
output_fail; return 1;
fi
# lport="$(echo $lport | sed -n 's/ \+/,/gp')"; rport="$(echo $rport | sed -n 's/ \+/,/gp')";
lport="${lport// / }"; lport="${lport// /,}";
rport="${rport// / }"; rport="${rport// /,}";
r_process_policy "$comment" "$iface" "$laddr" "$lport" "$raddr" "$rport"
if [ -n "$processPolicyStatus" ]; then
output_fail
errorStatusSummary="${errorStatusSummary}${processPolicyStatus}\\n"
else
output_ok
fi
}
table_destroy(){
local tid="$1" iface="$2" mark="$3"
if [ -n "$tid" ] && [ -n "$iface" ] && [ -n "$mark" ]; then
ip -4 rule del fwmark "$mark" table "$tid" >/dev/null 2>&1
ip -6 rule del fwmark "$mark" table "$tid" >/dev/null 2>&1
ip -4 rule del table "$tid" >/dev/null 2>&1
ip -6 rule del table "$tid" >/dev/null 2>&1
ip -4 route flush table "$tid";
ip -6 route flush table "$tid";
ips 'flush' "${iface}"; ips 'destroy' "${iface}";
ip -4 route flush cache
ip -6 route flush cache
return 0
else
return 1
fi
}
table_create(){
local tid="$1" mark="$2" iface="$3" gw4="$4" dev="$5" gw6="$6" dev6="$7" dscp s=0 i ipv4_set=0 ipv6_set=0 ipv4_error=0 ipv6_error=0
if [ -n "$tid" ] && [ -n "$mark" ] && [ -n "$iface" ]; then
table_destroy "$tid" "$iface" "$mark"
if { [ -n "$gw4" ] && [ "$gw4" != "0.0.0.0" ]; } || [ "$strictMode" -ne 0 ]; then
if [ -z "$gw4" ] || [ "$gw4" = "0.0.0.0" ]; then
ip -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1
else
ip -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1
fi
ip -4 route flush cache || ipv4_error=1
ip -4 rule add fwmark "$mark" table "$tid" || ipv4_error=1
[ "$ipv4_error" -eq 0 ] && ipv4_set=1 || s=$ipv4_error
fi
if [ "$ipv6Enabled" -ne 0 ]; then
if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strictMode" -ne 0 ]; then
if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then
ip -6 route add unreachable default table "$tid" || ipv6_error=1
else
ip -6 route show | grep " dev $dev6 " | while read -r i; do
ip -6 route add "$i" table "$tid" >/dev/null 2>&1 || ipv6_error=1
done
fi
ip -6 route flush cache || ipv6_error=1
ip -6 rule add fwmark "$mark" table "$tid" || ipv6_error=1
s=$ipv6_error
[ "$ipv6_error" -eq 0 ] && ipv6_set=1 || s=$ipv6_error
fi
fi
if [ $ipv4_set -ne 0 ] || [ $ipv6_set -ne 0 ]; then
dscp="$(uci -q get "${packageName}".config."${iface}"_dscp)"
if [ "${dscp:-0}" -ge 1 ] && [ "${dscp:-0}" -le 63 ]; then
ipt -t mangle -I VPR_PREROUTING 1 -m dscp --dscp "${dscp}" -j MARK --set-xmark "${mark}/${fwMask}" || s=1
fi
if [ $ipsetEnabled -ne 0 ]; then
ips 'create' "${iface}" 'hash:net family inet comment' && ips 'flush' "${iface}" || s=1
[ $ipv4_set -ne 0 ] && { ipt -t mangle -I VPR_PREROUTING 1 -m set --match-set "${iface}" dst -j MARK --set-xmark "${mark}/${fwMask}" || s=1; }
[ $ipv6_set -ne 0 ] && { ipt -t mangle -I VPR_PREROUTING 1 -m set --match-set "${iface}6" dst -j MARK --set-xmark "${mark}/${fwMask}" || s=1; }
if [ "$forwardChainEnabled" -ne 0 ]; then
[ $ipv4_set -ne 0 ] && { ipt -t mangle -I VPR_FORWARD 1 -m set --match-set "${iface}" dst -j MARK --set-xmark "${mark}/${fwMask}" || s=1; }
[ $ipv6_set -ne 0 ] && { ipt -t mangle -I VPR_FORWARD 1 -m set --match-set "${iface}6" dst -j MARK --set-xmark "${mark}/${fwMask}" || s=1; }
fi
if [ "$inputChainEnabled" -ne 0 ]; then
[ $ipv4_set -ne 0 ] && { ipt -t mangle -I VPR_INPUT 1 -m set --match-set "${iface}" dst -j MARK --set-xmark "${mark}/${fwMask}" || s=1; }
[ $ipv6_set -ne 0 ] && { ipt -t mangle -I VPR_INPUT 1 -m set --match-set "${iface}6" dst -j MARK --set-xmark "${mark}/${fwMask}" || s=1; }
fi
if [ "$outputChainEnabled" -ne 0 ]; then
[ $ipv4_set -ne 0 ] && { ipt -t mangle -I VPR_OUTPUT 1 -m set --match-set "${iface}" dst -j MARK --set-xmark "${mark}/${fwMask}" || s=1; }
[ $ipv6_set -ne 0 ] && { ipt -t mangle -I VPR_OUTPUT 1 -m set --match-set "${iface}6" dst -j MARK --set-xmark "${mark}/${fwMask}" || s=1; }
fi
fi
if [ "$iface" = "$icmpIface" ] && [ "$outputChainEnabled" -ne 0 ]; then
ipt -t mangle -I VPR_OUTPUT 1 -p icmp -j MARK --set-xmark "${mark}/${fwMask}" || s=1
fi
fi
fi
return $s
}
process_interface(){
local gw4 gw6 dev dev6 s=0 dscp iface="$1" action="$2" displayText
is_supported_interface "$iface" || return 0
is_wan6 "$iface" && return 0
[ $((ifaceMark)) -gt $((fwMask)) ] && return 1
network_get_device dev "$iface"
[ -z "$dev" ] && config_get dev "$iface" 'ifname'
if is_wan "$iface" && [ -n "$wanIface6" ]; then
config_get dev6 "$wanIface6" 'ifname'
fi
[ -z "$dev6" ] && dev6="$dev"
[ -z "$ifaceTableID" ] && ifaceTableID="$wanTableID"; [ -z "$ifaceMark" ] && ifaceMark="$wanMark";
case "$action" in
destroy)
table_destroy "${ifaceTableID}" "${iface}" "${ifaceMark}"
ifaceTableID="$((ifaceTableID + 1))"; ifaceMark="$(printf '0x%06x' $((ifaceMark + wanMark)))";
;;
create)
if ! is_ipv4 "$gw4" || [ "$gw4" = "0.0.0.0" ]; then gw4="$(get_device_default_gw "$dev")"; fi
if ! is_ipv6 "$gw6" || [ "$gw6" = "::/0" ]; then gw6="$(get_device_default_gw6 "$dev6")"; fi
if ! is_ipv4 "$gw4" || [ "$gw4" = "0.0.0.0" ]; then gw4="$(get_device_gw "$dev")"; fi
if ! is_ipv6 "$gw6" || [ "$gw6" = "::/0" ]; then gw6="$(get_device_gw6 "$dev6")"; fi
if ! is_ipv4 "$gw4" || [ "$gw4" = "0.0.0.0" ]; then gw4="$(get_tunnel_gw "$dev")"; fi
if ! is_ipv6 "$gw6" || [ "$gw6" = "::/0" ]; then gw6="$(get_tunnel_gw6 "$dev6")"; fi
eval "mark_${iface}=$ifaceMark"; eval "tid_${iface}=$ifaceTableID";
table_destroy "${ifaceTableID}" "${iface}"
# [ "$ipv6Enabled" -ne 0 ] && displayText="${iface}/${dev}/${gw4:-0.0.0.0}/${gw6:-::/0}" || displayText="${iface}/${dev}/${gw4:-0.0.0.0}"
[ "$ipv6Enabled" -ne 0 ] && displayText="${iface}/${gw4:-0.0.0.0}/${gw6:-::/0}" || displayText="${iface}/${gw4:-0.0.0.0}"
output 2 "Creating table '$displayText' "
if table_create "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6"; then
serviceStatusSummary="${serviceStatusSummary}${displayText} "
output_ok
else
errorStatusSummary="${errorStatusSummary}${_ERROR_}: Failed to set up '$displayText'\\n"
output_fail
fi
ifaceTableID="$((ifaceTableID + 1))"; ifaceMark="$(printf '0x%06x' $((ifaceMark + wanMark)))";
;;
*) return 1;;
esac
return $s
}
ubus_status(){
case "$1" in
add)
ubus_status set "$(ubus_status get)${2}"
;;
del | set)
ubus call service set "{ \"name\": \"${packageName}\", \"instances\": { \"status\": { \"command\": [ \"/bin/true\" ], \"data\": { \"status\": \"${2}\" }}}}"
# ubus call service set "{ \"name\": \"${packageName}\", \"instances\": { \"status\": { \"data\": { \"status\": \"${2}\" }}}}"
;;
get)
ubus call service list "{\"name\": \"${packageName}\"}" | jsonfilter -l1 -e "@['${packageName}']['instances']['status']['data']['status']"
;;
esac
}
start_service() {
local serviceStatusSummary errorStatusSummary dnsmasqStoredHash dnsmasqNewHash
is_enabled 'on_start' || return 1
if create_lock; then
if [ -s "$dnsmasqFile" ]; then
dnsmasqStoredHash="$(md5sum $dnsmasqFile | awk '{ print $1; }')"
rm -f "$dnsmasqFile"
fi
{ modprobe xt_set; modprobe ip_set; modprobe ip_set_hash_ip; } >/dev/null 2>&1;
ipt -t mangle -N VPR_PREROUTING; ipt -t mangle -A PREROUTING -m mark --mark "0x00/${fwMask}" -g VPR_PREROUTING;
if [ $forwardChainEnabled -ne 0 ]; then
ipt -t mangle -N VPR_FORWARD; ipt -t mangle -A FORWARD -m mark --mark "0x00/${fwMask}" -g VPR_FORWARD;
fi
if [ $inputChainEnabled -ne 0 ]; then
ipt -t mangle -N VPR_INPUT; ipt -t mangle -A INPUT -m mark --mark "0x00/${fwMask}" -g VPR_INPUT;
fi
if [ $outputChainEnabled -ne 0 ]; then
ipt -t mangle -N VPR_OUTPUT; ipt -t mangle -A OUTPUT -m mark --mark "0x00/${fwMask}" -g VPR_OUTPUT;
fi
output 1 'Processing interfaces '
config_load 'network'; config_foreach process_interface 'interface' 'create';
output 1 '\n'
output 1 'Processing policies '
config_load $packageName; config_foreach process_policy 'policy';
output 1 '\n'
if [ -s "$dnsmasqFile" ]; then
dnsmasqNewHash="$(md5sum $dnsmasqFile | awk '{ print $1; }')"
fi
[ "$dnsmasqNewHash" != "$dnsmasqStoredHash" ] && dnsmasq_restart
if [ -z "$serviceStatusSummary" ]; then
output "$_ERROR_: $serviceName failed to set up any interface!\\n"
ubus_status 'set' "Fail: $serviceName failed to set up any interface!"
return 1
else
if [ "$strictMode" -ne 0 ] && [ "${serviceStatusSummary//0.0.0.0}" != "${serviceStatusSummary}" ]; then
serviceStatusSummary="(strict mode): ${serviceStatusSummary}"
fi
output "$serviceName started on ${serviceStatusSummary}"
if [ -z "$errorStatusSummary" ]; then
output_okn
ubus_status 'set' "Success: ${serviceStatusSummary}"
else
output "with errors "
output_failn
output "${errorStatusSummary}"
ubus_status 'set' "Started on ${serviceStatusSummary} with error(s)"
fi
fi
remove_lock
else
output "$serviceName: another instance of ${packageName} is currently running "
output_failn
return 1
fi
}
stop_service() {
if create_lock; then
load_package_config
config_load 'network'; config_foreach process_interface 'interface' 'destroy'
ipt -t mangle -D PREROUTING -m mark --mark "0x00/${fwMask}" -g VPR_PREROUTING
ipt -t mangle -F VPR_PREROUTING; ipt -t mangle -X VPR_PREROUTING;
ipt -t mangle -D FORWARD -m mark --mark "0x00/${fwMask}" -g VPR_FORWARD
ipt -t mangle -F VPR_FORWARD; ipt -t mangle -X VPR_FORWARD;
ipt -t mangle -D INPUT -m mark --mark "0x00/${fwMask}" -g VPR_INPUT
ipt -t mangle -F VPR_INPUT; ipt -t mangle -X VPR_INPUT;
ipt -t mangle -D OUTPUT -m mark --mark "0x00/${fwMask}" -g VPR_OUTPUT
ipt -t mangle -F VPR_OUTPUT; ipt -t mangle -X VPR_OUTPUT;
unset ifaceTableID; unset ifaceMark;
if [ -s "$dnsmasqFile" ]; then
rm -f "$dnsmasqFile"
dnsmasq_kill
fi
if [ "$serviceEnabled" -ne 0 ]; then
output "$serviceName stopped "; output_okn;
fi
ubus_status 'set' "Stopped"
remove_lock
else
output "$serviceName: another instance of ${packageName} is currently running "; output_failn;
return 1
fi
}
# shellcheck disable=SC2119
service_triggers() {
local n
is_enabled || return 1
procd_open_validate
validate_config
validate_policies
procd_close_validate
procd_add_reload_trigger 'firewall'
procd_add_reload_trigger 'openvpn'
procd_add_reload_trigger 'vpn-policy-routing'
procd_open_trigger
for n in $ifacesListSupported; do procd_add_reload_interface_trigger "$n"; procd_add_interface_trigger "interface.*" "$n" /etc/init.d/${packageName} reload; done;
output "$serviceName monitoring interfaces: $ifacesListSupported"; output_okn;
procd_close_trigger
}
input() { local data; while read -r data; do echo "$data" | tee -a /var/${packageName}-support; done; }
status() { support "$@"; }
support() {
local dist vers out id s param status set_d set_p tableCount i=0 dev dev6
is_enabled
json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version
if [ -n "$wanIface4" ]; then
network_get_gateway wanGW4 "$wanIface4"
dev="$(uci -q get network."${wanIface4}".ifname)"
fi
if [ -n "$wanIface6" ]; then
dev6="$(uci -q get network."${wanIface6}".ifname)"
wanGW6=$(ip -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}')
[ "$wanGW6" = "default" ] && wanGW6=$(ip -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}')
fi
while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done
[ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support"
status="$serviceName running on $dist $vers."
[ -n "$wanIface4" ] && status="$status WAN (IPv4): $wanIface4/dev/${wanGW4:-0.0.0.0}."
[ -n "$wanIface6" ] && status="$status WAN (IPv6): $wanIface6/dev6/${wanGW6:-::/0}."
{
echo "$status"
echo "============================================================"
dnsmasq --version | sed '/^$/,$d'
[ -n "$1" ] && {
echo "============================================================"
echo "Resolving domains"
while [ -n "$1" ]; do echo "$1: $(resolveip "$1" | tr '\n' ' ')"; shift; done; }
echo "============================================================"
echo "Routes/IP Rules"
tableCount=$(ip rule list | grep -c 'fwmark') || tableCount=0
if [ -n "$set_d" ]; then route; else route | grep '^default'; fi
if [ -n "$set_d" ]; then ip rule list; fi # || ip rule list | grep 'fwmark'
i=0; while [ $i -lt $tableCount ]; do echo "IPv4 Table $((wanTableID + i)): $(ip route show table $((wanTableID + i)))"; echo "IPv4 Table $((wanTableID + i)) Rules:"; ip rule list | grep $((wanTableID + i)); let i++; done
[ "$ipv6Enabled" -ne 0 ] && {
i=0; while [ $i -lt $tableCount ]; do
ip -6 route show table $((wanTableID + i)) | while read -r param; do echo "IPv6 Table $((wanTableID + i)): $param"; done
let i++;
done; }
echo "============================================================"
if [ -z "$set_d" ]; then echo "IP Tables PREROUTING"; else echo "IP Tables"; fi
if [ -z "$set_d" ]; then iptables -v -t mangle -S VPR_PREROUTING; else iptables -L -t mangle; fi
[ "$ipv6Enabled" -ne 0 ] && {
echo "============================================================"
if [ -z "$set_d" ]; then echo "IP6 Tables PREROUTING"; else echo "IP6 Tables"; fi
if [ -z "$set_d" ]; then ip6tables -v -t mangle -S VPR_PREROUTING; else ip6tables -L -t mangle; fi
}
[ "$forwardChainEnabled" -ne 0 ] && [ -z "$set_d" ] && { echo "============================================================"
echo "IP Tables FORWARD"
iptables -v -t mangle -S VPR_FORWARD
[ "$ipv6Enabled" -ne 0 ] && {
echo "============================================================"
echo "IPv6 Tables FORWARD"
ip6tables -v -t mangle -S VPR_FORWARD
};}
[ "$inputChainEnabled" -ne 0 ] && [ -z "$set_d" ] && { echo "============================================================"
echo "IP Tables INPUT"
iptables -v -t mangle -S VPR_INPUT
[ "$ipv6Enabled" -ne 0 ] && {
echo "============================================================"
echo "IPv6 Tables INPUT"
ip6tables -v -t mangle -S VPR_INPUT
};}
[ "$outputChainEnabled" -ne 0 ] && [ -z "$set_d" ] && { echo "============================================================"
echo "IP Tables OUTPUT"
iptables -v -t mangle -S VPR_OUTPUT
[ "$ipv6Enabled" -ne 0 ] && {
echo "============================================================"
echo "IPv6 Tables OUTPUT"
ip6tables -v -t mangle -S VPR_OUTPUT
};}
echo "============================================================"
echo "Current ipsets"
ipset save
if [ -s "$dnsmasqFile" ]; then
echo "============================================================"
echo "DNSMASQ ipsets"
cat "$dnsmasqFile"
fi
echo "============================================================"
} | input
if [ -n "$set_p" ]; then
echo -e -n "Pasting to paste.ee... "
if is_installed 'curl' && is_installed 'libopenssl' && is_installed 'ca-bundle'; then
json_init; json_add_string "description" "${packageName}-support"
json_add_array "sections"; json_add_object '0'
json_add_string "name" "$(uci -q get system.@system[0].hostname)"
json_add_string "contents" "$(cat /var/${packageName}-support)"
json_close_object; json_close_array; payload=$(json_dump)
out=$(curl -s -k "https://api.paste.ee/v1/pastes" -X "POST" -H "Content-Type: application/json" -H "X-Auth-Token:uVOJt6pNqjcEWu7qiuUuuxWQafpHhwMvNEBviRV2B" -d "$payload")
json_load "$out"; json_get_var id id; json_get_var s success
[ "$s" = "1" ] && echo -e "https://paste.ee/p/$id $__OK__" || echo -e "$__FAIL__"
[ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support"
else
echo -e "$__FAIL__"
echo -e "$_ERROR_: curl, libopenssl or ca-bundle were not found!\\nRun 'opkg update; opkg install curl libopenssl ca-bundle' to install them."
fi
else
echo -e "Your support details have been logged to '/var/${packageName}-support'. $__OK__"
fi
}
# shellcheck disable=SC2120
validate_config() {
uci_validate_section "${packageName}" config "${1}" \
'enabled:bool:0' \
'strict_enforcement:bool:1' \
'ipv6_enabled:bool:1' \
'ipset_enabled:bool:1' \
'iprule_enabled:bool:0' \
'dnsmasq_enabled:bool:0' \
'udp_proto_enabled:bool:0' \
'forward_chain_enabled:bool:0' \
'input_chain_enabled:bool:0' \
'output_chain_enabled:bool:0' \
'verbosity:range(0,2):1' \
'wan_tid:integer:201' \
'wan_fw_mark:string' \
'fw_mask:string' \
'icmp_interface:string' \
'ignored_interface:list(string)' \
'supported_interface:list(string)'
}
# shellcheck disable=SC2120
validate_policies() {
uci_validate_section "${packageName}" policy "${1}" \
'comment:string' \
'gateway:string' \
'local_addresses:list(or(string,cidr4,cidr6))' \
'local_ports:list(or(port,portrange))' \
'remote_addresses:list(or(string,cidr4,cidr6))' \
'remote_ports:list(or(port,portrange))'
}