Forum topic: https://airvpn.org/topic/25140-the-issue-your-browser-is-avoiding-ipv6/ Ipv6 test: http://test-ipv6.com/
In the forum thread AirVPN writes:
Our assigned ULAs are in fde6:7a:7d20::/48 which is inside the range officially reserved to ULA so we don't understand why a browser should discriminate against them in favor of a local IPv4 address...
I hope to answer this question in this document.
This has been written and tested on Arch Linux. Other distributions and operating systems may use different defaults.
Most programs use getaddrinfo
to resolve host names. man 3 getaddrinfo
tells us:
Normally, the application should try using the addresses in the order in which they are returned. The sorting function used within getaddrinfo() is defined in RFC 3484; the order can be tweaked for a particular system by editing /etc/gai.conf (available since glibc 2.5).
RFC 3484 explains the sorting function. The default table according to it is:
Prefix | Precedence | Label |
---|---|---|
::1/128 | 50 | 0 |
::/0 | 40 | 1 |
2002::/16 | 30 | 2 |
::/96 | 20 | 3 |
::ffff:0:0/96 | 10 | 4 |
RFC 6724 "obsoletes RFC 3484" with this table:
Prefix | Precedence | Label |
---|---|---|
::1/128 | 50 | 0 |
::/0 | 40 | 1 |
::ffff:0:0/96 | 35 | 4 |
2002::/16 | 30 | 2 |
2001::/32 | 5 | 5 |
fc00::/7 | 3 | 13 |
::/96 | 1 | 3 |
fec0::/10 | 1 | 11 |
3ffe::/16 | 1 | 12 |
Finally, there is Arch's default table in /etc/gai.conf
:
Prefix | Precedence | Label |
---|---|---|
::1/128 | 50 | 0 |
::/0 | 40 | 1 |
2002::/16 | 30 | 2 |
::/96 | 20 | 3 |
::ffff:0:0/96 | 10 | 4 |
fec0::/10 | 0 | 5 |
fc00::/7 | 0 | 6 |
2001:0::/32 | 0 | 7 |
The 0
precedences are not specified in the file and not documented in the man page but the glibc source code shows that 0 is indeed the default.
The file gives an explanation for this table:
This default differs from the tables given in RFC 3484 by handling (now obsolete) site-local IPv6 addresses and Unique Local Addresses. The reason for this difference is that these addresses are never NATed while IPv4 site-local addresses most probably are. Given the precedence of IPv6 over IPv4 (see below) on machines having only site-local IPv4 and IPv6 addresses a lookup for a global address would see the IPv6 be preferred. The result is a long delay because the site-local IPv6 addresses cannot be used while the IPv4 address is (at least for the foreseeable future) NATed. We also treat Teredo tunnels special.
When connecting to an AirVPN servers the tunnel network interface receives an IPv4 addresses such as 10.0.0.1
and a unique local address (ULA) IPv6 address such as fc00::1
.
We can now understand the ordering of addresses returned by getaddrinfo
according to the rules in RFC 3484 and RFC 6724 and experimentally confirm them for example with this python program:
python3 -c "import socket; print('\n'.join(map(lambda x: x[4][0], filter(lambda x: x[1] == socket.SocketKind.SOCK_STREAM, socket.getaddrinfo('google.com', 80)))))"
This command prints out the ip addresses belonging to google.com
in the returned order. Re-running this command immediately reflects the changes to gai.conf
on my machine. A browser seems to need to be restarted in order for the changes to take effect.
On my machine with Arch's default configuration I get:
172.217.20.110
2a00:1450:400e:80c::200e
The IPv4 address comes first which means applications prefer it to the IPv6 address. The relevant rule for this and the following examples is:
Rule 5: Prefer matching label.
If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB), then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and Label(Source(DB)) = Label(DB), then prefer DB.
DA
and DB
are the two destination addresses being compared. I am using DA
as Google's IPv4 address and DB
as Google's IPv6 address.
Source(DA)
is the source address selected for DA
. In this case it the IPv4 address my interface got from the VPN (10.0.0.1
). Likewise, Source(DB)
is fc00::1
.
Label(Source(DA))
is 4
because DA
matches the prefix ::ffff:0:0/96
which is the prefix for IPv4-mapped IPv6 addresses. This is equal to Label(DA)
which matches the same prefix.
However, Label(Source(DB))
is 6
matching fc00::/7
which is not equal to Label(DB)
which is 1
matching ::/0
.
According to Rule 5
this means that DA
(the IPv4 address) is preferred.
Repeating the process with the table from RFC 6724 we get Label(Source(DA)) = Label(DA) = 4
and Label(Source(DB)) = 6 <> Label(DB) = 1
. DA
is preferred again.
Only with the table from RFC 3484 we get Label(Source(DA)) = Label(DA) = 4
and Label(Source(DB)) = Label(DB) = 1
which makes both source addresses equals so far. The next rule breaks the tie:
Rule 6: Prefer higher precedence.
If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if Precedence(DA) < Precedence(DB), then prefer DB.
Precedence(DA)
is 10
while Precedence(DB)
is 40
which makes DB
(the IPv6) address preferred.
We can fix this problem by changing the default table in /etc/gai.conf
to match RFC 3484. The correct entries are given in man 5 gai.conf
5. Or we could adapt the table from RFC 6724 to assign ULAs the label 1 which would look like this:
# RFC 6724 Default
label ::1/128 0
label ::/0 1
label ::ffff:0:0/96 4
label 2002::/16 2
label 2001::/32 5
# label fc00::/7 13
# Changed the above line for AirVPN workaround.
label fc00::/7 1
label ::/96 3
label fec0::/10 11
label 3ffe::/16 12
precedence ::1/128 50
precedence ::/0 40
precedence ::ffff:0:0/96 35
precedence 2002::/16 30
precedence 2001::/32 5
precedence fc00::/7 3
precedence ::/96 1
precedence fec0::/10 1
precedence 3ffe::/16 1
Whether using ULAs for this purpose is correct has not been answered in this document. RFC 4193 may answer the question but I have not read it carefully enough, yet.