sing-tun/monitor_linux.go
2022-07-15 14:59:26 +08:00

166 lines
3.6 KiB
Go

package tun
import (
"context"
"net"
"os"
"sync"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/x/list"
"github.com/vishvananda/netlink"
)
type networkUpdateMonitor struct {
routeUpdate chan netlink.RouteUpdate
linkUpdate chan netlink.LinkUpdate
close chan struct{}
errorHandler E.Handler
access sync.Mutex
callbacks list.List[NetworkUpdateCallback]
}
func NewNetworkUpdateMonitor(errorHandler E.Handler) (NetworkUpdateMonitor, error) {
return &networkUpdateMonitor{
routeUpdate: make(chan netlink.RouteUpdate, 2),
linkUpdate: make(chan netlink.LinkUpdate, 2),
close: make(chan struct{}),
errorHandler: errorHandler,
}, nil
}
func (m *networkUpdateMonitor) RegisterCallback(callback NetworkUpdateCallback) *list.Element[NetworkUpdateCallback] {
m.access.Lock()
defer m.access.Unlock()
return m.callbacks.PushBack(callback)
}
func (m *networkUpdateMonitor) UnregisterCallback(element *list.Element[NetworkUpdateCallback]) {
m.access.Lock()
defer m.access.Unlock()
m.callbacks.Remove(element)
}
func (m *networkUpdateMonitor) emit() {
m.access.Lock()
callbacks := m.callbacks.Array()
m.access.Unlock()
for _, callback := range callbacks {
err := callback()
if err != nil {
m.errorHandler.NewError(context.Background(), err)
}
}
}
func (m *networkUpdateMonitor) Start() error {
err := netlink.RouteSubscribe(m.routeUpdate, m.close)
if err != nil {
return err
}
err = netlink.LinkSubscribe(m.linkUpdate, m.close)
if err != nil {
return err
}
if err != nil {
return err
}
go m.loopUpdate()
return nil
}
func (m *networkUpdateMonitor) loopUpdate() {
for {
select {
case <-m.close:
return
case <-m.routeUpdate:
case <-m.linkUpdate:
}
m.emit()
}
}
func (m *networkUpdateMonitor) Close() error {
select {
case <-m.close:
return os.ErrClosed
default:
}
close(m.close)
return nil
}
type defaultInterfaceMonitor struct {
defaultInterfaceName string
defaultInterfaceIndex int
networkMonitor NetworkUpdateMonitor
element *list.Element[NetworkUpdateCallback]
callback DefaultInterfaceUpdateCallback
}
func NewDefaultInterfaceMonitor(networkMonitor NetworkUpdateMonitor, callback DefaultInterfaceUpdateCallback) (DefaultInterfaceMonitor, error) {
return &defaultInterfaceMonitor{
networkMonitor: networkMonitor,
callback: callback,
}, nil
}
func (m *defaultInterfaceMonitor) Start() error {
err := m.checkUpdate()
if err != nil {
return err
}
m.element = m.networkMonitor.RegisterCallback(m.checkUpdate)
return nil
}
func (m *defaultInterfaceMonitor) Close() error {
m.networkMonitor.UnregisterCallback(m.element)
return nil
}
func (m *defaultInterfaceMonitor) checkUpdate() error {
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
if err != nil {
return err
}
for _, route := range routes {
var link netlink.Link
link, err = netlink.LinkByIndex(route.LinkIndex)
if err != nil {
return err
}
if link.Attrs().Flags&net.FlagUp == 0 {
continue
}
if link.Type() == "tuntap" {
continue
}
oldInterface := m.defaultInterfaceName
oldIndex := m.defaultInterfaceIndex
m.defaultInterfaceName = link.Attrs().Name
m.defaultInterfaceIndex = link.Attrs().Index
if oldInterface == m.defaultInterfaceName && oldIndex == m.defaultInterfaceIndex {
return nil
}
m.callback()
return nil
}
return E.New("no route to internet")
}
func (m *defaultInterfaceMonitor) DefaultInterfaceName() string {
return m.defaultInterfaceName
}
func (m *defaultInterfaceMonitor) DefaultInterfaceIndex() int {
return m.defaultInterfaceIndex
}