Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'mptcp-fallback-to-tcp-after-3-mpc-drop-cache'

Matthieu Baerts says:

====================
mptcp: fallback to TCP after 3 MPC drop + cache

The SYN + MPTCP_CAPABLE packets could be explicitly dropped by firewalls
somewhere in the network, e.g. if they decide to drop packets based on
the TCP options, instead of stripping them off.

The idea of this series is to fallback to TCP after 3 SYN+MPC drop
(patch 2). If the connection succeeds after the fallback, it very likely
means a blackhole has been detected. In this case (patch 3), MPTCP can
be disabled for a certain period of time, 1h by default. If after this
period, MPTCP is still blocked, the period is doubled. This technique is
inspired by the one used by TCP FastOpen.

This should help applications which want to use MPTCP by default on the
client side if available.
====================

Link: https://patch.msgid.link/20240909-net-next-mptcp-fallback-x-mpc-v1-0-da7ebb4cd2a3@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+182 -11
+11
Documentation/networking/mptcp-sysctl.rst
··· 34 34 Shows the available schedulers choices that are registered. More packet 35 35 schedulers may be available, but not loaded. 36 36 37 + blackhole_timeout - INTEGER (seconds) 38 + Initial time period in second to disable MPTCP on active MPTCP sockets 39 + when a MPTCP firewall blackhole issue happens. This time period will 40 + grow exponentially when more blackhole issues get detected right after 41 + MPTCP is re-enabled and will reset to the initial value when the 42 + blackhole issue goes away. 43 + 44 + 0 to disable the blackhole detection. 45 + 46 + Default: 3600 47 + 37 48 checksum_enabled - BOOLEAN 38 49 Control whether DSS checksum can be enabled. 39 50
+4
include/net/mptcp.h
··· 223 223 224 224 return htonl(0u); 225 225 } 226 + 227 + void mptcp_active_detect_blackhole(struct sock *sk, bool expired); 226 228 #else 227 229 228 230 static inline void mptcp_init(void) ··· 309 307 } 310 308 311 309 static inline __be32 mptcp_reset_option(const struct sk_buff *skb) { return htonl(0u); } 310 + 311 + static inline void mptcp_active_detect_blackhole(struct sock *sk, bool expired) { } 312 312 #endif /* CONFIG_MPTCP */ 313 313 314 314 #if IS_ENABLED(CONFIG_MPTCP_IPV6)
+1
net/ipv4/tcp_timer.c
··· 282 282 expired = retransmits_timed_out(sk, retry_until, 283 283 READ_ONCE(icsk->icsk_user_timeout)); 284 284 tcp_fastopen_active_detect_blackhole(sk, expired); 285 + mptcp_active_detect_blackhole(sk, expired); 285 286 286 287 if (BPF_SOCK_OPS_TEST_FLAG(tp, BPF_SOCK_OPS_RTO_CB_FLAG)) 287 288 tcp_call_bpf_3arg(sk, BPF_SOCK_OPS_RTO_CB,
+133
net/mptcp/ctrl.c
··· 12 12 #include <net/netns/generic.h> 13 13 14 14 #include "protocol.h" 15 + #include "mib.h" 15 16 16 17 #define MPTCP_SYSCTL_PATH "net/mptcp" 17 18 ··· 28 27 #endif 29 28 30 29 unsigned int add_addr_timeout; 30 + unsigned int blackhole_timeout; 31 31 unsigned int close_timeout; 32 32 unsigned int stale_loss_cnt; 33 + atomic_t active_disable_times; 34 + unsigned long active_disable_stamp; 33 35 u8 mptcp_enabled; 34 36 u8 checksum_enabled; 35 37 u8 allow_join_initial_addr_port; ··· 91 87 { 92 88 pernet->mptcp_enabled = 1; 93 89 pernet->add_addr_timeout = TCP_RTO_MAX; 90 + pernet->blackhole_timeout = 3600; 91 + atomic_set(&pernet->active_disable_times, 0); 94 92 pernet->close_timeout = TCP_TIMEWAIT_LEN; 95 93 pernet->checksum_enabled = 0; 96 94 pernet->allow_join_initial_addr_port = 1; ··· 153 147 mptcp_get_available_schedulers(tbl.data, MPTCP_SCHED_BUF_MAX); 154 148 ret = proc_dostring(&tbl, write, buffer, lenp, ppos); 155 149 kfree(tbl.data); 150 + 151 + return ret; 152 + } 153 + 154 + static int proc_blackhole_detect_timeout(const struct ctl_table *table, 155 + int write, void *buffer, size_t *lenp, 156 + loff_t *ppos) 157 + { 158 + struct mptcp_pernet *pernet = mptcp_get_pernet(current->nsproxy->net_ns); 159 + int ret; 160 + 161 + ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); 162 + if (write && ret == 0) 163 + atomic_set(&pernet->active_disable_times, 0); 156 164 157 165 return ret; 158 166 } ··· 237 217 .mode = 0644, 238 218 .proc_handler = proc_dointvec_jiffies, 239 219 }, 220 + { 221 + .procname = "blackhole_timeout", 222 + .maxlen = sizeof(unsigned int), 223 + .mode = 0644, 224 + .proc_handler = proc_blackhole_detect_timeout, 225 + .extra1 = SYSCTL_ZERO, 226 + }, 240 227 }; 241 228 242 229 static int mptcp_pernet_new_table(struct net *net, struct mptcp_pernet *pernet) ··· 267 240 table[6].data = &pernet->scheduler; 268 241 /* table[7] is for available_schedulers which is read-only info */ 269 242 table[8].data = &pernet->close_timeout; 243 + table[9].data = &pernet->blackhole_timeout; 270 244 271 245 hdr = register_net_sysctl_sz(net, MPTCP_SYSCTL_PATH, table, 272 246 ARRAY_SIZE(mptcp_sysctl_table)); ··· 304 276 static void mptcp_pernet_del_table(struct mptcp_pernet *pernet) {} 305 277 306 278 #endif /* CONFIG_SYSCTL */ 279 + 280 + /* The following code block is to deal with middle box issues with MPTCP, 281 + * similar to what is done with TFO. 282 + * The proposed solution is to disable active MPTCP globally when SYN+MPC are 283 + * dropped, while SYN without MPC aren't. In this case, active side MPTCP is 284 + * disabled globally for 1hr at first. Then if it happens again, it is disabled 285 + * for 2h, then 4h, 8h, ... 286 + * The timeout is reset back to 1hr when a successful active MPTCP connection is 287 + * fully established. 288 + */ 289 + 290 + /* Disable active MPTCP and record current jiffies and active_disable_times */ 291 + void mptcp_active_disable(struct sock *sk) 292 + { 293 + struct net *net = sock_net(sk); 294 + struct mptcp_pernet *pernet; 295 + 296 + pernet = mptcp_get_pernet(net); 297 + 298 + if (!READ_ONCE(pernet->blackhole_timeout)) 299 + return; 300 + 301 + /* Paired with READ_ONCE() in mptcp_active_should_disable() */ 302 + WRITE_ONCE(pernet->active_disable_stamp, jiffies); 303 + 304 + /* Paired with smp_rmb() in mptcp_active_should_disable(). 305 + * We want pernet->active_disable_stamp to be updated first. 306 + */ 307 + smp_mb__before_atomic(); 308 + atomic_inc(&pernet->active_disable_times); 309 + 310 + MPTCP_INC_STATS(net, MPTCP_MIB_BLACKHOLE); 311 + } 312 + 313 + /* Calculate timeout for MPTCP active disable 314 + * Return true if we are still in the active MPTCP disable period 315 + * Return false if timeout already expired and we should use active MPTCP 316 + */ 317 + bool mptcp_active_should_disable(struct sock *ssk) 318 + { 319 + struct net *net = sock_net(ssk); 320 + unsigned int blackhole_timeout; 321 + struct mptcp_pernet *pernet; 322 + unsigned long timeout; 323 + int disable_times; 324 + int multiplier; 325 + 326 + pernet = mptcp_get_pernet(net); 327 + blackhole_timeout = READ_ONCE(pernet->blackhole_timeout); 328 + 329 + if (!blackhole_timeout) 330 + return false; 331 + 332 + disable_times = atomic_read(&pernet->active_disable_times); 333 + if (!disable_times) 334 + return false; 335 + 336 + /* Paired with smp_mb__before_atomic() in mptcp_active_disable() */ 337 + smp_rmb(); 338 + 339 + /* Limit timeout to max: 2^6 * initial timeout */ 340 + multiplier = 1 << min(disable_times - 1, 6); 341 + 342 + /* Paired with the WRITE_ONCE() in mptcp_active_disable(). */ 343 + timeout = READ_ONCE(pernet->active_disable_stamp) + 344 + multiplier * blackhole_timeout * HZ; 345 + 346 + return time_before(jiffies, timeout); 347 + } 348 + 349 + /* Enable active MPTCP and reset active_disable_times if needed */ 350 + void mptcp_active_enable(struct sock *sk) 351 + { 352 + struct mptcp_pernet *pernet = mptcp_get_pernet(sock_net(sk)); 353 + 354 + if (atomic_read(&pernet->active_disable_times)) { 355 + struct dst_entry *dst = sk_dst_get(sk); 356 + 357 + if (dst && dst->dev && (dst->dev->flags & IFF_LOOPBACK)) 358 + atomic_set(&pernet->active_disable_times, 0); 359 + } 360 + } 361 + 362 + /* Check the number of retransmissions, and fallback to TCP if needed */ 363 + void mptcp_active_detect_blackhole(struct sock *ssk, bool expired) 364 + { 365 + struct mptcp_subflow_context *subflow; 366 + u32 timeouts; 367 + 368 + if (!sk_is_mptcp(ssk)) 369 + return; 370 + 371 + timeouts = inet_csk(ssk)->icsk_retransmits; 372 + subflow = mptcp_subflow_ctx(ssk); 373 + 374 + if (subflow->request_mptcp && ssk->sk_state == TCP_SYN_SENT) { 375 + if (timeouts == 2 || (timeouts < 2 && expired)) { 376 + MPTCP_INC_STATS(sock_net(ssk), MPTCP_MIB_MPCAPABLEACTIVEDROP); 377 + subflow->mpc_drop = 1; 378 + mptcp_subflow_early_fallback(mptcp_sk(subflow->conn), subflow); 379 + } else { 380 + subflow->mpc_drop = 0; 381 + } 382 + } 383 + } 307 384 308 385 static int __net_init mptcp_net_init(struct net *net) 309 386 {
+3
net/mptcp/mib.c
··· 15 15 SNMP_MIB_ITEM("MPCapableACKRX", MPTCP_MIB_MPCAPABLEPASSIVEACK), 16 16 SNMP_MIB_ITEM("MPCapableFallbackACK", MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK), 17 17 SNMP_MIB_ITEM("MPCapableFallbackSYNACK", MPTCP_MIB_MPCAPABLEACTIVEFALLBACK), 18 + SNMP_MIB_ITEM("MPCapableSYNTXDrop", MPTCP_MIB_MPCAPABLEACTIVEDROP), 19 + SNMP_MIB_ITEM("MPCapableSYNTXDisabled", MPTCP_MIB_MPCAPABLEACTIVEDISABLED), 18 20 SNMP_MIB_ITEM("MPFallbackTokenInit", MPTCP_MIB_TOKENFALLBACKINIT), 19 21 SNMP_MIB_ITEM("MPTCPRetrans", MPTCP_MIB_RETRANSSEGS), 20 22 SNMP_MIB_ITEM("MPJoinNoTokenFound", MPTCP_MIB_JOINNOTOKEN), ··· 75 73 SNMP_MIB_ITEM("RcvWndConflictUpdate", MPTCP_MIB_RCVWNDCONFLICTUPDATE), 76 74 SNMP_MIB_ITEM("RcvWndConflict", MPTCP_MIB_RCVWNDCONFLICT), 77 75 SNMP_MIB_ITEM("MPCurrEstab", MPTCP_MIB_CURRESTAB), 76 + SNMP_MIB_ITEM("Blackhole", MPTCP_MIB_BLACKHOLE), 78 77 SNMP_MIB_SENTINEL 79 78 }; 80 79
+3
net/mptcp/mib.h
··· 10 10 MPTCP_MIB_MPCAPABLEPASSIVEACK, /* Received third ACK with MP_CAPABLE */ 11 11 MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK,/* Server-side fallback during 3-way handshake */ 12 12 MPTCP_MIB_MPCAPABLEACTIVEFALLBACK, /* Client-side fallback during 3-way handshake */ 13 + MPTCP_MIB_MPCAPABLEACTIVEDROP, /* Client-side fallback due to a MPC drop */ 14 + MPTCP_MIB_MPCAPABLEACTIVEDISABLED, /* Client-side disabled due to past issues */ 13 15 MPTCP_MIB_TOKENFALLBACKINIT, /* Could not init/allocate token */ 14 16 MPTCP_MIB_RETRANSSEGS, /* Segments retransmitted at the MPTCP-level */ 15 17 MPTCP_MIB_JOINNOTOKEN, /* Received MP_JOIN but the token was not found */ ··· 76 74 */ 77 75 MPTCP_MIB_RCVWNDCONFLICT, /* Conflict with while updating msk rcv wnd */ 78 76 MPTCP_MIB_CURRESTAB, /* Current established MPTCP connections */ 77 + MPTCP_MIB_BLACKHOLE, /* A blackhole has been detected */ 79 78 __MPTCP_MIB_MAX 80 79 }; 81 80
+8 -10
net/mptcp/protocol.c
··· 3717 3717 return 0; 3718 3718 } 3719 3719 3720 - static void mptcp_subflow_early_fallback(struct mptcp_sock *msk, 3721 - struct mptcp_subflow_context *subflow) 3722 - { 3723 - subflow->request_mptcp = 0; 3724 - __mptcp_do_fallback(msk); 3725 - } 3726 - 3727 3720 static int mptcp_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) 3728 3721 { 3729 3722 struct mptcp_subflow_context *subflow; ··· 3737 3744 if (rcu_access_pointer(tcp_sk(ssk)->md5sig_info)) 3738 3745 mptcp_subflow_early_fallback(msk, subflow); 3739 3746 #endif 3740 - if (subflow->request_mptcp && mptcp_token_new_connect(ssk)) { 3741 - MPTCP_INC_STATS(sock_net(ssk), MPTCP_MIB_TOKENFALLBACKINIT); 3742 - mptcp_subflow_early_fallback(msk, subflow); 3747 + if (subflow->request_mptcp) { 3748 + if (mptcp_active_should_disable(sk)) { 3749 + MPTCP_INC_STATS(sock_net(ssk), MPTCP_MIB_MPCAPABLEACTIVEDISABLED); 3750 + mptcp_subflow_early_fallback(msk, subflow); 3751 + } else if (mptcp_token_new_connect(ssk) < 0) { 3752 + MPTCP_INC_STATS(sock_net(ssk), MPTCP_MIB_TOKENFALLBACKINIT); 3753 + mptcp_subflow_early_fallback(msk, subflow); 3754 + } 3743 3755 } 3744 3756 3745 3757 WRITE_ONCE(msk->write_seq, subflow->idsn);
+15 -1
net/mptcp/protocol.h
··· 531 531 valid_csum_seen : 1, /* at least one csum validated */ 532 532 is_mptfo : 1, /* subflow is doing TFO */ 533 533 close_event_done : 1, /* has done the post-closed part */ 534 - __unused : 9; 534 + mpc_drop : 1, /* the MPC option has been dropped in a rtx */ 535 + __unused : 8; 535 536 bool data_avail; 536 537 bool scheduled; 537 538 u32 remote_nonce; ··· 698 697 unsigned int mptcp_close_timeout(const struct sock *sk); 699 698 int mptcp_get_pm_type(const struct net *net); 700 699 const char *mptcp_get_scheduler(const struct net *net); 700 + 701 + void mptcp_active_disable(struct sock *sk); 702 + bool mptcp_active_should_disable(struct sock *ssk); 703 + void mptcp_active_enable(struct sock *sk); 704 + 701 705 void mptcp_get_available_schedulers(char *buf, size_t maxlen); 702 706 void __mptcp_subflow_fully_established(struct mptcp_sock *msk, 703 707 struct mptcp_subflow_context *subflow, ··· 1220 1214 } 1221 1215 1222 1216 #define pr_fallback(a) pr_debug("%s:fallback to TCP (msk=%p)\n", __func__, a) 1217 + 1218 + static inline void mptcp_subflow_early_fallback(struct mptcp_sock *msk, 1219 + struct mptcp_subflow_context *subflow) 1220 + { 1221 + pr_fallback(msk); 1222 + subflow->request_mptcp = 0; 1223 + __mptcp_do_fallback(msk); 1224 + } 1223 1225 1224 1226 static inline bool mptcp_check_infinite_map(struct sk_buff *skb) 1225 1227 {
+4
net/mptcp/subflow.c
··· 546 546 subflow->mp_capable = 1; 547 547 MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPCAPABLEACTIVEACK); 548 548 mptcp_finish_connect(sk); 549 + mptcp_active_enable(parent); 549 550 mptcp_propagate_state(parent, sk, subflow, &mp_opt); 550 551 } else if (subflow->request_join) { 551 552 u8 hmac[SHA256_DIGEST_SIZE]; ··· 592 591 MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_JOINPORTSYNACKRX); 593 592 } 594 593 } else if (mptcp_check_fallback(sk)) { 594 + /* It looks like MPTCP is blocked, while TCP is not */ 595 + if (subflow->mpc_drop) 596 + mptcp_active_disable(parent); 595 597 fallback: 596 598 mptcp_propagate_state(parent, sk, subflow, NULL); 597 599 }