TFO: early-data for client outbound via socks5 proxy
authorJeremy Harris <jgh146exb@wizmail.org>
Mon, 18 Sep 2017 10:50:07 +0000 (11:50 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 19 Sep 2017 10:22:55 +0000 (11:22 +0100)
20 files changed:
src/exim_monitor/em_hdr.h
src/src/exim.h
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/hash.h
src/src/ip.c
src/src/routers/iplookup.c
src/src/smtp_out.c
src/src/transports/appendfile.c
src/src/transports/smtp_socks.c
src/src/verify.c
test/confs/4020
test/confs/4027 [new symlink]
test/log/4027 [new file with mode: 0644]
test/scripts/4020-socks/4020
test/scripts/4027-TFO-socks/4027 [new file with mode: 0644]
test/scripts/4027-TFO-socks/REQUIRES [new file with mode: 0644]
test/src/server.c
test/stdout/4027 [new file with mode: 0644]

index a7e874a..6729436 100644 (file)
@@ -100,6 +100,7 @@ typedef void hctx;
 
 #include "local_scan.h"
 #include "structs.h"
+#include "blob.h"
 #include "globals.h"
 #include "dbstuff.h"
 #include "functions.h"
index 4ca2813..f8dd9a9 100644 (file)
@@ -492,6 +492,7 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 #include "macros.h"
 #include "dbstuff.h"
 #include "structs.h"
+#include "blob.h"
 #include "globals.h"
 #include "hash.h"
 #include "functions.h"
index dd6c981..a96ffb6 100644 (file)
@@ -235,7 +235,7 @@ extern uschar *imap_utf7_encode(uschar *, const uschar *,
 extern void    invert_address(uschar *, uschar *);
 extern int     ip_addr(void *, int, const uschar *, int);
 extern int     ip_bind(int, int, uschar *, int);
-extern int     ip_connect(int, int, const uschar *, int, int, BOOL);
+extern int     ip_connect(int, int, const uschar *, int, int, const blob *);
 extern int     ip_connectedsocket(int, const uschar *, int, int,
                  int, host_item *, uschar **);
 extern int     ip_get_address_family(int);
@@ -397,7 +397,7 @@ extern uschar *smtp_cmd_hist(void);
 extern int     smtp_connect(host_item *, int, uschar *, int,
                 transport_instance *);
 extern int     smtp_sock_connect(host_item *, int, int, uschar *,
-                transport_instance * tb, int);
+                transport_instance * tb, int, const blob *);
 extern int     smtp_feof(void);
 extern int     smtp_ferror(void);
 extern uschar *smtp_get_connection_info(void);
index 97debee..57041fc 100644 (file)
@@ -1417,6 +1417,7 @@ BOOL    system_filter_uid_set  = FALSE;
 BOOL    system_filtering       = FALSE;
 
 BOOL    tcp_fastopen_ok        = FALSE;
+blob   tcp_fastopen_nodata    = { .data = NULL, .len = 0 };
 BOOL    tcp_in_fastopen        = FALSE;
 BOOL    tcp_in_fastopen_logged = FALSE;
 BOOL    tcp_nodelay            = TRUE;
index 7578a1d..2957587 100644 (file)
@@ -922,6 +922,7 @@ extern BOOL    system_filter_uid_set;  /* TRUE if uid set */
 extern BOOL    system_filtering;       /* TRUE when running system filter */
 
 extern BOOL    tcp_fastopen_ok;               /* appears to be supported by kernel */
+extern blob    tcp_fastopen_nodata;    /* for zero-data TFO connect requests */
 extern BOOL    tcp_in_fastopen;               /* conn used fastopen */
 extern BOOL    tcp_in_fastopen_logged; /* one-time logging */
 extern BOOL    tcp_nodelay;            /* Controls TCP_NODELAY on daemon */
index b745e62..337dc99 100644 (file)
@@ -12,7 +12,6 @@
 #define HASH_H
 
 #include "sha_ver.h"
-#include "blob.h"
 
 #ifdef SHA_OPENSSL
 # include <openssl/sha.h>
index 08d32f2..8727451 100644 (file)
@@ -175,14 +175,15 @@ Arguments:
   address     the remote address, in text form
   port        the remote port
   timeout     a timeout (zero for indefinite timeout)
-  fastopen    TRUE iff TCP_FASTOPEN can be used
+  fastopen    non-null iff TCP_FASTOPEN can be used; may indicate early-data to
+               be sent in SYN segment
 
 Returns:      0 on success; -1 on failure, with errno set
 */
 
 int
 ip_connect(int sock, int af, const uschar *address, int port, int timeout,
-  BOOL fastopen)
+  const blob * fastopen)
 {
 struct sockaddr_in s_in4;
 struct sockaddr *s_ptr;
@@ -235,19 +236,30 @@ connect in FASTOPEN mode but with zero data.
 
 if (fastopen)
   {
-  if ((rc = sendto(sock, NULL, 0, MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) < 0)
-    if (errno == EINPROGRESS)          /* the expected case */
-      rc = 0;
+  if ((rc = sendto(sock, fastopen->data, fastopen->len,
+                   MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) < 0)
+    if (errno == EINPROGRESS)          /* expected for nonready peer */
+      {                                        /* queue the data */
+      if (  (rc = send(sock, fastopen->data, fastopen->len, 0)) < 0
+        && errno == EINPROGRESS)       /* expected for nonready peer */
+       rc = 0;
+      }
     else if(errno == EOPNOTSUPP)
       {
       DEBUG(D_transport)
        debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
-      rc = connect(sock, s_ptr, s_len);
+      goto legacy_connect;
       }
   }
 else
 #endif
-  rc = connect(sock, s_ptr, s_len);
+  {
+legacy_connect:
+  if ((rc = connect(sock, s_ptr, s_len)) >= 0)
+    if (  fastopen && fastopen->data && fastopen->len
+       && send(sock, fastopen->data, fastopen->len, 0) < 0)
+       rc = -1;
+  }
 
 save_errno = errno;
 alarm(0);
@@ -292,6 +304,7 @@ Arguments:
   timeout       a timeout
   connhost     if not NULL, host_item filled in with connection details
   errstr        pointer for allocated string on error
+XXX could add early-data support
 
 Return:
   socket fd, or -1 on failure (having allocated an error string)
@@ -304,7 +317,8 @@ int namelen, port;
 host_item shost;
 host_item *h;
 int af = 0, fd, fd4 = -1, fd6 = -1;
-BOOL fastopen = tcp_fastopen_ok && type == SOCK_STREAM;
+blob * fastopen = tcp_fastopen_ok && type == SOCK_STREAM
+  ? &tcp_fastopen_nodata : NULL;
 
 shost.next = NULL;
 shost.address = NULL;
index 1af2a77..3592809 100644 (file)
@@ -254,9 +254,10 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
     /* Connect to the remote host, under a timeout. In fact, timeouts can occur
     here only for TCP calls; for a UDP socket, "connect" always works (the
     router will timeout later on the read call). */
+/*XXX could take advantage of TFO */
 
     if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout,
-                       ob->protocol != ip_udp) < 0)
+               ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
       {
       close(query_socket);
       DEBUG(D_route)
index ee39cb2..9221aa8 100644 (file)
@@ -164,9 +164,15 @@ if (getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
 #endif
 
 
+/* Arguments as for smtp_connect(), plus
+  early_data   if non-NULL, data to be sent - preferably in the TCP SYN segment
+
+Returns:      connected socket number, or -1 with errno set
+*/
+
 int
 smtp_sock_connect(host_item * host, int host_af, int port, uschar * interface,
-  transport_instance * tb, int timeout)
+  transport_instance * tb, int timeout, const blob * early_data)
 {
 smtp_transport_options_block * ob =
   (smtp_transport_options_block *)tb->options_block;
@@ -176,7 +182,6 @@ int dscp_level;
 int dscp_option;
 int sock;
 int save_errno = 0;
-BOOL fastopen = FALSE;
 
 #ifndef DISABLE_EVENT
 deliver_host_address = host->address;
@@ -209,10 +214,6 @@ if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value))
     (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
   }
 
-#ifdef TCP_FASTOPEN
-if (verify_check_given_host (&ob->hosts_try_fastopen, host) == OK) fastopen = TRUE;
-#endif
-
 /* Bind to a specific interface if requested. Caller must ensure the interface
 is the same type (IPv4 or IPv6) as the outgoing address. */
 
@@ -225,10 +226,24 @@ if (interface && ip_bind(sock, host_af, interface, 0) < 0)
   }
 
 /* Connect to the remote host, and add keepalive to the socket before returning
-it, if requested. */
+it, if requested.  If the build supports TFO, request it - and if the caller
+requested some early-data then include that in the TFO request. */
 
