Avoid bare TCP ACKs during TLS-on-connect startup.
[exim.git] / src / src / smtp_out.c
index 96ee152826ef504cbbf529cd35b3ae45afe1f388..2d2fd21802c88d063405fc23dac8bdfc61f64257 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* A number of functions for driving outgoing SMTP calls. */
@@ -63,9 +64,10 @@ if (is_tainted(expint))
   return FALSE;
   }
 
-while (isspace(*expint)) expint++;
-if (*expint == 0) return TRUE;
+Uskip_whitespace(&expint);
+if (!*expint) return TRUE;
 
+/* we just tested to ensure no taint, so big_buffer is ok */
 while ((iface = string_nextinlist(&expint, &sep, big_buffer,
           big_buffer_size)))
   {
@@ -157,20 +159,32 @@ tfo_out_check(int sock)
 {
 # ifdef __FreeBSD__
 struct tcp_info tinfo;
-int val;
-socklen_t len = sizeof(val);
+socklen_t len = sizeof(tinfo);
 
-/* The observability as of 12.1 is not useful as a client, only telling us that
-a TFO option was used on SYN.  It could have been a TFO-R, or ignored by the
-server. */
+/* A getsockopt TCP_FASTOPEN unfortunately returns "was-used" for a TFO/R as
+well as a TFO/C.  Use what we can of the Linux hack below; reliability issues ditto. */
+switch (tcp_out_fastopen)
+  {
+  case TFO_ATTEMPTED_NODATA:
+    if (  getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+       && tinfo.tcpi_state == TCPS_SYN_SENT
+       && tinfo.__tcpi_unacked > 0
+       )
+      {
+      DEBUG(D_transport|D_v)
+       debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.__tcpi_unacked);
+      tcp_out_fastopen = TFO_USED_NODATA;
+      }
+    break;
+  /*
+  case TFO_ATTEMPTED_DATA:
+  case TFO_ATTEMPTED_DATA:
+       if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)   XXX no equvalent as of 12.2
+  */
+  }
 
-/*
-if (tcp_out_fastopen == TFO_ATTEMPTED_NODATA || tcp_out_fastopen == TFO_ATTEMPTED_DATA)
-  if (getsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &val, &len) == 0 && val != 0) {}
-*/
 switch (tcp_out_fastopen)
   {
-  case TFO_ATTEMPTED_NODATA:   tcp_out_fastopen = TFO_USED_NODATA; break;
   case TFO_ATTEMPTED_DATA:     tcp_out_fastopen = TFO_USED_DATA; break;
   default: break; /* compiler quietening */
   }
@@ -315,12 +329,12 @@ else
     HDEBUG(D_transport|D_acl|D_v)
       debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len);
 
-#ifdef TCP_QUICKACK
-    (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
-#endif
     if (send(sock, early_data->data, early_data->len, 0) < 0)
       save_errno = errno;
     }
+#ifdef TCP_QUICKACK
+    (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
   }
 
 /* Either bind() or connect() failed */
@@ -374,7 +388,7 @@ smtp_port_for_connect(host_item * host, int port)
 {
 if (host->port != PORT_NONE)
   {
-  HDEBUG(D_transport|D_acl|D_v)
+  HDEBUG(D_transport|D_acl|D_v) if (port != host->port)
     debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
       host->port);
   port = host->port;
@@ -500,7 +514,7 @@ else
     rc = n;
     }
   else
-
+    {
     rc = send(outblock->cctx->sock, outblock->buffer, n,
 #ifdef MSG_MORE
              more ? MSG_MORE : 0
@@ -508,6 +522,17 @@ else
              0
 #endif
             );
+
+#if defined(__linux__)
+    /* This is a workaround for a current linux kernel bug: as of
+    5.6.8-200.fc31.x86_64  small (<MSS) writes get delayed by about 200ms,
+    This is despite NODELAY being active.
+    https://bugzilla.redhat.com/show_bug.cgi?id=1803806 */
+
+    if (!more)
+      setsockopt(outblock->cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off));
+#endif
+    }
   }
 
 if (rc <= 0)
@@ -597,7 +622,7 @@ if (format)
       while (!isspace(*p)) p++;
       while (isspace(*p)) p++;
       }
-    while (*p != 0) *p++ = '*';
+    while (*p) *p++ = '*';
     }
 
   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> %s\n", big_buffer);