go icon indicating copy to clipboard operation
go copied to clipboard

x/mobile: Calling net.InterfaceAddrs() fails on Android SDK 30

Open halseth opened this issue 4 years ago • 20 comments

What version of Go are you using (go version)?

$ go version
go version go1.14.6 darwin/amd64

$gomobile version
gomobile version +973feb4 Sat Aug 1 11:21:45 2020 +0000 (android,ios); androidSDK=/sdk/platforms/android-30

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/user/Library/Caches/go-build"
GOENV="/Users/user/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/user/golang"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.14.6/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.14.6/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/user/golang/src/golang.org/x/mobile/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/kq/3436m_v11sg0l7zqtmv2r1gw0000gn/T/go-build713467523=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Calling net.InterfaceAddrs() fails on Android app targetting SDK version 30. With build.gradle looking like:

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"
    defaultConfig {
        applicationId "com.example.testapp"
        minSdkVersion 29
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

Building the exact same app targetting SDK 29 works, and returns no error:

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"
    defaultConfig {
        applicationId "com.example.testapp"
        minSdkVersion 29
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

What did you expect to see?

Targetting SDK 30 would behave no differently.

What did you see instead?

Calling net.InterfaceAddrs() results in error route ip+net: netlinkrib: permission denied when embedded in Android app targetting SDK 30 (R):

2020-08-04 15:10:21.386 15754-15754/? W/Thread-2: type=1400 audit(0.0:616): avc: denied { bind } for scontext=u:r:untrusted_app:s0:c158,c256,c512,c768 tcontext=u:r:untrusted_app:s0:c158,c256,c512,c768 tclass=netlink_route_socket permissive=0 b/155595000 app=com.example.testapp

halseth avatar Aug 04 '20 13:08 halseth

/cc @hyangah

toothrot avatar Aug 04 '20 18:08 toothrot

Any more updates on this one? Android 11 was released today.

hsjoberg avatar Sep 08 '20 21:09 hsjoberg

Reproduced in Yggdrasil project for net.Interfaces(): https://github.com/yggdrasil-network/yggdrasil-go/blob/master/src/multicast/multicast.go#L192

vikulin avatar Oct 23 '20 14:10 vikulin

Any updates?

vikulin avatar Nov 21 '20 11:11 vikulin

I'd also love an update, if possible. This is blocking a project of mine right now.

rschulman avatar Feb 10 '21 17:02 rschulman

Anybody looking into this?

halseth avatar Mar 09 '21 14:03 halseth

Seems to be caused by these new restrictions in Android 11:

The following is a list of the ways that apps are affected by this change:

  • NetworkInterface.getHardwareAddress() returns null for every interface.
  • Apps cannot use the bind() function on NETLINK_ROUTE sockets.
  • The ip command does not return information about interfaces.
  • Apps cannot send RTM_GETLINK messages.

https://developer.android.com/training/articles/user-data-ids#mac-11-plus

bt90 avatar Mar 24 '21 19:03 bt90

Fix: https://github.com/Catfriend1/syncthing-android/pull/800

Catfriend1 avatar May 16 '21 17:05 Catfriend1

https://developer.android.com/distribute/best-practices/develop/target-sdk

In August 2021, new apps in the Google Play Store will be required to target API level 30. In November 2021, updates to existing apps in the Google Play Store will be required to target API level 30.

DentonGentry avatar Jul 03 '21 14:07 DentonGentry

Any updates on this one? This fix did not work for us: Catfriend1/syncthing-android#800

uburoiubu avatar Oct 07 '21 16:10 uburoiubu

Hello golang dev team, any update on this issue? This is blocking the Android 11 compatibility of my app :/. Thanks!

simbadMarino avatar Dec 09 '21 19:12 simbadMarino

We ended up developing an alternative using Android APIs, which we call instead of getInterfaceAddrs(). https://github.com/tailscale/tailscale-android/pull/21

DentonGentry avatar Dec 09 '21 20:12 DentonGentry

Great, will take a look and see if I can circumvent my issue with this . Thanks!

Sent from myMail for iOS

Thursday, 9 December 2021 14:39 -0600 from @.*** @.***>:

We ended up developing an alternative using Android APIs, which we call instead of getInterfaceAddrs(). tailscale/tailscale-android#21 — You are receiving this because you commented. Reply to this email directly, view it on GitHub , or unsubscribe . Triage notifications on the go with GitHub Mobile for iOS or Android .

simbadMarino avatar Dec 10 '21 03:12 simbadMarino

hello, any update on this?

I'm looking to use a go library inside an android app targeting API level 32. Code crashes on devices with API level >= 30 because of this reason (route ip+net: netlinkrib: permission denied).

I could downgrade to 29, but it's a hack. Go runtime should implement a workaround to fix the issue.

andob avatar Feb 24 '22 17:02 andob

Hello there, no, I decided to go for the “ wait until golang devs fix it” route. If I go for fixing it myself I’ll end up maintaining too many dependencies forks. If your app is being published on Google Play you have no choice, you have to find a work around , if not, then just downgrade target api to 29 and you will be fine, not a hack actually , it’s still allowed ;)

And I agree this needs to be fixed from go runtime itself . Hope someone skilled enough can help with a fix/workaround soon 😬. Maybe we should start a bounty on this one hahaha 

Sent from myMail for iOS

Thursday, 24 February 2022 11:46 -0600 from @.*** @.>: @. hello, any update on this?

I'm looking to use a go library inside an android app targeting API level 32. Code crashes on devices with API level >= 30 because of this reason (route ip+net: netlinkrib: permission denied). I could downgrade to 29, but it's a hack. Go runtime should implement a workaround to fix the issue. — Reply to this email directly, view it on GitHub , or unsubscribe . Triage notifications on the go with GitHub Mobile for iOS or Android . You are receiving this because you were mentioned. Message ID: <golang/go/issues/40569/1050103943 @ github . com>

simbadMarino avatar Feb 25 '22 00:02 simbadMarino

Here is how I fix this x/mobile: Calling net.InterfaceAddrs() fails on Android SDK 30 issue to resolve IPFS in mobile issue like route ip+net: netlinkrib: permission denied with x/mobile/cmd/gomobile as compile tools , ref to cmd/tailscale: implement getInterfaces + SDK 30 which use gioui.org as compile tools.

  • In MainApplication.java

Take react native project for example, in YOUR_PROJECT/android/app/src/main/AndroidManifest.xml

    <application
      android:name=".MainApplication"

so be the MainApplication.java here.

import go.Seq;
...
  @Override
  public void onCreate() {
    super.onCreate();
    ...

    // setContext here, so that if RunOnJVM() with golang.org/x/mobile/app to call JAVA from GO,
    // will not cause error "no current JVM"
    Seq.setContext(getApplicationContext());
  }
...
}

