* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for doing things with sockets. With the advent of IPv6 this has
got messier, so that it's worth pulling out the code into separate functions
-that other parts of Exim can call, expecially as there are now several
+that other parts of Exim can call, especially as there are now several
different places in the code where sockets are used. */
+/*************************************************
+*************************************************/
+
+#ifdef EXIM_TFO_PROBE
+void
+tfo_probe(void)
+{
+# ifdef TCP_FASTOPEN
+int sock, backlog = 5;
+
+if ( (sock = socket(SOCK_STREAM, AF_INET, 0)) < 0
+ && setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &backlog, sizeof(backlog))
+ )
+ tcp_fastopen_ok = TRUE;
+close(sock);
+# endif
+}
+#endif
+
+
/*************************************************
* Connect socket to remote host *
*************************************************/
address the remote address, in text form
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
Returns: 0 on success; -1 on failure, with errno set
*/
int
-ip_connect(int sock, int af, const uschar *address, int port, int timeout)
+ip_connect(int sock, int af, const uschar *address, int port, int timeout,
+ const blob * fastopen_blob)
{
struct sockaddr_in s_in4;
struct sockaddr *s_ptr;
/* If no connection timeout is set, just call connect() without setting a
timer, thereby allowing the inbuilt OS timeout to operate. */
+callout_address = string_sprintf("[%s]:%d", address, port);
sigalrm_seen = FALSE;
if (timeout > 0) alarm(timeout);
-rc = connect(sock, s_ptr, s_len);
+
+#if defined(TCP_FASTOPEN) && defined(MSG_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
+possibly use the data-on-syn, so support that too. */
+
+if (fastopen_blob && tcp_fastopen_ok)
+ {
+ 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 */
+ /* seen for with-data, proper TFO opt, with-cookie case */
+ {
+ DEBUG(D_transport|D_v)
+ debug_printf("non-TFO mode connection attempt to %s, %lu data\n",
+ address, (unsigned long)fastopen_blob->len);
+ tcp_out_fastopen = fastopen_blob->len > 0 ? 2 : 1;
+ }
+ else if (errno == EINPROGRESS) /* expected if we had no cookie for peer */
+ /* seen for no-data, proper TFO option, both cookie-request and with-cookie cases */
+ /* apparently no visibility of the diffference at this point */
+ /* seen for with-data, proper TFO opt, cookie-req */
+ /* with netwk delay, post-conn tcp_info sees unacked 1 for R, 2 for C; code in smtp_out.c */
+ /* ? older Experimental TFO option behaviour ? */
+ { /* queue unsent data */
+ 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 = 1; /* we tried; unknown if useful yet */
+ rc = 0;
+ }
+ else
+ rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
+ }
+ else if(errno == EOPNOTSUPP)
+ {
+ DEBUG(D_transport)
+ debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
+ goto legacy_connect;
+ }
+ }
+else
+#endif
+ {
+legacy_connect:
+ DEBUG(D_transport|D_v) if (fastopen_blob)
+ debug_printf("non-TFO mode connection attempt to %s, %lu data\n",
+ address, (unsigned long)fastopen_blob->len);
+ if ((rc = connect(sock, s_ptr, s_len)) >= 0)
+ if ( fastopen_blob && fastopen_blob->data && fastopen_blob->len
+ && send(sock, fastopen_blob->data, fastopen_blob->len, 0) < 0)
+ rc = -1;
+ }
+
save_errno = errno;
alarm(0);
/* Success */
if (rc >= 0)
- {
- callout_address = string_sprintf("[%s]:%d", address, port);
return 0;
- }
/* A failure whose error code is "Interrupted system call" is in fact
an externally applied timeout if the signal handler has been run. */
address the remote address, in text form
portlo,porthi the remote port range
timeout a timeout
- connhost if not NULL, host_item filled in with connection details
+ 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
Return:
socket fd, or -1 on failure (having allocated an error string)
*/
int
ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
- int timeout, host_item * connhost, uschar ** errstr)
+ int timeout, host_item * connhost, uschar ** errstr, const blob * fastopen_blob)
{
int namelen, port;
host_item shost;
hostname[namelen - 1] == ']')
{
uschar * host = string_copyn(hostname+1, namelen-2);
-debug_printf("%s: 1\n", __FUNCTION__);
if (string_is_ip_address(host, NULL) == 0)
{
*errstr = string_sprintf("malformed IP address \"%s\"", hostname);
/* Otherwise check for an unadorned IP address */
else if (string_is_ip_address(hostname, NULL) != 0)
- {
-debug_printf("%s: 2\n", __FUNCTION__);
shost.name = shost.address = string_copyn(hostname, namelen);
- }
/* Otherwise lookup IP address(es) from the name */
else
{
-debug_printf("%s: 3\n", __FUNCTION__);
shost.name = string_copyn(hostname, namelen);
if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE,
NULL, FALSE) != HOST_FOUND)
for (h = &shost; h; h = h->next)
{
-debug_printf("%s: 4 '%s'\n", __FUNCTION__, h->address);
fd = Ustrchr(h->address, ':') != 0
? fd6 < 0 ? (fd6 = ip_socket(type, af = AF_INET6)) : fd6
: fd4 < 0 ? (fd4 = ip_socket(type, af = AF_INET )) : fd4;
}
for(port = portlo; port <= porthi; port++)
- if (ip_connect(fd, af, h->address, port, timeout) == 0)
+ if (ip_connect(fd, af, h->address, port, timeout, fastopen_blob) == 0)
{
if (fd != fd6) close(fd6);
if (fd != fd4) close(fd4);
}
+/*XXX TFO? */
int
ip_tcpsocket(const uschar * hostport, uschar ** errstr, int tmo)
{
}
return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh,
- tmo, NULL, errstr);
+ tmo, NULL, errstr, NULL);
}
int
return -1;
}
+callout_address = string_copy(path);
server.sun_family = AF_UNIX;
Ustrncpy(server.sun_path, path, sizeof(server.sun_path)-1);
server.sun_path[sizeof(server.sun_path)-1] = '\0';
path, strerror(err));
return -1;
}
-callout_address = string_copy(path);
return sock;
}
{
int fodder = 1;
if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
- (uschar *)(&fodder), sizeof(fodder)) != 0)
+ US (&fodder), sizeof(fodder)) != 0)
log_write(0, LOG_MAIN, "setsockopt(SO_KEEPALIVE) on connection %s %s "
"failed: %s", torf? "to":"from", address, strerror(errno));
}
do
{
- struct timeval tv = { time_left, 0 };
+ struct timeval tv = { .tv_sec = time_left, .tv_usec = 0 };
FD_ZERO (&select_inset);
FD_SET (fd, &select_inset);