diff --git a/luci-app-mptcp/htdocs/luci-static/resources/view/network/mptcp.js b/luci-app-mptcp/htdocs/luci-static/resources/view/network/mptcp.js new file mode 100644 index 000000000..c9ec6074f --- /dev/null +++ b/luci-app-mptcp/htdocs/luci-static/resources/view/network/mptcp.js @@ -0,0 +1,234 @@ +'use strict'; +'require rpc'; +'require form'; +'require fs'; +'require uci'; +'require tools.widgets as widgets'; + +/* + * Copyright (C) 2024 Ycarus (Yannick Chabanois) for OpenMPTCProuter + * This is free software, licensed under the GNU General Public License v3. + * See /LICENSE for more information + */ + +var callSystemBoard = rpc.declare({ + object: 'system', + method: 'board' +}); + + +return L.view.extend({ + load: function() { + return Promise.all([ + L.resolveDefault(callSystemBoard(), {}) + ]); + }, + + render: function(res) { + var m, s, o; + var boardinfo = res[0]; + + m = new form.Map('network', _('MPTCP'),_('Networks MPTCP settings.')); + + s = m.section(form.TypedSection, 'globals'); + + o = s.option(form.ListValue, 'multipath', _('Multipath TCP')); + o.value("enable", _("enable")); + o.value("disable", _("disable")); + + o = s.option(form.ListValue, "mptcp_checksum", _("Multipath TCP checksum")); + o.value(1, _("enable")); + o.value(0, _("disable")); + + if (boardinfo.kernel.substring(1,4) != "5.15" && boardinfo.kernel.substring(1,1) != "6") { + o = s.option(form.ListValue, "mptcp_debug", _("Multipath Debug")); + o.value(1, _("enable")); + o.value(0, _("disable")); + } + + o = s.option(form.ListValue, "mptcp_path_manager", _("Multipath TCP path-manager"), _("Default is fullmesh")); + o.value("default", _("default")); + o.value("fullmesh", "fullmesh"); + + if (parseFloat(boardinfo.kernel.substring(0,4)) < 6) { + o.value("ndiffports", "ndiffports"); + o.value("binder", "binder"); + o.value("netlink", _("Netlink")); + } + + o = s.option(form.ListValue, "mptcp_scheduler", _("Multipath TCP scheduler")); + o.value("default", _("default")); + if (parseFloat(boardinfo.kernel.substring(0,4)) < 6) { + o.value("roundrobin", "round-robin"); + o.value("redundant", "redundant"); + o.value("blest", "BLEST"); + o.value("ecf", "ECF"); + } + + if (parseFloat(boardinfo.kernel.substring(0,3)) >= "6.6") { + o.load = function(section_id) { + return L.resolveDefault(fs.list('/usr/share/bpf/scheduler'), []).then(L.bind(function(entries) { + for (var i = 0; i < entries.length; i++) + if (entries[i].type == 'file' && entries[i].name.match(/\.o$/)) + this.value(entries[i].name); + return this.super('load', [section_id]); + }, this)); + }; + // bpf_burst => same as the default scheduler + // bpf_red => sends all packets redundantly on all available subflows + // bpf_first => always picks the first subflow to send data + // bpf_rr => always picks the next available subflow to send data (round-robin) + } + + if (parseFloat(boardinfo.kernel.substring(0,4)) < 6) { + o = s.option(form.Value, "mptcp_syn_retries", _("Multipath TCP SYN retries")); + o.datatype = "uinteger"; + o.rmempty = false; + } + + if (parseFloat(boardinfo.kernel.substring(0,4)) < 6) { + o = s.option(form.ListValue, "mptcp_version", _("Multipath TCP version")); + o.value(0, _("0")); + o.value(1, _("1")); + o.default = 0; + } + + o = s.option(form.ListValue, "congestion", _("Congestion Control"),_("Default is cubic")); + o.load = function(section_id) { + return fs.exec_direct('/sbin/sysctl', ['-n', 'net.ipv4.tcp_available_congestion_control']).then(L.bind(function(entries) { + var congestioncontrol = entries.toString().split(' '); + for (var d in congestioncontrol) { + this.value(congestioncontrol[d]); + }; + return this.super('load', [section_id]); + }, this)); + }; + + if (parseFloat(boardinfo.kernel.substring(0,4)) >= 6) { + if (boardinfo.kernel.substring(0,1) == "6") { + // Only available since 5.19 + o = s.option(form.ListValue, "mptcp_pm_type", _("Path Manager type")); + o.value(0, _("In-kernel path manager")); + o.value(1, _("Userspace path manager")); + o.default = 0; + } + + o = s.option(form.ListValue, "mptcp_disable_initial_config", _("Initial MPTCP configuration")); + o.depends("mptcp_pm_type","1"); + o.value("0", _("enable")); + o.value("1", _("disable")); + o.default = "0"; + + o = s.option(form.ListValue, "mptcp_force_multipath", _("Force Multipath configuration")); + o.depends("mptcp_pm_type","1"); + o.value("1", _("enable")); + o.value("0", _("disable")); + o.default = "1"; + + o = s.option(form.ListValue, "mptcpd_enable", _("Enable MPTCPd")); + o.depends("mptcp_pm_type","1"); + o.value("enable", _("enable")); + o.value("disable", _("disable")); + o.default = "disable"; + + o = s.option(form.DynamicList, "mptcpd_path_manager", _("MPTCPd path managers")); + o.load = function(section_id) { + return L.resolveDefault(fs.list('/usr/lib/mptcpd'), []).then(L.bind(function(entries) { + for (var i = 0; i < entries.length; i++) + if (entries[i].type == 'file' && entries[i].name.match(/\.so$/)) + this.value(entries[i].name); + return this.super('load', [section_id]); + }, this)); + }; + o.depends("mptcp_pm_type","1"); + + o = s.option(form.DynamicList, "mptcpd_plugins", _("MPTCPd plugins")); + o.load = function(section_id) { + return L.resolveDefault(fs.list('/usr/lib/mptcpd'), []).then(L.bind(function(entries) { + for (var i = 0; i < entries.length; i++) + if (entries[i].type == 'file' && entries[i].name.match(/\.so$/)) + this.value(entries[i].name); + return this.super('load', [section_id]); + }, this)); + }; + o.depends("mptcp_pm_type","1"); + + o = s.option(form.DynamicList, "mptcpd_addr_flags", _("MPTCPd Address annoucement flags")); + o.value("subflow","subflow"); + o.value("signal","signal"); + o.value("backup","backup"); + o.value("fullmesh","fullmesh"); + o.depends("mptcp_pm_type","1"); + + o = s.option(form.DynamicList, "mptcpd_notify_flags", _("MPTCPd Address notification flags")); + o.value("existing","existing"); + o.value("skip_link_local","skip_link_local"); + o.value("skip_loopback","skip_loopback"); + o.depends("mptcp_pm_type","1"); + + o = s.option(form.Value, "mptcp_subflows", _("Max subflows"),_("specifies the maximum number of additional subflows allowed for each MPTCP connection")); + o.datatype = "uinteger"; + o.rmempty = false; + o.default = 3; + + o = s.option(form.Value, "mptcp_stale_loss_cnt", _("Retranmission intervals"),_("The number of MPTCP-level retransmission intervals with no traffic and pending outstanding data on a given subflow required to declare it stale. A low stale_loss_cnt value allows for fast active-backup switch-over, an high value maximize links utilization on edge scenarios e.g. lossy link with high BER or peer pausing the data processing.")); + o.datatype = "uinteger"; + o.rmempty = false; + o.default = 4; + + o = s.option(form.Value, "mptcp_add_addr_accepted", _("Max add address"),_("specifies the maximum number of ADD_ADDR (add address) suboptions accepted for each MPTCP connection")); + o.datatype = "uinteger"; + o.rmempty = false; + o.default = 1; + + o = s.option(form.Value, "mptcp_add_addr_timeout", _("Control message timeout"),_("Set the timeout after which an ADD_ADDR (add address) control message will be resent to an MPTCP peer that has not acknowledged a previous ADD_ADDR message.")); + o.datatype = "uinteger"; + o.rmempty = false; + o.default = 120; + } else { + o = s.option(form.Value, "mptcp_fullmesh_num_subflows", _("Fullmesh subflows for each pair of IP addresses")); + o.datatype = "uinteger"; + o.rmempty = false; + o.default = 1; + //o.depends("mptcp_path_manager","fullmesh") + + o = s.option(form.ListValue, "mptcp_fullmesh_create_on_err", _("Re-create fullmesh subflows after a timeout")); + o.value(1, _("enable")); + o.value(0, _("disable")); + //o.depends("mptcp_path_manager","fullmesh"); + + o = s.option(form.Value, "mptcp_ndiffports_num_subflows", _("ndiffports subflows number")); + o.datatype = "uinteger"; + o.rmempty = false; + o.default = 1; + //o.depends("mptcp_path_manager","ndiffports") + + o = s.option(form.ListValue, "mptcp_rr_cwnd_limited", _("Fill the congestion window on all subflows for round robin")); + o.value("Y", _("enable")); + o.value("N", _("disable")); + o.default = "Y"; + //o.depends("mptcp_scheduler","roundrobin") + + o = s.option(form.Value, "mptcp_rr_num_segments", _("Consecutive segments that should be sent for round robin")); + o.datatype = "uinteger"; + o.rmempty = false; + o.default = 1; + //o.depends("mptcp_scheduler","roundrobin") + } + + s = m.section(form.TypedSection, "interface", _("Interfaces Settings")); + s.filter = function(section) { + return (!section.match("^oip.*") && !section.match("^lo.*") && section != "omrvpn" && section != "omr6in4"); + } + + o = s.option(form.ListValue, "multipath", _("Multipath TCP"), _("One interface must be set as master")); + o.value("on", _("enabled")); + o.value("off", _("disabled")); + o.value("master", _("master")); + o.value("backup", _("backup")); + //o.value("handover", _("handover")); + o.default = "off"; + + return m.render(); + } +}); \ No newline at end of file diff --git a/luci-app-mptcp/luasrc/controller/mptcp.lua b/luci-app-mptcp/luasrc/controller/mptcp.lua index 227d26602..8ccdecca1 100644 --- a/luci-app-mptcp/luasrc/controller/mptcp.lua +++ b/luci-app-mptcp/luasrc/controller/mptcp.lua @@ -9,7 +9,7 @@ module("luci.controller.mptcp", package.seeall) function index() local uname = nixio.uname() entry({"admin", "network", "mptcp"}, alias("admin", "network", "mptcp", "settings"), _("MPTCP")) - entry({"admin", "network", "mptcp", "settings"}, cbi("mptcp"), _("Settings"),2).leaf = true + entry({"admin", "network", "mptcp", "settings"}, view("network/mptcp"), _("Settings"),2).leaf = true entry({"admin", "network", "mptcp", "bandwidth"}, template("mptcp/multipath"), _("Bandwidth"), 3).leaf = true entry({"admin", "network", "mptcp", "multipath_bandwidth"}, call("multipath_bandwidth")).leaf = true entry({"admin", "network", "mptcp", "interface_bandwidth"}, call("interface_bandwidth")).leaf = true diff --git a/luci-app-mptcp/luasrc/model/cbi/mptcp.lua b/luci-app-mptcp/luasrc/model/cbi/mptcp.lua deleted file mode 100644 index f89cf7d99..000000000 --- a/luci-app-mptcp/luasrc/model/cbi/mptcp.lua +++ /dev/null @@ -1,200 +0,0 @@ -local net = require "luci.model.network".init() -local sys = require "luci.sys" -local ifaces = sys.net:devices() -local m, s, o -local uname = nixio.uname() - -m = Map("network", translate("MPTCP"), translate("Networks MPTCP settings.")) - -local unameinfo = nixio.uname() or { } - -s = m:section(TypedSection, "globals") -o = s:option(ListValue, "multipath", translate("Multipath TCP")) -o:value("enable", translate("enable")) -o:value("disable", translate("disable")) -o = s:option(ListValue, "mptcp_checksum", translate("Multipath TCP checksum")) -o:value(1, translate("enable")) -o:value(0, translate("disable")) - -if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then - o = s:option(ListValue, "mptcp_debug", translate("Multipath Debug")) - o:value(1, translate("enable")) - o:value(0, translate("disable")) -end - -o = s:option(ListValue, "mptcp_path_manager", translate("Multipath TCP path-manager"), translate("Default is fullmesh")) -o:value("default", translate("default")) -o:value("fullmesh", "fullmesh") ---if tonumber(uname.release:sub(1,4)) <= 5.15 then -if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then - o:value("ndiffports", "ndiffports") - o:value("binder", "binder") - if uname.release:sub(1,4) ~= "4.14" then - o:value("netlink", translate("Netlink")) - end -end -o = s:option(ListValue, "mptcp_scheduler", translate("Multipath TCP scheduler")) -o:value("default", translate("default")) --- if tonumber(uname.release:sub(1,4)) <= 5.15 then -if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then - o:value("roundrobin", "round-robin") - o:value("redundant", "redundant") - if uname.release:sub(1,4) ~= "4.14" then - o:value("blest", "BLEST") - o:value("ecf", "ECF") - end -end -if uname.release:sub(1,3) == "6.6" then - for dir in io.popen([[cd /usr/share/bpf/scheduler && ls -1 *.o | sed -e 's/.o//g' -e 's/mptcp_//g']]):lines() do - o:value(dir, dir) - end - -- bpf_burst => same as the default scheduler - -- bpf_red => sends all packets redundantly on all available subflows - -- bpf_first => always picks the first subflow to send data - -- bpf_rr => always picks the next available subflow to send data (round-robin) -end - --- if tonumber(uname.release:sub(1,4)) <= 5.15 then -if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then - o = s:option(Value, "mptcp_syn_retries", translate("Multipath TCP SYN retries")) - o.datatype = "uinteger" - o.rmempty = false -end --- if tonumber(uname.release:sub(1,4)) <= 5.15 then -if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then - o = s:option(ListValue, "mptcp_version", translate("Multipath TCP version")) - o:value(0, translate("0")) - o:value(1, translate("1")) - o.default = 0 -end -o = s:option(ListValue, "congestion", translate("Congestion Control"),translate("Default is cubic")) -local availablecong = sys.exec("sysctl -n net.ipv4.tcp_available_congestion_control | xargs -n1 | sort | xargs") -for cong in string.gmatch(availablecong, "[^%s]+") do - if cong == "bbr" and string.match(availablecong, "bbr1") then - o:value(cong, "bbr3") - else - o:value(cong, cong) - end -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 - 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(ListValue, "mptcp_disable_initial_config", translate("Initial MPTCP configuration")) - o:depends("mptcp_pm_type",1) - o:value("0", translate("enable")) - o:value("1", translate("disable")) - o.default = "0" - - o = s:option(ListValue, "mptcp_force_multipath", translate("Force Multipath configuration")) - o:depends("mptcp_pm_type",1) - o:value("1", translate("enable")) - o:value("0", translate("disable")) - o.default = "1" - - o = s:option(ListValue, "mptcpd_enable", translate("Enable MPTCPd")) - o:depends("mptcp_pm_type",1) - o:value("enable", translate("enable")) - o:value("disable", translate("disable")) - o.default = "disable" - - o = s:option(DynamicList, "mptcpd_path_manager", translate("MPTCPd path managers")) - for dir in io.popen([[cd /usr/lib/mptcpd && ls -1 *.so | sed 's/.so//g']]):lines() do - o:value(dir, dir) - end - o:depends("mptcp_pm_type",1) - - o = s:option(DynamicList, "mptcpd_plugins", translate("MPTCPd plugins")) - for dir in io.popen([[cd /usr/lib/mptcpd && ls -1 *.so | sed 's/.so//g']]):lines() do - o:value(dir, dir) - end - o:depends("mptcp_pm_type",1) - - o = s:option(DynamicList, "mptcpd_addr_flags", translate("MPTCPd Address annoucement flags")) - o:value("subflow","subflow") - o:value("signal","signal") - o:value("backup","backup") - o:value("fullmesh","fullmesh") - o:depends("mptcp_pm_type",1) - - o = s:option(DynamicList, "mptcpd_notify_flags", translate("MPTCPd Address notification flags")) - o:value("existing","existing") - o:value("skip_link_local","skip_link_local") - o:value("skip_loopback","skip_loopback") - o:depends("mptcp_pm_type",1) - - 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" - o.rmempty = false - o.default = 3 - - o = s:option(Value, "mptcp_stale_loss_cnt", translate("Retranmission intervals"),translate("The number of MPTCP-level retransmission intervals with no traffic and pending outstanding data on a given subflow required to declare it stale. A low stale_loss_cnt value allows for fast active-backup switch-over, an high value maximize links utilization on edge scenarios e.g. lossy link with high BER or peer pausing the data processing.")) - o.datatype = "uinteger" - o.rmempty = false - o.default = 4 - - o = s:option(Value, "mptcp_add_addr_accepted", translate("Max add address"),translate("specifies the maximum number of ADD_ADDR (add address) suboptions accepted for each MPTCP connection")) - o.datatype = "uinteger" - o.rmempty = false - o.default = 1 - - o = s:option(Value, "mptcp_add_addr_timeout", translate("Control message timeout"),translate("Set the timeout after which an ADD_ADDR (add address) control message will be resent to an MPTCP peer that has not acknowledged a previous ADD_ADDR message.")) - o.datatype = "uinteger" - o.rmempty = false - o.default = 120 - -else - o = s:option(Value, "mptcp_fullmesh_num_subflows", translate("Fullmesh subflows for each pair of IP addresses")) - o.datatype = "uinteger" - o.rmempty = false - o.default = 1 - --o:depends("mptcp_path_manager","fullmesh") - - o = s:option(ListValue, "mptcp_fullmesh_create_on_err", translate("Re-create fullmesh subflows after a timeout")) - o:value(1, translate("enable")) - o:value(0, translate("disable")) - --o:depends("mptcp_path_manager","fullmesh") - - o = s:option(Value, "mptcp_ndiffports_num_subflows", translate("ndiffports subflows number")) - o.datatype = "uinteger" - o.rmempty = false - o.default = 1 - --o:depends("mptcp_path_manager","ndiffports") - - o = s:option(ListValue, "mptcp_rr_cwnd_limited", translate("Fill the congestion window on all subflows for round robin")) - o:value("Y", translate("enable")) - o:value("N", translate("disable")) - o.default = "Y" - --o:depends("mptcp_scheduler","roundrobin") - - o = s:option(Value, "mptcp_rr_num_segments", translate("Consecutive segments that should be sent for round robin")) - o.datatype = "uinteger" - o.rmempty = false - o.default = 1 - --o:depends("mptcp_scheduler","roundrobin") -end - -s = m:section(TypedSection, "interface", translate("Interfaces Settings")) -function s.filter(self, section) - return not section:match("^oip.*") and not section:match("^lo.*") and section ~= "omrvpn" and section ~= "omr6in4" -end -o = s:option(ListValue, "multipath", translate("Multipath TCP"), translate("One interface must be set as master")) -o:value("on", translate("enabled")) -o:value("off", translate("disabled")) -o:value("master", translate("master")) -o:value("backup", translate("backup")) ---o:value("handover", translate("handover")) -o.default = "off" - -function m.on_after_apply(self,map) - sys.call('/etc/init.d/mptcp reload') -end - -return m diff --git a/luci-app-mptcp/root/usr/share/rpcd/acl.d/luci-app-mptcp.json b/luci-app-mptcp/root/usr/share/rpcd/acl.d/luci-app-mptcp.json index d07f07916..eaf813e8c 100644 --- a/luci-app-mptcp/root/usr/share/rpcd/acl.d/luci-app-mptcp.json +++ b/luci-app-mptcp/root/usr/share/rpcd/acl.d/luci-app-mptcp.json @@ -2,7 +2,16 @@ "luci-app-mptcp": { "description": "Grant UCI access for luci-app-mptcp", "read": { - "uci": [ "openmptcprouter", "network" ] + "uci": [ "openmptcprouter", "network" ], + "file": { + "/usr/lib/mptcpd": [ "list" ], + "/usr/share/bpf/scheduler": [ "list" ], + "/sbin/sysctl -n net.ipv4.tcp_available_congestion_control": [ "exec" ] + }, + "ubus": { + "file": [ "list", "exec" ], + "system": [ "board" ] + } }, "write": { "uci": [ "openmptcprouter", "network" ] diff --git a/omr-tracker/files/bin/omr-tracker-ss b/omr-tracker/files/bin/omr-tracker-ss index dec53015c..c4c59667e 100755 --- a/omr-tracker/files/bin/omr-tracker-ss +++ b/omr-tracker/files/bin/omr-tracker-ss @@ -122,7 +122,7 @@ while true; do [ -n "$script_alert_up" ] && eval $script_alert_up } if [ -z "$($IPTABLESSAVE 2>/dev/null | grep :ssr)" ] && [ -z "$(nft list ruleset 2>/dev/null | grep ss_r)" ] && [ -z "$(nft list ruleset 2>/dev/null | grep ssr_r)" ]; then - if [ "$type" = "libev" ] && [ "$(uci -q get shadowsocks-libev.ss_rules.disabled)" != "1" ]; then + if [ "$type" = "libev" ] && [ "$(uci -q get shadowsocks-libev.ss_rules.disabled)" != "1" ] && [ "$(uci -q get shadowsocks-libev.${server}.key)" != "" ] && [ "$(uci -q get shadowsocks-libev.${server}.server)" != "" ] && [ "$(uci -q get shadowsocks-libev.${server}.disabled)" != "1" ]; then _log "Reload Shadowsocks rules" /etc/init.d/shadowsocks-libev rules_up 2> /dev/null if ! /etc/init.d/shadowsocks-libev rules_exist ; then @@ -130,7 +130,7 @@ while true; do /etc/init.d/shadowsocks-libev restart >/dev/null 2>&1 fi _get_ip - elif [ "$type" = "rust" ] && [ "$(uci -q get shadowsocks-rust.ss_rules.disabled)" != "1" ]; then + elif [ "$type" = "rust" ] && [ "$(uci -q get shadowsocks-rust.ss_rules.disabled)" != "1" ] && [ "$(uci -q get shadowsocks-rust.${server}.key)" != "" ] && [ "$(uci -q get shadowsocks-rust.${server}.server)" != "" ] && [ "$(uci -q get shadowsocks-rust.${server}.disabled)" != "1" ]; then _log "Reload Shadowsocks Rust rules" /etc/init.d/shadowsocks-rust rules_up 2> /dev/null if ! /etc/init.d/shadowsocks-rust rules_exist ; then @@ -187,13 +187,13 @@ while true; do script_alert_down="$(uci -q get omr-tracker.proxy.script_alert_down)" [ -n "$script_alert_down" ] && eval $script_alert_down - if [ "$disabled" != "1" ]; then + if [ "$disabled" != "1" ] && [ "$serverip" != "1" ]; then if [ "$type" = "libev" ] && [ "$(pgrep ss-redir)" = "" ] && [ "$(uci -q get shadowsocks-libev.${server}.key)" != "" ]; then _log "Can't find shadowsocks, restart it..." /etc/init.d/shadowsocks-libev restart sleep 5 fi - if [ "$type" = "rust" ] && [ "$(pgrep sslocal)" = "" ] && [ "$(uci -q get shadowsocks-libev.${server}.key)" != "" ]; then + if [ "$type" = "rust" ] && [ "$(pgrep sslocal)" = "" ] && [ "$(uci -q get shadowsocks-rust.${server}.key)" != "" ]; then _log "Can't find shadowsocks rust, restart it..." /etc/init.d/shadowsocks-rust restart sleep 5 diff --git a/openmptcprouter-full/Makefile b/openmptcprouter-full/Makefile index 425be4763..7ea46ed10 100644 --- a/openmptcprouter-full/Makefile +++ b/openmptcprouter-full/Makefile @@ -84,11 +84,11 @@ MY_DEPENDS := \ luci-theme-openwrt-2020 luci-proto-wireguard luci-app-wireguard kmod-crypto-lib-blake2s \ !(LINUX_5_4):mptcpd (TARGET_x86||TARGET_x86_64):kmod-igc !TARGET_mvebu:kmod-mmc-spi kmod-macsec usbutils v2ray-core LINUX_5_4:v2ray-config !LINUX_5_4:v2ray-config-nft \ (TARGET_x86||TARGET_x86_64):kmod-mlx4-core \ - !(TARGET_ipq40xx||TARGET_ramips||LINUX_6_6):iptables-mod-ndpi !(TARGET_ipq40xx||TARGET_ramips||LINUX_6_6):kmod-ipt-ndpi libip4tc libip6tc \ + !(TARGET_ipq40xx||TARGET_ramips||LINUX_6_6||LINUX_6_10):iptables-mod-ndpi !(TARGET_ipq40xx||TARGET_ramips||LINUX_6_6||LINUX_6_10):kmod-ipt-ndpi libip4tc libip6tc \ xray-core LINUX_5_4:xray-config !LINUX_5_4:xray-config-nft (LINUX_5_4&&(TARGET_x86_64||aarch64)):kmod-tcp-bbr2 kmod-ovpn-dco-v2 \ shadowsocks-rust-sslocal shadowsocks-rust-ssservice LINUX_5_4:shadowsocks-rust-config !LINUX_5_4:shadowsocks-rust-config-nft luci-app-shadowsocks-rust \ luci-proto-external omr-schedule jq luci-app-ddns \ - LINUX_6_6:mptcp-bpf-burst LINUX_6_6:mptcp-bpf-first LINUX_6_6:mptcp-bpf-red LINUX_6_6:mptcp-bpf-rr LINUX_6_6:bpftool-full \ + (LINUX_6_6||LINUX_6_10):mptcp-bpf-burst (LINUX_6_6||LINUX_6_10):mptcp-bpf-first (LINUX_6_6||LINUX_6_10):mptcp-bpf-red (LINUX_6_6||LINUX_6_10):mptcp-bpf-rr (LINUX_6_6||LINUX_6_10):bpftool-full \ mbim-utils (TARGET_x86||TARGET_x86_64):kmod-r8169 !(TARGET_ipq40xx||TARGET_x86_64):kmod-mt7601u !TARGET_x86_64:kmod-ath9k-htc \ TARGET_mvebu:kmod-mwlwifi TARGET_mvebu:mwlwifi-firmware-88w8864 TARGET_mvebu:mwlwifi-firmware-88w8897 TARGET_mvebu:mwlwifi-firmware-88w8964 TARGET_mvebu:mwlwifi-firmware-88w8997 \ TARGET_x86_64:kmod-atlantic