mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
36 Commits
dev-wifi-l
...
v1.13.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36836624f9 | ||
|
|
b530d61860 | ||
|
|
15eb1d97ee | ||
|
|
a2ca132b84 | ||
|
|
69e1e8965f | ||
|
|
7e4c832b74 | ||
|
|
3644ac24df | ||
|
|
08b7d82471 | ||
|
|
49332c3dfd | ||
|
|
234424403a | ||
|
|
6b07226c27 | ||
|
|
44340add48 | ||
|
|
eb408ec8ec | ||
|
|
cdcb2cf9ac | ||
|
|
ef5a41a134 | ||
|
|
87d62d8e61 | ||
|
|
078ebe006a | ||
|
|
5f8c69e614 | ||
|
|
46be4f49cb | ||
|
|
10d0bc53bd | ||
|
|
28edf3b661 | ||
|
|
577dc72b40 | ||
|
|
d194060ca8 | ||
|
|
ed30d5c260 | ||
|
|
c3f2d8f528 | ||
|
|
6a2e85d641 | ||
|
|
330735b999 | ||
|
|
262683f60d | ||
|
|
6c75628ba8 | ||
|
|
f77953918d | ||
|
|
3eb458cde5 | ||
|
|
25c978a1c6 | ||
|
|
67df7eb282 | ||
|
|
8c4634c599 | ||
|
|
7394e3d311 | ||
|
|
169122a8bd |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -15,6 +15,5 @@
|
||||
.DS_Store
|
||||
/config.d/
|
||||
/venv/
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
/.claude/
|
||||
!/README.md
|
||||
/*.md
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@@ -84,7 +85,7 @@ type InboundContext struct {
|
||||
DestinationAddresses []netip.Addr
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *ConnectionOwner
|
||||
ProcessInfo *process.Info
|
||||
QueryType uint16
|
||||
FakeIP bool
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
type NetworkManager interface {
|
||||
Lifecycle
|
||||
Initialize(ruleSets []RuleSet)
|
||||
InterfaceFinder() control.InterfaceFinder
|
||||
UpdateInterfaces() error
|
||||
DefaultNetworkInterface() *NetworkInterface
|
||||
@@ -25,10 +24,9 @@ type NetworkManager interface {
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
NeedWIFIState() bool
|
||||
WIFIState() WIFIState
|
||||
UpdateWIFIState()
|
||||
ResetNetwork()
|
||||
UpdateWIFIState()
|
||||
}
|
||||
|
||||
type NetworkOptions struct {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type PlatformInterface interface {
|
||||
Initialize(networkManager NetworkManager) error
|
||||
|
||||
UsePlatformAutoDetectInterfaceControl() bool
|
||||
AutoDetectInterfaceControl(fd int) error
|
||||
|
||||
UsePlatformInterface() bool
|
||||
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||
|
||||
UsePlatformDefaultInterfaceMonitor() bool
|
||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||
|
||||
UsePlatformNetworkInterfaces() bool
|
||||
NetworkInterfaces() ([]NetworkInterface, error)
|
||||
|
||||
UnderNetworkExtension() bool
|
||||
NetworkExtensionIncludeAllNetworks() bool
|
||||
|
||||
ClearDNSCache()
|
||||
RequestPermissionForWIFIState() error
|
||||
ReadWIFIState() WIFIState
|
||||
SystemCertificates() []string
|
||||
|
||||
UsePlatformConnectionOwnerFinder() bool
|
||||
FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)
|
||||
|
||||
UsePlatformWIFIMonitor() bool
|
||||
|
||||
UsePlatformNotification() bool
|
||||
SendNotification(notification *Notification) error
|
||||
}
|
||||
|
||||
type FindConnectionOwnerRequest struct {
|
||||
IpProtocol int32
|
||||
SourceAddress string
|
||||
SourcePort int32
|
||||
DestinationAddress string
|
||||
DestinationPort int32
|
||||
}
|
||||
|
||||
type ConnectionOwner struct {
|
||||
ProcessID uint32
|
||||
UserId int32
|
||||
UserName string
|
||||
ProcessPath string
|
||||
AndroidPackageName string
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
Identifier string
|
||||
TypeName string
|
||||
TypeID int32
|
||||
Title string
|
||||
Subtitle string
|
||||
Body string
|
||||
OpenURL string
|
||||
}
|
||||
|
||||
type SystemProxyStatus struct {
|
||||
Available bool
|
||||
Enabled bool
|
||||
}
|
||||
@@ -24,6 +24,7 @@ type Router interface {
|
||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||
ConnectionRouterEx
|
||||
RuleSet(tag string) (RuleSet, bool)
|
||||
NeedWIFIState() bool
|
||||
Rules() []Rule
|
||||
AppendTracker(tracker ConnectionTracker)
|
||||
ResetNetwork()
|
||||
|
||||
9
box.go
9
box.go
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/sagernet/sing-box/dns/transport/local"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/protocol/direct"
|
||||
@@ -138,7 +139,7 @@ func New(options Options) (*Box, error) {
|
||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||
needV2RayAPI = true
|
||||
}
|
||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
var defaultLogWriter io.Writer
|
||||
if platformInterface != nil {
|
||||
defaultLogWriter = io.Discard
|
||||
@@ -183,7 +184,7 @@ func New(options Options) (*Box, error) {
|
||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize network manager")
|
||||
}
|
||||
@@ -526,7 +527,3 @@ func (s *Box) Inbound() adapter.InboundManager {
|
||||
func (s *Box) Outbound() adapter.OutboundManager {
|
||||
return s.outbound
|
||||
}
|
||||
|
||||
func (s *Box) LogFactory() log.Factory {
|
||||
return s.logFactory
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
@@ -35,7 +36,7 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
||||
switch options.Store {
|
||||
case C.CertificateStoreSystem, "":
|
||||
systemPool = x509.NewCertPool()
|
||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
var systemValid bool
|
||||
if platformInterface != nil {
|
||||
for _, cert := range platformInterface.SystemCertificates() {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/common/listener"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -48,7 +49,7 @@ type DefaultDialer struct {
|
||||
|
||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
|
||||
var (
|
||||
dialer net.Dialer
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net/netip"
|
||||
"os/user"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type Searcher interface {
|
||||
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)
|
||||
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error)
|
||||
}
|
||||
|
||||
var ErrNotFound = E.New("process not found")
|
||||
@@ -23,7 +22,15 @@ type Config struct {
|
||||
PackageManager tun.PackageManager
|
||||
}
|
||||
|
||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||
type Info struct {
|
||||
ProcessID uint32
|
||||
ProcessPath string
|
||||
PackageName string
|
||||
User string
|
||||
UserId int32
|
||||
}
|
||||
|
||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -31,7 +38,7 @@ func FindProcessInfo(searcher Searcher, ctx context.Context, network string, sou
|
||||
if info.UserId != -1 {
|
||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||
if osUser != nil {
|
||||
info.UserName = osUser.Username
|
||||
info.User = osUser.Username
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
)
|
||||
|
||||
@@ -18,22 +17,22 @@ func NewSearcher(config Config) (Searcher, error) {
|
||||
return &androidSearcher{config.PackageManager}, nil
|
||||
}
|
||||
|
||||
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
_, uid, err := resolveSocketByNetlink(network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
|
||||
return &adapter.ConnectionOwner{
|
||||
UserId: int32(uid),
|
||||
AndroidPackageName: sharedPackage,
|
||||
return &Info{
|
||||
UserId: int32(uid),
|
||||
PackageName: sharedPackage,
|
||||
}, nil
|
||||
}
|
||||
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
|
||||
return &adapter.ConnectionOwner{
|
||||
UserId: int32(uid),
|
||||
AndroidPackageName: packageName,
|
||||
return &Info{
|
||||
UserId: int32(uid),
|
||||
PackageName: packageName,
|
||||
}, nil
|
||||
}
|
||||
return &adapter.ConnectionOwner{UserId: int32(uid)}, nil
|
||||
return &Info{UserId: int32(uid)}, nil
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -24,12 +23,12 @@ func NewSearcher(_ Config) (Searcher, error) {
|
||||
return &darwinSearcher{}, nil
|
||||
}
|
||||
|
||||
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &adapter.ConnectionOwner{ProcessPath: processName, UserId: -1}, nil
|
||||
return &Info{ProcessPath: processName, UserId: -1}, nil
|
||||
}
|
||||
|
||||
var structSize = func() int {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
@@ -20,7 +19,7 @@ func NewSearcher(config Config) (Searcher, error) {
|
||||
return &linuxSearcher{config.Logger}, nil
|
||||
}
|
||||
|
||||
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
inode, uid, err := resolveSocketByNetlink(network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -29,7 +28,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, sou
|
||||
if err != nil {
|
||||
s.logger.DebugContext(ctx, "find process path: ", err)
|
||||
}
|
||||
return &adapter.ConnectionOwner{
|
||||
return &Info{
|
||||
UserId: int32(uid),
|
||||
ProcessPath: processPath,
|
||||
}, nil
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net/netip"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/winiphlpapi"
|
||||
|
||||
@@ -28,16 +27,16 @@ func initWin32API() error {
|
||||
return winiphlpapi.LoadExtendedTable()
|
||||
}
|
||||
|
||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
pid, err := winiphlpapi.FindPid(network, source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path, err := getProcessPath(pid)
|
||||
if err != nil {
|
||||
return &adapter.ConnectionOwner{ProcessID: pid, UserId: -1}, err
|
||||
return &Info{ProcessID: pid, UserId: -1}, err
|
||||
}
|
||||
return &adapter.ConnectionOwner{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
|
||||
return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
|
||||
}
|
||||
|
||||
func getProcessPath(pid uint32) (string, error) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package settings
|
||||
|
||||
import "github.com/sagernet/sing-box/adapter"
|
||||
|
||||
type WIFIMonitor interface {
|
||||
ReadWIFIState() adapter.WIFIState
|
||||
Start() error
|
||||
Close() error
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
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
|
||||
}
|
||||
if signal.Name == "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 {
|
||||
return m.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
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 {
|
||||
return m.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
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 primaryConnectionPath dbus.ObjectPath
|
||||
err := nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "PrimaryConnection").Store(&primaryConnectionPath)
|
||||
if err != nil || primaryConnectionPath == "/" {
|
||||
return adapter.WIFIState{}
|
||||
}
|
||||
|
||||
connObj := m.conn.Object("org.freedesktop.NetworkManager", primaryConnectionPath)
|
||||
|
||||
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 {
|
||||
return adapter.WIFIState{}
|
||||
}
|
||||
|
||||
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 {
|
||||
return m.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
)
|
||||
|
||||
type wpaSupplicantMonitor struct {
|
||||
socketPath string
|
||||
callback func(adapter.WIFIState)
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
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())
|
||||
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d", os.Getpid()), 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 {
|
||||
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d", os.Getpid()), 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, ":", "")),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *wpaSupplicantMonitor) sendCommand(conn *net.UnixConn, command string) (string, error) {
|
||||
_, err := conn.Write([]byte(command + "\n"))
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
_, err = conn.Write([]byte("ATTACH\n"))
|
||||
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():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
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") {
|
||||
state := m.ReadWIFIState()
|
||||
if state != lastState {
|
||||
lastState = state
|
||||
m.callback(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *wpaSupplicantMonitor) Close() error {
|
||||
if m.cancel != nil {
|
||||
m.cancel()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
//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
|
||||
}
|
||||
@@ -119,19 +119,21 @@ func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConn, err := aTLS.ClientHandshake(ctx, conn, d.config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
tlsConn, err := ClientHandshake(ctx, conn, d.config)
|
||||
if err == nil {
|
||||
return tlsConn, nil
|
||||
}
|
||||
conn.Close()
|
||||
if echRetry {
|
||||
var echErr *tls.ECHRejectionError
|
||||
if echRetry && errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
|
||||
if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
|
||||
if echConfig, isECH := d.config.(ECHCapableConfig); isECH {
|
||||
echConfig.SetECHConfigList(echErr.RetryConfigList)
|
||||
return d.dialContext(ctx, destination, false)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
return d.dialContext(ctx, destination, false)
|
||||
}
|
||||
return tlsConn, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (d *defaultDialer) Upstream() any {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
var _ deprecated.Manager = (*deprecatedManager)(nil)
|
||||
|
||||
type deprecatedManager struct {
|
||||
access sync.Mutex
|
||||
notes []deprecated.Note
|
||||
}
|
||||
|
||||
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.notes = common.Uniq(append(m.notes, feature))
|
||||
}
|
||||
|
||||
func (m *deprecatedManager) Get() []deprecated.Note {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
notes := m.notes
|
||||
m.notes = nil
|
||||
return notes
|
||||
}
|
||||
@@ -1,702 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type SubscribeHelperRequestRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
AcceptGetWIFIStateRequests bool `protobuf:"varint,1,opt,name=acceptGetWIFIStateRequests,proto3" json:"acceptGetWIFIStateRequests,omitempty"`
|
||||
AcceptFindConnectionOwnerRequests bool `protobuf:"varint,2,opt,name=acceptFindConnectionOwnerRequests,proto3" json:"acceptFindConnectionOwnerRequests,omitempty"`
|
||||
AcceptSendNotificationRequests bool `protobuf:"varint,3,opt,name=acceptSendNotificationRequests,proto3" json:"acceptSendNotificationRequests,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SubscribeHelperRequestRequest) Reset() {
|
||||
*x = SubscribeHelperRequestRequest{}
|
||||
mi := &file_daemon_helper_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SubscribeHelperRequestRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SubscribeHelperRequestRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SubscribeHelperRequestRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_helper_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SubscribeHelperRequestRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SubscribeHelperRequestRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_helper_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *SubscribeHelperRequestRequest) GetAcceptGetWIFIStateRequests() bool {
|
||||
if x != nil {
|
||||
return x.AcceptGetWIFIStateRequests
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *SubscribeHelperRequestRequest) GetAcceptFindConnectionOwnerRequests() bool {
|
||||
if x != nil {
|
||||
return x.AcceptFindConnectionOwnerRequests
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *SubscribeHelperRequestRequest) GetAcceptSendNotificationRequests() bool {
|
||||
if x != nil {
|
||||
return x.AcceptSendNotificationRequests
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type HelperRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// Types that are valid to be assigned to Request:
|
||||
//
|
||||
// *HelperRequest_GetWIFIState
|
||||
// *HelperRequest_FindConnectionOwner
|
||||
// *HelperRequest_SendNotification
|
||||
Request isHelperRequest_Request `protobuf_oneof:"request"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HelperRequest) Reset() {
|
||||
*x = HelperRequest{}
|
||||
mi := &file_daemon_helper_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *HelperRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*HelperRequest) ProtoMessage() {}
|
||||
|
||||
func (x *HelperRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_helper_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use HelperRequest.ProtoReflect.Descriptor instead.
|
||||
func (*HelperRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_helper_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *HelperRequest) GetId() int64 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *HelperRequest) GetRequest() isHelperRequest_Request {
|
||||
if x != nil {
|
||||
return x.Request
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HelperRequest) GetGetWIFIState() *emptypb.Empty {
|
||||
if x != nil {
|
||||
if x, ok := x.Request.(*HelperRequest_GetWIFIState); ok {
|
||||
return x.GetWIFIState
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HelperRequest) GetFindConnectionOwner() *FindConnectionOwnerRequest {
|
||||
if x != nil {
|
||||
if x, ok := x.Request.(*HelperRequest_FindConnectionOwner); ok {
|
||||
return x.FindConnectionOwner
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HelperRequest) GetSendNotification() *Notification {
|
||||
if x != nil {
|
||||
if x, ok := x.Request.(*HelperRequest_SendNotification); ok {
|
||||
return x.SendNotification
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isHelperRequest_Request interface {
|
||||
isHelperRequest_Request()
|
||||
}
|
||||
|
||||
type HelperRequest_GetWIFIState struct {
|
||||
GetWIFIState *emptypb.Empty `protobuf:"bytes,2,opt,name=getWIFIState,proto3,oneof"`
|
||||
}
|
||||
|
||||
type HelperRequest_FindConnectionOwner struct {
|
||||
FindConnectionOwner *FindConnectionOwnerRequest `protobuf:"bytes,3,opt,name=findConnectionOwner,proto3,oneof"`
|
||||
}
|
||||
|
||||
type HelperRequest_SendNotification struct {
|
||||
SendNotification *Notification `protobuf:"bytes,4,opt,name=sendNotification,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*HelperRequest_GetWIFIState) isHelperRequest_Request() {}
|
||||
|
||||
func (*HelperRequest_FindConnectionOwner) isHelperRequest_Request() {}
|
||||
|
||||
func (*HelperRequest_SendNotification) isHelperRequest_Request() {}
|
||||
|
||||
type FindConnectionOwnerRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
IpProtocol int32 `protobuf:"varint,1,opt,name=ipProtocol,proto3" json:"ipProtocol,omitempty"`
|
||||
SourceAddress string `protobuf:"bytes,2,opt,name=sourceAddress,proto3" json:"sourceAddress,omitempty"`
|
||||
SourcePort int32 `protobuf:"varint,3,opt,name=sourcePort,proto3" json:"sourcePort,omitempty"`
|
||||
DestinationAddress string `protobuf:"bytes,4,opt,name=destinationAddress,proto3" json:"destinationAddress,omitempty"`
|
||||
DestinationPort int32 `protobuf:"varint,5,opt,name=destinationPort,proto3" json:"destinationPort,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FindConnectionOwnerRequest) Reset() {
|
||||
*x = FindConnectionOwnerRequest{}
|
||||
mi := &file_daemon_helper_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *FindConnectionOwnerRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FindConnectionOwnerRequest) ProtoMessage() {}
|
||||
|
||||
func (x *FindConnectionOwnerRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_helper_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use FindConnectionOwnerRequest.ProtoReflect.Descriptor instead.
|
||||
func (*FindConnectionOwnerRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_helper_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *FindConnectionOwnerRequest) GetIpProtocol() int32 {
|
||||
if x != nil {
|
||||
return x.IpProtocol
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FindConnectionOwnerRequest) GetSourceAddress() string {
|
||||
if x != nil {
|
||||
return x.SourceAddress
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FindConnectionOwnerRequest) GetSourcePort() int32 {
|
||||
if x != nil {
|
||||
return x.SourcePort
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FindConnectionOwnerRequest) GetDestinationAddress() string {
|
||||
if x != nil {
|
||||
return x.DestinationAddress
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FindConnectionOwnerRequest) GetDestinationPort() int32 {
|
||||
if x != nil {
|
||||
return x.DestinationPort
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
|
||||
TypeName string `protobuf:"bytes,2,opt,name=typeName,proto3" json:"typeName,omitempty"`
|
||||
TypeId int32 `protobuf:"varint,3,opt,name=typeId,proto3" json:"typeId,omitempty"`
|
||||
Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"`
|
||||
Subtitle string `protobuf:"bytes,5,opt,name=subtitle,proto3" json:"subtitle,omitempty"`
|
||||
Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"`
|
||||
OpenURL string `protobuf:"bytes,7,opt,name=openURL,proto3" json:"openURL,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Notification) Reset() {
|
||||
*x = Notification{}
|
||||
mi := &file_daemon_helper_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Notification) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Notification) ProtoMessage() {}
|
||||
|
||||
func (x *Notification) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_helper_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Notification.ProtoReflect.Descriptor instead.
|
||||
func (*Notification) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_helper_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *Notification) GetIdentifier() string {
|
||||
if x != nil {
|
||||
return x.Identifier
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Notification) GetTypeName() string {
|
||||
if x != nil {
|
||||
return x.TypeName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Notification) GetTypeId() int32 {
|
||||
if x != nil {
|
||||
return x.TypeId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Notification) GetTitle() string {
|
||||
if x != nil {
|
||||
return x.Title
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Notification) GetSubtitle() string {
|
||||
if x != nil {
|
||||
return x.Subtitle
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Notification) GetBody() string {
|
||||
if x != nil {
|
||||
return x.Body
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Notification) GetOpenURL() string {
|
||||
if x != nil {
|
||||
return x.OpenURL
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type HelperResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// Types that are valid to be assigned to Response:
|
||||
//
|
||||
// *HelperResponse_WifiState
|
||||
// *HelperResponse_Error
|
||||
// *HelperResponse_ConnectionOwner
|
||||
Response isHelperResponse_Response `protobuf_oneof:"response"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HelperResponse) Reset() {
|
||||
*x = HelperResponse{}
|
||||
mi := &file_daemon_helper_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *HelperResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*HelperResponse) ProtoMessage() {}
|
||||
|
||||
func (x *HelperResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_helper_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use HelperResponse.ProtoReflect.Descriptor instead.
|
||||
func (*HelperResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_helper_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *HelperResponse) GetId() int64 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *HelperResponse) GetResponse() isHelperResponse_Response {
|
||||
if x != nil {
|
||||
return x.Response
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HelperResponse) GetWifiState() *WIFIState {
|
||||
if x != nil {
|
||||
if x, ok := x.Response.(*HelperResponse_WifiState); ok {
|
||||
return x.WifiState
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HelperResponse) GetError() string {
|
||||
if x != nil {
|
||||
if x, ok := x.Response.(*HelperResponse_Error); ok {
|
||||
return x.Error
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *HelperResponse) GetConnectionOwner() *ConnectionOwner {
|
||||
if x != nil {
|
||||
if x, ok := x.Response.(*HelperResponse_ConnectionOwner); ok {
|
||||
return x.ConnectionOwner
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isHelperResponse_Response interface {
|
||||
isHelperResponse_Response()
|
||||
}
|
||||
|
||||
type HelperResponse_WifiState struct {
|
||||
WifiState *WIFIState `protobuf:"bytes,2,opt,name=wifiState,proto3,oneof"`
|
||||
}
|
||||
|
||||
type HelperResponse_Error struct {
|
||||
Error string `protobuf:"bytes,3,opt,name=error,proto3,oneof"`
|
||||
}
|
||||
|
||||
type HelperResponse_ConnectionOwner struct {
|
||||
ConnectionOwner *ConnectionOwner `protobuf:"bytes,4,opt,name=connectionOwner,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*HelperResponse_WifiState) isHelperResponse_Response() {}
|
||||
|
||||
func (*HelperResponse_Error) isHelperResponse_Response() {}
|
||||
|
||||
func (*HelperResponse_ConnectionOwner) isHelperResponse_Response() {}
|
||||
|
||||
type ConnectionOwner struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"`
|
||||
UserName string `protobuf:"bytes,2,opt,name=userName,proto3" json:"userName,omitempty"`
|
||||
ProcessPath string `protobuf:"bytes,3,opt,name=processPath,proto3" json:"processPath,omitempty"`
|
||||
AndroidPackageName string `protobuf:"bytes,4,opt,name=androidPackageName,proto3" json:"androidPackageName,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ConnectionOwner) Reset() {
|
||||
*x = ConnectionOwner{}
|
||||
mi := &file_daemon_helper_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ConnectionOwner) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ConnectionOwner) ProtoMessage() {}
|
||||
|
||||
func (x *ConnectionOwner) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_helper_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionOwner.ProtoReflect.Descriptor instead.
|
||||
func (*ConnectionOwner) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_helper_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *ConnectionOwner) GetUserId() int32 {
|
||||
if x != nil {
|
||||
return x.UserId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ConnectionOwner) GetUserName() string {
|
||||
if x != nil {
|
||||
return x.UserName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ConnectionOwner) GetProcessPath() string {
|
||||
if x != nil {
|
||||
return x.ProcessPath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ConnectionOwner) GetAndroidPackageName() string {
|
||||
if x != nil {
|
||||
return x.AndroidPackageName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type WIFIState struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Ssid string `protobuf:"bytes,1,opt,name=ssid,proto3" json:"ssid,omitempty"`
|
||||
Bssid string `protobuf:"bytes,2,opt,name=bssid,proto3" json:"bssid,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *WIFIState) Reset() {
|
||||
*x = WIFIState{}
|
||||
mi := &file_daemon_helper_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *WIFIState) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*WIFIState) ProtoMessage() {}
|
||||
|
||||
func (x *WIFIState) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_helper_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use WIFIState.ProtoReflect.Descriptor instead.
|
||||
func (*WIFIState) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_helper_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *WIFIState) GetSsid() string {
|
||||
if x != nil {
|
||||
return x.Ssid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WIFIState) GetBssid() string {
|
||||
if x != nil {
|
||||
return x.Bssid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_daemon_helper_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_daemon_helper_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x13daemon/helper.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"\xf5\x01\n" +
|
||||
"\x1dSubscribeHelperRequestRequest\x12>\n" +
|
||||
"\x1aacceptGetWIFIStateRequests\x18\x01 \x01(\bR\x1aacceptGetWIFIStateRequests\x12L\n" +
|
||||
"!acceptFindConnectionOwnerRequests\x18\x02 \x01(\bR!acceptFindConnectionOwnerRequests\x12F\n" +
|
||||
"\x1eacceptSendNotificationRequests\x18\x03 \x01(\bR\x1eacceptSendNotificationRequests\"\x84\x02\n" +
|
||||
"\rHelperRequest\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12<\n" +
|
||||
"\fgetWIFIState\x18\x02 \x01(\v2\x16.google.protobuf.EmptyH\x00R\fgetWIFIState\x12V\n" +
|
||||
"\x13findConnectionOwner\x18\x03 \x01(\v2\".daemon.FindConnectionOwnerRequestH\x00R\x13findConnectionOwner\x12B\n" +
|
||||
"\x10sendNotification\x18\x04 \x01(\v2\x14.daemon.NotificationH\x00R\x10sendNotificationB\t\n" +
|
||||
"\arequest\"\xdc\x01\n" +
|
||||
"\x1aFindConnectionOwnerRequest\x12\x1e\n" +
|
||||
"\n" +
|
||||
"ipProtocol\x18\x01 \x01(\x05R\n" +
|
||||
"ipProtocol\x12$\n" +
|
||||
"\rsourceAddress\x18\x02 \x01(\tR\rsourceAddress\x12\x1e\n" +
|
||||
"\n" +
|
||||
"sourcePort\x18\x03 \x01(\x05R\n" +
|
||||
"sourcePort\x12.\n" +
|
||||
"\x12destinationAddress\x18\x04 \x01(\tR\x12destinationAddress\x12(\n" +
|
||||
"\x0fdestinationPort\x18\x05 \x01(\x05R\x0fdestinationPort\"\xc2\x01\n" +
|
||||
"\fNotification\x12\x1e\n" +
|
||||
"\n" +
|
||||
"identifier\x18\x01 \x01(\tR\n" +
|
||||
"identifier\x12\x1a\n" +
|
||||
"\btypeName\x18\x02 \x01(\tR\btypeName\x12\x16\n" +
|
||||
"\x06typeId\x18\x03 \x01(\x05R\x06typeId\x12\x14\n" +
|
||||
"\x05title\x18\x04 \x01(\tR\x05title\x12\x1a\n" +
|
||||
"\bsubtitle\x18\x05 \x01(\tR\bsubtitle\x12\x12\n" +
|
||||
"\x04body\x18\x06 \x01(\tR\x04body\x12\x18\n" +
|
||||
"\aopenURL\x18\a \x01(\tR\aopenURL\"\xbc\x01\n" +
|
||||
"\x0eHelperResponse\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x03R\x02id\x121\n" +
|
||||
"\twifiState\x18\x02 \x01(\v2\x11.daemon.WIFIStateH\x00R\twifiState\x12\x16\n" +
|
||||
"\x05error\x18\x03 \x01(\tH\x00R\x05error\x12C\n" +
|
||||
"\x0fconnectionOwner\x18\x04 \x01(\v2\x17.daemon.ConnectionOwnerH\x00R\x0fconnectionOwnerB\n" +
|
||||
"\n" +
|
||||
"\bresponse\"\x97\x01\n" +
|
||||
"\x0fConnectionOwner\x12\x16\n" +
|
||||
"\x06userId\x18\x01 \x01(\x05R\x06userId\x12\x1a\n" +
|
||||
"\buserName\x18\x02 \x01(\tR\buserName\x12 \n" +
|
||||
"\vprocessPath\x18\x03 \x01(\tR\vprocessPath\x12.\n" +
|
||||
"\x12androidPackageName\x18\x04 \x01(\tR\x12androidPackageName\"5\n" +
|
||||
"\tWIFIState\x12\x12\n" +
|
||||
"\x04ssid\x18\x01 \x01(\tR\x04ssid\x12\x14\n" +
|
||||
"\x05bssid\x18\x02 \x01(\tR\x05bssidB%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
|
||||
|
||||
var (
|
||||
file_daemon_helper_proto_rawDescOnce sync.Once
|
||||
file_daemon_helper_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_daemon_helper_proto_rawDescGZIP() []byte {
|
||||
file_daemon_helper_proto_rawDescOnce.Do(func() {
|
||||
file_daemon_helper_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)))
|
||||
})
|
||||
return file_daemon_helper_proto_rawDescData
|
||||
}
|
||||
|
||||
var (
|
||||
file_daemon_helper_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
file_daemon_helper_proto_goTypes = []any{
|
||||
(*SubscribeHelperRequestRequest)(nil), // 0: daemon.SubscribeHelperRequestRequest
|
||||
(*HelperRequest)(nil), // 1: daemon.HelperRequest
|
||||
(*FindConnectionOwnerRequest)(nil), // 2: daemon.FindConnectionOwnerRequest
|
||||
(*Notification)(nil), // 3: daemon.Notification
|
||||
(*HelperResponse)(nil), // 4: daemon.HelperResponse
|
||||
(*ConnectionOwner)(nil), // 5: daemon.ConnectionOwner
|
||||
(*WIFIState)(nil), // 6: daemon.WIFIState
|
||||
(*emptypb.Empty)(nil), // 7: google.protobuf.Empty
|
||||
}
|
||||
)
|
||||
|
||||
var file_daemon_helper_proto_depIdxs = []int32{
|
||||
7, // 0: daemon.HelperRequest.getWIFIState:type_name -> google.protobuf.Empty
|
||||
2, // 1: daemon.HelperRequest.findConnectionOwner:type_name -> daemon.FindConnectionOwnerRequest
|
||||
3, // 2: daemon.HelperRequest.sendNotification:type_name -> daemon.Notification
|
||||
6, // 3: daemon.HelperResponse.wifiState:type_name -> daemon.WIFIState
|
||||
5, // 4: daemon.HelperResponse.connectionOwner:type_name -> daemon.ConnectionOwner
|
||||
5, // [5:5] is the sub-list for method output_type
|
||||
5, // [5:5] is the sub-list for method input_type
|
||||
5, // [5:5] is the sub-list for extension type_name
|
||||
5, // [5:5] is the sub-list for extension extendee
|
||||
0, // [0:5] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_daemon_helper_proto_init() }
|
||||
func file_daemon_helper_proto_init() {
|
||||
if File_daemon_helper_proto != nil {
|
||||
return
|
||||
}
|
||||
file_daemon_helper_proto_msgTypes[1].OneofWrappers = []any{
|
||||
(*HelperRequest_GetWIFIState)(nil),
|
||||
(*HelperRequest_FindConnectionOwner)(nil),
|
||||
(*HelperRequest_SendNotification)(nil),
|
||||
}
|
||||
file_daemon_helper_proto_msgTypes[4].OneofWrappers = []any{
|
||||
(*HelperResponse_WifiState)(nil),
|
||||
(*HelperResponse_Error)(nil),
|
||||
(*HelperResponse_ConnectionOwner)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_daemon_helper_proto_goTypes,
|
||||
DependencyIndexes: file_daemon_helper_proto_depIdxs,
|
||||
MessageInfos: file_daemon_helper_proto_msgTypes,
|
||||
}.Build()
|
||||
File_daemon_helper_proto = out.File
|
||||
file_daemon_helper_proto_goTypes = nil
|
||||
file_daemon_helper_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package daemon;
|
||||
option go_package = "github.com/sagernet/sing-box/daemon";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
message SubscribeHelperRequestRequest {
|
||||
bool acceptGetWIFIStateRequests = 1;
|
||||
bool acceptFindConnectionOwnerRequests = 2;
|
||||
bool acceptSendNotificationRequests = 3;
|
||||
}
|
||||
|
||||
message HelperRequest {
|
||||
int64 id = 1;
|
||||
oneof request {
|
||||
google.protobuf.Empty getWIFIState = 2;
|
||||
FindConnectionOwnerRequest findConnectionOwner = 3;
|
||||
Notification sendNotification = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message FindConnectionOwnerRequest {
|
||||
int32 ipProtocol = 1;
|
||||
string sourceAddress = 2;
|
||||
int32 sourcePort = 3;
|
||||
string destinationAddress = 4;
|
||||
int32 destinationPort = 5;
|
||||
}
|
||||
|
||||
message Notification {
|
||||
string identifier = 1;
|
||||
string typeName = 2;
|
||||
int32 typeId = 3;
|
||||
string title = 4;
|
||||
string subtitle = 5;
|
||||
string body = 6;
|
||||
string openURL = 7;
|
||||
}
|
||||
|
||||
message HelperResponse {
|
||||
int64 id = 1;
|
||||
oneof response {
|
||||
WIFIState wifiState = 2;
|
||||
string error = 3;
|
||||
ConnectionOwner connectionOwner = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message ConnectionOwner {
|
||||
int32 userId = 1;
|
||||
string userName = 2;
|
||||
string processPath = 3;
|
||||
string androidPackageName = 4;
|
||||
}
|
||||
|
||||
message WIFIState {
|
||||
string ssid = 1;
|
||||
string bssid = 2;
|
||||
}
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
"github.com/sagernet/sing/service/pause"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
instance *box.Box
|
||||
clashServer adapter.ClashServer
|
||||
cacheFile adapter.CacheFile
|
||||
pauseManager pause.Manager
|
||||
urlTestHistoryStorage *urltest.HistoryStorage
|
||||
}
|
||||
|
||||
func (s *StartedService) baseContext() context.Context {
|
||||
dnsRegistry := include.DNSTransportRegistry()
|
||||
if s.platform != nil && s.platform.UsePlatformLocalDNSTransport() {
|
||||
dns.RegisterTransport[option.LocalDNSServerOptions](dnsRegistry, C.DNSTypeLocal, s.platform.LocalDNSTransport())
|
||||
}
|
||||
ctx := box.Context(s.ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
||||
ctx = filemanager.WithDefault(ctx, s.workingDirectory, s.tempDirectory, s.userID, s.groupID)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (s *StartedService) CheckConfig(configContent string) error {
|
||||
ctx := s.baseContext()
|
||||
options, err := parseConfig(ctx, configContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
instance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
})
|
||||
if err == nil {
|
||||
instance.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StartedService) FormatConfig(configContent string) (string, error) {
|
||||
options, err := parseConfig(s.baseContext(), configContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
encoder := json.NewEncoder(&buffer)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
type OverrideOptions struct {
|
||||
AutoRedirect bool
|
||||
IncludePackage []string
|
||||
ExcludePackage []string
|
||||
}
|
||||
|
||||
func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) {
|
||||
ctx := s.baseContext()
|
||||
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
||||
ctx, cancel := context.WithCancel(include.Context(ctx))
|
||||
options, err := parseConfig(ctx, profileContent)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
if overrideOptions != nil {
|
||||
for _, inbound := range options.Inbounds {
|
||||
if tunInboundOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN {
|
||||
tunInboundOptions.AutoRedirect = overrideOptions.AutoRedirect
|
||||
tunInboundOptions.IncludePackage = append(tunInboundOptions.IncludePackage, overrideOptions.IncludePackage...)
|
||||
tunInboundOptions.ExcludePackage = append(tunInboundOptions.ExcludePackage, overrideOptions.ExcludePackage...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||
i := &Instance{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
urlTestHistoryStorage: urlTestHistoryStorage,
|
||||
}
|
||||
boxInstance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
PlatformLogWriter: s,
|
||||
})
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
i.instance = boxInstance
|
||||
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
|
||||
i.pauseManager = service.FromContext[pause.Manager](ctx)
|
||||
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (i *Instance) Start() error {
|
||||
return i.instance.Start()
|
||||
}
|
||||
|
||||
func (i *Instance) Close() error {
|
||||
i.cancel()
|
||||
i.urlTestHistoryStorage.Close()
|
||||
return i.instance.Close()
|
||||
}
|
||||
|
||||
func (i *Instance) Box() *box.Box {
|
||||
return i.instance
|
||||
}
|
||||
|
||||
func (i *Instance) PauseManager() pause.Manager {
|
||||
return i.pauseManager
|
||||
}
|
||||
|
||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "decode config")
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type PlatformHandler interface {
|
||||
ServiceStop() error
|
||||
ServiceReload() error
|
||||
SystemProxyStatus() (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
WriteDebugMessage(message string)
|
||||
}
|
||||
|
||||
type PlatformInterface interface {
|
||||
adapter.PlatformInterface
|
||||
|
||||
UsePlatformLocalDNSTransport() bool
|
||||
LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions]
|
||||
}
|
||||
@@ -1,774 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
"github.com/sagernet/sing/common/observable"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
var _ StartedServiceServer = (*StartedService)(nil)
|
||||
|
||||
type StartedService struct {
|
||||
ctx context.Context
|
||||
platform PlatformInterface
|
||||
platformHandler PlatformHandler
|
||||
debug bool
|
||||
logMaxLines int
|
||||
workingDirectory string
|
||||
tempDirectory string
|
||||
userID int
|
||||
groupID int
|
||||
systemProxyEnabled bool
|
||||
serviceAccess sync.RWMutex
|
||||
serviceStatus *ServiceStatus
|
||||
serviceStatusSubscriber *observable.Subscriber[*ServiceStatus]
|
||||
serviceStatusObserver *observable.Observer[*ServiceStatus]
|
||||
logAccess sync.RWMutex
|
||||
logLines list.List[*log.Entry]
|
||||
logSubscriber *observable.Subscriber[*log.Entry]
|
||||
logObserver *observable.Observer[*log.Entry]
|
||||
instance *Instance
|
||||
urlTestSubscriber *observable.Subscriber[struct{}]
|
||||
urlTestObserver *observable.Observer[struct{}]
|
||||
urlTestHistoryStorage *urltest.HistoryStorage
|
||||
clashModeSubscriber *observable.Subscriber[struct{}]
|
||||
clashModeObserver *observable.Observer[struct{}]
|
||||
}
|
||||
|
||||
type ServiceOptions struct {
|
||||
Context context.Context
|
||||
Platform PlatformInterface
|
||||
PlatformHandler PlatformHandler
|
||||
Debug bool
|
||||
LogMaxLines int
|
||||
WorkingDirectory string
|
||||
TempDirectory string
|
||||
UserID int
|
||||
GroupID int
|
||||
SystemProxyEnabled bool
|
||||
}
|
||||
|
||||
func NewStartedService(options ServiceOptions) *StartedService {
|
||||
s := &StartedService{
|
||||
ctx: options.Context,
|
||||
platform: options.Platform,
|
||||
platformHandler: options.PlatformHandler,
|
||||
debug: options.Debug,
|
||||
logMaxLines: options.LogMaxLines,
|
||||
workingDirectory: options.WorkingDirectory,
|
||||
tempDirectory: options.TempDirectory,
|
||||
userID: options.UserID,
|
||||
groupID: options.GroupID,
|
||||
systemProxyEnabled: options.SystemProxyEnabled,
|
||||
serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE},
|
||||
serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4),
|
||||
logSubscriber: observable.NewSubscriber[*log.Entry](128),
|
||||
urlTestSubscriber: observable.NewSubscriber[struct{}](1),
|
||||
urlTestHistoryStorage: urltest.NewHistoryStorage(),
|
||||
clashModeSubscriber: observable.NewSubscriber[struct{}](1),
|
||||
}
|
||||
s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2)
|
||||
s.logObserver = observable.NewObserver(s.logSubscriber, 64)
|
||||
s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1)
|
||||
s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *StartedService) resetLogs() {
|
||||
s.logAccess.Lock()
|
||||
s.logLines = list.List[*log.Entry]{}
|
||||
s.logAccess.Unlock()
|
||||
s.logSubscriber.Emit(nil)
|
||||
}
|
||||
|
||||
func (s *StartedService) updateStatus(newStatus ServiceStatus_Type) {
|
||||
statusObject := &ServiceStatus{Status: newStatus}
|
||||
s.serviceStatusSubscriber.Emit(statusObject)
|
||||
s.serviceStatus = statusObject
|
||||
}
|
||||
|
||||
func (s *StartedService) updateStatusError(err error) error {
|
||||
statusObject := &ServiceStatus{Status: ServiceStatus_FATAL, ErrorMessage: err.Error()}
|
||||
s.serviceStatusSubscriber.Emit(statusObject)
|
||||
s.serviceStatus = statusObject
|
||||
s.serviceAccess.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error {
|
||||
s.serviceAccess.Lock()
|
||||
switch s.serviceStatus.Status {
|
||||
case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING:
|
||||
default:
|
||||
s.serviceAccess.Unlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
s.updateStatus(ServiceStatus_STARTING)
|
||||
s.resetLogs()
|
||||
instance, err := s.newInstance(profileContent, options)
|
||||
if err != nil {
|
||||
return s.updateStatusError(err)
|
||||
}
|
||||
s.instance = instance
|
||||
s.serviceAccess.Unlock()
|
||||
err = instance.Start()
|
||||
s.serviceAccess.Lock()
|
||||
if s.serviceStatus.Status != ServiceStatus_STARTING {
|
||||
s.serviceAccess.Unlock()
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return s.updateStatusError(err)
|
||||
}
|
||||
s.updateStatus(ServiceStatus_STARTED)
|
||||
s.serviceAccess.Unlock()
|
||||
runtime.GC()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StartedService) CloseService() error {
|
||||
s.serviceAccess.Lock()
|
||||
switch s.serviceStatus.Status {
|
||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
||||
default:
|
||||
s.serviceAccess.Unlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
s.updateStatus(ServiceStatus_STOPPING)
|
||||
if s.instance != nil {
|
||||
err := s.instance.Close()
|
||||
if err != nil {
|
||||
return s.updateStatusError(err)
|
||||
}
|
||||
}
|
||||
s.instance = nil
|
||||
s.updateStatus(ServiceStatus_IDLE)
|
||||
s.serviceAccess.Unlock()
|
||||
runtime.GC()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SetError(err error) {
|
||||
s.serviceAccess.Lock()
|
||||
s.updateStatusError(err)
|
||||
s.serviceAccess.Unlock()
|
||||
s.WriteMessage(log.LevelError, err.Error())
|
||||
}
|
||||
|
||||
func (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
err := s.platformHandler.ServiceStop()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
err := s.platformHandler.ServiceReload()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeServiceStatus(empty *emptypb.Empty, server grpc.ServerStreamingServer[ServiceStatus]) error {
|
||||
subscription, done, err := s.serviceStatusObserver.Subscribe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.serviceStatusObserver.UnSubscribe(subscription)
|
||||
err = server.Send(s.serviceStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
case <-server.Context().Done():
|
||||
return server.Context().Err()
|
||||
case newStatus := <-subscription:
|
||||
err = server.Send(newStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerStreamingServer[Log]) error {
|
||||
var savedLines []*log.Entry
|
||||
s.logAccess.Lock()
|
||||
savedLines = make([]*log.Entry, 0, s.logLines.Len())
|
||||
for element := s.logLines.Front(); element != nil; element = element.Next() {
|
||||
savedLines = append(savedLines, element.Value)
|
||||
}
|
||||
s.logAccess.Unlock()
|
||||
subscription, done, err := s.logObserver.Subscribe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.logObserver.UnSubscribe(subscription)
|
||||
err = server.Send(&Log{
|
||||
Messages: common.Map(savedLines, func(it *log.Entry) *Log_Message {
|
||||
return &Log_Message{
|
||||
Level: LogLevel(it.Level),
|
||||
Message: it.Message,
|
||||
}
|
||||
}),
|
||||
Reset_: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
case <-server.Context().Done():
|
||||
return server.Context().Err()
|
||||
case message := <-subscription:
|
||||
if message == nil {
|
||||
err = server.Send(&Log{Reset_: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
messages := []*Log_Message{{
|
||||
Level: LogLevel(message.Level),
|
||||
Message: message.Message,
|
||||
}}
|
||||
fetch:
|
||||
for {
|
||||
select {
|
||||
case message = <-subscription:
|
||||
messages = append(messages, &Log_Message{
|
||||
Level: LogLevel(message.Level),
|
||||
Message: message.Message,
|
||||
})
|
||||
default:
|
||||
break fetch
|
||||
}
|
||||
}
|
||||
err = server.Send(&Log{Messages: messages})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) GetDefaultLogLevel(ctx context.Context, empty *emptypb.Empty) (*DefaultLogLevel, error) {
|
||||
s.serviceAccess.RLock()
|
||||
switch s.serviceStatus.Status {
|
||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
||||
default:
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
logLevel := s.instance.instance.LogFactory().Level()
|
||||
s.serviceAccess.RUnlock()
|
||||
return &DefaultLogLevel{Level: LogLevel(logLevel)}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server grpc.ServerStreamingServer[Status]) error {
|
||||
interval := time.Duration(request.Interval)
|
||||
if interval <= 0 {
|
||||
interval = time.Second // Default to 1 second
|
||||
}
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
status := s.readStatus()
|
||||
uploadTotal := status.UplinkTotal
|
||||
downloadTotal := status.DownlinkTotal
|
||||
for {
|
||||
err := server.Send(status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
case <-server.Context().Done():
|
||||
return server.Context().Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
status = s.readStatus()
|
||||
upload := status.UplinkTotal - uploadTotal
|
||||
download := status.DownlinkTotal - downloadTotal
|
||||
uploadTotal = status.UplinkTotal
|
||||
downloadTotal = status.DownlinkTotal
|
||||
status.Uplink = upload
|
||||
status.Downlink = download
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) readStatus() *Status {
|
||||
var status Status
|
||||
status.Memory = memory.Inuse()
|
||||
status.Goroutines = int32(runtime.NumGoroutine())
|
||||
status.ConnectionsOut = int32(conntrack.Count())
|
||||
nowService := s.instance
|
||||
if nowService != nil {
|
||||
if clashServer := nowService.clashServer; clashServer != nil {
|
||||
status.TrafficAvailable = true
|
||||
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
|
||||
status.UplinkTotal, status.DownlinkTotal = trafficManager.Total()
|
||||
status.ConnectionsIn = int32(trafficManager.ConnectionsLen())
|
||||
}
|
||||
}
|
||||
return &status
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeGroups(empty *emptypb.Empty, server grpc.ServerStreamingServer[Groups]) error {
|
||||
subscription, done, err := s.urlTestObserver.Subscribe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.urlTestObserver.UnSubscribe(subscription)
|
||||
for {
|
||||
s.serviceAccess.RLock()
|
||||
switch s.serviceStatus.Status {
|
||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
||||
groups := s.readGroups()
|
||||
s.serviceAccess.RUnlock()
|
||||
err = server.Send(groups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
s.serviceAccess.RUnlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
select {
|
||||
case <-subscription:
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
case <-server.Context().Done():
|
||||
return server.Context().Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) readGroups() *Groups {
|
||||
historyStorage := s.instance.urlTestHistoryStorage
|
||||
boxService := s.instance
|
||||
outbounds := boxService.instance.Outbound().Outbounds()
|
||||
var iGroups []adapter.OutboundGroup
|
||||
for _, it := range outbounds {
|
||||
if group, isGroup := it.(adapter.OutboundGroup); isGroup {
|
||||
iGroups = append(iGroups, group)
|
||||
}
|
||||
}
|
||||
var gs Groups
|
||||
for _, iGroup := range iGroups {
|
||||
var g Group
|
||||
g.Tag = iGroup.Tag()
|
||||
g.Type = iGroup.Type()
|
||||
_, g.Selectable = iGroup.(*group.Selector)
|
||||
g.Selected = iGroup.Now()
|
||||
if boxService.cacheFile != nil {
|
||||
if isExpand, loaded := boxService.cacheFile.LoadGroupExpand(g.Tag); loaded {
|
||||
g.IsExpand = isExpand
|
||||
}
|
||||
}
|
||||
|
||||
for _, itemTag := range iGroup.All() {
|
||||
itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag)
|
||||
if !isLoaded {
|
||||
continue
|
||||
}
|
||||
|
||||
var item GroupItem
|
||||
item.Tag = itemTag
|
||||
item.Type = itemOutbound.Type()
|
||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil {
|
||||
item.UrlTestTime = history.Time.Unix()
|
||||
item.UrlTestDelay = int32(history.Delay)
|
||||
}
|
||||
g.Items = append(g.Items, &item)
|
||||
}
|
||||
if len(g.Items) < 2 {
|
||||
continue
|
||||
}
|
||||
gs.Group = append(gs.Group, &g)
|
||||
}
|
||||
return &gs
|
||||
}
|
||||
|
||||
func (s *StartedService) GetClashModeStatus(ctx context.Context, empty *emptypb.Empty) (*ClashModeStatus, error) {
|
||||
s.serviceAccess.RLock()
|
||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
clashServer := s.instance.clashServer
|
||||
s.serviceAccess.RUnlock()
|
||||
if clashServer == nil {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return &ClashModeStatus{
|
||||
ModeList: clashServer.ModeList(),
|
||||
CurrentMode: clashServer.Mode(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.ServerStreamingServer[ClashMode]) error {
|
||||
subscription, done, err := s.clashModeObserver.Subscribe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.clashModeObserver.UnSubscribe(subscription)
|
||||
for {
|
||||
select {
|
||||
case <-subscription:
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
case <-server.Context().Done():
|
||||
return server.Context().Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
||||
return nil
|
||||
}
|
||||
message := &ClashMode{Mode: s.instance.clashServer.Mode()}
|
||||
s.serviceAccess.RUnlock()
|
||||
err = server.Send(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) SetClashMode(ctx context.Context, request *ClashMode) (*emptypb.Empty, error) {
|
||||
s.serviceAccess.RLock()
|
||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
clashServer := s.instance.clashServer
|
||||
s.serviceAccess.RUnlock()
|
||||
clashServer.(*clashapi.Server).SetMode(request.Mode)
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) URLTest(ctx context.Context, request *URLTestRequest) (*emptypb.Empty, error) {
|
||||
s.serviceAccess.RLock()
|
||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
groupTag := request.OutboundTag
|
||||
abstractOutboundGroup, isLoaded := boxService.instance.Outbound().Outbound(groupTag)
|
||||
if !isLoaded {
|
||||
return nil, E.New("outbound group not found: ", groupTag)
|
||||
}
|
||||
outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup)
|
||||
if !isOutboundGroup {
|
||||
return nil, E.New("outbound is not a group: ", groupTag)
|
||||
}
|
||||
urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest)
|
||||
if isURLTest {
|
||||
go urlTest.CheckOutbounds()
|
||||
} else {
|
||||
var historyStorage adapter.URLTestHistoryStorage
|
||||
if s.instance.clashServer != nil {
|
||||
historyStorage = s.instance.clashServer.HistoryStorage()
|
||||
} else {
|
||||
return nil, E.New("Clash API is required for URLTest on non-URLTest group")
|
||||
}
|
||||
|
||||
outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||
itOutbound, _ := boxService.instance.Outbound().Outbound(it)
|
||||
return itOutbound
|
||||
}), func(it adapter.Outbound) bool {
|
||||
if it == nil {
|
||||
return false
|
||||
}
|
||||
_, isGroup := it.(adapter.OutboundGroup)
|
||||
if isGroup {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
b, _ := batch.New(boxService.ctx, batch.WithConcurrencyNum[any](10))
|
||||
for _, detour := range outbounds {
|
||||
outboundToTest := detour
|
||||
outboundTag := outboundToTest.Tag()
|
||||
b.Go(outboundTag, func() (any, error) {
|
||||
t, err := urltest.URLTest(boxService.ctx, "", outboundToTest)
|
||||
if err != nil {
|
||||
historyStorage.DeleteURLTestHistory(outboundTag)
|
||||
} else {
|
||||
historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{
|
||||
Time: time.Now(),
|
||||
Delay: t,
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SelectOutbound(ctx context.Context, request *SelectOutboundRequest) (*emptypb.Empty, error) {
|
||||
s.serviceAccess.RLock()
|
||||
switch s.serviceStatus.Status {
|
||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
||||
default:
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
boxService := s.instance.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
outboundGroup, isLoaded := boxService.Outbound().Outbound(request.GroupTag)
|
||||
if !isLoaded {
|
||||
return nil, E.New("selector not found: ", request.GroupTag)
|
||||
}
|
||||
selector, isSelector := outboundGroup.(*group.Selector)
|
||||
if !isSelector {
|
||||
return nil, E.New("outbound is not a selector: ", request.GroupTag)
|
||||
}
|
||||
if !selector.SelectOutbound(request.OutboundTag) {
|
||||
return nil, E.New("outbound not found in selector: ", request.OutboundTag)
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SetGroupExpand(ctx context.Context, request *SetGroupExpandRequest) (*emptypb.Empty, error) {
|
||||
s.serviceAccess.RLock()
|
||||
switch s.serviceStatus.Status {
|
||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
||||
default:
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
if boxService.cacheFile != nil {
|
||||
err := boxService.cacheFile.StoreGroupExpand(request.GroupTag, request.IsExpand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) {
|
||||
return s.platformHandler.SystemProxyStatus()
|
||||
}
|
||||
|
||||
func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
||||
err := s.platformHandler.SetSystemProxyEnabled(request.Enabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error {
|
||||
s.serviceAccess.RLock()
|
||||
switch s.serviceStatus.Status {
|
||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
||||
default:
|
||||
s.serviceAccess.RUnlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
ticker := time.NewTicker(time.Duration(request.Interval))
|
||||
defer ticker.Stop()
|
||||
trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager()
|
||||
var (
|
||||
connections = make(map[uuid.UUID]*Connection)
|
||||
outConnections []*Connection
|
||||
)
|
||||
for {
|
||||
outConnections = outConnections[:0]
|
||||
for _, connection := range trafficManager.Connections() {
|
||||
outConnections = append(outConnections, newConnection(connections, connection, false))
|
||||
}
|
||||
for _, connection := range trafficManager.ClosedConnections() {
|
||||
outConnections = append(outConnections, newConnection(connections, connection, true))
|
||||
}
|
||||
err := server.Send(&Connections{Connections: outConnections})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
case <-server.Context().Done():
|
||||
return server.Context().Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) *Connection {
|
||||
if oldConnection, loaded := connections[metadata.ID]; loaded {
|
||||
if isClosed {
|
||||
if oldConnection.ClosedAt == 0 {
|
||||
oldConnection.Uplink = 0
|
||||
oldConnection.Downlink = 0
|
||||
oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli()
|
||||
}
|
||||
return oldConnection
|
||||
}
|
||||
lastUplink := oldConnection.UplinkTotal
|
||||
lastDownlink := oldConnection.DownlinkTotal
|
||||
uplinkTotal := metadata.Upload.Load()
|
||||
downlinkTotal := metadata.Download.Load()
|
||||
oldConnection.Uplink = uplinkTotal - lastUplink
|
||||
oldConnection.Downlink = downlinkTotal - lastDownlink
|
||||
oldConnection.UplinkTotal = uplinkTotal
|
||||
oldConnection.DownlinkTotal = downlinkTotal
|
||||
return oldConnection
|
||||
}
|
||||
var rule string
|
||||
if metadata.Rule != nil {
|
||||
rule = metadata.Rule.String()
|
||||
}
|
||||
uplinkTotal := metadata.Upload.Load()
|
||||
downlinkTotal := metadata.Download.Load()
|
||||
uplink := uplinkTotal
|
||||
downlink := downlinkTotal
|
||||
var closedAt int64
|
||||
if !metadata.ClosedAt.IsZero() {
|
||||
closedAt = metadata.ClosedAt.UnixMilli()
|
||||
uplink = 0
|
||||
downlink = 0
|
||||
}
|
||||
connection := &Connection{
|
||||
Id: metadata.ID.String(),
|
||||
Inbound: metadata.Metadata.Inbound,
|
||||
InboundType: metadata.Metadata.InboundType,
|
||||
IpVersion: int32(metadata.Metadata.IPVersion),
|
||||
Network: metadata.Metadata.Network,
|
||||
Source: metadata.Metadata.Source.String(),
|
||||
Destination: metadata.Metadata.Destination.String(),
|
||||
Domain: metadata.Metadata.Domain,
|
||||
Protocol: metadata.Metadata.Protocol,
|
||||
User: metadata.Metadata.User,
|
||||
FromOutbound: metadata.Metadata.Outbound,
|
||||
CreatedAt: metadata.CreatedAt.UnixMilli(),
|
||||
ClosedAt: closedAt,
|
||||
Uplink: uplink,
|
||||
Downlink: downlink,
|
||||
UplinkTotal: uplinkTotal,
|
||||
DownlinkTotal: downlinkTotal,
|
||||
Rule: rule,
|
||||
Outbound: metadata.Outbound,
|
||||
OutboundType: metadata.OutboundType,
|
||||
ChainList: metadata.Chain,
|
||||
}
|
||||
connections[metadata.ID] = connection
|
||||
return connection
|
||||
}
|
||||
|
||||
func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) {
|
||||
s.serviceAccess.RLock()
|
||||
switch s.serviceStatus.Status {
|
||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
||||
default:
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
targetConn := boxService.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(request.Id))
|
||||
if targetConn != nil {
|
||||
targetConn.Close()
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
conntrack.Close()
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *emptypb.Empty) (*DeprecatedWarnings, error) {
|
||||
s.serviceAccess.RLock()
|
||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
notes := service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get()
|
||||
return &DeprecatedWarnings{
|
||||
Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning {
|
||||
return &DeprecatedWarning{
|
||||
Message: it.Message(),
|
||||
Impending: it.Impending(),
|
||||
MigrationLink: it.MigrationLink,
|
||||
}
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeHelperEvents(empty *emptypb.Empty, server grpc.ServerStreamingServer[HelperRequest]) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (s *StartedService) SendHelperResponse(ctx context.Context, response *HelperResponse) (*emptypb.Empty, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
||||
}
|
||||
|
||||
func (s *StartedService) WriteMessage(level log.Level, message string) {
|
||||
item := &log.Entry{Level: level, Message: message}
|
||||
s.logSubscriber.Emit(item)
|
||||
s.logAccess.Lock()
|
||||
s.logLines.PushBack(item)
|
||||
if s.logLines.Len() > s.logMaxLines {
|
||||
s.logLines.Remove(s.logLines.Front())
|
||||
}
|
||||
s.logAccess.Unlock()
|
||||
if s.debug {
|
||||
s.platformHandler.WriteDebugMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) Instance() *Instance {
|
||||
s.serviceAccess.RLock()
|
||||
defer s.serviceAccess.RUnlock()
|
||||
return s.instance
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,204 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package daemon;
|
||||
option go_package = "github.com/sagernet/sing-box/daemon";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "daemon/helper.proto";
|
||||
|
||||
service StartedService {
|
||||
rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
|
||||
rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {}
|
||||
rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {}
|
||||
rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {}
|
||||
rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {}
|
||||
rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {}
|
||||
|
||||
rpc GetClashModeStatus(google.protobuf.Empty) returns(ClashModeStatus) {}
|
||||
rpc SubscribeClashMode(google.protobuf.Empty) returns(stream ClashMode) {}
|
||||
rpc SetClashMode(ClashMode) returns(google.protobuf.Empty) {}
|
||||
|
||||
rpc URLTest(URLTestRequest) returns(google.protobuf.Empty) {}
|
||||
rpc SelectOutbound(SelectOutboundRequest) returns (google.protobuf.Empty) {}
|
||||
rpc SetGroupExpand(SetGroupExpandRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
||||
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
||||
|
||||
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream Connections) {}
|
||||
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
||||
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
||||
|
||||
rpc SubscribeHelperEvents(google.protobuf.Empty) returns(stream HelperRequest) {}
|
||||
rpc SendHelperResponse(HelperResponse) returns(google.protobuf.Empty) {}
|
||||
}
|
||||
|
||||
message ServiceStatus {
|
||||
enum Type {
|
||||
IDLE = 0;
|
||||
STARTING = 1;
|
||||
STARTED = 2;
|
||||
STOPPING = 3;
|
||||
FATAL = 4;
|
||||
}
|
||||
Type status = 1;
|
||||
string errorMessage = 2;
|
||||
}
|
||||
|
||||
message ReloadServiceRequest {
|
||||
string newProfileContent = 1;
|
||||
}
|
||||
|
||||
message SubscribeStatusRequest {
|
||||
int64 interval = 1;
|
||||
}
|
||||
|
||||
enum LogLevel {
|
||||
PANIC = 0;
|
||||
FATAL = 1;
|
||||
ERROR = 2;
|
||||
WARN = 3;
|
||||
INFO = 4;
|
||||
DEBUG = 5;
|
||||
TRACE = 6;
|
||||
}
|
||||
|
||||
message Log {
|
||||
repeated Message messages = 1;
|
||||
bool reset = 2;
|
||||
message Message {
|
||||
LogLevel level = 1;
|
||||
string message = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message DefaultLogLevel {
|
||||
LogLevel level = 1;
|
||||
}
|
||||
|
||||
message Status {
|
||||
uint64 memory = 1;
|
||||
int32 goroutines = 2;
|
||||
int32 connectionsIn = 3;
|
||||
int32 connectionsOut = 4;
|
||||
bool trafficAvailable = 5;
|
||||
int64 uplink = 6;
|
||||
int64 downlink = 7;
|
||||
int64 uplinkTotal = 8;
|
||||
int64 downlinkTotal = 9;
|
||||
}
|
||||
|
||||
message Groups {
|
||||
repeated Group group = 1;
|
||||
}
|
||||
|
||||
message Group {
|
||||
string tag = 1;
|
||||
string type = 2;
|
||||
bool selectable = 3;
|
||||
string selected = 4;
|
||||
bool isExpand = 5;
|
||||
repeated GroupItem items = 6;
|
||||
}
|
||||
|
||||
message GroupItem {
|
||||
string tag = 1;
|
||||
string type = 2;
|
||||
int64 urlTestTime = 3;
|
||||
int32 urlTestDelay = 4;
|
||||
}
|
||||
|
||||
message URLTestRequest {
|
||||
string outboundTag = 1;
|
||||
}
|
||||
|
||||
message SelectOutboundRequest {
|
||||
string groupTag = 1;
|
||||
string outboundTag = 2;
|
||||
}
|
||||
|
||||
message SetGroupExpandRequest {
|
||||
string groupTag = 1;
|
||||
bool isExpand = 2;
|
||||
}
|
||||
|
||||
message ClashMode {
|
||||
string mode = 3;
|
||||
}
|
||||
|
||||
message ClashModeStatus {
|
||||
repeated string modeList = 1;
|
||||
string currentMode = 2;
|
||||
}
|
||||
|
||||
message SystemProxyStatus {
|
||||
bool available = 1;
|
||||
bool enabled = 2;
|
||||
}
|
||||
|
||||
message SetSystemProxyEnabledRequest {
|
||||
bool enabled = 1;
|
||||
}
|
||||
|
||||
message SubscribeConnectionsRequest {
|
||||
int64 interval = 1;
|
||||
ConnectionFilter filter = 2;
|
||||
ConnectionSortBy sortBy = 3;
|
||||
}
|
||||
|
||||
enum ConnectionFilter {
|
||||
ALL = 0;
|
||||
ACTIVE = 1;
|
||||
CLOSED = 2;
|
||||
}
|
||||
|
||||
enum ConnectionSortBy {
|
||||
DATE = 0;
|
||||
TRAFFIC = 1;
|
||||
TOTAL_TRAFFIC = 2;
|
||||
}
|
||||
|
||||
message Connections {
|
||||
repeated Connection connections = 1;
|
||||
}
|
||||
|
||||
message Connection {
|
||||
string id = 1;
|
||||
string inbound = 2;
|
||||
string inboundType = 3;
|
||||
int32 ipVersion = 4;
|
||||
string network = 5;
|
||||
string source = 6;
|
||||
string destination = 7;
|
||||
string domain = 8;
|
||||
string protocol = 9;
|
||||
string user = 10;
|
||||
string fromOutbound = 11;
|
||||
int64 createdAt = 12;
|
||||
int64 closedAt = 13;
|
||||
int64 uplink = 14;
|
||||
int64 downlink = 15;
|
||||
int64 uplinkTotal = 16;
|
||||
int64 downlinkTotal = 17;
|
||||
string rule = 18;
|
||||
string outbound = 19;
|
||||
string outboundType = 20;
|
||||
repeated string chainList = 21;
|
||||
}
|
||||
|
||||
message CloseConnectionRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message DeprecatedWarnings {
|
||||
repeated DeprecatedWarning warnings = 1;
|
||||
}
|
||||
|
||||
message DeprecatedWarning {
|
||||
string message = 1;
|
||||
bool impending = 2;
|
||||
string migrationLink = 3;
|
||||
}
|
||||
@@ -1,919 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
|
||||
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
|
||||
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
||||
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
||||
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
||||
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
||||
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||
StartedService_SubscribeHelperEvents_FullMethodName = "/daemon.StartedService/SubscribeHelperEvents"
|
||||
StartedService_SendHelperResponse_FullMethodName = "/daemon.StartedService/SendHelperResponse"
|
||||
)
|
||||
|
||||
// StartedServiceClient is the client API for StartedService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type StartedServiceClient interface {
|
||||
StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error)
|
||||
SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error)
|
||||
GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error)
|
||||
SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error)
|
||||
SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error)
|
||||
GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error)
|
||||
SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error)
|
||||
SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error)
|
||||
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
||||
SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error)
|
||||
SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
}
|
||||
|
||||
type startedServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewStartedServiceClient(cc grpc.ClientConnInterface) StartedServiceClient {
|
||||
return &startedServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_StopService_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_ReloadService_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[0], StartedService_SubscribeServiceStatus_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, ServiceStatus]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeServiceStatusClient = grpc.ServerStreamingClient[ServiceStatus]
|
||||
|
||||
func (c *startedServiceClient) SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[1], StartedService_SubscribeLog_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, Log]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeLogClient = grpc.ServerStreamingClient[Log]
|
||||
|
||||
func (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DefaultLogLevel)
|
||||
err := c.cc.Invoke(ctx, StartedService_GetDefaultLogLevel_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[SubscribeStatusRequest, Status]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeStatusClient = grpc.ServerStreamingClient[Status]
|
||||
|
||||
func (c *startedServiceClient) SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[3], StartedService_SubscribeGroups_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, Groups]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeGroupsClient = grpc.ServerStreamingClient[Groups]
|
||||
|
||||
func (c *startedServiceClient) GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ClashModeStatus)
|
||||
err := c.cc.Invoke(ctx, StartedService_GetClashModeStatus_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[4], StartedService_SubscribeClashMode_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, ClashMode]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeClashModeClient = grpc.ServerStreamingClient[ClashMode]
|
||||
|
||||
func (c *startedServiceClient) SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_SetClashMode_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_URLTest_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_SelectOutbound_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_SetGroupExpand_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(SystemProxyStatus)
|
||||
err := c.cc.Invoke(ctx, StartedService_GetSystemProxyStatus_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_SetSystemProxyEnabled_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[SubscribeConnectionsRequest, Connections]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[Connections]
|
||||
|
||||
func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_CloseConnection_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_CloseAllConnections_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DeprecatedWarnings)
|
||||
err := c.cc.Invoke(ctx, StartedService_GetDeprecatedWarnings_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeHelperEvents_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, HelperRequest]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeHelperEventsClient = grpc.ServerStreamingClient[HelperRequest]
|
||||
|
||||
func (c *startedServiceClient) SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_SendHelperResponse_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// StartedServiceServer is the server API for StartedService service.
|
||||
// All implementations must embed UnimplementedStartedServiceServer
|
||||
// for forward compatibility.
|
||||
type StartedServiceServer interface {
|
||||
StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error
|
||||
SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error
|
||||
GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error)
|
||||
SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error
|
||||
SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error
|
||||
GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error)
|
||||
SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error
|
||||
SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error)
|
||||
URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error)
|
||||
SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error)
|
||||
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
||||
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
||||
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error
|
||||
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
||||
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
||||
SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error
|
||||
SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error)
|
||||
mustEmbedUnimplementedStartedServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedStartedServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedStartedServiceServer struct{}
|
||||
|
||||
func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method StopService not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ReloadService not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SubscribeServiceStatus not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SubscribeLog not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetDefaultLogLevel not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SubscribeGroups not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetClashModeStatus not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SubscribeClashMode not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SetClashMode not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method URLTest not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SelectOutbound not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SetGroupExpand not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetSystemProxyStatus not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SubscribeConnections not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CloseConnection not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CloseAllConnections not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetDeprecatedWarnings not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SubscribeHelperEvents not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SendHelperResponse not implemented")
|
||||
}
|
||||
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
||||
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeStartedServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to StartedServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeStartedServiceServer interface {
|
||||
mustEmbedUnimplementedStartedServiceServer()
|
||||
}
|
||||
|
||||
func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) {
|
||||
// If the following call pancis, it indicates UnimplementedStartedServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&StartedService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _StartedService_StopService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).StopService(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_StopService_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).StopService(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_ReloadService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).ReloadService(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_ReloadService_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).ReloadService(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeServiceStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(emptypb.Empty)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeServiceStatus(m, &grpc.GenericServerStream[emptypb.Empty, ServiceStatus]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeServiceStatusServer = grpc.ServerStreamingServer[ServiceStatus]
|
||||
|
||||
func _StartedService_SubscribeLog_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(emptypb.Empty)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeLog(m, &grpc.GenericServerStream[emptypb.Empty, Log]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeLogServer = grpc.ServerStreamingServer[Log]
|
||||
|
||||
func _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_GetDefaultLogLevel_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(SubscribeStatusRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeStatus(m, &grpc.GenericServerStream[SubscribeStatusRequest, Status]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeStatusServer = grpc.ServerStreamingServer[Status]
|
||||
|
||||
func _StartedService_SubscribeGroups_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(emptypb.Empty)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeGroups(m, &grpc.GenericServerStream[emptypb.Empty, Groups]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeGroupsServer = grpc.ServerStreamingServer[Groups]
|
||||
|
||||
func _StartedService_GetClashModeStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).GetClashModeStatus(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_GetClashModeStatus_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).GetClashModeStatus(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeClashMode_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(emptypb.Empty)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeClashMode(m, &grpc.GenericServerStream[emptypb.Empty, ClashMode]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeClashModeServer = grpc.ServerStreamingServer[ClashMode]
|
||||
|
||||
func _StartedService_SetClashMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ClashMode)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).SetClashMode(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_SetClashMode_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).SetClashMode(ctx, req.(*ClashMode))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_URLTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(URLTestRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).URLTest(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_URLTest_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).URLTest(ctx, req.(*URLTestRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SelectOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SelectOutboundRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).SelectOutbound(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_SelectOutbound_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).SelectOutbound(ctx, req.(*SelectOutboundRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SetGroupExpand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SetGroupExpandRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).SetGroupExpand(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_SetGroupExpand_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).SetGroupExpand(ctx, req.(*SetGroupExpandRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_GetSystemProxyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_GetSystemProxyStatus_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SetSystemProxyEnabledRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_SetSystemProxyEnabled_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, req.(*SetSystemProxyEnabledRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(SubscribeConnectionsRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, Connections]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[Connections]
|
||||
|
||||
func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CloseConnectionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).CloseConnection(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_CloseConnection_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).CloseConnection(ctx, req.(*CloseConnectionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_CloseAllConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).CloseAllConnections(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_CloseAllConnections_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).CloseAllConnections(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_GetDeprecatedWarnings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_GetDeprecatedWarnings_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeHelperEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(emptypb.Empty)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeHelperEvents(m, &grpc.GenericServerStream[emptypb.Empty, HelperRequest]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeHelperEventsServer = grpc.ServerStreamingServer[HelperRequest]
|
||||
|
||||
func _StartedService_SendHelperResponse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(HelperResponse)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).SendHelperResponse(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_SendHelperResponse_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).SendHelperResponse(ctx, req.(*HelperResponse))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "daemon.StartedService",
|
||||
HandlerType: (*StartedServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "StopService",
|
||||
Handler: _StartedService_StopService_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ReloadService",
|
||||
Handler: _StartedService_ReloadService_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetDefaultLogLevel",
|
||||
Handler: _StartedService_GetDefaultLogLevel_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetClashModeStatus",
|
||||
Handler: _StartedService_GetClashModeStatus_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SetClashMode",
|
||||
Handler: _StartedService_SetClashMode_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "URLTest",
|
||||
Handler: _StartedService_URLTest_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SelectOutbound",
|
||||
Handler: _StartedService_SelectOutbound_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SetGroupExpand",
|
||||
Handler: _StartedService_SetGroupExpand_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetSystemProxyStatus",
|
||||
Handler: _StartedService_GetSystemProxyStatus_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SetSystemProxyEnabled",
|
||||
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CloseConnection",
|
||||
Handler: _StartedService_CloseConnection_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CloseAllConnections",
|
||||
Handler: _StartedService_CloseAllConnections_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetDeprecatedWarnings",
|
||||
Handler: _StartedService_GetDeprecatedWarnings_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SendHelperResponse",
|
||||
Handler: _StartedService_SendHelperResponse_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "SubscribeServiceStatus",
|
||||
Handler: _StartedService_SubscribeServiceStatus_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SubscribeLog",
|
||||
Handler: _StartedService_SubscribeLog_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SubscribeStatus",
|
||||
Handler: _StartedService_SubscribeStatus_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SubscribeGroups",
|
||||
Handler: _StartedService_SubscribeGroups_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SubscribeClashMode",
|
||||
Handler: _StartedService_SubscribeClashMode_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SubscribeConnections",
|
||||
Handler: _StartedService_SubscribeConnections_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SubscribeHelperEvents",
|
||||
Handler: _StartedService_SubscribeHelperEvents_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "daemon/started_service.proto",
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
@@ -37,7 +38,7 @@ type Router struct {
|
||||
rules []adapter.DNSRule
|
||||
defaultDomainStrategy C.DomainStrategy
|
||||
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||
platformInterface adapter.PlatformInterface
|
||||
platformInterface platform.Interface
|
||||
}
|
||||
|
||||
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
||||
|
||||
@@ -46,7 +46,7 @@ type HTTPSTransport struct {
|
||||
destination *url.URL
|
||||
headers http.Header
|
||||
transportAccess sync.Mutex
|
||||
transport *HTTPSTransportWrapper
|
||||
transport *http.Transport
|
||||
transportResetAt time.Time
|
||||
}
|
||||
|
||||
@@ -61,8 +61,11 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tlsConfig.NextProtos()) == 0 {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"})
|
||||
if common.Error(tlsConfig.STDConfig()) == nil && !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), http2.NextProtoTLS))
|
||||
}
|
||||
if !common.Contains(tlsConfig.NextProtos(), "http/1.1") {
|
||||
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), "http/1.1"))
|
||||
}
|
||||
headers := options.Headers.Build()
|
||||
host := headers.Get("Host")
|
||||
@@ -120,13 +123,29 @@ func NewHTTPSRaw(
|
||||
serverAddr M.Socksaddr,
|
||||
tlsConfig tls.Config,
|
||||
) *HTTPSTransport {
|
||||
var transport *http.Transport
|
||||
if tlsConfig != nil {
|
||||
tlsDialer := tls.NewDialer(dialer, tlsConfig)
|
||||
transport = &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return tlsDialer.DialContext(ctx, network, serverAddr)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, serverAddr)
|
||||
},
|
||||
}
|
||||
}
|
||||
return &HTTPSTransport{
|
||||
TransportAdapter: adapter,
|
||||
logger: logger,
|
||||
dialer: dialer,
|
||||
destination: destination,
|
||||
headers: headers,
|
||||
transport: NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr),
|
||||
transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var errFallback = E.New("fallback to HTTP/1.1")
|
||||
|
||||
type HTTPSTransportWrapper struct {
|
||||
http2Transport *http2.Transport
|
||||
httpTransport *http.Transport
|
||||
fallback *atomic.Bool
|
||||
}
|
||||
|
||||
func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPSTransportWrapper {
|
||||
var fallback atomic.Bool
|
||||
return &HTTPSTransportWrapper{
|
||||
http2Transport: &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) {
|
||||
tlsConn, err := dialer.DialTLSContext(ctx, serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := tlsConn.ConnectionState()
|
||||
if state.NegotiatedProtocol == http2.NextProtoTLS {
|
||||
return tlsConn, nil
|
||||
}
|
||||
tlsConn.Close()
|
||||
fallback.Store(true)
|
||||
return nil, errFallback
|
||||
},
|
||||
},
|
||||
httpTransport: &http.Transport{
|
||||
DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return dialer.DialTLSContext(ctx, serverAddr)
|
||||
},
|
||||
},
|
||||
fallback: &fallback,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
if h.fallback.Load() {
|
||||
return h.httpTransport.RoundTrip(request)
|
||||
} else {
|
||||
response, err := h.http2Transport.RoundTrip(request)
|
||||
if err != nil {
|
||||
if errors.Is(err, errFallback) {
|
||||
return h.httpTransport.RoundTrip(request)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HTTPSTransportWrapper) CloseIdleConnections() {
|
||||
h.http2Transport.CloseIdleConnections()
|
||||
h.httpTransport.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (h *HTTPSTransportWrapper) Clone() *HTTPSTransportWrapper {
|
||||
return &HTTPSTransportWrapper{
|
||||
httpTransport: h.httpTransport,
|
||||
http2Transport: &http2.Transport{
|
||||
DialTLSContext: h.http2Transport.DialTLSContext,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.13.0-alpha.20
|
||||
#### 1.13.0-alpha.19
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [type](#type)
|
||||
|
||||
# DNS Server
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### type
|
||||
|
||||
DNS 服务器的类型。
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| `resolved` | [Resolved](./resolved/) |
|
||||
|
||||
#### tag
|
||||
|
||||
DNS 服务器的标签。
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [type](#type)
|
||||
|
||||
# DNS Server
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### type
|
||||
|
||||
DNS 服务器的类型。
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| `resolved` | [Resolved](./resolved/) |
|
||||
|
||||
#### tag
|
||||
|
||||
DNS 服务器的标签。
|
||||
|
||||
@@ -45,15 +45,15 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
||||
if t.Metadata.ProcessInfo != nil {
|
||||
if t.Metadata.ProcessInfo.ProcessPath != "" {
|
||||
processPath = t.Metadata.ProcessInfo.ProcessPath
|
||||
} else if t.Metadata.ProcessInfo.AndroidPackageName != "" {
|
||||
processPath = t.Metadata.ProcessInfo.AndroidPackageName
|
||||
} else if t.Metadata.ProcessInfo.PackageName != "" {
|
||||
processPath = t.Metadata.ProcessInfo.PackageName
|
||||
}
|
||||
if processPath == "" {
|
||||
if t.Metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(t.Metadata.ProcessInfo.UserId)
|
||||
}
|
||||
} else if t.Metadata.ProcessInfo.UserName != "" {
|
||||
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserName, ")")
|
||||
} else if t.Metadata.ProcessInfo.User != "" {
|
||||
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.User, ")")
|
||||
} else if t.Metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,18 @@ package libbox
|
||||
const (
|
||||
CommandLog int32 = iota
|
||||
CommandStatus
|
||||
CommandServiceReload
|
||||
CommandServiceClose
|
||||
CommandCloseConnections
|
||||
CommandGroup
|
||||
CommandSelectOutbound
|
||||
CommandURLTest
|
||||
CommandGroupExpand
|
||||
CommandClashMode
|
||||
CommandSetClashMode
|
||||
CommandGetSystemProxyStatus
|
||||
CommandSetSystemProxyEnabled
|
||||
CommandConnections
|
||||
CommandCloseConnection
|
||||
CommandGetDeprecatedNotes
|
||||
)
|
||||
|
||||
124
experimental/libbox/command_clash_mode.go
Normal file
124
experimental/libbox/command_clash_mode.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
func (c *CommandClient) SetClashMode(newMode string) error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandSetClashMode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = varbin.Write(conn, binary.BigEndian, newMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSetClashMode(conn net.Conn) error {
|
||||
newMode, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := s.service
|
||||
if service == nil {
|
||||
return writeError(conn, E.New("service not ready"))
|
||||
}
|
||||
service.clashServer.(*clashapi.Server).SetMode(newMode)
|
||||
return writeError(conn, nil)
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleModeConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
newMode, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.UpdateClashMode(newMode)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleModeConn(conn net.Conn) error {
|
||||
ctx := connKeepAlive(conn)
|
||||
for s.service == nil {
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
err := writeClashModeList(conn, s.service.clashServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-s.modeUpdate:
|
||||
err = varbin.Write(conn, binary.BigEndian, s.service.clashServer.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readClashModeList(reader io.Reader) (modeList []string, currentMode string, err error) {
|
||||
var modeListLength uint16
|
||||
err = binary.Read(reader, binary.BigEndian, &modeListLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if modeListLength == 0 {
|
||||
return
|
||||
}
|
||||
modeList = make([]string, modeListLength)
|
||||
for i := 0; i < int(modeListLength); i++ {
|
||||
modeList[i], err = varbin.ReadValue[string](reader, binary.BigEndian)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
currentMode, err = varbin.ReadValue[string](reader, binary.BigEndian)
|
||||
return
|
||||
}
|
||||
|
||||
func writeClashModeList(writer io.Writer, clashServer adapter.ClashServer) error {
|
||||
modeList := clashServer.ModeList()
|
||||
err := binary.Write(writer, binary.BigEndian, uint16(len(modeList)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(modeList) > 0 {
|
||||
for _, mode := range modeList {
|
||||
err = varbin.Write(writer, binary.BigEndian, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = varbin.Write(writer, binary.BigEndian, clashServer.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,46 +1,32 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
type CommandClient struct {
|
||||
handler CommandClientHandler
|
||||
grpcConn *grpc.ClientConn
|
||||
grpcClient daemon.StartedServiceClient
|
||||
options CommandClientOptions
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
clientMutex sync.RWMutex
|
||||
handler CommandClientHandler
|
||||
conn net.Conn
|
||||
options CommandClientOptions
|
||||
}
|
||||
|
||||
type CommandClientOptions struct {
|
||||
Command int32
|
||||
Commands Int32Iterator
|
||||
StatusInterval int64
|
||||
}
|
||||
|
||||
type CommandClientHandler interface {
|
||||
Connected()
|
||||
Disconnected(message string)
|
||||
SetDefaultLogLevel(level int32)
|
||||
ClearLogs()
|
||||
WriteLogs(messageList LogIterator)
|
||||
WriteLogs(messageList StringIterator)
|
||||
WriteStatus(message *StatusMessage)
|
||||
WriteGroups(message OutboundGroupIterator)
|
||||
InitializeClashMode(modeList StringIterator, currentMode string)
|
||||
@@ -48,17 +34,6 @@ type CommandClientHandler interface {
|
||||
WriteConnections(message *Connections)
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
Level int32
|
||||
Message string
|
||||
}
|
||||
|
||||
type LogIterator interface {
|
||||
Len() int32
|
||||
HasNext() bool
|
||||
Next() *LogEntry
|
||||
}
|
||||
|
||||
func NewStandaloneCommandClient() *CommandClient {
|
||||
return new(CommandClient)
|
||||
}
|
||||
@@ -70,24 +45,24 @@ func NewCommandClient(handler CommandClientHandler, options *CommandClientOption
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) grpcDial() (*grpc.ClientConn, error) {
|
||||
var target string
|
||||
if C.IsDarwin {
|
||||
port := sCommandServerListenPort
|
||||
if port == 0 {
|
||||
port = 8964
|
||||
}
|
||||
target = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))
|
||||
func (c *CommandClient) directConnect() (net.Conn, error) {
|
||||
if !sTVOS {
|
||||
return net.DialUnix("unix", nil, &net.UnixAddr{
|
||||
Name: filepath.Join(sBasePath, "command.sock"),
|
||||
Net: "unix",
|
||||
})
|
||||
} else {
|
||||
target = "unix://" + filepath.Join(sBasePath, "command.sock")
|
||||
return net.Dial("tcp", "127.0.0.1:8964")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) directConnectWithRetry() (net.Conn, error) {
|
||||
var (
|
||||
conn *grpc.ClientConn
|
||||
conn net.Conn
|
||||
err error
|
||||
)
|
||||
for i := 0; i < 10; i++ {
|
||||
conn, err = grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
conn, err = c.directConnect()
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
@@ -97,365 +72,79 @@ func (c *CommandClient) grpcDial() (*grpc.ClientConn, error) {
|
||||
}
|
||||
|
||||
func (c *CommandClient) Connect() error {
|
||||
c.clientMutex.Lock()
|
||||
common.Close(common.PtrOrNil(c.grpcConn))
|
||||
|
||||
conn, err := c.grpcDial()
|
||||
common.Close(c.conn)
|
||||
conn, err := c.directConnectWithRetry()
|
||||
if err != nil {
|
||||
c.clientMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
c.grpcConn = conn
|
||||
c.grpcClient = daemon.NewStartedServiceClient(conn)
|
||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||
c.clientMutex.Unlock()
|
||||
|
||||
var commands []int32
|
||||
if c.options.Commands != nil {
|
||||
commands = iteratorToArray[int32](c.options.Commands)
|
||||
} else {
|
||||
commands = []int32{c.options.Command}
|
||||
c.conn = conn
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(c.options.Command))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.handler.Connected()
|
||||
for _, command := range commands {
|
||||
switch command {
|
||||
case CommandLog:
|
||||
go c.handleLogStream()
|
||||
case CommandStatus:
|
||||
go c.handleStatusStream()
|
||||
case CommandGroup:
|
||||
go c.handleGroupStream()
|
||||
case CommandClashMode:
|
||||
go c.handleClashModeStream()
|
||||
case CommandConnections:
|
||||
go c.handleConnectionsStream()
|
||||
default:
|
||||
return E.New("unknown command: ", command)
|
||||
switch c.options.Command {
|
||||
case CommandLog:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleLogConn(conn)
|
||||
case CommandStatus:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleStatusConn(conn)
|
||||
case CommandGroup:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleGroupConn(conn)
|
||||
case CommandClashMode:
|
||||
var (
|
||||
modeList []string
|
||||
currentMode string
|
||||
)
|
||||
modeList, currentMode, err = readClashModeList(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sFixAndroidStack {
|
||||
go func() {
|
||||
c.handler.Connected()
|
||||
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
||||
if len(modeList) == 0 {
|
||||
conn.Close()
|
||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
c.handler.Connected()
|
||||
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
||||
if len(modeList) == 0 {
|
||||
conn.Close()
|
||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||
}
|
||||
}
|
||||
if len(modeList) == 0 {
|
||||
return nil
|
||||
}
|
||||
go c.handleModeConn(conn)
|
||||
case CommandConnections:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleConnectionsConn(conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) Disconnect() error {
|
||||
c.clientMutex.Lock()
|
||||
defer c.clientMutex.Unlock()
|
||||
if c.cancel != nil {
|
||||
c.cancel()
|
||||
}
|
||||
return common.Close(common.PtrOrNil(c.grpcConn))
|
||||
}
|
||||
|
||||
func (c *CommandClient) getClientForCall() (daemon.StartedServiceClient, error) {
|
||||
c.clientMutex.RLock()
|
||||
if c.grpcClient != nil {
|
||||
defer c.clientMutex.RUnlock()
|
||||
return c.grpcClient, nil
|
||||
}
|
||||
c.clientMutex.RUnlock()
|
||||
|
||||
c.clientMutex.Lock()
|
||||
defer c.clientMutex.Unlock()
|
||||
|
||||
if c.grpcClient != nil {
|
||||
return c.grpcClient, nil
|
||||
}
|
||||
|
||||
conn, err := c.grpcDial()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.grpcConn = conn
|
||||
c.grpcClient = daemon.NewStartedServiceClient(conn)
|
||||
if c.ctx == nil {
|
||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||
}
|
||||
return c.grpcClient, nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) getStreamContext() (daemon.StartedServiceClient, context.Context) {
|
||||
c.clientMutex.RLock()
|
||||
defer c.clientMutex.RUnlock()
|
||||
return c.grpcClient, c.ctx
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleLogStream() {
|
||||
client, ctx := c.getStreamContext()
|
||||
stream, err := client.SubscribeLog(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
defaultLogLevel, err := client.GetDefaultLogLevel(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.SetDefaultLogLevel(int32(defaultLogLevel.Level))
|
||||
for {
|
||||
logMessage, err := stream.Recv()
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
if logMessage.Reset_ {
|
||||
c.handler.ClearLogs()
|
||||
}
|
||||
var messages []*LogEntry
|
||||
for _, msg := range logMessage.Messages {
|
||||
messages = append(messages, &LogEntry{
|
||||
Level: int32(msg.Level),
|
||||
Message: msg.Message,
|
||||
})
|
||||
}
|
||||
c.handler.WriteLogs(newIterator(messages))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleStatusStream() {
|
||||
client, ctx := c.getStreamContext()
|
||||
interval := c.options.StatusInterval
|
||||
|
||||
stream, err := client.SubscribeStatus(ctx, &daemon.SubscribeStatusRequest{
|
||||
Interval: interval,
|
||||
})
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
status, err := stream.Recv()
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteStatus(StatusMessageFromGRPC(status))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleGroupStream() {
|
||||
client, ctx := c.getStreamContext()
|
||||
|
||||
stream, err := client.SubscribeGroups(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
groups, err := stream.Recv()
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteGroups(OutboundGroupIteratorFromGRPC(groups))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleClashModeStream() {
|
||||
client, ctx := c.getStreamContext()
|
||||
|
||||
modeStatus, err := client.GetClashModeStatus(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if sFixAndroidStack {
|
||||
go func() {
|
||||
c.handler.Connected()
|
||||
c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode)
|
||||
if len(modeStatus.ModeList) == 0 {
|
||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
c.handler.Connected()
|
||||
c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode)
|
||||
if len(modeStatus.ModeList) == 0 {
|
||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(modeStatus.ModeList) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
stream, err := client.SubscribeClashMode(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
mode, err := stream.Recv()
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.UpdateClashMode(mode.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleConnectionsStream() {
|
||||
client, ctx := c.getStreamContext()
|
||||
interval := c.options.StatusInterval
|
||||
|
||||
stream, err := client.SubscribeConnections(ctx, &daemon.SubscribeConnectionsRequest{
|
||||
Interval: interval,
|
||||
})
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var connections Connections
|
||||
for {
|
||||
conns, err := stream.Recv()
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
connections.input = ConnectionsFromGRPC(conns)
|
||||
c.handler.WriteConnections(&connections)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.SelectOutbound(context.Background(), &daemon.SelectOutboundRequest{
|
||||
GroupTag: groupTag,
|
||||
OutboundTag: outboundTag,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) URLTest(groupTag string) error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.URLTest(context.Background(), &daemon.URLTestRequest{
|
||||
OutboundTag: groupTag,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) SetClashMode(newMode string) error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.SetClashMode(context.Background(), &daemon.ClashMode{
|
||||
Mode: newMode,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) CloseConnection(connId string) error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.CloseConnection(context.Background(), &daemon.CloseConnectionRequest{
|
||||
Id: connId,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) CloseConnections() error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.CloseAllConnections(context.Background(), &emptypb.Empty{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) ServiceReload() error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.ReloadService(context.Background(), &emptypb.Empty{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) ServiceClose() error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.StopService(context.Background(), &emptypb.Empty{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, err := client.GetSystemProxyStatus(context.Background(), &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return SystemProxyStatusFromGRPC(status), nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.SetSystemProxyEnabled(context.Background(), &daemon.SetSystemProxyEnabledRequest{
|
||||
Enabled: isEnabled,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
warnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var notes []*DeprecatedNote
|
||||
for _, warning := range warnings.Warnings {
|
||||
notes = append(notes, &DeprecatedNote{
|
||||
Description: warning.Message,
|
||||
MigrationLink: warning.MigrationLink,
|
||||
})
|
||||
}
|
||||
return newIterator(notes), nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.SetGroupExpand(context.Background(), &daemon.SetGroupExpandRequest{
|
||||
GroupTag: groupTag,
|
||||
IsExpand: isExpand,
|
||||
})
|
||||
return err
|
||||
return common.Close(c.conn)
|
||||
}
|
||||
|
||||
54
experimental/libbox/command_close_connection.go
Normal file
54
experimental/libbox/command_close_connection.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
"github.com/sagernet/sing/common/binary"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func (c *CommandClient) CloseConnection(connId string) error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnection))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer := bufio.NewWriter(conn)
|
||||
err = varbin.Write(writer, binary.BigEndian, connId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleCloseConnection(conn net.Conn) error {
|
||||
reader := bufio.NewReader(conn)
|
||||
var connId string
|
||||
err := varbin.Read(reader, binary.BigEndian, &connId)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read connection id")
|
||||
}
|
||||
service := s.service
|
||||
if service == nil {
|
||||
return writeError(conn, E.New("service not ready"))
|
||||
}
|
||||
targetConn := service.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId))
|
||||
if targetConn == nil {
|
||||
return writeError(conn, E.New("connection already closed"))
|
||||
}
|
||||
targetConn.Close()
|
||||
return writeError(conn, nil)
|
||||
}
|
||||
269
experimental/libbox/command_connections.go
Normal file
269
experimental/libbox/command_connections.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/sing/common/binary"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func (c *CommandClient) handleConnectionsConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
reader := bufio.NewReader(conn)
|
||||
var (
|
||||
rawConnections []Connection
|
||||
connections Connections
|
||||
)
|
||||
for {
|
||||
rawConnections = nil
|
||||
err := varbin.Read(reader, binary.BigEndian, &rawConnections)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
connections.input = rawConnections
|
||||
c.handler.WriteConnections(&connections)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleConnectionsConn(conn net.Conn) error {
|
||||
var interval int64
|
||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read interval")
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval))
|
||||
defer ticker.Stop()
|
||||
ctx := connKeepAlive(conn)
|
||||
var trafficManager *trafficontrol.Manager
|
||||
for {
|
||||
service := s.service
|
||||
if service != nil {
|
||||
trafficManager = service.clashServer.(*clashapi.Server).TrafficManager()
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
var (
|
||||
connections = make(map[uuid.UUID]*Connection)
|
||||
outConnections []Connection
|
||||
)
|
||||
writer := bufio.NewWriter(conn)
|
||||
for {
|
||||
outConnections = outConnections[:0]
|
||||
for _, connection := range trafficManager.Connections() {
|
||||
outConnections = append(outConnections, newConnection(connections, connection, false))
|
||||
}
|
||||
for _, connection := range trafficManager.ClosedConnections() {
|
||||
outConnections = append(outConnections, newConnection(connections, connection, true))
|
||||
}
|
||||
err = varbin.Write(writer, binary.BigEndian, outConnections)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
ConnectionStateAll = iota
|
||||
ConnectionStateActive
|
||||
ConnectionStateClosed
|
||||
)
|
||||
|
||||
type Connections struct {
|
||||
input []Connection
|
||||
filtered []Connection
|
||||
}
|
||||
|
||||
func (c *Connections) FilterState(state int32) {
|
||||
c.filtered = c.filtered[:0]
|
||||
switch state {
|
||||
case ConnectionStateAll:
|
||||
c.filtered = append(c.filtered, c.input...)
|
||||
case ConnectionStateActive:
|
||||
for _, connection := range c.input {
|
||||
if connection.ClosedAt == 0 {
|
||||
c.filtered = append(c.filtered, connection)
|
||||
}
|
||||
}
|
||||
case ConnectionStateClosed:
|
||||
for _, connection := range c.input {
|
||||
if connection.ClosedAt != 0 {
|
||||
c.filtered = append(c.filtered, connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connections) SortByDate() {
|
||||
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
|
||||
if x.CreatedAt < y.CreatedAt {
|
||||
return 1
|
||||
} else if x.CreatedAt > y.CreatedAt {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) SortByTraffic() {
|
||||
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
|
||||
xTraffic := x.Uplink + x.Downlink
|
||||
yTraffic := y.Uplink + y.Downlink
|
||||
if xTraffic < yTraffic {
|
||||
return 1
|
||||
} else if xTraffic > yTraffic {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) SortByTrafficTotal() {
|
||||
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
|
||||
xTraffic := x.UplinkTotal + x.DownlinkTotal
|
||||
yTraffic := y.UplinkTotal + y.DownlinkTotal
|
||||
if xTraffic < yTraffic {
|
||||
return 1
|
||||
} else if xTraffic > yTraffic {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) Iterator() ConnectionIterator {
|
||||
return newPtrIterator(c.filtered)
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
ID string
|
||||
Inbound string
|
||||
InboundType string
|
||||
IPVersion int32
|
||||
Network string
|
||||
Source string
|
||||
Destination string
|
||||
Domain string
|
||||
Protocol string
|
||||
User string
|
||||
FromOutbound string
|
||||
CreatedAt int64
|
||||
ClosedAt int64
|
||||
Uplink int64
|
||||
Downlink int64
|
||||
UplinkTotal int64
|
||||
DownlinkTotal int64
|
||||
Rule string
|
||||
Outbound string
|
||||
OutboundType string
|
||||
ChainList []string
|
||||
}
|
||||
|
||||
func (c *Connection) Chain() StringIterator {
|
||||
return newIterator(c.ChainList)
|
||||
}
|
||||
|
||||
func (c *Connection) DisplayDestination() string {
|
||||
destination := M.ParseSocksaddr(c.Destination)
|
||||
if destination.IsIP() && c.Domain != "" {
|
||||
destination = M.Socksaddr{
|
||||
Fqdn: c.Domain,
|
||||
Port: destination.Port,
|
||||
}
|
||||
return destination.String()
|
||||
}
|
||||
return c.Destination
|
||||
}
|
||||
|
||||
type ConnectionIterator interface {
|
||||
Next() *Connection
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) Connection {
|
||||
if oldConnection, loaded := connections[metadata.ID]; loaded {
|
||||
if isClosed {
|
||||
if oldConnection.ClosedAt == 0 {
|
||||
oldConnection.Uplink = 0
|
||||
oldConnection.Downlink = 0
|
||||
oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli()
|
||||
}
|
||||
return *oldConnection
|
||||
}
|
||||
lastUplink := oldConnection.UplinkTotal
|
||||
lastDownlink := oldConnection.DownlinkTotal
|
||||
uplinkTotal := metadata.Upload.Load()
|
||||
downlinkTotal := metadata.Download.Load()
|
||||
oldConnection.Uplink = uplinkTotal - lastUplink
|
||||
oldConnection.Downlink = downlinkTotal - lastDownlink
|
||||
oldConnection.UplinkTotal = uplinkTotal
|
||||
oldConnection.DownlinkTotal = downlinkTotal
|
||||
return *oldConnection
|
||||
}
|
||||
var rule string
|
||||
if metadata.Rule != nil {
|
||||
rule = metadata.Rule.String()
|
||||
}
|
||||
uplinkTotal := metadata.Upload.Load()
|
||||
downlinkTotal := metadata.Download.Load()
|
||||
uplink := uplinkTotal
|
||||
downlink := downlinkTotal
|
||||
var closedAt int64
|
||||
if !metadata.ClosedAt.IsZero() {
|
||||
closedAt = metadata.ClosedAt.UnixMilli()
|
||||
uplink = 0
|
||||
downlink = 0
|
||||
}
|
||||
connection := Connection{
|
||||
ID: metadata.ID.String(),
|
||||
Inbound: metadata.Metadata.Inbound,
|
||||
InboundType: metadata.Metadata.InboundType,
|
||||
IPVersion: int32(metadata.Metadata.IPVersion),
|
||||
Network: metadata.Metadata.Network,
|
||||
Source: metadata.Metadata.Source.String(),
|
||||
Destination: metadata.Metadata.Destination.String(),
|
||||
Domain: metadata.Metadata.Domain,
|
||||
Protocol: metadata.Metadata.Protocol,
|
||||
User: metadata.Metadata.User,
|
||||
FromOutbound: metadata.Metadata.Outbound,
|
||||
CreatedAt: metadata.CreatedAt.UnixMilli(),
|
||||
ClosedAt: closedAt,
|
||||
Uplink: uplink,
|
||||
Downlink: downlink,
|
||||
UplinkTotal: uplinkTotal,
|
||||
DownlinkTotal: downlinkTotal,
|
||||
Rule: rule,
|
||||
Outbound: metadata.Outbound,
|
||||
OutboundType: metadata.OutboundType,
|
||||
ChainList: metadata.Chain,
|
||||
}
|
||||
connections[metadata.ID] = &connection
|
||||
return connection
|
||||
}
|
||||
28
experimental/libbox/command_conntrack.go
Normal file
28
experimental/libbox/command_conntrack.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
runtimeDebug "runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
)
|
||||
|
||||
func (c *CommandClient) CloseConnections() error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnections))
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleCloseConnections(conn net.Conn) error {
|
||||
conntrack.Close()
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
runtimeDebug.FreeOSMemory()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
46
experimental/libbox/command_deprecated_report.go
Normal file
46
experimental/libbox/command_deprecated_report.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandGetDeprecatedNotes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = readError(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var features []deprecated.Note
|
||||
err = varbin.Read(conn, binary.BigEndian, &features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newIterator(common.Map(features, func(it deprecated.Note) *DeprecatedNote { return (*DeprecatedNote)(&it) })), nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleGetDeprecatedNotes(conn net.Conn) error {
|
||||
boxService := s.service
|
||||
if boxService == nil {
|
||||
return writeError(conn, E.New("service not ready"))
|
||||
}
|
||||
err := writeError(conn, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return varbin.Write(conn, binary.BigEndian, service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get())
|
||||
}
|
||||
198
experimental/libbox/command_group.go
Normal file
198
experimental/libbox/command_group.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func (c *CommandClient) handleGroupConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
groups, err := readGroups(conn)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteGroups(groups)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleGroupConn(conn net.Conn) error {
|
||||
var interval int64
|
||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read interval")
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval))
|
||||
defer ticker.Stop()
|
||||
ctx := connKeepAlive(conn)
|
||||
writer := bufio.NewWriter(conn)
|
||||
for {
|
||||
service := s.service
|
||||
if service != nil {
|
||||
err = writeGroups(writer, service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = binary.Write(writer, binary.BigEndian, uint16(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-s.urlTestUpdate:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OutboundGroup struct {
|
||||
Tag string
|
||||
Type string
|
||||
Selectable bool
|
||||
Selected string
|
||||
IsExpand bool
|
||||
ItemList []*OutboundGroupItem
|
||||
}
|
||||
|
||||
func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
|
||||
return newIterator(g.ItemList)
|
||||
}
|
||||
|
||||
type OutboundGroupIterator interface {
|
||||
Next() *OutboundGroup
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
type OutboundGroupItem struct {
|
||||
Tag string
|
||||
Type string
|
||||
URLTestTime int64
|
||||
URLTestDelay int32
|
||||
}
|
||||
|
||||
type OutboundGroupItemIterator interface {
|
||||
Next() *OutboundGroupItem
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
|
||||
groups, err := varbin.ReadValue[[]*OutboundGroup](reader, binary.BigEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newIterator(groups), nil
|
||||
}
|
||||
|
||||
func writeGroups(writer io.Writer, boxService *BoxService) error {
|
||||
historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
|
||||
cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx)
|
||||
outbounds := boxService.instance.Outbound().Outbounds()
|
||||
var iGroups []adapter.OutboundGroup
|
||||
for _, it := range outbounds {
|
||||
if group, isGroup := it.(adapter.OutboundGroup); isGroup {
|
||||
iGroups = append(iGroups, group)
|
||||
}
|
||||
}
|
||||
var groups []OutboundGroup
|
||||
for _, iGroup := range iGroups {
|
||||
var outboundGroup OutboundGroup
|
||||
outboundGroup.Tag = iGroup.Tag()
|
||||
outboundGroup.Type = iGroup.Type()
|
||||
_, outboundGroup.Selectable = iGroup.(*group.Selector)
|
||||
outboundGroup.Selected = iGroup.Now()
|
||||
if cacheFile != nil {
|
||||
if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded {
|
||||
outboundGroup.IsExpand = isExpand
|
||||
}
|
||||
}
|
||||
|
||||
for _, itemTag := range iGroup.All() {
|
||||
itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag)
|
||||
if !isLoaded {
|
||||
continue
|
||||
}
|
||||
|
||||
var item OutboundGroupItem
|
||||
item.Tag = itemTag
|
||||
item.Type = itemOutbound.Type()
|
||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil {
|
||||
item.URLTestTime = history.Time.Unix()
|
||||
item.URLTestDelay = int32(history.Delay)
|
||||
}
|
||||
outboundGroup.ItemList = append(outboundGroup.ItemList, &item)
|
||||
}
|
||||
if len(outboundGroup.ItemList) < 2 {
|
||||
continue
|
||||
}
|
||||
groups = append(groups, outboundGroup)
|
||||
}
|
||||
return varbin.Write(writer, binary.BigEndian, groups)
|
||||
}
|
||||
|
||||
func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = varbin.Write(conn, binary.BigEndian, groupTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(conn, binary.BigEndian, isExpand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
|
||||
groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var isExpand bool
|
||||
err = binary.Read(conn, binary.BigEndian, &isExpand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serviceNow := s.service
|
||||
if serviceNow == nil {
|
||||
return writeError(conn, E.New("service not ready"))
|
||||
}
|
||||
cacheFile := service.FromContext[adapter.CacheFile](serviceNow.ctx)
|
||||
if cacheFile != nil {
|
||||
err = cacheFile.StoreGroupExpand(groupTag, isExpand)
|
||||
if err != nil {
|
||||
return writeError(conn, err)
|
||||
}
|
||||
}
|
||||
return writeError(conn, nil)
|
||||
}
|
||||
160
experimental/libbox/command_log.go
Normal file
160
experimental/libbox/command_log.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/binary"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
func (s *CommandServer) ResetLog() {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
s.savedLines.Init()
|
||||
select {
|
||||
case s.logReset <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) WriteMessage(message string) {
|
||||
s.subscriber.Emit(message)
|
||||
s.access.Lock()
|
||||
s.savedLines.PushBack(message)
|
||||
if s.savedLines.Len() > s.maxLines {
|
||||
s.savedLines.Remove(s.savedLines.Front())
|
||||
}
|
||||
s.access.Unlock()
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleLogConn(conn net.Conn) error {
|
||||
var (
|
||||
interval int64
|
||||
timer *time.Timer
|
||||
)
|
||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read interval")
|
||||
}
|
||||
timer = time.NewTimer(time.Duration(interval))
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
var savedLines []string
|
||||
s.access.Lock()
|
||||
savedLines = make([]string, 0, s.savedLines.Len())
|
||||
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
|
||||
}
|
||||
defer s.observer.UnSubscribe(subscription)
|
||||
writer := bufio.NewWriter(conn)
|
||||
select {
|
||||
case <-s.logReset:
|
||||
err = writer.WriteByte(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
}
|
||||
if len(savedLines) > 0 {
|
||||
err = writer.WriteByte(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = varbin.Write(writer, binary.BigEndian, savedLines)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ctx := connKeepAlive(conn)
|
||||
var logLines []string
|
||||
for {
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-s.logReset:
|
||||
err = writer.WriteByte(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-done:
|
||||
return nil
|
||||
case logLine := <-subscription:
|
||||
logLines = logLines[:0]
|
||||
logLines = append(logLines, logLine)
|
||||
timer.Reset(time.Duration(interval))
|
||||
loopLogs:
|
||||
for {
|
||||
select {
|
||||
case logLine = <-subscription:
|
||||
logLines = append(logLines, logLine)
|
||||
case <-timer.C:
|
||||
break loopLogs
|
||||
}
|
||||
}
|
||||
err = writer.WriteByte(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = varbin.Write(writer, binary.BigEndian, logLines)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleLogConn(conn net.Conn) {
|
||||
reader := bufio.NewReader(conn)
|
||||
for {
|
||||
messageType, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
var messages []string
|
||||
switch messageType {
|
||||
case 0:
|
||||
err = varbin.Read(reader, binary.BigEndian, &messages)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteLogs(newIterator(messages))
|
||||
case 1:
|
||||
c.handler.ClearLogs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func connKeepAlive(reader io.Reader) context.Context {
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
go func() {
|
||||
for {
|
||||
_, err := reader.Read(make([]byte, 1))
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ctx
|
||||
}
|
||||
59
experimental/libbox/command_power.go
Normal file
59
experimental/libbox/command_power.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
func (c *CommandClient) ServiceReload() error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceReload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleServiceReload(conn net.Conn) error {
|
||||
rErr := s.handler.ServiceReload()
|
||||
err := binary.Write(conn, binary.BigEndian, rErr != nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rErr != nil {
|
||||
return varbin.Write(conn, binary.BigEndian, rErr.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) ServiceClose() error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceClose))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleServiceClose(conn net.Conn) error {
|
||||
rErr := s.service.Close()
|
||||
s.handler.PostServiceClose()
|
||||
err := binary.Write(conn, binary.BigEndian, rErr != nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rErr != nil {
|
||||
return varbin.Write(conn, binary.BigEndian, rErr.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
58
experimental/libbox/command_select.go
Normal file
58
experimental/libbox/command_select.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandSelectOutbound))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = varbin.Write(conn, binary.BigEndian, groupTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = varbin.Write(conn, binary.BigEndian, outboundTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
|
||||
groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outboundTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := s.service
|
||||
if service == nil {
|
||||
return writeError(conn, E.New("service not ready"))
|
||||
}
|
||||
outboundGroup, isLoaded := service.instance.Outbound().Outbound(groupTag)
|
||||
if !isLoaded {
|
||||
return writeError(conn, E.New("selector not found: ", groupTag))
|
||||
}
|
||||
selector, isSelector := outboundGroup.(*group.Selector)
|
||||
if !isSelector {
|
||||
return writeError(conn, E.New("outbound is not a selector: ", groupTag))
|
||||
}
|
||||
if !selector.SelectOutbound(outboundTag) {
|
||||
return writeError(conn, E.New("outbound not found in selector: ", outboundTag))
|
||||
}
|
||||
return writeError(conn, nil)
|
||||
}
|
||||
@@ -1,225 +1,182 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/observable"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type CommandServer struct {
|
||||
*daemon.StartedService
|
||||
listener net.Listener
|
||||
handler CommandServerHandler
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
handler CommandServerHandler
|
||||
platformInterface PlatformInterface
|
||||
platformWrapper *platformInterfaceWrapper
|
||||
grpcServer *grpc.Server
|
||||
listener net.Listener
|
||||
endPauseTimer *time.Timer
|
||||
access sync.Mutex
|
||||
savedLines list.List[string]
|
||||
maxLines int
|
||||
subscriber *observable.Subscriber[string]
|
||||
observer *observable.Observer[string]
|
||||
service *BoxService
|
||||
|
||||
// These channels only work with a single client. if multi-client support is needed, replace with Subscriber/Observer
|
||||
urlTestUpdate chan struct{}
|
||||
modeUpdate chan struct{}
|
||||
logReset chan struct{}
|
||||
|
||||
closedConnections []Connection
|
||||
}
|
||||
|
||||
type CommandServerHandler interface {
|
||||
ServiceStop() error
|
||||
ServiceReload() error
|
||||
GetSystemProxyStatus() (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
WriteDebugMessage(message string)
|
||||
PostServiceClose()
|
||||
GetSystemProxyStatus() *SystemProxyStatus
|
||||
SetSystemProxyEnabled(isEnabled bool) error
|
||||
}
|
||||
|
||||
func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) {
|
||||
ctx := BaseContext(platformInterface)
|
||||
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
platformWrapper := &platformInterfaceWrapper{
|
||||
iif: platformInterface,
|
||||
useProcFS: platformInterface.UseProcFS(),
|
||||
}
|
||||
service.MustRegister[adapter.PlatformInterface](ctx, platformWrapper)
|
||||
func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
|
||||
server := &CommandServer{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
handler: handler,
|
||||
platformInterface: platformInterface,
|
||||
platformWrapper: platformWrapper,
|
||||
handler: handler,
|
||||
maxLines: int(maxLines),
|
||||
subscriber: observable.NewSubscriber[string](128),
|
||||
urlTestUpdate: make(chan struct{}, 1),
|
||||
modeUpdate: make(chan struct{}, 1),
|
||||
logReset: make(chan struct{}, 1),
|
||||
}
|
||||
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
||||
return server
|
||||
}
|
||||
|
||||
func (s *CommandServer) SetService(newService *BoxService) {
|
||||
if newService != nil {
|
||||
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
|
||||
newService.clashServer.(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
|
||||
}
|
||||
s.service = newService
|
||||
s.notifyURLTestUpdate()
|
||||
}
|
||||
|
||||
func (s *CommandServer) notifyURLTestUpdate() {
|
||||
select {
|
||||
case s.urlTestUpdate <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
server.StartedService = daemon.NewStartedService(daemon.ServiceOptions{
|
||||
Context: ctx,
|
||||
Platform: platformWrapper,
|
||||
PlatformHandler: (*platformHandler)(server),
|
||||
Debug: sDebug,
|
||||
LogMaxLines: sLogMaxLines,
|
||||
WorkingDirectory: sBasePath,
|
||||
TempDirectory: os.TempDir(),
|
||||
UserID: sUserID,
|
||||
GroupID: sGroupID,
|
||||
SystemProxyEnabled: false,
|
||||
})
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) Start() error {
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if C.IsAndroid && sCommandServerListenPort == 0 {
|
||||
sockPath := filepath.Join(sBasePath, "command.sock")
|
||||
os.Remove(sockPath)
|
||||
listener, err = net.ListenUnix("unix", &net.UnixAddr{
|
||||
Name: sockPath,
|
||||
Net: "unix",
|
||||
})
|
||||
if err != nil {
|
||||
return E.Cause(err, "listen command server")
|
||||
}
|
||||
if sUserID != os.Getuid() {
|
||||
err = os.Chown(sockPath, sUserID, sGroupID)
|
||||
if err != nil {
|
||||
listener.Close()
|
||||
os.Remove(sockPath)
|
||||
return E.Cause(err, "chown")
|
||||
}
|
||||
}
|
||||
if !sTVOS {
|
||||
return s.listenUNIX()
|
||||
} else {
|
||||
port := sCommandServerListenPort
|
||||
if port == 0 {
|
||||
port = 8964
|
||||
}
|
||||
listener, err = net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port))))
|
||||
if err != nil {
|
||||
return E.Cause(err, "listen command server")
|
||||
}
|
||||
return s.listenTCP()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) listenUNIX() error {
|
||||
sockPath := filepath.Join(sBasePath, "command.sock")
|
||||
os.Remove(sockPath)
|
||||
listener, err := net.ListenUnix("unix", &net.UnixAddr{
|
||||
Name: sockPath,
|
||||
Net: "unix",
|
||||
})
|
||||
if err != nil {
|
||||
return E.Cause(err, "listen ", sockPath)
|
||||
}
|
||||
err = os.Chown(sockPath, sUserID, sGroupID)
|
||||
if err != nil {
|
||||
listener.Close()
|
||||
os.Remove(sockPath)
|
||||
return E.Cause(err, "chown")
|
||||
}
|
||||
s.listener = listener
|
||||
s.grpcServer = grpc.NewServer()
|
||||
daemon.RegisterStartedServiceServer(s.grpcServer, s.StartedService)
|
||||
go s.grpcServer.Serve(listener)
|
||||
go s.loopConnection(listener)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) Close() {
|
||||
s.cancel()
|
||||
if s.grpcServer != nil {
|
||||
s.grpcServer.Stop()
|
||||
}
|
||||
common.Close(s.listener)
|
||||
}
|
||||
|
||||
type OverrideOptions struct {
|
||||
AutoRedirect bool
|
||||
IncludePackage StringIterator
|
||||
ExcludePackage StringIterator
|
||||
}
|
||||
|
||||
func (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error {
|
||||
return s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{
|
||||
AutoRedirect: options.AutoRedirect,
|
||||
IncludePackage: iteratorToArray(options.IncludePackage),
|
||||
ExcludePackage: iteratorToArray(options.ExcludePackage),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *CommandServer) CloseService() error {
|
||||
return s.StartedService.CloseService()
|
||||
}
|
||||
|
||||
func (s *CommandServer) WriteMessage(level int32, message string) {
|
||||
s.StartedService.WriteMessage(log.Level(level), message)
|
||||
}
|
||||
|
||||
func (s *CommandServer) SetError(message string) {
|
||||
s.StartedService.SetError(E.New(message))
|
||||
}
|
||||
|
||||
func (s *CommandServer) NeedWIFIState() bool {
|
||||
instance := s.StartedService.Instance()
|
||||
if instance == nil || instance.Box() == nil {
|
||||
return false
|
||||
}
|
||||
return instance.Box().Network().NeedWIFIState()
|
||||
}
|
||||
|
||||
func (s *CommandServer) Pause() {
|
||||
instance := s.StartedService.Instance()
|
||||
if instance == nil || instance.PauseManager() == nil {
|
||||
return
|
||||
}
|
||||
instance.PauseManager().DevicePause()
|
||||
if C.IsIos {
|
||||
if s.endPauseTimer == nil {
|
||||
s.endPauseTimer = time.AfterFunc(time.Minute, instance.PauseManager().DeviceWake)
|
||||
} else {
|
||||
s.endPauseTimer.Reset(time.Minute)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) Wake() {
|
||||
instance := s.StartedService.Instance()
|
||||
if instance == nil || instance.PauseManager() == nil {
|
||||
return
|
||||
}
|
||||
if !C.IsIos {
|
||||
instance.PauseManager().DeviceWake()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) ResetNetwork() {
|
||||
instance := s.StartedService.Instance()
|
||||
if instance == nil || instance.Box() == nil {
|
||||
return
|
||||
}
|
||||
instance.Box().Router().ResetNetwork()
|
||||
}
|
||||
|
||||
func (s *CommandServer) UpdateWIFIState() {
|
||||
instance := s.StartedService.Instance()
|
||||
if instance == nil || instance.Box() == nil {
|
||||
return
|
||||
}
|
||||
instance.Box().Network().UpdateWIFIState()
|
||||
}
|
||||
|
||||
type platformHandler CommandServer
|
||||
|
||||
func (h *platformHandler) ServiceStop() error {
|
||||
return (*CommandServer)(h).handler.ServiceStop()
|
||||
}
|
||||
|
||||
func (h *platformHandler) ServiceReload() error {
|
||||
return (*CommandServer)(h).handler.ServiceReload()
|
||||
}
|
||||
|
||||
func (h *platformHandler) SystemProxyStatus() (*daemon.SystemProxyStatus, error) {
|
||||
status, err := (*CommandServer)(h).handler.GetSystemProxyStatus()
|
||||
func (s *CommandServer) listenTCP() error {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:8964")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return E.Cause(err, "listen")
|
||||
}
|
||||
return &daemon.SystemProxyStatus{
|
||||
Enabled: status.Enabled,
|
||||
Available: status.Available,
|
||||
}, nil
|
||||
s.listener = listener
|
||||
go s.loopConnection(listener)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *platformHandler) SetSystemProxyEnabled(enabled bool) error {
|
||||
return (*CommandServer)(h).handler.SetSystemProxyEnabled(enabled)
|
||||
func (s *CommandServer) Close() error {
|
||||
return common.Close(
|
||||
s.listener,
|
||||
s.observer,
|
||||
)
|
||||
}
|
||||
|
||||
func (h *platformHandler) WriteDebugMessage(message string) {
|
||||
(*CommandServer)(h).handler.WriteDebugMessage(message)
|
||||
func (s *CommandServer) loopConnection(listener net.Listener) {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
hErr := s.handleConnection(conn)
|
||||
if hErr != nil && !E.IsClosed(err) {
|
||||
if debug.Enabled {
|
||||
log.Warn("log-server: process connection: ", hErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleConnection(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
var command uint8
|
||||
err := binary.Read(conn, binary.BigEndian, &command)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read command")
|
||||
}
|
||||
switch int32(command) {
|
||||
case CommandLog:
|
||||
return s.handleLogConn(conn)
|
||||
case CommandStatus:
|
||||
return s.handleStatusConn(conn)
|
||||
case CommandServiceReload:
|
||||
return s.handleServiceReload(conn)
|
||||
case CommandServiceClose:
|
||||
return s.handleServiceClose(conn)
|
||||
case CommandCloseConnections:
|
||||
return s.handleCloseConnections(conn)
|
||||
case CommandGroup:
|
||||
return s.handleGroupConn(conn)
|
||||
case CommandSelectOutbound:
|
||||
return s.handleSelectOutbound(conn)
|
||||
case CommandURLTest:
|
||||
return s.handleURLTest(conn)
|
||||
case CommandGroupExpand:
|
||||
return s.handleSetGroupExpand(conn)
|
||||
case CommandClashMode:
|
||||
return s.handleModeConn(conn)
|
||||
case CommandSetClashMode:
|
||||
return s.handleSetClashMode(conn)
|
||||
case CommandGetSystemProxyStatus:
|
||||
return s.handleGetSystemProxyStatus(conn)
|
||||
case CommandSetSystemProxyEnabled:
|
||||
return s.handleSetSystemProxyEnabled(conn)
|
||||
case CommandConnections:
|
||||
return s.handleConnectionsConn(conn)
|
||||
case CommandCloseConnection:
|
||||
return s.handleCloseConnection(conn)
|
||||
case CommandGetDeprecatedNotes:
|
||||
return s.handleGetDeprecatedNotes(conn)
|
||||
default:
|
||||
return E.New("unknown command: ", command)
|
||||
}
|
||||
}
|
||||
|
||||
39
experimental/libbox/command_shared.go
Normal file
39
experimental/libbox/command_shared.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
func readError(reader io.Reader) error {
|
||||
var hasError bool
|
||||
err := binary.Read(reader, binary.BigEndian, &hasError)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasError {
|
||||
errorMessage, err := varbin.ReadValue[string](reader, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return E.New(errorMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeError(writer io.Writer, wErr error) error {
|
||||
err := binary.Write(writer, binary.BigEndian, wErr != nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wErr != nil {
|
||||
err = varbin.Write(writer, binary.BigEndian, wErr.Error())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
85
experimental/libbox/command_status.go
Normal file
85
experimental/libbox/command_status.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
type StatusMessage struct {
|
||||
Memory int64
|
||||
Goroutines int32
|
||||
ConnectionsIn int32
|
||||
ConnectionsOut int32
|
||||
TrafficAvailable bool
|
||||
Uplink int64
|
||||
Downlink int64
|
||||
UplinkTotal int64
|
||||
DownlinkTotal int64
|
||||
}
|
||||
|
||||
func (s *CommandServer) readStatus() StatusMessage {
|
||||
var message StatusMessage
|
||||
message.Memory = int64(memory.Inuse())
|
||||
message.Goroutines = int32(runtime.NumGoroutine())
|
||||
message.ConnectionsOut = int32(conntrack.Count())
|
||||
|
||||
if s.service != nil {
|
||||
message.TrafficAvailable = true
|
||||
trafficManager := s.service.clashServer.(*clashapi.Server).TrafficManager()
|
||||
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
|
||||
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
||||
var interval int64
|
||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read interval")
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval))
|
||||
defer ticker.Stop()
|
||||
ctx := connKeepAlive(conn)
|
||||
status := s.readStatus()
|
||||
uploadTotal := status.UplinkTotal
|
||||
downloadTotal := status.DownlinkTotal
|
||||
for {
|
||||
err = binary.Write(conn, binary.BigEndian, status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
status = s.readStatus()
|
||||
upload := status.UplinkTotal - uploadTotal
|
||||
download := status.DownlinkTotal - downloadTotal
|
||||
uploadTotal = status.UplinkTotal
|
||||
downloadTotal = status.DownlinkTotal
|
||||
status.Uplink = upload
|
||||
status.Downlink = download
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleStatusConn(conn net.Conn) {
|
||||
for {
|
||||
var message StatusMessage
|
||||
err := binary.Read(conn, binary.BigEndian, &message)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteStatus(&message)
|
||||
}
|
||||
}
|
||||
80
experimental/libbox/command_system_proxy.go
Normal file
80
experimental/libbox/command_system_proxy.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
type SystemProxyStatus struct {
|
||||
Available bool
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {
|
||||
conn, err := c.directConnectWithRetry()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandGetSystemProxyStatus))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var status SystemProxyStatus
|
||||
err = binary.Read(conn, binary.BigEndian, &status.Available)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status.Available {
|
||||
err = binary.Read(conn, binary.BigEndian, &status.Enabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &status, nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleGetSystemProxyStatus(conn net.Conn) error {
|
||||
status := s.handler.GetSystemProxyStatus()
|
||||
err := binary.Write(conn, binary.BigEndian, status.Available)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status.Available {
|
||||
err = binary.Write(conn, binary.BigEndian, status.Enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandSetSystemProxyEnabled))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(conn, binary.BigEndian, isEnabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSetSystemProxyEnabled(conn net.Conn) error {
|
||||
var isEnabled bool
|
||||
err := binary.Read(conn, binary.BigEndian, &isEnabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.handler.SetSystemProxyEnabled(isEnabled)
|
||||
if err != nil {
|
||||
return writeError(conn, err)
|
||||
}
|
||||
return writeError(conn, nil)
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type StatusMessage struct {
|
||||
Memory int64
|
||||
Goroutines int32
|
||||
ConnectionsIn int32
|
||||
ConnectionsOut int32
|
||||
TrafficAvailable bool
|
||||
Uplink int64
|
||||
Downlink int64
|
||||
UplinkTotal int64
|
||||
DownlinkTotal int64
|
||||
}
|
||||
|
||||
type SystemProxyStatus struct {
|
||||
Available bool
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type OutboundGroup struct {
|
||||
Tag string
|
||||
Type string
|
||||
Selectable bool
|
||||
Selected string
|
||||
IsExpand bool
|
||||
ItemList []*OutboundGroupItem
|
||||
}
|
||||
|
||||
func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
|
||||
return newIterator(g.ItemList)
|
||||
}
|
||||
|
||||
type OutboundGroupIterator interface {
|
||||
Next() *OutboundGroup
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
type OutboundGroupItem struct {
|
||||
Tag string
|
||||
Type string
|
||||
URLTestTime int64
|
||||
URLTestDelay int32
|
||||
}
|
||||
|
||||
type OutboundGroupItemIterator interface {
|
||||
Next() *OutboundGroupItem
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
const (
|
||||
ConnectionStateAll = iota
|
||||
ConnectionStateActive
|
||||
ConnectionStateClosed
|
||||
)
|
||||
|
||||
type Connections struct {
|
||||
input []Connection
|
||||
filtered []Connection
|
||||
}
|
||||
|
||||
func (c *Connections) FilterState(state int32) {
|
||||
c.filtered = c.filtered[:0]
|
||||
switch state {
|
||||
case ConnectionStateAll:
|
||||
c.filtered = append(c.filtered, c.input...)
|
||||
case ConnectionStateActive:
|
||||
for _, connection := range c.input {
|
||||
if connection.ClosedAt == 0 {
|
||||
c.filtered = append(c.filtered, connection)
|
||||
}
|
||||
}
|
||||
case ConnectionStateClosed:
|
||||
for _, connection := range c.input {
|
||||
if connection.ClosedAt != 0 {
|
||||
c.filtered = append(c.filtered, connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connections) SortByDate() {
|
||||
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
|
||||
if x.CreatedAt < y.CreatedAt {
|
||||
return 1
|
||||
} else if x.CreatedAt > y.CreatedAt {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) SortByTraffic() {
|
||||
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
|
||||
xTraffic := x.Uplink + x.Downlink
|
||||
yTraffic := y.Uplink + y.Downlink
|
||||
if xTraffic < yTraffic {
|
||||
return 1
|
||||
} else if xTraffic > yTraffic {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) SortByTrafficTotal() {
|
||||
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
|
||||
xTraffic := x.UplinkTotal + x.DownlinkTotal
|
||||
yTraffic := y.UplinkTotal + y.DownlinkTotal
|
||||
if xTraffic < yTraffic {
|
||||
return 1
|
||||
} else if xTraffic > yTraffic {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) Iterator() ConnectionIterator {
|
||||
return newPtrIterator(c.filtered)
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
ID string
|
||||
Inbound string
|
||||
InboundType string
|
||||
IPVersion int32
|
||||
Network string
|
||||
Source string
|
||||
Destination string
|
||||
Domain string
|
||||
Protocol string
|
||||
User string
|
||||
FromOutbound string
|
||||
CreatedAt int64
|
||||
ClosedAt int64
|
||||
Uplink int64
|
||||
Downlink int64
|
||||
UplinkTotal int64
|
||||
DownlinkTotal int64
|
||||
Rule string
|
||||
Outbound string
|
||||
OutboundType string
|
||||
ChainList []string
|
||||
}
|
||||
|
||||
func (c *Connection) Chain() StringIterator {
|
||||
return newIterator(c.ChainList)
|
||||
}
|
||||
|
||||
func (c *Connection) DisplayDestination() string {
|
||||
destination := M.ParseSocksaddr(c.Destination)
|
||||
if destination.IsIP() && c.Domain != "" {
|
||||
destination = M.Socksaddr{
|
||||
Fqdn: c.Domain,
|
||||
Port: destination.Port,
|
||||
}
|
||||
return destination.String()
|
||||
}
|
||||
return c.Destination
|
||||
}
|
||||
|
||||
type ConnectionIterator interface {
|
||||
Next() *Connection
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage {
|
||||
if status == nil {
|
||||
return nil
|
||||
}
|
||||
return &StatusMessage{
|
||||
Memory: int64(status.Memory),
|
||||
Goroutines: status.Goroutines,
|
||||
ConnectionsIn: status.ConnectionsIn,
|
||||
ConnectionsOut: status.ConnectionsOut,
|
||||
TrafficAvailable: status.TrafficAvailable,
|
||||
Uplink: status.Uplink,
|
||||
Downlink: status.Downlink,
|
||||
UplinkTotal: status.UplinkTotal,
|
||||
DownlinkTotal: status.DownlinkTotal,
|
||||
}
|
||||
}
|
||||
|
||||
func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator {
|
||||
if groups == nil || len(groups.Group) == 0 {
|
||||
return newIterator([]*OutboundGroup{})
|
||||
}
|
||||
var libboxGroups []*OutboundGroup
|
||||
for _, g := range groups.Group {
|
||||
libboxGroup := &OutboundGroup{
|
||||
Tag: g.Tag,
|
||||
Type: g.Type,
|
||||
Selectable: g.Selectable,
|
||||
Selected: g.Selected,
|
||||
IsExpand: g.IsExpand,
|
||||
}
|
||||
for _, item := range g.Items {
|
||||
libboxGroup.ItemList = append(libboxGroup.ItemList, &OutboundGroupItem{
|
||||
Tag: item.Tag,
|
||||
Type: item.Type,
|
||||
URLTestTime: item.UrlTestTime,
|
||||
URLTestDelay: item.UrlTestDelay,
|
||||
})
|
||||
}
|
||||
libboxGroups = append(libboxGroups, libboxGroup)
|
||||
}
|
||||
return newIterator(libboxGroups)
|
||||
}
|
||||
|
||||
func ConnectionFromGRPC(conn *daemon.Connection) Connection {
|
||||
return Connection{
|
||||
ID: conn.Id,
|
||||
Inbound: conn.Inbound,
|
||||
InboundType: conn.InboundType,
|
||||
IPVersion: conn.IpVersion,
|
||||
Network: conn.Network,
|
||||
Source: conn.Source,
|
||||
Destination: conn.Destination,
|
||||
Domain: conn.Domain,
|
||||
Protocol: conn.Protocol,
|
||||
User: conn.User,
|
||||
FromOutbound: conn.FromOutbound,
|
||||
CreatedAt: conn.CreatedAt,
|
||||
ClosedAt: conn.ClosedAt,
|
||||
Uplink: conn.Uplink,
|
||||
Downlink: conn.Downlink,
|
||||
UplinkTotal: conn.UplinkTotal,
|
||||
DownlinkTotal: conn.DownlinkTotal,
|
||||
Rule: conn.Rule,
|
||||
Outbound: conn.Outbound,
|
||||
OutboundType: conn.OutboundType,
|
||||
ChainList: conn.ChainList,
|
||||
}
|
||||
}
|
||||
|
||||
func ConnectionsFromGRPC(connections *daemon.Connections) []Connection {
|
||||
if connections == nil || len(connections.Connections) == 0 {
|
||||
return nil
|
||||
}
|
||||
var libboxConnections []Connection
|
||||
for _, conn := range connections.Connections {
|
||||
libboxConnections = append(libboxConnections, ConnectionFromGRPC(conn))
|
||||
}
|
||||
return libboxConnections
|
||||
}
|
||||
|
||||
func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus {
|
||||
if status == nil {
|
||||
return nil
|
||||
}
|
||||
return &SystemProxyStatus{
|
||||
Available: status.Available,
|
||||
Enabled: status.Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
func SystemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus {
|
||||
if status == nil {
|
||||
return nil
|
||||
}
|
||||
return &daemon.SystemProxyStatus{
|
||||
Available: status.Available,
|
||||
Enabled: status.Enabled,
|
||||
}
|
||||
}
|
||||
86
experimental/libbox/command_urltest.go
Normal file
86
experimental/libbox/command_urltest.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func (c *CommandClient) URLTest(groupTag string) error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandURLTest))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = varbin.Write(conn, binary.BigEndian, groupTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||
groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serviceNow := s.service
|
||||
if serviceNow == nil {
|
||||
return nil
|
||||
}
|
||||
abstractOutboundGroup, isLoaded := serviceNow.instance.Outbound().Outbound(groupTag)
|
||||
if !isLoaded {
|
||||
return writeError(conn, E.New("outbound group not found: ", groupTag))
|
||||
}
|
||||
outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup)
|
||||
if !isOutboundGroup {
|
||||
return writeError(conn, E.New("outbound is not a group: ", groupTag))
|
||||
}
|
||||
urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest)
|
||||
if isURLTest {
|
||||
go urlTest.CheckOutbounds()
|
||||
} else {
|
||||
historyStorage := service.PtrFromContext[urltest.HistoryStorage](serviceNow.ctx)
|
||||
outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||
itOutbound, _ := serviceNow.instance.Outbound().Outbound(it)
|
||||
return itOutbound
|
||||
}), func(it adapter.Outbound) bool {
|
||||
if it == nil {
|
||||
return false
|
||||
}
|
||||
_, isGroup := it.(adapter.OutboundGroup)
|
||||
return !isGroup
|
||||
})
|
||||
b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10))
|
||||
for _, detour := range outbounds {
|
||||
outboundToTest := detour
|
||||
outboundTag := outboundToTest.Tag()
|
||||
b.Go(outboundTag, func() (any, error) {
|
||||
t, err := urltest.URLTest(serviceNow.ctx, "", outboundToTest)
|
||||
if err != nil {
|
||||
historyStorage.DeleteURLTestHistory(outboundTag)
|
||||
} else {
|
||||
historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{
|
||||
Time: time.Now(),
|
||||
Delay: t,
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return writeError(conn, nil)
|
||||
}
|
||||
@@ -3,16 +3,19 @@ package libbox
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
box "github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
@@ -52,7 +55,7 @@ func CheckConfig(configContent string) error {
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
ctx = service.ContextWith[adapter.PlatformInterface](ctx, (*platformInterfaceStub)(nil))
|
||||
ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil))
|
||||
instance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
@@ -77,11 +80,7 @@ func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformInterface() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||
func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -93,11 +92,7 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg
|
||||
return (*interfaceMonitorStub)(nil)
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformNetworkInterfaces() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) NetworkInterfaces() ([]adapter.NetworkInterface, error) {
|
||||
func (s *platformInterfaceStub) Interfaces() ([]adapter.NetworkInterface, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -105,21 +100,13 @@ func (s *platformInterfaceStub) UnderNetworkExtension() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) NetworkExtensionIncludeAllNetworks() bool {
|
||||
func (s *platformInterfaceStub) IncludeAllNetworks() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) ClearDNSCache() {
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) RequestPermissionForWIFIState() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformWIFIMonitor() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState {
|
||||
return adapter.WIFIState{}
|
||||
}
|
||||
@@ -128,27 +115,11 @@ func (s *platformInterfaceStub) SystemCertificates() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformConnectionOwnerFinder() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) {
|
||||
func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformNotification() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) SendNotification(notification *adapter.Notification) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions] {
|
||||
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -77,27 +77,22 @@ func NewHTTPClient() HTTPClient {
|
||||
}
|
||||
|
||||
func (c *httpClient) ModernTLS() {
|
||||
c.setTLSVersion(tls.VersionTLS12, 0, func(suite *tls.CipherSuite) bool { return true })
|
||||
c.tls.MinVersion = tls.VersionTLS12
|
||||
c.tls.CipherSuites = common.Map(tls.CipherSuites(), func(it *tls.CipherSuite) uint16 { return it.ID })
|
||||
}
|
||||
|
||||
func (c *httpClient) RestrictedTLS() {
|
||||
c.setTLSVersion(tls.VersionTLS13, 0, func(suite *tls.CipherSuite) bool {
|
||||
return common.Contains(suite.SupportedVersions, uint16(tls.VersionTLS13))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *httpClient) setTLSVersion(minVersion, maxVersion uint16, filter func(*tls.CipherSuite) bool) {
|
||||
c.tls.MinVersion = minVersion
|
||||
if maxVersion != 0 {
|
||||
c.tls.MaxVersion = maxVersion
|
||||
}
|
||||
c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), filter), func(it *tls.CipherSuite) uint16 {
|
||||
c.tls.MinVersion = tls.VersionTLS13
|
||||
c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), func(it *tls.CipherSuite) bool {
|
||||
return common.Contains(it.SupportedVersions, uint16(tls.VersionTLS13))
|
||||
}), func(it *tls.CipherSuite) uint16 {
|
||||
return it.ID
|
||||
})
|
||||
}
|
||||
|
||||
func (c *httpClient) PinnedTLS12() {
|
||||
c.setTLSVersion(tls.VersionTLS12, tls.VersionTLS12, func(suite *tls.CipherSuite) bool { return true })
|
||||
c.tls.MinVersion = tls.VersionTLS12
|
||||
c.tls.MaxVersion = tls.VersionTLS12
|
||||
}
|
||||
|
||||
func (c *httpClient) PinnedSHA256(sumHex string) {
|
||||
@@ -183,7 +178,9 @@ func (r *httpRequest) SetUserAgent(userAgent string) {
|
||||
}
|
||||
|
||||
func (r *httpRequest) SetContent(content []byte) {
|
||||
r.request.Body = io.NopCloser(bytes.NewReader(content))
|
||||
buffer := bytes.Buffer{}
|
||||
buffer.Write(content)
|
||||
r.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes()))
|
||||
r.request.ContentLength = int64(len(content))
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,6 @@ type StringIterator interface {
|
||||
Next() string
|
||||
}
|
||||
|
||||
type Int32Iterator interface {
|
||||
Len() int32
|
||||
HasNext() bool
|
||||
Next() int32
|
||||
}
|
||||
|
||||
var _ StringIterator = (*iterator[string])(nil)
|
||||
|
||||
type iterator[T any] struct {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
||||
@@ -10,6 +10,7 @@ type PlatformInterface interface {
|
||||
UsePlatformAutoDetectInterfaceControl() bool
|
||||
AutoDetectInterfaceControl(fd int32) error
|
||||
OpenTun(options TunOptions) (int32, error)
|
||||
WriteLog(message string)
|
||||
UseProcFS() bool
|
||||
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
|
||||
PackageNameByUid(uid int32) (string, error)
|
||||
@@ -25,6 +26,11 @@ type PlatformInterface interface {
|
||||
SendNotification(notification *Notification) error
|
||||
}
|
||||
|
||||
type TunInterface interface {
|
||||
FileDescriptor() int32
|
||||
Close() error
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool)
|
||||
}
|
||||
|
||||
35
experimental/libbox/platform/interface.go
Normal file
35
experimental/libbox/platform/interface.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Initialize(networkManager adapter.NetworkManager) error
|
||||
UsePlatformAutoDetectInterfaceControl() bool
|
||||
AutoDetectInterfaceControl(fd int) error
|
||||
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||
Interfaces() ([]adapter.NetworkInterface, error)
|
||||
UnderNetworkExtension() bool
|
||||
IncludeAllNetworks() bool
|
||||
ClearDNSCache()
|
||||
ReadWIFIState() adapter.WIFIState
|
||||
SystemCertificates() []string
|
||||
process.Searcher
|
||||
SendNotification(notification *Notification) error
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
Identifier string
|
||||
TypeName string
|
||||
TypeID int32
|
||||
Title string
|
||||
Subtitle string
|
||||
Body string
|
||||
OpenURL string
|
||||
}
|
||||
@@ -3,25 +3,121 @@ package libbox
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
runtimeDebug "runtime/debug"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/pause"
|
||||
)
|
||||
|
||||
var _ daemon.PlatformInterface = (*platformInterfaceWrapper)(nil)
|
||||
type BoxService struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
urlTestHistoryStorage adapter.URLTestHistoryStorage
|
||||
instance *box.Box
|
||||
clashServer adapter.ClashServer
|
||||
pauseManager pause.Manager
|
||||
|
||||
iOSPauseFields
|
||||
}
|
||||
|
||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||
ctx := BaseContext(platformInterface)
|
||||
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
||||
options, err := parseConfig(ctx, configContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtimeDebug.FreeOSMemory()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||
platformWrapper := &platformInterfaceWrapper{
|
||||
iif: platformInterface,
|
||||
useProcFS: platformInterface.UseProcFS(),
|
||||
}
|
||||
service.MustRegister[platform.Interface](ctx, platformWrapper)
|
||||
instance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
PlatformLogWriter: platformWrapper,
|
||||
})
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, E.Cause(err, "create service")
|
||||
}
|
||||
runtimeDebug.FreeOSMemory()
|
||||
return &BoxService{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
instance: instance,
|
||||
urlTestHistoryStorage: urlTestHistoryStorage,
|
||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||
clashServer: service.FromContext[adapter.ClashServer](ctx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *BoxService) Start() error {
|
||||
if sFixAndroidStack {
|
||||
var err error
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
err = s.instance.Start()
|
||||
close(done)
|
||||
}()
|
||||
<-done
|
||||
return err
|
||||
} else {
|
||||
return s.instance.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BoxService) Close() error {
|
||||
s.cancel()
|
||||
s.urlTestHistoryStorage.Close()
|
||||
var err error
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
err = s.instance.Close()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
return err
|
||||
case <-time.After(C.FatalStopTimeout):
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BoxService) NeedWIFIState() bool {
|
||||
return s.instance.Router().NeedWIFIState()
|
||||
}
|
||||
|
||||
var (
|
||||
_ platform.Interface = (*platformInterfaceWrapper)(nil)
|
||||
_ log.PlatformWriter = (*platformInterfaceWrapper)(nil)
|
||||
)
|
||||
|
||||
type platformInterfaceWrapper struct {
|
||||
iif PlatformInterface
|
||||
@@ -47,11 +143,7 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
|
||||
return w.iif.AutoDetectInterfaceControl(int32(fd))
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformInterface() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
|
||||
return nil, E.New("platform: unsupported uid options")
|
||||
}
|
||||
@@ -80,10 +172,6 @@ func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformO
|
||||
return tun.New(*options)
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
|
||||
return &platformDefaultInterfaceMonitor{
|
||||
platformInterfaceWrapper: w,
|
||||
@@ -91,11 +179,7 @@ func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.L
|
||||
}
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformNetworkInterfaces() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) NetworkInterfaces() ([]adapter.NetworkInterface, error) {
|
||||
func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, error) {
|
||||
interfaceIterator, err := w.iif.GetInterfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -132,7 +216,7 @@ func (w *platformInterfaceWrapper) UnderNetworkExtension() bool {
|
||||
return w.iif.UnderNetworkExtension()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) NetworkExtensionIncludeAllNetworks() bool {
|
||||
func (w *platformInterfaceWrapper) IncludeAllNetworks() bool {
|
||||
return w.iif.IncludeAllNetworks()
|
||||
}
|
||||
|
||||
@@ -140,14 +224,6 @@ func (w *platformInterfaceWrapper) ClearDNSCache() {
|
||||
w.iif.ClearDNSCache()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) RequestPermissionForWIFIState() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformWIFIMonitor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
|
||||
wifiState := w.iif.ReadWIFIState()
|
||||
if wifiState == nil {
|
||||
@@ -160,70 +236,41 @@ func (w *platformInterfaceWrapper) SystemCertificates() []string {
|
||||
return iteratorToArray[string](w.iif.SystemCertificates())
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformConnectionOwnerFinder() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) {
|
||||
func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
||||
var uid int32
|
||||
if w.useProcFS {
|
||||
var source netip.AddrPort
|
||||
var destination netip.AddrPort
|
||||
sourceAddr, _ := netip.ParseAddr(request.SourceAddress)
|
||||
source = netip.AddrPortFrom(sourceAddr, uint16(request.SourcePort))
|
||||
destAddr, _ := netip.ParseAddr(request.DestinationAddress)
|
||||
destination = netip.AddrPortFrom(destAddr, uint16(request.DestinationPort))
|
||||
|
||||
var network string
|
||||
switch request.IpProtocol {
|
||||
case int32(syscall.IPPROTO_TCP):
|
||||
network = "tcp"
|
||||
case int32(syscall.IPPROTO_UDP):
|
||||
network = "udp"
|
||||
default:
|
||||
return nil, E.New("unknown protocol: ", request.IpProtocol)
|
||||
}
|
||||
|
||||
uid = procfs.ResolveSocketByProcSearch(network, source, destination)
|
||||
if uid == -1 {
|
||||
return nil, E.New("procfs: not found")
|
||||
}
|
||||
} else {
|
||||
var ipProtocol int32
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
ipProtocol = syscall.IPPROTO_TCP
|
||||
case N.NetworkUDP:
|
||||
ipProtocol = syscall.IPPROTO_UDP
|
||||
default:
|
||||
return nil, E.New("unknown network: ", network)
|
||||
}
|
||||
var err error
|
||||
uid, err = w.iif.FindConnectionOwner(request.IpProtocol, request.SourceAddress, request.SourcePort, request.DestinationAddress, request.DestinationPort)
|
||||
uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
packageName, _ := w.iif.PackageNameByUid(uid)
|
||||
return &adapter.ConnectionOwner{
|
||||
UserId: uid,
|
||||
AndroidPackageName: packageName,
|
||||
}, nil
|
||||
return &process.Info{UserId: uid, PackageName: packageName}, nil
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) DisableColors() bool {
|
||||
return runtime.GOOS != "android"
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformNotification() bool {
|
||||
return true
|
||||
func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) {
|
||||
w.iif.WriteLog(message)
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notification) error {
|
||||
func (w *platformInterfaceWrapper) SendNotification(notification *platform.Notification) error {
|
||||
return w.iif.SendNotification((*Notification)(notification))
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformLocalDNSTransport() bool {
|
||||
return C.IsAndroid
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions] {
|
||||
localTransport := w.iif.LocalDNSTransport()
|
||||
if localTransport == nil {
|
||||
return nil
|
||||
}
|
||||
return func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||
return newPlatformTransport(localTransport, tag, options), nil
|
||||
}
|
||||
}
|
||||
|
||||
36
experimental/libbox/service_pause.go
Normal file
36
experimental/libbox/service_pause.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
type iOSPauseFields struct {
|
||||
endPauseTimer *time.Timer
|
||||
}
|
||||
|
||||
func (s *BoxService) Pause() {
|
||||
s.pauseManager.DevicePause()
|
||||
if C.IsIos {
|
||||
if s.endPauseTimer == nil {
|
||||
s.endPauseTimer = time.AfterFunc(time.Minute, s.pauseManager.DeviceWake)
|
||||
} else {
|
||||
s.endPauseTimer.Reset(time.Minute)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BoxService) Wake() {
|
||||
if !C.IsIos {
|
||||
s.pauseManager.DeviceWake()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BoxService) ResetNetwork() {
|
||||
s.instance.Router().ResetNetwork()
|
||||
}
|
||||
|
||||
func (s *BoxService) UpdateWIFIState() {
|
||||
s.instance.Network().UpdateWIFIState()
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package libbox
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -12,53 +14,55 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
sBasePath string
|
||||
sWorkingPath string
|
||||
sTempPath string
|
||||
sUserID int
|
||||
sGroupID int
|
||||
sFixAndroidStack bool
|
||||
sCommandServerListenPort uint16
|
||||
sCommandServerSecret string
|
||||
sLogMaxLines int
|
||||
sDebug bool
|
||||
sBasePath string
|
||||
sWorkingPath string
|
||||
sTempPath string
|
||||
sUserID int
|
||||
sGroupID int
|
||||
sTVOS bool
|
||||
sFixAndroidStack bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
debug.SetPanicOnFault(true)
|
||||
debug.SetTraceback("all")
|
||||
}
|
||||
|
||||
type SetupOptions struct {
|
||||
BasePath string
|
||||
WorkingPath string
|
||||
TempPath string
|
||||
FixAndroidStack bool
|
||||
CommandServerListenPort int32
|
||||
CommandServerSecret string
|
||||
LogMaxLines int
|
||||
Debug bool
|
||||
BasePath string
|
||||
WorkingPath string
|
||||
TempPath string
|
||||
Username string
|
||||
IsTVOS bool
|
||||
FixAndroidStack bool
|
||||
}
|
||||
|
||||
func Setup(options *SetupOptions) error {
|
||||
sBasePath = options.BasePath
|
||||
sWorkingPath = options.WorkingPath
|
||||
sTempPath = options.TempPath
|
||||
|
||||
sUserID = os.Getuid()
|
||||
sGroupID = os.Getgid()
|
||||
if options.Username != "" {
|
||||
sUser, err := user.Lookup(options.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sUserID, _ = strconv.Atoi(sUser.Uid)
|
||||
sGroupID, _ = strconv.Atoi(sUser.Gid)
|
||||
} else {
|
||||
sUserID = os.Getuid()
|
||||
sGroupID = os.Getgid()
|
||||
}
|
||||
sTVOS = options.IsTVOS
|
||||
|
||||
// TODO: remove after fixed
|
||||
// https://github.com/golang/go/issues/68760
|
||||
sFixAndroidStack = options.FixAndroidStack
|
||||
|
||||
sCommandServerListenPort = uint16(options.CommandServerListenPort)
|
||||
sCommandServerSecret = options.CommandServerSecret
|
||||
sLogMaxLines = options.LogMaxLines
|
||||
sDebug = options.Debug
|
||||
|
||||
os.MkdirAll(sWorkingPath, 0o777)
|
||||
os.MkdirAll(sTempPath, 0o777)
|
||||
if options.Username != "" {
|
||||
os.Chown(sWorkingPath, sUserID, sGroupID)
|
||||
os.Chown(sTempPath, sUserID, sGroupID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/option"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
@@ -50,9 +50,9 @@ func NewDefaultFactory(
|
||||
level: LevelTrace,
|
||||
subscriber: observable.NewSubscriber[Entry](128),
|
||||
}
|
||||
/*if platformWriter != nil {
|
||||
if platformWriter != nil {
|
||||
factory.platformFormatter.DisableColors = platformWriter.DisableColors()
|
||||
}*/
|
||||
}
|
||||
if needObservable {
|
||||
factory.observer = observable.NewObserver[Entry](factory.subscriber, 64)
|
||||
}
|
||||
@@ -111,30 +111,28 @@ type observableLogger struct {
|
||||
|
||||
func (l *observableLogger) Log(ctx context.Context, level Level, args []any) {
|
||||
level = OverrideLevelFromContext(level, ctx)
|
||||
if level > l.level && l.platformWriter == nil {
|
||||
if level > l.level {
|
||||
return
|
||||
}
|
||||
nowTime := time.Now()
|
||||
if level <= l.level {
|
||||
if l.needObservable {
|
||||
message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime)
|
||||
if level == LevelPanic {
|
||||
panic(message)
|
||||
}
|
||||
l.writer.Write([]byte(message))
|
||||
if level == LevelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
l.subscriber.Emit(Entry{level, messageSimple})
|
||||
} else {
|
||||
message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)
|
||||
if level == LevelPanic {
|
||||
panic(message)
|
||||
}
|
||||
l.writer.Write([]byte(message))
|
||||
if level == LevelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
if l.needObservable {
|
||||
message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime)
|
||||
if level == LevelPanic {
|
||||
panic(message)
|
||||
}
|
||||
l.writer.Write([]byte(message))
|
||||
if level == LevelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
l.subscriber.Emit(Entry{level, messageSimple})
|
||||
} else {
|
||||
message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)
|
||||
if level == LevelPanic {
|
||||
panic(message)
|
||||
}
|
||||
l.writer.Write([]byte(message))
|
||||
if level == LevelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if l.platformWriter != nil {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package log
|
||||
|
||||
type PlatformWriter interface {
|
||||
DisableColors() bool
|
||||
WriteMessage(level Level, message string)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/route/rule"
|
||||
@@ -78,7 +79,7 @@ type Endpoint struct {
|
||||
logger logger.ContextLogger
|
||||
dnsRouter adapter.DNSRouter
|
||||
network adapter.NetworkManager
|
||||
platformInterface adapter.PlatformInterface
|
||||
platformInterface platform.Interface
|
||||
server *tsnet.Server
|
||||
stack *stack.Stack
|
||||
icmpForwarder *tun.ICMPForwarder
|
||||
@@ -187,7 +188,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
logger: logger,
|
||||
dnsRouter: dnsRouter,
|
||||
network: service.FromContext[adapter.NetworkManager](ctx),
|
||||
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||
platformInterface: service.FromContext[platform.Interface](ctx),
|
||||
server: server,
|
||||
acceptRoutes: options.AcceptRoutes,
|
||||
exitNode: options.ExitNode,
|
||||
@@ -287,7 +288,7 @@ func (t *Endpoint) watchState() {
|
||||
if authURL != "" {
|
||||
t.logger.Info("Waiting for authentication: ", authURL)
|
||||
if t.platformInterface != nil {
|
||||
err := t.platformInterface.SendNotification(&adapter.Notification{
|
||||
err := t.platformInterface.SendNotification(&platform.Notification{
|
||||
Identifier: "tailscale-authentication",
|
||||
TypeName: "Tailscale Authentication Notifications",
|
||||
TypeID: 10,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/tailscale/net/netns"
|
||||
)
|
||||
|
||||
func setAndroidProtectFunc(platformInterface adapter.PlatformInterface) {
|
||||
func setAndroidProtectFunc(platformInterface platform.Interface) {
|
||||
if platformInterface != nil {
|
||||
netns.SetAndroidProtectFunc(func(fd int) error {
|
||||
return platformInterface.AutoDetectInterfaceControl(fd)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
package tailscale
|
||||
|
||||
import "github.com/sagernet/sing-box/adapter"
|
||||
import "github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
|
||||
func setAndroidProtectFunc(platformInterface adapter.PlatformInterface) {
|
||||
func setAndroidProtectFunc(platformInterface platform.Interface) {
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/route/rule"
|
||||
@@ -48,7 +49,7 @@ type Inbound struct {
|
||||
stack string
|
||||
tunIf tun.Tun
|
||||
tunStack tun.Stack
|
||||
platformInterface adapter.PlatformInterface
|
||||
platformInterface platform.Interface
|
||||
platformOptions option.TunPlatformOptions
|
||||
autoRedirect tun.AutoRedirect
|
||||
routeRuleSet []adapter.RuleSet
|
||||
@@ -130,7 +131,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
deprecated.Report(ctx, deprecated.OptionTUNGSO)
|
||||
}
|
||||
|
||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
tunMTU := options.MTU
|
||||
enableGSO := C.IsLinux && options.Stack == "gvisor" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152
|
||||
if tunMTU == 0 {
|
||||
@@ -371,8 +372,8 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
}
|
||||
monitor.Start("open interface")
|
||||
if t.platformInterface != nil && t.platformInterface.UsePlatformInterface() {
|
||||
tunInterface, err = t.platformInterface.OpenInterface(&tunOptions, t.platformOptions)
|
||||
if t.platformInterface != nil {
|
||||
tunInterface, err = t.platformInterface.OpenTun(&tunOptions, t.platformOptions)
|
||||
} else {
|
||||
if HookBeforeCreatePlatformInterface != nil {
|
||||
HookBeforeCreatePlatformInterface()
|
||||
@@ -392,7 +393,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
)
|
||||
if t.platformInterface != nil {
|
||||
forwarderBindInterface = true
|
||||
includeAllNetworks = t.platformInterface.NetworkExtensionIncludeAllNetworks()
|
||||
includeAllNetworks = t.platformInterface.IncludeAllNetworks()
|
||||
}
|
||||
tunStack, err := tun.NewStack(t.stack, tun.StackOptions{
|
||||
Context: t.ctx,
|
||||
|
||||
119
route/network.go
119
route/network.go
@@ -8,15 +8,14 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/common/settings"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
@@ -47,35 +46,32 @@ type NetworkManager struct {
|
||||
packageManager tun.PackageManager
|
||||
powerListener winpowrprof.EventListener
|
||||
pauseManager pause.Manager
|
||||
platformInterface adapter.PlatformInterface
|
||||
platformInterface platform.Interface
|
||||
endpoint adapter.EndpointManager
|
||||
inbound adapter.InboundManager
|
||||
outbound adapter.OutboundManager
|
||||
needWIFIState bool
|
||||
wifiMonitor settings.WIFIMonitor
|
||||
wifiState adapter.WIFIState
|
||||
wifiStateMutex sync.RWMutex
|
||||
started bool
|
||||
}
|
||||
|
||||
func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*NetworkManager, error) {
|
||||
defaultDomainResolver := common.PtrValueOrDefault(options.DefaultDomainResolver)
|
||||
if options.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||
func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) {
|
||||
defaultDomainResolver := common.PtrValueOrDefault(routeOptions.DefaultDomainResolver)
|
||||
if routeOptions.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||
return nil, E.New("`auto_detect_interface` is only supported on Linux, Windows and macOS")
|
||||
} else if options.OverrideAndroidVPN && !C.IsAndroid {
|
||||
} else if routeOptions.OverrideAndroidVPN && !C.IsAndroid {
|
||||
return nil, E.New("`override_android_vpn` is only supported on Android")
|
||||
} else if options.DefaultInterface != "" && !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||
} else if routeOptions.DefaultInterface != "" && !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||
return nil, E.New("`default_interface` is only supported on Linux, Windows and macOS")
|
||||
} else if options.DefaultMark != 0 && !C.IsLinux {
|
||||
} else if routeOptions.DefaultMark != 0 && !C.IsLinux {
|
||||
return nil, E.New("`default_mark` is only supported on linux")
|
||||
}
|
||||
nm := &NetworkManager{
|
||||
logger: logger,
|
||||
interfaceFinder: control.NewDefaultInterfaceFinder(),
|
||||
autoDetectInterface: options.AutoDetectInterface,
|
||||
autoDetectInterface: routeOptions.AutoDetectInterface,
|
||||
defaultOptions: adapter.NetworkOptions{
|
||||
BindInterface: options.DefaultInterface,
|
||||
RoutingMark: uint32(options.DefaultMark),
|
||||
BindInterface: routeOptions.DefaultInterface,
|
||||
RoutingMark: uint32(routeOptions.DefaultMark),
|
||||
DomainResolver: defaultDomainResolver.Server,
|
||||
DomainResolveOptions: adapter.DNSQueryOptions{
|
||||
Strategy: C.DomainStrategy(defaultDomainResolver.Strategy),
|
||||
@@ -83,28 +79,27 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options
|
||||
RewriteTTL: defaultDomainResolver.RewriteTTL,
|
||||
ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
||||
},
|
||||
NetworkStrategy: (*C.NetworkStrategy)(options.DefaultNetworkStrategy),
|
||||
NetworkType: common.Map(options.DefaultNetworkType, option.InterfaceType.Build),
|
||||
FallbackNetworkType: common.Map(options.DefaultFallbackNetworkType, option.InterfaceType.Build),
|
||||
FallbackDelay: time.Duration(options.DefaultFallbackDelay),
|
||||
NetworkStrategy: (*C.NetworkStrategy)(routeOptions.DefaultNetworkStrategy),
|
||||
NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build),
|
||||
FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build),
|
||||
FallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay),
|
||||
},
|
||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||
platformInterface: service.FromContext[platform.Interface](ctx),
|
||||
endpoint: service.FromContext[adapter.EndpointManager](ctx),
|
||||
inbound: service.FromContext[adapter.InboundManager](ctx),
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule),
|
||||
}
|
||||
if options.DefaultNetworkStrategy != nil {
|
||||
if options.DefaultInterface != "" {
|
||||
if routeOptions.DefaultNetworkStrategy != nil {
|
||||
if routeOptions.DefaultInterface != "" {
|
||||
return nil, E.New("`default_network_strategy` is conflict with `default_interface`")
|
||||
}
|
||||
if !options.AutoDetectInterface {
|
||||
if !routeOptions.AutoDetectInterface {
|
||||
return nil, E.New("`auto_detect_interface` is required by `default_network_strategy`")
|
||||
}
|
||||
}
|
||||
usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil
|
||||
enforceInterfaceMonitor := options.AutoDetectInterface
|
||||
enforceInterfaceMonitor := routeOptions.AutoDetectInterface
|
||||
if !usePlatformDefaultInterfaceMonitor {
|
||||
networkMonitor, err := tun.NewNetworkUpdateMonitor(logger)
|
||||
if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) {
|
||||
@@ -114,7 +109,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options
|
||||
nm.networkMonitor = networkMonitor
|
||||
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(nm.networkMonitor, logger, tun.DefaultInterfaceMonitorOptions{
|
||||
InterfaceFinder: nm.interfaceFinder,
|
||||
OverrideAndroidVPN: options.OverrideAndroidVPN,
|
||||
OverrideAndroidVPN: routeOptions.OverrideAndroidVPN,
|
||||
UnderNetworkExtension: nm.platformInterface != nil && nm.platformInterface.UnderNetworkExtension(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -188,35 +183,11 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
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 {
|
||||
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||
var err error
|
||||
@@ -248,13 +219,6 @@ func (r *NetworkManager) Close() error {
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
@@ -263,10 +227,10 @@ func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder {
|
||||
}
|
||||
|
||||
func (r *NetworkManager) UpdateInterfaces() error {
|
||||
if r.platformInterface == nil || !r.platformInterface.UsePlatformNetworkInterfaces() {
|
||||
if r.platformInterface == nil {
|
||||
return r.interfaceFinder.Update()
|
||||
} else {
|
||||
interfaces, err := r.platformInterface.NetworkInterfaces()
|
||||
interfaces, err := r.platformInterface.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -412,41 +376,20 @@ func (r *NetworkManager) PackageManager() tun.PackageManager {
|
||||
return r.packageManager
|
||||
}
|
||||
|
||||
func (r *NetworkManager) NeedWIFIState() bool {
|
||||
return r.needWIFIState
|
||||
}
|
||||
|
||||
func (r *NetworkManager) WIFIState() adapter.WIFIState {
|
||||
r.wifiStateMutex.RLock()
|
||||
defer r.wifiStateMutex.RUnlock()
|
||||
return r.wifiState
|
||||
}
|
||||
|
||||
func (r *NetworkManager) onWIFIStateChanged(state adapter.WIFIState) {
|
||||
r.wifiStateMutex.Lock()
|
||||
if state != r.wifiState {
|
||||
r.wifiState = state
|
||||
r.wifiStateMutex.Unlock()
|
||||
if state.SSID != "" {
|
||||
r.logger.Info("WIFI state changed: SSID=", state.SSID, ", BSSID=", state.BSSID)
|
||||
} else {
|
||||
r.logger.Info("WIFI disconnected")
|
||||
}
|
||||
} else {
|
||||
r.wifiStateMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if r.platformInterface != nil {
|
||||
state := r.platformInterface.ReadWIFIState()
|
||||
if state != r.wifiState {
|
||||
r.wifiState = state
|
||||
if state.SSID != "" {
|
||||
r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID)
|
||||
}
|
||||
}
|
||||
}
|
||||
r.onWIFIStateChanged(state)
|
||||
}
|
||||
|
||||
func (r *NetworkManager) ResetNetwork() {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type platformSearcher struct {
|
||||
platform adapter.PlatformInterface
|
||||
}
|
||||
|
||||
func newPlatformSearcher(platform adapter.PlatformInterface) process.Searcher {
|
||||
return &platformSearcher{platform: platform}
|
||||
}
|
||||
|
||||
func (s *platformSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||
if !s.platform.UsePlatformConnectionOwnerFinder() {
|
||||
return nil, process.ErrNotFound
|
||||
}
|
||||
|
||||
var ipProtocol int32
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
ipProtocol = syscall.IPPROTO_TCP
|
||||
case N.NetworkUDP:
|
||||
ipProtocol = syscall.IPPROTO_UDP
|
||||
default:
|
||||
return nil, process.ErrNotFound
|
||||
}
|
||||
|
||||
request := &adapter.FindConnectionOwnerRequest{
|
||||
IpProtocol: ipProtocol,
|
||||
SourceAddress: source.Addr().String(),
|
||||
SourcePort: int32(source.Port()),
|
||||
DestinationAddress: destination.Addr().String(),
|
||||
DestinationPort: int32(destination.Port()),
|
||||
}
|
||||
|
||||
return s.platform.FindConnectionOwner(request)
|
||||
}
|
||||
@@ -382,18 +382,18 @@ func (r *Router) matchRule(
|
||||
r.logger.InfoContext(ctx, "failed to search process: ", fErr)
|
||||
} else {
|
||||
if processInfo.ProcessPath != "" {
|
||||
if processInfo.UserName != "" {
|
||||
r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.UserName)
|
||||
if processInfo.User != "" {
|
||||
r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.User)
|
||||
} else if processInfo.UserId != -1 {
|
||||
r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user id: ", processInfo.UserId)
|
||||
} else {
|
||||
r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath)
|
||||
}
|
||||
} else if processInfo.AndroidPackageName != "" {
|
||||
r.logger.InfoContext(ctx, "found package name: ", processInfo.AndroidPackageName)
|
||||
} else if processInfo.PackageName != "" {
|
||||
r.logger.InfoContext(ctx, "found package name: ", processInfo.PackageName)
|
||||
} else if processInfo.UserId != -1 {
|
||||
if processInfo.UserName != "" {
|
||||
r.logger.InfoContext(ctx, "found user: ", processInfo.UserName)
|
||||
if processInfo.User != "" {
|
||||
r.logger.InfoContext(ctx, "found user: ", processInfo.User)
|
||||
} else {
|
||||
r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
@@ -36,7 +37,7 @@ type Router struct {
|
||||
processSearcher process.Searcher
|
||||
pauseManager pause.Manager
|
||||
trackers []adapter.ConnectionTracker
|
||||
platformInterface adapter.PlatformInterface
|
||||
platformInterface platform.Interface
|
||||
needWIFIState bool
|
||||
started bool
|
||||
}
|
||||
@@ -55,7 +56,8 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
|
||||
ruleSetMap: make(map[string]adapter.RuleSet),
|
||||
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||
platformInterface: service.FromContext[platform.Interface](ctx),
|
||||
needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,11 +119,13 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
||||
if metadata.ContainsProcessRule {
|
||||
needFindProcess = true
|
||||
}
|
||||
if metadata.ContainsWIFIRule {
|
||||
r.needWIFIState = true
|
||||
}
|
||||
}
|
||||
r.network.Initialize(r.ruleSets)
|
||||
if needFindProcess {
|
||||
if r.platformInterface != nil && r.platformInterface.UsePlatformConnectionOwnerFinder() {
|
||||
r.processSearcher = newPlatformSearcher(r.platformInterface)
|
||||
if r.platformInterface != nil {
|
||||
r.processSearcher = r.platformInterface
|
||||
} else {
|
||||
monitor.Start("initialize process searcher")
|
||||
searcher, err := process.NewSearcher(process.Config{
|
||||
@@ -191,6 +195,10 @@ func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) {
|
||||
return ruleSet, loaded
|
||||
}
|
||||
|
||||
func (r *Router) NeedWIFIState() bool {
|
||||
return r.needWIFIState
|
||||
}
|
||||
|
||||
func (r *Router) Rules() []adapter.Rule {
|
||||
return r.rules
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ func NewPackageNameItem(packageNameList []string) *PackageNameItem {
|
||||
}
|
||||
|
||||
func (r *PackageNameItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.ProcessInfo == nil || metadata.ProcessInfo.AndroidPackageName == "" {
|
||||
if metadata.ProcessInfo == nil || metadata.ProcessInfo.PackageName == "" {
|
||||
return false
|
||||
}
|
||||
return r.packageMap[metadata.ProcessInfo.AndroidPackageName]
|
||||
return r.packageMap[metadata.ProcessInfo.PackageName]
|
||||
}
|
||||
|
||||
func (r *PackageNameItem) String() string {
|
||||
|
||||
@@ -26,10 +26,10 @@ func NewUserItem(users []string) *UserItem {
|
||||
}
|
||||
|
||||
func (r *UserItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.ProcessInfo == nil || metadata.ProcessInfo.UserName == "" {
|
||||
if metadata.ProcessInfo == nil || metadata.ProcessInfo.User == "" {
|
||||
return false
|
||||
}
|
||||
return r.userMap[metadata.ProcessInfo.UserName]
|
||||
return r.userMap[metadata.ProcessInfo.User]
|
||||
}
|
||||
|
||||
func (r *UserItem) String() string {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@@ -110,7 +111,7 @@ func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundCont
|
||||
if err != nil {
|
||||
return metadata
|
||||
}
|
||||
var processInfo adapter.ConnectionOwner
|
||||
var processInfo process.Info
|
||||
metadata.ProcessInfo = &processInfo
|
||||
processInfo.ProcessID = uint32(senderPid)
|
||||
|
||||
@@ -139,7 +140,7 @@ func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundCont
|
||||
processInfo.UserId = int32(uid)
|
||||
uidFound = true
|
||||
if osUser, _ := user.LookupId(F.ToString(uid)); osUser != nil {
|
||||
processInfo.UserName = osUser.Username
|
||||
processInfo.User = osUser.Username
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -158,8 +159,8 @@ func (t *resolve1Manager) log(sender dbus.Sender, message ...any) {
|
||||
var prefix string
|
||||
if metadata.ProcessInfo.ProcessPath != "" {
|
||||
prefix = filepath.Base(metadata.ProcessInfo.ProcessPath)
|
||||
} else if metadata.ProcessInfo.UserName != "" {
|
||||
prefix = F.ToString("user:", metadata.ProcessInfo.UserName)
|
||||
} else if metadata.ProcessInfo.User != "" {
|
||||
prefix = F.ToString("user:", metadata.ProcessInfo.User)
|
||||
} else if metadata.ProcessInfo.UserId != 0 {
|
||||
prefix = F.ToString("uid:", metadata.ProcessInfo.UserId)
|
||||
}
|
||||
@@ -176,8 +177,8 @@ func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context
|
||||
var prefix string
|
||||
if metadata.ProcessInfo.ProcessPath != "" {
|
||||
prefix = filepath.Base(metadata.ProcessInfo.ProcessPath)
|
||||
} else if metadata.ProcessInfo.UserName != "" {
|
||||
prefix = F.ToString("user:", metadata.ProcessInfo.UserName)
|
||||
} else if metadata.ProcessInfo.User != "" {
|
||||
prefix = F.ToString("user:", metadata.ProcessInfo.User)
|
||||
} else if metadata.ProcessInfo.UserId != 0 {
|
||||
prefix = F.ToString("uid:", metadata.ProcessInfo.UserId)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user