MacOS: TCP Fast Open
authorJeremy Harris <jgh146exb@wizmail.org>
Tue, 30 Oct 2018 22:09:15 +0000 (22:09 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 30 Oct 2018 22:36:55 +0000 (22:36 +0000)
doc/doc-txt/NewStuff
src/OS/os.h-Darwin
src/src/daemon.c
src/src/expand.c
src/src/ip.c
src/src/malware.c
src/src/smtp_out.c
src/src/transports/smtp_socks.c
src/src/verify.c
test/scripts/1990-TCP-Fast-Open/1990
test/src/server.c

index 10461aa05f697b34231c28bfb519378365f9c11e..cc9721adaa79ccb18730ff2d0c8277c1b57cb190 100644 (file)
@@ -29,6 +29,8 @@ Version 4.92
 
  8. A "noutf8" debug option, for disabling the UTF-8 characters in debug output.
 
 
  8. A "noutf8" debug option, for disabling the UTF-8 characters in debug output.
 
+ 9. TCP Fast Open support on MacOS.
+
 Version 4.91
 --------------
 
 Version 4.91
 --------------
 
index 67aeac913727263c3edff597d4f2c61d1cd95a28..4667689c9b171cddc52aee5050774fd864b6636d 100644 (file)
@@ -51,10 +51,8 @@ in "man 2 getgroups". */
 #define _DARWIN_UNLIMITED_GETGROUPS
 #define EXIM_GROUPLIST_SIZE 64
 
 #define _DARWIN_UNLIMITED_GETGROUPS
 #define EXIM_GROUPLIST_SIZE 64
 
-/* TCP_FASTOPEN support.  For the moment, claim there is none
-(the probe fails; unsure why).
-Sometime in the future need to investigate connectex(). */
-
-#define EXIM_TFO_PROBE
+/* TCP Fast Open: Darwin uses a connectex() call
+rather than a modified sendto() */
+#define EXIM_TFO_CONNECTX
 
 /* End */
 
 /* End */
index ee9ddcc4f2b6ec8127e182318c96ce8a6d35eb08..1a15d46c0737102873cadb5123ab1fe0b8130204 100644 (file)
@@ -1458,7 +1458,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       else
         debug_printf("listening on %s port %d\n", ipa->address, ipa->port);
 
       else
         debug_printf("listening on %s port %d\n", ipa->address, ipa->port);
 
-#ifdef TCP_FASTOPEN
+#if defined(TCP_FASTOPEN) && !defined(__APPLE__)
     if (  f.tcp_fastopen_ok
        && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
                    &smtp_connect_backlog, sizeof(smtp_connect_backlog)))
     if (  f.tcp_fastopen_ok
        && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
                    &smtp_connect_backlog, sizeof(smtp_connect_backlog)))
@@ -1471,7 +1471,19 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     /* Start listening on the bound socket, establishing the maximum backlog of
     connections that is allowed. On success, continue to the next address. */
 
     /* Start listening on the bound socket, establishing the maximum backlog of
     connections that is allowed. On success, continue to the next address. */
 
-    if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0) continue;
+    if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0)
+      {
+#if defined(TCP_FASTOPEN) && defined(__APPLE__)
+      if (  f.tcp_fastopen_ok
+        && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
+                     &on, sizeof(on)))
+       {
+       DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+       f.tcp_fastopen_ok = FALSE;
+       }
+#endif
+      continue;
+      }
 
     /* Listening has failed. In an IPv6 environment, as for bind(), if listen()
     fails with the error EADDRINUSE and we are doing IPv4 wildcard listening
 
     /* Listening has failed. In an IPv6 environment, as for bind(), if listen()
     fails with the error EADDRINUSE and we are doing IPv4 wildcard listening
index 5054e151bb73bf2a593e5046fdb7d4730540168c..49e09ecd8e9c3480ec87e9e12847b45015b62fb4 100644 (file)
@@ -5021,6 +5021,7 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }
 
             port = ntohs(service_info->s_port);
             }
 
+         /*XXX we trust that the request is idempotent.  Hmm. */
          fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
                  timeout, &host, &expand_string_message,
                  do_tls ? NULL : &reqstr);
          fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
                  timeout, &host, &expand_string_message,
                  do_tls ? NULL : &reqstr);
