Debug: tag client SMTP output with buffering qualifier
[exim.git] / src / src / smtp_out.c
index 911dd537e268becd78ac34e48a0be338b6fc0d55..2b03c03f3e76eacd23bcddd3651b5ae50cb81526 100644 (file)
@@ -67,6 +67,7 @@ if (is_tainted(expint))
 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)))
   {
@@ -158,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 */
   }
@@ -233,9 +246,18 @@ switch (tcp_out_fastopen)
 #endif
 
 
-/* Arguments as for smtp_connect(), plus
-  early_data   if non-NULL, idenmpotent data to be sent -
+/* Arguments:
+  host        host item containing name and address and port
+  host_af     AF_INET or AF_INET6
+  port       TCP port number
+  interface   outgoing interface address or NULL
+  tb          transport
+  timeout     timeout value or 0
+  early_data   if non-NULL, idempotent data to be sent -
                preferably in the TCP SYN segment
+             Special case: non-NULL but with NULL blob.data - caller is
+             client-data-first (eg. TLS-on-connect) and a lazy-TCP-connect is
+             acceptable.
 
 Returns:      connected socket number, or -1 with errno set
 */
@@ -305,23 +327,45 @@ early-data but no TFO support, send it after connecting. */
 else
   {
 #ifdef TCP_FASTOPEN
+  /* See if TCP Fast Open usable.  Default is a traditional 3WHS connect */
   if (verify_check_given_host(CUSS &ob->hosts_try_fastopen, host) == OK)
-    fastopen_blob = early_data ? early_data : &tcp_fastopen_nodata;
+    {
+    if (!early_data)
+      fastopen_blob = &tcp_fastopen_nodata;    /* TFO, with no data */
+    else if (early_data->data)
+      fastopen_blob = early_data;              /* TFO, with data */
+# ifdef TCP_FASTOPEN_CONNECT
+    else
+      {                                                /* expecting client data */
+      debug_printf(" set up lazy-connect\n");
+      setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, US &on, sizeof(on));
+      /* fastopen_blob = NULL;          lazy TFO, triggered by data write */
+      }
+# endif
+    }
 #endif
 
   if (ip_connect(sock, host_af, host->address, port, timeout, fastopen_blob) < 0)
     save_errno = errno;
   else if (early_data && !fastopen_blob && early_data->data && early_data->len)
     {
+    /* We had some early-data to send, but couldn't do TFO */
     HDEBUG(D_transport|D_acl|D_v)
       debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len);
 
-#ifdef TCP_QUICKACK
+#ifdef TCP_QUICKACK_notdef
     (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_notdef
+  /* Under TFO (with openssl & pipe-conn; testcase 4069, as of
+  5.10.8-100.fc32.x86_64) this seems to be inop.
+  Perhaps overwritten when we (client) go -> ESTABLISHED on seeing the 3rd-ACK?
+  For that case, added at smtp_reap_banner(). */
+  (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
   }
 
 /* Either bind() or connect() failed */
@@ -396,6 +440,9 @@ host->address will always be an IPv4 address.
 Arguments:
   sc         details for making connection: host, af, interface, transport
   early_data  if non-NULL, data to be sent - preferably in the TCP SYN segment
+             Special case: non-NULL but with NULL blob.data - caller is
+             client-data-first (eg. TLS-on-connect) and a lazy-TCP-connect is
+             acceptable.
 
 Returns:      connected socket number, or -1 with errno set
 */
@@ -612,7 +659,8 @@ if (format)
     while (*p) *p++ = '*';
     }
 
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> %s\n", big_buffer);
+  HDEBUG(D_transport|D_acl|D_v)
+    debug_printf_indent("  SMTP%c> %s\n", ">+|"[mode], big_buffer);
   }
 
 if (mode != SCMD_BUFFER)