uint8_t th_tfo_data_rst; /* The number of times a SYN+data has received a RST */
uint8_t th_tfo_req_rst; /* The number of times a SYN+cookie-req has received a RST */
uint8_t th_mptcp_loss; /* The number of times a SYN+MP_CAPABLE has been lost */
+ uint8_t th_mptcp_success; /* The number of times MPTCP-negotiation has been successful */
uint8_t th_ecn_loss; /* The number of times a SYN+ecn has been lost */
uint8_t th_ecn_aggressive; /* The number of times we did an aggressive fallback */
uint8_t th_ecn_droprst; /* The number of times ECN connections received a RST after first data pkt */
uint32_t th_ecn_backoff; /* Time until when we should not try out ECN */
uint8_t th_tfo_in_backoff:1, /* Are we avoiding TFO due to the backoff timer? */
- th_mptcp_in_backoff:1; /* Are we avoiding MPTCP due to the backoff timer? */
+ th_mptcp_in_backoff:1, /* Are we avoiding MPTCP due to the backoff timer? */
+ th_mptcp_heuristic_disabled:1; /* Are heuristics disabled? */
char th_val_end[0]; /* Marker for memsetting to 0 */
};
struct tcp_cache {
SLIST_ENTRY(tcp_cache) list;
- u_int32_t tc_last_access;
+ uint32_t tc_last_access;
struct tcp_cache_key tc_key;
- u_int8_t tc_tfo_cookie[TFO_COOKIE_LEN_MAX];
- u_int8_t tc_tfo_cookie_len;
+ uint8_t tc_tfo_cookie[TFO_COOKIE_LEN_MAX];
+ uint8_t tc_tfo_cookie_len;
};
struct tcp_cache_head {
int af;
};
-static u_int32_t tcp_cache_hash_seed;
+static uint32_t tcp_cache_hash_seed;
size_t tcp_cache_size;
SYSCTL_UINT(_net_inet_tcp, OID_AUTO, backoff_maximum, CTLFLAG_RW | CTLFLAG_LOCKED,
&tcp_backoff_maximum, 0, "Maximum time for which we won't try TFO");
-SYSCTL_SKMEM_TCP_INT(OID_AUTO, ecn_timeout, CTLFLAG_RW | CTLFLAG_LOCKED,
- static int, tcp_ecn_timeout, 60, "Initial minutes to wait before re-trying ECN");
+static uint32_t tcp_ecn_timeout = 60;
-SYSCTL_SKMEM_TCP_INT(OID_AUTO, disable_tcp_heuristics, CTLFLAG_RW | CTLFLAG_LOCKED,
- static int, disable_tcp_heuristics, 0, "Set to 1, to disable all TCP heuristics (TFO, ECN, MPTCP)");
+SYSCTL_UINT(_net_inet_tcp, OID_AUTO, ecn_timeout, CTLFLAG_RW | CTLFLAG_LOCKED,
+ &tcp_ecn_timeout, 60, "Initial minutes to wait before re-trying ECN");
+
+static int disable_tcp_heuristics = 0;
+SYSCTL_INT(_net_inet_tcp, OID_AUTO, disable_tcp_heuristics, CTLFLAG_RW | CTLFLAG_LOCKED,
+ &disable_tcp_heuristics, 0, "Set to 1, to disable all TCP heuristics (TFO, ECN, MPTCP)");
static uint32_t
tcp_min_to_hz(uint32_t minutes)
#define TFO_MAX_COOKIE_LOSS 2
#define ECN_MAX_SYN_LOSS 2
#define MPTCP_MAX_SYN_LOSS 2
+#define MPTCP_SUCCESS_TRIGGER 10
#define ECN_MAX_DROPRST 1
#define ECN_MAX_DROPRXMT 4
#define ECN_MAX_SYNRST 4
* Might be worth moving this to a library so that others
* (e.g., scale_to_powerof2()) can use this as well instead of a while-loop.
*/
-static u_int32_t
-tcp_cache_roundup2(u_int32_t a)
+static uint32_t
+tcp_cache_roundup2(uint32_t a)
{
a--;
a |= a >> 1;
}
}
-static u_int16_t
+static uint16_t
tcp_cache_hash(struct tcp_cache_key_src *tcks, struct tcp_cache_key *key)
{
- u_int32_t hash;
+ uint32_t hash;
bzero(key, sizeof(struct tcp_cache_key));
hash = net_flowhash(key, sizeof(struct tcp_cache_key),
tcp_cache_hash_seed);
- return hash & (tcp_cache_size - 1);
+ return (uint16_t)(hash & (tcp_cache_size - 1));
}
static void
struct tcp_cache *tpcache = NULL;
struct tcp_cache_head *head;
struct tcp_cache_key key;
- u_int16_t hash;
+ uint16_t hash;
int i = 0;
hash = tcp_cache_hash(tcks, &key);
if ((tpcache == NULL) && create) {
if (i >= TCP_CACHE_BUCKET_SIZE) {
struct tcp_cache *oldest_cache = NULL;
- u_int32_t max_age = 0;
+ uint32_t max_age = 0;
/* Look for the oldest tcp_cache in the bucket */
SLIST_FOREACH(tpcache, &head->tcp_caches, list) {
- u_int32_t age = tcp_now - tpcache->tc_last_access;
+ uint32_t age = tcp_now - tpcache->tc_last_access;
if (age > max_age) {
max_age = age;
oldest_cache = tpcache;
tpcache = _MALLOC(sizeof(struct tcp_cache), M_TEMP,
M_NOWAIT | M_ZERO);
if (tpcache == NULL) {
+ os_log_error(OS_LOG_DEFAULT, "%s could not allocate cache", __func__);
goto out_null;
}
}
static void
-tcp_cache_set_cookie_common(struct tcp_cache_key_src *tcks, u_char *cookie, u_int8_t len)
+tcp_cache_set_cookie_common(struct tcp_cache_key_src *tcks, u_char *cookie, uint8_t len)
{
struct tcp_cache_head *head;
struct tcp_cache *tpcache;
}
void
-tcp_cache_set_cookie(struct tcpcb *tp, u_char *cookie, u_int8_t len)
+tcp_cache_set_cookie(struct tcpcb *tp, u_char *cookie, uint8_t len)
{
struct tcp_cache_key_src tcks;
}
static int
-tcp_cache_get_cookie_common(struct tcp_cache_key_src *tcks, u_char *cookie, u_int8_t *len)
+tcp_cache_get_cookie_common(struct tcp_cache_key_src *tcks, u_char *cookie, uint8_t *len)
{
struct tcp_cache_head *head;
struct tcp_cache *tpcache;
* Returns 1 if the cookie has been found and written.
*/
int
-tcp_cache_get_cookie(struct tcpcb *tp, u_char *cookie, u_int8_t *len)
+tcp_cache_get_cookie(struct tcpcb *tp, u_char *cookie, uint8_t *len)
{
struct tcp_cache_key_src tcks;
return tcp_cache_get_cookie_len_common(&tcks);
}
-static u_int16_t
+static uint16_t
tcp_heuristics_hash(struct tcp_cache_key_src *tcks, struct tcp_heuristic_key *key)
{
- u_int32_t hash;
+ uint32_t hash;
bzero(key, sizeof(struct tcp_heuristic_key));
hash = net_flowhash(key, sizeof(struct tcp_heuristic_key),
tcp_cache_hash_seed);
- return hash & (tcp_cache_size - 1);
+ return (uint16_t)(hash & (tcp_cache_size - 1));
}
static void
struct tcp_heuristic *tpheur = NULL;
struct tcp_heuristics_head *head;
struct tcp_heuristic_key key;
- u_int16_t hash;
+ uint16_t hash;
int i = 0;
hash = tcp_heuristics_hash(tcks, &key);
if ((tpheur == NULL) && create) {
if (i >= TCP_CACHE_BUCKET_SIZE) {
struct tcp_heuristic *oldest_heur = NULL;
- u_int32_t max_age = 0;
+ uint32_t max_age = 0;
/* Look for the oldest tcp_heur in the bucket */
SLIST_FOREACH(tpheur, &head->tcp_heuristics, list) {
- u_int32_t age = tcp_now - tpheur->th_last_access;
+ uint32_t age = tcp_now - tpheur->th_last_access;
if (age > max_age) {
max_age = age;
oldest_heur = tpheur;
tpheur = _MALLOC(sizeof(struct tcp_heuristic), M_TEMP,
M_NOWAIT | M_ZERO);
if (tpheur == NULL) {
+ os_log_error(OS_LOG_DEFAULT, "%s could not allocate cache", __func__);
goto out_null;
}
}
static void
-tcp_heuristic_reset_counters(struct tcp_cache_key_src *tcks, u_int8_t flags)
+tcp_heuristic_reset_counters(struct tcp_cache_key_src *tcks, uint8_t flags)
{
struct tcp_heuristics_head *head;
struct tcp_heuristic *tpheur;
/*
- * Don't attempt to create it! Keep the heuristics clean if the
- * server does not support TFO. This reduces the lookup-cost on
- * our side.
+ * Always create heuristics here because MPTCP needs to write success
+ * into it. Thus, we always end up creating them.
*/
- tpheur = tcp_getheuristic_with_lock(tcks, 0, &head);
+ tpheur = tcp_getheuristic_with_lock(tcks, 1, &head);
if (tpheur == NULL) {
return;
}
if (flags & TCPCACHE_F_TFO_DATA) {
+ if (tpheur->th_tfo_data_loss >= TFO_MAX_COOKIE_LOSS) {
+ os_log(OS_LOG_DEFAULT, "%s: Resetting TFO-data loss to 0 from %u on heur %lx\n",
+ __func__, tpheur->th_tfo_data_loss, (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
+ }
tpheur->th_tfo_data_loss = 0;
}
if (flags & TCPCACHE_F_TFO_REQ) {
+ if (tpheur->th_tfo_req_loss >= TFO_MAX_COOKIE_LOSS) {
+ os_log(OS_LOG_DEFAULT, "%s: Resetting TFO-req loss to 0 from %u on heur %lx\n",
+ __func__, tpheur->th_tfo_req_loss, (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
+ }
tpheur->th_tfo_req_loss = 0;
}
if (flags & TCPCACHE_F_TFO_DATA_RST) {
+ if (tpheur->th_tfo_data_rst >= TFO_MAX_COOKIE_LOSS) {
+ os_log(OS_LOG_DEFAULT, "%s: Resetting TFO-data RST to 0 from %u on heur %lx\n",
+ __func__, tpheur->th_tfo_data_rst, (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
+ }
tpheur->th_tfo_data_rst = 0;
}
if (flags & TCPCACHE_F_TFO_REQ_RST) {
+ if (tpheur->th_tfo_req_rst >= TFO_MAX_COOKIE_LOSS) {
+ os_log(OS_LOG_DEFAULT, "%s: Resetting TFO-req RST to 0 from %u on heur %lx\n",
+ __func__, tpheur->th_tfo_req_rst, (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
+ }
tpheur->th_tfo_req_rst = 0;
}
if (flags & TCPCACHE_F_ECN) {
+ if (tpheur->th_ecn_loss >= ECN_MAX_SYN_LOSS || tpheur->th_ecn_synrst >= ECN_MAX_SYNRST) {
+ os_log(OS_LOG_DEFAULT, "%s: Resetting ECN-loss to 0 from %u and synrst from %u on heur %lx\n",
+ __func__, tpheur->th_ecn_loss, tpheur->th_ecn_synrst, (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
+ }
tpheur->th_ecn_loss = 0;
tpheur->th_ecn_synrst = 0;
}
if (flags & TCPCACHE_F_MPTCP) {
tpheur->th_mptcp_loss = 0;
+ if (tpheur->th_mptcp_success < MPTCP_SUCCESS_TRIGGER) {
+ tpheur->th_mptcp_success++;
+
+ if (tpheur->th_mptcp_success == MPTCP_SUCCESS_TRIGGER) {
+ os_log(mptcp_log_handle, "%s disabling heuristics for 12 hours", __func__);
+ tpheur->th_mptcp_heuristic_disabled = 1;
+ /* Disable heuristics for 12 hours */
+ tpheur->th_mptcp_backoff = tcp_now + tcp_min_to_hz(tcp_ecn_timeout * 12);
+ }
+ }
}
tcp_heuristic_unlock(head);
if (tpheur->th_tfo_backoff > tcp_min_to_hz(tcp_backoff_maximum)) {
tpheur->th_tfo_backoff = tcp_min_to_hz(tcp_ecn_timeout);
}
+
+ os_log(OS_LOG_DEFAULT, "%s disable TFO until %u now %u on %lx\n", __func__,
+ tpheur->th_tfo_backoff_until, tcp_now, (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
}
static void
static void
tcp_heuristic_inc_counters(struct tcp_cache_key_src *tcks,
- u_int32_t flags)
+ uint32_t flags)
{
struct tcp_heuristics_head *head;
struct tcp_heuristic *tpheur;
}
}
- if ((flags & TCPCACHE_F_ECN) && tpheur->th_ecn_loss < TCP_CACHE_OVERFLOW_PROTECT) {
+ if ((flags & TCPCACHE_F_ECN) &&
+ tpheur->th_ecn_loss < TCP_CACHE_OVERFLOW_PROTECT &&
+ TSTMP_LEQ(tpheur->th_ecn_backoff, tcp_now)) {
tpheur->th_ecn_loss++;
if (tpheur->th_ecn_loss >= ECN_MAX_SYN_LOSS) {
tcpstat.tcps_ecn_fallback_synloss++;
tpheur->th_ecn_backoff = tcp_now +
(tcp_min_to_hz(tcp_ecn_timeout) <<
(tpheur->th_ecn_loss - ECN_MAX_SYN_LOSS));
+
+ os_log(OS_LOG_DEFAULT, "%s disable ECN until %u now %u on %lx for SYN-loss\n",
+ __func__, tpheur->th_ecn_backoff, tcp_now,
+ (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
}
}
if ((flags & TCPCACHE_F_MPTCP) &&
- tpheur->th_mptcp_loss < TCP_CACHE_OVERFLOW_PROTECT) {
+ tpheur->th_mptcp_loss < TCP_CACHE_OVERFLOW_PROTECT &&
+ tpheur->th_mptcp_heuristic_disabled == 0) {
tpheur->th_mptcp_loss++;
if (tpheur->th_mptcp_loss >= MPTCP_MAX_SYN_LOSS) {
/*
tpheur->th_mptcp_backoff = tcp_now +
(tcp_min_to_hz(tcp_ecn_timeout) <<
(tpheur->th_mptcp_loss - MPTCP_MAX_SYN_LOSS));
+ tpheur->th_mptcp_in_backoff = 1;
+
+ os_log(OS_LOG_DEFAULT, "%s disable MPTCP until %u now %u on %lx\n",
+ __func__, tpheur->th_mptcp_backoff, tcp_now,
+ (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
}
}
if ((flags & TCPCACHE_F_ECN_DROPRST) &&
- tpheur->th_ecn_droprst < TCP_CACHE_OVERFLOW_PROTECT) {
+ tpheur->th_ecn_droprst < TCP_CACHE_OVERFLOW_PROTECT &&
+ TSTMP_LEQ(tpheur->th_ecn_backoff, tcp_now)) {
tpheur->th_ecn_droprst++;
if (tpheur->th_ecn_droprst >= ECN_MAX_DROPRST) {
tcpstat.tcps_ecn_fallback_droprst++;
tpheur->th_ecn_backoff = tcp_now +
(tcp_min_to_hz(tcp_ecn_timeout) <<
(tpheur->th_ecn_droprst - ECN_MAX_DROPRST));
+
+ os_log(OS_LOG_DEFAULT, "%s disable ECN until %u now %u on %lx for drop-RST\n",
+ __func__, tpheur->th_ecn_backoff, tcp_now,
+ (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
}
}
if ((flags & TCPCACHE_F_ECN_DROPRXMT) &&
- tpheur->th_ecn_droprxmt < TCP_CACHE_OVERFLOW_PROTECT) {
+ tpheur->th_ecn_droprxmt < TCP_CACHE_OVERFLOW_PROTECT &&
+ TSTMP_LEQ(tpheur->th_ecn_backoff, tcp_now)) {
tpheur->th_ecn_droprxmt++;
if (tpheur->th_ecn_droprxmt >= ECN_MAX_DROPRXMT) {
tcpstat.tcps_ecn_fallback_droprxmt++;
tpheur->th_ecn_backoff = tcp_now +
(tcp_min_to_hz(tcp_ecn_timeout) <<
(tpheur->th_ecn_droprxmt - ECN_MAX_DROPRXMT));
+
+ os_log(OS_LOG_DEFAULT, "%s disable ECN until %u now %u on %lx for drop-Rxmit\n",
+ __func__, tpheur->th_ecn_backoff, tcp_now,
+ (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
}
}
if ((flags & TCPCACHE_F_ECN_SYNRST) &&
tpheur->th_ecn_backoff = tcp_now +
(tcp_min_to_hz(tcp_ecn_timeout) <<
(tpheur->th_ecn_synrst - ECN_MAX_SYNRST));
+
+ os_log(OS_LOG_DEFAULT, "%s disable ECN until %u now %u on %lx for SYN-RST\n",
+ __func__, tpheur->th_ecn_backoff, tcp_now,
+ (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
}
}
tcp_heuristic_unlock(head);
struct tcp_cache_key_src tcks;
uint32_t flag = 0;
+ if (symptoms_is_wifi_lossy() &&
+ IFNET_IS_WIFI(tp->t_inpcb->inp_last_outifp)) {
+ return;
+ }
+
tcp_cache_key_src_create(tp, &tcks);
if (tp->t_tfo_stats & TFO_S_SYN_DATA_SENT) {
{
struct tcp_cache_key_src tcks;
+ if (symptoms_is_wifi_lossy() &&
+ IFNET_IS_WIFI(tp->t_inpcb->inp_last_outifp)) {
+ return;
+ }
+
tcp_cache_key_src_create(tp, &tcks);
tcp_heuristic_inc_counters(&tcks, TCPCACHE_F_MPTCP);
{
struct tcp_cache_key_src tcks;
+ if (symptoms_is_wifi_lossy() &&
+ IFNET_IS_WIFI(tp->t_inpcb->inp_last_outifp)) {
+ return;
+ }
+
tcp_cache_key_src_create(tp, &tcks);
tcp_heuristic_inc_counters(&tcks, TCPCACHE_F_ECN);
return;
}
+ if (TSTMP_GT(tpheur->th_ecn_backoff, tcp_now)) {
+ /* We are already in aggressive mode */
+ tcp_heuristic_unlock(head);
+ return;
+ }
+
/* Must be done before, otherwise we will start off with expo-backoff */
tpheur->th_ecn_backoff = tcp_now +
(tcp_min_to_hz(tcp_ecn_timeout) << (tpheur->th_ecn_aggressive));
}
tcp_heuristic_unlock(head);
+
+ os_log(OS_LOG_DEFAULT, "%s disable ECN until %u now %u on %lx\n", __func__,
+ tpheur->th_ecn_backoff, tcp_now, (unsigned long)VM_KERNEL_ADDRPERM(tpheur));
}
void
return FALSE;
}
-
-boolean_t
+/*
+ * @return:
+ * 0 Enable MPTCP (we are still discovering middleboxes)
+ * -1 Enable MPTCP (heuristics have been temporarily disabled)
+ * 1 Disable MPTCP
+ */
+int
tcp_heuristic_do_mptcp(struct tcpcb *tp)
{
struct tcp_cache_key_src tcks;
struct tcp_heuristics_head *head = NULL;
struct tcp_heuristic *tpheur;
+ int ret = 0;
- if (disable_tcp_heuristics) {
- return TRUE;
+ if (disable_tcp_heuristics ||
+ (tptomptp(tp)->mpt_mpte->mpte_flags & MPTE_FORCE_ENABLE)) {
+ return 0;
}
tcp_cache_key_src_create(tp, &tcks);
/* Get the tcp-heuristic. */
tpheur = tcp_getheuristic_with_lock(&tcks, 0, &head);
if (tpheur == NULL) {
- return TRUE;
+ return 0;
+ }
+
+ if (tpheur->th_mptcp_in_backoff == 0 ||
+ tpheur->th_mptcp_heuristic_disabled == 1) {
+ goto mptcp_ok;
}
if (TSTMP_GT(tpheur->th_mptcp_backoff, tcp_now)) {
goto fallback;
}
- tcp_heuristic_unlock(head);
+ tpheur->th_mptcp_in_backoff = 0;
- return TRUE;
+mptcp_ok:
+ if (tpheur->th_mptcp_heuristic_disabled) {
+ ret = -1;
+
+ if (TSTMP_GT(tcp_now, tpheur->th_mptcp_backoff)) {
+ tpheur->th_mptcp_heuristic_disabled = 0;
+ tpheur->th_mptcp_success = 0;
+ }
+ }
+
+ tcp_heuristic_unlock(head);
+ return ret;
fallback:
if (head) {
tcpstat.tcps_mptcp_heuristic_fallback++;
}
- return FALSE;
+ return 1;
}
static boolean_t
if (tpheur->th_ecn_synrst >= ECN_RETRY_LIMIT) {
tpheur->th_ecn_synrst = 0;
}
+
+ /* Make sure it follows along */
+ tpheur->th_ecn_backoff = tcp_now;
}
tcp_heuristic_unlock(head);
boolean_t
tcp_heuristic_do_tfo_with_address(struct ifnet *ifp,
union sockaddr_in_4_6 *local_address, union sockaddr_in_4_6 *remote_address,
- u_int8_t *cookie, u_int8_t *cookie_len)
+ uint8_t *cookie, uint8_t *cookie_len)
{
struct tcp_cache_key_src tcks;
val = oldval;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error || !req->newptr) {
+ if (error) {
+ os_log_error(OS_LOG_DEFAULT, "%s could not parse int: %d", __func__, error);
+ }
return error;
}
* On machines with > 4GB of memory, we have a cache-size of 1024 entries,
* thus about 327KB.
*
- * Side-note: we convert to u_int32_t. If sane_size is more than
+ * Side-note: we convert to uint32_t. If sane_size is more than
* 16000 TB, we loose precision. But, who cares? :)
*/
- tcp_cache_size = tcp_cache_roundup2((u_int32_t)(sane_size_meg >> 2));
+ tcp_cache_size = tcp_cache_roundup2((uint32_t)(sane_size_meg >> 2));
if (tcp_cache_size < 32) {
tcp_cache_size = 32;
} else if (tcp_cache_size > 1024) {