Skip to content

Commit 53caef7

Browse files
committed
Add macOS support for MAC and hostname rule items
1 parent 701a2fa commit 53caef7

12 files changed

Lines changed: 956 additions & 414 deletions

experimental/libbox/neighbor.go

Lines changed: 2 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
1-
//go:build linux
2-
31
package libbox
42

53
import (
64
"net"
75
"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"
166
)
177

188
type NeighborEntry struct {
199
Address string
20-
MACAddress string
10+
MacAddress string
2111
Hostname string
2212
}
2313

@@ -30,88 +20,16 @@ type NeighborSubscription struct {
3020
done chan struct{}
3121
}
3222

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-
5623
func (s *NeighborSubscription) Close() {
5724
close(s.done)
5825
}
5926

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-
10927
func tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator {
11028
entries := make([]*NeighborEntry, 0, len(table))
11129
for address, mac := range table {
11230
entries = append(entries, &NeighborEntry{
11331
Address: address.String(),
114-
MACAddress: mac.String(),
32+
MacAddress: mac.String(),
11533
})
11634
}
11735
return &neighborEntryIterator{entries}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//go:build darwin
2+
3+
package libbox
4+
5+
import (
6+
"net"
7+
"net/netip"
8+
"os"
9+
"slices"
10+
"time"
11+
12+
"github.com/sagernet/sing-box/route"
13+
"github.com/sagernet/sing/common/buf"
14+
E "github.com/sagernet/sing/common/exceptions"
15+
16+
xroute "golang.org/x/net/route"
17+
"golang.org/x/sys/unix"
18+
)
19+
20+
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
21+
entries, err := route.ReadNeighborEntries()
22+
if err != nil {
23+
return nil, E.Cause(err, "initial neighbor dump")
24+
}
25+
table := make(map[netip.Addr]net.HardwareAddr)
26+
for _, entry := range entries {
27+
table[entry.Address] = entry.MACAddress
28+
}
29+
listener.UpdateNeighborTable(tableToIterator(table))
30+
routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
31+
if err != nil {
32+
return nil, E.Cause(err, "open route socket")
33+
}
34+
err = unix.SetNonblock(routeSocket, true)
35+
if err != nil {
36+
unix.Close(routeSocket)
37+
return nil, E.Cause(err, "set route socket nonblock")
38+
}
39+
subscription := &NeighborSubscription{
40+
done: make(chan struct{}),
41+
}
42+
go subscription.loop(listener, routeSocket, table)
43+
return subscription, nil
44+
}
45+
46+
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, routeSocket int, table map[netip.Addr]net.HardwareAddr) {
47+
routeSocketFile := os.NewFile(uintptr(routeSocket), "route")
48+
defer routeSocketFile.Close()
49+
buffer := buf.NewPacket()
50+
defer buffer.Release()
51+
for {
52+
select {
53+
case <-s.done:
54+
return
55+
default:
56+
}
57+
tv := unix.NsecToTimeval(int64(3 * time.Second))
58+
_ = unix.SetsockoptTimeval(routeSocket, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)
59+
n, err := routeSocketFile.Read(buffer.FreeBytes())
60+
if err != nil {
61+
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
62+
continue
63+
}
64+
select {
65+
case <-s.done:
66+
return
67+
default:
68+
}
69+
continue
70+
}
71+
messages, err := xroute.ParseRIB(xroute.RIBTypeRoute, buffer.FreeBytes()[:n])
72+
if err != nil {
73+
continue
74+
}
75+
changed := false
76+
for _, message := range messages {
77+
routeMessage, isRouteMessage := message.(*xroute.RouteMessage)
78+
if !isRouteMessage {
79+
continue
80+
}
81+
if routeMessage.Flags&unix.RTF_LLINFO == 0 {
82+
continue
83+
}
84+
address, mac, isDelete, ok := route.ParseRouteNeighborMessage(routeMessage)
85+
if !ok {
86+
continue
87+
}
88+
if isDelete {
89+
if _, exists := table[address]; exists {
90+
delete(table, address)
91+
changed = true
92+
}
93+
} else {
94+
existing, exists := table[address]
95+
if !exists || !slices.Equal(existing, mac) {
96+
table[address] = mac
97+
changed = true
98+
}
99+
}
100+
}
101+
if changed {
102+
listener.UpdateNeighborTable(tableToIterator(table))
103+
}
104+
}
105+
}
106+
107+
func ReadBootpdLeases() NeighborEntryIterator {
108+
leaseIPToMAC, ipToHostname, macToHostname := route.ReloadLeaseFiles([]string{"/var/db/dhcpd_leases"})
109+
entries := make([]*NeighborEntry, 0, len(leaseIPToMAC))
110+
for address, mac := range leaseIPToMAC {
111+
entry := &NeighborEntry{
112+
Address: address.String(),
113+
MacAddress: mac.String(),
114+
}
115+
hostname, found := ipToHostname[address]
116+
if !found {
117+
hostname = macToHostname[mac.String()]
118+
}
119+
entry.Hostname = hostname
120+
entries = append(entries, entry)
121+
}
122+
return &neighborEntryIterator{entries}
123+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
19+
entries, err := route.ReadNeighborEntries()
20+
if err != nil {
21+
return nil, E.Cause(err, "initial neighbor dump")
22+
}
23+
table := make(map[netip.Addr]net.HardwareAddr)
24+
for _, entry := range entries {
25+
table[entry.Address] = entry.MACAddress
26+
}
27+
listener.UpdateNeighborTable(tableToIterator(table))
28+
connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
29+
Groups: 1 << (unix.RTNLGRP_NEIGH - 1),
30+
})
31+
if err != nil {
32+
return nil, E.Cause(err, "subscribe neighbor updates")
33+
}
34+
subscription := &NeighborSubscription{
35+
done: make(chan struct{}),
36+
}
37+
go subscription.loop(listener, connection, table)
38+
return subscription, nil
39+
}
40+
41+
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, connection *netlink.Conn, table map[netip.Addr]net.HardwareAddr) {
42+
defer connection.Close()
43+
for {
44+
select {
45+
case <-s.done:
46+
return
47+
default:
48+
}
49+
err := connection.SetReadDeadline(time.Now().Add(3 * time.Second))
50+
if err != nil {
51+
return
52+
}
53+
messages, err := connection.Receive()
54+
if err != nil {
55+
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
56+
continue
57+
}
58+
select {
59+
case <-s.done:
60+
return
61+
default:
62+
}
63+
continue
64+
}
65+
changed := false
66+
for _, message := range messages {
67+
address, mac, isDelete, ok := route.ParseNeighborMessage(message)
68+
if !ok {
69+
continue
70+
}
71+
if isDelete {
72+
if _, exists := table[address]; exists {
73+
delete(table, address)
74+
changed = true
75+
}
76+
} else {
77+
existing, exists := table[address]
78+
if !exists || !slices.Equal(existing, mac) {
79+
table[address] = mac
80+
changed = true
81+
}
82+
}
83+
}
84+
if changed {
85+
listener.UpdateNeighborTable(tableToIterator(table))
86+
}
87+
}
88+
}
Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
1-
//go:build !linux
1+
//go:build !linux && !darwin
22

33
package libbox
44

55
import "os"
66

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) {
7+
func SubscribeNeighborTable(_ NeighborUpdateListener) (*NeighborSubscription, error) {
218
return nil, os.ErrInvalid
229
}
23-
24-
func (s *NeighborSubscription) Close() {}

experimental/libbox/platform.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type PlatformInterface interface {
2323
SendNotification(notification *Notification) error
2424
StartNeighborMonitor(listener NeighborUpdateListener) error
2525
CloseNeighborMonitor(listener NeighborUpdateListener) error
26+
RegisterMyInterface(name string)
2627
}
2728

2829
type NeighborUpdateListener interface {

experimental/libbox/service.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformO
7878
}
7979
options.FileDescriptor = dupFd
8080
w.myTunName = options.Name
81+
w.iif.RegisterMyInterface(options.Name)
8182
return tun.New(*options)
8283
}
8384

@@ -240,11 +241,14 @@ func (w *neighborUpdateListenerWrapper) UpdateNeighborTable(entries NeighborEntr
240241
var result []adapter.NeighborEntry
241242
for entries.HasNext() {
242243
entry := entries.Next()
244+
if entry == nil {
245+
continue
246+
}
243247
address, err := netip.ParseAddr(entry.Address)
244248
if err != nil {
245249
continue
246250
}
247-
macAddress, err := net.ParseMAC(entry.MACAddress)
251+
macAddress, err := net.ParseMAC(entry.MacAddress)
248252
if err != nil {
249253
continue
250254
}

0 commit comments

Comments
 (0)