-else if (ip_connect(sock, host_af, host->address, port, timeout, fastopen) < 0)
-  save_errno = errno;
+else
+  {
+  const blob * fastopen = NULL;
+
+#ifdef TCP_FASTOPEN
+  if (verify_check_given_host(&ob->hosts_try_fastopen, host) == OK)
+    fastopen = early_data ? early_data : &tcp_fastopen_nodata;
+#endif
+
+  if (ip_connect(sock, host_af, host->address, port, timeout, fastopen) < 0)
+    save_errno = errno;
+  else if (early_data && !fastopen && early_data->data && early_data->len)
+    if (send(sock, early_data->data, early_data->len, 0) < 0)
+      save_errno = errno;
+  }
 
 /* Either bind() or connect() failed */
 
@@ -336,7 +351,7 @@ if (ob->socks_proxy)
   return socks_sock_connect(host, host_af, port, interface, tb, timeout);
 #endif
 
-return smtp_sock_connect(host, host_af, port, interface, tb, timeout);
+return smtp_sock_connect(host, host_af, port, interface, tb, timeout, NULL);
 }
 
 
index 2e93261..3b463c6 100644 (file)
@@ -655,7 +655,7 @@ for (h = &host; h; h = h->next)
 
   /* Connect never fails for a UDP socket, so don't set a timeout. */
 
