落雨宸的时光机
873 字
4 分钟
Workaround for NetBird Android `netlinkrib: permission denied` error
2026-03-16
无标签
...

1. TL;DR#

I’ve set up a GitHub Workflow that automatically patchs the official code and builds working binaries for Android.

Just grab the latest binary from the latest workflow run here: https://github.com/lzc256/netbird/actions/workflows/sync-and-build.yml

Done! You can now enjoy a working IP sniffing, ICE & P2P peering experience.

The built binary is nothing different from the official one, but just applied a patch that replaces the original net library with anet, which works for Android IP sniffing. It’s built every 6 hours, up to date with the upstream branch.

2. Background#

I tried running the netbird-linux-arm64 cli directly in Android Termux. Wrapping it in a script seemed to get it working, except the P2P feature. You always get this error in the log:

2026-03-15T14:29:17.803Z ERRO client/internal/peer/ice/agent.go:44: failed to create pion's stdnet: route ip+net: netlinkrib: permission denied

What’s worse, all STUN servers fail. Since ICE is uninitialized, the android client could never connect to other peers via P2P. Depending on the relay results in high latency and slow speed, which is unacceptable.

3. Workaround#

After hard efforts of investigation for about 1 day, I found similar issues in other projects. In a nutshell, in Android 11 (API 30), the Android kernel forbids direct access to /proc/net, which disables the golang library net. A bunch of methods fail, including but not limited to:

  • net.Interfaces()
  • oif.Addrs()

These are the root of the problem.

Luckily, there are relevant discussions. I’m fortunate enough to find the most valuable one: https://github.com/golang/go/issues/40569, in which @wlynxg posted their own workaround repo wlynxg/anet that provides a net library alternative which works on Android, anet.

We could just simply replace every net with anet in the NetBird repo, after which the issue was resolved.

All modifications are based on commit 3e6baea (v0.66.4). If there are conflicts or more netlinkrib: permission denied errors arise after an upstream update, just simply replace all net methods in the new code.

Build script:

go mod edit -replace github.com/wlynxg/anet=github.com/lzc256/anet@latest
go mod tidy
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -v -trimpath -tags "load_wgnt_from_rsrc" -ldflags "-s -w -checklinkname=0" -a -o netbird-anet-linux-arm64 ./client

android-anet.patch

diff --git a/client/firewall/uspfilter/localip.go b/client/firewall/uspfilter/localip.go
index ffc807f4..0167564b 100644
--- a/client/firewall/uspfilter/localip.go
+++ b/client/firewall/uspfilter/localip.go
@@ -7,6 +7,7 @@ import (
 	"sync"
 
 	log "github.com/sirupsen/logrus"
+	"github.com/wlynxg/anet"
 
 	"github.com/netbirdio/netbird/client/firewall/uspfilter/common"
 )
