https://bugs.kde.org/show_bug.cgi?id=520110

            Bug ID: 520110
           Summary: KDE Connect Android rejects explicit tailnet peers
                    delivered as loopback by userspace VPNs
    Classification: Applications
           Product: kdeconnect
      Version First unspecified
       Reported In:
          Platform: Android
                OS: Android 14.x
            Status: REPORTED
          Severity: normal
          Priority: NOR
         Component: android-application
          Assignee: [email protected]
          Reporter: [email protected]
                CC: [email protected]
  Target Milestone: ---

This is related to, but distinct from, Bug 515707 (the fixed 100.64.0.0/10 /
ULA filtering regression).

I tested KDE Connect Android over a Tailscale-compatible tailnet endpoint
provided by sing-box on Android. The setup was network-reachable:

- Android phone had a tailnet IP in 100.64.0.0/10.
- Desktop had a tailnet IP, e.g. 100.64.0.1.
- The Android app had the desktop added through Add devices by IP.
- SSH from the phone to the desktop tailnet IP worked.
- Packet capture on the desktop showed TCP traffic reaching the phone on KDE
Connect port 1716.

However, KDE Connect Android still rejected the connection before identity /
TLS completed. After instrumenting LanLinkProvider, the key observation was
that the Java ServerSocket did not see the peer as 100.64.x.x. The accepted
socket appeared as loopback instead:

TCP listener accepted raw socket from /127.0.0.1:xxxxx to /127.0.0.1:1716
TCP connection accepted ... private:false explicitPeer:false
Discarding TCP packet from a non-local IP /127.0.0.1

A manual probe reproduced the same at the Android socket layer: although the
connection was made to the phone tailnet IP, ss on Android showed the app-side
connection as 127.0.0.1 -> 127.0.0.1:1716.

So this is a different failure mode from rejecting 100.64.0.0/10: some Android
userspace VPN/tailnet/proxy implementations may deliver inbound peer TCP to the
app as loopback. In that case, even if the peer was explicitly configured in
Add devices by IP, tcpPacketReceived() cannot match the configured 100.64.x.x
address anymore, because the app only sees 127.0.0.1.

The narrow fix I tested successfully in a fork was conceptually:

boolean privateAddress = isPrivateAddress(address);
boolean loopbackProxyPeer = address.isLoopbackAddress() &&
hasConfiguredCustomDevices();
boolean explicitPeer = isConfiguredCustomDevice(address) || loopbackProxyPeer;
if (!privateAddress && !explicitPeer) {
    Log.i("LanLinkProvider", "Discarding TCP packet from a non-local IP " +
address);
    return;
}

with:

private boolean hasConfiguredCustomDevices() {
    return !CustomDevicesActivity.getCustomDeviceList(context).isEmpty();
}

This intentionally does not blindly trust all loopback traffic. It only treats
loopback inbound as a possible userspace tailnet/proxy entry path when the user
has configured custom devices, i.e. when the user has already opted into
explicit peers.

Expected behavior:

If a peer was explicitly configured through Add devices by IP, KDE Connect
Android should not reject a userspace-VPN-delivered inbound TCP connection
solely because the app-visible socket source is loopback.

Actual behavior:

The connection is discarded before the identity packet is processed because
/127.0.0.1 is neither treated as private nor as the configured 100.64.x.x peer.

References:

- Related fixed regression: Bug 515707
- Merged KDE fix for CGNAT/ULA address classes:
https://invent.kde.org/network/kdeconnect-android/-/merge_requests/625
- Public discussion where I also left this as a data point:
https://github.com/tailscale/tailscale/issues/14476#issuecomment-4441788725

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to