Skip to content

transport server: bind to the address family enabled on the host - #1832

Draft
Narasimha-sc wants to merge 1 commit into
simplex-chat:masterfrom
Narasimha-sc:nd/rc-listener-ipv4-fallback
Draft

transport server: bind to the address family enabled on the host#1832
Narasimha-sc wants to merge 1 commit into
simplex-chat:masterfrom
Narasimha-sc:nd/rc-listener-ipv4-fallback

Conversation

@Narasimha-sc

@Narasimha-sc Narasimha-sc commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Connecting a mobile to the desktop fails when IPv6 is disabled in the kernel (ipv6.disable=1) — simplex-chat/simplex-chat#5515, simplex-chat/simplex-chat#3531.

startTCPServer resolves the wildcard address with AI_PASSIVE only, and then select picks the IPv6 address whenever one is returned:

let hints = defaultHints {addrFlags = [AI_PASSIVE], addrSocketType = Stream}
 in select <$> getAddrInfo (Just hints) host (Just port)
select as = fromJust $ family AF_INET6 <|> family AF_INET

getaddrinfo returns :: even when the host has no IPv6 at all, so the filter selects it and socket AF_INET6 throws EAFNOSUPPORT.

AI_ADDRCONFIG is the missing part of the filter: it excludes the address families that have no address configured on any interface. This adds startTCPServerConfigured, which passes it, and uses it for the remote control TLS server — the only listener that has to accept connections on an IPv6-less host, as its address is advertised as IPv4 in the invitation.

startTCPServer itself is unchanged: SMP/XFTP/ntf servers and the local TCP server keep requiring IPv6, and still fail if it is disabled.

Measured

Calling the two functions from this branch with host = Nothing:

host state startTCPServer startTCPServerConfigured
IPv6 enabled [::] [::]
ipv6.disable=1 (no IPv6 addresses, socket(AF_INET6) → EAFNOSUPPORT) Network.Socket.socket: unsupported operation (Address family not supported by protocol) 0.0.0.0
net.ipv6.conf.all.disable_ipv6=1 [::] 0.0.0.0

Verified in a network namespace — the third row with the real sysctl, the second with an LD_PRELOAD shim making socket(AF_INET6) return EAFNOSUPPORT on top of a namespace with no IPv6 addresses. Also checked that AI_ADDRCONFIG never empties the result: on a host with no addresses configured at all glibc falls back to returning both families, so fromJust in select stays safe.

Verified with the XRCP pairing tests, run in a namespace with disable_ipv6=1 plus an LD_PRELOAD shim making socket(AF_INET6) return EAFNOSUPPORT:

  • with startTCPServer (before this change) the tests hang — the bind throws inside bracketOnError's acquire, so startedPort is never filled and readTMVar blocks;
  • with startTCPServerConfigured they pass in 2.3s (3/3 repeat runs).

SMP server via TLS (37 tests) passes unchanged, and the strict startTCPServer still binds [::] in every state measured.

Two notes on AI_ADDRCONFIG semantics that shaped the patch. It keys off configured addresses rather than socket support, so a host that has IPv6 addresses (link-local included) but cannot create AF_INET6 sockets would still fail — a combination neither ipv6.disable=1 nor disable_ipv6=1 produces, as both remove every IPv6 address. And glibc counts only non-loopback addresses, falling back to returning all families when none are configured, while Windows documents that loopback is not a valid global address and promises no such fallback — hence resolving again without the flag if the flagged call fails, so an offline Windows desktop keeps binding as it does today.

Supersedes the earlier catchAny-on-bind version of this PR, which worked around the selection instead of fixing it.

@Narasimha-sc
Narasimha-sc force-pushed the nd/rc-listener-ipv4-fallback branch from d340e19 to bc8f91e Compare August 18, 2026 14:35
@Narasimha-sc Narasimha-sc changed the title remote control: fall back to IPv4 when IPv6 bind fails transport server: bind to the address family enabled on the host Aug 18, 2026
@Narasimha-sc
Narasimha-sc force-pushed the nd/rc-listener-ipv4-fallback branch from bc8f91e to 321731e Compare August 18, 2026 14:49
startTCPServer resolves the wildcard address with AI_PASSIVE only and then
selects IPv6 address when it is present. On the hosts where IPv6 is disabled
in the kernel (ipv6.disable=1) getaddrinfo still returns `::`, creating the
socket fails with EAFNOSUPPORT, and the desktop app cannot be connected from
the mobile app (simplex-chat/simplex-chat#5515).

Add startTCPServerConfigured that also passes AI_ADDRCONFIG, so that the
address families without any configured address are excluded - IPv6 wildcard
address is only chosen when IPv6 is enabled, and IPv4 wildcard address is used
otherwise. Use it for the remote control TLS server, as its address is
advertised as IPv4 in the invitation.

Some systems do not report configured families in all cases (e.g. Windows does
not count loopback addresses), so when resolution with AI_ADDRCONFIG fails the
addresses are resolved without it, with the previous behaviour.

startTCPServer is unchanged and is still used for SMP/XFTP/ntf servers and for
the local TCP server, so they keep failing when IPv6 is disabled.
@Narasimha-sc
Narasimha-sc force-pushed the nd/rc-listener-ipv4-fallback branch from 321731e to ff2c627 Compare August 18, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant