From 0e50872d0452fdfc8dd04bcac89a66494c195d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 18 Nov 2023 13:51:32 +0800 Subject: [PATCH] Add `wifi_ssid` and `wifi_bssid` route and DNS rules --- adapter/router.go | 6 ++++ experimental/clashapi/cachefile/cache.go | 8 ++++- experimental/clashapi/server.go | 2 +- experimental/libbox/command_log.go | 2 +- experimental/libbox/platform.go | 10 ++++++ experimental/libbox/platform/interface.go | 1 + experimental/libbox/service.go | 8 +++++ option/rule.go | 2 ++ option/rule_dns.go | 2 ++ route/router.go | 26 +++++++++++++++ route/router_geo_resources.go | 8 +++++ route/rule_default.go | 10 ++++++ route/rule_dns.go | 10 ++++++ route/rule_item_wifi_bssid.go | 39 +++++++++++++++++++++++ route/rule_item_wifi_ssid.go | 39 +++++++++++++++++++++++ 15 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 route/rule_item_wifi_bssid.go create mode 100644 route/rule_item_wifi_ssid.go diff --git a/adapter/router.go b/adapter/router.go index ab2d916c..e4c3904d 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -41,6 +41,7 @@ type Router interface { NetworkMonitor() tun.NetworkUpdateMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor PackageManager() tun.PackageManager + WIFIState() WIFIState Rules() []Rule ClashServer() ClashServer @@ -78,3 +79,8 @@ type DNSRule interface { type InterfaceUpdateListener interface { InterfaceUpdated() } + +type WIFIState struct { + SSID string + BSSID string +} diff --git a/experimental/clashapi/cachefile/cache.go b/experimental/clashapi/cachefile/cache.go index 0b96fa5d..09118297 100644 --- a/experimental/clashapi/cachefile/cache.go +++ b/experimental/clashapi/cachefile/cache.go @@ -1,6 +1,7 @@ package cachefile import ( + "context" "errors" "net/netip" "os" @@ -13,6 +14,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service/filemanager" ) var ( @@ -41,7 +43,7 @@ type CacheFile struct { saveMetadataTimer *time.Timer } -func Open(path string, cacheID string) (*CacheFile, error) { +func Open(ctx context.Context, path string, cacheID string) (*CacheFile, error) { const fileMode = 0o666 options := bbolt.Options{Timeout: time.Second} var ( @@ -67,6 +69,10 @@ func Open(path string, cacheID string) (*CacheFile, error) { if err != nil { return nil, err } + err = filemanager.Chown(ctx, path) + if err != nil { + return nil, E.Cause(err, "platform chown") + } var cacheIDBytes []byte if cacheID != "" { cacheIDBytes = append([]byte{0}, []byte(cacheID)...) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 493d7397..6a3d6f66 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -148,7 +148,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ func (s *Server) PreStart() error { if s.cacheFilePath != "" { - cacheFile, err := cachefile.Open(s.cacheFilePath, s.cacheID) + cacheFile, err := cachefile.Open(s.ctx, s.cacheFilePath, s.cacheID) if err != nil { return E.Cause(err, "open cache file") } diff --git a/experimental/libbox/command_log.go b/experimental/libbox/command_log.go index ce72010d..da142657 100644 --- a/experimental/libbox/command_log.go +++ b/experimental/libbox/command_log.go @@ -60,11 +60,11 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error { for element := s.savedLines.Front(); element != nil; element = element.Next() { savedLines = append(savedLines, element.Value) } - s.access.Unlock() subscription, done, err := s.observer.Subscribe() if err != nil { return err } + s.access.Unlock() defer s.observer.UnSubscribe(subscription) for _, line := range savedLines { err = writeLog(conn, []byte(line)) diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index b7418bd2..451a72a9 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -19,6 +19,7 @@ type PlatformInterface interface { UsePlatformInterfaceGetter() bool GetInterfaces() (NetworkInterfaceIterator, error) UnderNetworkExtension() bool + ReadWIFIState() *WIFIState ClearDNSCache() } @@ -38,6 +39,15 @@ type NetworkInterface struct { Addresses StringIterator } +type WIFIState struct { + SSID string + BSSID string +} + +func NewWIFIState(wifiSSID string, wifiBSSID string) *WIFIState { + return &WIFIState{wifiSSID, wifiBSSID} +} + type NetworkInterfaceIterator interface { Next() *NetworkInterface HasNext() bool diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 8c471971..54d35fa3 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -23,6 +23,7 @@ type Interface interface { Interfaces() ([]NetworkInterface, error) UnderNetworkExtension() bool ClearDNSCache() + ReadWIFIState() adapter.WIFIState process.Searcher } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index ce4d6d2a..3375f750 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -210,6 +210,14 @@ func (w *platformInterfaceWrapper) ClearDNSCache() { w.iif.ClearDNSCache() } +func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { + wifiState := w.iif.ReadWIFIState() + if wifiState == nil { + return adapter.WIFIState{} + } + return (adapter.WIFIState)(*wifiState) +} + func (w *platformInterfaceWrapper) DisableColors() bool { return runtime.GOOS != "android" } diff --git a/option/rule.go b/option/rule.go index f78a752d..8caba96e 100644 --- a/option/rule.go +++ b/option/rule.go @@ -78,6 +78,8 @@ type DefaultRule struct { User Listable[string] `json:"user,omitempty"` UserID Listable[int32] `json:"user_id,omitempty"` ClashMode string `json:"clash_mode,omitempty"` + WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` Invert bool `json:"invert,omitempty"` Outbound string `json:"outbound,omitempty"` } diff --git a/option/rule_dns.go b/option/rule_dns.go index 563d3085..ba572b9a 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -78,6 +78,8 @@ type DefaultDNSRule struct { UserID Listable[int32] `json:"user_id,omitempty"` Outbound Listable[string] `json:"outbound,omitempty"` ClashMode string `json:"clash_mode,omitempty"` + WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` Invert bool `json:"invert,omitempty"` Server string `json:"server,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` diff --git a/route/router.go b/route/router.go index 2d2f2cde..972c4ec0 100644 --- a/route/router.go +++ b/route/router.go @@ -86,6 +86,8 @@ type Router struct { clashServer adapter.ClashServer v2rayServer adapter.V2RayServer platformInterface platform.Interface + needWIFIState bool + wifiState adapter.WIFIState } func NewRouter( @@ -116,6 +118,7 @@ func NewRouter( defaultMark: options.DefaultMark, pauseManager: pause.ManagerFromContext(ctx), platformInterface: platformInterface, + needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), } router.dnsClient = dns.NewClient(dns.ClientOptions{ DisableCache: dnsOptions.DNSClientOptions.DisableCache, @@ -328,6 +331,11 @@ func NewRouter( service.ContextWith[serviceNTP.TimeService](ctx, timeService) router.timeService = timeService } + if platformInterface != nil && router.interfaceMonitor != nil && router.needWIFIState { + router.interfaceMonitor.RegisterCallback(func(_ int) { + router.updateWIFIState() + }) + } return router, nil } @@ -468,6 +476,9 @@ func (r *Router) Start() error { r.geositeCache = nil r.geositeReader = nil } + if r.needWIFIState { + r.updateWIFIState() + } for i, rule := range r.rules { err := rule.Start() if err != nil { @@ -940,6 +951,10 @@ func (r *Router) Rules() []adapter.Rule { return r.rules } +func (r *Router) WIFIState() adapter.WIFIState { + return r.wifiState +} + func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor { return r.networkMonitor } @@ -1019,3 +1034,14 @@ func (r *Router) ResetNetwork() error { } return nil } + +func (r *Router) updateWIFIState() { + if r.platformInterface == nil { + return + } + state := r.platformInterface.ReadWIFIState() + if state != r.wifiState { + r.wifiState = state + r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) + } +} diff --git a/route/router_geo_resources.go b/route/router_geo_resources.go index 523833dc..8715cf92 100644 --- a/route/router_geo_resources.go +++ b/route/router_geo_resources.go @@ -307,3 +307,11 @@ func isProcessDNSRule(rule option.DefaultDNSRule) bool { func notPrivateNode(code string) bool { return code != "private" } + +func isWIFIRule(rule option.DefaultRule) bool { + return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 +} + +func isWIFIDNSRule(rule option.DefaultDNSRule) bool { + return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 +} diff --git a/route/rule_default.go b/route/rule_default.go index 01322c13..2d62f97a 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -184,6 +184,16 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.WIFISSID) > 0 { + item := NewWIFISSIDItem(router, options.WIFISSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFIBSSID) > 0 { + item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } return rule, nil } diff --git a/route/rule_dns.go b/route/rule_dns.go index 15e4b16f..5132f024 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -180,6 +180,16 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.WIFISSID) > 0 { + item := NewWIFISSIDItem(router, options.WIFISSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFIBSSID) > 0 { + item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } return rule, nil } diff --git a/route/rule_item_wifi_bssid.go b/route/rule_item_wifi_bssid.go new file mode 100644 index 00000000..3b1ff9c8 --- /dev/null +++ b/route/rule_item_wifi_bssid.go @@ -0,0 +1,39 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*WIFIBSSIDItem)(nil) + +type WIFIBSSIDItem struct { + bssidList []string + bssidMap map[string]bool + router adapter.Router +} + +func NewWIFIBSSIDItem(router adapter.Router, bssidList []string) *WIFIBSSIDItem { + bssidMap := make(map[string]bool) + for _, bssid := range bssidList { + bssidMap[bssid] = true + } + return &WIFIBSSIDItem{ + bssidList, + bssidMap, + router, + } +} + +func (r *WIFIBSSIDItem) Match(metadata *adapter.InboundContext) bool { + return r.bssidMap[r.router.WIFIState().BSSID] +} + +func (r *WIFIBSSIDItem) String() string { + if len(r.bssidList) == 1 { + return F.ToString("wifi_bssid=", r.bssidList[0]) + } + return F.ToString("wifi_bssid=[", strings.Join(r.bssidList, " "), "]") +} diff --git a/route/rule_item_wifi_ssid.go b/route/rule_item_wifi_ssid.go new file mode 100644 index 00000000..62cf935e --- /dev/null +++ b/route/rule_item_wifi_ssid.go @@ -0,0 +1,39 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*WIFISSIDItem)(nil) + +type WIFISSIDItem struct { + ssidList []string + ssidMap map[string]bool + router adapter.Router +} + +func NewWIFISSIDItem(router adapter.Router, ssidList []string) *WIFISSIDItem { + ssidMap := make(map[string]bool) + for _, ssid := range ssidList { + ssidMap[ssid] = true + } + return &WIFISSIDItem{ + ssidList, + ssidMap, + router, + } +} + +func (r *WIFISSIDItem) Match(metadata *adapter.InboundContext) bool { + return r.ssidMap[r.router.WIFIState().SSID] +} + +func (r *WIFISSIDItem) String() string { + if len(r.ssidList) == 1 { + return F.ToString("wifi_ssid=", r.ssidList[0]) + } + return F.ToString("wifi_ssid=[", strings.Join(r.ssidList, " "), "]") +}