Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support FreeBSD #22

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ build:
GOOS=linux GOARCH=386 go build -v -tags with_gvisor .
GOOS=linux GOARCH=arm go build -v -tags with_gvisor .
GOOS=windows GOARCH=amd64 go build -v -tags with_gvisor .
GOOS=freebsd GOARCH=amd64 go build -v -tags with_gvisor .

fmt:
@gofumpt -l -w .
Expand Down
166 changes: 166 additions & 0 deletions monitor_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package tun

import (
"net"
"net/netip"
"sync"
"time"

"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/common/x/list"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
)

var _ NetworkUpdateMonitor = (*networkUpdateMonitor)(nil)

type networkUpdateMonitor struct {
access sync.Mutex
callbacks list.List[NetworkUpdateCallback]

closeOnce sync.Once
done chan struct{}
logger logger.Logger
}

func NewNetworkUpdateMonitor(logger logger.Logger) (NetworkUpdateMonitor, error) {

return &networkUpdateMonitor{
logger: logger,
done: make(chan struct{}),
}, nil
}

// Close implements NetworkUpdateMonitor.
func (m *networkUpdateMonitor) Close() error {
m.closeOnce.Do(func() {
close(m.done)
})
return nil
}

// Start implements NetworkUpdateMonitor.
func (m *networkUpdateMonitor) Start() error {
go m.loopUpdate()
return nil
}

func (m *networkUpdateMonitor) loopUpdate() {

useSocket(unix.AF_ROUTE, unix.SOCK_RAW|unix.SOCK_CLOEXEC, unix.AF_UNSPEC, func(socketFd int) error {

for {
select {
case <-m.done:
return nil
case <-time.After(time.Second):
}
err := m.updater(socketFd)
if err != nil {
m.logger.Error("listen network update: ", err)
return nil
}
}

})

}

func (m *networkUpdateMonitor) updater(socketFd int) error {
buffer := buf.NewPacket()
defer buffer.Release()

n, err := unix.Read(socketFd, buffer.FreeBytes())
if err != nil {
return err
}
buffer.Truncate(n)

messages, err := route.ParseRIB(route.RIBTypeRoute, buffer.Bytes())
if err != nil {
return err
}

for _, message := range messages {
if _, isRouteMessage := message.(*route.RouteMessage); isRouteMessage {
m.emit()
return nil
}
}
return nil
}

// checkUpdate find the first ipv4 default gateway, then emit event
func (m *defaultInterfaceMonitor) checkUpdate() error {
// TODO: ipv4 and ipv6 unix.AF_UNSPEC
ribMessage, err := route.FetchRIB(unix.AF_INET, route.RIBTypeRoute, 0)
if err != nil {
return err
}
routeMessages, err := route.ParseRIB(route.RIBTypeRoute, ribMessage)
if err != nil {
return err
}

for _, rawRouteMessage := range routeMessages {
routeMessage := rawRouteMessage.(*route.RouteMessage)

// TODO: ipv6
// switch addr := routeMessage.Addrs[unix.AF_UNSPEC].(type) {
// case *route.Inet4Addr:

// case *route.Inet6Addr:

// }

// dst addr of this route should be 0.0.0.0
if destination, isIPv4Destination := routeMessage.Addrs[unix.RTAX_DST].(*route.Inet4Addr); !isIPv4Destination || destination.IP != netip.IPv4Unspecified().As4() {
continue
}

// netmask should be vaild ipv4 addr
if mask, isIPv4Mask := routeMessage.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr); !isIPv4Mask {
continue
} else {
// netmask should be 0.0.0.0
if ones, _ := net.IPMask(mask.IP[:]).Size(); ones != 0 {
continue
}
}

// the route should be enabled && gateway && static
flag := unix.RTF_UP | unix.RTF_GATEWAY | unix.RTF_STATIC
if routeMessage.Flags&(flag) != flag {
continue
}

// the interface of above route should not be loop dev
if routeInterface, err := net.InterfaceByIndex(routeMessage.Index); err != nil || routeInterface.Flags&net.FlagLoopback != 0 {
continue
} else {

if routeInterface.Name == m.defaultInterfaceName && routeInterface.Index == m.defaultInterfaceIndex {
return nil
}

// update default interface
m.defaultInterfaceName = routeInterface.Name
m.defaultInterfaceIndex = routeInterface.Index
m.emit(EventInterfaceUpdate)

return nil
}
}

if m.options.UnderNetworkExtension {
// TODO: fallback of get default interface
m.logger.Warn("Not implemented: UnderNetworkExtension")
// defaultInterface, err = getDefaultInterfaceBySocket()
// if err != nil {
// return err
// }
}

return ErrNoRoute
}
2 changes: 1 addition & 1 deletion monitor_other.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !(linux || windows || darwin)
//go:build !(linux || windows || darwin || freebsd)

package tun

Expand Down
2 changes: 1 addition & 1 deletion monitor_shared.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build linux || windows || darwin
//go:build linux || windows || darwin || freebsd

package tun

Expand Down
Loading