From 91ccab1e40a10963764f449ba8309d47e90d6a8a Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Fri, 27 Apr 2018 13:20:31 +0200 Subject: [PATCH] resolved: TCP fast open connections Add suport for TCP fast open connection to reduce latency for successive DNS request over TCP --- src/resolve/resolved-dns-scope.c | 25 +++++++++---- src/resolve/resolved-dns-scope.h | 2 +- src/resolve/resolved-dns-stream.c | 51 +++++++++++++++++++++++--- src/resolve/resolved-dns-stream.h | 6 ++- src/resolve/resolved-dns-stub.c | 2 +- src/resolve/resolved-dns-transaction.c | 9 +++-- src/resolve/resolved-llmnr.c | 2 +- 7 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 763789c450f..b92bcd557ea 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -306,10 +306,11 @@ static int dns_scope_socket( int family, const union in_addr_union *address, DnsServer *server, - uint16_t port) { + uint16_t port, + union sockaddr_union *ret_socket_address) { _cleanup_close_ int fd = -1; - union sockaddr_union sa = {}; + union sockaddr_union sa; socklen_t salen; static const int one = 1; int r, ifindex; @@ -392,19 +393,27 @@ static int dns_scope_socket( } } - r = connect(fd, &sa.sa, salen); - if (r < 0 && errno != EINPROGRESS) - return -errno; + if (ret_socket_address) + *ret_socket_address = sa; + else { + r = connect(fd, &sa.sa, salen); + if (r < 0 && errno != EINPROGRESS) + return -errno; + } return TAKE_FD(fd); } int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) { - return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port); + return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port, NULL); } -int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) { - return dns_scope_socket(s, SOCK_STREAM, family, address, server, port); +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port, union sockaddr_union *ret_socket_address) { + /* If ret_socket_address is not NULL, the caller is responisble + * for calling connect() or sendmsg(). This is required by TCP + * Fast Open, to be able to send the initial SYN packet along + * with the first data packet. */ + return dns_scope_socket(s, SOCK_STREAM, family, address, server, port, ret_socket_address); } DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 15c58251d0d..0042193e204 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -75,7 +75,7 @@ void dns_scope_packet_received(DnsScope *s, usec_t rtt); void dns_scope_packet_lost(DnsScope *s, usec_t usec); int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p); -int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port); +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port, union sockaddr_union *ret_socket_address); int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port); DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index f537fe6ab48..5d2cc1ad8b4 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -182,6 +182,39 @@ static int dns_stream_identify(DnsStream *s) { return 0; } +static ssize_t dns_stream_writev(DnsStream *s, const struct iovec *iov, size_t iovcnt) { + ssize_t r; + + assert(s); + assert(iov); + + if (s->tfo_salen > 0) { + struct msghdr hdr = { + .msg_iov = (struct iovec*) iov, + .msg_iovlen = iovcnt, + .msg_name = &s->tfo_address.sa, + .msg_namelen = s->tfo_salen + }; + + r = sendmsg(s->fd, &hdr, MSG_FASTOPEN); + if (r < 0) { + if (errno == EOPNOTSUPP) { + s->tfo_salen = 0; + r = connect(s->fd, &s->tfo_address.sa, s->tfo_salen); + if (r < 0) + return -errno; + + r = -EAGAIN; + } else if (errno == EINPROGRESS) + r = -EAGAIN; + } else + s->tfo_salen = 0; /* connection is made */ + } else + r = writev(s->fd, iov, iovcnt); + + return r; +} + static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { DnsStream *s = userdata; @@ -196,9 +229,12 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use assert(s); - r = dns_stream_identify(s); - if (r < 0) - return dns_stream_complete(s, -r); + /* only identify after connecting */ + if (s->tfo_salen == 0) { + r = dns_stream_identify(s); + if (r < 0) + return dns_stream_complete(s, -r); + } if ((revents & EPOLLOUT) && s->write_packet && @@ -214,7 +250,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use IOVEC_INCREMENT(iov, 2, s->n_written); - ss = writev(fd, iov, 2); + ss = dns_stream_writev(s, iov, 2); if (ss < 0) { if (!IN_SET(errno, EINTR, EAGAIN)) return dns_stream_complete(s, errno); @@ -366,7 +402,7 @@ DnsStream *dns_stream_ref(DnsStream *s) { return s; } -int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { +int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd, const union sockaddr_union *tfo_address) { _cleanup_(dns_stream_unrefp) DnsStream *s = NULL; int r; @@ -408,6 +444,11 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { LIST_PREPEND(streams, m->dns_streams, s); s->manager = m; s->fd = fd; + if (tfo_address) { + s->tfo_address = *tfo_address; + s->tfo_salen = tfo_address->sa.sa_family == AF_INET6 ? sizeof(tfo_address->in6) : sizeof(tfo_address->in); + } + m->n_dns_streams++; *ret = TAKE_PTR(s); diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h index f392c13b0b5..5ba2bd18144 100644 --- a/src/resolve/resolved-dns-stream.h +++ b/src/resolve/resolved-dns-stream.h @@ -37,6 +37,10 @@ struct DnsStream { uint32_t ttl; bool identified; + /* only when using TCP fast open */ + union sockaddr_union tfo_address; + socklen_t tfo_salen; + sd_event_source *io_event_source; sd_event_source *timeout_event_source; @@ -55,7 +59,7 @@ struct DnsStream { LIST_FIELDS(DnsStream, streams); }; -int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); +int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd, const union sockaddr_union *tfo_address); DnsStream *dns_stream_unref(DnsStream *s); DnsStream *dns_stream_ref(DnsStream *s); diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 6b47a48df67..298d8132be0 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -469,7 +469,7 @@ static int on_dns_stub_stream(sd_event_source *s, int fd, uint32_t revents, void return -errno; } - r = dns_stream_new(m, &stream, DNS_PROTOCOL_DNS, cfd); + r = dns_stream_new(m, &stream, DNS_PROTOCOL_DNS, cfd, NULL); if (r < 0) { safe_close(cfd); return r; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 5d3f6bae278..5e4125cac50 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -557,6 +557,7 @@ static int dns_stream_on_packet(DnsStream *s) { static int dns_transaction_emit_tcp(DnsTransaction *t) { _cleanup_close_ int fd = -1; _cleanup_(dns_stream_unrefp) DnsStream *s = NULL; + union sockaddr_union sa; int r; assert(t); @@ -580,14 +581,14 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { if (t->server->stream) s = dns_stream_ref(t->server->stream); else - fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53); + fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53, &sa); break; case DNS_PROTOCOL_LLMNR: /* When we already received a reply to this (but it was truncated), send to its sender address */ if (t->received) - fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port); + fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port, &sa); else { union in_addr_union address; int family = AF_UNSPEC; @@ -604,7 +605,7 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { if (family != t->scope->family) return -ESRCH; - fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); + fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT, &sa); } break; @@ -617,7 +618,7 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { if (fd < 0) return fd; - r = dns_stream_new(t->scope->manager, &s, t->scope->protocol, fd); + r = dns_stream_new(t->scope->manager, &s, t->scope->protocol, fd, &sa); if (r < 0) return r; diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c index ca49df80907..9eef59be0df 100644 --- a/src/resolve/resolved-llmnr.c +++ b/src/resolve/resolved-llmnr.c @@ -345,7 +345,7 @@ static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *u return -errno; } - r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd); + r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd, NULL); if (r < 0) { safe_close(cfd); return r;