index fa688be644cdc1208aab75130501ae8ced76d81b..d601b46b3a3b8c6fe537aa2bded3cd343c1323a1 100644 (file)
@@ -196,7 +196,7 @@ Arguments:
   port        the remote port
   timeout     a timeout (zero for indefinite timeout)
   fastopen_blob    non-null iff TCP_FASTOPEN can be used; may indicate early-data to
   port        the remote port
   timeout     a timeout (zero for indefinite timeout)
   fastopen_blob    non-null iff TCP_FASTOPEN can be used; may indicate early-data to
-               be sent in SYN segment
+               be sent in SYN segment.  Any such data must be idempotent.
 
 Returns:      0 on success; -1 on failure, with errno set
 */
 
 Returns:      0 on success; -1 on failure, with errno set
 */
@@ -245,19 +245,19 @@ callout_address = string_sprintf("[%s]:%d", address, port);
 sigalrm_seen = FALSE;
 if (timeout > 0) ALARM(timeout);
 
 sigalrm_seen = FALSE;
 if (timeout > 0) ALARM(timeout);
 
-#if defined(TCP_FASTOPEN) && defined(MSG_FASTOPEN)
+#ifdef TCP_FASTOPEN
 /* TCP Fast Open, if the system has a cookie from a previous call to
 this peer, can send data in the SYN packet.  The peer can send data
 before it gets our ACK of its SYN,ACK - the latter is useful for
 the SMTP banner.  Other (than SMTP) cases of TCP connections can
 /* TCP Fast Open, if the system has a cookie from a previous call to
 this peer, can send data in the SYN packet.  The peer can send data
 before it gets our ACK of its SYN,ACK - the latter is useful for
 the SMTP banner.  Other (than SMTP) cases of TCP connections can
-possibly use the data-on-syn, so support that too.
-
-This is a Linux implementation.  It might be useable on FreeBSD; I have
-not checked.  I think MacOS has a "connectx" call for this purpose,
-rather than using "sendto" ? */
+possibly use the data-on-syn, so support that too. */
 
 if (fastopen_blob && f.tcp_fastopen_ok)
   {
 
 if (fastopen_blob && f.tcp_fastopen_ok)
   {
+# ifdef MSG_FASTOPEN
+  /* This is a Linux implementation.  It might be useable on FreeBSD; I have
+  not checked. */
+
   if ((rc = sendto(sock, fastopen_blob->data, fastopen_blob->len,
                    MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) >= 0)
        /* seen for with-data, experimental TFO option, with-cookie case */
   if ((rc = sendto(sock, fastopen_blob->data, fastopen_blob->len,
                    MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) >= 0)
        /* seen for with-data, experimental TFO option, with-cookie case */
@@ -292,9 +292,44 @@ if (fastopen_blob && f.tcp_fastopen_ok)
       debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
     goto legacy_connect;
     }
       debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
     goto legacy_connect;
     }
+# endif
+# ifdef EXIM_TFO_CONNECTX
+  /* MacOS */
+  sa_endpoints_t ends = {
+    .sae_srcif = 0, .sae_srcaddr = NULL, .sae_srcaddrlen = 0,
+    .sae_dstaddr = s_ptr, .sae_dstaddrlen = s_len };
+  struct iovec iov = {
+    .iov_base = fastopen_blob->data, .iov_len = fastopen_blob->len };
+  size_t len;
+
+  if ((rc = connectx(sock, &ends, SAE_ASSOCID_ANY,
+            CONNECT_DATA_IDEMPOTENT, &iov, 1, &len, NULL)) == 0)
+    {
+    DEBUG(D_transport|D_v)
+      debug_printf("TFO mode connection attempt to %s, %lu data\n",
+       address, (unsigned long)fastopen_blob->len);
+    tcp_out_fastopen = fastopen_blob->len > 0 ?  TFO_USED : TFO_ATTEMPTED;
+
+    if (len != fastopen_blob->len)
+      DEBUG(D_transport|D_v)
+       debug_printf(" only queued %lu data!\n", (unsigned long)len);
+    }
+  else if (errno == EINPROGRESS)
+    {
+    DEBUG(D_transport|D_v) debug_printf("TFO mode sendto, %s data: EINPROGRESS\n",
+      fastopen_blob->len > 0 ? "with"  : "no");
+    if (!fastopen_blob->data)
+      {
+      tcp_out_fastopen = TFO_ATTEMPTED;                /* we tried; unknown if useful yet */
+      rc = 0;
+      }
+    else       /* assume that no data was queued; block in send */
+      rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
+    }
+# endif
   }
 else
   }
 else
