+# ifdef __FreeBSD__
+struct tcp_info tinfo;
+socklen_t len = sizeof(tinfo);
+
+/* 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
+ */
+ }
+
+switch (tcp_out_fastopen)
+ {
+ case TFO_ATTEMPTED_DATA: tcp_out_fastopen = TFO_USED_DATA; break;
+ default: break; /* compiler quietening */
+ }
+
+# else /* Linux & Apple */
+# if defined(TCP_INFO) && defined(EXIM_HAVE_TCPI_UNACKED)
+struct tcp_info tinfo;
+socklen_t len = sizeof(tinfo);
+
+switch (tcp_out_fastopen)
+ {
+ /* This is a somewhat dubious detection method; totally undocumented so likely
+ to fail in future kernels. There seems to be no documented way. What we really
+ want to know is if the server sent smtp-banner data before our ACK of his SYN,ACK
+ hit him. What this (possibly?) detects is whether we sent a TFO cookie with our
+ SYN, as distinct from a TFO request. This gets a false-positive when the server
+ key is rotated; we send the old one (which this test sees) but the server returns
+ the new one and does not send its SMTP banner before we ACK his SYN,ACK.
+ To force that rotation case:
+ '# echo -n "00000000-00000000-00000000-0000000" >/proc/sys/net/ipv4/tcp_fastopen_key'
+ The kernel seems to be counting unack'd packets. */
+
+ case TFO_ATTEMPTED_NODATA:
+ if ( getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+ && tinfo.tcpi_state == TCP_SYN_SENT
+ && tinfo.tcpi_unacked > 1
+ )
+ {
+ DEBUG(D_transport|D_v)
+ debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.tcpi_unacked);
+ tcp_out_fastopen = TFO_USED_NODATA;
+ }
+ break;
+
+ /* When called after waiting for received data we should be able
+ to tell if data we sent was accepted. */
+
+ case TFO_ATTEMPTED_DATA:
+ if ( getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+ && tinfo.tcpi_state == TCP_ESTABLISHED
+ )
+ if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
+ {
+ DEBUG(D_transport|D_v) debug_printf("TFO: data was acked\n");
+ tcp_out_fastopen = TFO_USED_DATA;
+ }
+ else
+ {
+ DEBUG(D_transport|D_v) debug_printf("TFO: had to retransmit\n");
+ tcp_out_fastopen = TFO_NOT_USED;
+ }
+ break;
+
+ default: break; /* compiler quietening */
+ }
+# endif
+# endif /* Linux & Apple */
+}
+#endif
+
+
+/* Create and bind a socket, given the connect-args.
+Update those with the state. Return the fd, or -1 with errno set.
+*/
+
+int
+smtp_boundsock(smtp_connect_args * sc)
+{
+transport_instance * tb = sc->tblock;
+smtp_transport_options_block * ob =
+ (smtp_transport_options_block *)tb->options_block;
+const uschar * dscp = ob->dscp;
+int sock, dscp_value, dscp_level, dscp_option;
+
+if ((sock = ip_socket(SOCK_STREAM, sc->host_af)) < 0)
+ return -1;
+
+/* Set TCP_NODELAY; Exim does its own buffering. */
+
+if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf_indent("failed to set NODELAY: %s ", strerror(errno));
+
+/* Set DSCP value, if we can. For now, if we fail to set the value, we don't
+bomb out, just log it and continue in default traffic class. */
+
+if (dscp && dscp_lookup(dscp, sc->host_af, &dscp_level, &dscp_option, &dscp_value))
+ {
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf_indent("DSCP \"%s\"=%x ", dscp, dscp_value);
+ if (setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)) < 0)
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf_indent("failed to set DSCP: %s ", strerror(errno));
+ /* If the kernel supports IPv4 and IPv6 on an IPv6 socket, we need to set the
+ option for both; ignore failures here */
+ if (sc->host_af == AF_INET6 &&
+ dscp_lookup(dscp, AF_INET, &dscp_level, &dscp_option, &dscp_value))
+ (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
+ }
+
+/* Bind to a specific interface if requested. Caller must ensure the interface
+is the same type (IPv4 or IPv6) as the outgoing address. */
+
+if (sc->interface)
+ {
+ union sockaddr_46 interface_sock;
+ EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
+ if ( ip_bind(sock, sc->host_af, sc->interface, 0) < 0
+ || getsockname(sock, (struct sockaddr *) &interface_sock, &size) < 0
+ )
+ {
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf_indent("unable to bind outgoing SMTP call to %s: %s", sc->interface,
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+ sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
+ }
+
+sc->sock = sock;
+return sock;
+}
+
+
+/* Arguments:
+ host host item containing name and address and port