Reviving NAT-PMP in v2.x on Android 14+

Hi,

We’ve recently fixed the interface address discovery on Android by using the “anet” go library. Now there’s one problem left with the NAT-PMP feature of SyncthingNative. Android 14+ deny native code to read /proc/net/route. This approach is used by SyncthingNative in “lib/pmp.go:34” using the go lib “GitHub - jackpal/gateway: A golang library for discovering the address of a LAN gateway.”:

ip, err = gateway.DiscoverGateway()

I’ve tried to fix it in jackpal/gateway but could not find a solution. Multiple approaches don’t work from Android 14+, e.g. “ip route show”, “ip route get 8.8.8.8”, “cat /proc/net/…” or fake-binding a socket and reading out the info.

Android 14+ only lets java code get the gateway IPv4 address which is used in SynchtingNative’s NAT-PMP feature.

So I’ve added the java code to the wrapper, got the router IP address and feeded it to SynchtingNative by setting the env var “ANDROID_NET_GATEWAY_IPV4”.

This revives the NAT feature:

[Z36WU] INFO: Detected 1 NAT service

Function used based on the v2-main of SyncthingNative was:

func Discover(ctx context.Context, renewal, timeout time.Duration) []nat.Device {
	var ip net.IP
	err := svcutil.CallWithContext(ctx, func() error {
		var err error
		ip, err = gateway.DiscoverGateway()
		if err != nil {
			// Fails on Android 14+ due to permission denied error when reading
			// /proc/net/route. The wrapper may give a hint then because it is
			// able to discover the gateway from java code.
			if v := os.Getenv("ANDROID_NET_GATEWAY_IPV4"); v != "" {
				ip = net.ParseIP(v)
				l.Debugln("Using gateway IP hint from env var ANDROID_NET_GATEWAY_IPV4", ip)
				return nil
			}
		}
		return err
	})
	if err != nil {
		l.Debugln("Failed to discover gateway", err)
		return nil
	}
	if ip == nil || ip.IsUnspecified() {
		return nil
	}
(...)

Would you be fine with that workaround if I open a PR to Syncthing’s go code? It’s again a hacky workaround, but I personally think something lightweight is better than disabling the NAT feature completely for Android 14+.

What’s your opinion on this?

Should I also try to do the more-complicated-looking JNI binding from go code to java code?

More refs/reading on this:

It’s far from the worst thing I’ve seen. I’d hide it in a wrapper netutil.Gateway.

Absolutely not :sweat_smile:

2 Likes

You mean I should generate a “lib/netutil/gateway.go” in the code repo?

You can just put the function in the existing netutil.go

1 Like

PR updated :slight_smile:

I’m not sure how well this will work, given the value is set once, and phones are usually the things that migrate from network to network.

Perhaps a better option would be to specify a file you write it to and update it in the file periodically and get syncthing to read the file?

I’ve already discussed this on the PR. Tl;dr: later, according to user feedback and priority this will have.