openwrt-packages/net/mwan3/files/lib/mwan3/common.sh

213 lines
5.4 KiB
Bash
Raw Normal View History

#!/bin/sh
IP4="ip -4"
IP6="ip -6"
SCRIPTNAME="$(basename "$0")"
MWAN3_STATUS_DIR="/var/run/mwan3"
MWAN3_STATUS_IPTABLES_LOG_DIR="${MWAN3_STATUS_DIR}/iptables_log"
MWAN3TRACK_STATUS_DIR="/var/run/mwan3track"
MWAN3_INTERFACE_MAX=""
MMX_MASK=""
MMX_DEFAULT=""
MMX_BLACKHOLE=""
MM_BLACKHOLE=""
MMX_UNREACHABLE=""
MM_UNREACHABLE=""
mwan3: Fix packet routing when WAN interface is partially up This introduces a new concept of "unknown_wan" to mwan3. The action for this can be configured in the globals section the default of which is 'none'. This can be set to 'none', 'default', 'unreachable' or 'blacklist' switching out the matching ip rule for this match. This assignment for a connection is temporary and is re-resolved for each additional original direction packet through the firewall allowing the unknown WAN to start resolving once the ifup has finished for the given interface. An example configuration: config globals 'globals' option unknown_wan_action 'unreachable' Prior to this commit, mwan3 had multiple hit spots for packets in the following order: 1. Packets are checked to see if they originate from known WAN interfaces 2. Packets are checked to see if they destined for ipsets defined 3. Packets are checked against default WAN policies The WAN list is maintained via hotplug 'ifup'/'ifdown' events and the local route ipset list is maintained via monitoring the routing table. This means that while a WAN interface is brought up, the list for 2 is updated before the list for 1, since an interface is fully brought up before the ifup event is fired off. Additionally, we want to make sure we don't apply a WAN policy for incoming packets from a WAN interface that is in the process of being brought up. We can identify packets that are presumably coming from a WAN interface we don't recognize yet by eliminating all packets that the source comes from networks we don't know about in the ipsets that mwan3 manages. We have to be careful here to only match the original direction of the packet flow (e.g. for instance with ICMP, the ping request is in the ORIGINAL direction, and the response is in the REPLY direction) or else we might match something we didn't intend to. By modifying the rule set to the following: 1. Packets are checked to see if they are in a REPLY direction of flow 2. Packets are checked to see if they originate from known WAN interfaces 3. Packets are checked to see if they not sourced from ipsets defined 4. Packets are checked to see if they destined for ipsets defined 5. Packets are checked against default WAN policies If a packet is in the REPLY direction of flow, we definitely don't want to do any routing table assignments - we only want to do this for the original direction of traffic flow. This reduces the amount of rules parsed within mwan3. If a packet is not sourced from a defined ipset, this should match any packet originating from a "default route" upstream. We do this post the known WAN interface check since we don't know what mask to apply to this packet at this time until the 'ifup' has completed. It's also setup to reevaluate this decision by clearing this specific mark when a new packet comes in in the REPLY direction of flow before any subsequent evaluations. This allows additional packets for the same connection to eventually be assigned the appropriate mask once the 'ifup' has finished. One easy way to test this out before and after this change is to: - Bring down wan (e.g. ifdown wan) - Manually bring up WAN - This mitigates the firewall rules being added for 1 above, but 2 is still added since this is monitoring the routing interface - Ping the device from a non-local subnet via the WAN interface; leave running - Observe mark set to ICMP session via conntrack - Bring up wan (e.g. ifup wan) - Observe mark set to ICMP session from above Signed-off-by: Tim Nordell <tnordell@airgain.com>
2023-06-26 17:24:27 +02:00
MMX_UNKNOWN_WAN=""
MM_UNKNOWN_WAN=""
MAX_SLEEP=$(((1<<31)-1))
command -v ip6tables > /dev/null
NO_IPV6=$?
IPS="ipset"
IPT4="iptables -t mangle -w"
IPT6="ip6tables -t mangle -w"
IPT4R="iptables-restore -T mangle -w -n"
IPT6R="ip6tables-restore -T mangle -w -n"
LOG()
{
local facility=$1; shift
# in development, we want to show 'debug' level logs
# when this release is out of beta, the comment in the line below
# should be removed
[ "$facility" = "debug" ] && return
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_1='network_get_ipaddr'
addr_cmd_2='false'
default_ip="0.0.0.0"
sed_str='s/ *inet \([^ \/]*\).*/\1/;T; pq'
IP="$IP4"
elif [ "$family" = "ipv6" ]; then
addr_cmd_1='network_get_preferred_ipaddr6'
addr_cmd_2='network_get_ipaddr6'
default_ip="::"
sed_str='s/ *inet6 \([^ \/]*\).* scope.*/\1/;T; pq'
IP="$IP6"
fi
$addr_cmd_1 _src_ip "$true_iface" 2>&1 || $addr_cmd_2 _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="disabled"
fi
echo "$tracking"
}
mwan3_init()
{
local bitcnt mmdefault source_routing
config_load mwan3
[ -d $MWAN3_STATUS_DIR ] || mkdir -p $MWAN3_STATUS_DIR/iface_state
[ -d "$MWAN3_STATUS_IPTABLES_LOG_DIR" ] || mkdir -p "$MWAN3_STATUS_IPTABLES_LOG_DIR"
# 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_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
# remove "linkdown", expiry and source based routing modifiers from route lines
config_get_bool source_routing globals source_routing 0
[ $source_routing -eq 1 ] && unset source_routing
MWAN3_ROUTE_LINE_EXP="s/offload//; s/linkdown //; s/expires [0-9]\+sec//; s/error [0-9]\+//; ${source_routing:+s/default\(.*\) from [^ ]*/default\1/;} p"
# mark mask constants
bitcnt=$(mwan3_count_one_bits MMX_MASK)
mmdefault=$(((1<<bitcnt)-1))
MM_BLACKHOLE=$((mmdefault-2))
MM_UNREACHABLE=$((mmdefault-1))
mwan3: Fix packet routing when WAN interface is partially up This introduces a new concept of "unknown_wan" to mwan3. The action for this can be configured in the globals section the default of which is 'none'. This can be set to 'none', 'default', 'unreachable' or 'blacklist' switching out the matching ip rule for this match. This assignment for a connection is temporary and is re-resolved for each additional original direction packet through the firewall allowing the unknown WAN to start resolving once the ifup has finished for the given interface. An example configuration: config globals 'globals' option unknown_wan_action 'unreachable' Prior to this commit, mwan3 had multiple hit spots for packets in the following order: 1. Packets are checked to see if they originate from known WAN interfaces 2. Packets are checked to see if they destined for ipsets defined 3. Packets are checked against default WAN policies The WAN list is maintained via hotplug 'ifup'/'ifdown' events and the local route ipset list is maintained via monitoring the routing table. This means that while a WAN interface is brought up, the list for 2 is updated before the list for 1, since an interface is fully brought up before the ifup event is fired off. Additionally, we want to make sure we don't apply a WAN policy for incoming packets from a WAN interface that is in the process of being brought up. We can identify packets that are presumably coming from a WAN interface we don't recognize yet by eliminating all packets that the source comes from networks we don't know about in the ipsets that mwan3 manages. We have to be careful here to only match the original direction of the packet flow (e.g. for instance with ICMP, the ping request is in the ORIGINAL direction, and the response is in the REPLY direction) or else we might match something we didn't intend to. By modifying the rule set to the following: 1. Packets are checked to see if they are in a REPLY direction of flow 2. Packets are checked to see if they originate from known WAN interfaces 3. Packets are checked to see if they not sourced from ipsets defined 4. Packets are checked to see if they destined for ipsets defined 5. Packets are checked against default WAN policies If a packet is in the REPLY direction of flow, we definitely don't want to do any routing table assignments - we only want to do this for the original direction of traffic flow. This reduces the amount of rules parsed within mwan3. If a packet is not sourced from a defined ipset, this should match any packet originating from a "default route" upstream. We do this post the known WAN interface check since we don't know what mask to apply to this packet at this time until the 'ifup' has completed. It's also setup to reevaluate this decision by clearing this specific mark when a new packet comes in in the REPLY direction of flow before any subsequent evaluations. This allows additional packets for the same connection to eventually be assigned the appropriate mask once the 'ifup' has finished. One easy way to test this out before and after this change is to: - Bring down wan (e.g. ifdown wan) - Manually bring up WAN - This mitigates the firewall rules being added for 1 above, but 2 is still added since this is monitoring the routing interface - Ping the device from a non-local subnet via the WAN interface; leave running - Observe mark set to ICMP session via conntrack - Bring up wan (e.g. ifup wan) - Observe mark set to ICMP session from above Signed-off-by: Tim Nordell <tnordell@airgain.com>
2023-06-26 17:24:27 +02:00
MM_UNKNOWN_WAN=$((mmdefault-3))
# 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: Fix packet routing when WAN interface is partially up This introduces a new concept of "unknown_wan" to mwan3. The action for this can be configured in the globals section the default of which is 'none'. This can be set to 'none', 'default', 'unreachable' or 'blacklist' switching out the matching ip rule for this match. This assignment for a connection is temporary and is re-resolved for each additional original direction packet through the firewall allowing the unknown WAN to start resolving once the ifup has finished for the given interface. An example configuration: config globals 'globals' option unknown_wan_action 'unreachable' Prior to this commit, mwan3 had multiple hit spots for packets in the following order: 1. Packets are checked to see if they originate from known WAN interfaces 2. Packets are checked to see if they destined for ipsets defined 3. Packets are checked against default WAN policies The WAN list is maintained via hotplug 'ifup'/'ifdown' events and the local route ipset list is maintained via monitoring the routing table. This means that while a WAN interface is brought up, the list for 2 is updated before the list for 1, since an interface is fully brought up before the ifup event is fired off. Additionally, we want to make sure we don't apply a WAN policy for incoming packets from a WAN interface that is in the process of being brought up. We can identify packets that are presumably coming from a WAN interface we don't recognize yet by eliminating all packets that the source comes from networks we don't know about in the ipsets that mwan3 manages. We have to be careful here to only match the original direction of the packet flow (e.g. for instance with ICMP, the ping request is in the ORIGINAL direction, and the response is in the REPLY direction) or else we might match something we didn't intend to. By modifying the rule set to the following: 1. Packets are checked to see if they are in a REPLY direction of flow 2. Packets are checked to see if they originate from known WAN interfaces 3. Packets are checked to see if they not sourced from ipsets defined 4. Packets are checked to see if they destined for ipsets defined 5. Packets are checked against default WAN policies If a packet is in the REPLY direction of flow, we definitely don't want to do any routing table assignments - we only want to do this for the original direction of traffic flow. This reduces the amount of rules parsed within mwan3. If a packet is not sourced from a defined ipset, this should match any packet originating from a "default route" upstream. We do this post the known WAN interface check since we don't know what mask to apply to this packet at this time until the 'ifup' has completed. It's also setup to reevaluate this decision by clearing this specific mark when a new packet comes in in the REPLY direction of flow before any subsequent evaluations. This allows additional packets for the same connection to eventually be assigned the appropriate mask once the 'ifup' has finished. One easy way to test this out before and after this change is to: - Bring down wan (e.g. ifdown wan) - Manually bring up WAN - This mitigates the firewall rules being added for 1 above, but 2 is still added since this is monitoring the routing interface - Ping the device from a non-local subnet via the WAN interface; leave running - Observe mark set to ICMP session via conntrack - Bring up wan (e.g. ifup wan) - Observe mark set to ICMP session from above Signed-off-by: Tim Nordell <tnordell@airgain.com>
2023-06-26 17:24:27 +02:00
MMX_UNKNOWN_WAN=$(mwan3_id2mask MM_UNKNOWN_WAN 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
}
get_uptime() {
local uptime=$(cat /proc/uptime)
echo "${uptime%%.*}"
}
get_online_time() {
local time_n time_u iface
iface="$1"
time_u="$(cat "$MWAN3TRACK_STATUS_DIR/${iface}/ONLINE" 2>/dev/null)"
[ -z "${time_u}" ] || [ "${time_u}" = "0" ] || {
time_n="$(get_uptime)"
echo $((time_n-time_u))
}
}