And also in MainApplication.java

import java.lang.StringBuilder;

import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
...
  // To fix [x/mobile: Calling net.InterfaceAddrs() fails on Android SDK 30](https://github.com/golang/go/issues/40569)
  // Ref to getInterfaces() in https://github.com/tailscale/tailscale-android/pull/21/files
  //
  // Returns details of the interfaces in the system, encoded as a single string for ease
  // of JNI transfer over to the Go environment.
  //
  // Example:
  // rmnet_data0 10 2000 true false false false false | fe80::4059:dc16:7ed3:9c6e%rmnet_data0/64
  // dummy0 3 1500 true false false false false | fe80::1450:5cff:fe13:f891%dummy0/64
  // wlan0 30 1500 true true false false true | fe80::2f60:2c82:4163:8389%wlan0/64 10.1.10.131/24
  // r_rmnet_data0 21 1500 true false false false false | fe80::9318:6093:d1ad:ba7f%r_rmnet_data0/64
  // rmnet_data2 12 1500 true false false false false | fe80::3c8c:44dc:46a9:9907%rmnet_data2/64
  // r_rmnet_data1 22 1500 true false false false false | fe80::b6cd:5cb0:8ae6:fe92%r_rmnet_data1/64
  // rmnet_data1 11 1500 true false false false false | fe80::51f2:ee00:edce:d68b%rmnet_data1/64
  // lo 1 65536 true false true false false | ::1/128 127.0.0.1/8
  // v4-rmnet_data2 68 1472 true true false true true | 192.0.0.4/32
  //
  // Where the fields are:
  // name ifindex mtu isUp hasBroadcast isLoopback isPointToPoint hasMulticast | ip1/N ip2/N ip3/N;
  String getInterfacesAsString() {
    List<NetworkInterface> interfaces;
    try {
      interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
    } catch (Exception e) {
      return "";
    }

    StringBuilder sb = new StringBuilder("");
    for (NetworkInterface nif : interfaces) {
      try {
        // Android doesn't have a supportsBroadcast() but the Go net.Interface wants
        // one, so we say the interface has broadcast if it has multicast.
        sb.append(String.format("%s %d %d %b %b %b %b %b |", nif.getName(),
                       nif.getIndex(), nif.getMTU(), nif.isUp(), nif.supportsMulticast(),
                       nif.isLoopback(), nif.isPointToPoint(), nif.supportsMulticast()));

        for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
          // InterfaceAddress == hostname + "/" + IP
          String[] parts = ia.toString().split("/", 0);
          if (parts.length > 1) {
            sb.append(String.format("%s/%d ", parts[1], ia.getNetworkPrefixLength()));
          }
        }
      } catch (Exception e) {
        // TODO(dgentry) should log the exception not silently suppress it.
        continue;
      }
      sb.append("\n");
    }

    return sb.toString();
  }
  • In go.mod