-  (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0, FALSE);
+  (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0, NULL);
   rc = send(sock, buffer, Ustrlen(buffer) + 1, 0);
   (void)close(sock);
 
index 92d1365..1368849 100644 (file)
@@ -233,6 +233,7 @@ socks_opts proxies[32];                     /* max #proxies handled */
 unsigned nproxies;
 socks_opts * sob;
 unsigned size;
+blob early_data;
 
 if (!timeout) timeout = 24*60*60;      /* use 1 day for "indefinite" */
 tmo = time(NULL) + timeout;
@@ -268,6 +269,14 @@ for (nproxies = 0;
     socks_option(sob, option);
   }
 
+/* Set up the socks protocol method-selection message,
+for sending on connection */
+
+state = US"method select";
+buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
+early_data.data = buf;
+early_data.len = 3;
+
 /* Try proxies until a connection succeeds */
 
 for(;;)
@@ -289,7 +298,7 @@ for(;;)
   proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
 
   if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port,
-             interface, tb, sob->timeout)) >= 0)
+             interface, tb, sob->timeout, &early_data)) >= 0)
     {
     proxy_local_address = string_copy(proxy.address);
     proxy_local_port = sob->port;
@@ -301,13 +310,8 @@ for(;;)
   }
 
 /* Do the socks protocol stuff */
-/* Send method-selection */
 
-state = US"method select";
 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SOCKS>> 05 01 %02x\n", sob->auth_type);
-buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
-if (send(fd, buf, 3, 0) < 0)
-  goto snd_err;
 
 /* expect method response */
 
index a9ec730..ac5eb66 100644 (file)
@@ -2682,8 +2682,11 @@ if (ip_bind(sock, host_af, interface_address, 0) < 0)
   goto END_OFF;
   }
 
