Re: [libav-devel] [PATCH 1/2] network: Add RFC 8305 style "Happy Eyeballs"/"Fast Fallback" helper function

2018-08-31 Thread Martin Storsjö

On Wed, 22 Aug 2018, Luca Barbato wrote:


On 21/08/2018 09:29, Martin Storsjö wrote:

For cases with dual stack (IPv4 + IPv6) connectivity, but where one
stack potentially is less reliable, strive to trying to connect over
both protocols in parallel, using whichever address connected first.

In cases with a hostname resolving to multiple IPv4 and IPv6
addresses, the current connection mechanism would try all addresses
in the order returned by getaddrinfo (with all IPv6 addresses ordered
before the IPv4 addresses normally). If connection attempts to the
IPv6 addresses return quickly with an error, this was no problem, but
if they were unsuccessful leading up to timeouts, the connection process
would have to wait for timeouts on all IPv6 target addresses before
attempting any IPv4 address.

Similar to what RFC 8305 suggests, reorder the list of addresses to
try connecting to, interleaving address families. After starting one
connection attempt, start another one in parallel after a small delay
(200 ms as suggested by the RFC).

For cases with unreliable IPv6 but reliable IPv4, this should make
connection attempts work as reliably as with plain IPv4, with only an
extra 200 ms of connection delay.


The set looks fine to me.


Pushed.

// Martin
___
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel

Re: [libav-devel] [PATCH 1/2] network: Add RFC 8305 style "Happy Eyeballs"/"Fast Fallback" helper function

2018-08-22 Thread Luca Barbato
On 21/08/2018 09:29, Martin Storsjö wrote:
> For cases with dual stack (IPv4 + IPv6) connectivity, but where one
> stack potentially is less reliable, strive to trying to connect over
> both protocols in parallel, using whichever address connected first.
> 
> In cases with a hostname resolving to multiple IPv4 and IPv6
> addresses, the current connection mechanism would try all addresses
> in the order returned by getaddrinfo (with all IPv6 addresses ordered
> before the IPv4 addresses normally). If connection attempts to the
> IPv6 addresses return quickly with an error, this was no problem, but
> if they were unsuccessful leading up to timeouts, the connection process
> would have to wait for timeouts on all IPv6 target addresses before
> attempting any IPv4 address.
> 
> Similar to what RFC 8305 suggests, reorder the list of addresses to
> try connecting to, interleaving address families. After starting one
> connection attempt, start another one in parallel after a small delay
> (200 ms as suggested by the RFC).
> 
> For cases with unreliable IPv6 but reliable IPv4, this should make
> connection attempts work as reliably as with plain IPv4, with only an
> extra 200 ms of connection delay.

The set looks fine to me.

lu

___
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel

[libav-devel] [PATCH 1/2] network: Add RFC 8305 style "Happy Eyeballs"/"Fast Fallback" helper function

2018-08-21 Thread Martin Storsjö
For cases with dual stack (IPv4 + IPv6) connectivity, but where one
stack potentially is less reliable, strive to trying to connect over
both protocols in parallel, using whichever address connected first.

In cases with a hostname resolving to multiple IPv4 and IPv6
addresses, the current connection mechanism would try all addresses
in the order returned by getaddrinfo (with all IPv6 addresses ordered
before the IPv4 addresses normally). If connection attempts to the
IPv6 addresses return quickly with an error, this was no problem, but
if they were unsuccessful leading up to timeouts, the connection process
would have to wait for timeouts on all IPv6 target addresses before
attempting any IPv4 address.

Similar to what RFC 8305 suggests, reorder the list of addresses to
try connecting to, interleaving address families. After starting one
connection attempt, start another one in parallel after a small delay
(200 ms as suggested by the RFC).

For cases with unreliable IPv6 but reliable IPv4, this should make
connection attempts work as reliably as with plain IPv4, with only an
extra 200 ms of connection delay.
---
 libavformat/network.c | 226 ++
 libavformat/network.h |  28 +++
 2 files changed, 254 insertions(+)

diff --git a/libavformat/network.c b/libavformat/network.c
index 24fcf20539..2d281539c6 100644
--- a/libavformat/network.c
+++ b/libavformat/network.c
@@ -23,7 +23,9 @@
 #include "tls.h"
 #include "url.h"
 #include "libavcodec/internal.h"
+#include "libavutil/avassert.h"
 #include "libavutil/mem.h"
+#include "libavutil/time.h"
 
 void ff_tls_init(void)
 {
@@ -240,6 +242,230 @@ int ff_listen_connect(int fd, const struct sockaddr *addr,
 return ret;
 }
 
+static void interleave_addrinfo(struct addrinfo *base)
+{
+struct addrinfo **next = &base->ai_next;
+while (*next) {
+struct addrinfo *cur = *next;
+// Iterate forward until we find an entry of a different family.
+if (cur->ai_family == base->ai_family) {
+next = &cur->ai_next;
+continue;
+}
+if (cur == base->ai_next) {
+// If the first one following base is of a different family, just
+// move base forward one step and continue.
+base = cur;
+next = &base->ai_next;
+continue;
+}
+// Unchain cur from the rest of the list from its current spot.
+*next = cur->ai_next;
+// Hook in cur directly after base.
+cur->ai_next = base->ai_next;
+base->ai_next = cur;
+// Restart with a new base. We know that before moving the cur element,
+// everything between the previous base and cur had the same family,
+// different from cur->ai_family. Therefore, we can keep next pointing
+// where it was, and continue from there with base at the one after
+// cur.
+base = cur->ai_next;
+}
+}
+
+static void print_address_list(void *ctx, const struct addrinfo *addr,
+   const char *title)
+{
+char hostbuf[100], portbuf[20];
+av_log(ctx, AV_LOG_DEBUG, "%s:\n", title);
+while (addr) {
+getnameinfo(addr->ai_addr, addr->ai_addrlen,
+hostbuf, sizeof(hostbuf), portbuf, sizeof(portbuf),
+NI_NUMERICHOST | NI_NUMERICSERV);
+av_log(ctx, AV_LOG_DEBUG, "Address %s port %s\n", hostbuf, portbuf);
+addr = addr->ai_next;
+}
+}
+
+struct ConnectionAttempt {
+int fd;
+int64_t deadline_us;
+struct addrinfo *addr;
+};
+
+// Returns < 0 on error, 0 on successfully started connection attempt,
+// > 0 for a connection that succeeded already.
+static int start_connect_attempt(struct ConnectionAttempt *attempt,
+ struct addrinfo **ptr, int timeout_ms,
+ URLContext *h,
+ void (*customize_fd)(void *, int), void 
*customize_ctx)
+{
+struct addrinfo *ai = *ptr;
+int ret;
+
+*ptr = ai->ai_next;
+
+attempt->fd = ff_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+if (attempt->fd < 0)
+return ff_neterrno();
+attempt->deadline_us = av_gettime_relative() + timeout_ms * 1000;
+attempt->addr = ai;
+
+ff_socket_nonblock(attempt->fd, 1);
+
+if (customize_fd)
+customize_fd(customize_ctx, attempt->fd);
+
+while ((ret = connect(attempt->fd, ai->ai_addr, ai->ai_addrlen))) {
+ret = ff_neterrno();
+switch (ret) {
+case AVERROR(EINTR):
+if (ff_check_interrupt(&h->interrupt_callback)) {
+closesocket(attempt->fd);
+attempt->fd = -1;
+return AVERROR_EXIT;
+}
+continue;
+case AVERROR(EINPROGRESS):
+case AVERROR(EAGAIN):
+return 0;
+default:
+closesocket(attempt->fd);
+attempt->fd = -1;
+