Code: Select all
0.173 2020-06-25 20:34:55; Factorio 0.18.32 (build 52799, linux64, alpha)
0.338 Operating system: Linux (Debian testing)
When I try to connect to it using `factorio --mp-connect localhost:8181`, or use "localhost:8181" in the GUI to connect, factorio only tries 127.0.0.1 for the network address:
Code: Select all
88357.782 Joining game IP ADDR:({127.0.0.1:8181})
My configuration is very standard and not modified from default:
Code: Select all
$ getent hosts localhost
::1 localhost ip6-localhost ip6-loopback
$
Code: Select all
$ getent ahosts localhost
::1 STREAM localhost
::1 DGRAM
::1 RAW
127.0.0.1 STREAM
127.0.0.1 DGRAM
127.0.0.1 RAW
$ getent ahostsv4 localhost
127.0.0.1 STREAM localhost
127.0.0.1 DGRAM
127.0.0.1 RAW
127.0.0.1 STREAM
127.0.0.1 DGRAM
127.0.0.1 RAW
$ getent ahostsv6 localhost
::1 STREAM localhost
::1 DGRAM
::1 RAW
Code: Select all
$ cat /etc/hosts | grep localhost
127.0.0.1 localhost debian
::1 localhost ip6-localhost ip6-loopback
$
Code: Select all
$ grep hosts /etc/nsswitch.conf
hosts: files mdns4_minimal [NOTFOUND=return] dns myhostname mymachines
$
Code: Select all
$ egrep -v '^#' /etc/gai.conf
$
Code: Select all
$ cat /etc/host.conf
multi on # If set to on, the resolver library will return all valid addresses for a host that appears in the `/etc/hosts` file, instead of only the first.
Code: Select all
$ host localhost
localhost has address 127.0.0.1
localhost has IPv6 address ::1
$
But there must be more to it, because even if I change the gai.conf to:
Code: Select all
$ egrep -v '^#' /etc/gai.conf
label ::1/128 0 # IPv6 localhost will be returned first
label ::/0 1
label 2002::/16 2
label ::/96 3
label ::ffff:0:0/96 4 # IPv4 in "tunneled IPv6 form", will be returned, but last
precedence ::1/128 50
precedence ::/0 40
precedence 2002::/16 30
precedence ::/96 20
precedence ::ffff:0:0/96 10
$
Similarly, if I use, ask DNS resolver (which is not used for `localhost`, because it is handled by hosts resolver), to return tunneled form for IPv4 addresses, it still doesn't.
Code: Select all
$ cat /etc/resolv.conf
nameserver 2001:1620:2777:1::10
nameserver 2001:1620:2777:2::20
options inet6 # Ask `gethostbyname` to do AAAA query before A query, and wrap IPv4 addresses (A records) as "tunneled form" back to app if there are no AAAA records. AFAIK it is ignored by `getaddrinfo`, which requires explicit `AI_V4MAPPED` in the app for same functionality.
If I change the `/etc/hosts` to only list IPv6 for localhost it does work:
Code: Select all
# cat /etc/hosts
127.0.0.1 ipv4-localhost debian
::1 localhost ip6-localhost ip6-loopback
#
Code: Select all
13.228 Joining game IP ADDR:({[::1]:34197})
I know in reality if the factorio server has DNS address (and clients asked to use it), the factorio server process should listen on all IP addresses exposed via this DNS address, so any address would work (not doing so would be a server misconfiguration really). The problem is it is not totally possible with factorio headless server. It can only listen on ALL (current and future) interfaces (using `--port`), or on only one (via `--bind`). For non-public server, it doesn't is exactly a good option, because it forces one to listen on all interfaces or one.
However I do think the factorio is not respecting current standards:
RFC 6724: https://tools.ietf.org/rfc/rfc6724.txt - Default Address Selection for Internet Protocol Version 6 (IPv6)
(obsoletes RFC 3484)
RFC 3493: https://tools.ietf.org/rfc/rfc3493.txt - Basic Socket Interface Extensions for IPv6 (obsoletes RFC 2553)
RFC 4291: https://tools.ietf.org/rfc/rfc4291.txt - IP Version 6 Addressing Architecture (obsoletes RFC 3513)
RFC 4862: https://tools.ietf.org/rfc/rfc4862.txt - IPv6 Stateless Address Autoconfiguration (obsoletes RFC 2462)
Quote from RFC 6724 , section 2:
And more details about treatment of IPv4 and IPv6:2. Context in Which the Algorithms Operate
...
Well-behaved applications SHOULD NOT simply use the first address
returned from an API such as getaddrinfo() and then give up if it
fails.
...
"preferred" (in the RFC 4862) only means that they can be used unrestricted by the applications (i.e. they are not stale, or link-local). It doesn't determinine priority of which address to use. All "preferred" addresses (which is some set IPv6 and IPv4 addresses) are considered equal in this sense:...
3.2. IPv4 Addresses and IPv4-Mapped Addresses
The destination address selection algorithm operates on both IPv6 and
IPv4 addresses. For this purpose, IPv4 addresses MUST be represented
as IPv4-mapped addresses [RFC4291]. For example, to look up the
precedence or other attributes of an IPv4 address in the policy
table, look up the corresponding IPv4-mapped IPv6 address.
IPv4 addresses are assigned scopes as follows. IPv4 auto-
configuration addresses [RFC3927], which have the prefix 169.254/16,
are assigned link-local scope. IPv4 loopback addresses (Section
4.2.2.11 of [RFC1812]), which have the prefix 127/8, are assigned
link-local scope (analogously to the treatment of the IPv6 loopback
address (Section 4 of [RFC4007])). Other IPv4 addresses (including
IPv4 private addresses [RFC1918] and Shared Address Space addresses
[RFC6598]) are assigned global scope.
IPv4 addresses MUST be treated as having "preferred" (in the RFC 4862
sense) configuration status.
3.3. Other IPv6 Addresses with Embedded IPv4 Addresses
IPv4-compatible addresses [RFC4291], IPv4-mapped [RFC4291], IPv4-
converted [RFC6145], IPv4-translatable [RFC6145], and 6to4 addresses
[RFC3056] contain an embedded IPv4 address. For the purposes of this
document, these addresses MUST be treated as having global scope.
IPv4-compatible, IPv4-mapped, and IPv4-converted addresses MUST be
treated as having "preferred" (in the RFC 4862 sense) configuration
status.
3.4. IPv6 Loopback Address and Other Format Prefixes
The loopback address MUST be treated as having link-local scope
(Section 4 of [RFC4007]) and "preferred" (in the RFC 4862 sense)
configuration status.
...
From RFC 4862:
A non-preferred address types are tentative addresses and deprecated address. These will not be used. It is more for the purpose of selecting the source address, not the destination address, or the host resolution priorities....
preferred address - an address assigned to an interface whose use by
upper-layer protocols is unrestricted. Preferred addresses may be
used as the source (or destination) address of packets sent from
(or to) the interface.
...
As far as I know `getaddrinfo` in Linux (glibc 2.30 in my case), does respect this behaviors (as tested in other apps), but Factorio is not.
It somehow feels factorio is using own non-glibc resolver, and/or sets own address sorting, and only tries the first address.
It does appear factorio uses `getaddrinfo`, as I checked with gdb and ltrace:
Code: Select all
Thread 1 "factorio" hit Breakpoint 1, __GI_getaddrinfo (name=0x7ffd6deb5ca0 "localhost", service=0x7ffd6deb5cc0 "34197", hints=0x7ffd6deb5d10, pai=0x7ffd6deb5c88) at ../sysdeps/posix/getaddrinfo.c:2161
2161 in ../sysdeps/posix/getaddrinfo.c
(gdb) bt
#0 __GI_getaddrinfo (name=0x7ffd6deb5ca0 "localhost", service=0x7ffd6deb5cc0 "34197", hints=0x7ffd6deb5d10, pai=0x7ffd6deb5c88) at ../sysdeps/posix/getaddrinfo.c:2161
#1 0x0000000000ff69d8 in SocketAddress::SocketAddress () at /tmp/factorio-build-6v7ua5/src/Net/SocketAddress.cpp:117
#2 0x0000000000ff6b5b in NamedSocketAddress::resolve () at /tmp/factorio-build-6v7ua5/src/Net/NamedSocketAddress.cpp:12
#3 0x0000000001001164 in NamedSocketAddress::getAddress () at /tmp/factorio-build-6v7ua5/src/Net/NamedSocketAddress.cpp:5
#4 MultiplayerConnectSettings::getAddress () at /tmp/factorio-build-6v7ua5/src/Net/MultiplayerConnectSettings.cpp:27
#5 ClientRouter::joinGame () at /tmp/factorio-build-6v7ua5/src/Net/ClientRouter.cpp:40
#6 0x00000000011c3b22 in ClientMultiplayerManager::joinGame () at /tmp/factorio-build-6v7ua5/src/Net/ClientMultiplayerManager.cpp:271
...
(gdb) print *hints
$4 = {ai_flags = 1024, ai_family = 0, ai_socktype = 2, ai_protocol = 0, ai_addrlen = 0, ai_addr = 0x0, ai_canonname = 0x0, ai_next = 0x0}
(gdb)
`ai_family = 0` means `AF_UNSPEC`, which is also OK (return both AF_INET and AF_INET6).
`ai_socktype = 2` means `SOCK_DGRAM`, that is OK.
All is good, and if I trace it manually after the return:
Code: Select all
Thread 1 "factorio" hit Breakpoint 1, __GI_getaddrinfo (name=0x7ffd6deb5ca0 "localhost", service=0x7ffd6deb5cc0 "34197", hints=0x7ffd6deb5d10, pai=0x7ffd6deb5c88) at ../sysdeps/posix/getaddrinfo.c:2161
2161 ../sysdeps/posix/getaddrinfo.c: No such file or directory.
(gdb) print pai
$8 = (struct addrinfo **) 0x7ffd6deb5c88
(gdb) advance getaddrinfo
0x0000000000ff69d8 in SocketAddress::SocketAddress () at /tmp/factorio-build-6v7ua5/src/Net/SocketAddress.cpp:117
117 /tmp/factorio-build-6v7ua5/src/Net/SocketAddress.cpp: No such file or directory.
(gdb) print **(struct addrinfo**)(0x7ffd6deb5c88)
$17 = {ai_flags = 1024, ai_family = 10, ai_socktype = 2, ai_protocol = 17, ai_addrlen = 28, ai_addr = 0x4a996f0, ai_canonname = 0x0, ai_next = 0xae6dc10}
(gdb) print *(**(struct addrinfo**)(0x7ffd6deb5c88))->ai_next
$20 = {ai_flags = 1024, ai_family = 2, ai_socktype = 2, ai_protocol = 17, ai_addrlen = 16, ai_addr = 0xae6dc40, ai_canonname = 0x0, ai_next = 0x0}
(gdb) print *(**(struct addrinfo**)(0x7ffd6deb5c88)).ai_addr
$22 = {sa_family = 10, sa_data = "\205\225", '\000' <repeats 11 times>}
(gdb) print *(*(**(struct addrinfo**)(0x7ffd6deb5c88))->ai_next).ai_addr
$26 = {sa_family = 2, sa_data = "\205\225\177\000\000\001\000\000\000\000\000\000\000"}
Code: Select all
"\205\225", '\000' <repeats 11 times> corresponds to (sockaddr_in6 value) of [::1]:34197
'\205\225\177\000\000\001...' corresponds to (sockaddr_in value) of 127.0.0.1:34197 (0205 * 256 + 0225 == 34197)
(gdb) print ((**(struct addrinfo**)(0x7ffd6deb5c88)).ai_addr.sa_data)[/*port*/2+/*flow_info*/4+/*address_length*/16-1/*last_digit*/]
$73 = 1 '\001' # 1 in `0000:0000:0000:0000:0000:0000:0000:0001`