@@ -87,7 +88,7 @@ func (m *localIPManager) processIP(ip netip.Addr, bitmap *[256]*ipv4LowBitmap, i
 }
 
 func (m *localIPManager) processInterface(iface net.Interface, bitmap *[256]*ipv4LowBitmap, ipv4Set map[netip.Addr]struct{}, ipv4Addresses *[]netip.Addr) {
-	addrs, err := iface.Addrs()
+	addrs, err := anet.InterfaceAddrsByInterface(&iface)
 	if err != nil {
 		log.Debugf("get addresses for interface %s failed: %v", iface.Name, err)
 		return
@@ -140,7 +141,7 @@ func (m *localIPManager) UpdateLocalIPs(iface common.IFaceMapper) (err error) {
 		}
 	}
 
-	interfaces, err := net.Interfaces()
+	interfaces, err := anet.Interfaces()
 	if err != nil {
 		log.Warnf("failed to get interfaces: %v", err)
 	} else {
diff --git a/client/internal/routemanager/systemops/systemops_generic.go b/client/internal/routemanager/systemops/systemops_generic.go
index ec219c7f..bc52562c 100644
--- a/client/internal/routemanager/systemops/systemops_generic.go
+++ b/client/internal/routemanager/systemops/systemops_generic.go
@@ -398,6 +398,7 @@ func ipToAddr(ip net.IP, intf *net.Interface) (netip.Addr, error) {
 
 // IsAddrRouted checks if the candidate address would route to the vpn, in which case it returns true and the matched prefix.
 func IsAddrRouted(addr netip.Addr, vpnRoutes []netip.Prefix) (bool, netip.Prefix) {
+	return false, netip.Prefix{}
 	localRoutes, err := hasSeparateRouting()
 	if err != nil {
 		if !errors.Is(err, ErrRoutingIsSeparate) {
diff --git a/client/internal/stdnet/discover_pion.go b/client/internal/stdnet/discover_pion.go
index 83b4a1d1..5e164482 100644
--- a/client/internal/stdnet/discover_pion.go
+++ b/client/internal/stdnet/discover_pion.go
@@ -1,9 +1,8 @@
 package stdnet
 
 import (
-	"net"
-
 	"github.com/pion/transport/v3"
+	"github.com/wlynxg/anet"
 )
 
 type pionDiscover struct {
@@ -12,7 +11,7 @@ type pionDiscover struct {
 func (d pionDiscover) iFaces() ([]*transport.Interface, error) {
 	ifs := []*transport.Interface{}
 
-	oifs, err := net.Interfaces()
+	oifs, err := anet.Interfaces()
 	if err != nil {
 		return nil, err
 	}
@@ -20,7 +19,7 @@ func (d pionDiscover) iFaces() ([]*transport.Interface, error) {
 	for _, oif := range oifs {
 		ifc := transport.NewInterface(oif)
 
-		addrs, err := oif.Addrs()
+		addrs, err := anet.InterfaceAddrsByInterface(&oif)
 		if err != nil {
 			return nil, err
 		}
diff --git a/client/system/info.go b/client/system/info.go
index 01176e76..8cc1a5b2 100644
--- a/client/system/info.go
+++ b/client/system/info.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	log "github.com/sirupsen/logrus"
+	"github.com/wlynxg/anet"
 	"google.golang.org/grpc/metadata"
 
 	"github.com/netbirdio/netbird/shared/management/proto"
@@ -146,7 +147,7 @@ func extractDeviceName(ctx context.Context, defaultName string) string {
 }
 
 func networkAddresses() ([]NetworkAddress, error) {
-	interfaces, err := net.Interfaces()
+	interfaces, err := anet.Interfaces()
 	if err != nil {
 		return nil, err
 	}
@@ -156,7 +157,7 @@ func networkAddresses() ([]NetworkAddress, error) {
 		if iface.HardwareAddr.String() == "" {
 			continue
 		}
-		addrs, err := iface.Addrs()
+		addrs, err := anet.InterfaceAddrsByInterface(&iface)
 		if err != nil {
 			continue
 		}
diff --git a/go.mod b/go.mod
index 4bcdbdc7..0eef0ba3 100644
--- a/go.mod
+++ b/go.mod
@@ -101,6 +101,7 @@ require (
 	github.com/ti-mo/conntrack v0.5.1
 	github.com/ti-mo/netfilter v0.5.2
 	github.com/vmihailenco/msgpack/v5 v5.4.1
+	github.com/wlynxg/anet v0.0.6-0.20250109065809-5501d401a269
 	github.com/yusufpapurcu/wmi v1.2.4
 	github.com/zcalusic/sysinfo v1.1.3
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0
@@ -267,7 +268,6 @@ require (
 	github.com/tklauser/numcpus v0.8.0 // indirect
 	github.com/vishvananda/netns v0.0.5 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
-	github.com/wlynxg/anet v0.0.5 // indirect
 	github.com/yuin/goldmark v1.7.8 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
 	go.opentelemetry.io/auto/sdk v1.2.1 // indirect
@@ -298,3 +298,5 @@ replace github.com/pion/ice/v4 => github.com/netbirdio/ice/v4 v4.0.0-20250908184
 replace github.com/libp2p/go-netroute => github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944
 
 replace github.com/dexidp/dex => github.com/netbirdio/dex v0.244.0
+
+replace github.com/wlynxg/anet => github.com/lzc256/anet v0.0.0-20260316065608-bca8c2f29883
diff --git a/go.sum b/go.sum
index 1bd9396b..c8167d32 100644
--- a/go.sum
+++ b/go.sum
@@ -353,6 +353,8 @@ github.com/lrh3321/ipset-go v0.0.0-20250619021614-54a0a98ace81/go.mod h1:RD8ML/Y
 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
 github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
 github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
+github.com/lzc256/anet v0.0.0-20260316065608-bca8c2f29883 h1:TgQZ0mjWKfGAEGzINtZQBOVX2p7fA86qsJzw32fW9h0=
+github.com/lzc256/anet v0.0.0-20260316065608-bca8c2f29883/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
 github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
 github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
@@ -583,8 +585,6 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
 github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
 github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
 github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
-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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=

References:

https://github.com/golang/go/issues/40569#issuecomment-1614781739 https://codeberg.org/bg443/JetBird/issues/42 https://github.com/netbirdio/netbird/issues/1354

Workaround for NetBird Android `netlinkrib: permission denied` error
https://blog.lzc256.com/posts/workaround-for-netbird-android-netlinkrib-permission-denied-error-using-anet/
作者
落雨宸
发布于
2026-03-16
许可协议
CC BY-NC-SA 4.0


Loading Comment Component...