#!/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))' }