Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: android 30 support #303

Closed
wants to merge 2 commits into from
Closed

chore: android 30 support #303

wants to merge 2 commits into from

Conversation

richard-ramos
Copy link
Member

@richard-ramos richard-ramos commented Sep 8, 2022

Railgun reported that while using go-waku in Android with ndk-23 and targeting android 30, their application froze and this error appeared in the logs:

E/GoLog: 2022-09-07T16:21:45.685Z ERROR basichost basic/basic_host.go:327 failed to resolve local interface addresses {"error": "route ip+net: netlinkrib: permission denied"}

This error is described in detail in ipfs-shipyard/gomobile-ipfs#68, and happens when targeting android API version +30. In this issue a possible solution was described which involved all the steps shown below. I have modified these to take in account that we are using Go 1.18 as well as using a newer version of the Android NDK. (Notice that the fix involves monkeypatching one of the status-go/go-waku dependencies)

According to https://apilevels.com/ , the status app will have to target Sdk 31+ by Nov22 for app updates, so, we'll run into this issue too (assuming we aren't already doing so, if we are targeting android 30).

I'll update these steps as soon as I get a confirmation from Railgun's developers that the fix worked and create a proper issue in status-mobile/status-go. We should also watch golang/go#40569 in case gomobile releases a version that includes this fix.

cc: @alaibe @cammellos @fryorcraken @iurimatias @jakubgs @John-44 @Samyoul

Modifying the react native project

In MainApplication.java

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

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;
import java.util.Locale;
...
  // 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(java.util.Locale.ROOT, "%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(java.util.Locale.ROOT, "%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();
  }

Installing the requirements for gomobile

The following commands were executed in a Digital Ocean droplet, with the root user. It's an example of the actions that are needed and is not meant to be used as is. This script needs to be modified to be run either with nix / docker / bash scripts / etc.

# Installing required dependencies
apt update
apt install build-essential unzip openjdk-11-jdk


# Installing Go 1.18
wget https://go.dev/dl/go1.18.6.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.18.6.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
export PATH=$PATH:/root/go/bin

# Installing the Android NDK 25
mkdir -p /root/Android/Sdk/
wget https://dl.google.com/android/repository/android-ndk-r25b-linux.zip
unzip android-ndk-r25b-linux.zip
mv -r android-ndk-r25b /root/Android/Sdk/ndk
export ANDROID_NDK_HOME=/root/Android/Sdk/ndk

# Installing the Android SDK command line tools and android-30
mkdir /root/Android/Sdk/cmdline-tools
wget https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip
unzip commandlinetools-linux-8512546_latest.zip
mv cmdline-tools /root/Android/Sdk/cmdline-tools/latest
export PATH=$PATH:/root/Android/Sdk/cmdline-tools/latest/bin
sdkmanager "platform-tools" "platforms;android-30"

# Cloning go-waku
mkdir -p ~/go/src/github.com/status-im
cd ~/go/src/github.com/status-im
git clone https://github.com/status-im/go-waku
cd go-waku

Modifying go.mod and vendor folder

Modify go.mod and add the line

git.wow.st/gmp/jni v0.0.0-20200827154156-014cd5c7c4c0

and then execute

go mod download
go mod vendor

Verify that the directory ./vendor/git.wow.st is created. If the directory is not created or if go mod download modified go.mod into

git.wow.st/gmp/jni v0.0.0-20200827154156-014cd5c7c4c0 // indirect

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

	"git.wow.st/gmp/jni"

into one of the project's .go files, run go mod download again, go get the_go_file_you_modified.go,
run go mod vendor again, and then reset the file that was modified with git checkout the_go_file_you_modified.go

Monkeypatching go-multiaddr

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"

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

and

// 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 := net.ParseCIDR(addr)
			if err == nil {
				ifat = append(ifat, ip)
			}
		}
	}

	return ifat, nil
}

A git patch could be created for this operation.

Executing gomobile

export GO111MODULE=off
go get golang.org/x/mobile/cmd/gobind
go get golang.org/x/mobile/cmd/gomobile
export GO111MODULE=on
gomobile init
export GO111MODULE=off
gomobile bind -v -target=android -androidapi=30 -ldflags="-s -w" -v -o ./build/lib/gowaku.aar ./mobile

ls build/lib

@status-im-auto
Copy link

Jenkins Builds

Commit #️⃣ Finished (UTC) Duration Platform Result
e4daddd #1 2022-09-12 12:31:44 ~30 sec android 📄log
e4daddd #2 2022-09-12 12:33:59 ~2 min ios 📄log
e4daddd #1 2022-09-12 13:02:22 ~2 min ios 📄log

@richard-ramos richard-ramos linked an issue Sep 13, 2022 that may be closed by this pull request
@dao
Copy link

dao commented Sep 21, 2022

With these changes in place we are on to the second error described here:

"Also another error comes from netroute.New(), but this is not so important if we find a solution for net.InterfaceAddrs())"

I am unsure why this is "not so important" in their case, but for us it means that we never receive messages.

2022-09-20 15:20:55.718 14329-14329/com.railway.app W/Thread-3818: type=1400 audit(0.0:36627): avc: denied { bind } for scontext=u:r:untrusted_app:s0:c148,c256,c512,c768 tcontext=u:r:untrusted_app:s0:c148,c256,c512,c768 tclass=netlink_route_socket permissive=0 bug=b/155595000 app=com.railway.app
2022-09-20 15:20:55.723 14329-14501/com.railway.app E/NODEJS-MOBILE: 2022-09-20T19:20:55.720Z	DEBUG	basichost	basic/basic_host.go:301	failed to build Router for kernel's routing table	{"error": "permission denied"}

this error originates in go-libp2p/basic_host in method updateLocalIpAddr

@richard-ramos
Copy link
Member Author

Closing PR as it seems that the problem's been solved. The "route ip+net: netlinkrib: permission denied" error still appears, but the application is usable (messages are received/sent).

In case we still want to remove the error, A write-up is available here: https://github.com/waku-org/waku-react-native/blob/master/android-netlink.md , including instructions on building go-waku for android and linking against a local waku-react-native repository

@richard-ramos richard-ramos deleted the android-30 branch January 7, 2023 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

route ip+net: netlinkrib: permission denied in android 30
3 participants