#!/bin/sh
#
# Update the MP-TCP flags without the pached iproute2
#
# Author: Mario Krueger <openwrt at xedp3x.de>
# Released under GPL 3 or later

if [ -d "/proc/sys/net/mptcp" ]; then
        if ([ -f /proc/sys/net/mptcp/mptcp_enabled ] && [ `cat /proc/sys/net/mptcp/mptcp_enabled` = 0 ]) || ([ -f /proc/sys/net/mptcp/enabled ] && [ `cat /proc/sys/net/mptcp/enabled` = 0 ]); then
                echo "MPTCP is disabled!"
                exit 1
        fi
else
        echo "Your device don't support multipath-TCP."
        echo "You have to install the pached kernel to use MPTCP."
        echo "See http://multipath-tcp.org/ for details"
        exit 1
fi

# An endpoint address is one remote peers are told to open subflows to, so it
# has to be reachable from their WANs. `ip -j a show` lists every address on the
# device and this script used to announce all of them, which makes every client
# open MP_JOINs that can never complete. On a DigitalOcean droplet eth0 carries
# the public IPv4, the private anchor address (10.x/16) and an IPv6 link-local,
# and all of them were signalled.
#
# The failure hides well: the anchor address answers ping and TCP from a router,
# because it resolves through the VPN tunnel. A subflow does not go through the
# tunnel -- it is bound to a WAN, and from there the address is unreachable.
# Measured on the bench, every SYN-SENT during a transfer was
# `192.168.200.79%eth2 -> 10.19.0.6:65280`: 6 of every 10 MP_JOIN SYNs
# unanswered, 50622 sent against 7440 answered since boot, and the dead sockets
# sat in the subflow list for hours holding slots that real WANs needed.
#
# Scope filtering in the jsonfilter/jq expressions below drops link-local and
# loopback outright. Private addresses need judgement instead of a blanket rule:
# a VPS genuinely behind NAT has nothing else to announce, so they are dropped
# only when the device also has a public address of the same family.
omr_is_private_ip() {
	case "$1" in
		10.*|127.*|192.168.*)                    return 0 ;;
		172.1[6-9].*|172.2[0-9].*|172.3[01].*)   return 0 ;;
		169.254.*)                               return 0 ;;
		100.6[4-9].*|100.[7-9][0-9].*|100.1[01][0-9].*|100.12[0-7].*) return 0 ;;
		[fF][cCdD]*)                             return 0 ;;
		::1)                                     return 0 ;;
	esac
	return 1
}

omr_filter_endpoint_ips() {
	local ip have4=0 have6=0 out=

	for ip in $1; do
		omr_is_private_ip "$ip" && continue
		case "$ip" in
			*:*) have6=1 ;;
			*)   have4=1 ;;
		esac
	done

	for ip in $1; do
		if omr_is_private_ip "$ip"; then
			case "$ip" in
				*:*) [ "$have6" = "1" ] && continue ;;
				*)   [ "$have4" = "1" ] && continue ;;
			esac
		fi
		out="$out $ip"
	done

	echo $out
}

case $1 in
   "-h")
        echo "          Multipath-TCP configuration tool"
        echo "show/update flags:"
        echo "  multipath [device]"
        echo "  multipath device {on | off | backup | handover}"
        echo
        echo "show established conections: -c"
        echo "show fullmesh info: -f"
        echo "show kernel config: -k"
        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. Handover-flag indicates that his interface is not used at all (even "
        echo "no subflow being established), as long as there are other interfaces available."
        echo "See http://multipath-tcp.org/ for details"
        echo
        exit 0 ;;
   "-c")
        cat /proc/net/mptcp_net/mptcp
        exit 0;;
   "-f")
        cat /proc/net/mptcp_fullmesh
        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`
        else
            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
        echo See http://multipath-tcp.org/ for details
        exit 0 ;;
   "")
        for ifpath in /sys/class/net/*; do
                $0 ${ifpath##*/}
        done
        exit 0;;
   *);;
esac

DEVICE="$1"
TYPE="$2"
#FLAG_PATH=`find /sys/devices/ -path "*/net/$DEVICE/flags"`

[ -d "/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
        # Every id for this device, not just the first. The "off"/"on"/"signal"/
        # "backup" branches all loop over $ID to clear the device before adding
        # its addresses again, so `grep -m 1` meant a device with more than one
        # address (a dual-stack eth0 has at least two) never actually got
        # cleared: one endpoint was removed and the rest were left behind.
        # `multipath <dev> off` therefore could not turn a device off, and the
        # other branches relied on `endpoint add` refusing duplicates to stay
        # correct. IFF only reports the flags for display, so it still reads one.
        # The dev field is compared whole: grep "dev eth0" also matched eth0.100.
        ENDPOINTS=$(ip mptcp endpoint show | awk -v dev="$DEVICE" '{for (i = 1; i < NF; i++) if ($i == "dev" && $(i+1) == dev) {print; next}}')
        ID=$(echo "$ENDPOINTS" | awk '{print $3}')
        IFF=$(echo "$ENDPOINTS" | awk 'NR == 1 {print $4}')
        #IP=$(ip a show $DEVICE | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p')
	[ -f /usr/bin/jsonfilter ] && IP=$(ip -j a show $DEVICE | jsonfilter -e '@[0].addr_info[@.scope="global"].local')
        [ -f /usr/bin/jq ] && IP=$(ip -j a show $DEVICE | jq -r '.[0].addr_info[] | select(.scope=="global") | .local')
        IP=$(omr_filter_endpoint_ips "$IP")
	RMID=$(ip mptcp endpoint show | grep '::ffff' | awk '{ print $3 }')
        [ -n "$RMID" ] && ip mptcp endpoint delete id $RMID 2>&1 >/dev/null
        case $TYPE in
                "off")
                        [ -n "$ID" ] && {
                            for i in $ID; do
                                ip mptcp endpoint delete id $i 2>&1 >/dev/null
                            done
                        }
                        exit 0;;
                "on")
                        [ -n "$ID" ] && {
                            for i in $ID; do
                                ip mptcp endpoint delete id $i 2>&1 >/dev/null
                            done
                        }
                        for i in $IP; do
                            ip mptcp endpoint add $i dev $DEVICE subflow fullmesh
                        done
                        exit 0;;
                "signal")
                        [ -n "$ID" ] && {
                            for i in $ID; do
                                ip mptcp endpoint delete id $i 2>&1 >/dev/null
                            done
                        }
                        for i in $IP; do
                            ip mptcp endpoint add $i dev $DEVICE signal
                        done
                        exit 0;;
                "backup")
                        [ -n "$ID" ] && {
                            for i in $ID; do
                                ip mptcp endpoint delete id $i 2>&1 >/dev/null
                            done
                        }
                        for i in $IP; do
                            ip mptcp endpoint add $i dev $DEVICE backup fullmesh
                        done
                        exit 0;;
                "")
                        case "$IFF" in
                                "")          echo $DEVICE is deactivated;;
                                "subflow")   echo $DEVICE is in default mode;;
                                "backup")    echo $DEVICE is in backup mode;;
                                "signal")    echo $DEVICE is in signal mode;;
                                "fullmesh")  echo $DEVICE is in fullmesh mode;;
                                *)           echo "$DEVICE Unkown state!" && exit 1;;
                        esac
                        exit 0;;
                *) echo "Unkown flag! Use 'multipath -h' for help" && exit 1;;
        esac

fi