+/*XXX could take advantage of TFO early-data.  Hmm, what are the
+error returns; can we differentiate connect from data fails?
+Do we need to? */
 if (ip_connect(sock, host_af, sender_host_address, port,
-               rfc1413_query_timeout, TRUE) < 0)
+               rfc1413_query_timeout, &tcp_fastopen_nodata) < 0)
   {
   if (errno == ETIMEDOUT && LOGGING(ident_timeout))
     log_write(0, LOG_MAIN, "ident connection to %s timed out",
@@ -3154,18 +3157,16 @@ verify_check_this_host(const uschar **listptr, unsigned int *cache_bits,
 int rc;
 unsigned int *local_cache_bits = cache_bits;
 const uschar *save_host_address = deliver_host_address;
-check_host_block cb;
-cb.host_name = host_name;
-cb.host_address = host_address;
+check_host_block cb = { .host_name = host_name, .host_address = host_address };
 
-if (valueptr != NULL) *valueptr = NULL;
+if (valueptr) *valueptr = NULL;
 
 /* If the host address starts off ::ffff: it is an IPv6 address in
 IPv4-compatible mode. Find the IPv4 part for checking against IPv4
 addresses. */
 
-cb.host_ipv4 = (Ustrncmp(host_address, "::ffff:", 7) == 0)?
-  host_address + 7 : host_address;
+cb.host_ipv4 = Ustrncmp(host_address, "::ffff:", 7) == 0
+  host_address + 7 : host_address;
 
 /* During the running of the check, put the IP address into $host_address. In
 the case of calls from the smtp transport, it will already be there. However,
index 8dfd468..05c95a5 100644 (file)
@@ -32,11 +32,12 @@ my_main_router:
 begin transports
 
 my_smtp:
-  driver = smtp
-  interface = HOSTIPV4
-  port = PORT_S
-  hide socks_proxy = 127.0.0.1 port=PORT_D OPT
-  debug_print = transport_name <$transport_name>
+  driver =             smtp
+  interface =          HOSTIPV4
+  port =               PORT_S
+  hide socks_proxy =   127.0.0.1 port=PORT_D OPT
+  hosts_try_fastopen = ${if eq {$local_part}{user_tfo} {*}}
+  debug_print =                transport_name <$transport_name>
 
 
 # End
diff --git a/test/confs/4027 b/test/confs/4027
new file mode 120000 (symlink)
index 0000000..4af051c
--- /dev/null
@@ -0,0 +1 @@
+4020
\ No newline at end of file
diff --git a/test/log/4027 b/test/log/4027
new file mode 100644 (file)
index 0000000..ed6b926
--- /dev/null
@@ -0,0 +1,6 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => user_tfo@test.ex R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1]:1224 PRX=[127.0.0.1]:1225 C="250 accepted OK"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 => user_tfo@test.ex R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1]:1224 PRX=[127.0.0.1]:1225 C="250 accepted OK"
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
index 44c885b..4a0ac08 100644 (file)
@@ -81,5 +81,4 @@ quit
 ****
 #
 #
-#
-
+# Ends
diff --git a/test/scripts/4027-TFO-socks/4027 b/test/scripts/4027-TFO-socks/4027
new file mode 100644 (file)
index 0000000..533021c
--- /dev/null
@@ -0,0 +1,83 @@
+# socks5 proxy on smtp transport, TCP Fast Open
+#
+munge loopback
+#
+#
+# TFO client, not server
+server PORT_D
+<<\x05\x01\x00
+>>\x05\x00
+<<\x05\x01\x00\x01\x7f\x00\x00\x01\x04\xc8
+>>\x05\x00\x00\x01\x7f\x00\x00\x01\xbe\xef
+220 Connected OK
+EHLO
+250-server id
+250
+MAIL FROM
+250
+RCPT TO
+250
+DATA
+354 do me
+.
+250 accepted OK
+QUIT
+250 bye
+****
+#
+#
+exim -odi -bs -DOPT=
+ehlo test.ex
+mail from:<>
+rcpt to:<user_tfo@test.ex>
+data
+Date: Fri, 17 Dec 2004 14:35:01 +0100
+Subject: message should be sent
+
+connection trying TFO
+via null-auth proxy
+.
+quit
+****
+#
+#
+#
+# TFO client and server
+server -tfo PORT_D
+<<\x05\x01\x00
+>>\x05\x00
+<<\x05\x01\x00\x01\x7f\x00\x00\x01\x04\xc8
+>>\x05\x00\x00\x01\x7f\x00\x00\x01\xbe\xef
+220 Connected OK
+EHLO
+250-server id
+250
+MAIL FROM
+250
+RCPT TO
+250
+DATA
+354 do me mate
+.
+250 accepted OK
+QUIT
+250 bye
+****
+#
+#
+exim -odi -bs -DOPT=
+ehlo test.ex
+mail from:<>
+rcpt to:<user_tfo@test.ex>
+data
+Date: Fri, 17 Dec 2004 14:35:01 +0100
+Subject: message should be sent
+
+connection using TFO
+via null-auth proxy
+.
+quit
+****
+#
+#
+# Ends
diff --git a/test/scripts/4027-TFO-socks/REQUIRES b/test/scripts/4027-TFO-socks/REQUIRES
new file mode 100644 (file)
index 0000000..f7e42e6
--- /dev/null
@@ -0,0 +1,2 @@
+support SOCKS
+support TCP_Fast_Open
index ce55c5c..4656b02 100644 (file)
@@ -26,6 +26,7 @@ on all interfaces, unless the option -noipv6 is given. */
 #include <netinet/in_systm.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
+#include <netinet/tcp.h>
 
 #ifdef HAVE_NETINET_IP_VAR_H
 # include <netinet/ip_var.h>
@@ -169,7 +170,7 @@ int connection_count = 1;
 int count;
 int on = 1;
 int timeout = 5;
-int initial_pause = 0;
+int initial_pause = 0, tfo = 0;
 int use_ipv4 = 1;
 int use_ipv6 = 1;
 int debug = 0;
@@ -214,6 +215,7 @@ if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
        "\n\t-noipv6  disable ipv6"
        "\n\t-oP file write PID to file"
        "\n\t-t n     n seconds timeout"
+       "\n\t-tfo     enable TCP Fast Open"
   );
   exit(0);
   }
@@ -221,6 +223,7 @@ if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
 while (na < argc && argv[na][0] == '-')
   {
   if (strcmp(argv[na], "-d") == 0) debug = 1;
+  else if (strcmp(argv[na], "-tfo") == 0) tfo = 1;
   else if (strcmp(argv[na], "-t") == 0)
     {
     if (tmo_noerror = ((timeout = atoi(argv[++na])) < 0)) timeout = -timeout;
@@ -296,6 +299,14 @@ else
       exit(1);
       }
 
+    if (tfo)
+      {
+      int backlog = 5;
+      if (setsockopt(listen_socket[v6n], IPPROTO_TCP, TCP_FASTOPEN,
+                    &backlog, sizeof(backlog)))
+       if (debug) printf("setsockopt TCP_FASTOPEN: %s\n", strerror(errno));
+      }
+
     /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
     available. */
 
@@ -319,6 +330,14 @@ else
       printf("IPv4 socket creation failed: %s\n", strerror(errno));
       exit(1);
       }
+    if (tfo)
+      {
+      int backlog = 5;
+      if (setsockopt(listen_socket[v4n], IPPROTO_TCP, TCP_FASTOPEN,
+                    &backlog, sizeof(backlog)))
+       if (debug) printf("setsockopt TCP_FASTOPEN: %s\n", strerror(errno));
+      }
+
     }
   }
 