Add

	git.wow.st/gmp/jni v0.0.0-20200827154156-014cd5c7c4c0
	inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1
  • In CLI
go mod download

Above will download git.wow.st/gmp/jni and inet.af/netaddr into ~/go/pkg/mod/ , and modify go.mod automatically.

go mod vendor

Above will remove vendor/ automatically then copy again from ~/go/pkg/mod/ .

If go mod download modified go.mod into

	git.wow.st/gmp/jni v0.0.0-20200827154156-014cd5c7c4c0 // indirect
	inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1 // indirect

then they will not be copied into vendor/ by go mod vendor , so you can temporarily add

	"git.wow.st/gmp/jni"
	"inet.af/netaddr"

into one of your own .go file, run go mod download again, found // indirect disappeared, reset your own .go file, run go mod vendor again, found they were copied into vendor/ .

  • In vendor/github.com/multiformats/go-multiaddr/net/net.go

Replace

import (
	"context"
	"fmt"
	"net"

	ma "github.com/multiformats/go-multiaddr"
)

with

import (
	"context"
	"errors"
	"fmt"
	"net"
	"runtime"
	"strings"

	"git.wow.st/gmp/jni"
	"golang.org/x/mobile/app"
	"inet.af/netaddr"

	ma "github.com/multiformats/go-multiaddr"
)

And also in vendor/github.com/multiformats/go-multiaddr/net/net.go, replace

// InterfaceMultiaddrs will return the addresses matching net.InterfaceAddrs
func InterfaceMultiaddrs() ([]ma.Multiaddr, error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return nil, err
	}

	maddrs := make([]ma.Multiaddr, len(addrs))
	for i, a := range addrs {
		maddrs[i], err = FromNetAddr(a)
		if err != nil {
			return nil, err
		}
	}
	return maddrs, nil
}

with

// InterfaceMultiaddrs will return the addresses matching net.InterfaceAddrs
func InterfaceMultiaddrs() ([]ma.Multiaddr, error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		if runtime.GOOS == "android" {
			// To fix [x/mobile: Calling net.InterfaceAddrs() fails on Android SDK 30](https://github.com/golang/go/issues/40569)
			addrs, err = getInterfaceAddrsFromAndroid()
			if err != nil {
				return nil, err
			}
		} else {
			return nil, err
		}
	}

	maddrs := make([]ma.Multiaddr, len(addrs))
	for i, a := range addrs {
		maddrs[i], err = FromNetAddr(a)
		if err != nil {
			return nil, err
		}
	}
	return maddrs, nil
}

