TeamTalk5 icon indicating copy to clipboard operation
TeamTalk5 copied to clipboard

Android: fix IPv6-only server connectivity

Open Nardol opened this issue 3 months ago • 3 comments

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 problematic getaddrinfo() 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.

Nardol avatar Dec 12 '25 21:12 Nardol

Copilot Review suggestions applied, except hostname and port are two different fields and a TeamTalk server can be on a local network.

Nardol avatar Dec 14 '25 21:12 Nardol

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()?

bear101 avatar Dec 15 '25 21:12 bear101

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.

Nardol avatar Dec 15 '25 23:12 Nardol

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?

bear101 avatar Jan 07 '26 07:01 bear101

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.

Nardol avatar Jan 07 '26 09:01 Nardol

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

bear101 avatar Jan 07 '26 11:01 bear101

@Nardol do you have an IP-address and port of a TeamTalk server that runs on IPv6 only?

bear101 avatar Jan 08 '26 19:01 bear101

@bear101 I've just sent you the info privately.

Nardol avatar Jan 08 '26 20:01 Nardol

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.

bear101 avatar Jan 10 '26 11:01 bear101

I'll fix the merge errors

bear101 avatar Jan 10 '26 11:01 bear101

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)

bear101 avatar Jan 11 '26 08:01 bear101

@hwangsihu is it also crashing on your system? I wonder if it's some permissions that are messed up.

bear101 avatar Jan 11 '26 08:01 bear101

Sorry, but how can I reproduce this? Just connect to a server that uses IPv6?

hwangsihu avatar Jan 11 '26 08:01 hwangsihu

@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

bear101 avatar Jan 11 '26 09:01 bear101

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?

hwangsihu avatar Jan 11 '26 09:01 hwangsihu

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()

bear101 avatar Jan 11 '26 09:01 bear101

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.

hwangsihu avatar Jan 11 '26 09:01 hwangsihu

@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 avatar Jan 11 '26 12:01 Nardol

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

tt5eu.bearware.dk is both IPv4 and IPv6: 188.245.173.146 and 2a01:4f8:c013:1167::1

bear101 avatar Jan 11 '26 12:01 bear101

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?

Nardol avatar Jan 11 '26 12:01 Nardol

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 avatar Jan 11 '26 12:01 bear101

@bear101 I pushed a branch which does two things:

  1. Moves InetAddress.getAllByName() to a background executor to avoid NetworkOnMainThreadException on API 27.
  2. If DnsResolver returns 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?

Nardol avatar Jan 11 '26 13:01 Nardol

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

bear101 avatar Jan 11 '26 20:01 bear101

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.

Nardol avatar Jan 11 '26 22:01 Nardol