diff --git a/test/stdout/4027 b/test/stdout/4027
new file mode 100644 (file)
index 0000000..74837c4
--- /dev/null
@@ -0,0 +1,66 @@
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250-myhost.test.ex Hello CALLER at test.ex\r
+250-SIZE 52428800\r
+250-8BITMIME\r
+250-PIPELINING\r
+250 HELP\r
+250 OK\r
+250 Accepted\r
+354 Enter message, ending with "." on a line by itself\r
+250 OK id=10HmaX-0005vi-00\r
+221 myhost.test.ex closing connection\r
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250-myhost.test.ex Hello CALLER at test.ex\r
+250-SIZE 52428800\r
+250-8BITMIME\r
+250-PIPELINING\r
+250 HELP\r
+250 OK\r
+250 Accepted\r
+354 Enter message, ending with "." on a line by itself\r
+250 OK id=10HmaY-0005vi-00\r
+221 myhost.test.ex closing connection\r
+
+******** SERVER ********
+Listening on port 1225 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+<<\x05\x01\x00
+>>\x05\x00
+<<\x05\x01\x00\x01\x7f\x00\x00\x01\x04\xc8
+>>\x05\x00\x00\x01\x7f\x00\x00\x01\xbe\xef
+220 Connected OK
+EHLO
+250-server id
+250
+MAIL FROM
+250
+RCPT TO
+250
+DATA
+354 do me
+R
+250 accepted OK
+QUIT
+250 bye
+End of script
+Listening on port 1225 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+<<\x05\x01\x00
+>>\x05\x00
+<<\x05\x01\x00\x01\x7f\x00\x00\x01\x04\xc8
+>>\x05\x00\x00\x01\x7f\x00\x00\x01\xbe\xef
+220 Connected OK
+EHLO
+250-server id
+250
+MAIL FROM
+250
+RCPT TO
+250
+DATA
+354 do me mate
+R
+250 accepted OK
+QUIT
+250 bye
+End of script