// Ref to getInterfaces() in https://github.com/tailscale/tailscale-android/pull/21/files
func getInterfaceAddrsFromAndroid() ([]net.Addr, error) {
	var ifaceString string

	// if use "gioui.org/app", ref to jni.Do() in https://github.com/tailscale/tailscale-android/pull/21/files

	// if use "golang.org/x/mobile/app", use app.RunOnJVM() below
	err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
		jniEnv := jni.EnvFor(env)

		// cls := jni.FindClass(jniEnv, "YOUR/PACKAGE/NAME/CLASSNAME")
		// m := jni.GetMethodID(jniEnv, cls, "getInterfacesAsString", "()Ljava/lang/String;")
		// n, err := jni.CallStaticObjectMethod(jniEnv, cls, m)

		// above `YOUR.PACKAGE.NAME` `CLASSNAME.java` sometimes will cause strange [java.lang.ClassNotFoundException: Didn't find class on path: dexpathlist](https://stackoverflow.com/questions/22399572/java-lang-classnotfoundexception-didnt-find-class-on-path-dexpathlist)
		// so use below `MainApplication.java` comes from `<application android:name=".MainApplication"` in `YOUR_PROJECT/android/app/src/main/AndroidManifest.xml`

		appCtx := jni.Object(ctx)
		cls := jni.GetObjectClass(jniEnv, appCtx)
		m := jni.GetMethodID(jniEnv, cls, "getInterfacesAsString", "()Ljava/lang/String;")
		n, err := jni.CallObjectMethod(jniEnv, appCtx, m)

		if err != nil {
			return errors.New("getInterfacesAsString Method invocation failed")
		}
		ifaceString = jni.GoString(jniEnv, jni.String(n))
		return nil
	})

	if err != nil {
		return nil, err
	}

	var ifat []net.Addr
	for _, iface := range strings.Split(ifaceString, "\n") {
		// Example of the strings we're processing:
		// wlan0 30 1500 true true false false true | fe80::2f60:2c82:4163:8389%wlan0/64 10.1.10.131/24
		// r_rmnet_data0 21 1500 true false false false false | fe80::9318:6093:d1ad:ba7f%r_rmnet_data0/64
		// mnet_data2 12 1500 true false false false false | fe80::3c8c:44dc:46a9:9907%rmnet_data2/64

		if strings.TrimSpace(iface) == "" {
			continue
		}

		fields := strings.Split(iface, "|")
		if len(fields) != 2 {
			// log.Printf("getInterfaces: unable to split %q", iface)
			continue
		}

		addrs := strings.Trim(fields[1], " \n")
		for _, addr := range strings.Split(addrs, " ") {
			ip, err := netaddr.ParseIPPrefix(addr)
			if err == nil {
				ifat = append(ifat, ip.IPNet())
			}
		}
	}

		return ifat, nil
}
  • In CLI
    export ANDROID_NDK_HOME=~/tools/android-sdk/ndk/21.4.7075529

or

    cd ~/tools/android-sdk/ndk
    ln -s 21.4.7075529 ndk-bundle

21.4.7075529 is default in RN 0.66 and gradle 6.7.1

If r22 or higher, will cause

# golang.org/x/mobile/app
ld: error: duplicate symbol: display

just like x/mobile/cmd/gomobile: gomobile build on simple program returns "ld: error: duplicate symbol: x_cgo_inittls"

Finally, you can continue your gomobile bind work flow :yum:

PS: notice java.util.Locale.ROOT as described below https://github.com/golang/go/issues/40569#issuecomment-1191823859

flyskywhy avatar Jul 21 '22 02:07 flyskywhy

I did something similar for IPFS on Android as well, the project is https://github.com/AgregoreWeb/agregore-ipfs-daemon

Android Java code is here: https://github.com/AgregoreWeb/agregore-ipfs-daemon/blob/2a0798197769772be810947f2451c10fca03d1ca/get_interfaces.java

Parsing of interfaces string from Java is here: https://github.com/AgregoreWeb/agregore-ipfs-daemon/blob/2a0798197769772be810947f2451c10fca03d1ca/gateway/interfaces.go#L24

makew0rld avatar Jul 21 '22 17:07 makew0rld

For anyone using the technique of having Java code pass a string with an interface list over to the Go code, we did find an issue after some time in the field.

For an Android phone in a Locale like Saudi Arabia which uses Hindu-Arabic numerals, the Java code will automatically format the decimal numbers using the locale-appropriate glyphs:

lo ١ ٦٥٥٣٦ true false true false false |

The receiving Go code cannot handle this. As these strings are purely internal to pass information between the Java runtime and Go environment, they will never be visible to the user. We addressed it by setting the locale in the Java code to always format the string in a way which the Go code can handle:

