Merge pull request #13169 from aaronjg/mwan3-owner-procd

mwan3: mwan3track via default routing table and use procd from mwan3track & mwan3rtmon
This commit is contained in:
Florian Eckert 2020-10-21 16:33:19 +02:00 committed by GitHub
commit 9485b9401d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1097 additions and 751 deletions

View file

@ -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 <fe@dev.tdt.de>
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))

View file

@ -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'

View file

@ -9,7 +9,9 @@
SCRIPTNAME="mwan3-hotplug"
[ "$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
if [ "$ACTION" = ifup ] || [ "$ACTION" = ifdown ]; then
initscript=/etc/init.d/mwan3
. /lib/functions/procd.sh
fi
binary_status=$status
[ "$binary_status" = "online" ] || binary_status=offline
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_set_iface_hotplug_state $INTERFACE "$status"
if [ "$MWAN3_STARTUP" != 1 ]; then
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})"
[ "$status" = "online" ] && mwan3_set_policies_iptables
fi
else
mwan3_track $INTERFACE $DEVICE "$binary_status" "$src_ip"
LOG notice "Started tracker [$!] on interface $INTERFACE (${DEVICE:-unknown})"
fi
[ "$MWAN3_STARTUP" != 1 ] && [ "$binary_status" == "online" ] && mwan3_set_policies_iptables
[ "$ACTION" = ifup ] && procd_running mwan3 "track_$INTERFACE" && procd_send_signal mwan3 "track_$INTERFACE" USR2
;;
ifdown|disconnected)
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

View file

@ -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

View file

@ -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() {

View file

@ -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<<bitcnt)-1))
MWAN3_INTERFACE_MAX=$((mmdefault-3))
uci_toggle_state mwan3 globals iface_max "$MWAN3_INTERFACE_MAX"
LOG debug "Max interface count is ${MWAN3_INTERFACE_MAX}"
fi
# mark mask constants
bitcnt=$(mwan3_count_one_bits MMX_MASK)
mmdefault=$(((1<<bitcnt)-1))
MM_BLACKHOLE=$((mmdefault-2))
MM_UNREACHABLE=$((mmdefault-1))
# MMX_DEFAULT should equal MMX_MASK
MMX_DEFAULT=$(mwan3_id2mask mmdefault MMX_MASK)
MMX_BLACKHOLE=$(mwan3_id2mask MM_BLACKHOLE MMX_MASK)
MMX_UNREACHABLE=$(mwan3_id2mask MM_UNREACHABLE MMX_MASK)
}
# 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<<bit_msk)))
fi
bit_val=$((bit_val+1))
fi
done
printf "0x%x" $result
}
# counts how many bits are set to 1
# n&(n-1) clears the lowest bit set to 1
mwan3_count_one_bits()
{
local count n
count=0
n=$(($1))
while [ "$n" -gt "0" ]; do
n=$((n&(n-1)))
count=$((count+1))
done
echo $count
}

View file

