Skip to content

Commit da47382

Browse files
committed
Add android support for MAC and hostname rule items
1 parent 6142088 commit da47382

12 files changed

Lines changed: 462 additions & 86 deletions

adapter/neighbor.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,19 @@ import (
55
"net/netip"
66
)
77

8+
type NeighborEntry struct {
9+
Address netip.Addr
10+
MACAddress net.HardwareAddr
11+
Hostname string
12+
}
13+
814
type NeighborResolver interface {
915
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
1016
LookupHostname(address netip.Addr) (string, bool)
1117
Start() error
1218
Close() error
1319
}
20+
21+
type NeighborUpdateListener interface {
22+
UpdateNeighborTable(entries []NeighborEntry)
23+
}

adapter/platform.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ type PlatformInterface interface {
3636

3737
UsePlatformNotification() bool
3838
SendNotification(notification *Notification) error
39+
40+
UsePlatformNeighborResolver() bool
41+
StartNeighborMonitor(listener NeighborUpdateListener) error
42+
CloseNeighborMonitor(listener NeighborUpdateListener) error
3943
}
4044

4145
type FindConnectionOwnerRequest struct {

experimental/libbox/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,18 @@ func (s *platformInterfaceStub) SendNotification(notification *adapter.Notificat
144144
return nil
145145
}
146146

147+
func (s *platformInterfaceStub) UsePlatformNeighborResolver() bool {
148+
return false
149+
}
150+
151+
func (s *platformInterfaceStub) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
152+
return os.ErrInvalid
153+
}
154+
155+
func (s *platformInterfaceStub) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
156+
return nil
157+
}
158+
147159
func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool {
148160
return false
149161
}

experimental/libbox/neighbor.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//go:build linux
2+
3+
package libbox
4+
5+
import (
6+
"net"
7+
"net/netip"
8+
"slices"
9+
"time"
10+
11+
"github.com/sagernet/sing-box/route"
12+
E "github.com/sagernet/sing/common/exceptions"
13+
14+
"github.com/mdlayher/netlink"
15+
"golang.org/x/sys/unix"
16+
)
17+
18+
type NeighborEntry struct {
19+
Address string
20+
MACAddress string
21+
Hostname string
22+
}
23+
24+
type NeighborEntryIterator interface {
25+
Next() *NeighborEntry
26+
HasNext() bool
27+
}
28+
29+
type NeighborSubscription struct {
30+
done chan struct{}
31+
}
32+
33+
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
34+
entries, err := route.ReadNeighborEntries()
35+
if err != nil {
36+
return nil, E.Cause(err, "initial neighbor dump")
37+
}
38+
table := make(map[netip.Addr]net.HardwareAddr)
39+
for _, entry := range entries {
40+
table[entry.Address] = entry.MACAddress
41+
}
42+
listener.UpdateNeighborTable(tableToIterator(table))
43+
connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
44+
Groups: 1 << (unix.RTNLGRP_NEIGH - 1),
45+
})
46+
if err != nil {
47+
return nil, E.Cause(err, "subscribe neighbor updates")
48+
}
49+
subscription := &NeighborSubscription{
50+
done: make(chan struct{}),
51+
}
52+
go subscription.loop(listener, connection, table)
53+
return subscription, nil
54+
}
55+
56+
func (s *NeighborSubscription) Close() {
57+
close(s.done)
58+
}
59+
60+
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, connection *netlink.Conn, table map[netip.Addr]net.HardwareAddr) {
61+
defer connection.Close()
62+
for {
63+
select {
64+
case <-s.done:
65+
return
66+
default:
67+
}
68+
err := connection.SetReadDeadline(time.Now().Add(3 * time.Second))
69+
if err != nil {
70+
return
71+
}
72+
messages, err := connection.Receive()
73+
if err != nil {
74+
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
75+
continue
76+
}
77+
select {
78+
case <-s.done:
79+
return
80+
default:
81+
}
82+
continue
83+
}
84+
changed := false
85+
for _, message := range messages {
86+
address, mac, isDelete, ok := route.ParseNeighborMessage(message)
87+
if !ok {
88+
continue
89+
}
90+
if isDelete {
91+
if _, exists := table[address]; exists {
92+
delete(table, address)
93+
changed = true
94+
}
95+
} else {
96+
existing, exists := table[address]
97+
if !exists || !slices.Equal(existing, mac) {
98+
table[address] = mac
99+
changed = true
100+
}
101+
}
102+
}
103+
if changed {
104+
listener.UpdateNeighborTable(tableToIterator(table))
105+
}
106+
}
107+
}
108+
109+
func tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator {
110+
entries := make([]*NeighborEntry, 0, len(table))
111+
for address, mac := range table {
112+
entries = append(entries, &NeighborEntry{
113+
Address: address.String(),
114+
MACAddress: mac.String(),
115+
})
116+
}
117+
return &neighborEntryIterator{entries}
118+
}
119+
120+
type neighborEntryIterator struct {
121+
entries []*NeighborEntry
122+
}
123+
124+
func (i *neighborEntryIterator) HasNext() bool {
125+
return len(i.entries) > 0
126+
}
127+
128+
func (i *neighborEntryIterator) Next() *NeighborEntry {
129+
if len(i.entries) == 0 {
130+
return nil
131+
}
132+
entry := i.entries[0]
133+
i.entries = i.entries[1:]
134+
return entry
135+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//go:build !linux
2+
3+
package libbox
4+
5+
import "os"
6+
7+
type NeighborEntry struct {
8+
Address string
9+
MACAddress string
10+
Hostname string
11+
}
12+
13+
type NeighborEntryIterator interface {
14+
Next() *NeighborEntry
15+
HasNext() bool
16+
}
17+
18+
type NeighborSubscription struct{}
19+
20+
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
21+
return nil, os.ErrInvalid
22+
}
23+
24+
func (s *NeighborSubscription) Close() {}

