Steps To Reproduce
- Host a bitwarden server on your local network, accessible only over IPv6 (for example, some IPv6 ULA such as
fdd4:aa51:eed9:426:9f99:938b:ebf9:7433, accessible via bitwarden.example.com, which you'll see the in stacktrace below.).
- With your android phone connected to the same local network: open bitwarden, on the login screen, select "Self-hosted", tap "Self-hosted", and enter the url of your server (in this example:
https://bitwarden.example.com).
- Try to log in. You'll experience a delay after pressing "Continue" on the login screen, but you will eventually make it to the master password prompt screen.
- On the master password prompt screen: enter your master password and click "Log in with master password". After 10 seconds of "Logging in...", you'll get a popup that says "An error has occurred. We were unable to process your request. Please try again or contact us." If you click "Share error details", you'll see a stacktrace of a
SocketTimeoutException (full trace below).
Expected Result
I expect to be able to log into my bitwarden server.
Actual Result
Here's the stack trace:
Stacktrace:
java.net.SocketTimeoutException: failed to connect to bitwarden.example.com/fdd4:aa51:eed9:426:9f99:938b:ebf9:7433 (port 443) from /fdd4:aa51:eed9:426:9f99:93b5:4d8:4a2e (port 38658) after 10000ms
libcore.io.IoBridge.connectErrno(IoBridge.java:235)
libcore.io.IoBridge.connect(IoBridge.java:179)
java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:142)
java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:390)
java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230)
java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212)
java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436)
java.net.Socket.connect(Socket.java:646)
ev1.i(r8-map-id-01e002bd0632bb5cc7de20747ff74afc7c2ccce2c9b98107678ea7daf8b79b97:77)
ev1.d(r8-map-id-01e002bd0632bb5cc7de20747ff74afc7c2ccce2c9b98107678ea7daf8b79b97:38)
r63.a(r8-map-id-01e002bd0632bb5cc7de20747ff74afc7c2ccce2c9b98107678ea7daf8b79b97:3)
ia.a(r8-map-id-01e002bd0632bb5cc7de20747ff74afc7c2ccce2c9b98107678ea7daf8b79b97:67)
ia.run(r8-map-id-01e002bd0632bb5cc7de20747ff74afc7c2ccce2c9b98107678ea7daf8b79b97:82)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1100)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
java.lang.Thread.run(Thread.java:1572)
Version: 2026.6.1 (21713)
Device: 📱 google Pixel 6 🤖 17@37 📦 prod -fdroid
CI: 🧱 commit: bitwarden/android/release/2026.6-rc57@e5ee43f68773f9fe20bec3dd664781d65b804967
💻 build source: bitwarden/android/actions/runs/28253991408/attempts/1
Screenshots or Videos
No response
Additional Context
The last version of the app where this worked was 2026.5.0. I tested 2026.6.1, 2026.6.0, and 2026.5.1 and confirmed they do not work.
I bisected and found that this was introduced by #6916, which upgraded the android sdk from 36 to 37. One consequence of sdk version 37 is that local network permission is now disabled by default. The bitwarden android app only requests local network access if the address we're connecting to is a "site local address", as returned by InetAddress.isSiteLocalAddress. Here are the implementations for ipv4 and ipv6:
However, I don't think the correct fix here is to just add "is ula?" to the check. I believe the intent of the code is to check "would android block connections to this ip address?", which android conveniently documents on their Local Network Definition page. Fun fact: this reveals that our ipv4 check is likely wrong as well (android blocks link local and CGNAT addresses, which are not detected by Inet4Address.isSiteLocalAddress). The IPv6 check is even harder to implement, as it's not just a comparison against static subnets, it depends on your routing table.
AFAICT, there's no easy way to implement this check (I've asked android for advice on this in https://issuetracker.google.com/issues/534973523). I think one correct way to do it is to just try and check for the error reason afterwards with android_getnetworkblockedreason, but I guess that requires trying and waiting for a timeout, and it requires using the NDK.
Perhaps the least odious approach would be to prompt the user for local network permission if they select "Self-hosted"? Or catching SocketTimeoutException and warning the user that they might need to grant the "Nearby devices" permission?
Build Version
2026.6.1 (21713)
What server are you connecting to?
Self-host
Self-host Server Version
I promise this is not relevant
Environment Details
- Device: Google Pixel 6
- Android version: 17
Issue Tracking Info
Steps To Reproduce
fdd4:aa51:eed9:426:9f99:938b:ebf9:7433, accessible viabitwarden.example.com, which you'll see the in stacktrace below.).https://bitwarden.example.com).SocketTimeoutException(full trace below).Expected Result
I expect to be able to log into my bitwarden server.
Actual Result
Here's the stack trace:
Screenshots or Videos
No response
Additional Context
The last version of the app where this worked was 2026.5.0. I tested 2026.6.1, 2026.6.0, and 2026.5.1 and confirmed they do not work.
I bisected and found that this was introduced by #6916, which upgraded the android sdk from 36 to 37. One consequence of sdk version 37 is that local network permission is now disabled by default. The bitwarden android app only requests local network access if the address we're connecting to is a "site local address", as returned by
InetAddress.isSiteLocalAddress. Here are the implementations for ipv4 and ipv6:However, I don't think the correct fix here is to just add "is ula?" to the check. I believe the intent of the code is to check "would android block connections to this ip address?", which android conveniently documents on their Local Network Definition page. Fun fact: this reveals that our ipv4 check is likely wrong as well (android blocks link local and CGNAT addresses, which are not detected by
Inet4Address.isSiteLocalAddress). The IPv6 check is even harder to implement, as it's not just a comparison against static subnets, it depends on your routing table.AFAICT, there's no easy way to implement this check (I've asked android for advice on this in https://issuetracker.google.com/issues/534973523). I think one correct way to do it is to just try and check for the error reason afterwards with
android_getnetworkblockedreason, but I guess that requires trying and waiting for a timeout, and it requires using the NDK.Perhaps the least odious approach would be to prompt the user for local network permission if they select "Self-hosted"? Or catching
SocketTimeoutExceptionand warning the user that they might need to grant the "Nearby devices" permission?Build Version
2026.6.1 (21713)
What server are you connecting to?
Self-host
Self-host Server Version
I promise this is not relevant
Environment Details
Issue Tracking Info