From 0ab63f3dea522591218c0d65c78a686b7fa64db6 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Mon, 18 Sep 2017 11:50:07 +0100 Subject: [PATCH] TFO: early-data for client outbound via socks5 proxy --- src/exim_monitor/em_hdr.h | 1 + src/src/exim.h | 1 + src/src/functions.h | 4 +- src/src/globals.c | 1 + src/src/globals.h | 1 + src/src/hash.h | 1 - src/src/ip.c | 30 +++++++--- src/src/routers/iplookup.c | 3 +- src/src/smtp_out.c | 35 ++++++++---- src/src/transports/appendfile.c | 2 +- src/src/transports/smtp_socks.c | 16 ++++-- src/src/verify.c | 15 ++--- test/confs/4020 | 11 ++-- test/confs/4027 | 1 + test/log/4027 | 6 ++ test/scripts/4020-socks/4020 | 3 +- test/scripts/4027-TFO-socks/4027 | 83 ++++++++++++++++++++++++++++ test/scripts/4027-TFO-socks/REQUIRES | 2 + test/src/server.c | 21 ++++++- test/stdout/4027 | 66 ++++++++++++++++++++++ 20 files changed, 259 insertions(+), 44 deletions(-) create mode 120000 test/confs/4027 create mode 100644 test/log/4027 create mode 100644 test/scripts/4027-TFO-socks/4027 create mode 100644 test/scripts/4027-TFO-socks/REQUIRES create mode 100644 test/stdout/4027 diff --git a/src/exim_monitor/em_hdr.h b/src/exim_monitor/em_hdr.h index a7e874a87..67294368a 100644 --- a/src/exim_monitor/em_hdr.h +++ b/src/exim_monitor/em_hdr.h @@ -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" diff --git a/src/src/exim.h b/src/src/exim.h index 4ca28130a..f8dd9a9ba 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -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" diff --git a/src/src/functions.h b/src/src/functions.h index dd6c9810b..a96ffb69c 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -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); diff --git a/src/src/globals.c b/src/src/globals.c index 97debee58..57041fc4e 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -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; diff --git a/src/src/globals.h b/src/src/globals.h index 7578a1d82..2957587b0 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -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 */ diff --git a/src/src/hash.h b/src/src/hash.h index b745e6218..337dc9910 100644 --- a/src/src/hash.h +++ b/src/src/hash.h @@ -12,7 +12,6 @@ #define HASH_H #include "sha_ver.h" -#include "blob.h" #ifdef SHA_OPENSSL # include diff --git a/src/src/ip.c b/src/src/ip.c index 08d32f21b..872745144 100644 --- a/src/src/ip.c +++ b/src/src/ip.c @@ -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; diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c index 1af2a77b9..3592809ea 100644 --- a/src/src/routers/iplookup.c +++ b/src/src/routers/iplookup.c @@ -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) diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index ee39cb2fb..9221aa868 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -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); } diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c index 2e9326122..3b463c644 100644 --- a/src/src/transports/appendfile.c +++ b/src/src/transports/appendfile.c @@ -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); diff --git a/src/src/transports/smtp_socks.c b/src/src/transports/smtp_socks.c index 92d13659d..1368849d6 100644 --- a/src/src/transports/smtp_socks.c +++ b/src/src/transports/smtp_socks.c @@ -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 */ diff --git a/src/src/verify.c b/src/src/verify.c index a9ec730d1..ac5eb667b 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -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, diff --git a/test/confs/4020 b/test/confs/4020 index 8dfd46888..05c95a571 100644 --- a/test/confs/4020 +++ b/test/confs/4020 @@ -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 index 000000000..4af051ca9 --- /dev/null +++ b/test/confs/4027 @@ -0,0 +1 @@ +4020 \ No newline at end of file diff --git a/test/log/4027 b/test/log/4027 new file mode 100644 index 000000000..ed6b92611 --- /dev/null +++ b/test/log/4027 @@ -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 diff --git a/test/scripts/4020-socks/4020 b/test/scripts/4020-socks/4020 index 44c885b8d..4a0ac0893 100644 --- a/test/scripts/4020-socks/4020 +++ b/test/scripts/4020-socks/4020 @@ -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 index 000000000..533021cbf --- /dev/null +++ b/test/scripts/4027-TFO-socks/4027 @@ -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: +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: +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 index 000000000..f7e42e611 --- /dev/null +++ b/test/scripts/4027-TFO-socks/REQUIRES @@ -0,0 +1,2 @@ +support SOCKS +support TCP_Fast_Open diff --git a/test/src/server.c b/test/src/server.c index ce55c5c37..4656b02af 100644 --- a/test/src/server.c +++ b/test/src/server.c @@ -26,6 +26,7 @@ on all interfaces, unless the option -noipv6 is given. */ #include #include #include +#include #ifdef HAVE_NETINET_IP_VAR_H # include @@ -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 index 000000000..74837c4ac --- /dev/null +++ b/test/stdout/4027 @@ -0,0 +1,66 @@ +220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 +250-myhost.test.ex Hello CALLER at test.ex +250-SIZE 52428800 +250-8BITMIME +250-PIPELINING +250 HELP +250 OK +250 Accepted +354 Enter message, ending with "." on a line by itself +250 OK id=10HmaX-0005vi-00 +221 myhost.test.ex closing connection +220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 +250-myhost.test.ex Hello CALLER at test.ex +250-SIZE 52428800 +250-8BITMIME +250-PIPELINING +250 HELP +250 OK +250 Accepted +354 Enter message, ending with "." on a line by itself +250 OK id=10HmaY-0005vi-00 +221 myhost.test.ex closing connection + +******** 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 -- 2.30.2