diff --git a/net/mwan3/Makefile b/net/mwan3/Makefile index 65e2925c3..4850bfae4 100644 --- a/net/mwan3/Makefile +++ b/net/mwan3/Makefile @@ -8,10 +8,11 @@ include $(TOPDIR)/rules.mk PKG_NAME:=mwan3 -PKG_VERSION:=2.9.0 +PKG_VERSION:=2.10.0 PKG_RELEASE:=1 PKG_MAINTAINER:=Florian Eckert PKG_LICENSE:=GPL-2.0 +PKG_CONFIG_DEPENDS:=CONFIG_IPV6 include $(INCLUDE_DIR)/package.mk @@ -61,8 +62,13 @@ fi exit 0 endef +define Build/Compile + $(TARGET_CC) $(CFLAGS) $(LDFLAGS) $(FPIC) -shared -o $(PKG_BUILD_DIR)/libwrap_mwan3_sockopt.so.1.0 $(if $(CONFIG_IPV6),-DCONFIG_IPV6) $(PKG_BUILD_DIR)/sockopt_wrap.c -ldl +endef + define Package/mwan3/install -$(CP) ./files/* $(1) + $(CP) ./files/* $(1) + $(CP) $(PKG_BUILD_DIR)/libwrap_mwan3_sockopt.so.1.0 $(1)/lib/mwan3/ endef $(eval $(call BuildPackage,mwan3)) diff --git a/net/mwan3/files/etc/config/mwan3 b/net/mwan3/files/etc/config/mwan3 index 926719a3c..54c449fdf 100644 --- a/net/mwan3/files/etc/config/mwan3 +++ b/net/mwan3/files/etc/config/mwan3 @@ -1,3 +1,5 @@ +# For full documentation of mwan3 configuration: +# https://openwrt.org/docs/guide-user/network/wan/multiwan/mwan3#mwan3_configuration config globals 'globals' option mmx_mask '0x3F00' @@ -10,15 +12,6 @@ config interface 'wan' list track_ip '208.67.220.220' option family 'ipv4' option reliability '2' - option count '1' - option timeout '2' - option failure_latency '1000' - option recovery_latency '500' - option failure_loss '20' - option recovery_loss '5' - option interval '5' - option down '3' - option up '8' config interface 'wan6' option enabled '0' @@ -28,11 +21,6 @@ config interface 'wan6' list track_ip '2620:0:ccc::2' option family 'ipv6' option reliability '2' - option count '1' - option timeout '2' - option interval '5' - option down '3' - option up '8' config interface 'wanb' option enabled '0' @@ -42,15 +30,6 @@ config interface 'wanb' list track_ip '208.67.220.220' option family 'ipv4' option reliability '1' - option count '1' - option timeout '2' - option failure_latency '1000' - option recovery_latency '500' - option failure_loss '20' - option recovery_loss '5' - option interval '5' - option down '3' - option up '8' config interface 'wanb6' option enabled '0' @@ -60,11 +39,6 @@ config interface 'wanb6' list track_ip '2620:0:ccc::2' option family 'ipv6' option reliability '1' - option count '1' - option timeout '2' - option interval '5' - option down '3' - option up '8' config member 'wan_m1_w3' option interface 'wan' diff --git a/net/mwan3/files/etc/hotplug.d/iface/15-mwan3 b/net/mwan3/files/etc/hotplug.d/iface/15-mwan3 index 6a7e95ac4..320e7f787 100644 --- a/net/mwan3/files/etc/hotplug.d/iface/15-mwan3 +++ b/net/mwan3/files/etc/hotplug.d/iface/15-mwan3 @@ -7,9 +7,11 @@ . /lib/mwan3/common.sh SCRIPTNAME="mwan3-hotplug" -[ "$ACTION" = "ifup" ] || [ "$ACTION" = "ifdown" ] || [ "$ACTION" = "connected" ] || [ "$ACTION" = "disconnected" ] || exit 1 +[ "$ACTION" = "ifup" ] || [ "$ACTION" = "ifdown" ] || [ "$ACTION" = "connected" ] || [ "$ACTION" = "disconnected" ] || exit 1 [ -n "$INTERFACE" ] || exit 2 -if ( [ "$ACTION" = "ifup" ] || [ "$ACTION" = "connected" ] ) && [ -z "$DEVICE" ]; then +[ "$FIRSTCONNECT" = "1" ] || [ "$MWAN3_SHUTDOWN" = "1" ] && exit 0 + +if { [ "$ACTION" = "ifup" ] || [ "$ACTION" = "connected" ] ; } && [ -z "$DEVICE" ]; then LOG notice "$ACTION called on $INTERFACE with no device set" exit 3 fi @@ -17,10 +19,9 @@ fi [ "$MWAN3_STARTUP" = 1 ] || mwan3_lock "$ACTION" "$INTERFACE" config_load mwan3 -config_get_bool enabled globals 'enabled' '0' -[ "${enabled}" -gt 0 ] || { +/etc/init.d/mwan3 running || { [ "$MWAN3_STARTUP" = 1 ] || mwan3_unlock "$ACTION" "$INTERFACE" - LOG notice "mwan3 hotplug on $INTERFACE not called because globally disabled" + LOG notice "mwan3 hotplug $ACTION on $INTERFACE not called because globally disabled" mwan3_flush_conntrack "$INTERFACE" "$ACTION" exit 0 } @@ -33,15 +34,14 @@ $IPT4 -S mwan3_hook &>/dev/null || { mwan3_init [ "$MWAN3_STARTUP" = 1 ] || { - mwan3_set_connected_iptables - mwan3_set_custom_ipset + config_get family $INTERFACE family ipv4 + mwan3_set_connected_${family} } -if [ "$MWAN3_STARTUP" != 1 ]; then +if [ "$MWAN3_STARTUP" != 1 ] && [ "$ACTION" = "ifup" ]; then mwan3_set_user_iface_rules $INTERFACE $DEVICE fi -config_get initial_state $INTERFACE initial_state "online" config_get_bool enabled $INTERFACE 'enabled' '0' [ "${enabled}" -eq 1 ] || { [ "$MWAN3_STARTUP" = 1 ] || mwan3_unlock "$ACTION" "$INTERFACE" @@ -49,54 +49,47 @@ config_get_bool enabled $INTERFACE 'enabled' '0' exit 0 } -trackpid=$(pgrep -f "mwan3track $INTERFACE ") - +config_get initial_state $INTERFACE initial_state "online" if [ "$initial_state" = "offline" ]; then status=$(cat $MWAN3TRACK_STATUS_DIR/$INTERFACE/STATUS 2>/dev/null || echo unknown) + [ "$status" = "online" ] || status=offline else status=online fi -[ -z "$TRUE_INTERFACE" ] && mwan3_get_true_iface TRUE_INTERFACE $INTERFACE - -binary_status=$status -[ "$binary_status" = "online" ] || binary_status=offline +if [ "$ACTION" = ifup ] || [ "$ACTION" = ifdown ]; then + initscript=/etc/init.d/mwan3 + . /lib/functions/procd.sh +fi -LOG notice "Execute "$ACTION" event on interface $INTERFACE (${DEVICE:-unknown})" +LOG notice "Execute $ACTION event on interface $INTERFACE (${DEVICE:-unknown})" case "$ACTION" in - ifup|connected) + connected) + mwan3_set_iface_hotplug_state $INTERFACE "online" + mwan3_set_policies_iptables + ;; + ifup) mwan3_create_iface_iptables $INTERFACE $DEVICE mwan3_create_iface_rules $INTERFACE $DEVICE - mwan3_create_iface_route $INTERFACE $DEVICE - [ "$MWAN3_STARTUP" != 1 ] && mwan3_add_non_default_iface_route $INTERFACE $DEVICE - mwan3_set_iface_hotplug_state $INTERFACE "$binary_status" - - mwan3_get_src_ip src_ip "$TRUE_INTERFACE" - if [ -n "${trackpid}" ]; then - device_pid=$(pgrep -f "mwan3track $INTERFACE $DEVICE ") - if [ "$device_pid" = "$trackpid" ]; then - [ "$ACTION" = ifup ] && kill -USR2 "$trackpid" - else - mwan3_track $INTERFACE $DEVICE "$binary_status" "$src_ip" - LOG notice "Restarted tracker [$!] on interface $INTERFACE (${DEVICE:-unknown})" - fi - else - mwan3_track $INTERFACE $DEVICE "$binary_status" "$src_ip" - LOG notice "Started tracker [$!] on interface $INTERFACE (${DEVICE:-unknown})" + mwan3_set_iface_hotplug_state $INTERFACE "$status" + if [ "$MWAN3_STARTUP" != 1 ]; then + mwan3_create_iface_route $INTERFACE $DEVICE + [ "$status" = "online" ] && mwan3_set_policies_iptables fi - [ "$MWAN3_STARTUP" != 1 ] && [ "$binary_status" == "online" ] && mwan3_set_policies_iptables - - ;; - ifdown|disconnected) + [ "$ACTION" = ifup ] && procd_running mwan3 "track_$INTERFACE" && procd_send_signal mwan3 "track_$INTERFACE" USR2 + ;; + disconnected) + mwan3_set_iface_hotplug_state $INTERFACE "offline" + mwan3_set_policies_iptables + ;; + ifdown) mwan3_set_iface_hotplug_state $INTERFACE "offline" mwan3_delete_iface_ipset_entries $INTERFACE mwan3_delete_iface_rules $INTERFACE mwan3_delete_iface_route $INTERFACE mwan3_delete_iface_iptables $INTERFACE - if [ "$ACTION" = "ifdown" ]; then - [ -n "$trackpid" ] && kill -USR1 "$trackpid" - fi + procd_running mwan3 "track_$INTERFACE" && procd_send_signal mwan3 "track_$INTERFACE" USR1 mwan3_set_policies_iptables ;; esac diff --git a/net/mwan3/files/etc/hotplug.d/iface/16-mwan3-user b/net/mwan3/files/etc/hotplug.d/iface/16-mwan3-user index 2ec5c79a3..698fe0909 100644 --- a/net/mwan3/files/etc/hotplug.d/iface/16-mwan3-user +++ b/net/mwan3/files/etc/hotplug.d/iface/16-mwan3-user @@ -4,22 +4,22 @@ . /lib/functions.sh . /lib/mwan3/mwan3.sh - [ "$MWAN3_STARTUP" = 1 ] || mwan3_lock "$ACTION" "$DEVICE-user" + [ "$MWAN3_SHUTDOWN" != 1 ] && mwan3_lock "$ACTION" "$DEVICE-user" - config_load mwan3 - config_get_bool enabled globals 'enabled' '0' - [ "${enabled}" -gt 0 ] || { - [ "$MWAN3_STARTUP" = 1 ] || mwan3_unlock "$ACTION" "$DEVICE-user" + [ "$MWAN3_SHUTDOWN" != 1 ] && ! /etc/init.d/mwan3 running && { + mwan3_unlock "$ACTION" "$DEVICE-user" exit 0 } + config_load mwan3 + config_get_bool enabled "$INTERFACE" enabled 0 [ "${enabled}" -eq 1 ] || { - [ "$MWAN3_STARTUP" = 1 ] || mwan3_unlock "$ACTION" "$DEVICE-user" + [ "$MWAN3_SHUTDOWN" != 1 ] && mwan3_unlock "$ACTION" "$DEVICE-user" exit 0 } - [ "$MWAN3_STARTUP" = 1 ] || mwan3_unlock "$ACTION" "$DEVICE-user" + [ "$MWAN3_SHUTDOWN" != 1 ] && mwan3_unlock "$ACTION" "$DEVICE-user" env -i ACTION="$ACTION" INTERFACE="$INTERFACE" DEVICE="$DEVICE" \ /bin/sh /etc/mwan3.user diff --git a/net/mwan3/files/etc/init.d/mwan3 b/net/mwan3/files/etc/init.d/mwan3 index ba9d920cb..595fabf77 100755 --- a/net/mwan3/files/etc/init.d/mwan3 +++ b/net/mwan3/files/etc/init.d/mwan3 @@ -1,31 +1,114 @@ #!/bin/sh /etc/rc.common +. /lib/functions.sh +. /lib/mwan3/common.sh +. /lib/functions/network.sh +. /lib/mwan3/mwan3.sh + START=19 USE_PROCD=1 -boot() { - . /lib/config/uci.sh - # disabled until mwan3 start runs so hotplug scripts - # do not start prematurely - uci_toggle_state mwan3 globals enabled "0" - rc_procd start_service +service_running() { + [ -d "$MWAN3_STATUS_DIR" ] } -# FIXME -# fd 1000 is an inherited lock file descriptor for preventing concurrent -# init script executions. Close it here to prevent the mwan3 daemon from -# inheriting it further to avoid holding the lock indefinitely. +start_tracker() { + local enabled interface + interface=$1 + config_get_bool enabled $interface 'enabled' '0' + [ $enabled -eq 0 ] && return -reload_service() { - /usr/sbin/mwan3 restart 1000>&- + procd_open_instance "track_${1}" + procd_set_param command /usr/sbin/mwan3track $interface + procd_set_param respawn + procd_close_instance } start_service() { - /usr/sbin/mwan3 start 1000>&- + local enabled hotplug_pids + + config_load mwan3 + mwan3_init + config_foreach start_tracker interface + + mwan3_lock "command" "mwan3" + + mwan3_update_iface_to_table + mwan3_set_connected_ipset + mwan3_set_custom_ipset + mwan3_set_general_rules + mwan3_set_general_iptables + config_foreach mwan3_ifup interface 1 + wait $hotplug_pids + mwan3_set_policies_iptables + mwan3_set_user_rules + + mwan3_unlock "command" "mwan3" + + procd_open_instance rtmon_ipv4 + procd_set_param command /usr/sbin/mwan3rtmon ipv4 + procd_set_param respawn + procd_close_instance + + if command -v ip6tables > /dev/null; then + procd_open_instance rtmon_ipv6 + procd_set_param command /usr/sbin/mwan3rtmon ipv6 + procd_set_param respawn + procd_close_instance + fi } stop_service() { - /usr/sbin/mwan3 stop 1000>&- + local ipset rule IP IPTR IPT family table tid + + mwan3_lock "command" "mwan3" + + config_load mwan3 + mwan3_init + config_foreach mwan3_interface_shutdown interface + + for family in ipv4 ipv6; do + if [ "$family" = "ipv4" ]; then + IPT="$IPT4" + IPTR="$IPT4R" + IP="$IP4" + elif [ "$family" = "ipv6" ]; then + [ $NO_IPV6 -ne 0 ] && continue + IPT="$IPT6" + IPTR="$IPT6R" + IP="$IP6" + fi + + for tid in $(ip route list table all | sed -ne 's/.*table \([0-9]\+\).*/\1/p' | sort -u); do + [ $tid -gt $MWAN3_INTERFACE_MAX ] && continue + $IP route flush table $tid &> /dev/null + done + + for rule in $($IP rule list | grep -E '^[1-3][0-9]{3}\:' | cut -d ':' -f 1); do + $IP rule del pref $rule &> /dev/null + done + table="$($IPT -S)" + { + echo "*mangle"; + [ -z "${table##*PREROUTING -j mwan3_hook*}" ] && echo "-D PREROUTING -j mwan3_hook" + [ -z "${table##*OUTPUT -j mwan3_hook*}" ] && echo "-D OUTPUT -j mwan3_hook" + echo "$table" | awk '{print "-F "$2}' | grep mwan3 | sort -u + echo "$table" | awk '{print "-X "$2}' | grep mwan3 | sort -u + echo "COMMIT" + } | $IPTR + done + + for ipset in $($IPS -n list | grep mwan3_); do + $IPS -q destroy $ipset + done + + for ipset in $($IPS -n list | grep mwan3 | grep -E '_v4|_v6'); do + $IPS -q destroy $ipset + done + + rm -rf $MWAN3_STATUS_DIR $MWAN3TRACK_STATUS_DIR + + mwan3_unlock "command" "mwan3" } service_triggers() { diff --git a/net/mwan3/files/lib/mwan3/common.sh b/net/mwan3/files/lib/mwan3/common.sh index bb26327d5..daa4b2abc 100644 --- a/net/mwan3/files/lib/mwan3/common.sh +++ b/net/mwan3/files/lib/mwan3/common.sh @@ -5,7 +5,24 @@ get_uptime() { echo "${uptime%%.*}" } +IP4="ip -4" +IP6="ip -6" SCRIPTNAME="$(basename "$0")" + +MWAN3_STATUS_DIR="/var/run/mwan3" +MWAN3TRACK_STATUS_DIR="/var/run/mwan3track" + +MWAN3_INTERFACE_MAX="" + +MMX_MASK="" +MMX_DEFAULT="" +MMX_BLACKHOLE="" +MM_BLACKHOLE="" + +MMX_UNREACHABLE="" +MM_UNREACHABLE="" +MAX_SLEEP=$(((1<<31)-1)) + LOG() { local facility=$1; shift @@ -13,5 +30,150 @@ LOG() # when this release is out of beta, the comment in the line below # should be removed [ "$facility" = "debug" ] && return - logger -t "$SCRIPTNAME[$$]" -p $facility "$*" + logger -t "${SCRIPTNAME}[$$]" -p $facility "$*" +} + +mwan3_get_true_iface() +{ + local family V + _true_iface=$2 + config_get family "$2" family ipv4 + if [ "$family" = "ipv4" ]; then + V=4 + elif [ "$family" = "ipv6" ]; then + V=6 + fi + ubus call "network.interface.${2}_${V}" status &>/dev/null && _true_iface="${2}_${V}" + export "$1=$_true_iface" +} + +mwan3_get_src_ip() +{ + local family _src_ip interface true_iface device addr_cmd default_ip IP sed_str + interface=$2 + mwan3_get_true_iface true_iface $interface + + unset "$1" + config_get family "$interface" family ipv4 + if [ "$family" = "ipv4" ]; then + addr_cmd='network_get_ipaddr' + default_ip="0.0.0.0" + sed_str='s/ *inet \([^ \/]*\).*/\1/;T; pq' + IP="$IP4" + elif [ "$family" = "ipv6" ]; then + addr_cmd='network_get_ipaddr6' + default_ip="::" + sed_str='s/ *inet6 \([^ \/]*\).* scope.*/\1/;T; pq' + IP="$IP6" + fi + + $addr_cmd _src_ip "$true_iface" + if [ -z "$_src_ip" ]; then + network_get_device device $true_iface + _src_ip=$($IP address ls dev $device 2>/dev/null | sed -ne "$sed_str") + if [ -n "$_src_ip" ]; then + LOG warn "no src $family address found from netifd for interface '$true_iface' dev '$device' guessing $_src_ip" + else + _src_ip="$default_ip" + LOG warn "no src $family address found for interface '$true_iface' dev '$device'" + fi + fi + export "$1=$_src_ip" +} + +mwan3_get_mwan3track_status() +{ + local track_ips pid + mwan3_list_track_ips() + { + track_ips="$1 $track_ips" + } + config_list_foreach "$1" track_ip mwan3_list_track_ips + + if [ -n "$track_ips" ]; then + pid="$(pgrep -f "mwan3track $1$")" + if [ -n "$pid" ]; then + if [ "$(cat /proc/"$(pgrep -P $pid)"/cmdline)" = "sleep${MAX_SLEEP}" ]; then + tracking="paused" + else + tracking="active" + fi + else + tracking="down" + fi + else + tracking="not enabled" + fi + echo "$tracking" +} + +mwan3_init() +{ + local bitcnt + local mmdefault + + [ -d $MWAN3_STATUS_DIR ] || mkdir -p $MWAN3_STATUS_DIR/iface_state + + # mwan3's MARKing mask (at least 3 bits should be set) + if [ -e "${MWAN3_STATUS_DIR}/mmx_mask" ]; then + MMX_MASK=$(cat "${MWAN3_STATUS_DIR}/mmx_mask") + MWAN3_INTERFACE_MAX=$(uci_get_state mwan3 globals iface_max) + else + config_load mwan3 + config_get MMX_MASK globals mmx_mask '0x3F00' + echo "$MMX_MASK"| tr 'A-F' 'a-f' > "${MWAN3_STATUS_DIR}/mmx_mask" + LOG debug "Using firewall mask ${MMX_MASK}" + + bitcnt=$(mwan3_count_one_bits MMX_MASK) + mmdefault=$(((1<>bit_msk)&1)) = "1" ]; then + if [ $((($1>>bit_val)&1)) = "1" ]; then + result=$((result|(1< /dev/null NO_IPV6=$? @@ -43,14 +32,15 @@ mwan3_push_update() # helper function to build an update string to pass on to # IPTR or IPS RESTORE. Modifies the 'update' variable in # the local scope. - update="$update -$*"; + update="$update"$'\n'"$*"; } mwan3_update_dev_to_table() { local _tid + # shellcheck disable=SC2034 mwan3_dev_tbl_ipv4=" " + # shellcheck disable=SC2034 mwan3_dev_tbl_ipv6=" " update_table() @@ -81,25 +71,11 @@ mwan3_update_iface_to_table() config_foreach update_table interface } -mwan3_get_true_iface() -{ - local family V - _true_iface=$2 - config_get family "$iface" family ipv4 - if [ "$family" = "ipv4" ]; then - V=4 - elif [ "$family" = "ipv6" ]; then - V=6 - fi - ubus call "network.interface.${iface}_${V}" status &>/dev/null && _true_iface="${iface}_${V}" - export "$1=$_true_iface" -} - mwan3_route_line_dev() { # must have mwan3 config already loaded # arg 1 is route device - local _tid route_line route_device route_family entry curr_table + local _tid route_line route_device route_family entry curr_table route_line=$2 route_family=$3 route_device=$(echo "$route_line" | sed -ne "s/.*dev \([^ ]*\).*/\1/p") @@ -130,63 +106,6 @@ mwan3_count_one_bits() echo $count } -# maps the 1st parameter so it only uses the bits allowed by the bitmask (2nd parameter) -# which means spreading the bits of the 1st parameter to only use the bits that are set to 1 in the 2nd parameter -# 0 0 0 0 0 1 0 1 (0x05) 1st parameter -# 1 0 1 0 1 0 1 0 (0xAA) 2nd parameter -# 1 0 1 result -mwan3_id2mask() -{ - local bit_msk bit_val result - bit_val=0 - result=0 - for bit_msk in $(seq 0 31); do - if [ $((($2>>bit_msk)&1)) = "1" ]; then - if [ $((($1>>bit_val)&1)) = "1" ]; then - result=$((result|(1< "${MWAN3_STATUS_DIR}/mmx_mask" - LOG debug "Using firewall mask ${MMX_MASK}" - - bitcnt=$(mwan3_count_one_bits MMX_MASK) - mmdefault=$(((1<&1) || LOG error "set_connected_ipv6: $error" +} + +mwan3_set_connected_ipset() +{ + local error + local update="" mwan3_push_update -! create mwan3_connected list:set mwan3_push_update flush mwan3_connected - mwan3_push_update -! add mwan3_connected mwan3_connected_v4 - [ $NO_IPV6 -eq 0 ] && mwan3_push_update -! add mwan3_connected mwan3_connected_v6 mwan3_push_update -! create mwan3_dynamic_v4 hash:net mwan3_push_update -! add mwan3_connected mwan3_dynamic_v4 - [ $NO_IPV6 -eq 0 ] && mwan3_push_update -! create mwan3_dynamic_v6 hash:net family inet6 - [ $NO_IPV6 -eq 0 ] && mwan3_push_update -! add mwan3_connected mwan3_dynamic_v6 - error=$(echo "$update" | $IPS restore 2>&1) || LOG error "set_connected_iptables: $error" + if [ $NO_IPV6 -eq 0 ]; then + mwan3_push_update -! create mwan3_dynamic_v6 hash:net family inet6 + mwan3_push_update -! add mwan3_connected mwan3_dynamic_v6 + fi + + error=$(echo "$update" | $IPS restore 2>&1) || LOG error "set_connected_ipset: $error" } mwan3_set_general_rules() @@ -336,12 +241,12 @@ mwan3_set_general_rules() for IP in "$IP4" "$IP6"; do [ "$IP" = "$IP6" ] && [ $NO_IPV6 -ne 0 ] && continue - RULE_NO=$(($MM_BLACKHOLE+2000)) + RULE_NO=$((MM_BLACKHOLE+2000)) if [ -z "$($IP rule list | awk -v var="$RULE_NO:" '$1 == var')" ]; then $IP rule add pref $RULE_NO fwmark $MMX_BLACKHOLE/$MMX_MASK blackhole fi - RULE_NO=$(($MM_UNREACHABLE+2000)) + RULE_NO=$((MM_UNREACHABLE+2000)) if [ -z "$($IP rule list | awk -v var="$RULE_NO:" '$1 == var')" ]; then $IP rule add pref $RULE_NO fwmark $MMX_UNREACHABLE/$MMX_MASK unreachable fi @@ -353,7 +258,7 @@ mwan3_set_general_iptables() local IPT current update error for IPT in "$IPT4" "$IPT6"; do [ "$IPT" = "$IPT6" ] && [ $NO_IPV6 -ne 0 ] && continue - current="$($IPT -S)" + current="$($IPT -S)"$'\n' update="*mangle" if [ -n "${current##*-N mwan3_ifaces_in*}" ]; then mwan3_push_update -N mwan3_ifaces_in @@ -395,15 +300,10 @@ mwan3_set_general_iptables() -p ipv6-icmp \ -m icmp6 --icmpv6-type 137 \ -j RETURN - # do not mangle outgoing echo request - mwan3_push_update -A mwan3_hook \ - -m set --match-set mwan3_source_v6 src \ - -p ipv6-icmp \ - -m icmp6 --icmpv6-type 128 \ - -j RETURN fi mwan3_push_update -A mwan3_hook \ + -m mark --mark 0x0/$MMX_MASK \ -j CONNMARK --restore-mark --nfmask "$MMX_MASK" --ctmask "$MMX_MASK" mwan3_push_update -A mwan3_hook \ -m mark --mark 0x0/$MMX_MASK \ @@ -439,7 +339,7 @@ mwan3_set_general_iptables() mwan3_create_iface_iptables() { - local id family connected_name IPT IPTR current update error + local id family IPT IPTR current update error config_get family "$1" family ipv4 mwan3_get_iface_id id "$1" @@ -447,26 +347,22 @@ mwan3_create_iface_iptables() [ -n "$id" ] || return 0 if [ "$family" = "ipv4" ]; then - connected_name=mwan3_connected IPT="$IPT4" IPTR="$IPT4R" - $IPS -! create $connected_name list:set - elif [ "$family" = "ipv6" ] && [ $NO_IPV6 -eq 0 ]; then - connected_name=mwan3_connected_v6 IPT="$IPT6" IPTR="$IPT6R" - $IPS -! create $connected_name hash:net family inet6 else return fi - current="$($IPT -S)" + + current="$($IPT -S)"$'\n' update="*mangle" if [ -n "${current##*-N mwan3_ifaces_in*}" ]; then mwan3_push_update -N mwan3_ifaces_in fi - if [ -n "${current##*-N mwan3_iface_in_$1*}" ]; then + if [ -n "${current##*-N mwan3_iface_in_$1$'\n'*}" ]; then mwan3_push_update -N "mwan3_iface_in_$1" else mwan3_push_update -F "mwan3_iface_in_$1" @@ -474,23 +370,23 @@ mwan3_create_iface_iptables() mwan3_push_update -A "mwan3_iface_in_$1" \ -i "$2" \ - -m set --match-set $connected_name src \ - -m mark --mark 0x0/$MMX_MASK \ + -m set --match-set mwan3_connected src \ + -m mark --mark "0x0/$MMX_MASK" \ -m comment --comment "default" \ - -j MARK --set-xmark $MMX_DEFAULT/$MMX_MASK + -j MARK --set-xmark "$MMX_DEFAULT/$MMX_MASK" mwan3_push_update -A "mwan3_iface_in_$1" \ -i "$2" \ - -m mark --mark 0x0/$MMX_MASK \ + -m mark --mark "0x0/$MMX_MASK" \ -m comment --comment "$1" \ - -j MARK --set-xmark $(mwan3_id2mask id MMX_MASK)/$MMX_MASK + -j MARK --set-xmark "$(mwan3_id2mask id MMX_MASK)/$MMX_MASK" - if [ -n "${current##*-A mwan3_ifaces_in -m mark --mark 0x0/$MMX_MASK -j mwan3_iface_in_${1}*}" ]; then + if [ -n "${current##*-A mwan3_ifaces_in -m mark --mark 0x0/$MMX_MASK -j mwan3_iface_in_${1}$'\n'*}" ]; then mwan3_push_update -A mwan3_ifaces_in \ -m mark --mark 0x0/$MMX_MASK \ -j "mwan3_iface_in_$1" - LOG debug "create_iface_iptables: mwan3_iface_in_$1 not in iptables, adding" + LOG debug "create_iface_iptables: mwan3_iface_in_$1 not in iptables, adding" else - LOG debug "create_iface_iptables: mwan3_iface_in_$1 already in iptables, skip" + LOG debug "create_iface_iptables: mwan3_iface_in_$1 already in iptables, skip" fi mwan3_push_update COMMIT @@ -521,45 +417,17 @@ mwan3_delete_iface_iptables() } -mwan3_create_iface_route() +mwan3_get_routes() { - local id via metric V V_ IP family - local iface device cmd true_iface - - iface=$1 - device=$2 - config_get family "$iface" family ipv4 - mwan3_get_iface_id id "$iface" - - [ -n "$id" ] || return 0 - - mwan3_get_true_iface true_iface $iface - if [ "$family" = "ipv4" ]; then - V_="" - IP="$IP4" - elif [ "$family" = "ipv6" ]; then - V_=6 - IP="$IP6" - fi - - network_get_gateway${V_} via "$true_iface" - - { [ -z "$via" ] || [ "$via" = "0.0.0.0" ] || [ "$via" = "::" ] ; } && unset via - - network_get_metric metric "$true_iface" - - $IP route flush table "$id" - cmd="$IP route add table $id default \ - ${via:+via} $via \ - ${metric:+metric} $metric \ - dev $2" - $cmd || LOG warn "ip cmd failed $cmd" - + local source_routing + config_get_bool source_routing globals source_routing 0 + [ $source_routing -eq 0 ] && unset source_routing + $IP route list table main | sed -ne "/^linkdown/T; s/expires \([0-9]\+\)sec//;s/error [0-9]\+//; ${source_routing:+s/default\(.*\) from [^ ]*/default\1/;} p" | uniq } -mwan3_add_non_default_iface_route() +mwan3_create_iface_route() { - local tid route_line family IP id + local tid route_line family IP id tbl config_get family "$1" family ipv4 mwan3_get_iface_id id "$1" @@ -571,10 +439,15 @@ mwan3_add_non_default_iface_route() IP="$IP6" fi + tbl=$($IP route list table $id 2>/dev/null)$'\n' mwan3_update_dev_to_table - $IP route list table main | grep -v "^default\|linkdown\|^::/0\|^fe80::/64\|^unreachable" | while read route_line; do + mwan3_get_routes | while read -r route_line; do mwan3_route_line_dev "tid" "$route_line" "$family" + { [ -z "${route_line##default*}" ] || [ -z "${route_line##fe80::/64*}" ]; } && [ "$tid" != "$id" ] && continue if [ -z "$tid" ] || [ "$tid" = "$id" ]; then + # possible that routes are already in the table + # if 'connected' was called after 'ifup' + [ -n "$tbl" ] && [ -z "${tbl##*$route_line$'\n'*}" ] && continue $IP route add table $id $route_line || LOG warn "failed to add $route_line to table $id" fi @@ -582,63 +455,21 @@ mwan3_add_non_default_iface_route() done } -mwan3_add_all_nondefault_routes() -{ - local tid IP route_line ipv family active_tbls tid - - add_active_tbls() - { - let tid++ - config_get family "$1" family ipv4 - [ "$family" != "$ipv" ] && return - $IP route list table $tid 2>/dev/null | grep -q "^default\|^::/0" && { - active_tbls="$active_tbls${tid} " - } - } - - add_route() - { - let tid++ - [ -n "${active_tbls##* $tid *}" ] && return - $IP route add table $tid $route_line || - LOG warn "failed to add $route_line to table $tid" - } - - mwan3_update_dev_to_table - for ipv in ipv4 ipv6; do - [ "$ipv" = "ipv6" ] && [ $NO_IPV6 -ne 0 ] && continue - if [ "$ipv" = "ipv4" ]; then - IP="$IP4" - elif [ "$ipv" = "ipv6" ]; then - IP="$IP6" - fi - tid=0 - active_tbls=" " - config_foreach add_active_tbls interface - $IP route list table main | grep -v "^default\|linkdown\|^::/0\|^fe80::/64\|^unreachable" | while read route_line; do - mwan3_route_line_dev "tid" "$route_line" "$ipv" - if [ -n "$tid" ]; then - $IP route add table $tid $route_line - else - config_foreach add_route interface - fi - done - done -} mwan3_delete_iface_route() { - local id + local id family config_get family "$1" family ipv4 mwan3_get_iface_id id "$1" - [ -n "$id" ] || return 0 + if [ -z "$id" ]; then + LOG warn "delete_iface_route: could not find table id for interface $1" + return 0 + fi if [ "$family" = "ipv4" ]; then $IP4 route flush table "$id" - fi - - if [ "$family" = "ipv6" ] && [ $NO_IPV6 -eq 0 ]; then + elif [ "$family" = "ipv6" ] && [ $NO_IPV6 -eq 0 ]; then $IP6 route flush table "$id" fi } @@ -660,21 +491,16 @@ mwan3_create_iface_rules() return fi - while [ -n "$($IP rule list | awk '$1 == "'$(($id+1000)):'"')" ]; do - $IP rule del pref $(($id+1000)) - done - - while [ -n "$($IP rule list | awk '$1 == "'$(($id+2000)):'"')" ]; do - $IP rule del pref $(($id+2000)) - done + mwan3_delete_iface_rules "$1" - $IP rule add pref $(($id+1000)) iif "$2" lookup "$id" - $IP rule add pref $(($id+2000)) fwmark $(mwan3_id2mask id MMX_MASK)/$MMX_MASK lookup "$id" + $IP rule add pref $((id+1000)) iif "$2" lookup "$id" + $IP rule add pref $((id+2000)) fwmark "$(mwan3_id2mask id MMX_MASK)/$MMX_MASK" lookup "$id" + $IP rule add pref $((id+3000)) fwmark "$(mwan3_id2mask id MMX_MASK)/$MMX_MASK" unreachable } mwan3_delete_iface_rules() { - local id family + local id family IP rule_id config_get family "$1" family ipv4 mwan3_get_iface_id id "$1" @@ -689,12 +515,8 @@ mwan3_delete_iface_rules() return fi - while [ -n "$($IP rule list | awk '$1 == "'$(($id+1000)):'"')" ]; do - $IP rule del pref $(($id+1000)) - done - - while [ -n "$($IP rule list | awk '$1 == "'$(($id+2000)):'"')" ]; do - $IP rule del pref $(($id+2000)) + for rule_id in $(ip rule list | awk '$1 % 1000 == '$id' && $1 > 1000 && $1 < 4000 {print substr($1,0,4)}'); do + $IP rule del pref $rule_id done } @@ -713,39 +535,6 @@ mwan3_delete_iface_ipset_entries() done } -mwan3_rtmon() -{ - local protocol - for protocol in "ipv4" "ipv6"; do - pid="$(pgrep -f "mwan3rtmon $protocol")" - [ "$protocol" = "ipv6" ] && [ $NO_IPV6 -ne 0 ] && continue - if [ "${pid}" = "" ]; then - [ -x /usr/sbin/mwan3rtmon ] && /usr/sbin/mwan3rtmon $protocol & - fi - done -} - -mwan3_track() -{ - local track_ips pids - - mwan3_list_track_ips() - { - track_ips="$track_ips $1" - } - config_list_foreach "$1" track_ip mwan3_list_track_ips - - # don't match device in case it changed from last launch - if pids=$(pgrep -f "mwan3track $1 "); then - kill -TERM $pids > /dev/null 2>&1 - sleep 1 - kill -KILL $(pgrep -f "mwan3track $1 ") > /dev/null 2>&1 - fi - - if [ -n "$track_ips" ]; then - [ -x /usr/sbin/mwan3track ] && MWAN3_STARTUP=0 /usr/sbin/mwan3track "$1" "$2" "$3" "$4" $track_ips & - fi -} mwan3_set_policy() { @@ -776,7 +565,7 @@ mwan3_set_policy() IPT="$IPT6" IPTR="$IPT6R" fi - current="$($IPT -S)" + current="$($IPT -S)"$'\n' update="*mangle" if [ "$family" = "ipv4" ] && [ $is_offline -eq 0 ]; then @@ -785,7 +574,7 @@ mwan3_set_policy() total_weight_v4=$weight lowest_metric_v4=$metric elif [ "$metric" -eq "$lowest_metric_v4" ]; then - total_weight_v4=$(($total_weight_v4+$weight)) + total_weight_v4=$((total_weight_v4+weight)) total_weight=$total_weight_v4 else return @@ -796,7 +585,7 @@ mwan3_set_policy() total_weight_v6=$weight lowest_metric_v6=$metric elif [ "$metric" -eq "$lowest_metric_v6" ]; then - total_weight_v6=$(($total_weight_v6+$weight)) + total_weight_v6=$((total_weight_v6+weight)) total_weight=$total_weight_v6 else return @@ -807,9 +596,9 @@ mwan3_set_policy() mwan3_push_update -A "mwan3_policy_$policy" \ -m mark --mark 0x0/$MMX_MASK \ -m comment --comment \"$iface $weight $weight\" \ - -j MARK --set-xmark $(mwan3_id2mask id MMX_MASK)/$MMX_MASK + -j MARK --set-xmark "$(mwan3_id2mask id MMX_MASK)/$MMX_MASK" elif [ $is_offline -eq 0 ]; then - probability=$(($weight*1000/$total_weight)) + probability=$((weight*1000/total_weight)) if [ "$probability" -lt 10 ]; then probability="0.00$probability" elif [ $probability -lt 100 ]; then @@ -826,7 +615,7 @@ mwan3_set_policy() --mode random \ --probability "$probability" \ -m comment --comment \"$iface $weight $total_weight\" \ - -j MARK --set-xmark $(mwan3_id2mask id MMX_MASK)/$MMX_MASK + -j MARK --set-xmark "$(mwan3_id2mask id MMX_MASK)/$MMX_MASK" elif [ -n "$device" ]; then echo "$current" | grep -q "^-A mwan3_policy_$policy.*--comment .* [0-9]* [0-9]*" || mwan3_push_update -I "mwan3_policy_$policy" \ @@ -855,10 +644,10 @@ mwan3_create_policies_iptables() for IPT in "$IPT4" "$IPT6"; do [ "$IPT" = "$IPT6" ] && [ $NO_IPV6 -ne 0 ] && continue - current="$($IPT -S)" + current="$($IPT -S)"$'\n' update="*mangle" - if [ -n "${current##*-N mwan3_policy_$1*}" ]; then - mwan3_push_update -N "mwan3_policy_$1" + if [ -n "${current##*-N mwan3_policy_$1$'\n'*}" ]; then + mwan3_push_update -N "mwan3_policy_$1" fi mwan3_push_update -F "mwan3_policy_$1" @@ -915,14 +704,14 @@ mwan3_set_sticky_iptables() mwan3_get_iface_id id "$1" [ -n "$id" ] || return 0 - if [ -z "${current##*-N mwan3_iface_in_$1*}" ]; then + if [ -z "${current##*-N mwan3_iface_in_$1$'\n'*}" ]; then mwan3_push_update -I "mwan3_rule_$rule" \ - -m mark --mark $(mwan3_id2mask id MMX_MASK)/$MMX_MASK \ + -m mark --mark "$(mwan3_id2mask id MMX_MASK)/$MMX_MASK" \ -m set ! --match-set "mwan3_sticky_$rule" src,src \ - -j MARK --set-xmark 0x0/$MMX_MASK + -j MARK --set-xmark "0x0/$MMX_MASK" mwan3_push_update -I "mwan3_rule_$rule" \ - -m mark --mark 0/$MMX_MASK \ - -j MARK --set-xmark $(mwan3_id2mask id MMX_MASK)/$MMX_MASK + -m mark --mark "0/$MMX_MASK" \ + -j MARK --set-xmark "$(mwan3_id2mask id MMX_MASK)/$MMX_MASK" fi fi done @@ -932,7 +721,7 @@ mwan3_set_user_iptables_rule() { local ipset family proto policy src_ip src_port src_iface src_dev local sticky dest_ip dest_port use_policy timeout policy - local global_logging rule_logging loglevel rule_policy rule ipv + local global_logging rule_logging loglevel rule_policy rule ipv rule="$1" ipv="$2" @@ -952,6 +741,18 @@ mwan3_set_user_iptables_rule() config_get global_logging globals logging 0 config_get loglevel globals loglevel notice + [ "$ipv" = "ipv6" ] && [ $NO_IPV6 -ne 0 ] && return + [ "$family" = "ipv4" ] && [ "$ipv" = "ipv6" ] && return + [ "$family" = "ipv6" ] && [ "$ipv" = "ipv4" ] && return + + for ipaddr in "$src_ip" "$dest_ip"; do + if [ -n "$ipaddr" ] && { { [ "$ipv" = "ipv4" ] && echo "$ipaddr" | grep -qE "$IPv6_REGEX"; } || + { [ "$ipv" = "ipv6" ] && echo "$ipaddr" | grep -qE $IPv4_REGEX; } }; then + LOG warn "invalid $ipv address $ipaddr specified for rule $rule" + return + fi + done + if [ -n "$src_iface" ]; then network_get_device src_dev "$src_iface" if [ -z "$src_dev" ]; then @@ -963,9 +764,9 @@ mwan3_set_user_iptables_rule() [ -z "$dest_ip" ] && unset dest_ip [ -z "$src_ip" ] && unset src_ip [ -z "$ipset" ] && unset ipset - [ -z "$src_port" ] && unset src_port - [ -z "$dest_port" ] && unset dest_port - if [ "$proto" != 'tcp' ] && [ "$proto" != 'udp' ]; then + [ -z "$src_port" ] && unset src_port + [ -z "$dest_port" ] && unset dest_port + if [ "$proto" != 'tcp' ] && [ "$proto" != 'udp' ]; then [ -n "$src_port" ] && { LOG warn "src_port set to '$src_port' but proto set to '$proto' not tcp or udp. src_port will be ignored" } @@ -1013,16 +814,12 @@ mwan3_set_user_iptables_rule() fi fi - [ "$ipv" = "ipv6" ] && [ $NO_IPV6 -ne 0 ] && return - [ "$family" = "ipv4" ] && [ "$ipv" = "ipv6" ] && return - [ "$family" = "ipv6" ] && [ "$ipv" = "ipv4" ] && return - - if [ $rule_policy -eq 1 ] && [ -n "${current##*-N $policy*}" ]; then + if [ $rule_policy -eq 1 ] && [ -n "${current##*-N $policy$'\n'*}" ]; then mwan3_push_update -N "$policy" fi if [ $rule_policy -eq 1 ] && [ "$sticky" -eq 1 ]; then - if [ -n "${current##*-N mwan3_rule_$1*}" ]; then + if [ -n "${current##*-N mwan3_rule_$1$'\n'*}" ]; then mwan3_push_update -N "mwan3_rule_$1" fi @@ -1117,7 +914,7 @@ mwan3_set_user_rules() fi [ "$ipv" = "ipv6" ] && [ $NO_IPV6 -ne 0 ] && continue update="*mangle" - current="$($IPT -S)" + current="$($IPT -S)"$'\n' if [ -n "${current##*-N mwan3_rules*}" ]; then @@ -1136,6 +933,83 @@ mwan3_set_user_rules() } +mwan3_interface_hotplug_shutdown() +{ + local interface status device ifdown + interface="$1" + ifdown="$2" + [ -f $MWAN3TRACK_STATUS_DIR/$interface/STATUS ] && { + status=$(cat $MWAN3TRACK_STATUS_DIR/$interface/STATUS) + } + + [ "$status" != "online" ] && [ "$ifdown" != 1 ] && return + + if [ "$ifdown" = 1 ]; then + env -i ACTION=ifdown \ + INTERFACE=$interface \ + DEVICE=$device \ + sh /etc/hotplug.d/iface/15-mwan3 + else + [ "$status" = "online" ] && { + env -i MWAN3_SHUTDOWN="1" \ + ACTION="disconnected" \ + INTERFACE="$interface" \ + DEVICE="$device" /sbin/hotplug-call iface + } + fi + +} + +mwan3_interface_shutdown() +{ + mwan3_interface_hotplug_shutdown $1 + mwan3_track_clean $1 +} + +mwan3_ifup() +{ + local up l3_device status interface true_iface mwan3_startup + + interface=$1 + mwan3_startup=$2 + + if [ "${mwan3_startup}" != 1 ]; then + # It is not necessary to obtain a lock here, because it is obtained in the hotplug + # script, but we still want to do the check to print a useful error message + /etc/init.d/mwan3 running || { + echo 'The service mwan3 is global disabled.' + echo 'Please execute "/etc/init.d/mwan3 start" first.' + exit 1 + } + config_load mwan3 + fi + mwan3_get_true_iface true_iface $interface + status=$(ubus -S call network.interface.$true_iface status) + + [ -n "$status" ] && { + json_load "$status" + json_get_vars up l3_device + } + hotplug_startup() + { + env -i MWAN3_STARTUP=$mwan3_startup ACTION=ifup \ + INTERFACE=$interface DEVICE=$l3_device \ + sh /etc/hotplug.d/iface/15-mwan3 + } + + if [ "$up" != "1" ] || [ -z "$l3_device" ]; then + return + fi + + if [ "${mwan3_startup}" = 1 ]; then + hotplug_startup & + hotplug_pids="$hotplug_pids $!" + else + hotplug_startup + fi + +} + mwan3_set_iface_hotplug_state() { local iface=$1 local state=$2 @@ -1151,7 +1025,7 @@ mwan3_get_iface_hotplug_state() { mwan3_report_iface_status() { - local device result track_ips tracking IP IPT + local device result tracking IP IPT mwan3_get_iface_id id "$1" network_get_device device "$1" @@ -1170,8 +1044,9 @@ mwan3_report_iface_status() if [ -z "$id" ] || [ -z "$device" ]; then result="offline" - elif [ -n "$($IP rule | awk '$1 == "'$(($id+1000)):'"')" ] && \ - [ -n "$($IP rule | awk '$1 == "'$(($id+2000)):'"')" ] && \ + elif [ -n "$($IP rule | awk '$1 == "'$((id+1000)):'"')" ] && \ + [ -n "$($IP rule | awk '$1 == "'$((id+2000)):'"')" ] && \ + [ -n "$($IP rule | awk '$1 == "'$((id+3000)):'"')" ] && \ [ -n "$($IPT -S mwan3_iface_in_$1 2> /dev/null)" ] && \ [ -n "$($IP route list table $id default dev $device 2> /dev/null)" ]; then json_init @@ -1183,11 +1058,12 @@ mwan3_report_iface_status() json_get_vars online uptime json_select .. json_select .. - online="$(printf '%02dh:%02dm:%02ds\n' $(($online/3600)) $(($online%3600/60)) $(($online%60)))" - uptime="$(printf '%02dh:%02dm:%02ds\n' $(($uptime/3600)) $(($uptime%3600/60)) $(($uptime%60)))" + online="$(printf '%02dh:%02dm:%02ds\n' $((online/3600)) $((online%3600/60)) $((online%60)))" + uptime="$(printf '%02dh:%02dm:%02ds\n' $((uptime/3600)) $((uptime%3600/60)) $((uptime%60)))" result="$(mwan3_get_iface_hotplug_state $1) $online, uptime $uptime" - elif [ -n "$($IP rule | awk '$1 == "'$(($id+1000)):'"')" ] || \ - [ -n "$($IP rule | awk '$1 == "'$(($id+2000)):'"')" ] || \ + elif [ -n "$($IP rule | awk '$1 == "'$((id+1000)):'"')" ] || \ + [ -n "$($IP rule | awk '$1 == "'$((id+2000)):'"')" ] || \ + [ -n "$($IP rule | awk '$1 == "'$((id+3000)):'"')" ] || \ [ -n "$($IPT -S mwan3_iface_in_$1 2> /dev/null)" ] || \ [ -n "$($IP route list table $id default dev $device 2> /dev/null)" ]; then result="error" @@ -1197,22 +1073,7 @@ mwan3_report_iface_status() result="disabled" fi - mwan3_list_track_ips() - { - track_ips="$1 $track_ips" - } - config_list_foreach "$1" track_ip mwan3_list_track_ips - - if [ -n "$track_ips" ]; then - if [ -n "$(pgrep -f "mwan3track $1 $device")" ]; then - tracking="active" - else - tracking="down" - fi - else - tracking="not enabled" - fi - + tracking="$(mwan3_get_mwan3track_status $1)" echo " interface $1 is $result and tracking is $tracking" } @@ -1225,10 +1086,10 @@ mwan3_report_policies() total_weight=$($ipt -S "$policy" | grep -v '.*--comment "out .*" .*$' | cut -s -d'"' -f2 | head -1 | awk '{print $3}') - if [ ! -z "${total_weight##*[!0-9]*}" ]; then + if [ -n "${total_weight##*[!0-9]*}" ]; then for iface in $($ipt -S "$policy" | grep -v '.*--comment "out .*" .*$' | cut -s -d'"' -f2 | awk '{print $1}'); do weight=$($ipt -S "$policy" | grep -v '.*--comment "out .*" .*$' | cut -s -d'"' -f2 | awk '$1 == "'$iface'"' | awk '{print $2}') - percent=$(($weight*100/$total_weight)) + percent=$((weight*100/total_weight)) echo " $iface ($percent%)" done else @@ -1306,6 +1167,6 @@ mwan3_flush_conntrack() mwan3_track_clean() { - rm -rf "$MWAN3_STATUS_DIR/${1}" &> /dev/null + rm -rf "${MWAN3_STATUS_DIR:?}/${1}" &> /dev/null rmdir --ignore-fail-on-non-empty "$MWAN3_STATUS_DIR" } diff --git a/net/mwan3/files/usr/libexec/rpcd/mwan3 b/net/mwan3/files/usr/libexec/rpcd/mwan3 index 33e3e0284..76f557e9f 100755 --- a/net/mwan3/files/usr/libexec/rpcd/mwan3 +++ b/net/mwan3/files/usr/libexec/rpcd/mwan3 @@ -77,16 +77,13 @@ get_mwan3_status() { local online=0 local offline=0 local up="0" - local enabled pid device time_p time_n time_u time_d status + local enabled device time_p time_n time_u time_d status track_status network_get_device device $1 if [ "${iface}" = "${iface_select}" ] || [ "${iface_select}" = "" ]; then - pid="$(pgrep -f "mwan3track $iface $device")" - if [ "${pid}" != "" ]; then - running="1" - fi - + track_status="$(mwan3_get_mwan3track_status "$1")" + [ "$track_status" = "active" ] && running="1" time_p="$(cat "$MWAN3TRACK_STATUS_DIR/${iface}/TIME")" [ -z "${time_p}" ] || { time_n="$(get_uptime)" diff --git a/net/mwan3/files/usr/sbin/mwan3 b/net/mwan3/files/usr/sbin/mwan3 index fd6b5204f..0251607a0 100755 --- a/net/mwan3/files/usr/sbin/mwan3 +++ b/net/mwan3/files/usr/sbin/mwan3 @@ -12,39 +12,37 @@ help() Syntax: mwan3 [command] Available commands: - start Load iptables rules, ip rules and ip routes - stop Unload iptables rules, ip rules and ip routes - restart Reload iptables rules, ip rules and ip routes - ifup Load rules and routes for specific interface - ifdown Unload rules and routes for specific interface - interfaces Show interfaces status - policies Show currently active policy - connected Show directly connected networks - rules Show active rules - status Show all status - + start Load iptables rules, ip rules and ip routes + stop Unload iptables rules, ip rules and ip routes + restart Reload iptables rules, ip rules and ip routes + ifup Load rules and routes for specific interface + ifdown Unload rules and routes for specific interface + interfaces Show interfaces status + policies Show currently active policy + connected Show directly connected networks + rules Show active rules + status Show all status + use Run a command bound to and avoid mwan3 rules EOF } -ifdown() -{ + +ifdown() { if [ -z "$1" ]; then - echo "Error: Expecting interface. Usage: mwan3 ifdown " && exit 0 + echo "Error: Expecting interface. Usage: mwan3 ifdown " + exit 0 fi if [ -n "$2" ]; then - echo "Error: Too many arguments. Usage: mwan3 ifdown " && exit 0 + echo "Error: Too many arguments. Usage: mwan3 ifdown " + exit 0 fi - ACTION=ifdown INTERFACE=$1 /sbin/hotplug-call iface - - kill $(pgrep -f "mwan3track $1 ") &> /dev/null - mwan3_track_clean $1 + mwan3_interface_hotplug_shutdown "$1" 1 } -ifup() -{ - local device enabled up l3_device status interface true_iface +ifup() { + . /etc/init.d/mwan3 if [ -z "$1" ]; then echo "Expecting interface. Usage: mwan3 ifup " @@ -56,46 +54,7 @@ ifup() exit 0 fi - interface=$1 - - if [ "${MWAN3_STARTUP}" != 1 ]; then - # It is not necessary to obtain a lock here, because it is obtained in the hotplug - # script, but we still want to do the check to print a useful error message - config_load mwan3 - config_get_bool enabled globals 'enabled' 0 - - [ ${enabled} -gt 0 ] || { - echo "The service mwan3 is global disabled." - echo "Please execute \"/etc/init.d/mwan3 start\" first." - exit 1 - } - else - enabled=1 - fi - mwan3_get_true_iface true_iface $interface - status=$(ubus -S call network.interface.$true_iface status) - - [ -n "$status" ] && { - json_load "$status" - json_get_vars up l3_device - } - hotplug_startup() - { - MWAN3_STARTUP=$MWAN3_STARTUP ACTION=ifup INTERFACE=$interface DEVICE=$l3_device TRUE_INTERFACE=$true_iface sh /etc/hotplug.d/iface/15-mwan3 - MWAN3_STARTUP=$MWAN3_STARTUP ACTION=ifup INTERFACE=$interface DEVICE=$l3_device TRUE_INTERFACE=$true_iface sh /etc/hotplug.d/iface/16-mwan3-user - } - - if [ "$up" != "1" ] || [ -z "$l3_device" ] || [ "$enabled" != "1" ]; then - return - fi - - if [ "${MWAN3_STARTUP}" = 1 ]; then - hotplug_startup & - hotplug_pids="$hotplug_pids $!" - else - hotplug_startup - fi - + mwan3_ifup "$1" } interfaces() @@ -104,40 +63,40 @@ interfaces() echo "Interface status:" config_foreach mwan3_report_iface_status interface - echo -e + echo } policies() { echo "Current ipv4 policies:" mwan3_report_policies_v4 - echo -e + echo [ $NO_IPV6 -ne 0 ] && return echo "Current ipv6 policies:" mwan3_report_policies_v6 - echo -e + echo } connected() { echo "Directly connected ipv4 networks:" mwan3_report_connected_v4 - echo -e + echo [ $NO_IPV6 -ne 0 ] && return echo "Directly connected ipv6 networks:" mwan3_report_connected_v6 - echo -e + echo } rules() { echo "Active ipv4 user rules:" mwan3_report_rules_v4 - echo -e + echo [ $NO_IPV6 -ne 0 ] && return echo "Active ipv6 user rules:" mwan3_report_rules_v6 - echo -e + echo } status() @@ -148,113 +107,51 @@ status() rules } -start() -{ - local enabled hotplug_pids MWAN3_STARTUP - MWAN3_STARTUP=1 - mwan3_lock "command" "mwan3" - uci_toggle_state mwan3 globals enabled "1" - config_load mwan3 - - mwan3_update_iface_to_table - mwan3_set_connected_iptables - mwan3_set_custom_ipset - mwan3_set_general_rules - mwan3_set_general_iptables - config_foreach ifup interface - wait $hotplug_pids - mwan3_add_all_nondefault_routes - mwan3_set_policies_iptables - mwan3_set_user_rules - - - mwan3_unlock "command" "mwan3" - mwan3_rtmon - unset MWAN3_STARTUP +start() { + /etc/init.d/mwan3 enable + /etc/init.d/mwan3 start } -stop() -{ - local ipset rule IP IPTR IPT kill_pid family table tid - - mwan3_lock "command" "mwan3" - uci_toggle_state mwan3 globals enabled "0" +stop() { + /etc/init.d/mwan3 disable + /etc/init.d/mwan3 stop +} - { - kill -TERM $(pgrep -f "mwan3rtmon") > /dev/null 2>&1 - kill -TERM $(pgrep -f "mwan3track") > /dev/null 2>&1 +restart() { + /etc/init.d/mwan3 enable + /etc/init.d/mwan3 stop + /etc/init.d/mwan3 start +} - sleep 1 +wrap() { + # Run a command with the device, src_ip and fwmark set to avoid processing by mwan3 + # firewall rules - kill -KILL $(pgrep -f "mwan3rtmon") > /dev/null 2>&1 - kill -KILL $(pgrep -f "mwan3track") > /dev/null 2>&1 - } & - kill_pid=$! + local interface device src_ip family + mwan3_init config_load mwan3 - config_foreach mwan3_track_clean interface - - for family in ipv4 ipv6; do - if [ "$family" = "ipv4" ]; then - IPT="$IPT4" - IPTR="$IPT4R" - IP="$IP4" - elif [ "$family" = "ipv6" ]; then - [ $NO_IPV6 -ne 0 ] && continue - IPT="$IPT6" - IPTR="$IPT6R" - IP="$IP6" - fi - - for tid in $(ip route list table all | sed -ne 's/.*table \([0-9]\+\).*/\1/p'|sort -u); do - [ $tid -gt $MWAN3_INTERFACE_MAX ] && continue - $IP route flush table $tid &> /dev/null - done - - for rule in $($IP rule list | egrep '^[1-2][0-9]{3}\:' | cut -d ':' -f 1); do - $IP rule del pref $rule &> /dev/null - done - table="$($IPT -S)" - { - echo "*mangle"; - [ -z "${table##*PREROUTING -j mwan3_hook*}" ] && echo "-D PREROUTING -j mwan3_hook" - [ -z "${table##*OUTPUT -j mwan3_hook*}" ] && echo "-D OUTPUT -j mwan3_hook" - echo "$table" | awk '{print "-F "$2}' | grep mwan3 | sort -u - echo "$table" | awk '{print "-X "$2}' | grep mwan3 | sort -u - echo "COMMIT" - } | $IPTR - done - for ipset in $($IPS -n list | grep mwan3_); do - $IPS -q destroy $ipset - done + interface=$1 ; shift + [ -z "$*" ] && echo "no command specified for mwan3 wrap" && return + network_get_device device $interface + [ -z "$device" ] && echo "could not find device for $interface" && return - for ipset in $($IPS -n list | grep mwan3 | grep -E '_v4|_v6'); do - $IPS -q destroy $ipset - done + mwan3_get_src_ip src_ip $interface + [ -z "$src_ip" ] && echo "could not find src_ip for $interface" && return - if ! pgrep -f "mwan3track" >/dev/null && ! pgrep -f "mwan3rtmon" >/dev/null; then - # mwan3track has already exited, no need to send - # TERM signal - kill $kill_pid 2>/dev/null - else - # mwan3track has not exited, wait for the killer - # to do its work - wait $kill_pid - fi - rm -rf $MWAN3_STATUS_DIR $MWAN3TRACK_STATUS_DIR - - mwan3_unlock "command" "mwan3" + config_get family $interface family + [ -z "$family" ] && echo "could not find family for $interface. Using ipv4." && family='ipv4' -} + echo "Running '$*' with DEVICE=$device SRCIP=$src_ip FWMARK=$MMX_DEFAULT FAMILY=$family" + # shellcheck disable=SC2048 + FAMILY=$family DEVICE=$device SRCIP=$src_ip FWMARK=$MMX_DEFAULT LD_PRELOAD=/lib/mwan3/libwrap_mwan3_sockopt.so.1.0 $* -restart() { - stop - start } case "$1" in - ifup|ifdown|interfaces|policies|connected|rules|status|start|stop|restart) + ifup|ifdown|interfaces|policies|connected|rules|status|start|stop|restart|use) mwan3_init + # shellcheck disable=SC2048 $* ;; *) diff --git a/net/mwan3/files/usr/sbin/mwan3rtmon b/net/mwan3/files/usr/sbin/mwan3rtmon index 98a5c4b89..ee470837e 100755 --- a/net/mwan3/files/usr/sbin/mwan3rtmon +++ b/net/mwan3/files/usr/sbin/mwan3rtmon @@ -5,82 +5,139 @@ . /lib/mwan3/mwan3.sh . /lib/mwan3/common.sh +trap_with_arg() +{ + func="$1" ; shift + pid="$1" ; shift + for sig ; do + # shellcheck disable=SC2064 + trap "$func $sig $pid" "$sig" + done +} + +func_trap() +{ + kill -${1} ${2} 2>/dev/null +} + +mwan3_add_all_routes() +{ + local tid IP IPT route_line family active_tbls tid initial_state + local ipv=$1 + + add_active_tbls() + { + let tid++ + config_get family "$1" family ipv4 + config_get initial_state "$1" initial_state "online" + [ "$family" != "$ipv" ] && return + if $IPT -S "mwan3_iface_in_$1" &> /dev/null; then + active_tbls="$active_tbls${tid} " + fi + } + + add_route() + { + let tid++ + [ -n "${active_tbls##* $tid *}" ] && return + $IP route add table $tid $route_line || + LOG warn "failed to add $route_line to table $tid" + } + + mwan3_update_dev_to_table + [ "$ipv" = "ipv6" ] && [ $NO_IPV6 -ne 0 ] && return + if [ "$ipv" = "ipv4" ]; then + IP="$IP4" + IPT="$IPT4" + elif [ "$ipv" = "ipv6" ]; then + IP="$IP6" + IPT="$IPT6" + fi + tid=0 + active_tbls=" " + config_foreach add_active_tbls interface + [ $active_tbls = " " ] && return + mwan3_get_routes | while read -r route_line; do + mwan3_route_line_dev "tid" "$route_line" "$ipv" + if [ -n "$tid" ] && [ -z "${active_tbls##* $tid *}" ]; then + $IP route add table $tid $route_line + elif [ -n "${route_line##default*}" ] && [ -n "${route_line##fe80::/64*}" ]; then + config_foreach add_route interface + fi + done +} + mwan3_rtmon_route_handle() { - config_load mwan3 - local section action route_line family tbl device metric tos dst line - local route_device tid + local action route_line family tbl device line route_line_exp tid source_routing + route_line=${1##"Deleted "} route_family=$2 + config_get_boolean source_routing globals source_routing 0 + [ $source_routing -eq 0 ] && unset source_routing + + if [ "$route_line" = "$1" ]; then + action="replace" + route_line_exp="s/expires \([0-9]\+\)sec//;s/error [0-9]\+//; ${source_routing:+s/default\(.*\) from [^ ]*/default\1/}" + $IPS -! add mwan3_connected_${route_family##ip} ${route_line%% *} + else + action="del" + route_line_exp="s/expires [0-9]\+sec//;s/error [0-9]\+//; ${source_routing:+s/default\(.*\) from [^ ]*/default\1/}" + mwan3_set_connected_${route_family} + fi + if [ "$route_family" = "ipv4" ]; then IP="$IP4" elif [ "$route_family" = "ipv6" ] && [ $NO_IPV6 -eq 0 ]; then IP="$IP6" + route_line=$(echo "$route_line" | sed "$route_line_exp") else + LOG warn "route update called with invalid family - $route_family" return fi - if [ "$route_line" == "$1" ]; then - action="add" - else - action="del" + # don't try to add routes when link has gone down + if [ -z "${route_line##linkdown*}" ]; then + LOG debug "not adding route due to linkdown - skipping $route_line" + return fi - # never add default route lines, since this is handled elsewhere - [ -z "${route_line##default*}" ] && return - [ -z "${route_line##::/0*}" ] && return - route_line=${route_line%% linkdown*} - route_line=${route_line%% unreachable*} - mwan3_update_dev_to_table - mwan3_route_line_dev "tid" "$route_line" "$route_family" handle_route() { - tbl=$($IP route list table $tid) - if [ $action = "add" ]; then - echo "$tbl" | grep -q "^default\|^::/0" || return - else - [ -z "$tbl" ] && return + local iface=$1 + tbl=$($IP route list table $tid 2>/dev/null)$'\n' + + if [ "$(cat /var/run/mwan3track/$iface/STATUS)" != "online" ]; then + LOG debug "interface $iface is offline - skipping $route_line"; + return fi - # check that action needs to be performed. May not need to take action if: - # Got a route update on ipv6 where route is already in the table - # Got a delete event, but table was already flushed - - [ $action = "add" ] && [ -z "${tbl##*$route_line*}" ] && return - [ $action = "del" ] && [ -n "${tbl##*$route_line*}" ] && return - network_get_device device "$section" - LOG debug "adjusting route $device: $IP route "$action" table $tid $route_line" + + # check that action needs to be performed. May not need to take action if we + # got a delete event, but table was already flushed + if [ $action = "del" ] && [ -n "${tbl##*$route_line$'\n'*}" ]; then + LOG debug "skipping already deleted route table $tid - skipping $route_line" + return + fi + + network_get_device device "$iface" + LOG debug "adjusting route $device: $IP route $action table $tid $route_line" $IP route "$action" table $tid $route_line || LOG warn "failed: $IP route $action table $tid $route_line" } handle_route_cb(){ + local iface=$1 let tid++ - config_get family "$section" family ipv4 + config_get family "$iface" family ipv4 [ "$family" != "$route_family" ] && return - handle_route + handle_route "$iface" } - if [ $action = "add" ]; then - ## handle old routes from 'change' or 'replace' - metric=${route_line##*metric } - [ "$metric" = "$route_line" ] && unset metric || metric=${metric%% *} - - tos=${route_line##*tos } - [ "$tos" = "$route_line" ] && unset tos || tos=${tos%% *} - - dst=${route_line%% *} - grep_line="$dst ${tos:+tos $tos}.*table [0-9].*${metric:+metric $metric}" - $IP route list table all | grep "$grep_line" | while read line; do - tbl=${line##*table } - tbl=${tbl%% *} - [ $tbl -gt $MWAN3_INTERFACE_MAX ] && continue - LOG debug "removing route on ip route change/replace: $line" - $IP route del $line - done - fi + mwan3_update_dev_to_table + mwan3_route_line_dev "tid" "$route_line" "$route_family" if [ -n "$tid" ]; then handle_route - else + elif [ -n "${route_line##default*}" ] && [ -n "${route_line##fe80::/64*}" ]; then config_foreach handle_route_cb interface fi } @@ -92,19 +149,35 @@ main() config_load mwan3 family=$1 [ -z $family ] && family=ipv4 - if [ "$family" = ipv6 ]; then + if [ "$family" = "ipv6" ]; then + if [ $NO_IPV6 -ne 0 ]; then + LOG warn "mwan3rtmon started for ipv6, but ipv6 not enabled on system" + exit 1 + fi IP="$IP6" else IP="$IP4" fi mwan3_init - - $IP monitor route | while read line; do - [ -z "${line##*table*}" ] && continue - LOG debug "handling route update $family $line" - mwan3_lock "service" "mwan3rtmon" - mwan3_rtmon_route_handle "$line" "$family" - mwan3_unlock "service" "mwan3rtmon" - done + mwan3_lock "mwan3rtmon" "start" + sh -c "echo \$\$; exec $IP monitor route" | { + read -r monitor_pid + trap_with_arg func_trap "$monitor_pid" SIGINT SIGTERM SIGKILL + while read -r line; do + [ -z "${line##*table*}" ] && continue + LOG debug "handling route update $family $line" + mwan3_lock "service" "mwan3rtmon" + mwan3_rtmon_route_handle "$line" "$family" + mwan3_unlock "service" "mwan3rtmon" + done + } & + child=$! + kill -SIGSTOP $child + trap_with_arg func_trap "$child" SIGINT SIGTERM SIGKILL + mwan3_set_connected_${family} + mwan3_add_all_routes ${family} + mwan3_unlock "mwan3rtmon" "start" + kill -SIGCONT $child + wait $! } main "$@" diff --git a/net/mwan3/files/usr/sbin/mwan3track b/net/mwan3/files/usr/sbin/mwan3track index 5cbf5f75d..b771e8fce 100755 --- a/net/mwan3/files/usr/sbin/mwan3track +++ b/net/mwan3/files/usr/sbin/mwan3track @@ -1,37 +1,64 @@ #!/bin/sh . /lib/functions.sh +. /lib/functions/network.sh . /lib/mwan3/common.sh INTERFACE="" DEVICE="" -PING="/bin/ping" IFDOWN_EVENT=0 IFUP_EVENT=0 +TRACK_OUTPUT=$MWAN3TRACK_STATUS_DIR/$INTERFACE/TRACK_OUTPUT + +mwan3_init + +stop_subprocs() { + [ -n "$SLEEP_PID" ] && kill "$SLEEP_PID" && unset SLEEP_PID + [ -n "$TRACK_PID" ] && kill "$TRACK_PID" && unset TRACK_PID +} + +WRAP() { + # shellcheck disable=SC2048 + FAMILY=$FAMILY DEVICE=$DEVICE SRCIP=$SRC_IP FWMARK=$MMX_DEFAULT LD_PRELOAD=/lib/mwan3/libwrap_mwan3_sockopt.so.1.0 $* +} clean_up() { - LOG notice "Stopping mwan3track for interface \"${INTERFACE}\"" + LOG notice "Stopping mwan3track for interface \"${INTERFACE}\". Status was \"${STATUS}\"" + stop_subprocs exit 0 } if_down() { LOG info "Detect ifdown event on interface ${INTERFACE} (${DEVICE})" IFDOWN_EVENT=1 + stop_subprocs } if_up() { LOG info "Detect ifup event on interface ${INTERFACE} (${DEVICE})" + IFDOWN_EVENT=0 IFUP_EVENT=1 + STARTED=1 + stop_subprocs } validate_track_method() { case "$1" in ping) - [ -x "$PING" ] || { - LOG warn "Missing ping. Please enable ping util and recompile busybox." + if [ -x "/usr/bin/ping" ] && [ "$(/usr/bin/ping -V | grep -o '[0-9]*$')" -gt 20150519 ]; then + # -4 option added in iputils c3e68ac6 + PING="/usr/bin/ping -${FAMILY#ipv}" + elif [ "$FAMILY" = "ipv6" ] && [ -x "/usr/bin/ping6" ]; then + PING="/usr/bin/ping6" + elif [ "$FAMILY" = "ipv4" ] && [ -x "/usr/bin/ping" ]; then + PING="/usr/bin/ping" + elif [ -x "/bin/ping" ]; then + PING="/bin/ping -${FAMILY#ipv}" + else + LOG warn "Missing ping. Please enable BUSYBOX_DEFAULT_PING and recompile busybox or install iputils-ping package." return 1 - } + fi ;; arping) command -v arping 1>/dev/null 2>&1 || { @@ -44,10 +71,6 @@ validate_track_method() { LOG warn "Missing httping. Please install httping package." return 1 } - [ -n "$2" -a "$2" != "0.0.0.0" -a "$2" != "::" ] || { - LOG warn "Cannot determine source IP for the interface which is required by httping." - return 1 - } ;; nping-*) command -v nping 1>/dev/null 2>&1 || { @@ -62,30 +85,63 @@ validate_track_method() { esac } +validate_wrap() { + [ -x /lib/mwan3/libwrap_mwan3_sockopt.so.1.0 ] && return + LOG error "Missing libwrap_mwan3_sockopt. Please reinstall mwan3." && + exit 1 +} + disconnected() { - echo "offline" > /var/run/mwan3track/$INTERFACE/STATUS - echo "$(get_uptime)" > /var/run/mwan3track/$INTERFACE/OFFLINE - echo "0" > /var/run/mwan3track/$INTERFACE/ONLINE + STATUS='offline' + echo "offline" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/STATUS + get_uptime > $MWAN3TRACK_STATUS_DIR/$INTERFACE/OFFLINE + echo "0" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/ONLINE score=0 - [ "$1" == 1 ] && return + [ "$1" = 1 ] && return LOG notice "Interface $INTERFACE ($DEVICE) is offline" env -i ACTION="disconnected" INTERFACE="$INTERFACE" DEVICE="$DEVICE" /sbin/hotplug-call iface } connected() { - echo "online" > /var/run/mwan3track/$INTERFACE/STATUS - echo "0" > /var/run/mwan3track/$INTERFACE/OFFLINE - echo "$(get_uptime)" > /var/run/mwan3track/$INTERFACE/ONLINE + STATUS='online' + echo "online" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/STATUS + echo "0" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/OFFLINE + get_uptime > $MWAN3TRACK_STATUS_DIR/$INTERFACE/ONLINE host_up_count=0 lost=0 turn=0 loss=0 - [ "$1" == 1 ] && return LOG notice "Interface $INTERFACE ($DEVICE) is online" - env -i ACTION="connected" INTERFACE="$INTERFACE" DEVICE="$DEVICE" /sbin/hotplug-call iface + env -i FIRSTCONNECT=$1 ACTION="connected" INTERFACE="$INTERFACE" DEVICE="$DEVICE" /sbin/hotplug-call iface +} + +disabled() { + STATUS='disabled' + echo "disabled" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/STATUS + STARTED=0 } firstconnect() { + local true_iface + network_flush_cache + + mwan3_get_true_iface true_iface $INTERFACE + network_get_device DEVICE $true_iface + + if [ "$STATUS" != "online" ]; then + config_get STATUS $INTERFACE initial_state "online" + fi + + if ! network_is_up $true_iface || [ -z "$DEVICE" ]; then + disabled + return + fi + + mwan3_get_src_ip SRC_IP $INTERFACE + + LOG debug "firstconnect: called on $INTERFACE/$true_iface ($DEVICE). Status is $STATUS. SRC_IP is $SRC_IP" + + STARTED=1 if [ "$STATUS" = "offline" ]; then disconnected 1 else @@ -94,14 +150,12 @@ firstconnect() { } update_status() { - local status track_ip - track_ip=$1 - status=$2 + local track_ip=$1 - echo "$1" > /var/run/mwan3track/$INTERFACE/TRACK_${track_ip} + echo "$2" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/TRACK_${track_ip} [ -z "$3" ] && return - echo "$3" > /var/run/mwan3track/$INTERFACE/LATENCY_${track_ip} - echo "$4" > /var/run/mwan3track/$INTERFACE/LOSS_${track_ip} + echo "$3" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/LATENCY_${track_ip} + echo "$4" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/LOSS_${track_ip} } main() { @@ -109,23 +163,23 @@ main() { local recovery_interval down up size local keep_failure_interval check_quality failure_latency local recovery_latency failure_loss recovery_loss - local max_ttl httping_ssl - [ -z "$5" ] && echo "Error: should not be started manually" && exit 0 + local max_ttl httping_ssl track_ips do_log INTERFACE=$1 - DEVICE=$2 - STATUS=$3 - SRC_IP=$4 - mkdir -p /var/run/mwan3track/$INTERFACE + STATUS="" + STARTED=0 + mkdir -p $MWAN3TRACK_STATUS_DIR/$INTERFACE + trap clean_up TERM trap if_down USR1 trap if_up USR2 config_load mwan3 + config_get FAMILY $INTERFACE family ipv4 config_get track_method $INTERFACE track_method ping config_get_bool httping_ssl $INTERFACE httping_ssl 0 - validate_track_method $track_method $SRC_IP || { + validate_track_method $track_method || { track_method=ping if validate_track_method $track_method; then LOG warn "Using ping to track interface $INTERFACE avaliability" @@ -150,110 +204,102 @@ main() { config_get recovery_latency $INTERFACE recovery_latency 500 config_get failure_loss $INTERFACE failure_loss 40 config_get recovery_loss $INTERFACE recovery_loss 10 + local sleep_time result ping_status loss latency + mwan3_list_track_ips() + { + track_ips="$track_ips $1" + } + config_list_foreach "$1" track_ip mwan3_list_track_ips - local score=$(($down+$up)) - local track_ips=$(echo $* | cut -d ' ' -f 5-99) + local score=$((down+up)) local host_up_count=0 local lost=0 local turn=0 - local ping_protocol=4 - local sleep_time result ping_result ping_result_raw ping_status loss latency firstconnect while true; do - + [ $STARTED -eq 0 ] && { sleep $MAX_SLEEP & SLEEP_PID=$!; wait; } + unset SLEEP_PID sleep_time=$interval - for track_ip in $track_ips; do if [ $host_up_count -lt $reliability ]; then case "$track_method" in ping) - # pinging IPv6 hosts with an interface is troublesome - # https://bugs.openwrt.org/index.php?do=details&task_id=2897 - # so get the IP address of the interface and use that instead - if [ -z ${track_ip##*:*} ]; then - ping_protocol=6 - else - unset SRC_IP - fi if [ $check_quality -eq 0 ]; then - $PING -$ping_protocol -I ${SRC_IP:-$DEVICE} -c $count -W $timeout -s $size -t $max_ttl -q $track_ip &> /dev/null + WRAP $PING -c $count -W $timeout -s $size -t $max_ttl -q $track_ip &> /dev/null & + TRACK_PID=$! + wait $TRACK_PID result=$? else - ping_result_raw="$($PING -$ping_protocol -I ${SRC_IP:-$DEVICE} -c $count -W $timeout -s $size -t $max_ttl -q $track_ip 2>/dev/null)" + WRAP $PING -c $count -W $timeout -s $size -t $max_ttl -q $track_ip 2>/dev/null > $TRACK_OUTPUT & + TRACK_PID=$! + wait $TRACK_PID ping_status=$? - ping_result=$(echo "$ping_result_raw" | tail -n2) - loss="$(echo "$ping_result" | grep "packet loss" | cut -d "," -f3 | awk '{print $1}' | sed -e 's/%//')" + loss="$(sed $TRACK_OUTPUT -ne 's/.*\([0-9]\+\)% packet loss.*/\1/p')" if [ "$ping_status" -ne 0 ] || [ "$loss" -eq 100 ]; then latency=999999 loss=100 else - latency="$(echo "$ping_result" | grep -E 'rtt|round-trip' | cut -d "=" -f2 | cut -d "/" -f2 | cut -d "." -f1)" + latency="$(sed $TRACK_OUTPUT -ne 's%\(rtt\|round-trip\).* = [^/]*/\([0-9]\+\).*%\2%p')" fi fi ;; arping) - arping -I $DEVICE -c $count -w $timeout -q $track_ip &> /dev/null + WRAP arping -I $DEVICE -c $count -w $timeout -q $track_ip &> /dev/null & + TRACK_PID=$! + wait $TRACK_PID result=$? ;; httping) if [ "$httping_ssl" -eq 1 ]; then - httping -y $SRC_IP -c $count -t $timeout -q "https://$track_ip" &> /dev/null + WRAP httping -c $count -t $timeout -q "https://$track_ip" &> /dev/null & else - httping -y $SRC_IP -c $count -t $timeout -q "http://$track_ip" &> /dev/null + WRAP httping -c $count -t $timeout -q "http://$track_ip" &> /dev/null & fi + TRACK_PID=$! + wait $TRACK_PID result=$? ;; - nping-tcp) - result=$(nping -e $DEVICE -c $count $track_ip --tcp | grep Lost | awk '{print $12}') - ;; - nping-udp) - result=$(nping -e $DEVICE -c $count $track_ip --udp | grep Lost | awk '{print $12}') - ;; - nping-icmp) - result=$(nping -e $DEVICE -c $count $track_ip --icmp | grep Lost | awk '{print $12}') - ;; - nping-arp) - result=$(nping -e $DEVICE -c $count $track_ip --arp | grep Lost | awk '{print $12}') + nping-*) + WRAP nping -c $count $track_ip --${FAMILY#nping-} > $TRACK_OUTPUT & + TRACK_PID=$! + wait $TRACK_PID + result=$(grep $TRACK_OUTPUT Lost | awk '{print $12}') ;; esac + do_log="" if [ $check_quality -eq 0 ]; then if [ $result -eq 0 ]; then let host_up_count++ update_status "$track_ip" "up" - if [ $score -le $up ]; then - LOG info "Check ($track_method) success for target \"$track_ip\" on interface $INTERFACE ($DEVICE). Current score: $score" - fi + [ $score -le $up ] && do_log="success" else let lost++ update_status "$track_ip" "down" - if [ $score -gt $up ]; then - LOG info "Check ($track_method) failed for target \"$track_ip\" on interface $INTERFACE ($DEVICE). Current score: $score" - fi + [ $score -gt $up ] && do_log="failed" fi + [ -n "$do_log" ] && LOG info "Check ($track_method) ${do_log} for target \"$track_ip\" on interface $INTERFACE ($DEVICE). Current score: $score" + else - if [ "$loss" -ge "$failure_loss" -o "$latency" -ge "$failure_latency" ]; then + if [ "$loss" -ge "$failure_loss" ] || [ "$latency" -ge "$failure_latency" ]; then let lost++ update_status "$track_ip" "down" $latency $loss - if [ $score -gt $up ]; then - LOG info "Check (${track_method}: latency=${latency}ms loss=${loss}%) failed for target \"$track_ip\" on interface $INTERFACE ($DEVICE). Current score: $score" - fi - elif [ "$loss" -le "$recovery_loss" -a "$latency" -le "$recovery_latency" ]; then + [ $score -gt $up ] && do_log="failed" + elif [ "$loss" -le "$recovery_loss" ] && [ "$latency" -le "$recovery_latency" ]; then let host_up_count++ update_status "$track_ip" "up" $latency $loss - if [ $score -le $up ]; then - LOG info "Check (${track_method}: latency=${latency}ms loss=${loss}%) success for target \"$track_ip\" on interface $INTERFACE ($DEVICE). Current score: $score" - fi + [ $score -le $up ] && do_log="success" else - echo "skipped" > /var/run/mwan3track/$INTERFACE/TRACK_${track_ip} + echo "skipped" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/TRACK_${track_ip} fi + [ -n "$do_log" ] && LOG info "Check (${track_method}: latency=${latency}ms loss=${loss}%) ${do_log} for target \"$track_ip\" on interface $INTERFACE ($DEVICE). Current score: $score" fi else - echo "skipped" > /var/run/mwan3track/$INTERFACE/TRACK_${track_ip} + echo "skipped" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/TRACK_${track_ip} fi done @@ -262,9 +308,7 @@ main() { if [ $score -lt $up ]; then score=0 - [ ${keep_failure_interval} -eq 1 ] && { - sleep_time=$failure_interval - } + [ ${keep_failure_interval} -eq 1 ] && sleep_time=$failure_interval else sleep_time=$failure_interval fi @@ -274,31 +318,31 @@ main() { score=0 fi else - if [ $score -lt $(($down+$up)) ] && [ $lost -gt 0 ]; then - LOG info "Lost $(($lost*$count)) ping(s) on interface $INTERFACE ($DEVICE). Current score: $score" + if [ $score -lt $((down+up)) ] && [ $lost -gt 0 ]; then + LOG info "Lost $((lost*count)) ping(s) on interface $INTERFACE ($DEVICE). Current score: $score" fi let score++ lost=0 if [ $score -gt $up ]; then - echo "online" > /var/run/mwan3track/$INTERFACE/STATUS - score=$(($down+$up)) + echo "online" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/STATUS + score=$((down+up)) elif [ $score -le $up ]; then sleep_time=$recovery_interval fi if [ $score -eq $up ]; then - connected $INTERFACE $DEVICE + connected fi fi let turn++ - mkdir -p "/var/run/mwan3track/${1}" - echo "${lost}" > /var/run/mwan3track/$INTERFACE/LOST - echo "${score}" > /var/run/mwan3track/$INTERFACE/SCORE - echo "${turn}" > /var/run/mwan3track/$INTERFACE/TURN - echo "$(get_uptime)" > /var/run/mwan3track/$INTERFACE/TIME + mkdir -p "$MWAN3TRACK_STATUS_DIR/${1}" + echo "${lost}" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/LOST + echo "${score}" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/SCORE + echo "${turn}" > $MWAN3TRACK_STATUS_DIR/$INTERFACE/TURN + get_uptime > $MWAN3TRACK_STATUS_DIR/$INTERFACE/TIME host_up_count=0 sleep "${sleep_time}" & @@ -306,7 +350,8 @@ main() { if [ "${IFDOWN_EVENT}" -eq 1 ]; then LOG debug "Register ifdown event on interface ${INTERFACE} (${DEVICE})" - disconnected 1 + disabled + disconnected IFDOWN_EVENT=0 fi if [ "${IFUP_EVENT}" -eq 1 ]; then diff --git a/net/mwan3/src/sockopt_wrap.c b/net/mwan3/src/sockopt_wrap.c new file mode 100644 index 000000000..695e57502 --- /dev/null +++ b/net/mwan3/src/sockopt_wrap.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Aaron Goodman . All Rights Reserved. + */ + +/* + * sockopt_wrap.c provides a shared library that intercepts syscalls to various + * networking functions to bind the sockets a source IP address and network device + * and to set the firewall mark on otugoing packets. Parameters are set using the + * DEVICE, SRCIP, FWMARK environment variables. + * + * Additionally the FAMILY environment variable can be set to either 'ipv4' or + * 'ipv6' to cause sockets opened with ipv6 or ipv4 to fail, respectively. + * + * Each environment variable is optional, and if not set, the library will not + * enforce the particular parameter. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static int (*next_socket)(int domain, int type, int protocol); +static int (*next_setsockopt)(int sockfd, int level, int optname, + const void *optval, socklen_t optlen); +static int (*next_bind)(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +static int (*next_close)(int fd); +static ssize_t (*next_send)(int sockfd, const void *buf, size_t len, int flags); +static ssize_t (*next_sendto)(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen); +static ssize_t (*next_sendmsg)(int sockfd, const struct msghdr *msg, int flags); +static int (*next_connect)(int sockfd, const struct sockaddr *addr, + socklen_t addrlen); +static int device=0; +static struct sockaddr_in source4 = {0}; +#ifdef CONFIG_IPV6 +static struct sockaddr_in6 source6 = {0}; +#endif +static struct sockaddr * source = 0; +static int sockaddr_size = 0; +static int is_bound [1024] = {0}; + +#define next_func(x)\ +void set_next_##x(){\ + if (next_##x) return;\ + next_##x = dlsym(RTLD_NEXT, #x);\ + dlerror_handle();\ + return;\ +} + +void dlerror_handle() +{ + char *msg; + if ((msg = dlerror()) != NULL) { + fprintf(stderr, "socket: dlopen failed : %s\n", msg); + fflush(stderr); + exit(EXIT_FAILURE); + } +} + +next_func(bind); +next_func(close); +next_func(setsockopt); +next_func(socket); +next_func(send); +next_func(sendto); +next_func(sendmsg); +next_func(connect); + +void dobind(int sockfd) +{ + if (source && sockfd < 1024 && !is_bound[sockfd]) { + set_next_bind(); + if (next_bind(sockfd, source, sockaddr_size)) { + perror("failed to bind to ip address"); + next_close(sockfd); + exit(EXIT_FAILURE); + } + is_bound[sockfd] = 1; + } +} + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + set_next_connect(); + dobind(sockfd); + return next_connect(sockfd, addr, addrlen); +} + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + set_next_send(); + dobind(sockfd); + return next_send(sockfd, buf, len, flags); +} + +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + set_next_sendto(); + dobind(sockfd); + return next_sendto(sockfd, buf, len, flags, dest_addr, addrlen); +} + +ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + set_next_sendmsg(); + dobind(sockfd); + return next_sendmsg(sockfd, msg, flags); +} + +int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + set_next_bind(); + if (device && addr->sa_family == AF_PACKET) { + ((struct sockaddr_ll*)addr)->sll_ifindex=device; + } + else if (source && addr->sa_family == AF_INET) { + ((struct sockaddr_in*)addr)->sin_addr = source4.sin_addr; + } +#ifdef CONFIG_IPV6 + else if (source && addr->sa_family == AF_INET6) { + ((struct sockaddr_in6*)addr)->sin6_addr = source6.sin6_addr; + } +#endif + if (sockfd < 1024) + is_bound[sockfd] = 1; + return next_bind(sockfd, addr, addrlen); +} + +int close (int sockfd) +{ + set_next_close(); + if (sockfd < 1024) + is_bound[sockfd]=0; + return next_close(sockfd); +} + +int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) +{ + set_next_setsockopt(); + if (level == SOL_SOCKET && (optname == SO_MARK || optname == SO_BINDTODEVICE)) + return 0; + return next_setsockopt(sockfd, level, optname, optval, optlen); +} + +int socket(int domain, int type, int protocol) +{ + int handle; + + const char *socket_str = getenv("DEVICE"); + const char *srcip_str = getenv("SRCIP"); + const char *fwmark_str = getenv("FWMARK"); + const char *family_str = getenv("FAMILY"); + const int iface_len = socket_str ? strnlen(socket_str, IFNAMSIZ) : 0; + int has_family = family_str && *family_str != 0; + int has_srcip = srcip_str && *srcip_str != 0; + const int fwmark = fwmark_str ? (int)strtol(fwmark_str, NULL, 0) : 0; + + set_next_close(); + set_next_socket(); + set_next_send(); + set_next_setsockopt(); + set_next_sendmsg(); + set_next_sendto(); + set_next_connect(); + if(has_family) { +#ifdef CONFIG_IPV6 + if(domain == AF_INET && strncmp(family_str,"ipv6",4) == 0) + return -1; +#endif + if(domain == AF_INET6 && strncmp(family_str,"ipv4",4) == 0) + return -1; + } + + if (domain != AF_INET +#ifdef CONFIG_IPV6 + && domain != AF_INET6 +#endif + ) { + return next_socket(domain, type, protocol); + } + + + if (iface_len > 0) { + if (iface_len == IFNAMSIZ) { + fprintf(stderr,"socket: Too long iface name\n"); + fflush(stderr); + exit(EXIT_FAILURE); + } + } + + if (has_srcip) { + int s; + void * addr_buf; + if (domain == AF_INET) { + addr_buf = &source4.sin_addr; + sockaddr_size=sizeof source4; + memset(&source4, 0, sockaddr_size); + source4.sin_family = domain; + source = (struct sockaddr*)&source4; + } +#ifdef CONFIG_IPV6 + else { + addr_buf = &source6.sin6_addr; + sockaddr_size=sizeof source6; + memset(&source6, 0, sockaddr_size); + source6.sin6_family=domain; + source = (struct sockaddr*)&source6; + } +#endif + s = inet_pton(domain, srcip_str, addr_buf); + if (s == 0) { + fprintf(stderr, "socket: ip address invalid format for family %s\n", + domain == AF_INET ? "AF_INET" : domain == AF_INET6 ? + "AF_INET6" : "unknown"); + return -1; + } + if (s < 0) { + perror("inet_pton"); + exit(EXIT_FAILURE); + } + } + + handle = next_socket(domain, type, protocol); + if (handle == -1 ) { + return handle; + } + + if (iface_len > 0) { + device=if_nametoindex(socket_str); + if (next_setsockopt(handle, SOL_SOCKET, SO_BINDTODEVICE, + socket_str, iface_len + 1)) { + perror("socket: setting interface name failed with error"); + next_close(handle); + exit(EXIT_FAILURE); + } + } + + if (fwmark > 0) { + if (next_setsockopt(handle, SOL_SOCKET, SO_MARK, + &fwmark, sizeof fwmark)) { + perror("failed setting mark for socket"); + next_close(handle); + exit(EXIT_FAILURE); + } + } + return handle; +}