Compare commits

..

31 Commits

Author SHA1 Message Date
世界
d46daeb790 documentation: Bump version 2024-07-13 17:47:57 +08:00
世界
fafc7e76ba Add inline rule-set & Add reload for local rule-set 2024-07-13 17:47:41 +08:00
世界
ec167cf4d2 Unique rule-set names 2024-07-13 17:47:41 +08:00
世界
9f20431d9c Add accept empty DNS rule option 2024-07-13 17:47:40 +08:00
世界
4fc69e5ce6 Add custom options for TUN auto-route and auto-redirect 2024-07-13 17:47:40 +08:00
世界
0e69a92f14 Improve base DNS transports 2024-07-13 17:47:39 +08:00
世界
5945bec4c8 Add auto-redirect & Improve auto-route 2024-07-13 17:47:39 +08:00
世界
9f91a0ce2b Add rule-set decompile command 2024-07-13 17:47:39 +08:00
世界
317d498a12 Add IP address support for rule-set match match 2024-07-13 17:47:39 +08:00
世界
9e99fc1764 WTF is this 2024-07-13 17:47:39 +08:00
世界
8f991720a6 platform: Fix clash server reload on android 2024-07-13 17:47:39 +08:00
世界
bc6ae998c0 platform: Add log update interval 2024-07-13 17:47:38 +08:00
世界
27f0705968 platform: Prepare connections list 2024-07-13 17:47:38 +08:00
世界
2feb4b71e9 Drop support for go1.18 and go1.19 2024-07-13 17:47:38 +08:00
世界
d41dad9a22 Introduce DTLS sniffer 2024-07-13 17:47:38 +08:00
iosmanthus
4c0ddf250f 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-07-13 17:47:38 +08:00
世界
98ff897f35 Fix reset outbounds 2024-07-13 17:46:22 +08:00
ayooh
6144c8e340 Fix default end value of the rangeItem 2024-07-13 17:45:32 +08:00
HystericalDragon
c8caac9f67 Fix v2rayquic default NextProtos (#1934) 2024-07-11 23:34:09 +08:00
printfer
81e9eda357 documentation: Fix typo 2024-07-07 16:09:41 +08:00
世界
7cba3da108 shadowsocks: Fix packet process for AEAD multi-user server 2024-07-05 17:09:09 +08:00
世界
82d06b43e7 vless: Fix missing deadline interfaces 2024-07-05 17:08:10 +08:00
世界
a7ac91f573 Move legacy vless implemetation to library 2024-07-03 20:17:32 +08:00
世界
0540a95a43 Fix v2ray-plugin 2024-07-02 14:08:17 +08:00
世界
94707dfcdd Add name to errors from v2ray HTTP transports 2024-07-02 14:08:17 +08:00
世界
8a17043502 Fix command output for rule-set match 2024-06-26 12:26:35 +08:00
世界
b0aaa86806 Minor fixes 2024-06-25 13:14:45 +08:00
世界
8a2d3fbb28 Update quic-go to v0.45.1 & Filter HTTPS ipv4/6hint 2024-06-24 11:07:32 +08:00
renovate[bot]
4652019608 [dependencies] Update docker/build-push-action action to v6 2024-06-24 10:24:13 +08:00
世界
06fa5abf63 Fix non-IP queries accepted by address filter rules 2024-06-24 10:13:16 +08:00
世界
996fbbf0c3 docs: Switch to venv 2024-06-24 10:10:49 +08:00
107 changed files with 1490 additions and 2342 deletions

View File

@@ -49,7 +49,7 @@ jobs:
with:
images: ghcr.io/sagernet/sing-box
- name: Build and release Docker images
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
context: .

View File

@@ -199,7 +199,7 @@ publish_docs:
docs_install:
python -m venv venv
source ./venv/bin/active && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
clean:
rm -rf bin dist sing-box

View File

@@ -4,14 +4,13 @@ import (
"bytes"
"context"
"encoding/binary"
"io"
"net"
"time"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-dns"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
)
type ClashServer interface {
@@ -56,16 +55,15 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
if err != nil {
return nil, err
}
err = rw.WriteUVariant(&buffer, uint64(len(s.Content)))
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
if err != nil {
return nil, err
}
buffer.Write(s.Content)
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
if err != nil {
return nil, err
}
err = rw.WriteVString(&buffer, s.LastEtag)
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
if err != nil {
return nil, err
}
@@ -79,12 +77,7 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
if err != nil {
return err
}
contentLen, err := rw.ReadUVariant(reader)
if err != nil {
return err
}
s.Content = make([]byte, contentLen)
_, err = io.ReadFull(reader, s.Content)
err = varbin.Read(reader, binary.BigEndian, &s.Content)
if err != nil {
return err
}
@@ -94,7 +87,7 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
return err
}
s.LastUpdated = time.Unix(lastUpdated, 0)
s.LastEtag, err = rw.ReadVString(reader)
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
if err != nil {
return err
}

View File

@@ -51,7 +51,9 @@ type InboundContext struct {
// rule cache
IPCIDRMatchSource bool
IPCIDRMatchSource bool
IPCIDRAcceptEmpty bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
@@ -62,6 +64,7 @@ type InboundContext struct {
func (c *InboundContext) ResetRuleCache() {
c.IPCIDRMatchSource = false
c.IPCIDRAcceptEmpty = false
c.SourceAddressMatch = false
c.SourcePortMatch = false
c.DestinationAddressMatch = false

View File

@@ -22,4 +22,5 @@ type V2RayServerTransportHandler interface {
type V2RayClientTransport interface {
DialContext(ctx context.Context) (net.Conn, error)
Close() error
}

6
box.go
View File

@@ -204,7 +204,7 @@ func (s *Box) PreStart() error {
defer func() {
v := recover()
if v != nil {
log.Error(E.Cause(err, "origin error"))
println(err.Error())
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
}
@@ -223,9 +223,9 @@ func (s *Box) Start() error {
defer func() {
v := recover()
if v != nil {
log.Error(E.Cause(err, "origin error"))
println(err.Error())
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
println("panic on early start: " + fmt.Sprint(v))
}
}()
s.Close()

View File

@@ -45,7 +45,9 @@ func (s *Box) startOutbounds() error {
}
started[outboundTag] = true
canContinue = true
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start()
monitor.Finish()

View File

@@ -93,7 +93,7 @@ func buildAndroid() {
const name = "libbox.aar"
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
if rw.FileExists(copyPath) {
if rw.IsDir(copyPath) {
copyPath, _ = filepath.Abs(copyPath)
err = rw.CopyFile(name, filepath.Join(copyPath, name))
if err != nil {
@@ -134,7 +134,7 @@ func buildiOS() {
}
copyPath := filepath.Join("..", "sing-box-for-apple")
if rw.FileExists(copyPath) {
if rw.IsDir(copyPath) {
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)
os.RemoveAll(targetDir)

View File

@@ -30,7 +30,7 @@ func FindSDK() {
}
for _, path := range searchPath {
path = os.ExpandEnv(path)
if rw.FileExists(filepath.Join(path, "licenses", "android-sdk-license")) {
if rw.IsFile(filepath.Join(path, "licenses", "android-sdk-license")) {
androidSDKPath = path
break
}
@@ -60,7 +60,7 @@ func FindSDK() {
func findNDK() bool {
const fixedVersion = "26.2.11394342"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.FileExists(filepath.Join(fixedPath, versionFile)) {
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath
return true
}
@@ -86,7 +86,7 @@ func findNDK() bool {
})
for _, versionName := range versionNames {
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
if rw.FileExists(filepath.Join(androidSDKPath, versionFile)) {
if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) {
androidNDKPath = currentNDKPath
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
return true
@@ -100,11 +100,11 @@ var GoBinPath string
func FindMobile() {
goBin := filepath.Join(build.Default.GOPATH, "bin")
if runtime.GOOS == "windows" {
if !rw.FileExists(filepath.Join(goBin, "gobind.exe")) {
if !rw.IsFile(filepath.Join(goBin, "gobind.exe")) {
log.Fatal("missing gomobile installation")
}
} else {
if !rw.FileExists(filepath.Join(goBin, "gobind")) {
if !rw.IsFile(filepath.Join(goBin, "gobind")) {
log.Fatal("missing gomobile installation")
}
}

View File

@@ -54,7 +54,11 @@ func merge(outputPath string) error {
return nil
}
}
err = rw.WriteFile(outputPath, buffer.Bytes())
err = rw.MkdirParent(outputPath)
if err != nil {
return err
}
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
if err != nil {
return err
}

View File

@@ -6,7 +6,7 @@ import (
var commandRuleSet = &cobra.Command{
Use: "rule-set",
Short: "Manage rule sets",
Short: "Manage rule-sets",
}
func init() {

View File

@@ -55,10 +55,10 @@ func compileRuleSet(sourcePath string) error {
if err != nil {
return err
}
ruleSet, err := plainRuleSet.Upgrade()
if err != nil {
return err
}
ruleSet := plainRuleSet.Upgrade()
var outputPath string
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".json") {

View File

@@ -0,0 +1,83 @@
package main
import (
"io"
"os"
"strings"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var flagRuleSetDecompileOutput string
const flagRuleSetDecompileDefaultOutput = "<file_name>.json"
var commandRuleSetDecompile = &cobra.Command{
Use: "decompile [binary-path]",
Short: "Decompile rule-set binary to json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := decompileRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSet.AddCommand(commandRuleSetDecompile)
commandRuleSetDecompile.Flags().StringVarP(&flagRuleSetDecompileOutput, "output", "o", flagRuleSetDecompileDefaultOutput, "Output file")
}
func decompileRuleSet(sourcePath string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return err
}
}
plainRuleSet, err := srs.Read(reader, true)
if err != nil {
return err
}
ruleSet := option.PlainRuleSetCompat{
Version: C.RuleSetVersion1,
Options: plainRuleSet,
}
var outputPath string
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".srs") {
outputPath = sourcePath[:len(sourcePath)-4] + ".json"
} else {
outputPath = sourcePath + ".json"
}
} else {
outputPath = flagRuleSetDecompileOutput
}
outputFile, err := os.Create(outputPath)
if err != nil {
return err
}
encoder := json.NewEncoder(outputFile)
encoder.SetIndent("", " ")
err = encoder.Encode(ruleSet)
if err != nil {
outputFile.Close()
os.Remove(outputPath)
return err
}
outputFile.Close()
return nil
}

View File

@@ -12,7 +12,9 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
M "github.com/sagernet/sing/common/metadata"
"github.com/spf13/cobra"
)
@@ -20,8 +22,8 @@ import (
var flagRuleSetMatchFormat string
var commandRuleSetMatch = &cobra.Command{
Use: "match <rule-set path> <domain>",
Short: "Check if a domain matches the rule set",
Use: "match <rule-set path> <IP address/domain>",
Short: "Check if an IP address or a domain matches the rule-set",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
err := ruleSetMatch(args[0], args[1])
@@ -61,14 +63,24 @@ func ruleSetMatch(sourcePath string, domain string) error {
if err != nil {
return err
}
plainRuleSet = compat.Upgrade()
plainRuleSet, err = compat.Upgrade()
if err != nil {
return err
}
case C.RuleSetFormatBinary:
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
if err != nil {
return err
}
default:
return E.New("unknown rule set format: ", flagRuleSetMatchFormat)
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
}
ipAddress := M.ParseAddr(domain)
var metadata adapter.InboundContext
if ipAddress.IsValid() {
metadata.Destination = M.SocksaddrFrom(ipAddress, 0)
} else {
metadata.Domain = domain
}
for i, ruleOptions := range plainRuleSet.Rules {
var currentRule adapter.HeadlessRule
@@ -76,10 +88,8 @@ func ruleSetMatch(sourcePath string, domain string) error {
if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}
if currentRule.Match(&adapter.InboundContext{
Domain: domain,
}) {
println("match rules.[", i, "]: "+currentRule.String())
if currentRule.Match(&metadata) {
println(F.ToString("match rules.[", i, "]: ", currentRule))
}
}
return nil

View File

@@ -109,7 +109,7 @@ func readConfigAndMerge() (option.Options, error) {
}
var mergedMessage json.RawMessage
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage)
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
if err != nil {
return option.Options{}, E.Cause(err, "merge config at ", options.path)
}

View File

@@ -1,11 +1,15 @@
package geosite
import (
"bufio"
"encoding/binary"
"io"
"os"
"sync/atomic"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
)
type Reader struct {
@@ -34,45 +38,36 @@ func Open(path string) (*Reader, []string, error) {
return reader, codes, nil
}
type geositeMetadata struct {
Code string
Index uint64
Length uint64
}
func (r *Reader) readMetadata() error {
version, err := rw.ReadByte(r.reader)
reader := bufio.NewReader(r.reader)
version, err := reader.ReadByte()
if err != nil {
return err
}
if version != 0 {
return E.New("unknown version")
}
entryLength, err := rw.ReadUVariant(r.reader)
metadataEntries, err := varbin.ReadValue[[]geositeMetadata](reader, binary.BigEndian)
if err != nil {
return err
}
keys := make([]string, entryLength)
domainIndex := make(map[string]int)
domainLength := make(map[string]int)
for i := 0; i < int(entryLength); i++ {
var (
code string
codeIndex uint64
codeLength uint64
)
code, err = rw.ReadVString(r.reader)
if err != nil {
return err
}
keys[i] = code
codeIndex, err = rw.ReadUVariant(r.reader)
if err != nil {
return err
}
codeLength, err = rw.ReadUVariant(r.reader)
if err != nil {
return err
}
domainIndex[code] = int(codeIndex)
domainLength[code] = int(codeLength)
for _, entry := range metadataEntries {
domainIndex[entry.Code] = int(entry.Index)
domainLength[entry.Code] = int(entry.Length)
}
r.domainIndex = domainIndex
r.domainLength = domainLength
if reader.Buffered() > 0 {
return common.Error(r.reader.Seek(int64(-reader.Buffered()), io.SeekCurrent))
}
return nil
}
@@ -85,27 +80,28 @@ func (r *Reader) Read(code string) ([]Item, error) {
if err != nil {
return nil, err
}
counter := &rw.ReadCounter{Reader: r.reader}
domain := make([]Item, r.domainLength[code])
for i := range domain {
var (
item Item
err error
)
item.Type, err = rw.ReadByte(counter)
if err != nil {
return nil, err
}
item.Value, err = rw.ReadVString(counter)
if err != nil {
return nil, err
}
domain[i] = item
counter := &readCounter{Reader: r.reader}
domain, err := varbin.ReadValue[[]Item](bufio.NewReader(counter), binary.BigEndian)
if err != nil {
return nil, err
}
_, err = r.reader.Seek(int64(-index)-counter.Count(), io.SeekCurrent)
_, err = r.reader.Seek(int64(-index)-counter.count, io.SeekCurrent)
return domain, err
}
func (r *Reader) Upstream() any {
return r.reader
}
type readCounter struct {
io.Reader
count int64
}
func (r *readCounter) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
if n > 0 {
atomic.AddInt64(&r.count, int64(n))
}
return
}

View File

@@ -2,13 +2,14 @@ package geosite
import (
"bytes"
"io"
"encoding/binary"
"sort"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/varbin"
)
func Write(writer io.Writer, domains map[string][]Item) error {
func Write(writer varbin.Writer, domains map[string][]Item) error {
keys := make([]string, 0, len(domains))
for code := range domains {
keys = append(keys, code)
@@ -19,40 +20,28 @@ func Write(writer io.Writer, domains map[string][]Item) error {
index := make(map[string]int)
for _, code := range keys {
index[code] = content.Len()
for _, domain := range domains[code] {
content.WriteByte(domain.Type)
err := rw.WriteVString(content, domain.Value)
if err != nil {
return err
}
err := varbin.Write(content, binary.BigEndian, domains[code])
if err != nil {
return err
}
}
err := rw.WriteByte(writer, 0)
err := writer.WriteByte(0)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(keys)))
err = varbin.Write(writer, binary.BigEndian, common.Map(keys, func(it string) *geositeMetadata {
return &geositeMetadata{
Code: it,
Index: uint64(index[it]),
Length: uint64(len(domains[it])),
}
}))
if err != nil {
return err
}
for _, code := range keys {
err = rw.WriteVString(writer, code)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(index[code]))
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(domains[code])))
if err != nil {
return err
}
}
_, err = writer.Write(content.Bytes())
if err != nil {
return err

31
common/sniff/dtls.go Normal file
View File

@@ -0,0 +1,31 @@
package sniff
import (
"context"
"os"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
)
func DTLSRecord(ctx context.Context, packet []byte) (*adapter.InboundContext, error) {
const fixedHeaderSize = 13
if len(packet) < fixedHeaderSize {
return nil, os.ErrInvalid
}
contentType := packet[0]
switch contentType {
case 20, 21, 22, 23, 25:
default:
return nil, os.ErrInvalid
}
versionMajor := packet[1]
if versionMajor != 0xfe {
return nil, os.ErrInvalid
}
versionMinor := packet[2]
if versionMinor != 0xff && versionMinor != 0xfd {
return nil, os.ErrInvalid
}
return &adapter.InboundContext{Protocol: C.ProtocolDTLS}, nil
}

30
common/sniff/dtls_test.go Normal file
View File

@@ -0,0 +1,30 @@
package sniff_test
import (
"context"
"encoding/hex"
"testing"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/stretchr/testify/require"
)
func TestSniffDTLSClientHello(t *testing.T) {
t.Parallel()
packet, err := hex.DecodeString("16fefd0000000000000000007e010000720000000000000072fefd668a43523798e064bd806d0c87660de9c611a59bbdfc3892c4e072d94f2cafc40000000cc02bc02fc00ac014c02cc0300100003c000d0010000e0403050306030401050106010807ff01000100000a00080006001d00170018000b00020100000e000900060008000700010000170000")
require.NoError(t, err)
metadata, err := sniff.DTLSRecord(context.Background(), packet)
require.NoError(t, err)
require.Equal(t, metadata.Protocol, C.ProtocolDTLS)
}
func TestSniffDTLSClientApplicationData(t *testing.T) {
t.Parallel()
packet, err := hex.DecodeString("17fefd000100000000000100440001000000000001a4f682b77ecadd10f3f3a2f78d90566212366ff8209fd77314f5a49352f9bb9bd12f4daba0b4736ae29e46b9714d3b424b3e6d0234736619b5aa0d3f")
require.NoError(t, err)
metadata, err := sniff.DTLSRecord(context.Background(), packet)
require.NoError(t, err)
require.Equal(t, metadata.Protocol, C.ProtocolDTLS)
}

View File

@@ -1,6 +1,7 @@
package srs
import (
"bufio"
"compress/zlib"
"encoding/binary"
"io"
@@ -11,7 +12,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/domain"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
"go4.org/netipx"
)
@@ -38,14 +39,14 @@ const (
ruleItemFinal uint8 = 0xFF
)
func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err error) {
func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) {
var magicBytes [3]byte
_, err = io.ReadFull(reader, magicBytes[:])
if err != nil {
return
}
if magicBytes != MagicBytes {
err = E.New("invalid sing-box rule set file")
err = E.New("invalid sing-box rule-set file")
return
}
var version uint8
@@ -60,13 +61,14 @@ func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err err
if err != nil {
return
}
length, err := rw.ReadUVariant(zReader)
bReader := bufio.NewReader(zReader)
length, err := binary.ReadUvarint(bReader)
if err != nil {
return
}
ruleSet.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ {
ruleSet.Rules[i], err = readRule(zReader, recovery)
ruleSet.Rules[i], err = readRule(bReader, recover)
if err != nil {
err = E.Cause(err, "read rule[", i, "]")
return
@@ -88,20 +90,25 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet) error {
if err != nil {
return err
}
err = rw.WriteUVariant(zWriter, uint64(len(ruleSet.Rules)))
bWriter := bufio.NewWriter(zWriter)
_, err = varbin.WriteUvarint(bWriter, uint64(len(ruleSet.Rules)))
if err != nil {
return err
}
for _, rule := range ruleSet.Rules {
err = writeRule(zWriter, rule)
err = writeRule(bWriter, rule)
if err != nil {
return err
}
}
err = bWriter.Flush()
if err != nil {
return err
}
return zWriter.Close()
}
func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err error) {
func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err error) {
var ruleType uint8
err = binary.Read(reader, binary.BigEndian, &ruleType)
if err != nil {
@@ -110,17 +117,17 @@ func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err er
switch ruleType {
case 0:
rule.Type = C.RuleTypeDefault
rule.DefaultOptions, err = readDefaultRule(reader, recovery)
rule.DefaultOptions, err = readDefaultRule(reader, recover)
case 1:
rule.Type = C.RuleTypeLogical
rule.LogicalOptions, err = readLogicalRule(reader, recovery)
rule.LogicalOptions, err = readLogicalRule(reader, recover)
default:
err = E.New("unknown rule type: ", ruleType)
}
return
}
func writeRule(writer io.Writer, rule option.HeadlessRule) error {
func writeRule(writer varbin.Writer, rule option.HeadlessRule) error {
switch rule.Type {
case C.RuleTypeDefault:
return writeDefaultRule(writer, rule.DefaultOptions)
@@ -131,7 +138,7 @@ func writeRule(writer io.Writer, rule option.HeadlessRule) error {
}
}
func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadlessRule, err error) {
func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHeadlessRule, err error) {
var lastItemType uint8
for {
var itemType uint8
@@ -158,6 +165,9 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle
return
}
rule.DomainMatcher = matcher
if recover {
rule.Domain, rule.DomainSuffix = matcher.Dump()
}
case ruleItemDomainKeyword:
rule.DomainKeyword, err = readRuleItemString(reader)
case ruleItemDomainRegex:
@@ -167,7 +177,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle
if err != nil {
return
}
if recovery {
if recover {
rule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), netip.Prefix.String)
}
case ruleItemIPCIDR:
@@ -175,7 +185,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle
if err != nil {
return
}
if recovery {
if recover {
rule.IPCIDR = common.Map(rule.IPSet.Prefixes(), netip.Prefix.String)
}
case ruleItemSourcePort:
@@ -209,7 +219,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle
}
}
func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error {
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule) error {
err := binary.Write(writer, binary.BigEndian, uint8(0))
if err != nil {
return err
@@ -327,73 +337,31 @@ func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error {
return nil
}
func readRuleItemString(reader io.Reader) ([]string, error) {
length, err := rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
value := make([]string, length)
for i := uint64(0); i < length; i++ {
value[i], err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
}
return value, nil
func readRuleItemString(reader varbin.Reader) ([]string, error) {
return varbin.ReadValue[[]string](reader, binary.BigEndian)
}
func writeRuleItemString(writer io.Writer, itemType uint8, value []string) error {
err := binary.Write(writer, binary.BigEndian, itemType)
func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {
err := writer.WriteByte(itemType)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(value)))
return varbin.Write(writer, binary.BigEndian, value)
}
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
}
func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {
err := writer.WriteByte(itemType)
if err != nil {
return err
}
for _, item := range value {
err = rw.WriteVString(writer, item)
if err != nil {
return err
}
}
return nil
return varbin.Write(writer, binary.BigEndian, value)
}
func readRuleItemUint16(reader io.Reader) ([]uint16, error) {
length, err := rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
value := make([]uint16, length)
for i := uint64(0); i < length; i++ {
err = binary.Read(reader, binary.BigEndian, &value[i])
if err != nil {
return nil, err
}
}
return value, nil
}
func writeRuleItemUint16(writer io.Writer, itemType uint8, value []uint16) error {
err := binary.Write(writer, binary.BigEndian, itemType)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(value)))
if err != nil {
return err
}
for _, item := range value {
err = binary.Write(writer, binary.BigEndian, item)
if err != nil {
return err
}
}
return nil
}
func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error {
func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {
var builder netipx.IPSetBuilder
for i, prefixString := range value {
prefix, err := netip.ParsePrefix(prefixString)
@@ -419,9 +387,8 @@ func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error {
return writeIPSet(writer, ipSet)
}
func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {
var mode uint8
err = binary.Read(reader, binary.BigEndian, &mode)
func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {
mode, err := reader.ReadByte()
if err != nil {
return
}
@@ -434,7 +401,7 @@ func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.Logica
err = E.New("unknown logical mode: ", mode)
return
}
length, err := rw.ReadUVariant(reader)
length, err := binary.ReadUvarint(reader)
if err != nil {
return
}
@@ -453,7 +420,7 @@ func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.Logica
return
}
func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) error {
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule) error {
err := binary.Write(writer, binary.BigEndian, uint8(1))
if err != nil {
return err
@@ -469,7 +436,7 @@ func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(logicalRule.Rules)))
_, err = varbin.WriteUvarint(writer, uint64(len(logicalRule.Rules)))
if err != nil {
return err
}

View File

@@ -2,11 +2,13 @@ package srs
import (
"encoding/binary"
"io"
"net/netip"
"os"
"unsafe"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/varbin"
"go4.org/netipx"
)
@@ -20,94 +22,57 @@ type myIPRange struct {
to netip.Addr
}
func readIPSet(reader io.Reader) (*netipx.IPSet, error) {
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)
type myIPRangeData struct {
From []byte
To []byte
}
func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
version, err := reader.ReadByte()
if err != nil {
return nil, err
}
if version != 1 {
return nil, os.ErrInvalid
}
// WTF why using uint64 here
var length uint64
err = binary.Read(reader, binary.BigEndian, &length)
if err != nil {
return nil, err
}
mySet := &myIPSet{
rr: make([]myIPRange, length),
ranges := make([]myIPRangeData, length)
err = varbin.Read(reader, binary.BigEndian, &ranges)
if err != nil {
return nil, err
}
for i := uint64(0); i < length; i++ {
var (
fromLen uint64
toLen uint64
fromAddr netip.Addr
toAddr netip.Addr
)
fromLen, err = rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
fromBytes := make([]byte, fromLen)
_, err = io.ReadFull(reader, fromBytes)
if err != nil {
return nil, err
}
err = fromAddr.UnmarshalBinary(fromBytes)
if err != nil {
return nil, err
}
toLen, err = rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
toBytes := make([]byte, toLen)
_, err = io.ReadFull(reader, toBytes)
if err != nil {
return nil, err
}
err = toAddr.UnmarshalBinary(toBytes)
if err != nil {
return nil, err
}
mySet.rr[i] = myIPRange{fromAddr, toAddr}
mySet := &myIPSet{
rr: make([]myIPRange, len(ranges)),
}
for i, rangeData := range ranges {
mySet.rr[i].from = M.AddrFromIP(rangeData.From)
mySet.rr[i].to = M.AddrFromIP(rangeData.To)
}
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
}
func writeIPSet(writer io.Writer, set *netipx.IPSet) error {
err := binary.Write(writer, binary.BigEndian, uint8(1))
func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error {
err := writer.WriteByte(1)
if err != nil {
return err
}
mySet := (*myIPSet)(unsafe.Pointer(set))
err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))
dataList := common.Map((*myIPSet)(unsafe.Pointer(set)).rr, func(rr myIPRange) myIPRangeData {
return myIPRangeData{
From: rr.from.AsSlice(),
To: rr.to.AsSlice(),
}
})
err = binary.Write(writer, binary.BigEndian, uint64(len(dataList)))
if err != nil {
return err
}
for _, rr := range mySet.rr {
var (
fromBinary []byte
toBinary []byte
)
fromBinary, err = rr.from.MarshalBinary()
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(fromBinary)))
if err != nil {
return err
}
_, err = writer.Write(fromBinary)
if err != nil {
return err
}
toBinary, err = rr.to.MarshalBinary()
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(toBinary)))
if err != nil {
return err
}
_, err = writer.Write(toBinary)
for _, data := range dataList {
err = varbin.Write(writer, binary.BigEndian, data)
if err != nil {
return err
}

View File

@@ -11,12 +11,11 @@ import (
"strings"
cftls "github.com/sagernet/cloudflare-tls"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
"github.com/fsnotify/fsnotify"
)
type echServerConfig struct {
@@ -26,9 +25,8 @@ type echServerConfig struct {
key []byte
certificatePath string
keyPath string
watcher *fsnotify.Watcher
echKeyPath string
echWatcher *fsnotify.Watcher
watcher *fswatch.Watcher
}
func (c *echServerConfig) ServerName() string {
@@ -66,146 +64,84 @@ func (c *echServerConfig) Clone() Config {
}
func (c *echServerConfig) Start() error {
if c.certificatePath != "" && c.keyPath != "" {
err := c.startWatcher()
if err != nil {
c.logger.Warn("create fsnotify watcher: ", err)
}
}
if c.echKeyPath != "" {
err := c.startECHWatcher()
if err != nil {
c.logger.Warn("create fsnotify watcher: ", err)
}
err := c.startWatcher()
if err != nil {
c.logger.Warn("create credentials watcher: ", err)
}
return nil
}
func (c *echServerConfig) startWatcher() error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
var watchPath []string
if c.certificatePath != "" {
err = watcher.Add(c.certificatePath)
if err != nil {
return err
}
watchPath = append(watchPath, c.certificatePath)
}
if c.keyPath != "" {
err = watcher.Add(c.keyPath)
if err != nil {
return err
}
watchPath = append(watchPath, c.keyPath)
}
if c.echKeyPath != "" {
watchPath = append(watchPath, c.echKeyPath)
}
if len(watchPath) == 0 {
return nil
}
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: watchPath,
Callback: func(path string) {
err := c.credentialsUpdated(path)
if err != nil {
c.logger.Error(E.Cause(err, "reload credentials from ", path))
}
},
})
if err != nil {
return err
}
c.watcher = watcher
go c.loopUpdate()
return nil
}
func (c *echServerConfig) loopUpdate() {
for {
select {
case event, ok := <-c.watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write != fsnotify.Write {
continue
}
err := c.reloadKeyPair()
func (c *echServerConfig) credentialsUpdated(path string) error {
if path == c.certificatePath || path == c.keyPath {
if path == c.certificatePath {
certificate, err := os.ReadFile(c.certificatePath)
if err != nil {
c.logger.Error(E.Cause(err, "reload TLS key pair"))
return err
}
case err, ok := <-c.watcher.Errors:
if !ok {
return
}
c.logger.Error(E.Cause(err, "fsnotify error"))
}
}
}
func (c *echServerConfig) reloadKeyPair() error {
if c.certificatePath != "" {
certificate, err := os.ReadFile(c.certificatePath)
if err != nil {
return E.Cause(err, "reload certificate from ", c.certificatePath)
}
c.certificate = certificate
}
if c.keyPath != "" {
key, err := os.ReadFile(c.keyPath)
if err != nil {
return E.Cause(err, "reload key from ", c.keyPath)
}
c.key = key
}
keyPair, err := cftls.X509KeyPair(c.certificate, c.key)
if err != nil {
return E.Cause(err, "reload key pair")
}
c.config.Certificates = []cftls.Certificate{keyPair}
c.logger.Info("reloaded TLS certificate")
return nil
}
func (c *echServerConfig) startECHWatcher() error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
err = watcher.Add(c.echKeyPath)
if err != nil {
return err
}
c.echWatcher = watcher
go c.loopECHUpdate()
return nil
}
func (c *echServerConfig) loopECHUpdate() {
for {
select {
case event, ok := <-c.echWatcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write != fsnotify.Write {
continue
}
err := c.reloadECHKey()
c.certificate = certificate
} else {
key, err := os.ReadFile(c.keyPath)
if err != nil {
c.logger.Error(E.Cause(err, "reload ECH key"))
return err
}
case err, ok := <-c.echWatcher.Errors:
if !ok {
return
}
c.logger.Error(E.Cause(err, "fsnotify error"))
c.key = key
}
keyPair, err := cftls.X509KeyPair(c.certificate, c.key)
if err != nil {
return E.Cause(err, "parse key pair")
}
c.config.Certificates = []cftls.Certificate{keyPair}
c.logger.Info("reloaded TLS certificate")
} else {
echKeyContent, err := os.ReadFile(c.echKeyPath)
if err != nil {
return err
}
block, rest := pem.Decode(echKeyContent)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return E.New("invalid ECH keys pem")
}
echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes)
if err != nil {
return E.Cause(err, "parse ECH keys")
}
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys)
if err != nil {
return E.Cause(err, "create ECH key set")
}
c.config.ServerECHProvider = echKeySet
c.logger.Info("reloaded ECH keys")
}
}
func (c *echServerConfig) reloadECHKey() error {
echKeyContent, err := os.ReadFile(c.echKeyPath)
if err != nil {
return err
}
block, rest := pem.Decode(echKeyContent)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return E.New("invalid ECH keys pem")
}
echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes)
if err != nil {
return E.Cause(err, "parse ECH keys")
}
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys)
if err != nil {
return E.Cause(err, "create ECH key set")
}
c.config.ServerECHProvider = echKeySet
c.logger.Info("reloaded ECH keys")
return nil
}
@@ -213,12 +149,7 @@ func (c *echServerConfig) Close() error {
var err error
if c.watcher != nil {
err = E.Append(err, c.watcher.Close(), func(err error) error {
return E.Cause(err, "close certificate watcher")
})
}
if c.echWatcher != nil {
err = E.Append(err, c.echWatcher.Close(), func(err error) error {
return E.Cause(err, "close ECH key watcher")
return E.Cause(err, "close credentials watcher")
})
}
return err

View File

@@ -7,14 +7,13 @@ import (
"os"
"strings"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
"github.com/fsnotify/fsnotify"
)
var errInsecureUnused = E.New("tls: insecure unused")
@@ -27,7 +26,7 @@ type STDServerConfig struct {
key []byte
certificatePath string
keyPath string
watcher *fsnotify.Watcher
watcher *fswatch.Watcher
}
func (c *STDServerConfig) ServerName() string {
@@ -88,59 +87,37 @@ func (c *STDServerConfig) Start() error {
}
func (c *STDServerConfig) startWatcher() error {
watcher, err := fsnotify.NewWatcher()
var watchPath []string
if c.certificatePath != "" {
watchPath = append(watchPath, c.certificatePath)
}
if c.keyPath != "" {
watchPath = append(watchPath, c.keyPath)
}
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: watchPath,
Callback: func(path string) {
err := c.certificateUpdated(path)
if err != nil {
c.logger.Error(err)
}
},
})
if err != nil {
return err
}
if c.certificatePath != "" {
err = watcher.Add(c.certificatePath)
if err != nil {
return err
}
}
if c.keyPath != "" {
err = watcher.Add(c.keyPath)
if err != nil {
return err
}
}
c.watcher = watcher
go c.loopUpdate()
return nil
}
func (c *STDServerConfig) loopUpdate() {
for {
select {
case event, ok := <-c.watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write != fsnotify.Write {
continue
}
err := c.reloadKeyPair()
if err != nil {
c.logger.Error(E.Cause(err, "reload TLS key pair"))
}
case err, ok := <-c.watcher.Errors:
if !ok {
return
}
c.logger.Error(E.Cause(err, "fsnotify error"))
}
}
}
func (c *STDServerConfig) reloadKeyPair() error {
if c.certificatePath != "" {
func (c *STDServerConfig) certificateUpdated(path string) error {
if path == c.certificatePath {
certificate, err := os.ReadFile(c.certificatePath)
if err != nil {
return E.Cause(err, "reload certificate from ", c.certificatePath)
}
c.certificate = certificate
}
if c.keyPath != "" {
} else if path == c.keyPath {
key, err := os.ReadFile(c.keyPath)
if err != nil {
return E.Cause(err, "reload key from ", c.keyPath)

View File

@@ -13,14 +13,14 @@ var resourcePaths []string
func FindPath(name string) (string, bool) {
name = os.ExpandEnv(name)
if rw.FileExists(name) {
if rw.IsFile(name) {
return name, true
}
for _, dir := range resourcePaths {
if path := filepath.Join(dir, dirName, name); rw.FileExists(path) {
if path := filepath.Join(dir, dirName, name); rw.IsFile(path) {
return path, true
}
if path := filepath.Join(dir, name); rw.FileExists(path) {
if path := filepath.Join(dir, name); rw.IsFile(path) {
return path, true
}
}

View File

@@ -7,4 +7,5 @@ const (
ProtocolDNS = "dns"
ProtocolSTUN = "stun"
ProtocolBitTorrent = "bittorrent"
ProtocolDTLS = "dtls"
)

View File

@@ -11,6 +11,7 @@ const (
)
const (
RuleSetTypeInline = "inline"
RuleSetTypeLocal = "local"
RuleSetTypeRemote = "remote"
RuleSetVersion1 = 1

View File

@@ -2,10 +2,76 @@
icon: material/alert-decagram
---
#### 1.10.0-alpha.15
#### 1.10.0-alpha.21
* Fixes and improvements
#### 1.10.0-alpha.20
* Add DTLS sniffer
* Fixes and improvements
#### 1.10.0-alpha.19
* Add `rule-set decompile` command
* Add IP address support for `rule-set match` command
* Fixes and improvements
#### 1.10.0-alpha.18
* Add new `inline` rule-set type **1**
* Add auto reload support for local rule-set
* Update fsnotify usages **2**
* Fixes and improvements
**1**:
The new [rule-set] type inline (which also becomes the default type)
allows you to write headless rules directly without creating a rule-set file.
[rule-set]: /configuration/rule-set/
**2**:
sing-box now uses fsnotify correctly and will not cancel watching
if the target file is deleted or recreated via rename (e.g. `mv`).
This affects all path options that support reload, including
`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`.
#### 1.10.0-alpha.17
* Some chaotic changes **1**
* `rule_set_ipcidr_match_source` rule items are renamed **2**
* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **3**
* Update quic-go to v0.45.1
* Fixes and improvements
**1**:
Something may be broken, please actively report problems with this version.
**2**:
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
**3**:
See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty).
#### 1.10.0-alpha.16
* Add custom options for `auto-route` and `auto-redirect` **1**
* Fixes and improvements
**1**:
See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index),
[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index),
[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and
[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark).
#### 1.10.0-alpha.13
* TUN address fields are merged **1**
@@ -420,7 +486,7 @@ See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
Important changes since 1.7:
* Migrate cache file from Clash API to independent options **1**
* Introducing [Rule Set](/configuration/rule-set/) **2**
* Introducing [rule-set](/configuration/rule-set/) **2**
* Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3**
* Allow nested logical rules **4**
* Independent `source_ip_is_private` and `ip_is_private` rules **5**
@@ -440,7 +506,7 @@ See [Cache File](/configuration/experimental/cache-file/) and
**2**:
Rule set is independent collections of rules that can be compiled into binaries to improve performance.
rule-set is independent collections of rules that can be compiled into binaries to improve performance.
Compared to legacy GeoIP and Geosite resources,
it can include more types of rules, load faster,
use less memory, and update automatically.
@@ -448,16 +514,16 @@ use less memory, and update automatically.
See [Route#rule_set](/configuration/route/#rule_set),
[Route Rule](/configuration/route/rule/),
[DNS Rule](/configuration/dns/rule/),
[Rule Set](/configuration/rule-set/),
[rule-set](/configuration/rule-set/),
[Source Format](/configuration/rule-set/source-format/) and
[Headless Rule](/configuration/rule-set/headless-rule/).
For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and
[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets).
For GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and
[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets).
**3**:
New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets.
New commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets.
**4**:
@@ -654,7 +720,7 @@ This change is intended to break incorrect usage and essentially requires no act
**1**:
Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule sets,
Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule-sets,
rather than completely following the AND logic.
#### 1.8.0-alpha.5
@@ -670,7 +736,7 @@ Since GeoIP was deprecated, we made this rule independent, see [Migration](/migr
#### 1.8.0-alpha.1
* Migrate cache file from Clash API to independent options **1**
* Introducing [Rule Set](/configuration/rule-set/) **2**
* Introducing [rule-set](/configuration/rule-set/) **2**
* Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3**
* Allow nested logical rules **4**
@@ -681,7 +747,7 @@ See [Cache File](/configuration/experimental/cache-file/) and
**2**:
Rule set is independent collections of rules that can be compiled into binaries to improve performance.
rule-set is independent collections of rules that can be compiled into binaries to improve performance.
Compared to legacy GeoIP and Geosite resources,
it can include more types of rules, load faster,
use less memory, and update automatically.
@@ -689,16 +755,16 @@ use less memory, and update automatically.
See [Route#rule_set](/configuration/route/#rule_set),
[Route Rule](/configuration/route/rule/),
[DNS Rule](/configuration/dns/rule/),
[Rule Set](/configuration/rule-set/),
[rule-set](/configuration/rule-set/),
[Source Format](/configuration/rule-set/source-format/) and
[Headless Rule](/configuration/rule-set/headless-rule/).
For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and
[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets).
For GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and
[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets).
**3**:
New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets.
New commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets.
**4**:

View File

@@ -2,6 +2,12 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.10.0"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [geoip](#geoip)
@@ -117,7 +123,10 @@ icon: material/new-box
"geoip-cn",
"geosite-cn"
],
// deprecated
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false,
"rule_set_ip_cidr_accept_empty": false,
"invert": false,
"outbound": [
"direct"
@@ -157,7 +166,7 @@ icon: material/new-box
(`source_port` || `source_port_range`) &&
`other fields`
Additionally, included rule sets can be considered merged rather than as a single rule sub-item.
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
#### inbound
@@ -303,13 +312,23 @@ Match WiFi BSSID.
!!! question "Since sing-box 1.8.0"
Match [Rule Set](/configuration/route/#rule_set).
Match [rule-set](/configuration/route/#rule_set).
#### rule_set_ipcidr_match_source
!!! question "Since sing-box 1.9.0"
Make `ipcidr` in rule sets match the source IP.
!!! failure "Deprecated in sing-box 1.10.0"
`rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
Make `ip_cidr` rule items in rule-sets match the source IP.
#### rule_set_ip_cidr_match_source
!!! question "Since sing-box 1.10.0"
Make `ip_cidr` rule items in rule-sets match the source IP.
#### invert
@@ -347,11 +366,11 @@ Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
### Address Filter Fields
Only takes effect for IP address requests. When the query results do not match the address filtering rule items, the current rule will be skipped.
Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.
!!! info ""
`ip_cidr` items in included rule sets also takes effect as an address filtering field.
`ip_cidr` items in included rule-sets also takes effect as an address filtering field.
!!! note ""
@@ -375,6 +394,12 @@ Match IP CIDR with query response.
Match private IP with query response.
#### rule_set_ip_cidr_accept_empty
!!! question "Since sing-box 1.10.0"
Make `ip_cidr` rules in rule-sets accept empty query response.
### Logical Fields
#### type

View File

@@ -2,6 +2,12 @@
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [geoip](#geoip)
@@ -117,7 +123,10 @@ icon: material/new-box
"geoip-cn",
"geosite-cn"
],
// 已弃用
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false,
"rule_set_ip_cidr_accept_empty": false,
"invert": false,
"outbound": [
"direct"
@@ -307,7 +316,17 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! question "自 sing-box 1.9.0 起"
使规则集中的 `ipcidr` 规则匹配源 IP。
!!! failure "已在 sing-box 1.10.0 废弃"
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
使规则集中的 `ip_cidr` 规则匹配源 IP。
#### rule_set_ip_cidr_match_source
!!! question "自 sing-box 1.10.0 起"
使规则集中的 `ip_cidr` 规则匹配源 IP。
#### invert
@@ -345,7 +364,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
### 地址筛选字段
仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
!!! info ""
@@ -365,7 +384,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! question "自 sing-box 1.9.0 起"
与查询应匹配 IP CIDR。
与查询应匹配 IP CIDR。
#### ip_is_private
@@ -373,6 +392,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
与查询响应匹配非公开 IP。
#### rule_set_ip_cidr_accept_empty
!!! question "自 sing-box 1.10.0 起"
使规则集中的 `ip_cidr` 规则接受空查询响应。
### 逻辑字段
#### type

View File

@@ -13,7 +13,11 @@ icon: material/new-box
:material-plus: [route_exclude_address](#route_address)
:material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
:material-plus: [iproute2_table_index](#iproute2_table_index)
:material-plus: [iproute2_rule_index](#iproute2_table_index)
:material-plus: [auto_redirect](#auto_redirect)
:material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark)
:material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark)
:material-plus: [route_address_set](#route_address_set)
:material-plus: [route_exclude_address_set](#route_address_set)
@@ -53,8 +57,12 @@ icon: material/new-box
"mtu": 9000,
"gso": false,
"auto_route": true,
"strict_route": true,
"iproute2_table_index": 2022,
"iproute2_rule_index": 9000,
"auto_redirect": false,
"auto_redirect_input_mark": "0x2023",
"auto_redirect_output_mark": "0x2024",
"strict_route": true,
"route_address": [
"0.0.0.0/1",
"128.0.0.0/1",
@@ -129,8 +137,8 @@ icon: material/new-box
"match_domain": []
}
},
... // Listen Fields
...
// Listen Fields
}
```
@@ -180,7 +188,7 @@ The maximum transmission unit.
!!! quote ""
Only supported on Linux.
Only supported on Linux with `auto_route` enabled.
Enable generic segmentation offload.
@@ -196,24 +204,21 @@ Set the default route to the Tun.
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
#### strict_route
#### iproute2_table_index
Enforce strict routing rules when `auto_route` is enabled:
!!! question "Since sing-box 1.10.0"
*In Linux*:
Linux iproute2 table index generated by `auto_route`.
* Let unsupported network unreachable
* Make ICMP traffic route to tun instead of upstream interfaces
* Route all connections to tun
`2022` is used by default.
It prevents IP address leaks and makes DNS hijacking work on Android.
#### iproute2_rule_index
*In Windows*:
!!! question "Since sing-box 1.10.0"
* Add firewall rules to prevent DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
Linux iproute2 rule start index generated by `auto_route`.
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
`9000` is used by default.
#### auto_redirect
@@ -234,6 +239,41 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
#### auto_redirect_input_mark
!!! question "Since sing-box 1.10.0"
Connection input mark used by `route_address_set` and `route_exclude_address_set`.
`0x2023` is used by default.
#### auto_redirect_output_mark
!!! question "Since sing-box 1.10.0"
Connection output mark used by `route_address_set` and `route_exclude_address_set`.
`0x2024` is used by default.
#### strict_route
Enforce strict routing rules when `auto_route` is enabled:
*In Linux*:
* Let unsupported network unreachable
* Make ICMP traffic route to tun instead of upstream interfaces
* Route all connections to tun
It prevents IP address leaks and makes DNS hijacking work on Android.
*In Windows*:
* Add firewall rules to prevent DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
#### route_address
!!! question "Since sing-box 1.10.0"
@@ -244,7 +284,8 @@ Use custom routes instead of default when `auto_route` is enabled.
!!! failure "Deprecated in sing-box 1.10.0"
`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead.
`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
instead.
Use custom routes instead of default when `auto_route` is enabled.
@@ -252,7 +293,8 @@ Use custom routes instead of default when `auto_route` is enabled.
!!! failure "Deprecated in sing-box 1.10.0"
`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead.
`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
instead.
Use custom routes instead of default when `auto_route` is enabled.
@@ -266,7 +308,8 @@ Exclude custom routes when `auto_route` is enabled.
!!! failure "Deprecated in sing-box 1.10.0"
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead.
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
use [route_exclude_address](#route_exclude_address) instead.
Exclude custom routes when `auto_route` is enabled.
@@ -274,7 +317,8 @@ Exclude custom routes when `auto_route` is enabled.
!!! failure "Deprecated in sing-box 1.10.0"
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead.
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
use [route_exclude_address](#route_exclude_address) instead.
Exclude custom routes when `auto_route` is enabled.

View File

@@ -12,8 +12,12 @@ icon: material/new-box
:material-delete-clock: [inet6_route_address](#inet6_route_address)
:material-plus: [route_exclude_address](#route_address)
:material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
:material-plus: [iproute2_table_index](#iproute2_table_index)
:material-plus: [iproute2_rule_index](#iproute2_table_index)
:material-plus: [auto_redirect](#auto_redirect)
:material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark)
:material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark)
:material-plus: [route_address_set](#route_address_set)
:material-plus: [route_exclude_address_set](#route_address_set)
@@ -53,8 +57,12 @@ icon: material/new-box
"mtu": 9000,
"gso": false,
"auto_route": true,
"strict_route": true,
"iproute2_table_index": 2022,
"iproute2_rule_index": 9000,
"auto_redirect": false,
"auto_redirect_input_mark": "0x2023",
"auto_redirect_output_mark": "0x2024",
"strict_route": true,
"route_address": [
"0.0.0.0/1",
"128.0.0.0/1",
@@ -160,7 +168,7 @@ tun 接口的 IPv4 和 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除.
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除
==必填==
@@ -170,7 +178,7 @@ tun 接口的 IPv4 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除.
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除
tun 接口的 IPv6 前缀。
@@ -200,6 +208,56 @@ tun 接口的 IPv6 前缀。
VPN 默认优先于 tun。要使 tun 经过 VPN启用 `route.override_android_vpn`
#### iproute2_table_index
!!! question "自 sing-box 1.10.0 起"
`auto_route` 生成的 iproute2 路由表索引。
默认使用 `2022`
#### iproute2_rule_index
!!! question "自 sing-box 1.10.0 起"
`auto_route` 生成的 iproute2 规则起始索引。
默认使用 `9000`
#### auto_redirect
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Linux且需要 `auto_route` 已启用。
自动配置 iptables 以重定向 TCP 连接。
*在 Android 中*
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
*在 Linux 中*:
带有 `auto_redirect ``auto_route` 现在可以在路由器上按预期工作,**无需干预**。
#### auto_redirect_input_mark
!!! question "自 sing-box 1.10.0 起"
`route_address_set``route_exclude_address_set` 使用的连接输入标记。
默认使用 `0x2023`
#### auto_redirect_output_mark
!!! question "自 sing-box 1.10.0 起"
`route_address_set``route_exclude_address_set` 使用的连接输出标记。
默认使用 `0x2024`
#### strict_route
启用 `auto_route` 时执行严格的路由规则。
@@ -220,24 +278,6 @@ tun 接口的 IPv6 前缀。
它可能会使某些应用程序(如 VirtualBox在某些情况下无法正常工作。
#### auto_redirect
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Linux。
自动配置 iptables 以重定向 TCP 连接。
*在 Android 中*
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
*在 Linux 中*:
带有 `auto_redirect ``auto_route` 现在可以在路由器上按预期工作,**无需干预**。
#### route_address
!!! question "自 sing-box 1.10.0 起"
@@ -248,7 +288,7 @@ tun 接口的 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除.
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除
启用 `auto_route` 时使用自定义路由而不是默认路由。
@@ -256,7 +296,7 @@ tun 接口的 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除.
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除
启用 `auto_route` 时使用自定义路由而不是默认路由。
@@ -270,7 +310,7 @@ tun 接口的 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除.
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除
启用 `auto_route` 时排除自定义路由。
@@ -278,7 +318,7 @@ tun 接口的 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除.
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除
启用 `auto_route` 时排除自定义路由。

View File

@@ -9,6 +9,6 @@
}
```
### 字段
### Fields
No fields.
No fields.

View File

@@ -39,7 +39,7 @@ List of [Route Rule](./rule/)
!!! question "Since sing-box 1.8.0"
List of [Rule Set](/configuration/rule-set/)
List of [rule-set](/configuration/rule-set/)
#### final

View File

@@ -1,3 +1,12 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.10.0"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
!!! quote "Changes in sing-box 1.8.0"
:material-plus: [rule_set](#rule_set)
@@ -105,7 +114,9 @@
"geoip-cn",
"geosite-cn"
],
// deprecated
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false,
"invert": false,
"outbound": "direct"
},
@@ -137,7 +148,7 @@
(`source_port` || `source_port_range`) &&
`other fields`
Additionally, included rule sets can be considered merged rather than as a single rule sub-item.
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
#### inbound
@@ -297,13 +308,23 @@ Match WiFi BSSID.
!!! question "Since sing-box 1.8.0"
Match [Rule Set](/configuration/route/#rule_set).
Match [rule-set](/configuration/route/#rule_set).
#### rule_set_ipcidr_match_source
!!! question "Since sing-box 1.8.0"
Make `ipcidr` in rule sets match the source IP.
!!! failure "Deprecated in sing-box 1.10.0"
`rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
Make `ip_cidr` in rule-sets match the source IP.
#### rule_set_ip_cidr_match_source
!!! question "Since sing-box 1.10.0"
Make `ip_cidr` in rule-sets match the source IP.
#### invert

View File

@@ -1,3 +1,12 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.10.0 中的更改"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
!!! quote "sing-box 1.8.0 中的更改"
:material-plus: [rule_set](#rule_set)
@@ -103,7 +112,9 @@
"geoip-cn",
"geosite-cn"
],
// 已弃用
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false,
"invert": false,
"outbound": "direct"
},
@@ -301,7 +312,17 @@
!!! question "自 sing-box 1.8.0 起"
使规则集中的 `ipcidr` 规则匹配源 IP。
!!! failure "已在 sing-box 1.10.0 废弃"
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
使规则集中的 `ip_cidr` 规则匹配源 IP。
#### rule_set_ip_cidr_match_source
!!! question "自 sing-box 1.10.0 起"
使规则集中的 `ip_cidr` 规则匹配源 IP。
#### invert

View File

@@ -1,12 +1,22 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.10.0"
:material-plus: BitTorrent support
:material-plus: DTLS support
If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed.
#### Supported Protocols
| Network | Protocol | Domain Name |
|:-------:|:-----------:|:-----------:|
| TCP | HTTP | Host |
| TCP | TLS | Server Name |
| UDP | QUIC | Server Name |
| UDP | STUN | / |
| TCP/UDP | DNS | / |
| TCP/UDP | BitTorrent | / |
| Network | Protocol | Domain Name |
|:-------:|:------------:|:-----------:|
| TCP | `http` | Host |
| TCP | `tls` | Server Name |
| UDP | `quic` | Server Name |
| UDP | `stun` | / |
| TCP/UDP | `dns` | / |
| TCP/UDP | `bittorrent` | / |
| UDP | `dtls` | / |

View File

@@ -1,12 +1,22 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: BitTorrent 支持
:material-plus: DTLS 支持
如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。
#### 支持的协议
| 网络 | 协议 | 域名 |
|:-------:|:-----------:|:-----------:|
| TCP | HTTP | Host |
| TCP | TLS | Server Name |
| UDP | QUIC | Server Name |
| UDP | STUN | / |
| TCP/UDP | DNS | / |
| TCP/UDP | BitTorrent | / |
| 网络 | 协议 | 域名 |
|:-------:|:------------:|:-----------:|
| TCP | `http` | Host |
| TCP | `tls` | Server Name |
| UDP | `quic` | Server Name |
| UDP | `stun` | / |
| TCP/UDP | `dns` | / |
| TCP/UDP | `bittorrent` | / |
| UDP | `dtls` | / |

View File

@@ -1,48 +1,56 @@
# Rule Set
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.10.0"
:material-plus: `type: inline`
# rule-set
!!! question "Since sing-box 1.8.0"
### Structure
```json
{
"type": "",
"tag": "",
"format": "",
... // Typed Fields
}
```
=== "Inline"
#### Local Structure
!!! question "Since sing-box 1.10.0"
```json
{
"type": "local",
...
"path": ""
}
```
```json
{
"type": "inline", // optional
"tag": "",
"rules": []
}
```
#### Remote Structure
=== "Local File"
!!! info ""
```json
{
"type": "local",
"tag": "",
"format": "source", // or binary
"path": ""
}
```
Remote rule-set will be cached if `experimental.cache_file.enabled`.
=== "Remote File"
```json
{
"type": "remote",
...,
"url": "",
"download_detour": "",
"update_interval": ""
}
```
!!! info ""
Remote rule-set will be cached if `experimental.cache_file.enabled`.
```json
{
"type": "remote",
"tag": "",
"format": "source", // or binary
"url": "",
"download_detour": "", // optional
"update_interval": "" // optional
}
```
### Fields
@@ -50,19 +58,31 @@
==Required==
Type of Rule Set, `local` or `remote`.
Type of rule-set, `local` or `remote`.
#### tag
==Required==
Tag of Rule Set.
Tag of rule-set.
### Inline Fields
!!! question "Since sing-box 1.10.0"
#### rules
==Required==
List of [Headless Rule](./headless-rule.md/).
### Local or Remote Fields
#### format
==Required==
Format of Rule Set, `source` or `binary`.
Format of rule-set file, `source` or `binary`.
### Local Fields
@@ -70,7 +90,11 @@ Format of Rule Set, `source` or `binary`.
==Required==
File path of Rule Set.
!!! note ""
Will be automatically reloaded if file modified since sing-box 1.10.0.
File path of rule-set.
### Remote Fields
@@ -78,7 +102,7 @@ File path of Rule Set.
==Required==
Download URL of Rule Set.
Download URL of rule-set.
#### download_detour
@@ -88,6 +112,6 @@ Default outbound will be used if empty.
#### update_interval
Update interval of Rule Set.
Update interval of rule-set.
`1d` will be used if empty.

View File

@@ -21,7 +21,7 @@ Use `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` to c
==Required==
Version of Rule Set, must be `1`.
Version of rule-set, must be `1`.
#### rules

View File

@@ -178,6 +178,10 @@ The server certificate line array, in PEM format.
#### certificate_path
!!! note ""
Will be automatically reloaded if file modified.
The path to the server certificate, in PEM format.
#### key
@@ -190,6 +194,10 @@ The server private key line array, in PEM format.
==Server only==
!!! note ""
Will be automatically reloaded if file modified.
The path to the server private key, in PEM format.
## Custom TLS support
@@ -266,6 +274,10 @@ ECH key line array, in PEM format.
==Server only==
!!! note ""
Will be automatically reloaded if file modified.
The path to ECH key, in PEM format.
#### config
@@ -397,8 +409,4 @@ A hexadecimal string with zero to eight digits.
The maximum time difference between the server and the client.
Check disabled if empty.
### Reload
For server configuration, certificate, key and ECH key will be automatically reloaded if modified.
Check disabled if empty.

View File

@@ -176,12 +176,20 @@ TLS 版本值:
#### certificate_path
!!! note ""
文件更改时将自动重新加载。
服务器 PEM 证书路径。
#### key
==仅服务器==
!!! note ""
文件更改时将自动重新加载。
服务器 PEM 私钥行数组。
#### key_path
@@ -258,6 +266,10 @@ ECH PEM 密钥行数组
==仅服务器==
!!! note ""
文件更改时将自动重新加载。
ECH PEM 密钥路径
#### config
@@ -384,7 +396,3 @@ ACME DNS01 验证字段。如果配置,将禁用其他验证方法。
服务器与和客户端之间允许的最大时间差。
默认禁用检查。
### 重载
对于服务器配置,如果修改,证书和密钥将自动重新加载。

View File

@@ -33,7 +33,7 @@ The maxmind GeoIP National Database, as an IP classification database,
is not entirely suitable for traffic bypassing,
and all existing implementations suffer from high memory usage and difficult management.
sing-box 1.8.0 introduces [Rule Set](/configuration/rule-set/), which can completely replace GeoIP,
sing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace GeoIP,
check [Migration](/migration/#migrate-geoip-to-rule-sets).
#### Geosite
@@ -43,7 +43,7 @@ Geosite is deprecated and may be removed in the future.
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.
sing-box 1.8.0 introduces [Rule Set](/configuration/rule-set/), which can completely replace Geosite,
sing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace Geosite,
check [Migration](/migration/#migrate-geosite-to-rule-sets).
## 1.6.0

View File

@@ -128,7 +128,7 @@ which will disrupt the existing `process_path` use cases in Windows.
}
```
### :material-checkbox-intermediate: Migrate GeoIP to rule sets
### :material-checkbox-intermediate: Migrate GeoIP to rule-sets
!!! info "References"
@@ -136,11 +136,11 @@ which will disrupt the existing `process_path` use cases in Windows.
[Route](/configuration/route/) /
[Route Rule](/configuration/route/rule/) /
[DNS Rule](/configuration/dns/rule/) /
[Rule Set](/configuration/rule-set/)
[rule-set](/configuration/rule-set/)
!!! tip
`sing-box geoip` commands can help you convert custom GeoIP into rule sets.
`sing-box geoip` commands can help you convert custom GeoIP into rule-sets.
=== ":material-card-remove: Deprecated"
@@ -207,13 +207,13 @@ which will disrupt the existing `process_path` use cases in Windows.
},
"experimental": {
"cache_file": {
"enabled": true // required to save Rule Set cache
"enabled": true // required to save rule-set cache
}
}
}
```
### :material-checkbox-intermediate: Migrate Geosite to rule sets
### :material-checkbox-intermediate: Migrate Geosite to rule-sets
!!! info "References"
@@ -221,11 +221,11 @@ which will disrupt the existing `process_path` use cases in Windows.
[Route](/configuration/route/) /
[Route Rule](/configuration/route/rule/) /
[DNS Rule](/configuration/dns/rule/) /
[Rule Set](/configuration/rule-set/)
[rule-set](/configuration/rule-set/)
!!! tip
`sing-box geosite` commands can help you convert custom Geosite into rule sets.
`sing-box geosite` commands can help you convert custom Geosite into rule-sets.
=== ":material-card-remove: Deprecated"
@@ -268,7 +268,7 @@ which will disrupt the existing `process_path` use cases in Windows.
},
"experimental": {
"cache_file": {
"enabled": true // required to save Rule Set cache
"enabled": true // required to save rule-set cache
}
}
}

View File

@@ -206,7 +206,7 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold
},
"experimental": {
"cache_file": {
"enabled": true // required to save Rule Set cache
"enabled": true // required to save rule-set cache
}
}
}
@@ -267,7 +267,7 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold
},
"experimental": {
"cache_file": {
"enabled": true // required to save Rule Set cache
"enabled": true // required to save rule-set cache
}
}
}

View File

@@ -7,7 +7,9 @@ import (
"net"
"net/http"
"os"
"runtime"
"strings"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -143,7 +145,18 @@ func (s *Server) PreStart() error {
func (s *Server) Start() error {
if s.externalController {
s.checkAndDownloadExternalUI()
listener, err := net.Listen("tcp", s.httpServer.Addr)
var (
listener net.Listener
err error
)
for i := 0; i < 3; i++ {
listener, err = net.Listen("tcp", s.httpServer.Addr)
if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) {
time.Sleep(100 * time.Millisecond)
continue
}
break
}
if err != nil {
return E.Cause(err, "external controller listen error")
}

View File

@@ -9,7 +9,7 @@ import (
"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/rw"
"github.com/sagernet/sing/common/varbin"
)
func (c *CommandClient) SetClashMode(newMode string) error {
@@ -22,7 +22,7 @@ func (c *CommandClient) SetClashMode(newMode string) error {
if err != nil {
return err
}
err = rw.WriteVString(conn, newMode)
err = varbin.Write(conn, binary.BigEndian, newMode)
if err != nil {
return err
}
@@ -30,7 +30,7 @@ func (c *CommandClient) SetClashMode(newMode string) error {
}
func (s *CommandServer) handleSetClashMode(conn net.Conn) error {
newMode, err := rw.ReadVString(conn)
newMode, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return err
}
@@ -50,7 +50,7 @@ func (c *CommandClient) handleModeConn(conn net.Conn) {
defer conn.Close()
for {
newMode, err := rw.ReadVString(conn)
newMode, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
c.handler.Disconnected(err.Error())
return
@@ -80,7 +80,7 @@ func (s *CommandServer) handleModeConn(conn net.Conn) error {
for {
select {
case <-s.modeUpdate:
err = rw.WriteVString(conn, clashServer.Mode())
err = varbin.Write(conn, binary.BigEndian, clashServer.Mode())
if err != nil {
return err
}
@@ -101,12 +101,12 @@ func readClashModeList(reader io.Reader) (modeList []string, currentMode string,
}
modeList = make([]string, modeListLength)
for i := 0; i < int(modeListLength); i++ {
modeList[i], err = rw.ReadVString(reader)
modeList[i], err = varbin.ReadValue[string](reader, binary.BigEndian)
if err != nil {
return
}
}
currentMode, err = rw.ReadVString(reader)
currentMode, err = varbin.ReadValue[string](reader, binary.BigEndian)
return
}
@@ -118,12 +118,12 @@ func writeClashModeList(writer io.Writer, clashServer adapter.ClashServer) error
}
if len(modeList) > 0 {
for _, mode := range modeList {
err = rw.WriteVString(writer, mode)
err = varbin.Write(writer, binary.BigEndian, mode)
if err != nil {
return err
}
}
err = rw.WriteVString(writer, clashServer.Mode())
err = varbin.Write(writer, binary.BigEndian, clashServer.Mode())
if err != nil {
return err
}

View File

@@ -7,6 +7,7 @@ import (
"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"
)
@@ -18,7 +19,7 @@ func (c *CommandClient) CloseConnection(connId string) error {
}
defer conn.Close()
writer := bufio.NewWriter(conn)
err = binary.WriteData(writer, binary.BigEndian, connId)
err = varbin.Write(writer, binary.BigEndian, connId)
if err != nil {
return err
}
@@ -32,7 +33,7 @@ func (c *CommandClient) CloseConnection(connId string) error {
func (s *CommandServer) handleCloseConnection(conn net.Conn) error {
reader := bufio.NewReader(conn)
var connId string
err := binary.ReadData(reader, binary.BigEndian, &connId)
err := varbin.Read(reader, binary.BigEndian, &connId)
if err != nil {
return E.Cause(err, "read connection id")
}

View File

@@ -12,6 +12,7 @@ import (
"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"
)
@@ -19,13 +20,17 @@ import (
func (c *CommandClient) handleConnectionsConn(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
var connections Connections
var (
rawConnections []Connection
connections Connections
)
for {
err := binary.ReadData(reader, binary.BigEndian, &connections.connections)
err := varbin.Read(reader, binary.BigEndian, &rawConnections)
if err != nil {
c.handler.Disconnected(err.Error())
return
}
connections.input = rawConnections
c.handler.WriteConnections(&connections)
}
}
@@ -69,7 +74,7 @@ func (s *CommandServer) handleConnectionsConn(conn net.Conn) error {
for _, connection := range trafficManager.ClosedConnections() {
outConnections = append(outConnections, newConnection(connections, connection, true))
}
err = binary.WriteData(writer, binary.BigEndian, outConnections)
err = varbin.Write(writer, binary.BigEndian, outConnections)
if err != nil {
return err
}
@@ -92,33 +97,32 @@ const (
)
type Connections struct {
connections []Connection
filteredConnections []Connection
outConnections *[]Connection
input []Connection
filtered []Connection
}
func (c *Connections) FilterState(state int32) {
c.filteredConnections = c.filteredConnections[:0]
c.filtered = c.filtered[:0]
switch state {
case ConnectionStateAll:
c.filteredConnections = append(c.filteredConnections, c.connections...)
c.filtered = append(c.filtered, c.input...)
case ConnectionStateActive:
for _, connection := range c.connections {
for _, connection := range c.input {
if connection.ClosedAt == 0 {
c.filteredConnections = append(c.filteredConnections, connection)
c.filtered = append(c.filtered, connection)
}
}
case ConnectionStateClosed:
for _, connection := range c.connections {
for _, connection := range c.input {
if connection.ClosedAt != 0 {
c.filteredConnections = append(c.filteredConnections, connection)
c.filtered = append(c.filtered, connection)
}
}
}
}
func (c *Connections) SortByDate() {
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
if x.CreatedAt < y.CreatedAt {
return 1
} else if x.CreatedAt > y.CreatedAt {
@@ -130,7 +134,7 @@ func (c *Connections) SortByDate() {
}
func (c *Connections) SortByTraffic() {
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
xTraffic := x.Uplink + x.Downlink
yTraffic := y.Uplink + y.Downlink
if xTraffic < yTraffic {
@@ -144,7 +148,7 @@ func (c *Connections) SortByTraffic() {
}
func (c *Connections) SortByTrafficTotal() {
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
slices.SortStableFunc(c.filtered, func(x, y Connection) int {
xTraffic := x.UplinkTotal + x.DownlinkTotal
yTraffic := y.UplinkTotal + y.DownlinkTotal
if xTraffic < yTraffic {
@@ -158,7 +162,7 @@ func (c *Connections) SortByTrafficTotal() {
}
func (c *Connections) Iterator() ConnectionIterator {
return newPtrIterator(c.filteredConnections)
return newPtrIterator(c.filtered)
}
type Connection struct {

View File

@@ -1,6 +1,7 @@
package libbox
import (
"bufio"
"encoding/binary"
"io"
"net"
@@ -10,7 +11,7 @@ import (
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/outbound"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
"github.com/sagernet/sing/service"
)
@@ -36,19 +37,24 @@ func (s *CommandServer) handleGroupConn(conn net.Conn) error {
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(conn, service)
err = writeGroups(writer, service)
if err != nil {
return err
}
} else {
err := binary.Write(conn, binary.BigEndian, uint16(0))
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()
@@ -68,11 +74,11 @@ type OutboundGroup struct {
Selectable bool
Selected string
IsExpand bool
items []*OutboundGroupItem
ItemList []*OutboundGroupItem
}
func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
return newIterator(g.items)
return newIterator(g.ItemList)
}
type OutboundGroupIterator interface {
@@ -93,73 +99,10 @@ type OutboundGroupItemIterator interface {
}
func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
var groupLength uint16
err := binary.Read(reader, binary.BigEndian, &groupLength)
groups, err := varbin.ReadValue[[]*OutboundGroup](reader, binary.BigEndian)
if err != nil {
return nil, err
}
groups := make([]*OutboundGroup, 0, groupLength)
for i := 0; i < int(groupLength); i++ {
var group OutboundGroup
group.Tag, err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
group.Type, err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
err = binary.Read(reader, binary.BigEndian, &group.Selectable)
if err != nil {
return nil, err
}
group.Selected, err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
err = binary.Read(reader, binary.BigEndian, &group.IsExpand)
if err != nil {
return nil, err
}
var itemLength uint16
err = binary.Read(reader, binary.BigEndian, &itemLength)
if err != nil {
return nil, err
}
group.items = make([]*OutboundGroupItem, itemLength)
for j := 0; j < int(itemLength); j++ {
var item OutboundGroupItem
item.Tag, err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
item.Type, err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
err = binary.Read(reader, binary.BigEndian, &item.URLTestTime)
if err != nil {
return nil, err
}
err = binary.Read(reader, binary.BigEndian, &item.URLTestDelay)
if err != nil {
return nil, err
}
group.items[j] = &item
}
groups = append(groups, &group)
}
return newIterator(groups), nil
}
@@ -199,63 +142,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
item.URLTestTime = history.Time.Unix()
item.URLTestDelay = int32(history.Delay)
}
group.items = append(group.items, &item)
group.ItemList = append(group.ItemList, &item)
}
if len(group.items) < 2 {
if len(group.ItemList) < 2 {
continue
}
groups = append(groups, group)
}
err := binary.Write(writer, binary.BigEndian, uint16(len(groups)))
if err != nil {
return err
}
for _, group := range groups {
err = rw.WriteVString(writer, group.Tag)
if err != nil {
return err
}
err = rw.WriteVString(writer, group.Type)
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, group.Selectable)
if err != nil {
return err
}
err = rw.WriteVString(writer, group.Selected)
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, group.IsExpand)
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, uint16(len(group.items)))
if err != nil {
return err
}
for _, item := range group.items {
err = rw.WriteVString(writer, item.Tag)
if err != nil {
return err
}
err = rw.WriteVString(writer, item.Type)
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, item.URLTestTime)
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, item.URLTestDelay)
if err != nil {
return err
}
}
}
return nil
return varbin.Write(writer, binary.BigEndian, groups)
}
func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
@@ -268,7 +162,7 @@ func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
if err != nil {
return err
}
err = rw.WriteVString(conn, groupTag)
err = varbin.Write(conn, binary.BigEndian, groupTag)
if err != nil {
return err
}
@@ -280,7 +174,7 @@ func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
}
func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
groupTag, err := rw.ReadVString(conn)
groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return err
}

View File

@@ -9,8 +9,19 @@ import (
"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()
@@ -21,26 +32,6 @@ func (s *CommandServer) WriteMessage(message string) {
s.access.Unlock()
}
func writeLog(writer *bufio.Writer, messages []string) error {
err := binary.Write(writer, binary.BigEndian, uint8(0))
if err != nil {
return err
}
err = binary.WriteData(writer, binary.BigEndian, messages)
if err != nil {
return err
}
return writer.Flush()
}
func writeClearLog(writer *bufio.Writer) error {
err := binary.Write(writer, binary.BigEndian, uint8(1))
if err != nil {
return err
}
return writer.Flush()
}
func (s *CommandServer) handleLogConn(conn net.Conn) error {
var (
interval int64
@@ -67,8 +58,24 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
}
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 = writeLog(writer, savedLines)
err = writer.WriteByte(0)
if err != nil {
return err
}
err = varbin.Write(writer, binary.BigEndian, savedLines)
if err != nil {
return err
}
@@ -76,11 +83,15 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
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 = writeClearLog(writer)
err = writer.WriteByte(1)
if err != nil {
return err
}
@@ -99,7 +110,11 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
break loopLogs
}
}
err = writeLog(writer, logLines)
err = writer.WriteByte(0)
if err != nil {
return err
}
err = varbin.Write(writer, binary.BigEndian, logLines)
if err != nil {
return err
}
@@ -110,8 +125,7 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
func (c *CommandClient) handleLogConn(conn net.Conn) {
reader := bufio.NewReader(conn)
for {
var messageType uint8
err := binary.Read(reader, binary.BigEndian, &messageType)
messageType, err := reader.ReadByte()
if err != nil {
c.handler.Disconnected(err.Error())
return
@@ -119,7 +133,7 @@ func (c *CommandClient) handleLogConn(conn net.Conn) {
var messages []string
switch messageType {
case 0:
err = binary.ReadData(reader, binary.BigEndian, &messages)
err = varbin.Read(reader, binary.BigEndian, &messages)
if err != nil {
c.handler.Disconnected(err.Error())
return

View File

@@ -5,7 +5,7 @@ import (
"net"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
)
func (c *CommandClient) ServiceReload() error {
@@ -24,7 +24,7 @@ func (c *CommandClient) ServiceReload() error {
return err
}
if hasError {
errorMessage, err := rw.ReadVString(conn)
errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return err
}
@@ -40,7 +40,7 @@ func (s *CommandServer) handleServiceReload(conn net.Conn) error {
return err
}
if rErr != nil {
return rw.WriteVString(conn, rErr.Error())
return varbin.Write(conn, binary.BigEndian, rErr.Error())
}
return nil
}
@@ -61,7 +61,7 @@ func (c *CommandClient) ServiceClose() error {
return nil
}
if hasError {
errorMessage, err := rw.ReadVString(conn)
errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return nil
}
@@ -78,7 +78,7 @@ func (s *CommandServer) handleServiceClose(conn net.Conn) error {
return err
}
if rErr != nil {
return rw.WriteVString(conn, rErr.Error())
return varbin.Write(conn, binary.BigEndian, rErr.Error())
}
return nil
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/sagernet/sing-box/outbound"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
)
func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error {
@@ -19,11 +19,11 @@ func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) erro
if err != nil {
return err
}
err = rw.WriteVString(conn, groupTag)
err = varbin.Write(conn, binary.BigEndian, groupTag)
if err != nil {
return err
}
err = rw.WriteVString(conn, outboundTag)
err = varbin.Write(conn, binary.BigEndian, outboundTag)
if err != nil {
return err
}
@@ -31,11 +31,11 @@ func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) erro
}
func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
groupTag, err := rw.ReadVString(conn)
groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return err
}
outboundTag, err := rw.ReadVString(conn)
outboundTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return err
}

View File

@@ -66,14 +66,6 @@ func (s *CommandServer) SetService(newService *BoxService) {
s.notifyURLTestUpdate()
}
func (s *CommandServer) ResetLog() {
s.savedLines.Init()
select {
case s.logReset <- struct{}{}:
default:
}
}
func (s *CommandServer) notifyURLTestUpdate() {
select {
case s.urlTestUpdate <- struct{}{}:

View File

@@ -5,7 +5,7 @@ import (
"io"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
)
func readError(reader io.Reader) error {
@@ -15,7 +15,7 @@ func readError(reader io.Reader) error {
return err
}
if hasError {
errorMessage, err := rw.ReadVString(reader)
errorMessage, err := varbin.ReadValue[string](reader, binary.BigEndian)
if err != nil {
return err
}
@@ -30,7 +30,7 @@ func writeError(writer io.Writer, wErr error) error {
return err
}
if wErr != nil {
err = rw.WriteVString(writer, wErr.Error())
err = varbin.Write(writer, binary.BigEndian, wErr.Error())
if err != nil {
return err
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
"github.com/sagernet/sing/service"
)
@@ -25,7 +25,7 @@ func (c *CommandClient) URLTest(groupTag string) error {
if err != nil {
return err
}
err = rw.WriteVString(conn, groupTag)
err = varbin.Write(conn, binary.BigEndian, groupTag)
if err != nil {
return err
}
@@ -33,7 +33,7 @@ func (c *CommandClient) URLTest(groupTag string) error {
}
func (s *CommandServer) handleURLTest(conn net.Conn) error {
groupTag, err := rw.ReadVString(conn)
groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return err
}

View File

@@ -1,13 +1,13 @@
package libbox
import (
"bufio"
"bytes"
"compress/gzip"
"encoding/binary"
"io"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
)
func EncodeChunkedMessage(data []byte) []byte {
@@ -35,13 +35,13 @@ type ErrorMessage struct {
func (e *ErrorMessage) Encode() []byte {
var buffer bytes.Buffer
buffer.WriteByte(MessageTypeError)
rw.WriteVString(&buffer, e.Message)
varbin.Write(&buffer, binary.BigEndian, e.Message)
return buffer.Bytes()
}
func DecodeErrorMessage(data []byte) (*ErrorMessage, error) {
reader := bytes.NewReader(data)
messageType, err := rw.ReadByte(reader)
messageType, err := reader.ReadByte()
if err != nil {
return nil, err
}
@@ -49,7 +49,7 @@ func DecodeErrorMessage(data []byte) (*ErrorMessage, error) {
return nil, E.New("invalid message")
}
var message ErrorMessage
message.Message, err = rw.ReadVString(reader)
message.Message, err = varbin.ReadValue[string](reader, binary.BigEndian)
if err != nil {
return nil, err
}
@@ -87,7 +87,7 @@ func (e *ProfileEncoder) Encode() []byte {
binary.Write(&buffer, binary.BigEndian, uint16(len(e.profiles)))
for _, preview := range e.profiles {
binary.Write(&buffer, binary.BigEndian, preview.ProfileID)
rw.WriteVString(&buffer, preview.Name)
varbin.Write(&buffer, binary.BigEndian, preview.Name)
binary.Write(&buffer, binary.BigEndian, preview.Type)
}
return buffer.Bytes()
@@ -117,7 +117,7 @@ func (d *ProfileDecoder) Decode(data []byte) error {
if err != nil {
return err
}
profile.Name, err = rw.ReadVString(reader)
profile.Name, err = varbin.ReadValue[string](reader, binary.BigEndian)
if err != nil {
return err
}
@@ -147,7 +147,7 @@ func (r *ProfileContentRequest) Encode() []byte {
func DecodeProfileContentRequest(data []byte) (*ProfileContentRequest, error) {
reader := bytes.NewReader(data)
messageType, err := rw.ReadByte(reader)
messageType, err := reader.ReadByte()
if err != nil {
return nil, err
}
@@ -176,12 +176,13 @@ func (c *ProfileContent) Encode() []byte {
buffer := new(bytes.Buffer)
buffer.WriteByte(MessageTypeProfileContent)
buffer.WriteByte(1)
writer := gzip.NewWriter(buffer)
rw.WriteVString(writer, c.Name)
gWriter := gzip.NewWriter(buffer)
writer := bufio.NewWriter(gWriter)
varbin.Write(writer, binary.BigEndian, c.Name)
binary.Write(writer, binary.BigEndian, c.Type)
rw.WriteVString(writer, c.Config)
varbin.Write(writer, binary.BigEndian, c.Config)
if c.Type != ProfileTypeLocal {
rw.WriteVString(writer, c.RemotePath)
varbin.Write(writer, binary.BigEndian, c.RemotePath)
}
if c.Type == ProfileTypeRemote {
binary.Write(writer, binary.BigEndian, c.AutoUpdate)
@@ -189,29 +190,31 @@ func (c *ProfileContent) Encode() []byte {
binary.Write(writer, binary.BigEndian, c.LastUpdated)
}
writer.Flush()
writer.Close()
gWriter.Flush()
gWriter.Close()
return buffer.Bytes()
}
func DecodeProfileContent(data []byte) (*ProfileContent, error) {
var reader io.Reader = bytes.NewReader(data)
messageType, err := rw.ReadByte(reader)
reader := bytes.NewReader(data)
messageType, err := reader.ReadByte()
if err != nil {
return nil, err
}
if messageType != MessageTypeProfileContent {
return nil, E.New("invalid message")
}
version, err := rw.ReadByte(reader)
version, err := reader.ReadByte()
if err != nil {
return nil, err
}
reader, err = gzip.NewReader(reader)
gReader, err := gzip.NewReader(reader)
if err != nil {
return nil, E.Cause(err, "unsupported profile")
}
bReader := varbin.StubReader(gReader)
var content ProfileContent
content.Name, err = rw.ReadVString(reader)
content.Name, err = varbin.ReadValue[string](bReader, binary.BigEndian)
if err != nil {
return nil, err
}
@@ -219,12 +222,12 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
if err != nil {
return nil, err
}
content.Config, err = rw.ReadVString(reader)
content.Config, err = varbin.ReadValue[string](bReader, binary.BigEndian)
if err != nil {
return nil, err
}
if content.Type != ProfileTypeLocal {
content.RemotePath, err = rw.ReadVString(reader)
content.RemotePath, err = varbin.ReadValue[string](bReader, binary.BigEndian)
if err != nil {
return nil, err
}

View File

@@ -3,6 +3,7 @@ package libbox
import (
"os"
"os/user"
"runtime/debug"
"strconv"
"time"
@@ -21,6 +22,11 @@ var (
sTVOS bool
)
func init() {
debug.SetPanicOnFault(true)
debug.SetTraceback("all")
}
func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) {
sBasePath = basePath
sWorkingPath = workingPath

21
go.mod
View File

@@ -7,7 +7,6 @@ require (
github.com/caddyserver/certmagic v0.20.0
github.com/cloudflare/circl v1.3.7
github.com/cretz/bine v0.2.0
github.com/fsnotify/fsnotify v1.7.0
github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3
@@ -17,24 +16,25 @@ require (
github.com/libdns/cloudflare v0.1.1
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mholt/acmez v1.2.0
github.com/miekg/dns v1.1.59
github.com/miekg/dns v1.1.61
github.com/ooni/go-libtor v1.1.8
github.com/oschwald/maxminddb-golang v1.12.0
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.3
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
github.com/sagernet/quic-go v0.45.0-beta.2
github.com/sagernet/quic-go v0.45.1-beta.2
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.5.0-alpha.11
github.com/sagernet/sing-dns v0.3.0-beta.5
github.com/sagernet/sing v0.5.0-alpha.12
github.com/sagernet/sing-dns v0.3.0-beta.10
github.com/sagernet/sing-mux v0.2.0
github.com/sagernet/sing-quic v0.2.0-beta.9
github.com/sagernet/sing-shadowsocks v0.2.6
github.com/sagernet/sing-quic v0.2.0-beta.12
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.4.0-beta.11
github.com/sagernet/sing-vmess v0.1.8
github.com/sagernet/sing-tun v0.4.0-beta.13.0.20240703164908-1f043289199d
github.com/sagernet/sing-vmess v0.1.12
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
github.com/sagernet/utls v1.5.4
@@ -59,6 +59,7 @@ require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
@@ -97,5 +98,5 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)

38
go.sum
View File

@@ -76,8 +76,8 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
@@ -100,6 +100,8 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4B8=
github.com/sagernet/gomobile v0.1.3/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
@@ -108,29 +110,29 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.45.0-beta.2 h1:nWq9KJTR+cGU8UU4E20XNjdM6QgbLkBgpq+NCExg5RY=
github.com/sagernet/quic-go v0.45.0-beta.2/go.mod h1:rs3XCo3SQ2sB96NtaKnEyq+ZkyaKWL51BvIW3veaiWw=
github.com/sagernet/quic-go v0.45.1-beta.2 h1:zkEeCbhdFFkrxKcuIRBtXNKci/1t2J/39QSG/sPvlmc=
github.com/sagernet/quic-go v0.45.1-beta.2/go.mod h1:+N3FqM9DAzOWfe64uxXuBejVJwX7DeW7BslzLO6N/xI=
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.5.0-alpha.11 h1:4nR9hv3Thxb16tdMY8eQ3xNqyvqGV2gLCwiTht6TkD8=
github.com/sagernet/sing v0.5.0-alpha.11/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.3.0-beta.5 h1:lX+wfnBVaOlSd7+GBgb431Tt/gmYwJXSHvS1HutfnD4=
github.com/sagernet/sing-dns v0.3.0-beta.5/go.mod h1:qeO/lOUK/c3Zczp5a1VO13fbmolaM8xGKCUXtaX0/NQ=
github.com/sagernet/sing v0.5.0-alpha.12 h1:pjffG3SUpuF9PLDCqPO2fOAUozXItIBmnMVTKQ/QMhM=
github.com/sagernet/sing v0.5.0-alpha.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.3.0-beta.10 h1:Js61EjQXVpcu2VDegWEQTH1isCcVwJju8WEHYgG4tQ0=
github.com/sagernet/sing-dns v0.3.0-beta.10/go.mod h1:nXE6EYMXahB5DV3AcXYbFfuorqF7tbQ86kxweSxRKM4=
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.9 h1:gfqUwVgKA6APwFOPhwge9VrPZ0XQtmuXF8hbbIVZML8=
github.com/sagernet/sing-quic v0.2.0-beta.9/go.mod h1:hb6RwYy9Js0gZi80zlTBWABMOIvJDv46K5yak/pbZ4w=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/sagernet/sing-quic v0.2.0-beta.12 h1:BhvA5mmrDFEyDUQB5eeu+9UhF+ieyuNJ5Rsb0dAG3QY=
github.com/sagernet/sing-quic v0.2.0-beta.12/go.mod h1:YVpLfVi8BvYM7NMrjmnvcRm3E8iMETf1gFQmTQDN9jI=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
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.4.0-beta.11 h1:BRzcAFLhfysu3gd46CNJqIkgWw3rYDPKgUBbpbDvBfE=
github.com/sagernet/sing-tun v0.4.0-beta.11/go.mod h1:YgaSM4cm+YIn6erBZN/eF+sW7I27BfBt91EWGo53MME=
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/sing-tun v0.4.0-beta.13.0.20240703164908-1f043289199d h1:2nBM9W9fOCM45hjlu1Fh9qyzBCgKEkq+SOuRCbCCs7c=
github.com/sagernet/sing-tun v0.4.0-beta.13.0.20240703164908-1f043289199d/go.mod h1:81JwnnYw8X9W9XvmZetSTTiPgIE3SbAbnc+EHKwPJ5U=
github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg=
github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI=
@@ -220,5 +222,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=

View File

@@ -12,10 +12,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/protocol/http"
"github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/protocol/socks/socks4"
@@ -51,16 +48,17 @@ func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogg
}
func (h *Mixed) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
headerType, err := rw.ReadByte(conn)
reader := std_bufio.NewReader(conn)
headerBytes, err := reader.Peek(1)
if err != nil {
return err
}
switch headerType {
switch headerBytes[0] {
case socks4.Version, socks5.Version:
return socks.HandleConnection0(ctx, conn, headerType, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
return socks.HandleConnection0(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
default:
return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
}
reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType})))
return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
}
func (h *Mixed) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {

View File

@@ -141,11 +141,11 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
if ruleIndex == 0 {
ruleIndex = tun.DefaultIPRoute2RuleIndex
}
inputMark := options.AutoRedirectInputMark
inputMark := uint32(options.AutoRedirectInputMark)
if inputMark == 0 {
inputMark = tun.DefaultAutoRedirectInputMark
}
outputMark := options.AutoRedirectOutputMark
outputMark := uint32(options.AutoRedirectOutputMark)
if outputMark == 0 {
outputMark = tun.DefaultAutoRedirectOutputMark
}
@@ -311,7 +311,7 @@ func (t *Tun) Start() error {
forwarderBindInterface = true
includeAllNetworks = t.platformInterface.IncludeAllNetworks()
}
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
tunStack, err := tun.NewStack(t.stack, tun.StackOptions{
Context: t.ctx,
Tun: tunInterface,
TunOptions: t.tunOptions,
@@ -327,8 +327,9 @@ func (t *Tun) Start() error {
return err
}
monitor.Start("initiating tun stack")
err = t.tunStack.Start()
err = tunStack.Start()
monitor.Finish()
t.tunStack = tunStack
if err != nil {
return err
}
@@ -355,7 +356,7 @@ func (t *Tun) PostStart() error {
}
t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
}
monitor.Start("initiating auto-redirect")
monitor.Start("initialize auto-redirect")
err := t.autoRedirect.Start()
monitor.Finish()
if err != nil {

View File

@@ -13,9 +13,9 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2ray"
"github.com/sagernet/sing-box/transport/vless"
"github.com/sagernet/sing-vmess"
"github.com/sagernet/sing-vmess/packetaddr"
"github.com/sagernet/sing-vmess/vless"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
@@ -83,12 +83,11 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
}
func (h *VLESS) Start() error {
err := common.Start(
h.service,
h.tlsConfig,
)
if err != nil {
return err
if h.tlsConfig != nil {
err := h.tlsConfig.Start()
if err != nil {
return err
}
}
if h.transport == nil {
return h.myInboundAdapter.Start()

View File

@@ -93,13 +93,16 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
}
func (h *VMess) Start() error {
err := common.Start(
h.service,
h.tlsConfig,
)
err := h.service.Start()
if err != nil {
return err
}
if h.tlsConfig != nil {
err = h.tlsConfig.Start()
if err != nil {
return err
}
}
if h.transport == nil {
return h.myInboundAdapter.Start()
}

View File

@@ -64,7 +64,7 @@ func (r Rule) IsValid() bool {
}
}
type DefaultRule struct {
type _DefaultRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
Network Listable[string] `json:"network,omitempty"`
@@ -94,12 +94,31 @@ type DefaultRule struct {
WIFISSID Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
Invert bool `json:"invert,omitempty"`
Outbound string `json:"outbound,omitempty"`
// Deprecated: renamed to rule_set_ip_cidr_match_source
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
}
func (r DefaultRule) IsValid() bool {
type DefaultRule _DefaultRule
func (r *DefaultRule) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_DefaultRule)(r))
if err != nil {
return err
}
//nolint:staticcheck
//goland:noinspection GoDeprecation
if r.Deprecated_RulesetIPCIDRMatchSource {
r.Deprecated_RulesetIPCIDRMatchSource = false
r.RuleSetIPCIDRMatchSource = true
}
return nil
}
func (r *DefaultRule) IsValid() bool {
var defaultValue DefaultRule
defaultValue.Invert = r.Invert
defaultValue.Outbound = r.Outbound

View File

@@ -64,7 +64,7 @@ func (r DNSRule) IsValid() bool {
}
}
type DefaultDNSRule struct {
type _DefaultDNSRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
@@ -96,15 +96,35 @@ type DefaultDNSRule struct {
WIFISSID Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
// Deprecated: renamed to rule_set_ip_cidr_match_source
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
}
func (r DefaultDNSRule) IsValid() bool {
type DefaultDNSRule _DefaultDNSRule
func (r *DefaultDNSRule) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_DefaultDNSRule)(r))
if err != nil {
return err
}
//nolint:staticcheck
//goland:noinspection GoDeprecation
if r.Deprecated_RulesetIPCIDRMatchSource {
r.Deprecated_RulesetIPCIDRMatchSource = false
r.RuleSetIPCIDRMatchSource = true
}
return nil
}
func (r *DefaultDNSRule) IsValid() bool {
var defaultValue DefaultDNSRule
defaultValue.Invert = r.Invert
defaultValue.Server = r.Server

View File

@@ -17,6 +17,7 @@ type _RuleSet struct {
Type string `json:"type"`
Tag string `json:"tag"`
Format string `json:"format"`
InlineOptions PlainRuleSet `json:"-"`
LocalOptions LocalRuleSet `json:"-"`
RemoteOptions RemoteRuleSet `json:"-"`
}
@@ -26,12 +27,15 @@ type RuleSet _RuleSet
func (r RuleSet) MarshalJSON() ([]byte, error) {
var v any
switch r.Type {
case "", C.RuleSetTypeInline:
r.Type = ""
v = r.InlineOptions
case C.RuleSetTypeLocal:
v = r.LocalOptions
case C.RuleSetTypeRemote:
v = r.RemoteOptions
default:
return nil, E.New("unknown rule set type: " + r.Type)
return nil, E.New("unknown rule-set type: " + r.Type)
}
return MarshallObjects((_RuleSet)(r), v)
}
@@ -44,23 +48,28 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
if r.Tag == "" {
return E.New("missing tag")
}
switch r.Format {
case "":
return E.New("missing format")
case C.RuleSetFormatSource, C.RuleSetFormatBinary:
default:
return E.New("unknown rule set format: " + r.Format)
if r.Type != C.RuleSetTypeInline {
switch r.Format {
case "":
return E.New("missing format")
case C.RuleSetFormatSource, C.RuleSetFormatBinary:
default:
return E.New("unknown rule-set format: " + r.Format)
}
} else {
r.Format = ""
}
var v any
switch r.Type {
case "", C.RuleSetTypeInline:
r.Type = C.RuleSetTypeInline
v = &r.InlineOptions
case C.RuleSetTypeLocal:
v = &r.LocalOptions
case C.RuleSetTypeRemote:
v = &r.RemoteOptions
case "":
return E.New("missing type")
default:
return E.New("unknown rule set type: " + r.Type)
return E.New("unknown rule-set type: " + r.Type)
}
err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v)
if err != nil {
@@ -188,7 +197,7 @@ func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
case C.RuleSetVersion1:
v = r.Options
default:
return nil, E.New("unknown rule set version: ", r.Version)
return nil, E.New("unknown rule-set version: ", r.Version)
}
return MarshallObjects((_PlainRuleSetCompat)(r), v)
}
@@ -203,9 +212,9 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
case C.RuleSetVersion1:
v = &r.Options
case 0:
return E.New("missing rule set version")
return E.New("missing rule-set version")
default:
return E.New("unknown rule set version: ", r.Version)
return E.New("unknown rule-set version: ", r.Version)
}
err = UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v)
if err != nil {
@@ -214,15 +223,13 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
return nil
}
func (r PlainRuleSetCompat) Upgrade() PlainRuleSet {
var result PlainRuleSet
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
switch r.Version {
case C.RuleSetVersion1:
result = r.Options
default:
panic("unknown rule set version: " + F.ToString(r.Version))
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
}
return result
return r.Options, nil
}
type PlainRuleSet struct {

View File

@@ -1,6 +1,13 @@
package option
import "net/netip"
import (
"net/netip"
"strconv"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
)
type TunInboundOptions struct {
InterfaceName string `json:"interface_name,omitempty"`
@@ -11,8 +18,8 @@ type TunInboundOptions struct {
IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"`
IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"`
AutoRedirect bool `json:"auto_redirect,omitempty"`
AutoRedirectInputMark uint32 `json:"auto_redirect_input_mark,omitempty"`
AutoRedirectOutputMark uint32 `json:"auto_redirect_output_mark,omitempty"`
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"`
RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"`
RouteAddressSet Listable[string] `json:"route_address_set,omitempty"`
@@ -46,3 +53,26 @@ type TunInboundOptions struct {
// Deprecated: merged to RouteExcludeAddress
Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
}
type FwMark uint32
func (f FwMark) MarshalJSON() ([]byte, error) {
return json.Marshal(F.ToString("0x", strconv.FormatUint(uint64(f), 16)))
}
func (f *FwMark) UnmarshalJSON(bytes []byte) error {
var stringValue string
err := json.Unmarshal(bytes, &stringValue)
if err != nil {
if rawErr := json.Unmarshal(bytes, (*uint32)(f)); rawErr == nil {
return nil
}
return E.Cause(err, "invalid number or string mark")
}
intValue, err := strconv.ParseUint(stringValue, 0, 32)
if err != nil {
return err
}
*f = FwMark(intValue)
return nil
}

View File

@@ -130,8 +130,8 @@ func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, m
return NewPacketConnection(ctx, h, conn, metadata)
}
func (h *Hysteria) InterfaceUpdated() error {
return h.client.CloseWithError(E.New("network changed"))
func (h *Hysteria) InterfaceUpdated() {
h.client.CloseWithError(E.New("network changed"))
}
func (h *Hysteria) Close() error {

View File

@@ -116,8 +116,8 @@ func (h *Hysteria2) NewPacketConnection(ctx context.Context, conn N.PacketConn,
return NewPacketConnection(ctx, h, conn, metadata)
}
func (h *Hysteria2) InterfaceUpdated() error {
return h.client.CloseWithError(E.New("network changed"))
func (h *Hysteria2) InterfaceUpdated() {
h.client.CloseWithError(E.New("network changed"))
}
func (h *Hysteria2) Close() error {

View File

@@ -1,7 +1,6 @@
package outbound
import (
std_bufio "bufio"
"context"
"crypto/rand"
"encoding/hex"
@@ -11,16 +10,10 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
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/common/rw"
"github.com/sagernet/sing/protocol/http"
"github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/protocol/socks/socks4"
"github.com/sagernet/sing/protocol/socks/socks5"
)
type ProxyListener struct {
@@ -102,16 +95,7 @@ func (l *ProxyListener) acceptLoop() {
}
func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error {
headerType, err := rw.ReadByte(conn)
if err != nil {
return err
}
switch headerType {
case socks4.Version, socks5.Version:
return socks.HandleConnection0(ctx, conn, headerType, l.authenticator, l, M.Metadata{})
}
reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType})))
return http.HandleConnection(ctx, conn, reader, l.authenticator, l, M.Metadata{})
return socks.HandleConnection(ctx, conn, l.authenticator, l, M.Metadata{})
}
func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {

View File

@@ -44,10 +44,10 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger
startConf.ExtraArgs = options.ExtraArgs
if options.DataDirectory != "" {
dataDirAbs, _ := filepath.Abs(startConf.DataDir)
if geoIPPath := filepath.Join(dataDirAbs, "geoip"); rw.FileExists(geoIPPath) && !common.Contains(options.ExtraArgs, "--GeoIPFile") {
if geoIPPath := filepath.Join(dataDirAbs, "geoip"); rw.IsFile(geoIPPath) && !common.Contains(options.ExtraArgs, "--GeoIPFile") {
options.ExtraArgs = append(options.ExtraArgs, "--GeoIPFile", geoIPPath)
}
if geoIP6Path := filepath.Join(dataDirAbs, "geoip6"); rw.FileExists(geoIP6Path) && !common.Contains(options.ExtraArgs, "--GeoIPv6File") {
if geoIP6Path := filepath.Join(dataDirAbs, "geoip6"); rw.IsFile(geoIP6Path) && !common.Contains(options.ExtraArgs, "--GeoIPv6File") {
options.ExtraArgs = append(options.ExtraArgs, "--GeoIPv6File", geoIP6Path)
}
}
@@ -58,8 +58,12 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger
}
if startConf.DataDir != "" {
torrcFile := filepath.Join(startConf.DataDir, "torrc")
if !rw.FileExists(torrcFile) {
err := rw.WriteFile(torrcFile, []byte(""))
err := rw.MkdirParent(torrcFile)
if err != nil {
return nil, err
}
if !rw.IsFile(torrcFile) {
err := os.WriteFile(torrcFile, []byte(""), 0o600)
if err != nil {
return nil, err
}

View File

@@ -108,6 +108,9 @@ func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, met
}
func (h *Trojan) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()
}
if h.multiplexDialer != nil {
h.multiplexDialer.Reset()
}

View File

@@ -385,9 +385,9 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
continue
}
b.Go(realTag, func() (any, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)
testCtx, cancel := context.WithTimeout(g.ctx, C.TCPTimeout)
defer cancel()
t, err := urltest.URLTest(ctx, g.link, p)
t, err := urltest.URLTest(testCtx, g.link, p)
if err != nil {
g.logger.Debug("outbound ", tag, " unavailable: ", err)
g.history.DeleteURLTestHistory(realTag)

View File

@@ -12,8 +12,8 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2ray"
"github.com/sagernet/sing-box/transport/vless"
"github.com/sagernet/sing-vmess/packetaddr"
"github.com/sagernet/sing-vmess/vless"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
@@ -127,6 +127,9 @@ func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, meta
}
func (h *VLESS) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()
}
if h.multiplexDialer != nil {
h.multiplexDialer.Reset()
}

View File

@@ -103,6 +103,9 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
}
func (h *VMess) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()
}
if h.multiplexDialer != nil {
h.multiplexDialer.Reset()
}

View File

@@ -534,7 +534,10 @@ func (r *Router) Start() error {
if r.needPackageManager && r.platformInterface == nil {
monitor.Start("initialize package manager")
packageManager, err := tun.NewPackageManager(r)
packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{
Callback: r,
Logger: r.logger,
})
monitor.Finish()
if err != nil {
return E.Cause(err, "create package manager")
@@ -983,6 +986,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
sniff.STUNMessage,
sniff.UTP,
sniff.UDPTracker,
sniff.DTLSRecord,
)
if sniffMetadata != nil {
metadata.Protocol = sniffMetadata.Protocol

View File

@@ -36,7 +36,7 @@ func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) {
return domain, loaded
}
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) {
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int, isAddressQuery bool) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) {
metadata := adapter.ContextFrom(ctx)
if metadata == nil {
panic("no context")
@@ -47,6 +47,9 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (con
dnsRules = dnsRules[index+1:]
}
for currentRuleIndex, rule := range dnsRules {
if rule.WithAddressLimit() && !isAddressQuery {
continue
}
metadata.ResetRuleCache()
if rule.Match(metadata) {
detour := rule.Outbound()
@@ -101,7 +104,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
response, cached = r.dnsClient.ExchangeCache(ctx, message)
if !cached {
var metadata *adapter.InboundContext
ctx, metadata = adapter.AppendContext(ctx)
ctx, metadata = adapter.ExtendContext(ctx)
metadata.Destination = M.Socksaddr{}
if len(message.Question) > 0 {
metadata.QueryType = message.Question[0].Qtype
switch metadata.QueryType {
@@ -123,12 +127,16 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
dnsCtx context.Context
addressLimit bool
)
dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex)
if rule != nil && rule.WithAddressLimit() && isAddressQuery(message) {
dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message))
dnsCtx = adapter.OverrideContext(dnsCtx)
if rule != nil && rule.WithAddressLimit() {
addressLimit = true
response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool {
metadata.DestinationAddresses, _ = dns.MessageToAddresses(response)
addresses, addrErr := dns.MessageToAddresses(response)
if addrErr != nil {
return false
}
metadata.DestinationAddresses = addresses
return rule.MatchAddressLimit(metadata)
})
} else {
@@ -184,7 +192,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
return responseAddrs, nil
}
r.dnsLogger.DebugContext(ctx, "lookup domain ", domain)
ctx, metadata := adapter.AppendContext(ctx)
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Destination = M.Socksaddr{}
metadata.Domain = domain
var (
transport dns.Transport
@@ -198,9 +207,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
dnsCtx context.Context
addressLimit bool
)
metadata.ResetRuleCache()
metadata.DestinationAddresses = nil
dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex)
dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true)
dnsCtx = adapter.OverrideContext(dnsCtx)
if strategy == dns.DomainStrategyAsIS {
strategy = transportStrategy
}
@@ -249,7 +257,7 @@ func (r *Router) ClearDNSCache() {
func isAddressQuery(message *mDNS.Msg) bool {
for _, question := range message.Question {
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA || question.Qtype == mDNS.TypeHTTPS {
return true
}
}

View File

@@ -50,7 +50,7 @@ func (r *Router) prepareGeoIPDatabase() error {
geoPath = foundPath
}
}
if !rw.FileExists(geoPath) {
if !rw.IsFile(geoPath) {
geoPath = filemanager.BasePath(r.ctx, geoPath)
}
if stat, err := os.Stat(geoPath); err == nil {
@@ -61,7 +61,7 @@ func (r *Router) prepareGeoIPDatabase() error {
os.Remove(geoPath)
}
}
if !rw.FileExists(geoPath) {
if !rw.IsFile(geoPath) {
r.logger.Warn("geoip database not exists: ", geoPath)
var err error
for attempts := 0; attempts < 3; attempts++ {
@@ -96,7 +96,7 @@ func (r *Router) prepareGeositeDatabase() error {
geoPath = foundPath
}
}
if !rw.FileExists(geoPath) {
if !rw.IsFile(geoPath) {
geoPath = filemanager.BasePath(r.ctx, geoPath)
}
if stat, err := os.Stat(geoPath); err == nil {
@@ -107,7 +107,7 @@ func (r *Router) prepareGeositeDatabase() error {
os.Remove(geoPath)
}
}
if !rw.FileExists(geoPath) {
if !rw.IsFile(geoPath) {
r.logger.Warn("geosite database not exists: ", geoPath)
var err error
for attempts := 0; attempts < 3; attempts++ {

View File

@@ -29,9 +29,13 @@ func (r *abstractDefaultRule) Type() string {
func (r *abstractDefaultRule) Start() error {
for _, item := range r.allItems {
err := common.Start(item)
if err != nil {
return err
if starter, isStarter := item.(interface {
Start() error
}); isStarter {
err := starter.Start()
if err != nil {
return err
}
}
}
return nil
@@ -183,8 +187,13 @@ func (r *abstractLogicalRule) UpdateGeosite() error {
}
func (r *abstractLogicalRule) Start() error {
for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (common.Starter, bool) {
rule, loaded := it.(common.Starter)
for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (interface {
Start() error
}, bool,
) {
rule, loaded := it.(interface {
Start() error
})
return rule, loaded
}) {
err := rule.Start()

View File

@@ -205,7 +205,7 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
rule.allItems = append(rule.allItems, item)
}
if len(options.RuleSet) > 0 {
item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource)
item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource, false)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}

View File

@@ -219,7 +219,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
rule.allItems = append(rule.allItems, item)
}
if len(options.RuleSet) > 0 {
item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource)
item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource, options.RuleSetIPCIDRAcceptEmpty)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}

View File

@@ -75,18 +75,19 @@ func NewRawIPCIDRItem(isSource bool, ipSet *netipx.IPSet) *IPCIDRItem {
func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
if r.isSource || metadata.IPCIDRMatchSource {
return r.ipSet.Contains(metadata.Source.Addr)
} else {
if metadata.Destination.IsIP() {
return r.ipSet.Contains(metadata.Destination.Addr)
} else {
for _, address := range metadata.DestinationAddresses {
if r.ipSet.Contains(address) {
return true
}
}
if metadata.Destination.IsIP() {
return r.ipSet.Contains(metadata.Destination.Addr)
}
if len(metadata.DestinationAddresses) > 0 {
for _, address := range metadata.DestinationAddresses {
if r.ipSet.Contains(address) {
return true
}
}
return false
}
return false
return metadata.IPCIDRAcceptEmpty
}
func (r *IPCIDRItem) String() string {

View File

@@ -39,7 +39,7 @@ func NewPortRangeItem(isSource bool, rangeList []string) (*PortRangeItem, error)
}
}
if subIndex == len(portRange)-1 {
end = 0xFF
end = 0xFFFF
} else {
end, err = strconv.ParseUint(portRange[subIndex+1:], 10, 16)
if err != nil {

View File

@@ -15,14 +15,16 @@ type RuleSetItem struct {
router adapter.Router
tagList []string
setList []adapter.RuleSet
ipcidrMatchSource bool
ipCidrMatchSource bool
ipCidrAcceptEmpty bool
}
func NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource bool) *RuleSetItem {
func NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource bool, ipCidrAcceptEmpty bool) *RuleSetItem {
return &RuleSetItem{
router: router,
tagList: tagList,
ipcidrMatchSource: ipCIDRMatchSource,
ipCidrMatchSource: ipCIDRMatchSource,
ipCidrAcceptEmpty: ipCidrAcceptEmpty,
}
}
@@ -39,7 +41,8 @@ func (r *RuleSetItem) Start() error {
}
func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
metadata.IPCIDRMatchSource = r.ipcidrMatchSource
metadata.IPCIDRMatchSource = r.ipCidrMatchSource
metadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
for _, ruleSet := range r.setList {
if ruleSet.Match(metadata) {
return true
@@ -49,7 +52,7 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
}
func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {
if r.ipcidrMatchSource {
if r.ipCidrMatchSource {
return false
}
return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool {

View File

@@ -20,12 +20,12 @@ import (
func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) {
switch options.Type {
case C.RuleSetTypeLocal:
return NewLocalRuleSet(router, options)
case C.RuleSetTypeInline, C.RuleSetTypeLocal, "":
return NewLocalRuleSet(router, logger, options)
case C.RuleSetTypeRemote:
return NewRemoteRuleSet(ctx, router, logger, options), nil
default:
return nil, E.New("unknown rule set type: ", options.Type)
return nil, E.New("unknown rule-set type: ", options.Type)
}
}

View File

@@ -3,8 +3,10 @@ package route
import (
"context"
"os"
"path/filepath"
"strings"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
@@ -14,6 +16,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/common/x/list"
"go4.org/netipx"
@@ -22,50 +25,55 @@ import (
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
type LocalRuleSet struct {
tag string
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
refs atomic.Int32
router adapter.Router
logger logger.Logger
tag string
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
fileFormat string
watcher *fswatch.Watcher
refs atomic.Int32
}
func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) {
var plainRuleSet option.PlainRuleSet
switch options.Format {
case C.RuleSetFormatSource, "":
content, err := os.ReadFile(options.LocalOptions.Path)
if err != nil {
return nil, err
}
compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return nil, err
}
plainRuleSet = compat.Upgrade()
case C.RuleSetFormatBinary:
setFile, err := os.Open(options.LocalOptions.Path)
if err != nil {
return nil, err
}
plainRuleSet, err = srs.Read(setFile, false)
if err != nil {
return nil, err
}
default:
return nil, E.New("unknown rule set format: ", options.Format)
func NewLocalRuleSet(router adapter.Router, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
ruleSet := &LocalRuleSet{
router: router,
logger: logger,
tag: options.Tag,
fileFormat: options.Format,
}
rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules))
var err error
for i, ruleOptions := range plainRuleSet.Rules {
rules[i], err = NewHeadlessRule(router, ruleOptions)
if options.Type == C.RuleSetTypeInline {
if len(options.InlineOptions.Rules) == 0 {
return nil, E.New("empty inline rule-set")
}
err := ruleSet.reloadRules(options.InlineOptions.Rules)
if err != nil {
return nil, E.Cause(err, "parse rule_set.rules.[", i, "]")
return nil, err
}
} else {
err := ruleSet.reloadFile(options.LocalOptions.Path)
if err != nil {
return nil, err
}
}
var metadata adapter.RuleSetMetadata
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
return &LocalRuleSet{tag: options.Tag, rules: rules, metadata: metadata}, nil
if options.Type == C.RuleSetTypeLocal {
var watcher *fswatch.Watcher
filePath, _ := filepath.Abs(options.LocalOptions.Path)
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: []string{filePath},
Callback: func(path string) {
uErr := ruleSet.reloadFile(path)
if uErr != nil {
logger.Error(E.Cause(uErr, "reload rule-set ", options.Tag))
}
},
})
if err != nil {
return nil, err
}
ruleSet.watcher = watcher
}
return ruleSet, nil
}
func (s *LocalRuleSet) Name() string {
@@ -77,6 +85,61 @@ func (s *LocalRuleSet) String() string {
}
func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
if s.watcher != nil {
err := s.watcher.Start()
if err != nil {
s.logger.Error(E.Cause(err, "watch rule-set file"))
}
}
return nil
}
func (s *LocalRuleSet) reloadFile(path string) error {
var plainRuleSet option.PlainRuleSet
switch s.fileFormat {
case C.RuleSetFormatSource, "":
content, err := os.ReadFile(path)
if err != nil {
return err
}
compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
plainRuleSet, err = compat.Upgrade()
if err != nil {
return err
}
case C.RuleSetFormatBinary:
setFile, err := os.Open(path)
if err != nil {
return err
}
plainRuleSet, err = srs.Read(setFile, false)
if err != nil {
return err
}
default:
return E.New("unknown rule-set format: ", s.fileFormat)
}
return s.reloadRules(plainRuleSet.Rules)
}
func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
rules := make([]adapter.HeadlessRule, len(headlessRules))
var err error
for i, ruleOptions := range headlessRules {
rules[i], err = NewHeadlessRule(s.router, ruleOptions)
if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}
}
var metadata adapter.RuleSetMetadata
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
s.rules = rules
s.metadata = metadata
return nil
}
@@ -117,7 +180,7 @@ func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetU
func (s *LocalRuleSet) Close() error {
s.rules = nil
return nil
return common.Close(common.PtrOrNil(s.watcher))
}
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {

View File

@@ -168,14 +168,17 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
if err != nil {
return err
}
plainRuleSet = compat.Upgrade()
plainRuleSet, err = compat.Upgrade()
if err != nil {
return err
}
case C.RuleSetFormatBinary:
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
if err != nil {
return err
}
default:
return E.New("unknown rule set format: ", s.options.Format)
return E.New("unknown rule-set format: ", s.options.Format)
}
rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules))
for i, ruleOptions := range plainRuleSet.Rules {

View File

@@ -1,12 +1,14 @@
package trojan
import (
std_bufio "bufio"
"context"
"net"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/task"
"github.com/sagernet/smux"
)
@@ -33,27 +35,36 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata
return group.Run(ctx)
}
func newMuxConnection(ctx context.Context, stream net.Conn, metadata M.Metadata, handler Handler) {
err := newMuxConnection0(ctx, stream, metadata, handler)
func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) {
err := newMuxConnection0(ctx, conn, metadata, handler)
if err != nil {
handler.NewError(ctx, E.Cause(err, "process trojan-go multiplex connection"))
}
}
func newMuxConnection0(ctx context.Context, stream net.Conn, metadata M.Metadata, handler Handler) error {
command, err := rw.ReadByte(stream)
func newMuxConnection0(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) error {
reader := std_bufio.NewReader(conn)
command, err := reader.ReadByte()
if err != nil {
return E.Cause(err, "read command")
}
metadata.Destination, err = M.SocksaddrSerializer.ReadAddrPort(stream)
metadata.Destination, err = M.SocksaddrSerializer.ReadAddrPort(reader)
if err != nil {
return E.Cause(err, "read destination")
}
if reader.Buffered() > 0 {
buffer := buf.NewSize(reader.Buffered())
_, err = buffer.ReadFullFrom(reader, buffer.Len())
if err != nil {
return err
}
conn = bufio.NewCachedConn(conn, buffer)
}
switch command {
case CommandTCP:
return handler.NewConnection(ctx, stream, metadata)
return handler.NewConnection(ctx, conn, metadata)
case CommandUDP:
return handler.NewPacketConnection(ctx, &PacketConn{Conn: stream}, metadata)
return handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata)
default:
return E.New("unknown command ", command)
}

View File

@@ -2,6 +2,7 @@ package trojan
import (
"context"
"encoding/binary"
"net"
"github.com/sagernet/sing/common/auth"
@@ -76,7 +77,8 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata
return E.Cause(err, "skip crlf")
}
command, err := rw.ReadByte(conn)
var command byte
err = binary.Read(conn, binary.BigEndian, &command)
if err != nil {
return E.Cause(err, "read command")
}

View File

@@ -72,12 +72,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
}, nil
}
func (c *Client) Close() error {
return common.Close(
common.PtrOrNil(c.conn),
)
}
func (c *Client) connect() (*grpc.ClientConn, error) {
conn := c.conn
if conn != nil && conn.GetState() != connectivity.Shutdown {
@@ -113,3 +107,13 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
}
return NewGRPCConn(stream, cancel), nil
}
func (c *Client) Close() error {
c.connAccess.Lock()
defer c.connAccess.Unlock()
if c.conn != nil {
c.conn.Close()
c.conn = nil
}
return nil
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/baderror"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/rw"
N "github.com/sagernet/sing/common/network"
)
var _ net.Conn = (*GRPCConn)(nil)
@@ -90,7 +90,7 @@ func (c *GRPCConn) Upstream() any {
return c.GunService
}
var _ rw.WriteCloser = (*clientConnWrapper)(nil)
var _ N.WriteCloser = (*clientConnWrapper)(nil)
type clientConnWrapper struct {
GunService_TunClient

View File

@@ -100,7 +100,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
conn.setup(nil, err)
} else if response.StatusCode != 200 {
response.Body.Close()
conn.setup(nil, E.New("unexpected status: ", response.Status))
conn.setup(nil, E.New("v2ray-grpc: unexpected status: ", response.Status))
} else {
conn.setup(response.Body, nil)
}
@@ -109,8 +109,6 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
}
func (c *Client) Close() error {
if c.transport != nil {
v2rayhttp.CloseIdleConnections(c.transport)
}
v2rayhttp.ResetTransport(c.transport)
return nil
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/sagernet/sing/common/baderror"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/varbin"
)
// kanged from: https://github.com/Qv2ray/gun-lite
@@ -96,7 +96,7 @@ func (c *GunConn) read(b []byte) (n int, err error) {
}
func (c *GunConn) Write(b []byte) (n int, err error) {
varLen := rw.UVariantLen(uint64(len(b)))
varLen := varbin.UvarintLen(uint64(len(b)))
buffer := buf.NewSize(6 + varLen + len(b))
header := buffer.Extend(6 + varLen)
header[0] = 0x00
@@ -117,13 +117,13 @@ func (c *GunConn) Write(b []byte) (n int, err error) {
func (c *GunConn) WriteBuffer(buffer *buf.Buffer) error {
defer buffer.Release()
dataLen := buffer.Len()
varLen := rw.UVariantLen(uint64(dataLen))
varLen := varbin.UvarintLen(uint64(dataLen))
header := buffer.ExtendHeader(6 + varLen)
header[0] = 0x00
binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen))
header[5] = 0x0A
binary.PutUvarint(header[6:], uint64(dataLen))
err := rw.WriteBytes(c.writer, buffer.Bytes())
err := common.Error(c.writer.Write(buffer.Bytes()))
if err != nil {
return baderror.WrapH2(err)
}

View File

@@ -146,7 +146,7 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
conn.Setup(nil, err)
} else if response.StatusCode != 200 {
response.Body.Close()
conn.Setup(nil, E.New("unexpected status: ", response.Status))
conn.Setup(nil, E.New("v2ray-http: unexpected status: ", response.Status))
} else {
conn.Setup(response.Body, nil)
}
@@ -155,6 +155,6 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
}
func (c *Client) Close() error {
CloseIdleConnections(c.transport)
c.transport = ResetTransport(c.transport)
return nil
}

View File

@@ -43,7 +43,7 @@ func (c *HTTPConn) Read(b []byte) (n int, err error) {
return 0, E.Cause(err, "read response")
}
if response.StatusCode != 200 {
return 0, E.New("unexpected status: ", response.Status)
return 0, E.New("v2ray-http: unexpected status: ", response.Status)
}
if cacheLen := reader.Buffered(); cacheLen > 0 {
c.responseCache = buf.NewSize(cacheLen)

View File

@@ -0,0 +1,47 @@
package v2rayhttp
import (
"net/http"
"reflect"
"sync"
"unsafe"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/net/http2"
)
type clientConnPool struct {
t *http2.Transport
mu sync.Mutex
conns map[string][]*http2.ClientConn // key is host:port
}
type efaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
func ResetTransport(rawTransport http.RoundTripper) http.RoundTripper {
switch transport := rawTransport.(type) {
case *http.Transport:
transport.CloseIdleConnections()
return transport.Clone()
case *http2.Transport:
connPool := transportConnPool(transport)
p := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data)
p.mu.Lock()
defer p.mu.Unlock()
for _, vv := range p.conns {
for _, cc := range vv {
cc.Close()
}
}
return transport
default:
panic(E.New("unknown transport type: ", reflect.TypeOf(transport)))
}
}
//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool
func transportConnPool(t *http2.Transport) http2.ClientConnPool

View File

@@ -104,7 +104,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
if response.StatusCode != 101 ||
!strings.EqualFold(response.Header.Get("Connection"), "upgrade") ||
!strings.EqualFold(response.Header.Get("Upgrade"), "websocket") {
return nil, E.New("unexpected status: ", response.Status)
return nil, E.New("v2ray-http-upgrade: unexpected status: ", response.Status)
}
if bufReader.Buffered() > 0 {
buffer := buf.NewSize(bufReader.Buffered())
@@ -116,3 +116,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
}
return conn, nil
}
func (c *Client) Close() error {
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"sync"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
@@ -37,7 +38,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
}
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"h2", "http/1.1"})
tlsConfig.SetNextProtos([]string{http3.NextProtoH3})
}
return &Client{
ctx: ctx,
@@ -96,5 +97,15 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
}
func (c *Client) Close() error {
return common.Close(c.conn, c.rawConn)
c.connAccess.Lock()
defer c.connAccess.Unlock()
if c.conn != nil {
c.conn.CloseWithError(0, "")
}
if c.rawConn != nil {
c.rawConn.Close()
}
c.conn = nil
c.rawConn = nil
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"os"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
@@ -34,7 +35,7 @@ func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig t
DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
}
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"h2", "http/1.1"})
tlsConfig.SetNextProtos([]string{http3.NextProtoH3})
}
server := &Server{
ctx: ctx,

View File

@@ -127,3 +127,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
return &EarlyWebsocketConn{Client: c, ctx: ctx, create: make(chan struct{})}, nil
}
}
func (c *Client) Close() error {
return nil
}

Some files were not shown because too many files have changed in this diff Show More