Skip to content

Commit ce80361

Browse files
committed
Split MDNS.Run() into platform-specific implementations for Android
The standard go-libp2p mDNS service calls net.Interfaces() internally, which triggers a bind() on netlink_route_socket — blocked by SELinux on Android. Replace the single Run() with two build-tag variants: - mdns_run.go (!android): original behaviour via mdns.NewMdnsService - mdns_android.go (android): uses anet.Interfaces() to bypass netlinkrib, calls zeroconf.RegisterProxy/Browse directly with explicit interfaces, and runs the peer-handling loop in a goroutine so Run() returns immediately (matching the non-Android contract — blocking here prevents startNetwork() from returning and starves the ledger and VPN service)
1 parent a25d890 commit ce80361

File tree

3 files changed

+303
-8
lines changed

3 files changed

+303
-8
lines changed

pkg/discovery/mdns.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121

2222
"github.com/libp2p/go-libp2p/core/host"
2323
"github.com/libp2p/go-libp2p/core/peer"
24-
"github.com/libp2p/go-libp2p/p2p/discovery/mdns"
2524
)
2625

2726
type MDNS struct {
@@ -48,10 +47,3 @@ func (n *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) {
4847
func (d *MDNS) Option(ctx context.Context) func(c *libp2p.Config) error {
4948
return func(*libp2p.Config) error { return nil }
5049
}
51-
52-
func (d *MDNS) Run(l log.StandardLogger, ctx context.Context, host host.Host) error {
53-
// setup mDNS discovery to find local peers
54-
55-
disc := mdns.NewMdnsService(host, d.DiscoveryServiceTag, &discoveryNotifee{h: host, c: l})
56-
return disc.Start()
57-
}

pkg/discovery/mdns_android.go

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
//go:build android
2+
3+
package discovery
4+
5+
// Android-specific mDNS implementation that avoids net.Interfaces() (blocked by
6+
// SELinux on Android) by using anet.Interfaces() instead.
7+
//
8+
// go-libp2p's mdns.NewMdnsService always passes nil to zeroconf.RegisterProxy and
9+
// zeroconf.Browse, which causes both to call listMulticastInterfaces() → net.Interfaces()
10+
// → syscall.NetlinkRIB → bind() on netlink_route_socket → EACCES on Android.
11+
//
12+
// We bypass this by calling zeroconf.RegisterProxy and zeroconf.Browse ourselves
13+
// with explicit interfaces obtained from anet, which uses sendto() instead of bind()
14+
// on the netlink socket.
15+
//
16+
// We also avoid h.Addrs() at startup because the address manager's background goroutine
17+
// may not have populated currentAddrs.localAddrs yet. Instead we derive IPs directly from
18+
// anet.InterfaceAddrs() and ports from h.Network().ListenAddresses().
19+
20+
import (
21+
"context"
22+
"math/rand"
23+
"net"
24+
"strings"
25+
26+
"github.com/ipfs/go-log"
27+
"github.com/libp2p/go-libp2p/core/host"
28+
"github.com/libp2p/go-libp2p/core/peer"
29+
"github.com/libp2p/zeroconf/v2"
30+
ma "github.com/multiformats/go-multiaddr"
31+
manet "github.com/multiformats/go-multiaddr/net"
32+
"github.com/wlynxg/anet"
33+
)
34+
35+
const (
36+
mdnsServiceName = "_p2p._udp"
37+
mdnsDomain = "local"
38+
dnsaddrPrefix = "dnsaddr="
39+
)
40+
41+
func (d *MDNS) Run(l log.StandardLogger, ctx context.Context, h host.Host) error {
42+
serviceName := d.DiscoveryServiceTag
43+
if serviceName == "" {
44+
serviceName = mdnsServiceName
45+
}
46+
l.Infof("mdns(android): starting, service=%s", serviceName)
47+
48+
// Get network interfaces without calling net.Interfaces() (blocked on Android).
49+
ifaces, err := anet.Interfaces()
50+
if err != nil {
51+
return err
52+
}
53+
54+
// Keep only interfaces that are up and support multicast.
55+
var mcIfaces []net.Interface
56+
for _, iface := range ifaces {
57+
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagMulticast == 0 {
58+
continue
59+
}
60+
mcIfaces = append(mcIfaces, iface)
61+
}
62+
if len(mcIfaces) == 0 {
63+
l.Warnf("mdns(android): no multicast-capable interfaces found (WiFi off?), mDNS skipped")
64+
return nil
65+
}
66+
l.Infof("mdns(android): using interfaces: %v", ifaceNames(mcIfaces))
67+
68+
// Get real interface IPs via anet (avoids netlinkrib bind() call).
69+
// These are used both for the mDNS A/AAAA records and for expanding
70+
// wildcard/loopback listen addresses into routable addresses.
71+
ifAddrs, err := anet.InterfaceAddrs()
72+
if err != nil {
73+
return err
74+
}
75+
var routableIPs []net.IP
76+
for _, a := range ifAddrs {
77+
ipNet, ok := a.(*net.IPNet)
78+
if !ok {
79+
continue
80+
}
81+
ip := ipNet.IP
82+
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
83+
continue
84+
}
85+
routableIPs = append(routableIPs, ip)
86+
}
87+
if len(routableIPs) == 0 {
88+
l.Warnf("mdns(android): no routable interface addresses found, mDNS skipped")
89+
return nil
90+
}
91+
92+
// Build multiaddrs for each routable IP (without port — used as base for expansion).
93+
var routableMaddrs []ma.Multiaddr
94+
for _, ip := range routableIPs {
95+
maddr, err := manet.FromIP(ip)
96+
if err != nil {
97+
continue
98+
}
99+
routableMaddrs = append(routableMaddrs, maddr)
100+
}
101+
102+
// Get the raw listen addresses (e.g. /ip4/0.0.0.0/tcp/PORT or /ip4/127.0.0.1/tcp/PORT).
103+
// These may be wildcards or loopback; we expand them to real IPs below.
104+
listenAddrs := h.Network().ListenAddresses()
105+
106+
// Expand wildcard/loopback listen addresses to routable IPs, keeping transport/port.
107+
var expandedListenAddrs []ma.Multiaddr
108+
for _, la := range listenAddrs {
109+
ip, err := manet.ToIP(la)
110+
if err != nil {
111+
// Non-IP address (e.g. /p2p-circuit); skip for mDNS.
112+
continue
113+
}
114+
if !ip.IsUnspecified() && !ip.IsLoopback() {
115+
// Already a routable address.
116+
expandedListenAddrs = append(expandedListenAddrs, la)
117+
continue
118+
}
119+
// Wildcard or loopback → expand to real IPs with same transport/port.
120+
_, transport := ma.SplitFirst(la)
121+
for _, rAddr := range routableMaddrs {
122+
expanded := rAddr
123+
if transport != nil {
124+
expanded = rAddr.Encapsulate(transport)
125+
}
126+
expandedListenAddrs = append(expandedListenAddrs, expanded)
127+
}
128+
}
129+
if len(expandedListenAddrs) == 0 {
130+
l.Warnf("mdns(android): no listen addresses to advertise, mDNS skipped")
131+
return nil
132+
}
133+
134+
// Build p2p multiaddrs (with /p2p/PeerID suffix) for TXT records.
135+
p2pAddrs, err := peer.AddrInfoToP2pAddrs(&peer.AddrInfo{
136+
ID: h.ID(),
137+
Addrs: expandedListenAddrs,
138+
})
139+
if err != nil {
140+
return err
141+
}
142+
143+
var txts []string
144+
for _, addr := range p2pAddrs {
145+
if isSuitableForMDNS(addr) {
146+
txts = append(txts, dnsaddrPrefix+addr.String())
147+
}
148+
}
149+
150+
// Build the IP strings for the mDNS A/AAAA records.
151+
ips := ipsToStrings(routableIPs)
152+
153+
peerName := randomString(32 + rand.Intn(32))
154+
155+
l.Infof("mdns(android): advertising IPs=%v txts=%v", ips, txts)
156+
server, err := zeroconf.RegisterProxy(
157+
peerName,
158+
serviceName,
159+
mdnsDomain,
160+
4001, // port is carried in TXT records; this value is required but ignored by libp2p peers
161+
peerName,
162+
ips,
163+
txts,
164+
mcIfaces,
165+
)
166+
if err != nil {
167+
return err
168+
}
169+
l.Infof("mdns(android): registered proxy, browsing for peers...")
170+
171+
// Browse for peers on the same service. SelectIfaces bypasses listMulticastInterfaces().
172+
entryChan := make(chan *zeroconf.ServiceEntry, 1000)
173+
174+
errCh := make(chan error, 1)
175+
go func() {
176+
errCh <- zeroconf.Browse(ctx, serviceName, mdnsDomain, entryChan, zeroconf.SelectIfaces(mcIfaces))
177+
}()
178+
179+
notifee := &discoveryNotifee{h: h, c: l}
180+
181+
// Run the peer-handling loop in the background so Run() returns immediately,
182+
// matching the non-android implementation (mdns_run.go) which is also non-blocking.
183+
// startNetwork() must return for the ledger and VPN network services to start.
184+
go func() {
185+
defer server.Shutdown()
186+
for {
187+
select {
188+
case entry, ok := <-entryChan:
189+
if !ok {
190+
return
191+
}
192+
var addrs []ma.Multiaddr
193+
for _, txt := range entry.Text {
194+
if !strings.HasPrefix(txt, dnsaddrPrefix) {
195+
continue
196+
}
197+
addr, err := ma.NewMultiaddr(txt[len(dnsaddrPrefix):])
198+
if err != nil {
199+
continue
200+
}
201+
addrs = append(addrs, addr)
202+
}
203+
infos, err := peer.AddrInfosFromP2pAddrs(addrs...)
204+
if err != nil {
205+
continue
206+
}
207+
for _, info := range infos {
208+
if info.ID == h.ID() {
209+
continue
210+
}
211+
l.Infof("mdns(android): found peer %s addrs=%v", info.ID, info.Addrs)
212+
go notifee.HandlePeerFound(info)
213+
}
214+
case err := <-errCh:
215+
if err != nil {
216+
l.Warnf("mdns(android): browse error: %v", err)
217+
}
218+
return
219+
case <-ctx.Done():
220+
return
221+
}
222+
}
223+
}()
224+
225+
return nil
226+
}
227+
228+
// ifaceNames returns the names of the given interfaces for logging.
229+
func ifaceNames(ifaces []net.Interface) []string {
230+
names := make([]string, 0, len(ifaces))
231+
for _, iface := range ifaces {
232+
names = append(names, iface.Name)
233+
}
234+
return names
235+
}
236+
237+
// ipsToStrings converts net.IP slice to string slice (IPv4 as dotted decimal, IPv6 as standard).
238+
func ipsToStrings(ips []net.IP) []string {
239+
ss := make([]string, 0, len(ips))
240+
for _, ip := range ips {
241+
ss = append(ss, ip.String())
242+
}
243+
return ss
244+
}
245+
246+
// isSuitableForMDNS mirrors the same function from go-libp2p's mdns package.
247+
// It filters multiaddrs to those suitable for LAN mDNS advertisement.
248+
func isSuitableForMDNS(addr ma.Multiaddr) bool {
249+
if addr == nil {
250+
return false
251+
}
252+
first, _ := ma.SplitFirst(addr)
253+
if first == nil {
254+
return false
255+
}
256+
switch first.Protocol().Code {
257+
case ma.P_IP4, ma.P_IP6:
258+
// ok
259+
case ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR:
260+
if !strings.HasSuffix(strings.ToLower(first.Value()), ".local") {
261+
return false
262+
}
263+
default:
264+
return false
265+
}
266+
// Reject circuit relay and browser-only transports.
267+
unsuitable := false
268+
ma.ForEach(addr, func(c ma.Component) bool {
269+
switch c.Protocol().Code {
270+
case ma.P_CIRCUIT, ma.P_WEBTRANSPORT, ma.P_WEBRTC, ma.P_WEBRTC_DIRECT, ma.P_P2P_WEBRTC_DIRECT, ma.P_WS, ma.P_WSS:
271+
unsuitable = true
272+
return false
273+
}
274+
return true
275+
})
276+
return !unsuitable
277+
}
278+
279+
// randomString generates a random lowercase alphanumeric string of length l.
280+
func randomString(l int) string {
281+
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
282+
s := make([]byte, 0, l)
283+
for i := 0; i < l; i++ {
284+
s = append(s, alphabet[rand.Intn(len(alphabet))])
285+
}
286+
return string(s)
287+
}

pkg/discovery/mdns_run.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//go:build !android
2+
3+
package discovery
4+
5+
import (
6+
"context"
7+
8+
"github.com/ipfs/go-log"
9+
"github.com/libp2p/go-libp2p/core/host"
10+
"github.com/libp2p/go-libp2p/p2p/discovery/mdns"
11+
)
12+
13+
func (d *MDNS) Run(l log.StandardLogger, ctx context.Context, host host.Host) error {
14+
disc := mdns.NewMdnsService(host, d.DiscoveryServiceTag, &discoveryNotifee{h: host, c: l})
15+
return disc.Start()
16+
}

0 commit comments

Comments
 (0)