Why the Dropbox Desktop Client Went IPv6

For most of the internet's history, IPv4 has been the addressing standard. Its roughly four billion unique addresses, however, are no longer sufficient. The successor protocol, IPv6, solves this with a vastly expanded address space, but adoption has been slow. That is changing: by April 2017, Google measured global IPv6 adoption at 14%, with the U.S. at over 30%.

The shift is most visible among cellular carriers moving to IPv6-only networks, where devices lack IPv4 connectivity entirely and must rely on NAT64/DNS64 gateways to reach legacy IPv4 services. Enterprise and ISP networks are also increasingly dual-stack, supporting both protocols.

To ensure the desktop client remained functional on these networks, Dropbox added IPv6 support in version 24 of its client, released April 17, 2017. The work centered on two areas: resolving hostnames and establishing connections.

Resolving Addresses Without Blocking

Legacy lookup functions like gethostbyname() do not handle IPv6 addresses. The cross-platform replacement, getaddrinfo(), accepts a hostname and returns both IPv6 and IPv4 addresses in an order determined by the host's preferred address family. Callers iterate through the list until a connection succeeds.

getaddrinfo() supports address family filters: AF_INET for IPv4, AF_INET6 for IPv6, or AF_UNSPEC to request all available families. Internally, this triggers both A and AAAA DNS queries.

The problem with getaddrinfo() is that it blocks and does not support caller-defined timeouts. Operating system defaults often fall in the 30-90 second range. For a client that needs to be responsive, waiting for a slow AF_UNSPEC call and then falling back to AF_INET could introduce minutes of latency.

Dropbox's approach runs concurrent AF_UNSPEC and AF_INET resolutions in a thread pool, using Python's concurrent.futures module. Since IPv6 is preferred, the client waits a few seconds for the AF_UNSPEC result; if it does not arrive, it uses whichever call finishes first. DNS caching by operating systems makes this dual lookup a rare occurrence.

Metrics showed that roughly 80% of connection attempts resolve successfully via the AF_UNSPEC call. When that call took longer than a few seconds, both calls failed more than 86% of the time—an indicator of a bad network or a machine suspending mid-connect. Cases where only one lookup succeeded represented about 0.3% of attempts.

Developers found one additional pitfall: Python's non-blocking sockets still incur blocking delays if connect() is given a hostname rather than an IP address, because connect() invokes getaddrinfo() internally. Resolution must happen explicitly before attempting a non-blocking connect.

>>> import socket
>>> import time
>>> def connect_nonblocking(host):
...   """This function creates a non-blocking socket and attempts to connect to 'host'.
...    connect() on a non-blocking socket throws an exception with EINPROGRESS."""
...   sock = socket.socket()
...   sock.setblocking(False)
...   start = time.time()
...   try:
...     sock.connect((host, 80))
...   except socket.error:
...     print "non-blocking socket threw exception after %f seconds." % (time.time() - start)
...
>>> # We clear the system DNS cache.
>>> # Then we use the Network Link Conditioner to intentionally introduce a 3 second delay in DNS lookup.
>>> connect_nonblocking('dropbox.com')
non-blocking socket threw exception after 3.009090 seconds.
>>> # At this point, the cache is used so the response is instantaneous.
>>> connect_nonblocking('dropbox.com')
non-blocking socket threw exception after 0.008408 seconds.

Connection Establishment and Happy Eyeballs

With a mixed list of IPv6 and IPv4 addresses in hand, the naive approach would be to try each address in sequence until one connects. That fails on dual-stack networks where IPv4 works but IPv6 is broken—for instance, when a NAT64/DNS64 gateway is slow or unavailable. On such a network, a host might waste 20 seconds (at a 10-second per-attempt timeout) failing two IPv6 connections before reaching a working IPv4 address.

['2001:DB8::1', '2001:DB8::2', '198.51.100.1', '198.51.100.2']

The standard solution is documented in RFC 6555, known as Happy Eyeballs. The strategy races the first IPv6 address against the first IPv4 address. Since IPv6 is preferred, the connection to it starts immediately; if it does not establish within 300 milliseconds, the IPv4 connection begins. The first successful connection wins. On a network with functioning IPv4 and broken IPv6, that means the client reaches 198.51.100.1 promptly instead of waiting out IPv6 timeouts. Only if both initial attempts fail does the client proceed through the remaining addresses in order.

Dropbox's address resolution strategy draws clear inspiration from Happy Eyeballs. The company noted that since its servers do not advertise native AAAA records, dual-stack users across the internet still connect via IPv4. During connection attempts, fewer than 0.5% of hosts resolved Dropbox servers to IPv6 addresses.

Proxy Compatibility

Proxy support was a secondary concern. Dropbox supports SOCKS4, SOCKS5, and HTTP(S) proxies. SOCKS4 and its extension SOCKS4a lack IPv6 support entirely and remain unusable on IPv6-only networks. SOCKS5, however, uses a binary protocol where Dropbox added support for the x03 ATYP field to carry IPv6 addresses.

HTTP(S) proxies require no negotiation on the client side; the request URL is sent directly and the proxy connects using whatever protocol it supports. No changes were needed there. When both the client and the proxy sit on an IPv6-only network, the connection logic from the general case is reused to reach the proxy itself.

Rolling Out Safely

Deploying these changes touched critical client infrastructure, so Dropbox kept the new code behind feature flags for several weeks, enabling it only for in-office and beta builds. Daily internal deployments surfaced issues quickly. Developers used Google's public DNS64 servers along with the Network Link Conditioner on macOS to simulate constrained IPv6 environments during testing.

Out-of-process updaters provided a safety net: if a bug slipped through and prevented users from connecting, a fixed build could be pushed without manual intervention. This happened with one beta build that performed local DNS resolution even for proxy connections; a rapid follow-up release restored functionality for affected users within days.

The migration to IPv6 required cross-platform changes to both DNS resolution and socket connection logic while maintaining full backward compatibility for IPv4-only users. The dual-resolution and Happy Eyeballs approaches described here are the result, and Dropbox now handles IPv6-only, dual-stack, and IPv4-only networks without user-visible disruption.