-#endif
+#endif /*TCP_FASTOPEN*/
   {
 legacy_connect:
   DEBUG(D_transport|D_v) if (fastopen_blob)
   {
 legacy_connect:
   DEBUG(D_transport|D_v) if (fastopen_blob)
@@ -350,7 +385,7 @@ Arguments:
   connhost     if not NULL, host_item to be filled in with connection details
   errstr        pointer for allocated string on error
   fastopen_blob        with SOCK_STREAM, if non-null, request TCP Fast Open.
   connhost     if not NULL, host_item to be filled in with connection details
   errstr        pointer for allocated string on error
   fastopen_blob        with SOCK_STREAM, if non-null, request TCP Fast Open.
-               Additionally, optional early-data to send
+               Additionally, optional idempotent early-data to send
 
 Return:
   socket fd, or -1 on failure (having allocated an error string)
 
 Return:
   socket fd, or -1 on failure (having allocated an error string)
index 069a544833f95b55b8dd096fd6f1c7664c3229b9..541ff3b3c5f4ee00a847c806a39c0440bbea6e84 100644 (file)
@@ -1573,6 +1573,7 @@ badseek:  err = errno;
           * on both connections (as one host could resolve to multiple ips) */
          for (;;)
            {
           * on both connections (as one host could resolve to multiple ips) */
          for (;;)
            {
+           /*XXX we trust that the cmd_str is ideempotent */
            if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
                                    &connhost, &errstr, &cmd_str)) >= 0)
              {
            if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
                                    &connhost, &errstr, &cmd_str)) >= 0)
              {
index ef2c9fdeb37a0d07e0beb7b8026b1d7ff48c00e2..c5eafbc57499f003bdd66742925c73bea293db62 100644 (file)
@@ -194,7 +194,8 @@ if (getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
 
 
 /* Arguments as for smtp_connect(), plus
 
 
 /* Arguments as for smtp_connect(), plus
-  early_data   if non-NULL, data to be sent - preferably in the TCP SYN segment
+  early_data   if non-NULL, idenmpotent data to be sent -
+               preferably in the TCP SYN segment
 
 Returns:      connected socket number, or -1 with errno set
 */
 
 Returns:      connected socket number, or -1 with errno set
 */
index c7415c35703211bf4620ad19d19f4054546fc822..7d3a462305a7245a57b2405af860f93a48708752 100644 (file)
@@ -297,6 +297,7 @@ for(;;)
   proxy.address = proxy.name = sob->proxy_host;
   proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
 
   proxy.address = proxy.name = sob->proxy_host;
   proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
 
+  /*XXX we trust that the method-select command is idempotent */
   if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port,
              interface, tb, sob->timeout, &early_data)) >= 0)
     {
   if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port,
              interface, tb, sob->timeout, &early_data)) >= 0)
     {
index 9aff78a9a83129dc0148a6d50c7bc492c364f1eb..d14cb685e8da5670a16c331ef5f5e089d32807e6 100644 (file)
@@ -2745,6 +2745,7 @@ qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n",
 early_data.data = buffer;
 early_data.len = qlen;
 
 early_data.data = buffer;
 early_data.len = qlen;
 
+/*XXX we trust that the query is idempotent */
 if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port,
                rfc1413_query_timeout, &early_data) < 0)
   {
 if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port,
                rfc1413_query_timeout, &early_data) < 0)
   {
index 04b41a7af8b644f55439c7c6a987c057ec70f78b..ec8e32c8f0935780d345995b635514f1c182b102 100644 (file)
 # an unhelpful error from RTNETLINK.
 # To tidy up:  'sudo tc qdisc delete dev lo root'
 #
 # an unhelpful error from RTNETLINK.
 # To tidy up:  'sudo tc qdisc delete dev lo root'
 #
+# MacOS:
+# The kernel seems to have TFO enabled both ways as default.
+# There is a net.inet.tcp.clear_tfocache parameter
+## sysctl -w foo-val
+#
+# For network delays there is something called 'Network Link Conditioner'
+# which might do the job.  But how to manipulate it?
+#
+#
 sudo perl
 system ("tc qdisc add dev lo root netem delay 50ms");
 ****
 sudo perl
 system ("tc qdisc add dev lo root netem delay 50ms");
 ****
@@ -22,17 +31,14 @@ system ("tc qdisc add dev lo root netem delay 50ms");
 # (currently on a separate packet after the server SYN,ACK but before
 # the client ACK).
 #
 # (currently on a separate packet after the server SYN,ACK but before
 # the client ACK).
 #
-# The client log => lint.ex  should have a "TFO" element.
-# Assuming this is the first run since boot, the a@test recipient will not.
+# The client log => line  should have a "TFO" element.
+# The server log <= line for a@test.ex  should not.
 #
 #
+# First clear any previously-obtained cookie:
 sudo perl
 system ("ip tcp_metrics delete 127.0.0.1");
 ****
 #
 sudo perl
 system ("ip tcp_metrics delete 127.0.0.1");
 ****
 #
-# The server log <= line for b@test.ex  should have a "TFO" element, but
-# this will only be obtained when the above delay is inserted into the
-# loopback net path.
-#
 #
 #
 # FreeBSD: it looks like you have to compile a custom kernel, with
 #
 #
 # FreeBSD: it looks like you have to compile a custom kernel, with
@@ -48,6 +54,10 @@ Testing
 ****
 sleep 3
 #
 ****
 sleep 3
 #
+# The server log <= line for b@test.ex  should have a "TFO" element, but
+# this will only be obtained when the above delay is inserted into the
+# loopback net path.
+#
 exim b@test.ex
 Testing
 ****
 exim b@test.ex
 Testing
 ****
index fe1c79f020d7d4be1cfc1176f39937d41b5a553a..ba731625b3733fca1234f280655e20568952bc96 100644 (file)
@@ -298,7 +298,7 @@ else
       printf("IPv6 socket creation failed: %s\n", strerror(errno));
       exit(1);
       }
       printf("IPv6 socket creation failed: %s\n", strerror(errno));
       exit(1);
       }