experimental/libbox/platform.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ type PlatformInterface interface {
2121
SystemCertificates() StringIterator
2222
ClearDNSCache()
2323
SendNotification(notification *Notification) error
24+
StartNeighborMonitor(listener NeighborUpdateListener) error
25+
CloseNeighborMonitor(listener NeighborUpdateListener) error
26+
}
27+
28+
type NeighborUpdateListener interface {
29+
UpdateNeighborTable(entries NeighborEntryIterator)
2430
}
2531

2632
type ConnectionOwner struct {

experimental/libbox/service.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,43 @@ func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notifi
220220
return w.iif.SendNotification((*Notification)(notification))
221221
}
222222

223+
func (w *platformInterfaceWrapper) UsePlatformNeighborResolver() bool {
224+
return true
225+
}
226+
227+
func (w *platformInterfaceWrapper) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
228+
return w.iif.StartNeighborMonitor(&neighborUpdateListenerWrapper{listener: listener})
229+
}
230+
231+
func (w *platformInterfaceWrapper) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
232+
return w.iif.CloseNeighborMonitor(nil)
233+
}
234+
235+
type neighborUpdateListenerWrapper struct {
236+
listener adapter.NeighborUpdateListener
237+
}
238+
239+
func (w *neighborUpdateListenerWrapper) UpdateNeighborTable(entries NeighborEntryIterator) {
240+
var result []adapter.NeighborEntry
241+
for entries.HasNext() {
242+
entry := entries.Next()
243+
address, err := netip.ParseAddr(entry.Address)
244+
if err != nil {
245+
continue
246+
}
247+
macAddress, err := net.ParseMAC(entry.MACAddress)
248+
if err != nil {
249+
continue
250+
}
251+
result = append(result, adapter.NeighborEntry{
252+
Address: address,
253+
MACAddress: macAddress,
254+
Hostname: entry.Hostname,
255+
})
256+
}
257+
w.listener.UpdateNeighborTable(result)
258+
}
259+
223260
func AvailablePort(startPort int32) (int32, error) {
224261
for port := int(startPort); ; port++ {
225262
if port > 65535 {

route/neighbor_resolver_linux.go

Lines changed: 9 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package route
44

55
import (
66
"bufio"
7-
"encoding/binary"
87
"encoding/hex"
98
"net"
109
"net/netip"
@@ -204,43 +203,17 @@ func (r *neighborResolver) subscribeNeighborUpdates() {
204203
continue
205204
}
206205
for _, message := range messages {
207-
switch message.Header.Type {
208-
case unix.RTM_NEWNEIGH:
209-
var neighMessage rtnetlink.NeighMessage
210-
unmarshalErr := neighMessage.UnmarshalBinary(message.Data)
211-
if unmarshalErr != nil {
212-
continue
213-
}
214-
if neighMessage.Attributes == nil {
215-
continue
216-
}
217-
if neighMessage.Attributes.LLAddress == nil || len(neighMessage.Attributes.Address) == 0 {
218-
continue
219-
}
220-
address, ok := netip.AddrFromSlice(neighMessage.Attributes.Address)
221-
if !ok {
222-
continue
223-
}
224-
r.access.Lock()
225-
r.neighborIPToMAC[address] = slices.Clone(neighMessage.Attributes.LLAddress)
226-
r.access.Unlock()
227-
case unix.RTM_DELNEIGH:
228-
var neighMessage rtnetlink.NeighMessage
229-
unmarshalErr := neighMessage.UnmarshalBinary(message.Data)
230-
if unmarshalErr != nil {
231-
continue
232-
}
233-
if neighMessage.Attributes == nil || len(neighMessage.Attributes.Address) == 0 {
234-
continue
235-
}
236-
address, ok := netip.AddrFromSlice(neighMessage.Attributes.Address)
237-
if !ok {
238-
continue
239-
}
240-
r.access.Lock()
206+
address, mac, isDelete, ok := ParseNeighborMessage(message)
207+
if !ok {
208+
continue
209+
}
210+
r.access.Lock()
211+
if isDelete {
241212
delete(r.neighborIPToMAC, address)
242-
r.access.Unlock()
213+
} else {
214+
r.neighborIPToMAC[address] = mac
243215
}
216+
r.access.Unlock()
244217
}
245218
}
246219
}
@@ -554,43 +527,3 @@ func (r *neighborResolver) parseKeaCSV6(file *os.File, ipToMAC map[netip.Addr]ne
554527
}
555528
}
556529
}
557-
558-
func extractMACFromDUID(duid []byte) (net.HardwareAddr, bool) {
559-
if len(duid) < 4 {
560-
return nil, false
561-
}
562-
duidType := binary.BigEndian.Uint16(duid[0:2])
563-
hwType := binary.BigEndian.Uint16(duid[2:4])
564-
if hwType != 1 {
565-
return nil, false
566-
}
567-
switch duidType {
568-
case 1:
569-
if len(duid) < 14 {
570-
return nil, false
571-
}
572-
return net.HardwareAddr(slices.Clone(duid[8:14])), true
573-
case 3:
574-
if len(duid) < 10 {
575-
return nil, false
576-
}
577-
return net.HardwareAddr(slices.Clone(duid[4:10])), true
578-
}
579-
return nil, false
580-
}
581-
582-
func extractMACFromEUI64(address netip.Addr) (net.HardwareAddr, bool) {
583-
if !address.Is6() {
584-
return nil, false
585-
}
586-
b := address.As16()
587-
if b[11] != 0xff || b[12] != 0xfe {
588-
return nil, false
589-
}
590-
return net.HardwareAddr{b[8] ^ 0x02, b[9], b[10], b[13], b[14], b[15]}, true
591-
}
592-
593-
func parseDUID(s string) ([]byte, error) {
594-
cleaned := strings.ReplaceAll(s, ":", "")
595-
return hex.DecodeString(cleaned)
596-
}

0 commit comments

Comments
 (0)