From be04e6d733fa8c66714bd496ee62918d764e217b Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Wed, 9 Aug 2023 21:54:25 +0200 Subject: [PATCH 1/7] Out kernel path manager only since 5.19 --- luci-app-mptcp/luasrc/model/cbi/mptcp.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/luci-app-mptcp/luasrc/model/cbi/mptcp.lua b/luci-app-mptcp/luasrc/model/cbi/mptcp.lua index e8f827bee..17a2f6a69 100644 --- a/luci-app-mptcp/luasrc/model/cbi/mptcp.lua +++ b/luci-app-mptcp/luasrc/model/cbi/mptcp.lua @@ -65,10 +65,13 @@ end -- if tonumber(uname.release:sub(1,4)) >= 5.15 then if uname.release:sub(1,4) == "5.15" or uname.release:sub(1,1) == "6" then - o = s:option(ListValue, "mptcp_pm_type", translate("Path Manager type")) - o:value(0, translate("In-kernel path manager")) - o:value(1, translate("Userspace path manager")) - o.default = 0 + if uname.release:sub(1,1) == "6" then + -- Only available since 5.19 + o = s:option(ListValue, "mptcp_pm_type", translate("Path Manager type")) + o:value(0, translate("In-kernel path manager")) + o:value(1, translate("Userspace path manager")) + o.default = 0 + end o = s:option(Value, "mptcp_subflows", translate("Max subflows"),translate("specifies the maximum number of additional subflows allowed for each MPTCP connection")) o.datatype = "uinteger" From 9714c5bfbcc63e5888aa2cac332c13e7821963cc Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Wed, 9 Aug 2023 21:54:58 +0200 Subject: [PATCH 2/7] Fix wizard part to do a speedtest after validation --- luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua b/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua index b760b205c..85d04ae72 100644 --- a/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua +++ b/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua @@ -457,7 +457,7 @@ function wizard_add() end ucic:set("openmptcprouter",intf,"testspeed",testspeed) if testspeed == "1" then - ucic:set("openmptcprouter",intf,"testspeed_lc") + ucic:delete("openmptcprouter",intf,"testspeed_lc") end if downloadspeed ~= "0" and downloadspeed ~= "" then if sqmautorate == "1" and (ucic:get("network",intf,"downloadspeed") ~= downloadspeed or ucic:get("sqm",intf,"max_download") == "" or ucic:get("sqm",intf,"download") == "0") then From 8ea31add308fba40ff5f2cfc44e951ab0fa8fb7c Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Wed, 9 Aug 2023 21:55:31 +0200 Subject: [PATCH 3/7] Use omr-tracker default IPs for SQM-Autorate --- .../usr/share/sqm-autorate/config_template.sh | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/config_template.sh b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/config_template.sh index a3ac23966..63848c0bf 100755 --- a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/config_template.sh +++ b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/config_template.sh @@ -53,16 +53,16 @@ pinger_binary=$(uci -q get sqm.${INTERFACE}.pinger || echo 'tsping') # so e.g. if 6 reflectors are specified and the number of pingers is set to 4, the first 4 reflectors will be used initially # and the remaining 2 reflectors in the list will be used in the event any of the first 4 go bad # a bad reflector will go to the back of the queue on reflector rotation -#reflectors=$(uci -q get omr-tracker.defaults.hosts) -reflectors=( -"1.1.1.1" "1.0.0.1" # Cloudflare -"8.8.8.8" "8.8.4.4" # Google -"9.9.9.9" "9.9.9.10" "9.9.9.11" # Quad9 -"94.140.14.15" "94.140.14.140" "94.140.14.141" "94.140.15.15" "94.140.15.16" # AdGuard -"64.6.65.6" "156.154.70.1" "156.154.70.2" "156.154.70.3" "156.154.70.4" "156.154.70.5" "156.154.71.1" "156.154.71.2" "156.154.71.3" "156.154.71.4" "156.154.71.5" # Neustar -"208.67.220.2" "208.67.220.123" "208.67.220.220" "208.67.222.2" "208.67.222.123" # OpenDNS -"185.228.168.9" "185.228.168.10" # CleanBrowsing -) +reflectors=( $(uci -q get omr-tracker.defaults.hosts) ) +#reflectors=( +#"1.1.1.1" "1.0.0.1" # Cloudflare +#"8.8.8.8" "8.8.4.4" # Google +#"9.9.9.9" "9.9.9.10" "9.9.9.11" # Quad9 +#"94.140.14.15" "94.140.14.140" "94.140.14.141" "94.140.15.15" "94.140.15.16" # AdGuard +#"64.6.65.6" "156.154.70.1" "156.154.70.2" "156.154.70.3" "156.154.70.4" "156.154.70.5" "156.154.71.1" "156.154.71.2" "156.154.71.3" "156.154.71.4" "156.154.71.5" # Neustar +#"208.67.220.2" "208.67.220.123" "208.67.220.220" "208.67.222.2" "208.67.222.123" # OpenDNS +#"185.228.168.9" "185.228.168.10" # CleanBrowsing +#) randomize_reflectors=1 # enable (1) or disable (0) randomization of reflectors on startup From 7674501a93622133c8cc4808e3e4650da94b0864 Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Wed, 9 Aug 2023 21:56:02 +0200 Subject: [PATCH 4/7] Set pm_type via mptcp init script --- mptcp/files/etc/init.d/mptcp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mptcp/files/etc/init.d/mptcp b/mptcp/files/etc/init.d/mptcp index 5c0c43d8e..584594f6b 100755 --- a/mptcp/files/etc/init.d/mptcp +++ b/mptcp/files/etc/init.d/mptcp @@ -30,6 +30,7 @@ global_multipath_settings() { config_get mptcp_rr_num_segments globals mptcp_rr_num_segments config_get mptcp_version globals mptcp_version "0" config_get mptcp_stale_loss_cnt globals mptcp_stale_loss_cnt "4" + config_get mptcp_pm_type globals mptcp_pm_type "1" [ "$multipath" = "enable" ] && multipath_status=1 @@ -52,11 +53,10 @@ global_multipath_settings() { ip mptcp limits set add_addr_accepted $mptcp_add_addr_accepted subflows $mptcp_subflows 2>&1 >/dev/null #[ -z "$mptcp_debug" ] || sysctl -qew net.mptcp.mptcp_debug="$mptcp_debug" [ -z "$mptcp_checksum" ] || sysctl -qew net.mptcp.mptcp_checksum="$mptcp_checksum" - [ -z "$mptcp_path_manager" ] || sysctl -qew net.mptcp.mptcp_path_manager="$mptcp_path_manager" - [ -z "$mptcp_scheduler" ] || sysctl -qew net.mptcp.mptcp_scheduler="$mptcp_scheduler" [ -z "$mptcp_add_addr_timeout" ] || sysctl -qew net.mptcp.add_addr_timeout="$mptcp_add_addr_timeout" [ -z "$mptcp_checksum" ] || sysctl -qew net.mptcp.checksum_enabled="$mptcp_checksum" [ -z "$mptcp_stale_loss_cnt" ] || sysctl -qew net.mptcp.stale_loss_cnt="$mptcp_stale_loss_cnt" + [ -z "$mptcp_pm_type" ] || sysctl -qew net.mptcp.pm_type="$mptcp_pm_type" fi [ -z "$congestion" ] || sysctl -qew net.ipv4.tcp_congestion_control="$congestion" } @@ -195,10 +195,12 @@ interface_multipath_settings() { #echo "îface: $iface" - if [ "$mode" = "master" ]; then + if [ "$(uci -q get openmptcprouter.settings.force_multipath)" != "0" ]; then + if [ "$mode" = "master" ]; then multipath "$iface" "on" - else - multipath "$iface" "$mode" + else + multipath "$iface" "$mode" + fi fi #[ "$mode" = "off" ] && { # ip rule del table $id > /dev/null 2>&1 From 33ef12e7e3f135989afc2e7c72eef9662f6cb3fe Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Wed, 9 Aug 2023 21:56:48 +0200 Subject: [PATCH 5/7] Can do speedtest in multiple server config: current server is used for test --- .../share/omr/post-tracking.d/post-tracking | 83 ++++++++++--------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/mptcp/files/usr/share/omr/post-tracking.d/post-tracking b/mptcp/files/usr/share/omr/post-tracking.d/post-tracking index ae6a868df..29c32df20 100755 --- a/mptcp/files/usr/share/omr/post-tracking.d/post-tracking +++ b/mptcp/files/usr/share/omr/post-tracking.d/post-tracking @@ -1291,7 +1291,7 @@ if [ -n "$OMR_TRACKER_INTERFACE" ] && ([ "$multipath_config" = "on" ] || [ "$mul config_load openmptcprouter config_foreach glorytunudppath server fi -[ "$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.multipathvpn)" != "1" ] && { +[ "$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.multipathvpn)" != "1" ] && [ "$(uci -q get openmptcprouter.settings.force_multipath)" != "0" ] && { [ "$multipath_status" = "$multipath_config" ] || { if [ "$(sysctl -qen net.mptcp.mptcp_enabled | tr -d '\n')" = "1" ] || [ "$(sysctl -qen net.mptcp.enabled | tr -d '\n')" = "1" ]; then if [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.force_link)" != "0" ]; then @@ -1610,48 +1610,55 @@ if [ -n "$OMR_TRACKER_INTERFACE" ] && [ -n "$OMR_TRACKER_DEVICE" ]; then fi if [ "$(pgrep -f omr-test-speed-server)" = "" ] && [ "$(uci -q get openmptcprouter.${OMR_TRACKER_INTERFACE}.testspeed)" = "1" ] && [ -z "$(uci -q get openmptcprouter.${OMR_TRACKER_INTERFACE}.testspeed_lc)" ]; then - download_speed=$(/bin/omr-test-speed-server vps ${OMR_TRACKER_DEVICE} fasttest) - download_speed2=$(/bin/omr-test-speed-server vps ${OMR_TRACKER_DEVICE} fasttest) - download_speed3=$(/bin/omr-test-speed-server vps ${OMR_TRACKER_DEVICE} fasttest) - [ "$download_speed2" -gt "$download_speed" ] && download_speed=${download_speed2} - [ "$download_speed3" -gt "$download_speed" ] && download_speed=${download_speed3} - download_speed=$((download_speed/1000)) - if [ -n "$download_speed" ] && [ "$download_speed" != "0" ]; then - upload_speed=$(/bin/omr-test-speed-server vps ${OMR_TRACKER_DEVICE} fasttest upload) - upload_speed2=$(/bin/omr-test-speed-server vps ${OMR_TRACKER_DEVICE} fasttest upload) - upload_speed3=$(/bin/omr-test-speed-server vps ${OMR_TRACKER_DEVICE} fasttest upload) - [ "$upload_speed2" -gt "$upload_speed" ] && upload_speed=${upload_speed2} - [ "$upload_speed3" -gt "$upload_speed" ] && upload_speed=${upload_speed3} - - # Set Download speed settings - if [ "$(uci -q get sqm.${OMR_TRACKER_INTERFACE}.autorate)" = "1" ]; then - uci -q set sqm.${OMR_TRACKER_INTERFACE}.download=$((download_speed*65/100)) - uci -q set sqm.${OMR_TRACKER_INTERFACE}.max_download=${download_speed} - uci -q set sqm.${OMR_TRACKER_INTERFACE}.min_download=$((download_speed*10/100)) - else - uci -q set sqm.${OMR_TRACKER_INTERFACE}.download=$((download_speed*95/100)) - fi - _log "Calculated ${OMR_TRACKER_INTERFACE} download speed: ${download_speed}" - uci -q set network.${OMR_TRACKER_INTERFACE}.downloadspeed=${download_speed} + launch_speedtest() { + local server=$1 + [ "$(uci -q get openmptcprouter.${server}.current)" != "1" ] && return + download_speed=$(/bin/omr-test-speed-server ${server} ${OMR_TRACKER_DEVICE} fasttest) + download_speed2=$(/bin/omr-test-speed-server ${server} ${OMR_TRACKER_DEVICE} fasttest) + download_speed3=$(/bin/omr-test-speed-server ${server} ${OMR_TRACKER_DEVICE} fasttest) + [ "$download_speed2" -gt "$download_speed" ] && download_speed=${download_speed2} + [ "$download_speed3" -gt "$download_speed" ] && download_speed=${download_speed3} + download_speed=$((download_speed/1000)) + if [ -n "$download_speed" ] && [ "$download_speed" != "0" ]; then + upload_speed=$(/bin/omr-test-speed-server ${server} ${OMR_TRACKER_DEVICE} fasttest upload) + upload_speed2=$(/bin/omr-test-speed-server ${server} ${OMR_TRACKER_DEVICE} fasttest upload) + upload_speed3=$(/bin/omr-test-speed-server ${server} ${OMR_TRACKER_DEVICE} fasttest upload) + [ "$upload_speed2" -gt "$upload_speed" ] && upload_speed=${upload_speed2} + [ "$upload_speed3" -gt "$upload_speed" ] && upload_speed=${upload_speed3} - # Set Upload speed settings - upload_speed=$((upload_speed/1000)) - if [ -n "$upload_speed" ] && [ "$upload_speed" != "0" ]; then + # Set Download speed settings if [ "$(uci -q get sqm.${OMR_TRACKER_INTERFACE}.autorate)" = "1" ]; then - uci -q set sqm.${OMR_TRACKER_INTERFACE}.upload=$((upload_speed*65/100)) - uci -q set sqm.${OMR_TRACKER_INTERFACE}.max_upload=${upload_speed} - uci -q set sqm.${OMR_TRACKER_INTERFACE}.min_upload=$((upload_speed*10/100)) + uci -q set sqm.${OMR_TRACKER_INTERFACE}.download=$((download_speed*65/100)) + uci -q set sqm.${OMR_TRACKER_INTERFACE}.max_download=${download_speed} + uci -q set sqm.${OMR_TRACKER_INTERFACE}.min_download=$((download_speed*10/100)) else - uci -q set sqm.${OMR_TRACKER_INTERFACE}.upload=$((upload_speed*95/100)) + uci -q set sqm.${OMR_TRACKER_INTERFACE}.download=$((download_speed*95/100)) + fi + _log "Calculated ${OMR_TRACKER_INTERFACE} download speed: ${download_speed}" + uci -q set network.${OMR_TRACKER_INTERFACE}.downloadspeed=${download_speed} + + # Set Upload speed settings + upload_speed=$((upload_speed/1000)) + if [ -n "$upload_speed" ] && [ "$upload_speed" != "0" ]; then + if [ "$(uci -q get sqm.${OMR_TRACKER_INTERFACE}.autorate)" = "1" ]; then + uci -q set sqm.${OMR_TRACKER_INTERFACE}.upload=$((upload_speed*65/100)) + uci -q set sqm.${OMR_TRACKER_INTERFACE}.max_upload=${upload_speed} + uci -q set sqm.${OMR_TRACKER_INTERFACE}.min_upload=$((upload_speed*10/100)) + else + uci -q set sqm.${OMR_TRACKER_INTERFACE}.upload=$((upload_speed*95/100)) + fi + _log "Calculated ${OMR_TRACKER_INTERFACE} upload speed: ${upload_speed}" + uci -q set network.${OMR_TRACKER_INTERFACE}.uploadspeed=${upload_speed} + uci -q set openmptcprouter.$OMR_TRACKER_INTERFACE.testspeed_lc=$(date +"%s") + uci commit network + uci commit sqm + uci commit openmptcprouter fi - _log "Calculated ${OMR_TRACKER_INTERFACE} upload speed: ${upload_speed}" - uci -q set network.${OMR_TRACKER_INTERFACE}.uploadspeed=${upload_speed} fi - uci -q set openmptcprouter.$OMR_TRACKER_INTERFACE.testspeed_lc=$(date +"%s") - uci commit network - uci commit sqm - uci commit openmptcprouter - fi + } + config_load openmptcprouter + config_foreach launch_speedtest server + sleep 5 fi From 8f5b99689fe392089e1b4cb5b4a7ff53909a2324 Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Wed, 9 Aug 2023 21:57:11 +0200 Subject: [PATCH 6/7] Fix omr-test-speed --- openmptcprouter/files/bin/omr-test-speed | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openmptcprouter/files/bin/omr-test-speed b/openmptcprouter/files/bin/omr-test-speed index f80c6d1ce..54b761cca 100755 --- a/openmptcprouter/files/bin/omr-test-speed +++ b/openmptcprouter/files/bin/omr-test-speed @@ -19,7 +19,7 @@ INTERFACE="$i" exit 0 } -[ "$FASTTEST" ] || echo "Select best test server..." +[ "$FASTTEST" = true ] || echo "Select best test server..." HOSTLST="http://scaleway.testdebit.info/10G.iso http://bordeaux.testdebit.info/10G.iso http://aix-marseille.testdebit.info/10G.iso http://lyon.testdebit.info/10G.iso http://lille.testdebit.info/10G.iso http://paris.testdebit.info/10G.iso http://appliwave.testdebit.info/10G/10G.iso http://speedtest.frankfurt.linode.com/garbage.php?ckSize=10000 http://speedtest.tokyo2.linode.com/garbage.php?ckSize=10000 http://speedtest.singapore.linode.com/garbage.php?ckSize=10000 http://speedtest.newark.linode.com/garbage.php?ckSize=10000 http://speedtest.atlanta.linode.com/garbage.php?ckSize=10000 http://speedtest.dallas.linode.com/garbage.php?ckSize=10000 http://speedtest.fremont.linode.com/garbage.php?ckSize=10000 https://speed.hetzner.de/10GB.bin http://ipv4.bouygues.testdebit.info/10G.iso http://par.download.datapacket.com/10000mb.bin http://nyc.download.datapacket.com/10000mb.bin http://ams.download.datapacket.com/10000mb.bin http://fra.download.datapacket.com/10000mb.bin http://lon.download.datapacket.com/10000mb.bin http://mad.download.datapacket.com/10000mb.bin http://prg.download.datapacket.com/10000mb.bin http://sto.download.datapacket.com/10000mb.bin http://vie.download.datapacket.com/10000mb.bin http://war.download.datapacket.com/10000mb.bin http://atl.download.datapacket.com/10000mb.bin http://chi.download.datapacket.com/10000mb.bin http://lax.download.datapacket.com/10000mb.bin http://mia.download.datapacket.com/10000mb.bin http://nyc.download.datapacket.com/10000mb.bin http://speedtest.milkywan.fr/files/10G.iso" bestping="9999" for pinghost in $HOSTLST; do @@ -29,7 +29,7 @@ for pinghost in $HOSTLST; do else ping=$(ping -4 -c1 -w2 -I $INTERFACE -B $domain | cut -d "/" -s -f5 | cut -d "." -f1 | tr -d '\n') fi - [ "$FASTTEST" ] || echo "host: $domain - ping: $ping" + [ "$FASTTEST" = true ] || echo "host: $domain - ping: $ping" if [ -n "$ping" ] && [ "$ping" -lt "$bestping" ]; then bestping=$ping HOST=$pinghost @@ -38,7 +38,7 @@ done [ -z "$HOST" ] && HOST="https://speed.hetzner.de/10GB.bin" -[ "$FASTTEST" ] || echo "Best server is $HOST, running test:" +[ "$FASTTEST" = true ] || echo "Best server is $HOST, running test:" trap : HUP INT TERM if [ -z "$INTERFACE" ]; then curl -4 -o /dev/null $HOST || echo @@ -51,11 +51,11 @@ else ipset add ss_rules_dst_bypass_all $ip done fi - if [ "$FASTTEST" ]; then + if [ "$FASTTEST" = true ]; then avg_speed=$(curl -4 --max-time 10 -o /dev/null -qfsS -w '%{speed_download}' --interface $INTERFACE $HOST 2>/dev/null) echo "$avg_speed" else - curl -4 --max-time 4 -o /dev/null --interface $INTERFACE $HOST || echo + curl -4 -o /dev/null --interface $INTERFACE $HOST || echo fi if [ -n "$(ipset list 2>/dev/null | grep ss_rules)" ]; then for ip in $hostip; do From 9faa13a09942f89e632160a3a409adbf78361b5e Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Wed, 9 Aug 2023 21:57:52 +0200 Subject: [PATCH 7/7] Update mptcpd and add ndiffports path manager --- mptcpd/Makefile | 8 +- mptcpd/patches/ndiffports.patch | 703 ++++++++++++++++++++++++++++++++ 2 files changed, 707 insertions(+), 4 deletions(-) create mode 100644 mptcpd/patches/ndiffports.patch diff --git a/mptcpd/Makefile b/mptcpd/Makefile index 1b67f08fd..b0a21ac54 100644 --- a/mptcpd/Makefile +++ b/mptcpd/Makefile @@ -1,5 +1,5 @@ # -# Copyright (C) 2020-2022 Ycarus (Yannick Chabanois) for OpenMPTCProuter project +# Copyright (C) 2020-2023 Ycarus (Yannick Chabanois) for OpenMPTCProuter project # # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. @@ -9,9 +9,9 @@ include $(TOPDIR)/rules.mk PKG_NAME:=mptcpd PKG_SOURCE_PROTO:=git -PKG_SOURCE_URL:=https://github.com/intel/mptcpd.git -PKG_SOURCE_VERSION:=88bea6802379ee1cdbcba4817351b63fd1489537 -PKG_VERSION:=0.9-$(PKG_SOURCE_VERSION) +PKG_SOURCE_URL:=https://github.com/multipath-tcp/mptcpd.git +PKG_SOURCE_VERSION:=863e83f255f55dfd0ba9325d816bd99f6d9da0c2 +PKG_VERSION:=0.12-$(PKG_SOURCE_VERSION) PKG_RELEASE:=1 PKG_MAINTAINER:=Ycarus (Yannick Chabanois) diff --git a/mptcpd/patches/ndiffports.patch b/mptcpd/patches/ndiffports.patch new file mode 100644 index 000000000..29f3e91e6 --- /dev/null +++ b/mptcpd/patches/ndiffports.patch @@ -0,0 +1,703 @@ +From f127effabdb5aa1114613b637c96b212a603c57a Mon Sep 17 00:00:00 2001 +From: Mat Martineau +Date: Tue, 14 Jun 2022 16:35:18 -0700 +Subject: [PATCH] plugins: Add ndiffports plugin + +This plugin creates N different subflows for each MPTCP connection, all +using the same single interface on each peer. Only the original local +and remote IP addresses are used, but with unique port numbers for each +subflow. +--- + plugins/path_managers/Makefile.am | 17 +- + plugins/path_managers/ndiffports.c | 647 +++++++++++++++++++++++++++++ + 2 files changed, 663 insertions(+), 1 deletion(-) + create mode 100644 plugins/path_managers/ndiffports.c + +diff --git a/plugins/path_managers/Makefile.am b/plugins/path_managers/Makefile.am +index 50719921..8320b750 100644 +--- a/plugins/path_managers/Makefile.am ++++ b/plugins/path_managers/Makefile.am +@@ -7,7 +7,7 @@ include $(top_srcdir)/aminclude_static.am + MPTCPD_PLUGIN_CPPFLAGS = \ + -I$(top_srcdir)/include -I$(top_builddir)/include + +-pkglib_LTLIBRARIES = addr_adv.la sspi.la ++pkglib_LTLIBRARIES = addr_adv.la ndiffports.la sspi.la + + sspi_la_SOURCES = sspi.c + sspi_la_CPPFLAGS = $(MPTCPD_PLUGIN_CPPFLAGS) $(CODE_COVERAGE_CPPFLAGS) +@@ -24,6 +24,21 @@ sspi_la_LIBADD = \ + $(top_builddir)/lib/libmptcpd.la \ + $(CODE_COVERAGE_LIBS) + ++ndiffports_la_SOURCES = ndiffports.c ++ndiffports_la_CPPFLAGS = $(MPTCPD_PLUGIN_CPPFLAGS) $(CODE_COVERAGE_CPPFLAGS) ++ndiffports_la_CFLAGS = \ ++ $(ELL_CFLAGS) \ ++ $(MPTCPD_PLUGIN_CFLAGS) \ ++ $(CODE_COVERAGE_CFLAGS) ++ndiffports_la_LDFLAGS = \ ++ -no-undefined \ ++ -module \ ++ -avoid-version \ ++ $(ELL_LIBS) ++ndiffports_la_LIBADD = \ ++ $(top_builddir)/lib/libmptcpd.la \ ++ $(CODE_COVERAGE_LIBS) ++ + addr_adv_la_SOURCES = addr_adv.c + addr_adv_la_CPPFLAGS = $(MPTCPD_PLUGIN_CPPFLAGS) $(CODE_COVERAGE_CPPFLAGS) + addr_adv_la_CFLAGS = \ +diff --git a/plugins/path_managers/ndiffports.c b/plugins/path_managers/ndiffports.c +new file mode 100644 +index 00000000..f217dbbd +--- /dev/null ++++ b/plugins/path_managers/ndiffports.c +@@ -0,0 +1,647 @@ ++// SPDX-License-Identifier: BSD-3-Clause ++/** ++ * @file ndiffports.c ++ * ++ * @brief MPTCP n-different-ports path manager plugin. ++ * ++ * This plugin creates N different subflows for each MPTCP connection, ++ * all using the same single interface on each peer. Only the original ++ * local and remote IP addresses are used, but with unique port numbers ++ * for each subflow. ++ * ++ * Copyright (c) 2022, Intel Corporation ++ */ ++ ++#ifdef HAVE_CONFIG_H ++# include // For NDEBUG and mptcpd VERSION. ++#endif ++ ++#include ++#include // For NULL. ++#include ++ ++#include ++ ++#pragma GCC diagnostic push ++#pragma GCC diagnostic ignored "-Wpedantic" ++#include // For L_STRINGIFY needed by l_error(). ++#include ++#include ++#include ++#pragma GCC diagnostic pop ++ ++#include ++#include ++#include ++#include ++ ++/* ++ * Development notes ++ * ++ * - Need config file value for N in Ndiffports ++ * - Track both local and remote port to allow for remote-initiated subflows ++ * - Subflow request timeout (clear pending flag?) ++ * ++ * Questions ++ * - Is subflow close "reason code" available on the genl api? ++ * ++ * Longer-term ideas ++ * ++ * - Accept advertisements for remote ports with the same remote address ++ * as the initial subflow. ++ * - Advertise multiple local listening ports ++ */ ++ ++/** ++ * @brief Number of subflows to allow. ++ * ++ * Compile-time constant as a temporary measure, until plugins have access ++ * to config file values. ++ */ ++#define NDIFFPORTS_LIMIT 2 ++ ++/** ++ * @brief Time threshold for detecting subflows rejected by the peer ++ * ++ * Subflows with a lifetime under this number of second are considered an ++ * indication of hitting the remote peer's subflow limit. ++ */ ++#define NDIFFPORTS_REJECT_TIME 10 ++ ++/** ++ * @struct ndiffports_subflow_info ++ * ++ * @brief Per-subflow information. ++ * ++ * Each connection tracks this information for each subflow it uses. ++ * With ndiffports, all local and remote addresses are the same so only ++ * port information is required to identify a specific subflow. ++ * ++ * Other fields track the state of the subflow. ++ */ ++struct ndiffports_subflow_info ++{ ++ /// Local port number for this subflow ++ in_port_t local_port; ++ ++ /// Subflow slot is free or in use ++ bool in_use; ++ ++ /// Timestamp for tracking last change to subflow state ++ uint64_t timestamp; ++}; ++ ++/** ++ * @struct ndiffports_connection_info ++ * ++ * @brief Per-connection information. ++ * ++ * Instances of this structure are stored in the ndiffports_connections ++ * hashmap, indexed by token. They are created when a connection is ++ * established and freed when the connection is closed or the plugin ++ * exits. ++ */ ++struct ndiffports_connection_info ++{ ++ /// Local address for all subflows. Port number ignored. ++ struct sockaddr_storage laddr; ++ ++ /// Remote address for all subflows. ++ struct sockaddr_storage raddr; ++ ++ /// Server (vs client) side ++ bool server_side; ++ ++ /// Active subflow count ++ int active_subflows; ++ ++ /// Max subflow count ++ int max_subflows; ++ ++ /// Peer rejected subflows ++ int peer_rejected_consecutive; ++ ++ /// Subflow was requested, but no new_subflow event yet ++ bool pending_request; ++ ++ /// Subflow info ++ struct ndiffports_subflow_info subflow[NDIFFPORTS_LIMIT]; ++}; ++ ++/** ++ * Hashmap of @c ndiffports_connection_info objects that track the ++ * active MPTCP connections and associated state. ++ */ ++static struct l_hashmap *ndiffports_connections; ++ ++// ---------------------------------------------------------------- ++// Helper functions ++// ---------------------------------------------------------------- ++ ++/** ++ * @brief Extract local port information from a @c sockaddr ++ * ++ * @param[in] addr A @c struct @c sockaddr with an IPv4 or IPv6 address+port ++ * ++ * @return @c sin_port or @c sin6_port from the @a addr, or @c 0 if ++ * an invalid @c sa_family was detected. ++ * ++ */ ++static in_port_t get_port(struct sockaddr const *addr) ++{ ++ in_port_t port = 0; ++ ++ if (addr->sa_family == AF_INET) { ++ port = ((struct sockaddr_in const *) addr)->sin_port; ++ } else if (addr->sa_family == AF_INET6) { ++ port = ((struct sockaddr_in6 const *) addr)->sin6_port; ++ } ++ ++ return port; ++} ++ ++/** ++ * @brief Set the port number to 0 in a @c struct @c sockaddr. ++ * ++ * @param[in] addr A @c struct @c sockaddr to modify ++ */ ++static void clear_port(struct sockaddr *addr) ++{ ++ if (addr->sa_family == AF_INET) { ++ ((struct sockaddr_in *) addr)->sin_port = 0; ++ } else if (addr->sa_family == AF_INET6) { ++ ((struct sockaddr_in6 *) addr)->sin6_port = 0; ++ } else { ++ l_error("Unexpected sockaddr family %d", addr->sa_family); ++ } ++} ++ ++/** ++ * @brief Locate an unused subflow slot ++ * ++ * Each @c struct @c ndiffports_connection_info contains an array of @c ++ * struct @c ndiffports_subflow_info objects. Find the first subflow ++ * object that is not in use. ++ * ++ * @param[in] info A @c struct @c ndiffports_connection_info ++ * ++ * @return Pointer to the first available object, or NULL if none available ++ */ ++static struct ndiffports_subflow_info * ++find_empty_subflow(struct ndiffports_connection_info *info) ++{ ++ for (int i = 0; i < NDIFFPORTS_LIMIT && i < info->max_subflows; i++) { ++ if (!info->subflow[i].in_use) { ++ return &info->subflow[i]; ++ } ++ } ++ ++ return NULL; ++} ++ ++/** ++ * @brief Find an existing subflow matching the provided local endpoint ++ * ++ * Look up a subflow object and return a pointer to it, using the ++ * local port number. Address values are assumed to be validated already. ++ * ++ * @param[in] info A @c struct @c ndiffports_connection_info ++ * @param[in] laddr A @c struct @c sockaddr containing the port number ++ * ++ * @return Pointer to the subflow object if found, or @c NULL if not found. ++ */ ++static struct ndiffports_subflow_info * ++find_subflow(struct ndiffports_connection_info *info, ++ struct sockaddr const *laddr) ++{ ++ in_port_t lport = get_port(laddr); ++ ++ for (int i = 0; i < NDIFFPORTS_LIMIT && i < info->max_subflows; i++) { ++ struct ndiffports_subflow_info *sub = &info->subflow[i]; ++ ++ if (sub->in_use && sub->local_port == lport) { ++ return &info->subflow[i]; ++ } ++ } ++ ++ return NULL; ++} ++ ++/** ++ * @brief Initiate a new subflow ++ * ++ * @param[in] pm Path manager object to act on ++ * @param[in] token Token for the connection where the subflow will be added ++ * @param[in] info Connection object where the subflow will be added ++ */ ++static void add_new_subflow(struct mptcpd_pm *pm, mptcpd_token_t token, ++ struct ndiffports_connection_info *info) ++{ ++ if (info->pending_request) { ++ l_warn("New subflow request while previous request is pending"); ++ ++ return; ++ } ++ ++ info->pending_request = true; ++ ++ mptcpd_pm_add_subflow(pm, token, 1, 0, ++ (struct sockaddr *)&info->laddr, ++ (struct sockaddr *)&info->raddr, ++ false); ++ ++ /* TODO: start a timer to retry subflow creation */ ++} ++ ++/** ++ * @brief Check two @c sockaddr objects for equality. ++ * ++ * Addresses are equal if the @c sa_family and @c sin_addr / @c sin6_addr ++ * fields match. Port numbers are ignored. ++ * ++ * @param[in] addr1 Address to compare ++ * @param[in] addr2 Address to compare ++ * ++ * @return @c true if the network addresses match (including address ++ * family). @false if the address family or address values don't match. ++ */ ++static bool addrs_equal(struct sockaddr const *addr1, ++ struct sockaddr const *addr2) ++{ ++ if (addr1->sa_family != addr2->sa_family) { ++ return false; ++ } ++ ++ if (addr1->sa_family == AF_INET) { ++ struct sockaddr_in const *const addr1_in = ++ (struct sockaddr_in const *) addr1; ++ struct sockaddr_in const *const addr2_in = ++ (struct sockaddr_in const *) addr2; ++ ++ return addr1_in->sin_addr.s_addr == addr2_in->sin_addr.s_addr; ++ } else { ++ struct sockaddr_in6 const *const addr1_in6 = ++ (struct sockaddr_in6 const *) addr1; ++ struct sockaddr_in6 const *const addr2_in6 = ++ (struct sockaddr_in6 const *) addr2; ++ ++ return 0 == memcmp(&addr1_in6->sin6_addr, &addr2_in6->sin6_addr, ++ sizeof(addr1_in6->sin6_addr)); ++ } ++} ++ ++/** ++ * @brief Validate addresses on a new subflow ++ * ++ * With ndiffports, all subflows are expected to use the same local and ++ * remote addresses as the initial subflow. Valid subflows will have ++ * both addresses match (port numbers are ignored). ++ * ++ * @param[in] info Connection to validate against ++ * @param[in] laddr Local address for new subflow ++ * @param[in] raddr Remote address for new subflow ++ * ++ * @return @c true if local/remote addresses for the connection match ++ * those in the new subflow. @c false if either pair differs. ++ */ ++static bool validate_addrs(struct ndiffports_connection_info *info, ++ struct sockaddr const *laddr, ++ struct sockaddr const *raddr) ++{ ++ return (addrs_equal((struct sockaddr*)&info->laddr, laddr) && ++ addrs_equal((struct sockaddr*)&info->raddr, raddr)); ++} ++ ++/** ++ * @brief Calculate how many seconds ago a timestamp was ++ * ++ * @param[in] timestamp Timestamp in the past ++ * ++ * @return Number of seconds since the @c timestamp. 0 if timestamp is in ++ * the future. ++ */ ++static uint64_t seconds_elapsed(uint64_t timestamp) ++{ ++ uint64_t now = l_time_now(); ++ ++ return (now > timestamp) ? l_time_to_secs(now - timestamp) : 0; ++} ++ ++// ---------------------------------------------------------------- ++// Mptcpd Plugin Operations ++// ---------------------------------------------------------------- ++ ++/** ++ * @brief Connection established event handler ++ * ++ * When a new connection is created, set up data structures to track ++ * connection-related info and track the initial subflow. ++ * ++ * If additional subflows need to be established and this peer initiated ++ * the connection, initiate one new subflow. ++ * ++ * @param[in] token MPTCP connection token for this new connection ++ * @param[in] laddr Local address and port for the initial subflow ++ * @param[in] raddr Remote address and port for the initial subflow ++ * @param[in] server_side @c true if this peer was the listener (server), ++ * @c false if this peer initiated the connection. ++ * @param[in] pm Path manager object ++ */ ++static void ndiffports_connection_established(mptcpd_token_t token, ++ struct sockaddr const *laddr, ++ struct sockaddr const *raddr, ++ bool server_side, ++ struct mptcpd_pm *pm) ++{ ++ struct ndiffports_subflow_info *first_subflow; ++ struct ndiffports_connection_info *new_entry; ++ in_port_t local_port = get_port(laddr); ++ void *old_entry; ++ ++ if (!local_port) { ++ l_error("Invalid local port"); ++ return; ++ } ++ ++ new_entry = l_new(struct ndiffports_connection_info, 1); ++ ++ memcpy(&new_entry->laddr, laddr, sizeof(new_entry->laddr)); ++ memcpy(&new_entry->raddr, raddr, sizeof(new_entry->raddr)); ++ new_entry->server_side = server_side; ++ new_entry->active_subflows = 1; ++ new_entry->max_subflows = NDIFFPORTS_LIMIT; ++ ++ /* Zero out the port number on the local addr so it's assigned ++ * at connect time for new subflows ++ */ ++ clear_port((struct sockaddr *)&new_entry->laddr); ++ ++ first_subflow = &new_entry->subflow[0]; ++ first_subflow->local_port = local_port; ++ first_subflow->timestamp = l_time_now(); ++ ++ if (!l_hashmap_replace(ndiffports_connections, L_UINT_TO_PTR(token), ++ new_entry, &old_entry)) { ++ l_error("Unable to store connection info"); ++ } ++ ++ if (old_entry) { ++ /* Unexpected: token already exists. ++ * The kernel enforces unique tokens so assume it's stale ++ * data from an old connection that no longer exists. ++ */ ++ l_warn("Found old entry for token %08x", token); ++ l_free(old_entry); ++ } ++ ++ if (!server_side && ++ new_entry->active_subflows < new_entry->max_subflows) { ++ add_new_subflow(pm, token, new_entry); ++ } ++} ++ ++/** ++ * @brief Connection closed event handler ++ * ++ * When a connection is closed, delete the tracking information for ++ * that connection. ++ * ++ * @param[in] token MPTCP connection token for this new connection ++ * @param[in] pm Path manager object (unused) ++ */ ++static void ndiffports_connection_closed(mptcpd_token_t token, ++ struct mptcpd_pm *pm) ++{ ++ struct ndiffports_connection_info *entry; ++ (void) pm; ++ ++ entry = l_hashmap_remove(ndiffports_connections, L_UINT_TO_PTR(token)); ++ ++ if (!entry) { ++ l_warn("Missing entry for token %08x", token); ++ } ++ ++ l_free(entry); ++} ++ ++/** ++ * @brief New subflow event handler ++ * ++ * When a new subflow is created, track the subflow information within ++ * the corresponding connection entry. ++ * ++ * If additional subflows need to be established and this peer initiated ++ * the overall MPTCP connection, initiate one new subflow. ++ * ++ * @param[in] token MPTCP connection token for this new connection ++ * @param[in] laddr Local address and port for new subflow ++ * @param[in] raddr Remote address and port for new subflow ++ * @param[in] backup @c true if this is backup subflow, @c false for ++ * regular priority. (not used) ++ * @param[in] pm Path manager object ++ */ ++static void ndiffports_new_subflow(mptcpd_token_t token, ++ struct sockaddr const *laddr, ++ struct sockaddr const *raddr, ++ bool backup, ++ struct mptcpd_pm *pm) ++{ ++ struct ndiffports_subflow_info *sub; ++ struct ndiffports_connection_info *entry; ++ ++ (void) backup; ++ ++ entry = l_hashmap_lookup(ndiffports_connections, L_UINT_TO_PTR(token)); ++ ++ if (!entry) { ++ l_warn("New subflow for unmanaged token %08x", token); ++ ++ mptcpd_pm_remove_subflow(pm, token, laddr, raddr); ++ return; ++ } ++ ++ if (!validate_addrs(entry, laddr, raddr) || ++ entry->active_subflows >= entry->max_subflows) { ++ goto reject; ++ } ++ ++ sub = find_subflow(entry, laddr); ++ ++ if (sub) { ++ l_warn("Unexpected event from established subflow"); ++ ++ // TODO: stop timer. ++ } else { ++ sub = find_empty_subflow(entry); ++ if (!sub) { ++ goto reject; ++ } ++ ++ sub->in_use = true; ++ sub->timestamp = l_time_now(); ++ ++ entry->active_subflows++; ++ ++ sub->local_port = get_port(laddr); ++ } ++ ++ sub->timestamp = l_time_now(); ++ entry->pending_request = false; ++ ++ /* TODO: start timer to clear consecutive_reject counter */ ++ ++ if (!entry->server_side && ++ entry->active_subflows < entry->max_subflows) { ++ add_new_subflow(pm, token, entry); ++ } ++ ++ return; ++ ++reject: ++ mptcpd_pm_remove_subflow(pm, token, laddr, raddr); ++} ++ ++/** ++ * @brief Subflow closed event handler ++ * ++ * When a subflow is closed, clear related information. If the number of ++ * subflows is below the expected count and this peer initiated the ++ * original connection, request a new subflow to replace the closed one. ++ * ++ * @param[in] token MPTCP connection token for this new connection ++ * @param[in] laddr Local address and port for new subflow ++ * @param[in] raddr Remote address and port for new subflow ++ * @param[in] backup @c true if this is backup subflow, @c false for ++ * regular priority. (not used) ++ * @param[in] pm Path manager object ++ */ ++static void ndiffports_subflow_closed(mptcpd_token_t token, ++ struct sockaddr const *laddr, ++ struct sockaddr const *raddr, ++ bool backup, ++ struct mptcpd_pm *pm) ++{ ++ struct ndiffports_connection_info *entry; ++ struct ndiffports_subflow_info *sub; ++ ++ (void) backup; ++ ++ entry = l_hashmap_lookup(ndiffports_connections, L_UINT_TO_PTR(token)); ++ ++ if (!entry) { ++ l_warn("Closed subflow for unmanaged token %08x", token); ++ return; ++ } ++ ++ if (!validate_addrs(entry, laddr, raddr)) { ++ l_warn("Address/token mismatch for token %08x", token); ++ return; ++ } ++ ++ sub = find_subflow(entry, laddr); ++ ++ if (sub) { ++ if (seconds_elapsed(sub->timestamp) < NDIFFPORTS_REJECT_TIME) { ++ entry->peer_rejected_consecutive++; ++ } ++ ++ sub->in_use = false; ++ sub->timestamp = l_time_now(); ++ ++ if (entry->active_subflows > 0) { ++ entry->active_subflows--; ++ } else { ++ l_error("Underflow when adjusting subflow count"); ++ } ++ } else { ++ l_warn("Untracked subflow was closed"); ++ } ++ ++ if (!entry->server_side && ++ entry->active_subflows < entry->max_subflows) { ++ add_new_subflow(pm, token, entry); ++ } ++} ++ ++/** ++ * Plugin ops to register with the mptcpd core ++ */ ++static struct mptcpd_plugin_ops const pm_ops = { ++ .connection_established = ndiffports_connection_established, ++ .connection_closed = ndiffports_connection_closed, ++ .new_subflow = ndiffports_new_subflow, ++ .subflow_closed = ndiffports_subflow_closed, ++}; ++ ++/** ++ * @brief Ndiffports plugin init hook ++ * ++ * Set up the hashmap for tracking connections. ++ * ++ * @param[in] pm Path manager object (not used) ++ */ ++static int ndiffports_init(struct mptcpd_pm *pm) ++{ ++ (void) pm; ++ ++ static char const name[] = "ndiffports"; ++ ++ if (!mptcpd_plugin_register_ops(name, &pm_ops)) { ++ l_error("Failed to initialize " ++ "n-different-ports " ++ "path manager plugin."); ++ ++ return -1; ++ } ++ ++ ndiffports_connections = l_hashmap_new(); ++ ++ l_info("MPTCP n-different-ports " ++ "path manager initialized."); ++ ++ return 0; ++} ++ ++/** ++ * @brief Helper for cleaning up connection info ++ * ++ * Delete one connection entry. ++ * ++ * @param[in] pm Path manager object (not used) ++ */ ++static void ndiffports_connection_destroy(void *value) ++{ ++ struct ndiffports_connection_info *info = value; ++ ++ l_free(info); ++} ++ ++/** ++ * @brief Ndiffports plugin exit hook ++ * ++ * Delete the hashmap for tracking connections. ++ * ++ * @param[in] pm Path manager object (not used) ++ */ ++static void ndiffports_exit(struct mptcpd_pm *pm) ++{ ++ (void) pm; ++ ++ l_hashmap_destroy(ndiffports_connections, ++ ndiffports_connection_destroy); ++ ndiffports_connections = NULL; ++ l_info("MPTCP n-different-ports path manager exited."); ++} ++ ++MPTCPD_PLUGIN_DEFINE(ndiffports, ++ "N-different-ports path manager", ++ MPTCPD_PLUGIN_PRIORITY_DEFAULT, ++ ndiffports_init, ++ ndiffports_exit) ++ ++ ++/* ++ Local Variables: ++ c-file-style: "linux" ++ End: ++*/