@ -2,8 +2,6 @@
. /usr/share/libubox/jshn.sh
IP4="ip -4"
IP6="ip -6"
IPS="ipset"
IPT4="iptables -t mangle -w"
IPT6="ip6tables -t mangle -w"
@ -22,18 +20,9 @@ IPv6_REGEX="${IPv6_REGEX}:((:[0-9a-fA-F]{1,4}){1,7}|:)|"
IPv6_REGEX="${IPv6_REGEX}fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"
IPv6_REGEX="${IPv6_REGEX}::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|"
IPv6_REGEX="${IPv6_REGEX}([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"
IPv4_REGEX="((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
MWAN3_STATUS_DIR="/var/run/mwan3"
MWAN3TRACK_STATUS_DIR="/var/run/mwan3track"
MWAN3_INTERFACE_MAX=""
DEFAULT_LOWEST_METRIC=256
MMX_MASK=""
MMX_DEFAULT=""
MMX_BLACKHOLE=""
MM_BLACKHOLE=""
MMX_UNREACHABLE=""
MM_UNREACHABLE=""
command -v ip6tables > /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,20 +71,6 @@ 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
@ -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<<bit_msk)))
fi
bit_val=$((bit_val+1))
fi
done
printf "0x%x" $result
}
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<<bitcnt)-1))
MWAN3_INTERFACE_MAX=$(($mmdefault-3))
uci_toggle_state mwan3 globals iface_max "$MWAN3_INTERFACE_MAX"
LOG debug "Max interface count is ${MWAN3_INTERFACE_MAX}"
fi
# mark mask constants
bitcnt=$(mwan3_count_one_bits MMX_MASK)
mmdefault=$(((1<<bitcnt)-1))
MM_BLACKHOLE=$(($mmdefault-2))
MM_UNREACHABLE=$(($mmdefault-1))
# MMX_DEFAULT should equal MMX_MASK
MMX_DEFAULT=$(mwan3_id2mask mmdefault MMX_MASK)
MMX_BLACKHOLE=$(mwan3_id2mask MM_BLACKHOLE MMX_MASK)
MMX_UNREACHABLE=$(mwan3_id2mask MM_UNREACHABLE MMX_MASK)
}
mwan3_lock() {
lock /var/run/mwan3.lock
#LOG debug "$1 $2 (lock)"
@ -197,22 +116,6 @@ mwan3_unlock() {
lock -u /var/run/mwan3.lock
}
mwan3_get_src_ip()
{
local family _src_ip true_iface
true_iface=$2
unset "$1"
config_get family "$true_iface" family ipv4
if [ "$family" = "ipv4" ]; then
network_get_ipaddr _src_ip "$true_iface"
[ -n "$_src_ip" ] || _src_ip="0.0.0.0"
elif [ "$family" = "ipv6" ]; then
network_get_ipaddr6 _src_ip "$true_iface"
[ -n "$_src_ip" ] || _src_ip="::"
fi
export "$1=$_src_ip"
}
mwan3_get_iface_id()
{
local _tmp
@ -220,14 +123,13 @@ mwan3_get_iface_id()
_tmp="${mwan3_iface_tbl##* ${2}=}"
_tmp=${_tmp%% *}
export "$1=$_tmp"
new_val=$_tmp
}
mwan3_set_custom_ipset_v4()
{
local custom_network_v4
for custom_network_v4 in $($IP4 route list table "$1" | awk '{print $1}' | egrep '[0-9]{1,3}(\.[0-9]{1,3}){3}'); do
for custom_network_v4 in $($IP4 route list table "$1" | awk '{print $1}' | grep -E "$IPv4_REGEX"); do
LOG notice "Adding network $custom_network_v4 from table $1 to mwan3_custom_v4 ipset"
mwan3_push_update -! add mwan3_custom_v4 "$custom_network_v4"
done
@ -237,7 +139,7 @@ mwan3_set_custom_ipset_v6()
{
local custom_network_v6
for custom_network_v6 in $($IP6 route list table "$1" | awk '{print $1}' | egrep "$IPv6_REGEX"); do
for custom_network_v6 in $($IP6 route list table "$1" | awk '{print $1}' | grep -E "$IPv6_REGEX"); do
LOG notice "Adding network $custom_network_v6 from table $1 to mwan3_custom_v6 ipset"
mwan3_push_update -! add mwan3_custom_v6 "$custom_network_v6"
done
@ -263,7 +165,6 @@ mwan3_set_custom_ipset()
mwan3_set_connected_ipv4()
{
local connected_network_v4 candidate_list cidr_list
local ipv4regex='[0-9]{1,3}(\.[0-9]{1,3}){3}'
$IPS -! create mwan3_connected_v4 hash:net
$IPS create mwan3_connected_v4_temp hash:net
@ -274,7 +175,7 @@ mwan3_set_connected_ipv4()
$IP4 route | awk '{print $1}'
$IP4 route list table 0 | awk '{print $2}'
}
for connected_network_v4 in $(route_lists | egrep "$ipv4regex"); do
for connected_network_v4 in $(route_lists | grep -E "$IPv4_REGEX"); do
if [ -z "${connected_network_v4##*/*}" ]; then
cidr_list="$cidr_list $connected_network_v4"
else
@ -294,40 +195,44 @@ mwan3_set_connected_ipv4()
$IPS swap mwan3_connected_v4_temp mwan3_connected_v4
$IPS destroy mwan3_connected_v4_temp
$IPS -! add mwan3_connected mwan3_connected_v4
}
mwan3_set_connected_iptables()
mwan3_set_connected_ipv6()
{
local connected_network_v6 source_network_v6 error
local connected_network_v6 error
local update=""
mwan3_set_connected_ipv4
[ $NO_IPV6 -eq 0 ] || return
[ $NO_IPV6 -eq 0 ] && {
mwan3_push_update -! create mwan3_connected_v6 hash:net family inet6
mwan3_push_update flush mwan3_connected_v6
for connected_network_v6 in $($IP6 route | awk '{print $1}' | egrep "$IPv6_REGEX"); do
for connected_network_v6 in $($IP6 route | awk '{print $1}' | grep -E "$IPv6_REGEX"); do
mwan3_push_update -! add mwan3_connected_v6 "$connected_network_v6"
done
mwan3_push_update -! create mwan3_source_v6 hash:net family inet6
for source_network_v6 in $($IP6 addr ls | sed -ne 's/ *inet6 \([^ \/]*\).* scope global.*/\1/p'); do
mwan3_push_update -! add mwan3_source_v6 "$source_network_v6"
done
}
mwan3_push_update -! add mwan3_connected mwan3_connected_v6
error=$(echo "$update" | $IPS restore 2>&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,17 +370,17 @@ 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"
@ -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
mwan3_delete_iface_rules "$1"
while [ -n "$($IP rule list | awk '$1 == "'$(($id+2000)):'"')" ]; do
$IP rule del pref $(($id+2000))
done
$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,9 +644,9 @@ 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
if [ -n "${current##*-N mwan3_policy_$1$'\n'*}" ]; then
mwan3_push_update -N "mwan3_policy_$1"
fi
@ -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
@ -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
@ -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"
}

View file

@ -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)"

View file

@ -22,29 +22,27 @@ Available commands:
connected Show directly connected networks
rules Show active rules
status Show all status
use <iface> <cmd> Run a command bound to <iface> and avoid mwan3 rules
EOF
}
ifdown()
{
ifdown() {
if [ -z "$1" ]; then
echo "Error: Expecting interface. Usage: mwan3 ifdown <interface>" && exit 0
echo "Error: Expecting interface. Usage: mwan3 ifdown <interface>"
exit 0
fi
if [ -n "$2" ]; then
echo "Error: Too many arguments. Usage: mwan3 ifdown <interface>" && exit 0
echo "Error: Too many arguments. Usage: mwan3 ifdown <interface>"
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 <interface>"
@ -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"
{
kill -TERM $(pgrep -f "mwan3rtmon") > /dev/null 2>&1
kill -TERM $(pgrep -f "mwan3track") > /dev/null 2>&1
sleep 1
kill -KILL $(pgrep -f "mwan3rtmon") > /dev/null 2>&1
kill -KILL $(pgrep -f "mwan3track") > /dev/null 2>&1
} &
kill_pid=$!
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
for ipset in $($IPS -n list | grep mwan3 | grep -E '_v4|_v6'); do
$IPS -q destroy $ipset
done
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"
stop() {
/etc/init.d/mwan3 disable
/etc/init.d/mwan3 stop
}
restart() {
stop
start
/etc/init.d/mwan3 enable
/etc/init.d/mwan3 stop
/etc/init.d/mwan3 start
}
wrap() {
# Run a command with the device, src_ip and fwmark set to avoid processing by mwan3
# firewall rules
local interface device src_ip family
mwan3_init
config_load mwan3
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
mwan3_get_src_ip src_ip $interface
[ -z "$src_ip" ] && echo "could not find src_ip for $interface" && return
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 $*
}
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
$*
;;
*)

View file

@ -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
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
local iface=$1
tbl=$($IP route list table $tid 2>/dev/null)$'\n'
[ $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"
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 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
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 "$@"

View file

@ -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"
[ $score -le $up ] && do_log="success"
else
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}
fi
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

View file

@ -0,0 +1,255 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 Aaron Goodman <aaronjg@alumni.stanford.edu>. 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 <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <linux/if_packet.h>
#include <net/if.h>
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;
}