-#ifdef TCP_FASTOPEN
+#if defined(TCP_FASTOPEN) && !defined(__APPLE__)
     if (tfo)
       {
       int backlog = 5;
     if (tfo)
       {
       int backlog = 5;
@@ -330,7 +330,7 @@ else
       printf("IPv4 socket creation failed: %s\n", strerror(errno));
       exit(1);
       }
       printf("IPv4 socket creation failed: %s\n", strerror(errno));
       exit(1);
       }
-#ifdef TCP_FASTOPEN
+#if defined(TCP_FASTOPEN) && !defined(__APPLE__)
     if (tfo)
       {
       int backlog = 5;
     if (tfo)
       {
       int backlog = 5;
@@ -438,16 +438,21 @@ error because it means that the IPv6 socket will handle IPv4 connections. Don't
 output anything, because it will mess up the test output, which will be
 different for systems that do this and those that don't. */
 
 output anything, because it will mess up the test output, which will be
 different for systems that do this and those that don't. */
 
-for (i = 0; i <= skn; i++)
+for (i = 0; i <= skn; i++) if (listen_socket[i] >= 0)
   {
   {
-  if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
-    {
+  if (listen(listen_socket[i], 5) < 0)
     if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
       {
       printf("listen() failed: %s\n", strerror(errno));
       exit(1);
       }
     if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
       {
       printf("listen() failed: %s\n", strerror(errno));
       exit(1);
       }
-    }
+
+#if defined(TCP_FASTOPEN) && defined(__APPLE__)
+  if (  tfo
+     && setsockopt(listen_socket[v4n], IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on))
+     && debug)
+      printf("setsockopt TCP_FASTOPEN: %s\n", strerror(errno));
+#endif
   }
 
 
   }