Files

481 lines
17 KiB
Bash
Executable File

#!/bin/sh
#
# Update the MPTCP flags with or without the pached iproute2
#
# Author: Mario Krueger <openwrt at xedp3x.de>
# Ycarus (Yannick Chabanois) for OpenMPTCProuter <contact@openmptcprouter.com>
# Released under GPL 3 or later
if [ -d "/proc/sys/net/mptcp" ]; then
# read, not `cat` in two subshells: this runs on every call
if { [ -f /proc/sys/net/mptcp/mptcp_enabled ] && read -r mptcp_enabled < /proc/sys/net/mptcp/mptcp_enabled && [ "$mptcp_enabled" = 0 ]; } || { [ -f /proc/sys/net/mptcp/enabled ] && read -r mptcp_enabled < /proc/sys/net/mptcp/enabled && [ "$mptcp_enabled" = 0 ]; }; then
echo "MPTCP is disabled!"
exit 1
fi
else
echo "Your device don't support multipath-TCP."
exit 1
fi
# From an "ip mptcp endpoint show" listing ($1), set id_used when an endpoint
# has the id $2 and id_holder to its device, and dev_ids to the endpoint ids
# of device $3. Lines are "ADDR [port P] id ID [FLAGS...] [dev DEV]".
endpoint_ids() {
local listing="$1" want="$2" dev="$3" ep_line ep_id ep_dev
id_used=""
id_holder=""
dev_ids=""
while IFS= read -r ep_line; do
ep_id=""
ep_dev=""
set -- $ep_line
while [ "$#" -gt 1 ]; do
case "$1" in
id) ep_id="$2" ;;
dev) ep_dev="$2" ;;
esac
shift
done
[ -n "$ep_id" ] || continue
[ "$ep_id" = "$want" ] && {
id_used=1
id_holder="$ep_dev"
}
[ -n "$dev" ] && [ "$ep_dev" = "$dev" ] && dev_ids="$dev_ids $ep_id"
done <<EOF
$listing
EOF
}
# Add an endpoint with the flags $@ for each global address ($IP) of $DEVICE.
# The explicit id ($ENDPOINT_ID, the WAN's metric) goes to the first address,
# the IPv4 one on a dual-stack WAN, the one the VPS pins a WAN by. Each
# endpoint needs its own id: with the same one for all, the IPv6 add failed
# ("Resource busy") and IPv6 was left out of MPTCP. The next addresses get
# id+128 then id+192, above the WAN metrics so that they never take the id
# of another WAN, and an address left without one is not added at all: an
# id picked by the kernel would be one of those WAN ids.
# When another device still holds the id of the first address, it is added
# with an id picked by the kernel instead of failing on every tracker check,
# and "multipath -i" tells the callers to fix it once the id is free.
# The in-kernel path manager takes 8 endpoints at most (MPTCP_PM_ADDR_MAX;
# beyond: "Result not representable"). Added WAN by WAN as they come up, the
# extra IPv6 endpoints of the first WANs could take every slot and leave a
# later WAN out of MPTCP, with nothing logged. When full, the first endpoint
# of a WAN takes the slot of another WAN's extra one (id 128 and up), extra
# endpoints only use a free slot, and a refused add is logged.
MPTCP_ENDPOINTS_MAX=8
# The id of the endpoint whose address is $2 in the listing $1. The address
# is compared as the whole first field: "grep -F '1.2.3.4 '" also matched
# 11.2.3.4, the endpoint of another WAN, which was then deleted.
endpoint_id_of_addr() {
printf '%s\n' "$1" | awk -v a="$2" '$1 == a {
for (i = 2; i < NF; i++) if ($i == "id") { print $(i + 1); exit }
}'
}
# Whether the device $2 has an endpoint with an id below $3 in the listing $1
_dev_has_lower_id() {
local listing="$1" dev="$2" below="$3" ep_line ep_id ep_dev
[ -n "$dev" ] || return 1
while IFS= read -r ep_line; do
ep_id=""
ep_dev=""
set -- $ep_line
while [ "$#" -gt 1 ]; do
case "$1" in
id) ep_id="$2" ;;
dev) ep_dev="$2" ;;
esac
shift
done
[ "$ep_dev" = "$dev" ] && [ -n "$ep_id" ] && [ "$ep_id" -lt "$below" ] && return 0
done <<EOF
$listing
EOF
return 1
}
# Return 0 when a slot is free, or was freed by removing the extra endpoint
# with the highest id of another device. An id of 128 or more alone does not
# make an extra endpoint: one the kernel picked (no id given, or the WAN's id
# was held by another device) can be the only endpoint of a WAN, which was
# then taken out of MPTCP. The device must keep an endpoint with a lower id.
_free_endpoint_slot() {
local line ep_addr ep_id ep_dev victim_id="" victim_addr="" victim_dev="" count=0 listing
listing="$(ip mptcp endpoint show)"
while IFS= read -r line; do
[ -n "$line" ] || continue
count=$((count + 1))
set -- $line
ep_addr="$1"
ep_id=""
ep_dev=""
while [ "$#" -gt 1 ]; do
case "$1" in
id) ep_id="$2" ;;
dev) ep_dev="$2" ;;
esac
shift
done
[ -n "$ep_id" ] && [ "$ep_id" -ge 128 ] && [ "$ep_dev" != "$DEVICE" ] || continue
[ -z "$victim_id" ] || [ "$ep_id" -gt "$victim_id" ] || continue
_dev_has_lower_id "$listing" "$ep_dev" "$ep_id" || continue
victim_id="$ep_id"
victim_addr="$ep_addr"
victim_dev="$ep_dev"
done <<EOF
$listing
EOF
[ "$count" -lt "$MPTCP_ENDPOINTS_MAX" ] && return 0
[ -n "$victim_id" ] || return 1
ip mptcp endpoint delete id "$victim_id" >/dev/null 2>&1 || return 1
logger -t "MPTCP" "Endpoint limit ($MPTCP_ENDPOINTS_MAX) reached: $victim_addr of $victim_dev removed so that $DEVICE gets one"
}
add_endpoints() {
local addr addr_id err n=0
for addr in $IP; do
addr_id=""
if [ -n "$ENDPOINT_ID" ]; then
case "$n" in
0) addr_id="$ENDPOINT_ID" ;;
1) addr_id=$((ENDPOINT_ID + 128)) ;;
2) addr_id=$((ENDPOINT_ID + 192)) ;;
*) addr_id=256 ;;
esac
endpoint_ids "$endpoint" "$addr_id" "$DEVICE"
if [ -n "$id_used" ] && [ "$id_holder" != "$DEVICE" ]; then
logger -t "MPTCP" "Endpoint id $addr_id for $addr on $DEVICE is used by ${id_holder:-another endpoint}"
addr_id=256
fi
if [ "$addr_id" -gt 255 ]; then
if [ "$n" -gt 0 ]; then
logger -t "MPTCP" "No endpoint id left for $addr on $DEVICE, not added to MPTCP"
n=$((n + 1))
continue
fi
addr_id=""
fi
fi
if [ "$n" -eq 0 ]; then
_free_endpoint_slot
elif [ "$(ip mptcp endpoint show | grep -c .)" -ge "$MPTCP_ENDPOINTS_MAX" ]; then
logger -t "MPTCP" "Endpoint limit ($MPTCP_ENDPOINTS_MAX) reached: $addr on $DEVICE not added to MPTCP"
n=$((n + 1))
continue
fi
err="$(ip mptcp endpoint add "$addr" dev "$DEVICE" "$@" ${addr_id:+id "$addr_id"} 2>&1)" || \
logger -t "MPTCP" "Can't add endpoint $addr on $DEVICE: $err"
n=$((n + 1))
done
}
case $1 in
"-h")
echo " Multipath-TCP configuration tool"
echo "show/update flags:"
echo " multipath [device]"
if [ -f /proc/sys/net/mptcp/mptcp_enabled ]; then
echo " multipath device {on | off | backup }"
else
echo " multipath device {on | off | signal | backup } [id]"
fi
echo
echo "show established conections: -c"
echo "show fullmesh info: -f"
echo "show kernel config: -k"
echo "show MPTCP info: -m"
if [ -f /proc/sys/net/mptcp/enabled ]; then
echo "verify MPTCP interface: -v device"
echo "check the endpoint id of an interface: -i device id"
fi
echo
echo "Flag on the device, to enable/disable MPTCP for this interface. The backup-flag"
echo "will allow a subflow to be established across this interface, but only be used"
echo "as backup."
echo
echo "Optional trailing [id] (on/signal/backup only): explicit MPTCP endpoint id"
echo "(0-255) to pass to 'ip mptcp endpoint add ... id <id>' instead of letting the"
echo "kernel auto-assign one. Needed for a stable id per WAN across reconnects, e.g."
echo "so a peer (VPS) can pin traffic to this WAN via the subflow's remote_id."
echo "It goes to the first address of the device, the next ones get id+128 and"
echo "id+192 (IPv6 on a dual-stack WAN)."
echo
exit 0 ;;
"-c")
if [ -f /proc/net/mptcp_net/mptcp ]; then
cat /proc/net/mptcp_net/mptcp
else
ss -M
fi
exit 0;;
"-f")
if [ -f /proc/net/mptcp_fullmesh ]; then
cat /proc/net/mptcp_fullmesh
else
ip mptcp endpoint | grep fullmesh
fi
exit 0;;
"-k")
if [ -f /proc/sys/net/mptcp/mptcp_enabled ]; then
echo Enabled: `cat /proc/sys/net/mptcp/mptcp_enabled`
elif [ -f /proc/sys/net/mptcp/enabled ]; then
echo Enabled: `cat /proc/sys/net/mptcp/enabled`
fi
if [ -f /proc/sys/net/mptcp/mptcp_path_manager ]; then
echo Path Manager: `cat /proc/sys/net/mptcp/mptcp_path_manager`
fi
if [ -f /proc/sys/net/mptcp/mptcp_checksum ]; then
echo Use checksum: `cat /proc/sys/net/mptcp/mptcp_checksum`
elif [ -f /proc/sys/net/mptcp/checksum_enabled ]; then
echo Use checksum: `cat /proc/sys/net/mptcp/checksum_enabled`
fi
if [ -f /proc/sys/net/mptcp/mptcp_scheduler ]; then
echo Scheduler: `cat /proc/sys/net/mptcp/mptcp_scheduler`
fi
if [ -f /proc/sys/net/mptcp/mptcp_syn_retries ]; then
echo Syn retries: `cat /proc/sys/net/mptcp/mptcp_syn_retries`
fi
if [ -f /proc/sys/net/mptcp/mptcp_debug ]; then
echo Debugmode: `cat /proc/sys/net/mptcp/mptcp_debug`
fi
echo
exit 0 ;;
"-m")
if [ -f /proc/net/mptcp_net/snmp ]; then
cat /proc/net/mptcp_net/snmp
else
# Absolute counters (-a), without touching nstat's history (-s):
# plain "nstat -z" printed the delta since anyone's previous nstat
# call and reset it, so two viewers zeroed each other's numbers.
nstat -a -s -z | grep -i mptcp
fi
exit 0 ;;
"-v")
if [ -f /proc/sys/net/mptcp/enabled ]; then
DEVICE="$2"
endpoint="$(ip mptcp endpoint show)"
if [ -f /usr/bin/jsonfilter ]; then
IP=$(ip -j a show $DEVICE scope global | jsonfilter -e '@[0].addr_info[*].local')
elif [ -f /usr/bin/jq ]; then
IP=$(ip -j a show $DEVICE scope global | jq -r '.[0].addr_info[].local')
else
echo "jsonfilter or jq are required"
exit 1
fi
if [ -n "$IP" ] && [ -n "$endpoint" ]; then
for i in $IP; do
ID=$(endpoint_id_of_addr "${endpoint}" "$i")
[ -n "$ID" ] && break
done
fi
fi
if [ -n "$ID" ]; then
echo "OK"
exit 0
else
echo "NOK"
exit 1
fi
;;
"-i")
# Exit 1 when the endpoint of device $2 doesn't have the explicit id $3
# although no other endpoint holds it, so that recreating it gives that
# id back. An endpoint that kept an old id is never fixed by the mode
# checks of the callers, and it blocks the WAN that now has this id.
[ -f /proc/sys/net/mptcp/enabled ] || exit 0
case "$3" in
''|*[!0-9]*) exit 0 ;;
esac
[ "$3" -le 255 ] || exit 0
endpoint_ids "$(ip mptcp endpoint show)" "$3" "$2"
if [ -z "$dev_ids" ] || [ -n "$id_used" ]; then
exit 0
fi
exit 1
;;
"")
for ifpath in /sys/class/net/*; do
$0 ${ifpath##*/}
done
exit 0;;
*);;
esac
DEVICE="$1"
TYPE="$2"
# Optional explicit MPTCP endpoint id (stable across delete/recreate, unlike
# the kernel's auto-assigned one) so a peer can key on subflow->remote_id.
# Anything that isn't a plain 0-255 integer is ignored (kernel auto-assigns).
ENDPOINT_ID="$3"
case "$ENDPOINT_ID" in
''|*[!0-9]*) ENDPOINT_ID="" ;;
*) [ "$ENDPOINT_ID" -ge 0 ] 2>/dev/null && [ "$ENDPOINT_ID" -le 255 ] 2>/dev/null || ENDPOINT_ID="" ;;
esac
#FLAG_PATH=`find /sys/devices/ -path "*/net/$DEVICE/flags"`
[ -e "/sys/class/net/$DEVICE/" ] || {
#echo "Device '$DEVICE' can't found!"
#echo "Use the hardware name like in ifconfig"
exit 1
}
if [ -f /proc/sys/net/mptcp/mptcp_enabled ]; then
FLAG_PATH="/sys/class/net/$DEVICE/flags"
IFF=`cat $FLAG_PATH`
IFF_OFF="0x80000"
IFF_ON="0x00"
IFF_BACKUP="0x100000"
IFF_HANDOVER="0x200000"
IFF_MASK="0x380000"
case $TYPE in
"off") FLAG=$IFF_OFF;;
"on") FLAG=$IFF_ON;;
"backup") FLAG=$IFF_BACKUP;;
"handover") FLAG=$IFF_HANDOVER;;
"")
IFF=`printf "0x%02x" $(($IFF&$IFF_MASK))`
case "$IFF" in
$IFF_OFF) echo $DEVICE is deactivated;;
$IFF_ON) echo $DEVICE is in default mode;;
$IFF_BACKUP) echo $DEVICE is in backup mode;;
$IFF_HANDOVER) echo $DEVICE is in handover mode;;
*) echo "Unkown state!" && exit 1;;
esac
exit 0;;
*) echo "Unkown flag! Use 'multipath -h' for help" && exit 1;;
esac
printf "0x%02x" $(($(($IFF^$(($IFF&$IFF_MASK))))|$FLAG)) > $FLAG_PATH
else
endpoint="$(ip mptcp endpoint show)"
if [ -n "$TYPE" ]; then
# Remove not needed if* interfaces in MPTCP
[ -n "$endpoint" ] && oldids=$(echo "${endpoint}" | grep "dev if" | awk '{ print $3 }')
[ -n "$oldids" ] && {
for oldid in $oldids; do
ip mptcp endpoint delete id $oldid >/dev/null 2>&1
done
endpoint="$(ip mptcp endpoint show)"
}
fi
if [ -n "$endpoint" ]; then
# The ids of the device's endpoints, and the flags of its first
# endpoint in sorted order. A sort|grep|awk pipeline for each of the
# three cost 15 processes per call, and a status query runs for every
# device ("multipath" alone lists them all): one pass over the list
# gives the same values without any when the device has one endpoint.
ep_count=0
while IFS= read -r ep_line; do
case "$ep_line" in
*"dev $DEVICE "*)
ep_first="$ep_line"
ep_count=$((ep_count + 1))
;;
esac
done <<EOF
$endpoint
EOF
if [ "$ep_count" -gt 1 ]; then
ID=$(echo "${endpoint}" | sort | grep -F "dev $DEVICE " | awk '{print $3}')
ep_first=$(echo "${endpoint}" | sort | grep -F -m 1 "dev $DEVICE ")
fi
if [ "$ep_count" -gt 0 ]; then
read -r ep_addr ep_idkw ep_id IFF IFF2 ep_rest <<EOF
$ep_first
EOF
[ "$ep_count" -gt 1 ] || ID="$ep_id"
fi
fi
if [ -n "$TYPE" ]; then
#IP=$(ip a show $DEVICE | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p')
if [ -f /usr/bin/jsonfilter ]; then
IP=$(ip -j a show $DEVICE scope global | jsonfilter -e '@[0].addr_info[*].local')
elif [ -f /usr/bin/jq ]; then
IP=$(ip -j a show $DEVICE scope global | jq -r '.[0].addr_info[].local')
else
echo "jsonfilter or jq are required"
exit 1
fi
if [ -z "$ID" ] && [ -n "$IP" ] && [ -n "$endpoint" ]; then
for i in $IP; do
ID=$(endpoint_id_of_addr "${endpoint}" "$i")
[ -n "$ID" ] && break
done
fi
[ -n "$endpoint" ] && RMID=$(echo "${endpoint}" | grep '::ffff' | awk '{ print $3 }')
# One id per call: "delete id 3 4" is a syntax error for ip, so
# with two such endpoints neither was removed
for i in $RMID; do
ip mptcp endpoint delete id $i >/dev/null 2>&1
done
fi
case $TYPE in
"off")
[ -n "$ID" ] && {
for i in $ID; do
ip mptcp endpoint delete id $i >/dev/null 2>&1
done
}
exit 0;;
"on")
[ -z "$IP" ] && {
echo "No global IP found on $DEVICE"
exit 1
}
[ -n "$ID" ] && {
for i in $ID; do
ip mptcp endpoint delete id $i >/dev/null 2>&1
done
}
add_endpoints subflow fullmesh
exit 0;;
"signal")
[ -z "$IP" ] && {
echo "No global IP found on $DEVICE"
exit 1
}
[ -n "$ID" ] && {
for i in $ID; do
ip mptcp endpoint delete id $i >/dev/null 2>&1
done
}
#add_endpoints signal subflow fullmesh
add_endpoints signal
exit 0;;
"backup")
[ -z "$IP" ] && {
echo "No global IP found on $DEVICE"
exit 1
}
[ -n "$ID" ] && {
for i in $ID; do
ip mptcp endpoint delete id $i >/dev/null 2>&1
done
}
add_endpoints subflow backup fullmesh
exit 0;;
"")
case "$IFF" in
"") echo $DEVICE is deactivated;;
"subflow")
case "$IFF2" in
"backup") echo $DEVICE is in backup mode;;
"fullmesh") echo $DEVICE is in default mode;;
"") echo $DEVICE is in subflow mode;;
esac
;;
"signal") echo $DEVICE is in signal mode;;
*) echo "$DEVICE Unkown state!" && exit 1;;
esac
exit 0;;
*) echo "Unkown flag! Use 'multipath -h' for help" && exit 1;;
esac
fi