Add Linux WI-FI state support
Support monitoring WIFI state on Linux through: - NetworkManager (D-Bus) - IWD (D-Bus) - wpa_supplicant (control socket) - ConnMan (D-Bus)
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type NetworkManager interface {
|
type NetworkManager interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
|
Initialize(ruleSets []RuleSet)
|
||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
UpdateInterfaces() error
|
UpdateInterfaces() error
|
||||||
DefaultNetworkInterface() *NetworkInterface
|
DefaultNetworkInterface() *NetworkInterface
|
||||||
@@ -24,9 +25,10 @@ type NetworkManager interface {
|
|||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
NeedWIFIState() bool
|
||||||
WIFIState() WIFIState
|
WIFIState() WIFIState
|
||||||
ResetNetwork()
|
|
||||||
UpdateWIFIState()
|
UpdateWIFIState()
|
||||||
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkOptions struct {
|
type NetworkOptions struct {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ type Router interface {
|
|||||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
AppendTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
|
|||||||
2
box.go
2
box.go
@@ -184,7 +184,7 @@ func New(options Options) (*Box, error) {
|
|||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
}
|
}
|
||||||
|
|||||||
9
common/settings/wifi.go
Normal file
9
common/settings/wifi.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/adapter"
|
||||||
|
|
||||||
|
type WIFIMonitor interface {
|
||||||
|
ReadWIFIState() adapter.WIFIState
|
||||||
|
Start() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
46
common/settings/wifi_linux.go
Normal file
46
common/settings/wifi_linux.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LinuxWIFIMonitor struct {
|
||||||
|
monitor WIFIMonitor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
|
||||||
|
monitors := []func(func(adapter.WIFIState)) (WIFIMonitor, error){
|
||||||
|
newNetworkManagerMonitor,
|
||||||
|
newIWDMonitor,
|
||||||
|
newWpaSupplicantMonitor,
|
||||||
|
newConnManMonitor,
|
||||||
|
}
|
||||||
|
var errors []error
|
||||||
|
for _, factory := range monitors {
|
||||||
|
monitor, err := factory(callback)
|
||||||
|
if err == nil {
|
||||||
|
return &LinuxWIFIMonitor{monitor: monitor}, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
return nil, E.Cause(E.Errors(errors...), "no supported WIFI manager found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LinuxWIFIMonitor) ReadWIFIState() adapter.WIFIState {
|
||||||
|
return m.monitor.ReadWIFIState()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LinuxWIFIMonitor) Start() error {
|
||||||
|
if m.monitor != nil {
|
||||||
|
return m.monitor.Start()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LinuxWIFIMonitor) Close() error {
|
||||||
|
if m.monitor != nil {
|
||||||
|
return m.monitor.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
166
common/settings/wifi_linux_connman.go
Normal file
166
common/settings/wifi_linux_connman.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type connmanMonitor struct {
|
||||||
|
conn *dbus.Conn
|
||||||
|
callback func(adapter.WIFIState)
|
||||||
|
cancel context.CancelFunc
|
||||||
|
signalChan chan *dbus.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnManMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
|
||||||
|
conn, err := dbus.ConnectSystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmObj := conn.Object("net.connman", "/")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
call := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0)
|
||||||
|
if call.Err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, call.Err
|
||||||
|
}
|
||||||
|
return &connmanMonitor{conn: conn, callback: callback}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmObj := m.conn.Object("net.connman", "/")
|
||||||
|
var services []interface{}
|
||||||
|
err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services)
|
||||||
|
if err != nil {
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range services {
|
||||||
|
servicePair, ok := service.([]interface{})
|
||||||
|
if !ok || len(servicePair) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceProps, ok := servicePair[1].(map[string]dbus.Variant)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
typeVariant, hasType := serviceProps["Type"]
|
||||||
|
if !hasType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
serviceType, ok := typeVariant.Value().(string)
|
||||||
|
if !ok || serviceType != "wifi" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stateVariant, hasState := serviceProps["State"]
|
||||||
|
if !hasState {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state, ok := stateVariant.Value().(string)
|
||||||
|
if !ok || (state != "online" && state != "ready") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nameVariant, hasName := serviceProps["Name"]
|
||||||
|
if !hasName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ssid, ok := nameVariant.Value().(string)
|
||||||
|
if !ok || ssid == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bssidVariant, hasBSSID := serviceProps["BSSID"]
|
||||||
|
if !hasBSSID {
|
||||||
|
return adapter.WIFIState{SSID: ssid}
|
||||||
|
}
|
||||||
|
bssid, ok := bssidVariant.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
return adapter.WIFIState{SSID: ssid}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter.WIFIState{
|
||||||
|
SSID: ssid,
|
||||||
|
BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *connmanMonitor) Start() error {
|
||||||
|
if m.callback == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
m.cancel = cancel
|
||||||
|
|
||||||
|
m.signalChan = make(chan *dbus.Signal, 10)
|
||||||
|
m.conn.Signal(m.signalChan)
|
||||||
|
|
||||||
|
err := m.conn.AddMatchSignal(
|
||||||
|
dbus.WithMatchInterface("net.connman.Service"),
|
||||||
|
dbus.WithMatchSender("net.connman"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
go m.monitorSignals(ctx, m.signalChan, state)
|
||||||
|
m.callback(state)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *connmanMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case signal, ok := <-signalChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// godbus Signal.Name uses "interface.member" format (e.g. "net.connman.Service.PropertyChanged"),
|
||||||
|
// not just the member name. This differs from the D-Bus signal member in the match rule.
|
||||||
|
if signal.Name == "net.connman.Service.PropertyChanged" {
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
if state != lastState {
|
||||||
|
lastState = state
|
||||||
|
m.callback(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *connmanMonitor) Close() error {
|
||||||
|
if m.cancel != nil {
|
||||||
|
m.cancel()
|
||||||
|
}
|
||||||
|
if m.signalChan != nil {
|
||||||
|
m.conn.RemoveSignal(m.signalChan)
|
||||||
|
close(m.signalChan)
|
||||||
|
}
|
||||||
|
if m.conn != nil {
|
||||||
|
m.conn.RemoveMatchSignal(
|
||||||
|
dbus.WithMatchInterface("net.connman.Service"),
|
||||||
|
dbus.WithMatchSender("net.connman"),
|
||||||
|
)
|
||||||
|
return m.conn.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
188
common/settings/wifi_linux_iwd.go
Normal file
188
common/settings/wifi_linux_iwd.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type iwdMonitor struct {
|
||||||
|
conn *dbus.Conn
|
||||||
|
callback func(adapter.WIFIState)
|
||||||
|
cancel context.CancelFunc
|
||||||
|
signalChan chan *dbus.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIWDMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
|
||||||
|
conn, err := dbus.ConnectSystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
iwdObj := conn.Object("net.connman.iwd", "/")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
call := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0)
|
||||||
|
if call.Err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, call.Err
|
||||||
|
}
|
||||||
|
return &iwdMonitor{conn: conn, callback: callback}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *iwdMonitor) ReadWIFIState() adapter.WIFIState {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
iwdObj := m.conn.Object("net.connman.iwd", "/")
|
||||||
|
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||||
|
err := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&objects)
|
||||||
|
if err != nil {
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, interfaces := range objects {
|
||||||
|
stationProps, hasStation := interfaces["net.connman.iwd.Station"]
|
||||||
|
if !hasStation {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stateVariant, hasState := stationProps["State"]
|
||||||
|
if !hasState {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state, ok := stateVariant.Value().(string)
|
||||||
|
if !ok || state != "connected" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedNetworkVariant, hasNetwork := stationProps["ConnectedNetwork"]
|
||||||
|
if !hasNetwork {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
networkPath, ok := connectedNetworkVariant.Value().(dbus.ObjectPath)
|
||||||
|
if !ok || networkPath == "/" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
networkInterfaces, hasNetworkPath := objects[networkPath]
|
||||||
|
if !hasNetworkPath {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
networkProps, hasNetworkInterface := networkInterfaces["net.connman.iwd.Network"]
|
||||||
|
if !hasNetworkInterface {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nameVariant, hasName := networkProps["Name"]
|
||||||
|
if !hasName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ssid, ok := nameVariant.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedBSSVariant, hasBSS := stationProps["ConnectedAccessPoint"]
|
||||||
|
if !hasBSS {
|
||||||
|
return adapter.WIFIState{SSID: ssid}
|
||||||
|
}
|
||||||
|
bssPath, ok := connectedBSSVariant.Value().(dbus.ObjectPath)
|
||||||
|
if !ok || bssPath == "/" {
|
||||||
|
return adapter.WIFIState{SSID: ssid}
|
||||||
|
}
|
||||||
|
|
||||||
|
bssInterfaces, hasBSSPath := objects[bssPath]
|
||||||
|
if !hasBSSPath {
|
||||||
|
return adapter.WIFIState{SSID: ssid}
|
||||||
|
}
|
||||||
|
|
||||||
|
bssProps, hasBSSInterface := bssInterfaces["net.connman.iwd.BasicServiceSet"]
|
||||||
|
if !hasBSSInterface {
|
||||||
|
return adapter.WIFIState{SSID: ssid}
|
||||||
|
}
|
||||||
|
|
||||||
|
addressVariant, hasAddress := bssProps["Address"]
|
||||||
|
if !hasAddress {
|
||||||
|
return adapter.WIFIState{SSID: ssid}
|
||||||
|
}
|
||||||
|
bssid, ok := addressVariant.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
return adapter.WIFIState{SSID: ssid}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter.WIFIState{
|
||||||
|
SSID: ssid,
|
||||||
|
BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *iwdMonitor) Start() error {
|
||||||
|
if m.callback == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
m.cancel = cancel
|
||||||
|
|
||||||
|
m.signalChan = make(chan *dbus.Signal, 10)
|
||||||
|
m.conn.Signal(m.signalChan)
|
||||||
|
|
||||||
|
err := m.conn.AddMatchSignal(
|
||||||
|
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
|
||||||
|
dbus.WithMatchSender("net.connman.iwd"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
go m.monitorSignals(ctx, m.signalChan, state)
|
||||||
|
m.callback(state)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *iwdMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case signal, ok := <-signalChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" {
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
if state != lastState {
|
||||||
|
lastState = state
|
||||||
|
m.callback(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *iwdMonitor) Close() error {
|
||||||
|
if m.cancel != nil {
|
||||||
|
m.cancel()
|
||||||
|
}
|
||||||
|
if m.signalChan != nil {
|
||||||
|
m.conn.RemoveSignal(m.signalChan)
|
||||||
|
close(m.signalChan)
|
||||||
|
}
|
||||||
|
if m.conn != nil {
|
||||||
|
m.conn.RemoveMatchSignal(
|
||||||
|
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
|
||||||
|
dbus.WithMatchSender("net.connman.iwd"),
|
||||||
|
)
|
||||||
|
return m.conn.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
163
common/settings/wifi_linux_nm.go
Normal file
163
common/settings/wifi_linux_nm.go
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type networkManagerMonitor struct {
|
||||||
|
conn *dbus.Conn
|
||||||
|
callback func(adapter.WIFIState)
|
||||||
|
cancel context.CancelFunc
|
||||||
|
signalChan chan *dbus.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNetworkManagerMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
|
||||||
|
conn, err := dbus.ConnectSystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nmObj := conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
var state uint32
|
||||||
|
err = nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "State").Store(&state)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &networkManagerMonitor{conn: conn, callback: callback}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *networkManagerMonitor) ReadWIFIState() adapter.WIFIState {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
nmObj := m.conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
|
||||||
|
|
||||||
|
var activeConnectionPaths []dbus.ObjectPath
|
||||||
|
err := nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "ActiveConnections").Store(&activeConnectionPaths)
|
||||||
|
if err != nil || len(activeConnectionPaths) == 0 {
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, connectionPath := range activeConnectionPaths {
|
||||||
|
connObj := m.conn.Object("org.freedesktop.NetworkManager", connectionPath)
|
||||||
|
|
||||||
|
var devicePaths []dbus.ObjectPath
|
||||||
|
err = connObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Connection.Active", "Devices").Store(&devicePaths)
|
||||||
|
if err != nil || len(devicePaths) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, devicePath := range devicePaths {
|
||||||
|
deviceObj := m.conn.Object("org.freedesktop.NetworkManager", devicePath)
|
||||||
|
|
||||||
|
var deviceType uint32
|
||||||
|
err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device", "DeviceType").Store(&deviceType)
|
||||||
|
if err != nil || deviceType != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessPointPath dbus.ObjectPath
|
||||||
|
err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device.Wireless", "ActiveAccessPoint").Store(&accessPointPath)
|
||||||
|
if err != nil || accessPointPath == "/" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
apObj := m.conn.Object("org.freedesktop.NetworkManager", accessPointPath)
|
||||||
|
|
||||||
|
var ssidBytes []byte
|
||||||
|
err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "Ssid").Store(&ssidBytes)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var hwAddress string
|
||||||
|
err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "HwAddress").Store(&hwAddress)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ssid := strings.TrimSpace(string(ssidBytes))
|
||||||
|
if ssid == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter.WIFIState{
|
||||||
|
SSID: ssid,
|
||||||
|
BSSID: strings.ToUpper(strings.ReplaceAll(hwAddress, ":", "")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *networkManagerMonitor) Start() error {
|
||||||
|
if m.callback == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
m.cancel = cancel
|
||||||
|
|
||||||
|
m.signalChan = make(chan *dbus.Signal, 10)
|
||||||
|
m.conn.Signal(m.signalChan)
|
||||||
|
|
||||||
|
err := m.conn.AddMatchSignal(
|
||||||
|
dbus.WithMatchSender("org.freedesktop.NetworkManager"),
|
||||||
|
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
go m.monitorSignals(ctx, m.signalChan, state)
|
||||||
|
m.callback(state)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *networkManagerMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case signal, ok := <-signalChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" {
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
if state != lastState {
|
||||||
|
lastState = state
|
||||||
|
m.callback(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *networkManagerMonitor) Close() error {
|
||||||
|
if m.cancel != nil {
|
||||||
|
m.cancel()
|
||||||
|
}
|
||||||
|
if m.signalChan != nil {
|
||||||
|
m.conn.RemoveSignal(m.signalChan)
|
||||||
|
close(m.signalChan)
|
||||||
|
}
|
||||||
|
if m.conn != nil {
|
||||||
|
m.conn.RemoveMatchSignal(
|
||||||
|
dbus.WithMatchSender("org.freedesktop.NetworkManager"),
|
||||||
|
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
|
||||||
|
)
|
||||||
|
return m.conn.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
225
common/settings/wifi_linux_wpa.go
Normal file
225
common/settings/wifi_linux_wpa.go
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
)
|
||||||
|
|
||||||
|
var wpaSocketCounter atomic.Uint64
|
||||||
|
|
||||||
|
type wpaSupplicantMonitor struct {
|
||||||
|
socketPath string
|
||||||
|
callback func(adapter.WIFIState)
|
||||||
|
cancel context.CancelFunc
|
||||||
|
monitorConn *net.UnixConn
|
||||||
|
connMutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
|
||||||
|
socketDirs := []string{"/var/run/wpa_supplicant", "/run/wpa_supplicant"}
|
||||||
|
for _, socketDir := range socketDirs {
|
||||||
|
entries, err := os.ReadDir(socketDir)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() || entry.Name() == "." || entry.Name() == ".." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
socketPath := filepath.Join(socketDir, entry.Name())
|
||||||
|
id := wpaSocketCounter.Add(1)
|
||||||
|
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d-%d", os.Getpid(), id), Net: "unixgram"}
|
||||||
|
remoteAddr := &net.UnixAddr{Name: socketPath, Net: "unixgram"}
|
||||||
|
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
return &wpaSupplicantMonitor{socketPath: socketPath, callback: callback}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
|
||||||
|
id := wpaSocketCounter.Add(1)
|
||||||
|
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d-%d", os.Getpid(), id), Net: "unixgram"}
|
||||||
|
remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
|
||||||
|
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
conn.SetDeadline(time.Now().Add(3 * time.Second))
|
||||||
|
|
||||||
|
status, err := m.sendCommand(conn, "STATUS")
|
||||||
|
if err != nil {
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ssid, bssid string
|
||||||
|
var connected bool
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(status))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "wpa_state=") {
|
||||||
|
state := strings.TrimPrefix(line, "wpa_state=")
|
||||||
|
connected = state == "COMPLETED"
|
||||||
|
} else if strings.HasPrefix(line, "ssid=") {
|
||||||
|
ssid = strings.TrimPrefix(line, "ssid=")
|
||||||
|
} else if strings.HasPrefix(line, "bssid=") {
|
||||||
|
bssid = strings.TrimPrefix(line, "bssid=")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connected || ssid == "" {
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter.WIFIState{
|
||||||
|
SSID: ssid,
|
||||||
|
BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendCommand sends a command to wpa_supplicant and returns the response.
|
||||||
|
// Commands are sent without trailing newlines per the wpa_supplicant control
|
||||||
|
// interface protocol - the official wpa_ctrl.c sends raw command strings.
|
||||||
|
func (m *wpaSupplicantMonitor) sendCommand(conn *net.UnixConn, command string) (string, error) {
|
||||||
|
_, err := conn.Write([]byte(command))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := string(buf[:n])
|
||||||
|
if strings.HasPrefix(response, "FAIL") {
|
||||||
|
return "", os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(response), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *wpaSupplicantMonitor) Start() error {
|
||||||
|
if m.callback == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
m.cancel = cancel
|
||||||
|
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
go m.monitorEvents(ctx, state)
|
||||||
|
m.callback(state)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adapter.WIFIState) {
|
||||||
|
var consecutiveErrors int
|
||||||
|
var debounceTimer *time.Timer
|
||||||
|
var debounceMutex sync.Mutex
|
||||||
|
|
||||||
|
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-mon-%d", os.Getpid()), Net: "unixgram"}
|
||||||
|
remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
|
||||||
|
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
m.connMutex.Lock()
|
||||||
|
m.monitorConn = conn
|
||||||
|
m.connMutex.Unlock()
|
||||||
|
|
||||||
|
// ATTACH/DETACH commands use os_strcmp() for exact matching in wpa_supplicant,
|
||||||
|
// so they must be sent without trailing newlines.
|
||||||
|
// See: https://w1.fi/cgit/hostap/tree/wpa_supplicant/ctrl_iface_unix.c
|
||||||
|
_, err = conn.Write([]byte("ATTACH"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil || !strings.HasPrefix(string(buf[:n]), "OK") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
debounceMutex.Lock()
|
||||||
|
if debounceTimer != nil {
|
||||||
|
debounceTimer.Stop()
|
||||||
|
}
|
||||||
|
debounceMutex.Unlock()
|
||||||
|
conn.Write([]byte("DETACH"))
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
consecutiveErrors++
|
||||||
|
if consecutiveErrors > 10 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
consecutiveErrors = 0
|
||||||
|
|
||||||
|
msg := string(buf[:n])
|
||||||
|
if strings.Contains(msg, "CTRL-EVENT-CONNECTED") || strings.Contains(msg, "CTRL-EVENT-DISCONNECTED") {
|
||||||
|
debounceMutex.Lock()
|
||||||
|
if debounceTimer != nil {
|
||||||
|
debounceTimer.Stop()
|
||||||
|
}
|
||||||
|
debounceTimer = time.AfterFunc(500*time.Millisecond, func() {
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
if state != lastState {
|
||||||
|
lastState = state
|
||||||
|
m.callback(state)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
debounceMutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *wpaSupplicantMonitor) Close() error {
|
||||||
|
if m.cancel != nil {
|
||||||
|
m.cancel()
|
||||||
|
}
|
||||||
|
m.connMutex.Lock()
|
||||||
|
if m.monitorConn != nil {
|
||||||
|
m.monitorConn.Close()
|
||||||
|
}
|
||||||
|
m.connMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
27
common/settings/wifi_stub.go
Normal file
27
common/settings/wifi_stub.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stubWIFIMonitor struct{}
|
||||||
|
|
||||||
|
func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *stubWIFIMonitor) ReadWIFIState() adapter.WIFIState {
|
||||||
|
return adapter.WIFIState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *stubWIFIMonitor) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *stubWIFIMonitor) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -412,7 +412,7 @@ Match default interface address.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
Only supported in graphical clients on Android and Apple platforms, or on Linux.
|
||||||
|
|
||||||
Match WiFi SSID.
|
Match WiFi SSID.
|
||||||
|
|
||||||
@@ -420,7 +420,7 @@ Match WiFi SSID.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
Only supported in graphical clients on Android and Apple platforms, or on Linux.
|
||||||
|
|
||||||
Match WiFi BSSID.
|
Match WiFi BSSID.
|
||||||
|
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。
|
||||||
|
|
||||||
匹配 WiFi SSID。
|
匹配 WiFi SSID。
|
||||||
|
|
||||||
@@ -419,7 +419,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。
|
||||||
|
|
||||||
匹配 WiFi BSSID。
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ Match default interface address.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
Only supported in graphical clients on Android and Apple platforms, or on Linux.
|
||||||
|
|
||||||
Match WiFi SSID.
|
Match WiFi SSID.
|
||||||
|
|
||||||
@@ -438,7 +438,7 @@ Match WiFi SSID.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
Only supported in graphical clients on Android and Apple platforms, or on Linux.
|
||||||
|
|
||||||
Match WiFi BSSID.
|
Match WiFi BSSID.
|
||||||
|
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。
|
||||||
|
|
||||||
匹配 WiFi SSID。
|
匹配 WiFi SSID。
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。
|
||||||
|
|
||||||
匹配 WiFi BSSID。
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ func (s *platformInterfaceStub) IncludeAllNetworks() bool {
|
|||||||
func (s *platformInterfaceStub) ClearDNSCache() {
|
func (s *platformInterfaceStub) ClearDNSCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *platformInterfaceStub) UsePlatformWIFIMonitor() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState {
|
func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState {
|
||||||
return adapter.WIFIState{}
|
return adapter.WIFIState{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type Interface interface {
|
|||||||
UnderNetworkExtension() bool
|
UnderNetworkExtension() bool
|
||||||
IncludeAllNetworks() bool
|
IncludeAllNetworks() bool
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
|
UsePlatformWIFIMonitor() bool
|
||||||
ReadWIFIState() adapter.WIFIState
|
ReadWIFIState() adapter.WIFIState
|
||||||
SystemCertificates() []string
|
SystemCertificates() []string
|
||||||
process.Searcher
|
process.Searcher
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func (s *BoxService) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) NeedWIFIState() bool {
|
func (s *BoxService) NeedWIFIState() bool {
|
||||||
return s.instance.Router().NeedWIFIState()
|
return s.instance.Network().NeedWIFIState()
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -224,6 +224,10 @@ func (w *platformInterfaceWrapper) ClearDNSCache() {
|
|||||||
w.iif.ClearDNSCache()
|
w.iif.ClearDNSCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) UsePlatformWIFIMonitor() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
|
func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
|
||||||
wifiState := w.iif.ReadWIFIState()
|
wifiState := w.iif.ReadWIFIState()
|
||||||
if wifiState == nil {
|
if wifiState == nil {
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
@@ -50,11 +52,14 @@ type NetworkManager struct {
|
|||||||
endpoint adapter.EndpointManager
|
endpoint adapter.EndpointManager
|
||||||
inbound adapter.InboundManager
|
inbound adapter.InboundManager
|
||||||
outbound adapter.OutboundManager
|
outbound adapter.OutboundManager
|
||||||
|
needWIFIState bool
|
||||||
|
wifiMonitor settings.WIFIMonitor
|
||||||
wifiState adapter.WIFIState
|
wifiState adapter.WIFIState
|
||||||
|
wifiStateMutex sync.RWMutex
|
||||||
started bool
|
started bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) {
|
func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions, dnsOptions option.DNSOptions) (*NetworkManager, error) {
|
||||||
defaultDomainResolver := common.PtrValueOrDefault(routeOptions.DefaultDomainResolver)
|
defaultDomainResolver := common.PtrValueOrDefault(routeOptions.DefaultDomainResolver)
|
||||||
if routeOptions.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
if routeOptions.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||||
return nil, E.New("`auto_detect_interface` is only supported on Linux, Windows and macOS")
|
return nil, E.New("`auto_detect_interface` is only supported on Linux, Windows and macOS")
|
||||||
@@ -89,6 +94,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
|
|||||||
endpoint: service.FromContext[adapter.EndpointManager](ctx),
|
endpoint: service.FromContext[adapter.EndpointManager](ctx),
|
||||||
inbound: service.FromContext[adapter.InboundManager](ctx),
|
inbound: service.FromContext[adapter.InboundManager](ctx),
|
||||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
|
needWIFIState: hasRule(routeOptions.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule),
|
||||||
}
|
}
|
||||||
if routeOptions.DefaultNetworkStrategy != nil {
|
if routeOptions.DefaultNetworkStrategy != nil {
|
||||||
if routeOptions.DefaultInterface != "" {
|
if routeOptions.DefaultInterface != "" {
|
||||||
@@ -183,11 +189,35 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case adapter.StartStatePostStart:
|
case adapter.StartStatePostStart:
|
||||||
|
if r.needWIFIState && !(r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor()) {
|
||||||
|
wifiMonitor, err := settings.NewWIFIMonitor(r.onWIFIStateChanged)
|
||||||
|
if err != nil {
|
||||||
|
if err != os.ErrInvalid {
|
||||||
|
r.logger.Warn(E.Cause(err, "create WIFI monitor"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.wifiMonitor = wifiMonitor
|
||||||
|
err = r.wifiMonitor.Start()
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Warn(E.Cause(err, "start WIFI monitor"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
r.started = true
|
r.started = true
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *NetworkManager) Initialize(ruleSets []adapter.RuleSet) {
|
||||||
|
for _, ruleSet := range ruleSets {
|
||||||
|
metadata := ruleSet.Metadata()
|
||||||
|
if metadata.ContainsWIFIRule {
|
||||||
|
r.needWIFIState = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *NetworkManager) Close() error {
|
func (r *NetworkManager) Close() error {
|
||||||
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
@@ -219,6 +249,13 @@ func (r *NetworkManager) Close() error {
|
|||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
}
|
}
|
||||||
|
if r.wifiMonitor != nil {
|
||||||
|
monitor.Start("close WIFI monitor")
|
||||||
|
err = E.Append(err, r.wifiMonitor.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close WIFI monitor")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,20 +413,39 @@ func (r *NetworkManager) PackageManager() tun.PackageManager {
|
|||||||
return r.packageManager
|
return r.packageManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *NetworkManager) NeedWIFIState() bool {
|
||||||
|
return r.needWIFIState
|
||||||
|
}
|
||||||
|
|
||||||
func (r *NetworkManager) WIFIState() adapter.WIFIState {
|
func (r *NetworkManager) WIFIState() adapter.WIFIState {
|
||||||
|
r.wifiStateMutex.RLock()
|
||||||
|
defer r.wifiStateMutex.RUnlock()
|
||||||
return r.wifiState
|
return r.wifiState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NetworkManager) UpdateWIFIState() {
|
func (r *NetworkManager) onWIFIStateChanged(state adapter.WIFIState) {
|
||||||
if r.platformInterface != nil {
|
r.wifiStateMutex.Lock()
|
||||||
state := r.platformInterface.ReadWIFIState()
|
if state == r.wifiState {
|
||||||
if state != r.wifiState {
|
r.wifiStateMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
r.wifiState = state
|
r.wifiState = state
|
||||||
|
r.wifiStateMutex.Unlock()
|
||||||
if state.SSID != "" {
|
if state.SSID != "" {
|
||||||
r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID)
|
r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *NetworkManager) UpdateWIFIState() {
|
||||||
|
var state adapter.WIFIState
|
||||||
|
if r.wifiMonitor != nil {
|
||||||
|
state = r.wifiMonitor.ReadWIFIState()
|
||||||
|
} else if r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor() {
|
||||||
|
state = r.platformInterface.ReadWIFIState()
|
||||||
|
} else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
r.onWIFIStateChanged(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NetworkManager) ResetNetwork() {
|
func (r *NetworkManager) ResetNetwork() {
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ type Router struct {
|
|||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
trackers []adapter.ConnectionTracker
|
trackers []adapter.ConnectionTracker
|
||||||
platformInterface platform.Interface
|
platformInterface platform.Interface
|
||||||
needWIFIState bool
|
|
||||||
started bool
|
started bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +56,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
|
|||||||
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
||||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||||
platformInterface: service.FromContext[platform.Interface](ctx),
|
platformInterface: service.FromContext[platform.Interface](ctx),
|
||||||
needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,15 +111,13 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
if cacheContext != nil {
|
if cacheContext != nil {
|
||||||
cacheContext.Close()
|
cacheContext.Close()
|
||||||
}
|
}
|
||||||
|
r.network.Initialize(r.ruleSets)
|
||||||
needFindProcess := r.needFindProcess
|
needFindProcess := r.needFindProcess
|
||||||
for _, ruleSet := range r.ruleSets {
|
for _, ruleSet := range r.ruleSets {
|
||||||
metadata := ruleSet.Metadata()
|
metadata := ruleSet.Metadata()
|
||||||
if metadata.ContainsProcessRule {
|
if metadata.ContainsProcessRule {
|
||||||
needFindProcess = true
|
needFindProcess = true
|
||||||
}
|
}
|
||||||
if metadata.ContainsWIFIRule {
|
|
||||||
r.needWIFIState = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if needFindProcess {
|
if needFindProcess {
|
||||||
if r.platformInterface != nil {
|
if r.platformInterface != nil {
|
||||||
@@ -195,10 +191,6 @@ func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) {
|
|||||||
return ruleSet, loaded
|
return ruleSet, loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) NeedWIFIState() bool {
|
|
||||||
return r.needWIFIState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Rules() []adapter.Rule {
|
func (r *Router) Rules() []adapter.Rule {
|
||||||
return r.rules
|
return r.rules
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user