sb.append(String.format(java.util.Locale.ROOT, "%s %d %d %b %b %b %b %b |", nif.getName(),

The full PR to address it is in https://github.com/tailscale/tailscale-android/commit/fd42b4b3526a33338e6fa3f38ce332380139a860, the App.java file contains the relevant changes. We had also used a similar technique for DNS which required a similar fix but is not likely used in your system.

DentonGentry avatar Jul 21 '22 18:07 DentonGentry

I did something similar for IPFS on Android as well, the project is https://github.com/AgregoreWeb/agregore-ipfs-daemon

Android Java code is here: https://github.com/AgregoreWeb/agregore-ipfs-daemon/blob/2a0798197769772be810947f2451c10fca03d1ca/get_interfaces.java

Parsing of interfaces string from Java is here: https://github.com/AgregoreWeb/agregore-ipfs-daemon/blob/2a0798197769772be810947f2451c10fca03d1ca/gateway/interfaces.go#L24

FYI @makeworld-the-better-one, in your code you replace the address parsing func: instead of using netaddr.ParseIPPrefix() you use net.ParseCIDR(). In my testing I found it did not give the same/desired result, however. For example parsing net.ParseCIDR("10.150.6.112/16") results in 10.150.0.0/16.

ssalenik avatar Aug 19 '22 20:08 ssalenik

I was able to work around this issue by calling getifaddrs via cgo, which was preferable to using JNI for me. getifaddrs is available in Android 7.0+.

Code in the gist here replicates most but not all of the stdlib functionality. Some data isn't populated. https://gist.github.com/iamcalledrob/67b710b1ca09465b906f04b91bb56e1f

iamcalledrob avatar Oct 27 '22 23:10 iamcalledrob

Here is how I fix this x/mobile: Calling net.InterfaceAddrs() fails on Android SDK 30 issue to resolve IPFS in mobile issue like route ip+net: netlinkrib: permission denied with x/mobile/cmd/gomobile as compile tools , ref to cmd/tailscale: implement getInterfaces + SDK 30 which use gioui.org as compile tools.

  • In MainApplication.java

Take react native project for example, in YOUR_PROJECT/android/app/src/main/AndroidManifest.xml

    <application
      android:name=".MainApplication"

so be the MainApplication.java here.

import go.Seq;

@flyskywhy Hello there! I'm trying to solve the issue with your solution but Android Studio is giving me an error when importing go.Seq, maybe a stupid question but could not find an answer on google... how did you import the go.Seq dependence?

simbadMarino avatar Nov 02 '22 00:11 simbadMarino

I was able to work around this issue by calling getifaddrs via cgo

Brilliant! Inspired by your work, I looked into how Android implements getifaddrs, and found that RTM_GETADDR is used for non-system apps as RTM_GETLINK is forbidden [1]. On the other hand, there are already some code using RTM_GETADDR in Go [2]. I collected relevant functions, removed usage of forbidden RTM_GETLINK and got a working version:

Pure-Go InterfaceAddrs() for newer Android
import (
	"net"
	"os"
	"syscall"
	"unsafe"
)

// NetlinkRouteRequest represents a request message to receive routing
// and link states from the kernel.
type NetlinkRouteRequest struct {
	Header syscall.NlMsghdr
	Data   syscall.RtGenmsg
}

func (rr *NetlinkRouteRequest) toWireFormat() []byte {
	b := make([]byte, rr.Header.Len)
	*(*uint32)(unsafe.Pointer(&b[0:4][0])) = rr.Header.Len
	*(*uint16)(unsafe.Pointer(&b[4:6][0])) = rr.Header.Type
	*(*uint16)(unsafe.Pointer(&b[6:8][0])) = rr.Header.Flags
	*(*uint32)(unsafe.Pointer(&b[8:12][0])) = rr.Header.Seq
	*(*uint32)(unsafe.Pointer(&b[12:16][0])) = rr.Header.Pid
	b[16] = byte(rr.Data.Family)
	return b
}

func newNetlinkRouteRequest(proto, seq, family int) []byte {
	rr := &NetlinkRouteRequest{}
	rr.Header.Len = uint32(syscall.NLMSG_HDRLEN + syscall.SizeofRtGenmsg)
	rr.Header.Type = uint16(proto)
	rr.Header.Flags = syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST
	rr.Header.Seq = uint32(seq)
	rr.Data.Family = uint8(family)
	return rr.toWireFormat()
}

// NetlinkRIB returns routing information base, as known as RIB, which
// consists of network facility information, states and parameters.
func NetlinkRIB(proto, family int) ([]byte, error) {
	s, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW|syscall.SOCK_CLOEXEC, syscall.NETLINK_ROUTE)
	if err != nil {
		return nil, err
	}
	defer syscall.Close(s)
	sa := &syscall.SockaddrNetlink{Family: syscall.AF_NETLINK}
	wb := newNetlinkRouteRequest(proto, 1, family)
	if err := syscall.Sendto(s, wb, 0, sa); err != nil {
		return nil, err
	}
	lsa, err := syscall.Getsockname(s)
	if err != nil {
		return nil, err
	}
	lsanl, ok := lsa.(*syscall.SockaddrNetlink)
	if !ok {
		return nil, syscall.EINVAL
	}
	var tab []byte
	rbNew := make([]byte, syscall.Getpagesize())
done:
	for {
		rb := rbNew
		nr, _, err := syscall.Recvfrom(s, rb, 0)
		if err != nil {
			return nil, err
		}
		if nr < syscall.NLMSG_HDRLEN {
			return nil, syscall.EINVAL
		}
		rb = rb[:nr]
		tab = append(tab, rb...)
		msgs, err := syscall.ParseNetlinkMessage(rb)
		if err != nil {
			return nil, err
		}
		for _, m := range msgs {
			if m.Header.Seq != 1 || m.Header.Pid != lsanl.Pid {
				return nil, syscall.EINVAL
			}
			if m.Header.Type == syscall.NLMSG_DONE {
				break done
			}
			if m.Header.Type == syscall.NLMSG_ERROR {
				return nil, syscall.EINVAL
			}
		}
	}
	return tab, nil
}


func newAddr(ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRouteAttr) net.Addr {
	var ipPointToPoint bool
	// Seems like we need to make sure whether the IP interface
	// stack consists of IP point-to-point numbered or unnumbered
	// addressing.
	for _, a := range attrs {
		if a.Attr.Type == syscall.IFA_LOCAL {
			ipPointToPoint = true
			break
		}
	}
	for _, a := range attrs {
		if ipPointToPoint && a.Attr.Type == syscall.IFA_ADDRESS {
			continue
		}
		switch ifam.Family {
		case syscall.AF_INET:
			return &net.IPNet{IP: net.IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3]), Mask: net.CIDRMask(int(ifam.Prefixlen), 8*net.IPv4len)}
		case syscall.AF_INET6:
			ifa := &net.IPNet{IP: make(net.IP, net.IPv6len), Mask: net.CIDRMask(int(ifam.Prefixlen), 8*net.IPv6len)}
			copy(ifa.IP, a.Value[:])
			return ifa
		}
	}
	return nil
}

