Skip to content

Instantly share code, notes, and snippets.

@e00E
Last active October 3, 2023 20:28
Show Gist options
  • Save e00E/70bcb5f7f0db216739029a7b7e342fdf to your computer and use it in GitHub Desktop.
Save e00E/70bcb5f7f0db216739029a7b7e342fdf to your computer and use it in GitHub Desktop.
Why does a client connected to the AirVPN vpn service prefer IPv4 over IPv6 addresses?

Question

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.

Answer

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.

Fix

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.conf5. 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

ULAs

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment