Просмотр исходного кода

Upgrade to wlynxg/anet v0.0.5

- Uses stock net.Interfaces for Android < 11
- CGO builds (including Android and Client Library) automatically detect
  Android version
- Fixes IPv6 zoneCache issue with net.Listen after anet.Interfaces
- Limitations
  - Non-CGO builds now default to stock net.Interface, even on Android 11+
  - Currently requires `-checklinkname=0` linker flag workaround
Rod Hynes 11 месяцев назад
Родитель
Сommit
e4a3e7c1fc

+ 1 - 1
ClientLibrary/Dockerfile

@@ -29,7 +29,7 @@ RUN curl -L https://storage.googleapis.com/golang/$GOVERSION.linux-amd64.tar.gz
    && echo $GOVERSION > $GOROOT/VERSION
 
 # Setup Android Environment.
-ENV ANDROID_NDK_VERSION=r17b
+ENV ANDROID_NDK_VERSION=r22b
 ENV ANDROID_NDK_TOOLCHAIN_ROOT=/android-ndk-toolchain
 
 # Setup Android NDK

+ 8 - 2
ClientLibrary/make.bash

@@ -51,20 +51,26 @@ build_for_android () {
 
   prepare_build android
 
+  # Required workaround for an PSIPHON_ENABLE_INPROXY dependency:
+  # https://github.com/wlynxg/anet/tree/5501d401a269290292909e6cc75f105571f97cfa?tab=readme-ov-file#how-to-build-with-go-1230-or-later
+  #
+  # TODO: conditional on PSIPHON_ENABLE_INPROXY build tag?
+  ANDROID_LDFLAGS="-checklinkname=0 $LDFLAGS"
+
   TARGET_ARCH=arm
   ARMV=7
 
   CC="${ANDROID_NDK_TOOLCHAIN_ROOT}/${TARGET_ARCH}/bin/arm-linux-androideabi-clang" \
   CXX="${ANDROID_NDK_TOOLCHAIN_ROOT}/${TARGET_ARCH}/bin/arm-linux-androideabi-clang++" \
   GOARM=${ARMV} \
-  GOOS=${TARGET_OS} GOARCH=${TARGET_ARCH} go build -buildmode=c-shared -ldflags "$LDFLAGS" -tags "${BUILD_TAGS}" -o "${OUTPUT_DIR}/${TARGET_ARCH}${ARMV}/libpsiphontunnel.so" PsiphonTunnel.go
+  GOOS=${TARGET_OS} GOARCH=${TARGET_ARCH} go build -buildmode=c-shared -ldflags "$ANDROID_LDFLAGS" -tags "${BUILD_TAGS}" -o "${OUTPUT_DIR}/${TARGET_ARCH}${ARMV}/libpsiphontunnel.so" PsiphonTunnel.go
 
 
   TARGET_ARCH=arm64
 
   CC="${ANDROID_NDK_TOOLCHAIN_ROOT}/${TARGET_ARCH}/bin/aarch64-linux-android-clang" \
   CXX="${ANDROID_NDK_TOOLCHAIN_ROOT}/${TARGET_ARCH}/bin/aarch64-linux-android-clang++" \
-  GOOS=${TARGET_OS} GOARCH=${TARGET_ARCH} go build -buildmode=c-shared -ldflags "$LDFLAGS" -tags "${BUILD_TAGS}" -o "${OUTPUT_DIR}/${TARGET_ARCH}/libpsiphontunnel.so" PsiphonTunnel.go
+  GOOS=${TARGET_OS} GOARCH=${TARGET_ARCH} go build -buildmode=c-shared -ldflags "$ANDROID_LDFLAGS" -tags "${BUILD_TAGS}" -o "${OUTPUT_DIR}/${TARGET_ARCH}/libpsiphontunnel.so" PsiphonTunnel.go
 
 }
 

+ 6 - 0
MobileLibrary/Android/make.bash

@@ -21,7 +21,13 @@ BUILDREPO="https://github.com/Psiphon-Labs/psiphon-tunnel-core.git"
 BUILDREV=$(git rev-parse --short HEAD)
 GOVERSION=$(go version | perl -ne '/go version (.*?) / && print $1')
 
+# -checklinkname=0 is a required workaround for an PSIPHON_ENABLE_INPROXY dependency:
+# https://github.com/wlynxg/anet/tree/5501d401a269290292909e6cc75f105571f97cfa?tab=readme-ov-file#how-to-build-with-go-1230-or-later
+#
+# TODO: conditional on PSIPHON_ENABLE_INPROXY build tag?
+
 LDFLAGS="\
+-checklinkname=0 \
 -s \
 -w \
 -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo.buildDate=$BUILDDATE \

+ 1 - 1
go.mod

@@ -84,7 +84,7 @@ require (
 	github.com/stretchr/testify v1.9.0
 	github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8
 	github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78
-	github.com/wlynxg/anet v0.0.1
+	github.com/wlynxg/anet v0.0.5
 	golang.org/x/crypto v0.35.0
 	golang.org/x/net v0.36.0
 	golang.org/x/sync v0.11.0

+ 2 - 0
go.sum

@@ -287,6 +287,8 @@ github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 h1:9sreu9e9K
 github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78/go.mod h1:HazXTRLhXFyq80TQp7PUXi6BKE6mS+ydEdzEqNBKopQ=
 github.com/wlynxg/anet v0.0.1 h1:VbkEEgHxPSrRQSiyRd0pmrbcEQAEU2TTb8fb4DmSYoQ=
 github.com/wlynxg/anet v0.0.1/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
+github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
+github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=

+ 11 - 4
vendor/github.com/wlynxg/anet/README.md

@@ -25,9 +25,7 @@ The specific fix logic includes:
 Removing the `Bind()` operation on `Netlink` sockets in the `NetlinkRIB()` function.
 Using `ioctl` based on the Index number returned by `RTM_GETADDR` to retrieve the network card's name, MTU, and flags.
 
-
-
-
+There are two implementations of the `net` package: one from the [Go standard library](https://pkg.go.dev/net) and another from the [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) module. Both of these implementations have the same issues in the Android environment. The `anet` package should be compatible with both of them.
 
 ## Test Code
 ### net.Interface()
@@ -116,4 +114,13 @@ result:
 ...
 192.168.6.143/24
 fe80::7e4f:4446:eb3:1eb8/64
-```
+```
+
+## Other issues due to #40569
+- https://github.com/golang/go/issues/68082
+
+## How to build with Go 1.23.0 or later
+The `anet` library internally relies on `//go:linkname` directive. Since the usage of `//go:linkname` has been restricted since Go 1.23.0 ([Go 1.23 Release Notes](https://tip.golang.org/doc/go1.23#linker)), it is necessary to specify the `-checklinkname=0` linker flag when building the `anet` package with Go 1.23.0 or later. Without this flag, the following linker error will occur:
+```
+link: github.com/wlynxg/anet: invalid reference to net.zoneCache
+```

+ 4 - 1
vendor/github.com/wlynxg/anet/README_zh.md

@@ -19,4 +19,7 @@
 具体修复逻辑包括:
 
 - 取消了`NetlinkRIB()`函数中对`Netlink`套接字的`Bind()`操作。
-- 根据`RTM_GETADDR`返回的Index号,使用`ioctl`获取其网卡的名称、MTU和标志位。
+- 根据`RTM_GETADDR`返回的Index号,使用`ioctl`获取其网卡的名称、MTU和标志位。
+
+## 由于 #40569 导致的其他问题
+- #[68082](https://github.com/golang/go/issues/68082)

+ 7 - 0
vendor/github.com/wlynxg/anet/android_api_level.go

@@ -0,0 +1,7 @@
+//go:build !(android && cgo)
+
+package anet
+
+func androidDeviceApiLevel() int {
+	return -1
+}

+ 23 - 0
vendor/github.com/wlynxg/anet/android_api_level_cgo.go

@@ -0,0 +1,23 @@
+//go:build android && cgo
+
+package anet
+
+// #include <android/api-level.h>
+import "C"
+
+import "sync"
+
+var (
+	apiLevel int
+	once     sync.Once
+)
+
+// Returns the API level of the device we're actually running on, or -1 on failure.
+// The returned value is equivalent to the Java Build.VERSION.SDK_INT API.
+func androidDeviceApiLevel() int {
+	once.Do(func() {
+		apiLevel = int(C.android_get_device_api_level())
+	})
+
+	return apiLevel
+}

+ 127 - 9
vendor/github.com/wlynxg/anet/interface_android.go

@@ -5,25 +5,30 @@ import (
 	"errors"
 	"net"
 	"os"
+	"sync"
 	"syscall"
+	"time"
 	"unsafe"
 )
 
 const (
-	android11 = 11
+	android11ApiLevel = 30
 )
 
 var (
-	androidVersion      uint
-	errNoSuchInterface  = errors.New("no such network interface")
-	errInvalidInterface = errors.New("invalid network interface")
+	customAndroidApiLevel       = -1
+	errInvalidInterface         = errors.New("invalid network interface")
+	errInvalidInterfaceIndex    = errors.New("invalid network interface index")
+	errInvalidInterfaceName     = errors.New("invalid network interface name")
+	errNoSuchInterface          = errors.New("no such network interface")
+	errNoSuchMulticastInterface = errors.New("no such multicast network interface")
 )
 
 type ifReq [40]byte
 
 // Interfaces returns a list of the system's network interfaces.
 func Interfaces() ([]net.Interface, error) {
-	if androidVersion > android11 {
+	if androidApiLevel() < android11ApiLevel {
 		return net.Interfaces()
 	}
 
@@ -31,7 +36,10 @@ func Interfaces() ([]net.Interface, error) {
 	if err != nil {
 		return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
 	}
-	// TODO: zoneCache implementation
+	if len(ift) != 0 {
+		zoneCache.update(ift, true)
+		zoneCacheX.update(ift, true)
+	}
 	return ift, nil
 }
 
@@ -41,7 +49,7 @@ func Interfaces() ([]net.Interface, error) {
 // The returned list does not identify the associated interface; use
 // Interfaces and Interface.Addrs for more detail.
 func InterfaceAddrs() ([]net.Addr, error) {
-	if androidVersion > android11 {
+	if androidApiLevel() < android11ApiLevel {
 		return net.InterfaceAddrs()
 	}
 
@@ -52,6 +60,51 @@ func InterfaceAddrs() ([]net.Addr, error) {
 	return ifat, err
 }
 
+// InterfaceByIndex returns the interface specified by index.
+//
+// On Solaris, it returns one of the logical network interfaces
+// sharing the logical data link; for more precision use
+// InterfaceByName.
+func InterfaceByIndex(index int) (*net.Interface, error) {
+	if androidApiLevel() < android11ApiLevel {
+		return net.InterfaceByIndex(index)
+	}
+
+	if index <= 0 {
+		return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterfaceIndex}
+	}
+	ift, err := interfaceTable(index)
+	if err != nil {
+		return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
+	}
+	ifi, err := interfaceByIndex(ift, index)
+	if err != nil {
+		err = &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
+	}
+	return ifi, err
+}
+
+// InterfaceByName returns the interface specified by name.
+func InterfaceByName(name string) (*net.Interface, error) {
+	if name == "" {
+		return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterfaceName}
+	}
+	ift, err := interfaceTable(0)
+	if err != nil {
+		return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
+	}
+	if len(ift) != 0 {
+		zoneCache.update(ift, true)
+		zoneCacheX.update(ift, true)
+	}
+	for _, ifi := range ift {
+		if name == ifi.Name {
+			return &ifi, nil
+		}
+	}
+	return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errNoSuchInterface}
+}
+
 // InterfaceAddrsByInterface returns a list of the system's unicast
 // interface addresses by specific interface.
 func InterfaceAddrsByInterface(ifi *net.Interface) ([]net.Addr, error) {
@@ -59,7 +112,7 @@ func InterfaceAddrsByInterface(ifi *net.Interface) ([]net.Addr, error) {
 		return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterface}
 	}
 
-	if androidVersion > android11 {
+	if androidApiLevel() < android11ApiLevel {
 		return ifi.Addrs()
 	}
 
@@ -73,8 +126,73 @@ func InterfaceAddrsByInterface(ifi *net.Interface) ([]net.Addr, error) {
 // SetAndroidVersion set the Android environment in which the program runs.
 // The Android system version number can be obtained through
 // `android.os.Build.VERSION.RELEASE` of the Android framework.
+// If version is 0 the actual version will be detected automatically if possible.
 func SetAndroidVersion(version uint) {
-	androidVersion = version
+	switch {
+	case version == 0:
+		customAndroidApiLevel = -1
+	case version >= 11:
+		customAndroidApiLevel = android11ApiLevel
+	default:
+		customAndroidApiLevel = 0
+	}
+}
+
+func androidApiLevel() int {
+	if customAndroidApiLevel != -1 {
+		// user-provided api level should be used
+		return customAndroidApiLevel
+	}
+
+	// try to autodetect api level
+	return androidDeviceApiLevel()
+}
+
+// An ipv6ZoneCache represents a cache holding partial network
+// interface information. It is used for reducing the cost of IPv6
+// addressing scope zone resolution.
+//
+// Multiple names sharing the index are managed by first-come
+// first-served basis for consistency.
+type ipv6ZoneCache struct {
+	sync.RWMutex                // guard the following
+	lastFetched  time.Time      // last time routing information was fetched
+	toIndex      map[string]int // interface name to its index
+	toName       map[int]string // interface index to its name
+}
+
+//go:linkname zoneCache net.zoneCache
+var zoneCache ipv6ZoneCache
+
+//go:linkname zoneCacheX golang.org/x/net/internal/socket.zoneCache
+var zoneCacheX ipv6ZoneCache
+
+// update refreshes the network interface information if the cache was last
+// updated more than 1 minute ago, or if force is set. It reports whether the
+// cache was updated.
+func (zc *ipv6ZoneCache) update(ift []net.Interface, force bool) (updated bool) {
+	zc.Lock()
+	defer zc.Unlock()
+	now := time.Now()
+	if !force && zc.lastFetched.After(now.Add(-60*time.Second)) {
+		return false
+	}
+	zc.lastFetched = now
+	if len(ift) == 0 {
+		var err error
+		if ift, err = interfaceTable(0); err != nil {
+			return false
+		}
+	}
+	zc.toIndex = make(map[string]int, len(ift))
+	zc.toName = make(map[int]string, len(ift))
+	for _, ifi := range ift {
+		zc.toIndex[ifi.Name] = ifi.Index
+		if _, ok := zc.toName[ifi.Index]; !ok {
+			zc.toName[ifi.Index] = ifi.Name
+		}
+	}
+	return true
 }
 
 // If the ifindex is zero, interfaceTable returns mappings of all

+ 1 - 1
vendor/modules.txt

@@ -491,7 +491,7 @@ github.com/vishvananda/netns
 # github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78
 ## explicit; go 1.12
 github.com/wader/filtertransport
-# github.com/wlynxg/anet v0.0.1
+# github.com/wlynxg/anet v0.0.5
 ## explicit; go 1.20
 github.com/wlynxg/anet
 # github.com/x448/float16 v0.8.4