func addrTable(msgs []syscall.NetlinkMessage) ([]net.Addr, error) {
	var ifat []net.Addr
loop:
	for _, m := range msgs {
		switch m.Header.Type {
		case syscall.NLMSG_DONE:
			break loop
		case syscall.RTM_NEWADDR:
			ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0]))
			attrs, err := syscall.ParseNetlinkRouteAttr(&m)
			if err != nil {
				return nil, os.NewSyscallError("parsenetlinkrouteattr", err)
			}
			ifa := newAddr(ifam, attrs)
			if ifa != nil {
				ifat = append(ifat, ifa)
			}
		}
	}
	return ifat, nil
}

func interfaceAddrTable() ([]net.Addr, error) {
	tab, err := NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC)
	if err != nil {
		return nil, os.NewSyscallError("netlinkrib", err)
	}
	msgs, err := syscall.ParseNetlinkMessage(tab)
	if err != nil {
		return nil, os.NewSyscallError("parsenetlinkmessage", err)
	}
	ifat, err := addrTable(msgs)
	if err != nil {
		return nil, err
	}
	return ifat, nil
}

func InterfaceAddrs() ([]net.Addr, error) {
	ifat, err := interfaceAddrTable()
	if err != nil {
		err = &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
	}
	return ifat, err
}

