golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.99k stars 17.67k forks source link

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

Open halseth opened 4 years ago

halseth commented 4 years ago

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
toothrot commented 4 years ago

/cc @hyangah

hsjoberg commented 4 years ago

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

vikulin commented 4 years ago

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

vikulin commented 3 years ago

Any updates?

rschulman commented 3 years ago

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

halseth commented 3 years ago

Anybody looking into this?

bt90 commented 3 years ago

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

Catfriend1 commented 3 years ago

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

DentonGentry commented 3 years ago

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.

uburoiubu commented 3 years ago

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

simbadMarino commented 2 years ago

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

DentonGentry commented 2 years ago

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

simbadMarino commented 2 years ago

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 .

andob commented 2 years ago

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.

simbadMarino commented 2 years ago

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>

flyskywhy commented 2 years ago

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.

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

Add

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

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

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
}

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

makew0rld commented 2 years ago

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

DentonGentry commented 2 years ago

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.

ssalenik commented 2 years ago

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.

iamcalledrob commented 2 years ago

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

simbadMarino commented 2 years ago

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?

yan12125 commented 1 year ago

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 ```Go 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

yianding commented 1 year ago

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.

iamcalledrob commented 1 year ago

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

yan12125 commented 1 year ago

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 commented 1 year ago

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?

simbadMarino commented 1 year ago

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!

yan12125 commented 1 year ago

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

yiweichi commented 1 year ago

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?

yan12125 commented 1 year ago

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.

yianding commented 1 year ago

any update?

yiweichi commented 1 year ago

I did resovled this problem. No need java code, only need change some code of golang lib source code.

yiweichi commented 1 year ago

My android app intergrated ipfs ,but can not run on Android 13, this problem bother me for serveral months, I did resolve this problem yesterday.

simbadMarino commented 1 year ago

@yiweichi , would you please explain how you did it? I'm trying with several proposals from others here but still struggling to make it work. I would like to have a NO JAVA code solution like you did :)

lovishpuri commented 1 year ago

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

@yan12125 You're calling linux specific syscall constants (processor specific too I think). Can't work with this code on macOS (darwin syscall is different). There should be a platform independent way to implement this logic.

yan12125 commented 1 year ago

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

@yan12125 You're calling linux specific syscall constants (processor specific too I think). Can't work with this code on macOS (darwin syscall is different). There should be a platform independent way to implement this logic.

It should work. Building Android apps on macOS will use Linux codes instead of Darwin ones.

lovishpuri commented 1 year ago

@yan12125 I need to run and test the go library separately, this go code doesn't compile on macOS. Is there a tag I can add to avoid using this code when compiling in darwin.

yan12125 commented 1 year ago

You probably want something like // +build linux.

lovishpuri commented 1 year ago

@yan12125 Doesn't compile in editor (vscode) with build tags. How can I modify the go/src/net and /syscall libraries in my project like you did in Work-around ?

ehsan6sha commented 1 year ago

My android app intergrated ipfs ,but can not run on Android 13, this problem bother me for serveral months, I did resolve this problem yesterday.

can you please share your solution on how you resolved it?

yan12125 commented 1 year ago

@yan12125 Doesn't compile in editor (vscode) with build tags. How can I modify the go/src/net and /syscall libraries in my project like you did in Work-around ?

That commit is for go itself, not applications. Please check InterfaceAddrs function in https://github.com/golang/go/issues/40569#issuecomment-1356266758 instead.

lovishpuri commented 1 year ago

@yan12125 Can't use InterfaceAddrs function you shared in comment. I need to compile and test my go code on macOS before I can use it in my android app . Can you open/ ask for a pull request for the changes in work-around ?

alexballas commented 1 year ago

In my test I saw that even if you set targetSdkVersion to 29, you'll still get the "route ip+net: netlinkrib: permission denied" error in Android 13+. Previous Android versions work OK.

Mc231 commented 1 year ago

Any updates?

simbadMarino commented 1 year ago

Hello @yan12125 , I'm finally following your recommendation, downloaded go source code and modified both the netling_linux (removing the bind call) and replaced the content from interface_linux with your code, but I'm having the following issue while compiling from source in macOS:

ok      go/ast  0.349s
--- FAIL: TestDependencies (1.32s)
    deps_test.go:639: unexpected dependency: net imports [net]
FAIL
FAIL    go/build    1.769s

Looks like the test is having a conflict with the net import. Did you do anything else beyond modifying such files?

Thanks!

yan12125 commented 1 year ago

@simbadMarino could you try this patch? It works with Go 1.21 rc1 source.

diff --git a/src/net/interface_linux.go b/src/net/interface_linux.go
index 9112ecc854c74..02b5a6047ed93 100644
--- a/src/net/interface_linux.go
+++ b/src/net/interface_linux.go
@@ -6,6 +6,7 @@ package net

 import (
    "os"
+   "runtime"
    "syscall"
    "unsafe"
 )
@@ -133,7 +134,7 @@ func interfaceAddrTable(ifi *Interface) ([]Addr, error) {
    if ifi == nil {
        var err error
        ift, err = interfaceTable(0)
-       if err != nil {
+       if err != nil && (runtime.GOOS != "android" || err != syscall.EACCES) {
            return nil, err
        }
    }
@@ -153,6 +154,18 @@ loop:
            break loop
        case syscall.RTM_NEWADDR:
            ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0]))
+           if len(ift) == 0 && ifi == nil {
+               // if len(ift) == 0, the restriction on Android API 30+ applies, and RTM_GETLINK messages are prohibited
+               // Only querying all interfaces (ifi == nil) makes sense in this case.
+               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)
+               }
+           }
            if len(ift) != 0 || ifi.Index == int(ifam.Index) {
                if len(ift) != 0 {
                    var err error
diff --git a/src/syscall/netlink_linux.go b/src/syscall/netlink_linux.go
index a503a0744005b..c4fba399a710d 100644
--- a/src/syscall/netlink_linux.go
+++ b/src/syscall/netlink_linux.go
@@ -7,6 +7,7 @@
 package syscall

 import (
+   "runtime"
    "sync"
    "unsafe"
 )
@@ -65,7 +66,9 @@ func NetlinkRIB(proto, family int) ([]byte, error) {
    defer Close(s)
    sa := &SockaddrNetlink{Family: AF_NETLINK}
    if err := Bind(s, sa); err != nil {
-       return nil, err
+       if runtime.GOOS != "android" || err != EACCES {
+           return nil, err
+       }
    }
    wb := newNetlinkRouteRequest(proto, 1, family)
    if err := Sendto(s, wb, 0, sa); err != nil {
simbadMarino commented 1 year ago

@simbadMarino could you try this patch? It works with Go 1.21 rc1 source.

diff --git a/src/net/interface_linux.go b/src/net/interface_linux.go
index 9112ecc854c74..02b5a6047ed93 100644
--- a/src/net/interface_linux.go
+++ b/src/net/interface_linux.go
@@ -6,6 +6,7 @@ package net

 import (
  "os"
+ "runtime"
  "syscall"
  "unsafe"
 )
@@ -133,7 +134,7 @@ func interfaceAddrTable(ifi *Interface) ([]Addr, error) {
  if ifi == nil {
      var err error
      ift, err = interfaceTable(0)
-     if err != nil {
+     if err != nil && (runtime.GOOS != "android" || err != syscall.EACCES) {
          return nil, err
      }
  }
@@ -153,6 +154,18 @@ loop:
          break loop
      case syscall.RTM_NEWADDR:
          ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0]))
+         if len(ift) == 0 && ifi == nil {
+             // if len(ift) == 0, the restriction on Android API 30+ applies, and RTM_GETLINK messages are prohibited
+             // Only querying all interfaces (ifi == nil) makes sense in this case.
+             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)
+             }
+         }
          if len(ift) != 0 || ifi.Index == int(ifam.Index) {
              if len(ift) != 0 {
                  var err error
diff --git a/src/syscall/netlink_linux.go b/src/syscall/netlink_linux.go
index a503a0744005b..c4fba399a710d 100644
--- a/src/syscall/netlink_linux.go
+++ b/src/syscall/netlink_linux.go
@@ -7,6 +7,7 @@
 package syscall

 import (
+ "runtime"
  "sync"
  "unsafe"
 )
@@ -65,7 +66,9 @@ func NetlinkRIB(proto, family int) ([]byte, error) {
  defer Close(s)
  sa := &SockaddrNetlink{Family: AF_NETLINK}
  if err := Bind(s, sa); err != nil {
-     return nil, err
+     if runtime.GOOS != "android" || err != EACCES {
+         return nil, err
+     }
  }
  wb := newNetlinkRouteRequest(proto, 1, family)
  if err := Sendto(s, wb, 0, sa); err != nil {

Thanks my friend, will give it a try tonight!

simbadMarino commented 1 year ago

@yan12125 looks like its working! I can finally run BTFS on Android which is a go-ipfs fork, meaning those willing to run ipfs on android might find this patch handy, I tested with targetSdkVersion =30 & 33 and so far so good, thanks so much!! :)

ehsan6sha commented 1 year ago

@yan12125 looks like its working! I can finally run BTFS on Android which is a go-ipfs fork, meaning those willing to run ipfs on android might find this patch handy, I tested with targetSdkVersion =30 & 33 and so far so good, thanks so much!! :)

@simbadMarino Can you please elaborate on the full steps you took? I mean which files you modified, etc ? Sorry for being a noob here

simbadMarino commented 1 year ago

@ehsan6sha , sure, below I describe what you need to do:

  1. Download your prefered go version source code from: https://go.dev/dl/ (I downloaded go1.19.9 for instance) and unzip it into your home/yourusername directory, if you already have a "go" folder remove it and later on unzip.image

    image
  2. As per https://github.com/golang/go/issues/40569#issuecomment-1600898227 go to your recently unzipped go folder and find the src folder and proceed to modify src/net/interface_linux.go & /src/syscall/netlink_linux.go files as described in the comment above.

  3. Open a terminal window and go to your /home/yourusername/go/src, then build your modified go source by using sending $ ./all.bash through the command line as per go source install instructions

  4. After successful build, go to your golfing project and build your binaries as usual, change your targetSdkVersion to 30 or above in your build.gradle file, compile your project in Android Studio et voila!

As @yan12125 this is a super clean patch in the go source code to work around the issue, looks effective until now :). Next step its to upload my app to google play as final test hehe