Android: fix IPv6-only server connectivity
Fixes #1987
Context
The Android client could not connect to an IPv6-only TeamTalk server. On some Android devices/networks, name resolution via getaddrinfo() can also fail transiently (EAI_AGAIN), making connections unreliable even when IPv6 connectivity is available.
What this changes
- Fix UDP socket creation to match the resolved address family (AF_INET/AF_INET6) instead of always forcing IPv4.
- Make native resolution more robust on Android: retry on
EAI_AGAIN, avoid problematicgetaddrinfo()flags/constraints, and handle numeric IPv4/IPv6 literals without relying on the platform resolver. - On Android, prefer
DnsResolver(system ordering) and only fall back to UDP/53 DNS as a last resort (resolver timeouts/failures), with caching and logs limited to the fallback path.
Compatibility / Risk
- Keeps IPv4-only and dual-stack working.
- The UDP/53 fallback is intentionally last-resort (unencrypted) to avoid total failure when the system/NDK resolver is flaky.
Testing
- Verified on device: connects to IPv6-only server, dual-stack hostname (respects system preference), and IPv4-only server.
Copilot Review suggestions applied, except hostname and port are two different fields and a TeamTalk server can be on a local network.
Is there a way to avoid changing files in TeamTalk DLL? It appears strange that it works on all other platform than Android.
Is it possible to make the DNS lookup in TeamTalkAndroid and always pass an IP-address (instead of a hostname) to TT_Connect()?
I tried an Android-only approach (no changes in TeamTalk DLL / TeamTalkLib): resolve hostnames in TeamTalkAndroid (DnsResolver on API 29+, fallback to InetAddress) and always pass IP literals to TT_Connect().
Result: even when we successfully resolve and pass an IPv6 literal, the connection still fails on Android.
Example logs:
DNS: Candidates for ipv6.hostname.tld -> [2001:xxxx:yyyy:zzzz::]
connect() to 2001:xxxx:yyyy:zzzz:: ... -> false
And even when entering the IPv6 literal directly:
connect(): host=2001:xxxx:yyyy:zzzz:: candidates=[2001:xxxx:yyyy:zzzz::]
connect() ... -> false
So this is not just a hostname/DNS issue. The Android native layer (JNI / underlying socket/connect path) appears to fail for IPv6, meaning an Android-only DNS workaround cannot fix #1987 on its own.
I pushed a branch called android-ipv6-java-only to test.
Proposed compromise:
We could keep the Android-side hostname resolution as a fallback to improve dual-stack hostname handling (and avoid DNS issues/timeouts), but it won’t fix IPv6-only/IPv6 literal connects.
The actual fix should be in the native layer so that TT_Connect() works with IPv6 literals on Android (proper IPv6 parsing + socket connect), ideally with additional Android-native logging (errno/family/address) to validate the fix.
I'll resolve the conflicts with master but I plan to put #ifdef ANDROID around the new code in this merge-request. The new code complicates the code a lot and the problems with IPv6 are only related to Android.
Should there also be a check on Android platform level? In other words does newer Android devices also have this issue?
Should there also be a check on Android platform level? In other words does newer Android devices also have this issue?
Yes, I’m using a Pixel 8a running Android 16, and I’m experiencing this issue.
@beqabeqa473 are you ok with the changes in this PR? In the TeamTalk DLL I've isolated the Android code, so none of the other platforms are affected.
@Nardol do you have an IP-address and port of a TeamTalk server that runs on IPv6 only?
@bear101 I've just sent you the info privately.
Hm, on iOS it connects fine to the server you sent me on master branch. But if Android doesn't work without your changes then it's fine by me to merge it.
I'll fix the merge errors
Hm, I seem to remember testing this on your branch but when I test on master branch now I get this exception:
Process:dk.bearware.gui,PID:21183
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1450)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:102)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:90)
at java.net.InetAddress.getAllByName(InetAddress.java:787)
at dk.bearware.utils.DnsUtils.resolveHostCandidatesForConnect(DnsUtils.java:189)
at dk.bearware.backend.TeamTalkService.reconnect(TeamTalkService.java:638)
at dk.bearware.gui.ServerListActivity.onServerClick(ServerListActivity.java:388)
@hwangsihu is it also crashing on your system? I wonder if it's some permissions that are messed up.
Sorry, but how can I reproduce this? Just connect to a server that uses IPv6?
@Nardol hm, on Android 8.1 it crashes with the stack trace above.
When I try on Android 15 to connect to tt5eu.bearware.dk:10335 then it only tries IPv6 addresses as far I can see:
2026-01-11 10:02:12.849 9080-9080 bearware dk.bearware.gui I DNS fallback (udp53) for tt5eu.bearware.dk: dnsServers=[/10.0.2.3] privateDnsActive=false privateDnsServerName=null
2026-01-11 10:02:12.849 9080-9080 bearware dk.bearware.gui I DNS resolved (udp53) tt5eu.bearware.dk -> 2a01:4f8:c013:1167::1
2026-01-11 10:02:12.853 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:12.854 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:17.871 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:17.886 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:22.896 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:22.960 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:27.970 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:27.983 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:32.995 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:33.058 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:38.067 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:38.087 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:43.096 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:43.159 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:48.169 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:48.194 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:53.203 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:53.270 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:02:58.280 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:02:58.288 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:03:03.298 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:03:03.364 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
2026-01-11 10:03:08.374 9080-9080 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 10:03:08.395 9080-9080 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
I can't connect to the server either.
By the way, didn't you recently change the library code regarding IPv6? Could it be conflicting with that?
I can't connect to the server either.
By the way, didn't you recently change the library code regarding IPv6? Could it be conflicting with that?
Yes, you need to rebuild libTeamTalk5.so. But in the Android 8.1 call stack it doesn't event make it to TeamTalk5.connect()
I tested it on Android 12, and I couldn't connect to the official servers, repeatedly receiving server connection failure messages. However, since I wasn't using the debug build, I couldn't check the logs.
@Nardol hm, on Android 8.1 it crashes with the stack trace above.
When I try on Android 15 to connect to tt5eu.bearware.dk:10335 then it only tries IPv6 addresses as far I can see:
So two separate issues if I understand correctly?
For the second, is tt5eu.bearware.dk:10335 IPv4-only? That's what it looks.
I might have forgoten this test case.
@Nardol hm, on Android 8.1 it crashes with the stack trace above. When I try on Android 15 to connect to tt5eu.bearware.dk:10335 then it only tries IPv6 addresses as far I can see:
So two separate issues if I understand correctly? For the second, is
tt5eu.bearware.dk:10335IPv4-only? That's what it looks. I might have forgoten this test case.
tt5eu.bearware.dk is both IPv4 and IPv6: 188.245.173.146 and 2a01:4f8:c013:1167::1
tt5eu.bearware.dk is both IPv4 and IPv6: 188.245.173.146 and 2a01:4f8:c013:1167::1
Yes, the hostname. But what about the server itself? Is it configured to listen on IPv4-only?
tt5eu.bearware.dk is both IPv4 and IPv6: 188.245.173.146 and 2a01:4f8:c013:1167::1
Yes, the hostname. But what about the server itself? Is it configured to listen on IPv4-only?
It binds to both:
<bind-ip>188.245.173.146</bind-ip>
<bind-ip>2a01:4f8:c013:1167::1</bind-ip>
@bear101 I pushed a branch which does two things:
- Moves
InetAddress.getAllByName()to a background executor to avoidNetworkOnMainThreadExceptionon API 27. - If
DnsResolverreturns only one family (e.g. IPv6), it fills the missing family using UDP fallback and merges the list, so IPv4 is actually tried.
I also added a temporary log of connectHosts to verify what’s being attempted.
Branch: fix/android-dns-fallback (commit 3c5c9193c).
Before I open a PR, could you test and tell me if it fixes both issues?
With this change I can connect to tt5eu.bearware.dk with API 27.
But then I tried API 35 and it cannot connect to tt5eu.bearware.dk
2026-01-11 21:08:39.308 11141-11141 bearware dk.bearware.gui I DNS fallback (udp53) for tt5eu.bearware.dk: dnsServers=[/10.0.2.3] privateDnsActive=false privateDnsServerName=null
2026-01-11 21:08:39.308 11141-11141 bearware dk.bearware.gui I DNS resolved (udp53) tt5eu.bearware.dk -> 2a01:4f8:c013:1167::1
2026-01-11 21:08:39.309 11141-11141 bearware dk.bearware.gui I DNS candidates for tt5eu.bearware.dk: [2a01:4f8:c013:1167::1, 188.245.173.146]
2026-01-11 21:08:39.311 11141-11141 bearware dk.bearware.gui I connect() to 2a01:4f8:c013:1167::1:10335 udp 10335 enc=false -> true
2026-01-11 21:08:39.312 11141-11141 bearware dk.bearware.gui I Failed to connect tt5eu.bearware.dk:10335
I updated the same branch with a fix for API 35. Issue: connect() returns true for IPv6 (attempt started), then fails later; our loop stops, so IPv4 is never tried. Fix: on onConnectFailed() we now try the next DNS candidate before giving up. New commit: 389cd3cb7 on branch fix/android-dns-fallback. Could you retest API 35 with this?
Not sure this is the cleanest approach, but it should verify the suspected failure path.