[1] https://android.googlesource.com/platform/bionic/+/refs/tags/android-13.0.0_r18/libc/bionic/ifaddrs.cpp#315 [2] https://github.com/golang/go/blob/go1.20rc1/src/net/interface_linux.go#L124

yan12125 avatar Dec 17 '22 13:12 yan12125

I was able to work around this issue by calling getifaddrs via cgo

Brilliant! Inspired by your work, I looked into how Android implements getifaddrs, and found that RTM_GETADDR is used for non-system apps as RTM_GETLINK is forbidden [1]. On the other hand, there are already some code using RTM_GETADDR in Go [2]. I collected relevant functions, removed usage of forbidden RTM_GETLINK and got a working version:

Pure-Go InterfaceAddrs() for newer Android [1] https://android.googlesource.com/platform/bionic/+/refs/tags/android-13.0.0_r18/libc/bionic/ifaddrs.cpp#315 [2] https://github.com/golang/go/blob/go1.20rc1/src/net/interface_linux.go#L124

I do it samething follew your ,but it does't work on ipfs kubo.

yianding avatar Jan 16 '23 10:01 yianding

@yan12125 I haven't tested your code, but this approach looks great.

Unfortunately any of our workarounds don't work when using 3rd party libraries that call net.Interfaces() directly. This is a bit of a showstopper for a lot of networking code on Android.

I wonder if the net/interface pkg in stdlib can be updated to use these different syscalls for Android?

iamcalledrob avatar Jan 17 '23 23:01 iamcalledrob

it does't work on ipfs kubo.

Any error meesages?


I wonder if the net/interface pkg in stdlib can be updated to use these different syscalls for Android?

I believe so. My example is modified from existing functions in stdlib, so those modifications should be compatible with stdlib. I can give it a try and create a pull request after finding time on reading contribution guidelines.

yan12125 avatar Jan 18 '23 12:01 yan12125

Sorry I made a mistake in my previous example - I tested on an app targeting older SDK. I updated the example after testing on newer SDK.

The key difference in the updated example is that I copied codes from syscall.NetlinkRIB and remove the Bind call on the netlink socket, which is forbidden by new Android. I assume binding is not necessary for net.InterfaceAddrs() , but maybe necessary for some other scenarios. I'm not sure how to integrate such a change into Go stdlib. Maybe a new system call or a new argument for syscall.NetlinkRIB?

yan12125 avatar Jan 19 '23 07:01 yan12125

Sorry I made a mistake in my previous example - I tested on an app targeting older SDK. I updated the example after testing on newer SDK.

The key difference in the updated example is that I copied codes from syscall.NetlinkRIB and remove the Bind call on the netlink socket, which is forbidden by new Android. I assume binding is not necessary for net.InterfaceAddrs() , but maybe necessary for some other scenarios. I'm not sure how to integrate such a change into Go stdlib. Maybe a new system call or a new argument for syscall.NetlinkRIB?

Would be great if you could do the PR. Wishing you the best of luck!

simbadMarino avatar Jan 23 '23 17:01 simbadMarino

Thanks! But I'm not sure how to integrate those changes into stdlib in an elegant way as I've mentioned above.

yan12125 avatar Jan 24 '23 03:01 yan12125

Sorry I made a mistake in my previous example - I tested on an app targeting older SDK. I updated the example after testing on newer SDK.

The key difference in the updated example is that I copied codes from syscall.NetlinkRIB and remove the Bind call on the netlink socket, which is forbidden by new Android. I assume binding is not necessary for net.InterfaceAddrs() , but maybe necessary for some other scenarios. I'm not sure how to integrate such a change into Go stdlib. Maybe a new system call or a new argument for syscall.NetlinkRIB?

Could you provide more detail of remove bind?

yiweichi avatar Jan 31 '23 03:01 yiweichi

Could you provide more detail of remove bind?

I removed https://github.com/golang/go/blob/go1.20rc1/src/syscall/netlink_linux.go#L59-L61, which violate the second rule mentioned at https://github.com/golang/go/issues/40569#issuecomment-806080888:

  • Apps cannot use the bind() function on NETLINK_ROUTE sockets.

As a result, the code fails on newer Android.

yan12125 avatar Feb 01 '23 16:02 yan12125