Compare commits

..

5 Commits

Author SHA1 Message Date
世界
ae47deab58 documentation: Bump version 2024-05-28 13:30:29 +08:00
世界
c0341118ec Add per app proxy support for auto redirect 2024-05-28 13:11:50 +08:00
世界
116492cb46 Add support for tailing comma in json decoder 2024-05-27 22:15:18 +08:00
世界
2d8d9a1959 Add simple auto redirect for Android 2024-05-27 22:05:38 +08:00
iosmanthus
5735227174 Introduce bittorrent related protocol sniffers
* Introduce bittorrent related protocol sniffers

including, sniffers of
1. BitTorrent Protocol (TCP)
2. uTorrent Transport Protocol (UDP)

Signed-off-by: iosmanthus <myosmanthustree@gmail.com>
Co-authored-by: 世界 <i@sekai.icu>
2024-05-27 21:48:04 +08:00
27 changed files with 702 additions and 216 deletions

View File

@@ -26,7 +26,7 @@ jobs:
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Publish release
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser-pro
version: latest

View File

@@ -93,6 +93,7 @@ type DNSRule interface {
type RuleSet interface {
StartContext(ctx context.Context, startContext RuleSetStartContext) error
PostStart() error
Metadata() RuleSetMetadata
Close() error
HeadlessRule

View File

@@ -16,40 +16,30 @@ import (
)
type LinuxSystemProxy struct {
hasGSettings bool
kWriteConfigCmd string
sudoUser string
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
hasGSettings bool
hasKWriteConfig5 bool
sudoUser string
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
kWriteConfigCmds := []string{
"kwriteconfig5",
"kwriteconfig6",
}
var kWriteConfigCmd string
for _, cmd := range kWriteConfigCmds {
if common.Error(exec.LookPath(cmd)) == nil {
kWriteConfigCmd = cmd
break
}
}
hasKWriteConfig5 := common.Error(exec.LookPath("kwriteconfig5")) == nil
var sudoUser string
if os.Getuid() == 0 {
sudoUser = os.Getenv("SUDO_USER")
}
if !hasGSettings && kWriteConfigCmd == "" {
if !hasGSettings && !hasKWriteConfig5 {
return nil, E.New("unsupported desktop environment")
}
return &LinuxSystemProxy{
hasGSettings: hasGSettings,
kWriteConfigCmd: kWriteConfigCmd,
sudoUser: sudoUser,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
hasGSettings: hasGSettings,
hasKWriteConfig5: hasKWriteConfig5,
sudoUser: sudoUser,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil
}
@@ -80,8 +70,8 @@ func (p *LinuxSystemProxy) Enable() error {
return err
}
}
if p.kWriteConfigCmd != "" {
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
if p.hasKWriteConfig5 {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
if err != nil {
return err
}
@@ -93,7 +83,7 @@ func (p *LinuxSystemProxy) Enable() error {
if err != nil {
return err
}
err = p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
if err != nil {
return err
}
@@ -113,8 +103,8 @@ func (p *LinuxSystemProxy) Disable() error {
return err
}
}
if p.kWriteConfigCmd != "" {
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
if p.hasKWriteConfig5 {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
if err != nil {
return err
}
@@ -160,7 +150,7 @@ func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
proxyUrl = "http://" + p.serverAddr.String()
}
err := p.runAsUser(
p.kWriteConfigCmd,
"kwriteconfig5",
"--file",
"kioslaverc",
"--group",

113
common/sniff/bittorrent.go Normal file
View File

@@ -0,0 +1,113 @@
package sniff
import (
"bytes"
"context"
"encoding/binary"
"io"
"os"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
)
const (
trackerConnectFlag = iota
trackerAnnounceFlag
trackerScrapeFlag
trackerProtocolID = 0x41727101980
trackerConnectMinSize = 16
trackerAnnounceMinSize = 20
trackerScrapeMinSize = 8
)
// BitTorrent detects if the stream is a BitTorrent connection.
// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html
func BitTorrent(_ context.Context, reader io.Reader) (*adapter.InboundContext, error) {
var first byte
err := binary.Read(reader, binary.BigEndian, &first)
if err != nil {
return nil, err
}
if first != 19 {
return nil, os.ErrInvalid
}
var protocol [19]byte
_, err = reader.Read(protocol[:])
if err != nil {
return nil, err
}
if string(protocol[:]) != "BitTorrent protocol" {
return nil, os.ErrInvalid
}
return &adapter.InboundContext{
Protocol: C.ProtocolBitTorrent,
}, nil
}
// UTP detects if the packet is a uTP connection packet.
// For the uTP protocol specification, see
// 1. https://www.bittorrent.org/beps/bep_0029.html
// 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112
func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
// A valid uTP packet must be at least 20 bytes long.
if len(packet) < 20 {
return nil, os.ErrInvalid
}
version := packet[0] & 0x0F
ty := packet[0] >> 4
if version != 1 || ty > 4 {
return nil, os.ErrInvalid
}
// Validate the extensions
extension := packet[1]
reader := bytes.NewReader(packet[20:])
for extension != 0 {
err := binary.Read(reader, binary.BigEndian, &extension)
if err != nil {
return nil, err
}
var length byte
err = binary.Read(reader, binary.BigEndian, &length)
if err != nil {
return nil, err
}
_, err = reader.Seek(int64(length), io.SeekCurrent)
if err != nil {
return nil, err
}
}
return &adapter.InboundContext{
Protocol: C.ProtocolBitTorrent,
}, nil
}
// UDPTracker detects if the packet is a UDP Tracker Protocol packet.
// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html
func UDPTracker(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
switch {
case len(packet) >= trackerConnectMinSize &&
binary.BigEndian.Uint64(packet[:8]) == trackerProtocolID &&
binary.BigEndian.Uint32(packet[8:12]) == trackerConnectFlag:
fallthrough
case len(packet) >= trackerAnnounceMinSize &&
binary.BigEndian.Uint32(packet[8:12]) == trackerAnnounceFlag:
fallthrough
case len(packet) >= trackerScrapeMinSize &&
binary.BigEndian.Uint32(packet[8:12]) == trackerScrapeFlag:
return &adapter.InboundContext{
Protocol: C.ProtocolBitTorrent,
}, nil
default:
return nil, os.ErrInvalid
}
}

View File

@@ -0,0 +1,81 @@
package sniff_test
import (
"bytes"
"context"
"encoding/hex"
"testing"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/stretchr/testify/require"
)
func TestSniffBittorrent(t *testing.T) {
t.Parallel()
packets := []string{
"13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1",
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638",
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130",
}
for _, pkt := range packets {
pkt, err := hex.DecodeString(pkt)
require.NoError(t, err)
metadata, err := sniff.BitTorrent(context.TODO(), bytes.NewReader(pkt))
require.NoError(t, err)
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
}
}
func TestSniffUTP(t *testing.T) {
t.Parallel()
packets := []string{
"010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8",
"01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb",
"21001ecb6817f2805d044fd700100000dbd03029",
"410277ef0b1fb1f60000000000040000c233000000080000000000000000",
}
for _, pkt := range packets {
pkt, err := hex.DecodeString(pkt)
require.NoError(t, err)
metadata, err := sniff.UTP(context.TODO(), pkt)
require.NoError(t, err)
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
}
}
func TestSniffUDPTracker(t *testing.T) {
t.Parallel()
connectPackets := []string{
// connect packets
"00000417271019800000000078e90560",
"00000417271019800000000022c5d64d",
"000004172710198000000000b3863541",
// announce packets
"3d7592ead4b8c9e300000001b871a3820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
"3d7592ead4b8c9e30000000188deed1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
"3d7592ead4b8c9e300000001ceb948ad0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a3362cdb7020ff920e5aa642c3d4066950dd1f01f4d00000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
// scrape packets
"3d7592ead4b8c9e300000002d2f4bba5a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
"3d7592ead4b8c9e300000002441243292aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
"3d7592ead4b8c9e300000002b2aa461b1ad1fa9661cf3fe45fb2504ad52ec6c67758e294",
}
for _, pkt := range connectPackets {
pkt, err := hex.DecodeString(pkt)
require.NoError(t, err)
metadata, err := sniff.UDPTracker(context.TODO(), pkt)
require.NoError(t, err)
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
}
}

View File

@@ -1,9 +1,10 @@
package constant
const (
ProtocolTLS = "tls"
ProtocolHTTP = "http"
ProtocolQUIC = "quic"
ProtocolDNS = "dns"
ProtocolSTUN = "stun"
ProtocolTLS = "tls"
ProtocolHTTP = "http"
ProtocolQUIC = "quic"
ProtocolDNS = "dns"
ProtocolSTUN = "stun"
ProtocolBitTorrent = "bittorrent"
)

View File

@@ -2,13 +2,25 @@
icon: material/alert-decagram
---
### 1.9.2
#### 1.10.0-alpha.1
* Fixes and improvements
* Add tailing comma support in JSON configuration
* Add simple auto redirect for Android **1**
* Add BitTorrent sniffer **2**
### 1.9.1
**1**:
* Fixes and improvements
It allows you to use redirect inbound in the sing-box Android client
and automatically configures IPv4 TCP redirection via su.
This may alleviate the symptoms of some OCD patients who think that
redirect can effectively save power compared to the system HTTP Proxy.
See [Redirect](/configuration/inbound/redirect/).
**2**:
See [Protocol Sniff](/configuration/route/sniff/).
### 1.9.0

View File

@@ -1,3 +1,11 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [auto_redirect](#auto_redirect)
!!! quote ""
Only supported on Linux and macOS.
@@ -9,6 +17,11 @@
"type": "redirect",
"tag": "redirect-in",
"auto_redirect": {
"enabled": false,
"continue_on_no_permission": false
},
... // Listen Fields
}
```
@@ -16,3 +29,23 @@
### Listen Fields
See [Listen Fields](/configuration/shared/listen/) for details.
### Fields
#### `auto_redirect`
!!! question "Since sing-box 1.10.0"
!!! quote ""
Only supported on Android.
Automatically add iptables nat rules to hijack **IPv4 TCP** connections.
It is expected to run with the Android graphical client (it will attempt to su at runtime).
#### `auto_redirect.continue_on_no_permission`
!!! question "Since sing-box 1.10.0"
Ignore errors when the Android device is not rooted or is denied root access.

View File

@@ -1,3 +1,11 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: [auto_redirect](#auto_redirect)
!!! quote ""
仅支持 Linux 和 macOS。
@@ -9,9 +17,35 @@
"type": "redirect",
"tag": "redirect-in",
"auto_redirect": {
"enabled": false,
"continue_on_no_permission": false
},
... // 监听字段
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### `auto_redirect`
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Android。
自动添加 iptables nat 规则以劫持 **IPv4 TCP** 连接。
它预计与 Android 图形客户端一起运行(将在运行时尝试 su
#### `auto_redirect.continue_on_no_permission`
!!! question "自 sing-box 1.10.0 起"
当 Android 设备未获得 root 权限或 root 访问权限被拒绝时,忽略错误。

View File

@@ -2,10 +2,11 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
#### Supported Protocols
| Network | Protocol | Domain Name |
|:-------:|:--------:|:-----------:|
| TCP | HTTP | Host |
| TCP | TLS | Server Name |
| UDP | QUIC | Server Name |
| UDP | STUN | / |
| TCP/UDP | DNS | / |
| Network | Protocol | Domain Name |
|:-------:|:-----------:|:-----------:|
| TCP | HTTP | Host |
| TCP | TLS | Server Name |
| UDP | QUIC | Server Name |
| UDP | STUN | / |
| TCP/UDP | DNS | / |
| TCP/UDP | BitTorrent | / |

View File

@@ -2,10 +2,11 @@
#### 支持的协议
| 网络 | 协议 | 域名 |
|:-------:|:----:|:-----------:|
| TCP | HTTP | Host |
| TCP | TLS | Server Name |
| UDP | QUIC | Server Name |
| UDP | STUN | / |
| TCP/UDP | DNS | / |
| 网络 | 协议 | 域名 |
|:-------:|:-----------:|:-----------:|
| TCP | HTTP | Host |
| TCP | TLS | Server Name |
| UDP | QUIC | Server Name |
| UDP | STUN | / |
| TCP/UDP | DNS | / |
| TCP/UDP | BitTorrent | / |

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
@@ -97,6 +98,14 @@ func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network str
return nil, os.ErrInvalid
}
func (s *platformInterfaceStub) PerAppProxyList() ([]uint32, error) {
return nil, os.ErrInvalid
}
func (s *platformInterfaceStub) PerAppProxyMode() int32 {
return platform.PerAppProxyModeDisabled
}
type interfaceMonitorStub struct{}
func (s *interfaceMonitorStub) Start() error {

View File

@@ -22,6 +22,8 @@ type PlatformInterface interface {
IncludeAllNetworks() bool
ReadWIFIState() *WIFIState
ClearDNSCache()
PerAppProxyList() (IntegerIterator, error)
PerAppProxyMode() int32
}
type TunInterface interface {
@@ -54,6 +56,11 @@ type NetworkInterfaceIterator interface {
HasNext() bool
}
type IntegerIterator interface {
Next() int32
HasNext() bool
}
type OnDemandRule interface {
Target() int32
DNSSearchDomainMatch() StringIterator

View File

@@ -11,6 +11,12 @@ import (
"github.com/sagernet/sing/common/logger"
)
const (
PerAppProxyModeDisabled int32 = iota
PerAppProxyModeExclude
PerAppProxyModeInclude
)
type Interface interface {
Initialize(ctx context.Context, router adapter.Router) error
UsePlatformAutoDetectInterfaceControl() bool
@@ -24,5 +30,7 @@ type Interface interface {
IncludeAllNetworks() bool
ClearDNSCache()
ReadWIFIState() adapter.WIFIState
PerAppProxyList() ([]uint32, error)
PerAppProxyMode() int32
process.Searcher
}

View File

@@ -229,6 +229,18 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
return (adapter.WIFIState)(*wifiState)
}
func (w *platformInterfaceWrapper) PerAppProxyList() ([]uint32, error) {
uidIterator, err := w.iif.PerAppProxyList()
if err != nil {
return nil, err
}
return common.Map(iteratorToArray[int32](uidIterator), func(it int32) uint32 { return uint32(it) }), nil
}
func (w *platformInterfaceWrapper) PerAppProxyMode() int32 {
return w.iif.PerAppProxyMode()
}
func (w *platformInterfaceWrapper) DisableColors() bool {
return runtime.GOOS != "android"
}

View File

@@ -16,18 +16,25 @@ func (s *BoxService) Pause() {
if s.pauseTimer != nil {
s.pauseTimer.Stop()
}
s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork)
s.pauseTimer = time.AfterFunc(time.Minute, s.pause)
}
func (s *BoxService) pause() {
s.pauseAccess.Lock()
defer s.pauseAccess.Unlock()
s.pauseManager.DevicePause()
_ = s.instance.Router().ResetNetwork()
s.pauseTimer = nil
}
func (s *BoxService) Wake() {
_ = s.instance.Router().ResetNetwork()
s.pauseAccess.Lock()
defer s.pauseAccess.Unlock()
if s.pauseTimer != nil {
s.pauseTimer.Stop()
s.pauseTimer = nil
return
}
s.pauseTimer = time.AfterFunc(3*time.Minute, s.ResetNetwork)
}
func (s *BoxService) ResetNetwork() {
_ = s.instance.Router().ResetNetwork()
s.pauseManager.DeviceWake()
}

8
go.mod
View File

@@ -26,14 +26,14 @@ require (
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
github.com/sagernet/quic-go v0.43.1-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.4.1
github.com/sagernet/sing-dns v0.2.0
github.com/sagernet/sing v0.5.0-alpha.4
github.com/sagernet/sing-dns v0.2.0-beta.18
github.com/sagernet/sing-mux v0.2.0
github.com/sagernet/sing-quic v0.2.0-beta.5
github.com/sagernet/sing-shadowsocks v0.2.6
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.3.2
github.com/sagernet/sing-tun v0.3.0-beta.6
github.com/sagernet/sing-vmess v0.1.8
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
@@ -46,7 +46,7 @@ require (
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.23.0
golang.org/x/net v0.25.0
golang.org/x/sys v0.21.0
golang.org/x/sys v0.20.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0

16
go.sum
View File

@@ -106,10 +106,10 @@ github.com/sagernet/quic-go v0.43.1-beta.1/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhY
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
github.com/sagernet/sing-dns v0.2.0 h1:dka3weRX6+CrYO3v+hrTy2z68rCOCZXNBiNXpLZ6JNs=
github.com/sagernet/sing-dns v0.2.0/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg=
github.com/sagernet/sing v0.5.0-alpha.4 h1:8ljxrEq1BMcDuGe8FxoeUGasPUXQsIywpUusGrlGkQ0=
github.com/sagernet/sing v0.5.0-alpha.4/go.mod h1:Xh4KO9nGdvm4K/LVg9Xn9jSxJdqe9KcXbAzNC1S2qfw=
github.com/sagernet/sing-dns v0.2.0-beta.18 h1:6vzXZThRdA7YUzBOpSbUT48XRumtl/KIpIHFSOP0za8=
github.com/sagernet/sing-dns v0.2.0-beta.18/go.mod h1:k/dmFcQpg6+m08gC1yQBy+13+QkuLqpKr4bIreq4U24=
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-quic v0.2.0-beta.5 h1:ceKFLd1iS5AtM+pScKmcDp5k7R6WgYIe8vl6nB0aVsE=
@@ -120,8 +120,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY=
github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
github.com/sagernet/sing-tun v0.3.0-beta.6 h1:L11kMrM7UfUW0pzQiU66Fffh4o86KZc1SFGbkYi8Ma8=
github.com/sagernet/sing-tun v0.3.0-beta.6/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@@ -184,8 +184,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@@ -19,7 +19,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
case C.TypeTun:
return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface)
case C.TypeRedirect:
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions, platformInterface)
case C.TypeTProxy:
return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil
case C.TypeDirect:

View File

@@ -2,25 +2,39 @@ package inbound
import (
"context"
"errors"
"net"
"net/netip"
"os"
"os/exec"
"sort"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
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/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Redirect struct {
myInboundAdapter
platformInterface platform.Interface
autoRedirect option.AutoRedirectOptions
needSu bool
suPath string
}
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect {
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions, platformInterface platform.Interface) (*Redirect, error) {
redirect := &Redirect{
myInboundAdapter{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeRedirect,
network: []string{N.NetworkTCP},
ctx: ctx,
@@ -29,9 +43,28 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL
tag: tag,
listenOptions: options.ListenOptions,
},
platformInterface: platformInterface,
autoRedirect: common.PtrValueOrDefault(options.AutoRedirect),
}
if redirect.autoRedirect.Enabled {
if !C.IsAndroid {
return nil, E.New("auto redirect is only supported on Android")
}
userId := os.Getuid()
if userId != 0 {
suPath, err := exec.LookPath("/bin/su")
if err == nil {
redirect.needSu = true
redirect.suPath = suPath
} else if redirect.autoRedirect.ContinueOnNoPermission {
redirect.autoRedirect.Enabled = false
} else {
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
}
}
}
redirect.connHandler = redirect
return redirect
return redirect, nil
}
func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
@@ -42,3 +75,124 @@ func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata ad
metadata.Destination = M.SocksaddrFromNetIP(destination)
return r.newConnection(ctx, conn, metadata)
}
func (r *Redirect) Start() error {
err := r.myInboundAdapter.Start()
if err != nil {
return err
}
if r.autoRedirect.Enabled {
r.cleanupRedirect()
err = r.setupRedirect()
if err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) && exitError.ExitCode() == 13 && r.autoRedirect.ContinueOnNoPermission {
r.logger.Error(E.Cause(err, "setup auto redirect"))
return nil
}
r.cleanupRedirect()
return E.Cause(err, "setup auto redirect")
}
}
return nil
}
func (r *Redirect) Close() error {
if r.autoRedirect.Enabled {
r.cleanupRedirect()
}
return r.myInboundAdapter.Close()
}
func (r *Redirect) setupRedirect() error {
tableName := "sing-box"
rules := `
set -e -o pipefail
iptables -t nat -N sing-box
`
rules += strings.Join(common.FlatMap(r.router.(adapter.Router).InterfaceFinder().Interfaces(), func(it control.Interface) []string {
return common.Map(common.Filter(it.Addresses, func(it netip.Prefix) bool { return it.Addr().Is4() }), func(it netip.Prefix) string {
return "iptables -t nat -A " + tableName + " -p tcp -j RETURN -d " + it.String()
})
}), "\n")
var (
myUid = uint32(os.Getuid())
perAppProxyList []uint32
perAppProxyMap = make(map[uint32]bool)
perAppProxyMode int32
err error
)
if r.platformInterface != nil {
perAppProxyMode = r.platformInterface.PerAppProxyMode()
if perAppProxyMode != platform.PerAppProxyModeDisabled {
perAppProxyList, err = r.platformInterface.PerAppProxyList()
if err != nil {
return E.Cause(err, "read per app proxy configuration")
}
}
for _, proxyUID := range perAppProxyList {
perAppProxyMap[proxyUID] = true
}
}
excludeUser := func(userID uint32) {
if perAppProxyMode != platform.PerAppProxyModeInclude {
perAppProxyMap[userID] = false
} else {
delete(perAppProxyMap, userID)
}
}
excludeUser(myUid)
if myUid != 0 && myUid != 2000 {
excludeUser(0)
}
perAppProxyList = perAppProxyList[:0]
for uid := range perAppProxyMap {
perAppProxyList = append(perAppProxyList, uid)
}
sort.SliceStable(perAppProxyList, func(i, j int) bool {
return perAppProxyList[i] < perAppProxyList[j]
})
redirectPortStr := F.ToString(M.AddrPortFromNet(r.tcpListener.Addr()).Port())
if perAppProxyMode != platform.PerAppProxyModeInclude {
rules += "\n" + strings.Join(common.Map(perAppProxyList, func(it uint32) string {
return "iptables -t nat -A " + tableName + " -j RETURN -m owner --uid-owner " + F.ToString(it)
}), "\n")
rules += "\niptables -t nat -A " + tableName + " -p tcp -j REDIRECT --to-ports " + redirectPortStr
} else {
rules += "\n" + strings.Join(common.Map(perAppProxyList, func(it uint32) string {
return "iptables -t nat -A " + tableName + " -p tcp -j REDIRECT --to-ports " + redirectPortStr + " -m owner --uid-owner " + F.ToString(it)
}), "\n")
}
rules += "\niptables -t nat -A OUTPUT -p tcp -j " + tableName
for _, ruleLine := range strings.Split(rules, "\n") {
ruleLine = strings.TrimSpace(ruleLine)
if ruleLine == "" {
continue
}
r.logger.Debug("# ", ruleLine)
}
return r.runAndroidShell(rules)
}
func (r *Redirect) cleanupRedirect() {
_ = r.runAndroidShell(`
iptables -t nat -D OUTPUT -p tcp -j sing-box
iptables -t nat -F sing-box
iptables -t nat -X sing-box
`)
}
func (r *Redirect) runAndroidShell(content string) error {
var command *exec.Cmd
if r.needSu {
command = exec.Command(r.suPath, "-c", "sh")
} else {
command = exec.Command("sh")
}
command.Stdin = strings.NewReader(content)
combinedOutput, err := command.CombinedOutput()
if err != nil {
return E.Extend(err, string(combinedOutput))
}
return nil
}

View File

@@ -2,6 +2,12 @@ package option
type RedirectInboundOptions struct {
ListenOptions
AutoRedirect *AutoRedirectOptions `json:"auto_redirect,omitempty"`
}
type AutoRedirectOptions struct {
Enabled bool `json:"enabled,omitempty"`
ContinueOnNoPermission bool `json:"continue_on_no_permission,omitempty"`
}
type TProxyInboundOptions struct {

View File

@@ -19,7 +19,6 @@ import (
"github.com/sagernet/sing-box/transport/wireguard"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -112,25 +111,6 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
}
func (w *WireGuard) Start() error {
if common.Any(w.peers, func(peer wireguard.PeerConfig) bool {
return !peer.Endpoint.IsValid()
}) {
// wait for all outbounds to be started and continue in PortStart
return nil
}
return w.start()
}
func (w *WireGuard) PostStart() error {
if common.All(w.peers, func(peer wireguard.PeerConfig) bool {
return peer.Endpoint.IsValid()
}) {
return nil
}
return w.start()
}
func (w *WireGuard) start() error {
err := wireguard.ResolvePeers(w.ctx, w.router, w.peers)
if err != nil {
return err

View File

@@ -221,7 +221,7 @@ func NewRouter(
if serverAddress == "" {
serverAddress = server.Address
}
notIpAddress := !M.ParseSocksaddr(serverAddress).Addr.IsValid()
_, notIpAddress := netip.ParseAddr(serverAddress)
if server.AddressResolver != "" {
if !transportTagMap[server.AddressResolver] {
return nil, E.New("parse dns server[", tag, "]: address resolver not found: ", server.AddressResolver)
@@ -231,7 +231,7 @@ func NewRouter(
} else {
continue
}
} else if notIpAddress && strings.Contains(server.Address, ".") {
} else if notIpAddress != nil && strings.Contains(server.Address, ".") {
return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
}
}
@@ -352,6 +352,14 @@ func NewRouter(
router.interfaceMonitor = interfaceMonitor
}
if runtime.GOOS == "windows" {
powerListener, err := winpowrprof.NewEventListener(router.notifyWindowsPowerEvent)
if err != nil {
return nil, E.Cause(err, "initialize power listener")
}
router.powerListener = powerListener
}
if ntpOptions.Enabled {
ntpDialer, err := dialer.New(router, ntpOptions.DialerOptions)
if err != nil {
@@ -394,17 +402,20 @@ func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outb
defaultOutboundForPacketConnection = detour
}
}
var index, packetIndex int
if defaultOutboundForConnection == nil {
for _, detour := range outbounds {
for i, detour := range outbounds {
if common.Contains(detour.Network(), N.NetworkTCP) {
index = i
defaultOutboundForConnection = detour
break
}
}
}
if defaultOutboundForPacketConnection == nil {
for _, detour := range outbounds {
for i, detour := range outbounds {
if common.Contains(detour.Network(), N.NetworkUDP) {
packetIndex = i
defaultOutboundForPacketConnection = detour
break
}
@@ -421,6 +432,22 @@ func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outb
outbounds = append(outbounds, detour)
outboundByTag[detour.Tag()] = detour
}
if defaultOutboundForConnection != defaultOutboundForPacketConnection {
var description string
if defaultOutboundForConnection.Tag() != "" {
description = defaultOutboundForConnection.Tag()
} else {
description = F.ToString(index)
}
var packetDescription string
if defaultOutboundForPacketConnection.Tag() != "" {
packetDescription = defaultOutboundForPacketConnection.Tag()
} else {
packetDescription = F.ToString(packetIndex)
}
r.logger.Info("using ", defaultOutboundForConnection.Type(), "[", description, "] as default outbound for connection")
r.logger.Info("using ", defaultOutboundForPacketConnection.Type(), "[", packetDescription, "] as default outbound for packet connection")
}
r.inboundByTag = inboundByTag
r.outbounds = outbounds
r.defaultOutboundForConnection = defaultOutboundForConnection
@@ -509,12 +536,75 @@ func (r *Router) Start() error {
r.geositeReader = nil
}
if runtime.GOOS == "windows" {
powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent)
if err == nil {
r.powerListener = powerListener
if len(r.ruleSets) > 0 {
monitor.Start("initialize rule-set")
ruleSetStartContext := NewRuleSetStartContext()
var ruleSetStartGroup task.Group
for i, ruleSet := range r.ruleSets {
ruleSetInPlace := ruleSet
ruleSetStartGroup.Append0(func(ctx context.Context) error {
err := ruleSetInPlace.StartContext(ctx, ruleSetStartContext)
if err != nil {
return E.Cause(err, "initialize rule-set[", i, "]")
}
return nil
})
}
ruleSetStartGroup.Concurrency(5)
ruleSetStartGroup.FastFail()
err := ruleSetStartGroup.Run(r.ctx)
monitor.Finish()
if err != nil {
return err
}
ruleSetStartContext.Close()
}
var (
needProcessFromRuleSet bool
needWIFIStateFromRuleSet bool
)
for _, ruleSet := range r.ruleSets {
metadata := ruleSet.Metadata()
if metadata.ContainsProcessRule {
needProcessFromRuleSet = true
}
if metadata.ContainsWIFIRule {
needWIFIStateFromRuleSet = true
}
}
if needProcessFromRuleSet || r.needFindProcess || r.needPackageManager {
if C.IsAndroid && r.platformInterface == nil {
monitor.Start("initialize package manager")
packageManager, err := tun.NewPackageManager(r)
monitor.Finish()
if err != nil {
return E.Cause(err, "create package manager")
}
monitor.Start("start package manager")
err = packageManager.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start package manager")
}
r.packageManager = packageManager
}
if r.platformInterface != nil {
r.processSearcher = r.platformInterface
} else {
r.logger.Warn("initialize power listener: ", err)
monitor.Start("initialize process searcher")
searcher, err := process.NewSearcher(process.Config{
Logger: r.logger,
PackageManager: r.packageManager,
})
monitor.Finish()
if err != nil {
if err != os.ErrInvalid {
r.logger.Warn(E.Cause(err, "create process searcher"))
}
} else {
r.processSearcher = searcher
}
}
}
@@ -527,6 +617,25 @@ func (r *Router) Start() error {
}
}
if (needWIFIStateFromRuleSet || r.needWIFIState) && r.platformInterface != nil {
monitor.Start("initialize WIFI state")
r.needWIFIState = true
r.interfaceMonitor.RegisterCallback(func(_ int) {
r.updateWIFIState()
})
r.updateWIFIState()
monitor.Finish()
}
for i, rule := range r.rules {
monitor.Start("initialize rule[", i, "]")
err := rule.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize rule[", i, "]")
}
}
monitor.Start("initialize DNS client")
r.dnsClient.Start()
monitor.Finish()
@@ -635,93 +744,12 @@ func (r *Router) Close() error {
}
func (r *Router) PostStart() error {
monitor := taskmonitor.New(r.logger, C.StopTimeout)
if len(r.ruleSets) > 0 {
monitor.Start("initialize rule-set")
ruleSetStartContext := NewRuleSetStartContext()
var ruleSetStartGroup task.Group
for i, ruleSet := range r.ruleSets {
ruleSetInPlace := ruleSet
ruleSetStartGroup.Append0(func(ctx context.Context) error {
err := ruleSetInPlace.StartContext(ctx, ruleSetStartContext)
if err != nil {
return E.Cause(err, "initialize rule-set[", i, "]")
}
return nil
})
}
ruleSetStartGroup.Concurrency(5)
ruleSetStartGroup.FastFail()
err := ruleSetStartGroup.Run(r.ctx)
monitor.Finish()
if err != nil {
return err
}
ruleSetStartContext.Close()
}
var (
needProcessFromRuleSet bool
needWIFIStateFromRuleSet bool
)
for _, ruleSet := range r.ruleSets {
metadata := ruleSet.Metadata()
if metadata.ContainsProcessRule {
needProcessFromRuleSet = true
}
if metadata.ContainsWIFIRule {
needWIFIStateFromRuleSet = true
}
}
if needProcessFromRuleSet || r.needFindProcess || r.needPackageManager {
if C.IsAndroid && r.platformInterface == nil {
monitor.Start("initialize package manager")
packageManager, err := tun.NewPackageManager(r)
monitor.Finish()
err := ruleSet.PostStart()
if err != nil {
return E.Cause(err, "create package manager")
return E.Cause(err, "post start rule-set[", i, "]")
}
monitor.Start("start package manager")
err = packageManager.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start package manager")
}
r.packageManager = packageManager
}
if r.platformInterface != nil {
r.processSearcher = r.platformInterface
} else {
monitor.Start("initialize process searcher")
searcher, err := process.NewSearcher(process.Config{
Logger: r.logger,
PackageManager: r.packageManager,
})
monitor.Finish()
if err != nil {
if err != os.ErrInvalid {
r.logger.Warn(E.Cause(err, "create process searcher"))
}
} else {
r.processSearcher = searcher
}
}
}
if (needWIFIStateFromRuleSet || r.needWIFIState) && r.platformInterface != nil {
monitor.Start("initialize WIFI state")
r.needWIFIState = true
r.interfaceMonitor.RegisterCallback(func(_ int) {
r.updateWIFIState()
})
r.updateWIFIState()
monitor.Finish()
}
for i, rule := range r.rules {
monitor.Start("initialize rule[", i, "]")
err := rule.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize rule[", i, "]")
}
}
r.started = true
@@ -822,7 +850,16 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
if metadata.InboundOptions.SniffEnabled {
buffer := buf.NewPacket()
sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost)
sniffMetadata, err := sniff.PeekStream(
ctx,
conn,
buffer,
time.Duration(metadata.InboundOptions.SniffTimeout),
sniff.StreamDomainNameQuery,
sniff.TLSClientHello,
sniff.HTTPHost,
sniff.BitTorrent,
)
if sniffMetadata != nil {
metadata.Protocol = sniffMetadata.Protocol
metadata.Domain = sniffMetadata.Domain
@@ -949,7 +986,15 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
metadata.Destination = destination
}
if metadata.InboundOptions.SniffEnabled {
sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage)
sniffMetadata, _ := sniff.PeekPacket(
ctx,
buffer.Bytes(),
sniff.DomainNameQuery,
sniff.QUICClientHello,
sniff.STUNMessage,
sniff.UTP,
sniff.UDPTracker,
)
if sniffMetadata != nil {
metadata.Protocol = sniffMetadata.Protocol
metadata.Domain = sniffMetadata.Domain
@@ -1093,9 +1138,6 @@ func (r *Router) AutoDetectInterfaceFunc() control.Func {
if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() {
return r.platformInterface.AutoDetectInterfaceControl()
} else {
if r.interfaceMonitor == nil {
return nil
}
return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
remoteAddr := M.ParseSocksaddr(address).Addr
if C.IsLinux {

View File

@@ -78,6 +78,10 @@ func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.Ru
return nil
}
func (s *LocalRuleSet) PostStart() error {
return nil
}
func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
return s.metadata
}

View File

@@ -112,6 +112,16 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.R
return nil
}
func (s *RemoteRuleSet) PostStart() error {
if s.lastUpdated.IsZero() {
err := s.fetchOnce(s.ctx, nil)
if err != nil {
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
}
}
return nil
}
func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
return s.metadata
}

View File

@@ -12,8 +12,6 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
"github.com/sagernet/wireguard-go/conn"
)
@@ -21,9 +19,6 @@ var _ conn.Bind = (*ClientBind)(nil)
type ClientBind struct {
ctx context.Context
pauseManager pause.Manager
bindCtx context.Context
bindDone context.CancelFunc
errorHandler E.Handler
dialer N.Dialer
reservedForEndpoint map[netip.AddrPort][3]uint8
@@ -38,7 +33,6 @@ type ClientBind struct {
func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind {
return &ClientBind{
ctx: ctx,
pauseManager: service.FromContext[pause.Manager](ctx),
errorHandler: errorHandler,
dialer: dialer,
reservedForEndpoint: make(map[netip.AddrPort][3]uint8),
@@ -61,11 +55,6 @@ func (c *ClientBind) connect() (*wireConn, error) {
}
c.connAccess.Lock()
defer c.connAccess.Unlock()
select {
case <-c.done:
return nil, net.ErrClosed
default:
}
serverConn = c.conn
if serverConn != nil {
select {
@@ -76,7 +65,7 @@ func (c *ClientBind) connect() (*wireConn, error) {
}
}
if c.isConnect {
udpConn, err := c.dialer.DialContext(c.bindCtx, N.NetworkUDP, M.SocksaddrFromNetIP(c.connectAddr))
udpConn, err := c.dialer.DialContext(c.ctx, N.NetworkUDP, M.SocksaddrFromNetIP(c.connectAddr))
if err != nil {
return nil, err
}
@@ -85,7 +74,7 @@ func (c *ClientBind) connect() (*wireConn, error) {
done: make(chan struct{}),
}
} else {
udpConn, err := c.dialer.ListenPacket(c.bindCtx, M.Socksaddr{Addr: netip.IPv4Unspecified()})
udpConn, err := c.dialer.ListenPacket(c.ctx, M.Socksaddr{Addr: netip.IPv4Unspecified()})
if err != nil {
return nil, err
}
@@ -103,7 +92,6 @@ func (c *ClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint1
c.done = make(chan struct{})
default:
}
c.bindCtx, c.bindDone = context.WithCancel(c.ctx)
return []conn.ReceiveFunc{c.receive}, 0, nil
}
@@ -117,7 +105,6 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
}
c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server"))
err = nil
c.pauseManager.WaitActive()
time.Sleep(time.Second)
return
}
@@ -143,17 +130,12 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
}
func (c *ClientBind) Close() error {
common.Close(common.PtrOrNil(c.conn))
select {
case <-c.done:
default:
close(c.done)
}
if c.bindDone != nil {
c.bindDone()
}
c.connAccess.Lock()
defer c.connAccess.Unlock()
common.Close(common.PtrOrNil(c.conn))
return nil
}
@@ -164,8 +146,6 @@ func (c *ClientBind) SetMark(mark uint32) error {
func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint) error {
udpConn, err := c.connect()
if err != nil {
c.pauseManager.WaitActive()
time.Sleep(time.Second)
return err
}
destination := netip.AddrPort(ep.(Endpoint))