Compare commits

...

38 Commits

Author SHA1 Message Date
世界
cd5c2a7999 Update documentation 2023-02-25 16:28:39 +08:00
世界
fbc94b9e3e Add VLESS server, vision flow and reality TLS 2023-02-25 16:24:08 +08:00
zakuwaki
e766f25d55 Fix private ip will never be matched 2023-02-24 13:31:49 +08:00
世界
140ed9a4cb Fix platform wrapper 2023-02-24 13:00:49 +08:00
世界
60094884cd Update documentation 2023-02-22 11:45:31 +08:00
H3arn
0e8a4d141a Fix incorrect NTP server address 2023-02-21 23:08:05 +08:00
世界
17b78a6339 Fix documentation 2023-02-21 22:06:12 +08:00
世界
e99741159b Update documentation 2023-02-21 20:54:25 +08:00
世界
6b9603227b Add strict mode support for shadowtls v3 2023-02-21 20:51:26 +08:00
世界
23e8d282a3 Add multiple server names and multi-user support for shadowtls 2023-02-21 16:09:06 +08:00
世界
611d6bbfc5 Add NTP service 2023-02-21 16:09:06 +08:00
世界
f26785c0ba Add uTLS support for shadowtls v3 2023-02-20 21:04:07 +08:00
世界
5bcfb71737 Update dependencies 2023-02-20 21:04:07 +08:00
世界
4135c4974f Merge shadowtls to library 2023-02-20 13:53:06 +08:00
世界
222196b182 Add libbox wrapper 2023-02-20 11:07:49 +08:00
世界
86e55c5c1c Fix tproxy inbound 2023-02-19 18:56:38 +08:00
世界
73c068b96f Update documentation 2023-02-19 17:49:05 +08:00
世界
f516026540 Fix shadowtls in go versiojns below 1.20 2023-02-19 12:02:11 +08:00
dyhkwong
3c5bc842ed Update QUIC v2 version number and initial salt 2023-02-18 23:51:55 +08:00
世界
d8270a66f4 Update release script 2023-02-18 21:14:17 +08:00
世界
123c383eae Fix documentation 2023-02-18 19:33:35 +08:00
世界
67814faf92 Remove TLS min version for shadowtls v3 2023-02-18 19:26:05 +08:00
世界
ec4a0c8497 Update documentation 2023-02-18 15:02:27 +08:00
世界
21cb227bc2 Add ShadowTLS protocol v3 2023-02-18 14:55:47 +08:00
世界
1610bdc5dd Update workflow 2023-02-18 14:28:21 +08:00
世界
3296a2f7b2 Update dependencies 2023-02-18 14:28:21 +08:00
世界
2bd91baad0 Add fallback support for v2ray transport 2023-02-18 14:28:21 +08:00
世界
a624cd9b49 Disable vmess header protection if transport enabled 2023-02-13 05:15:26 +08:00
Tim Xylon
02afba132f Replace deprecated 'set-output' 2023-02-09 22:07:00 +08:00
世界
99890a1af0 Fix socks connect response 2023-02-09 21:25:00 +08:00
世界
437f1f819c Fix lint 2023-02-09 21:01:48 +08:00
世界
92a79e6158 Remove cancel-workflow-action 2023-02-09 18:04:49 +08:00
renovate[bot]
c9efd0a74f [dependencies] Update golang Docker tag to v1.20
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 18:04:16 +08:00
renovate[bot]
9da349748a [dependencies] Update github-actions
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 17:42:06 +08:00
世界
2423cbbbfe Add renovate config 2023-02-09 17:39:15 +08:00
Gavin Luo
4833f6d5db Fix systemd service caps for process sniffing 2023-02-09 13:32:31 +08:00
世界
9db3cb5cb7 Update scripts 2023-02-09 13:27:05 +08:00
shadow750d6
c14b353a29 Fix parse hysteria UDP message 2023-02-09 13:20:16 +08:00
132 changed files with 4586 additions and 1061 deletions

28
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"commitMessagePrefix": "[dependencies]",
"extends": [
"config:base",
":disableRateLimiting"
],
"baseBranches": [
"dev-next"
],
"golang": {
"enabled": false
},
"packageRules": [
{
"matchManagers": [
"github-actions"
],
"groupName": "github-actions"
},
{
"matchManagers": [
"dockerfile"
],
"groupName": "Dockerfile"
}
]
}

View File

@@ -19,24 +19,20 @@ jobs:
name: Debug build
runs-on: ubuntu-latest
steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
@@ -58,22 +54,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: 1.18.7
go-version: 1.18.10
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: |
go test -v ./...
run: make
cross:
strategy:
matrix:
@@ -190,19 +185,19 @@ jobs:
TAGS: with_clash_api,with_quic
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
@@ -211,7 +206,7 @@ jobs:
id: build
run: make
- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sing-box-${{ matrix.name }}
path: sing-box*

View File

@@ -9,20 +9,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Setup QEMU for Docker Buildx
uses: docker/setup-qemu-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: metadata
uses: docker/metadata-action@v3
uses: docker/metadata-action@v4
with:
images: ghcr.io/sagernet/sing-box
- name: Get tag to build
@@ -35,7 +35,7 @@ jobs:
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
fi
- name: Build and release Docker images
uses: docker/build-push-action@v2
uses: docker/build-push-action@v4
with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
target: dist

View File

@@ -8,7 +8,7 @@ on:
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/debug.yml'
- '!.github/workflows/lint.yml'
pull_request:
branches:
- main-next
@@ -19,31 +19,24 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Get dependencies
run: |
go mod download -x
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:

View File

@@ -10,9 +10,11 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: pip install mkdocs-material mkdocs-static-i18n
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
- run: |
pip install mkdocs-material=="9.*" mkdocs-static-i18n=="0.53"
- run: |
mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history

View File

@@ -8,7 +8,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
- uses: actions/stale@v7
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 60

7
.gitignore vendored
View File

@@ -5,4 +5,9 @@
/site/
/bin/
/dist/
/sing-box
/sing-box
/build/
/*.jar
/*.aar
/*.xcframework/
.DS_Store

View File

@@ -3,7 +3,7 @@ linters:
enable:
- gofumpt
- govet
# - gci
- gci
- staticcheck
- paralleltest
@@ -12,12 +12,15 @@ run:
- transport/simple-obfs
- transport/clashssr
- transport/cloudflaretls
- transport/shadowtls/tls
- transport/shadowtls/tls_go119
linters-settings:
# gci:
# sections:
# - standard
# - prefix(github.com/sagernet/)
# - default
gci:
custom-order: true
sections:
- standard
- prefix(github.com/sagernet/)
- default
staticcheck:
go: '1.19'
go: '1.20'

View File

@@ -1,4 +1,4 @@
FROM golang:1.19-alpine AS builder
FROM golang:1.20-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box

View File

@@ -1,7 +1,7 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
MAIN = ./cmd/sing-box
@@ -16,11 +16,11 @@ install:
fmt:
@gofumpt -l -w .
@gofmt -s -w .
@gci write -s "standard,prefix(github.com/sagernet/),default" .
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
fmt_install:
go install -v mvdan.cc/gofumpt@latest
go install -v github.com/daixiang0/gci@v0.4.0
go install -v github.com/daixiang0/gci@latest
lint:
GOOS=linux golangci-lint run ./...
@@ -71,6 +71,14 @@ test_stdio:
go mod tidy && \
go test -v -tags "$(TAGS_TEST),force_stdio" .
lib:
go run ./cmd/internal/build_libbox
lib_install:
go get -v -d
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
clean:
rm -rf bin dist sing-box
rm -f $(shell go env GOPATH)/sing-box

View File

@@ -34,12 +34,15 @@ type Router interface {
InterfaceFinder() control.InterfaceFinder
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
TimeService
ClashServer() ClashServer
SetClashServer(server ClashServer)

8
adapter/time.go Normal file
View File

@@ -0,0 +1,8 @@
package adapter
import "time"
type TimeService interface {
Service
TimeFunc() func() time.Time
}

View File

@@ -3,6 +3,10 @@ package adapter
import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type V2RayServerTransport interface {
@@ -12,6 +16,12 @@ type V2RayServerTransport interface {
Close() error
}
type V2RayServerTransportHandler interface {
N.TCPConnectionHandler
E.Handler
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
}
type V2RayClientTransport interface {
DialContext(ctx context.Context) (net.Conn, error)
}

66
box.go
View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log"
@@ -53,19 +54,25 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
var logFactory log.Factory
var observableLogFactory log.ObservableFactory
var logFile *os.File
var logWriter io.Writer
if logOptions.Disabled {
observableLogFactory = log.NewNOPFactory()
logFactory = observableLogFactory
} else {
var logWriter io.Writer
switch logOptions.Output {
case "", "stderr":
case "":
if options.PlatformInterface != nil {
logWriter = io.Discard
} else {
logWriter = os.Stdout
}
case "stderr":
logWriter = os.Stderr
case "stdout":
logWriter = os.Stdout
default:
var err error
logFile, err = os.OpenFile(logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
@@ -79,10 +86,10 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
TimestampFormat: "-0700 2006-01-02 15:04:05",
}
if needClashAPI {
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, options.PlatformInterface)
logFactory = observableLogFactory
} else {
logFactory = log.NewFactory(logFormatter, logWriter)
logFactory = log.NewFactory(logFormatter, logWriter, options.PlatformInterface)
}
if logOptions.Level != "" {
logLevel, err := log.ParseLevel(logOptions.Level)
@@ -100,7 +107,9 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
logFactory,
common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds,
options.PlatformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse route options")
@@ -120,6 +129,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
inboundOptions,
options.PlatformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]")
@@ -255,19 +265,43 @@ func (s *Box) Close() error {
default:
close(s.done)
}
for _, in := range s.inbounds {
in.Close()
var errors error
for i, in := range s.inbounds {
errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
})
}
for _, out := range s.outbounds {
common.Close(out)
for i, out := range s.outbounds {
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
})
}
return common.Close(
s.router,
s.logFactory,
s.clashServer,
s.v2rayServer,
common.PtrOrNil(s.logFile),
)
if err := common.Close(s.router); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close router")
})
}
if err := common.Close(s.logFactory); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close log factory")
})
}
if err := common.Close(s.clashServer); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close clash api server")
})
}
if err := common.Close(s.v2rayServer); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close v2ray api server")
})
}
if s.logFile != nil {
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
return E.Cause(err, "close log file")
})
}
return errors
}
func (s *Box) Router() adapter.Router {

View File

@@ -4,11 +4,12 @@ import (
"os"
"os/exec"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
)
func main() {
findSDK()
build_shared.FindSDK()
command := exec.Command(os.Args[1], os.Args[2:]...)
command.Stdout = os.Stdout

View File

@@ -0,0 +1,112 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
_ "github.com/sagernet/gomobile/asset"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/rw"
)
var (
debugEnabled bool
target string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
}
func main() {
flag.Parse()
build_shared.FindMobile()
switch target {
case "android":
buildAndroid()
case "ios":
buildiOS()
}
}
func buildAndroid() {
build_shared.FindSDK()
args := []string{
"bind",
"-v",
"-androidapi", "21",
"-javapkg=io.nekohasekai",
"-libname=box",
}
if !debugEnabled {
args = append(args,
"-trimpath", "-ldflags=-s -w -buildid=",
"-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api",
)
} else {
args = append(args, "-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
}
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
const name = "libbox.aar"
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
if rw.FileExists(copyPath) {
copyPath, _ = filepath.Abs(copyPath)
err = rw.CopyFile(name, filepath.Join(copyPath, name))
if err != nil {
log.Fatal(err)
}
log.Info("copied to ", copyPath)
}
}
func buildiOS() {
args := []string{
"bind",
"-v",
"-target", "ios,iossimulator,macos",
"-libname=box",
}
if !debugEnabled {
args = append(args,
"-trimpath", "-ldflags=-s -w -buildid=",
)
} else {
args = append(args, "-tags", "debug")
}
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
copyPath := filepath.Join("..", "sfi")
if rw.FileExists(copyPath) {
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)
os.RemoveAll(targetDir)
os.Rename("Libbox.xcframework", targetDir)
log.Info("copied to ", targetDir)
}
}

View File

@@ -1,6 +1,7 @@
package main
package build_shared
import (
"go/build"
"os"
"path/filepath"
"runtime"
@@ -18,7 +19,7 @@ var (
androidNDKPath string
)
func findSDK() {
func FindSDK() {
searchPath := []string{
"$ANDROID_HOME",
"$HOME/Android/Sdk",
@@ -79,3 +80,13 @@ func findNDK() bool {
}
return false
}
var GoBinPath string
func FindMobile() {
goBin := filepath.Join(build.Default.GOPATH, "bin")
if !rw.FileExists(goBin + "/" + "gobind") {
log.Fatal("missing gomobile installation")
}
GoBinPath = goBin
}

View File

@@ -70,15 +70,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router.AutoDetectInterface() {
const useInterfaceName = C.IsLinux
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
remoteAddr := M.ParseSocksaddr(address).Addr
if C.IsLinux {
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
} else {
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
}
})
bindFunc := router.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router.DefaultInterface() != "" {

View File

@@ -13,13 +13,13 @@ import (
const (
VersionDraft29 = 0xff00001d
Version1 = 0x1
Version2 = 0x709a50c4
Version2 = 0x6b3343cf
)
var (
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
SaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
SaltV2 = []byte{0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}
SaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}
)
const (

View File

@@ -31,18 +31,23 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
}
if options.ECH != nil && options.ECH.Enabled {
return NewECHClient(router, serverAddress, options)
} else if options.Reality != nil && options.Reality.Enabled {
return NewRealityClient(router, serverAddress, options)
} else if options.UTLS != nil && options.UTLS.Enabled {
return NewUTLSClient(router, serverAddress, options)
} else {
return NewSTDClient(serverAddress, options)
return NewSTDClient(router, serverAddress, options)
}
}
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
tlsConn := config.Client(conn)
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
tlsConn, err := config.Client(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}

View File

@@ -10,8 +10,9 @@ import (
)
type (
STDConfig = tls.Config
STDConn = tls.Conn
STDConfig = tls.Config
STDConn = tls.Conn
ConnectionState = tls.ConnectionState
)
type Config interface {
@@ -20,20 +21,29 @@ type Config interface {
NextProtos() []string
SetNextProtos(nextProto []string)
Config() (*STDConfig, error)
Client(conn net.Conn) Conn
Client(conn net.Conn) (Conn, error)
Clone() Config
}
type ConfigWithSessionIDGenerator interface {
SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error)
}
type ServerConfig interface {
Config
adapter.Service
Server(conn net.Conn) Conn
Server(conn net.Conn) (Conn, error)
}
type ServerConfigCompat interface {
ServerConfig
ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error)
}
type Conn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() tls.ConnectionState
ConnectionState() ConnectionState
}
func ParseTLSVersion(version string) (uint16, error) {

View File

@@ -44,8 +44,8 @@ func (e *ECHClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for ECH")
}
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
return &echConnWrapper{cftls.Client(conn, e.config)}
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
}
func (e *ECHClientConfig) Clone() Config {
@@ -76,6 +76,10 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
}
}
func (c *echConnWrapper) Upstream() any {
return c.Conn
}
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
@@ -90,6 +94,7 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
}
var tlsConfig cftls.Config
tlsConfig.Time = router.TimeFunc()
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -11,7 +11,10 @@ import (
"time"
)
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
if timeFunc == nil {
timeFunc = time.Now
}
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
@@ -22,8 +25,8 @@ func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
}
template := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: time.Now().Add(time.Hour * -1),
NotAfter: time.Now().Add(time.Hour),
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: timeFunc().Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,

View File

@@ -0,0 +1,187 @@
//go:build with_utls
package tls
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"reflect"
"time"
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
utls "github.com/sagernet/utls"
"golang.org/x/crypto/hkdf"
)
var _ Config = (*RealityClientConfig)(nil)
type RealityClientConfig struct {
uClient *UTLSClientConfig
publicKey []byte
shortID []byte
}
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
if options.UTLS == nil || !options.UTLS.Enabled {
return nil, E.New("uTLS is required by reality client")
}
uClient, err := NewUTLSClient(router, serverAddress, options)
if err != nil {
return nil, err
}
publicKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode public_key")
}
if len(publicKey) != 32 {
return nil, E.New("invalid public_key")
}
shortID, err := hex.DecodeString(options.Reality.ShortID)
if err != nil {
return nil, E.Cause(err, "decode short_id")
}
if len(shortID) != 8 {
return nil, E.New("invalid short_id")
}
return &RealityClientConfig{uClient, publicKey, shortID}, nil
}
func (e *RealityClientConfig) ServerName() string {
return e.uClient.ServerName()
}
func (e *RealityClientConfig) SetServerName(serverName string) {
e.uClient.SetServerName(serverName)
}
func (e *RealityClientConfig) NextProtos() []string {
return e.uClient.NextProtos()
}
func (e *RealityClientConfig) SetNextProtos(nextProto []string) {
e.uClient.SetNextProtos(nextProto)
}
func (e *RealityClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for reality")
}
func (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {
verifier := &realityVerifier{
serverName: e.uClient.ServerName(),
}
uConfig := e.uClient.config.Clone()
uConfig.InsecureSkipVerify = true
uConfig.SessionTicketsDisabled = true
uConfig.VerifyPeerCertificate = verifier.VerifyPeerCertificate
uConn := utls.UClient(conn, uConfig, e.uClient.id)
verifier.UConn = uConn
err := uConn.BuildHandshakeState()
if err != nil {
return nil, err
}
hello := uConn.HandshakeState.Hello
hello.SessionId = make([]byte, 32)
copy(hello.Raw[39:], hello.SessionId)
var nowTime time.Time
if uConfig.Time != nil {
nowTime = uConfig.Time()
} else {
nowTime = time.Now()
}
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
hello.SessionId[0] = 1
hello.SessionId[1] = 7
hello.SessionId[2] = 5
copy(hello.SessionId[8:], e.shortID)
if debug.Enabled {
fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16])
}
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(e.publicKey)
if authKey == nil {
return nil, E.New("nil auth_key")
}
verifier.authKey = authKey
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
if err != nil {
return nil, err
}
aesBlock, _ := aes.NewCipher(authKey)
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
copy(hello.Raw[39:], hello.SessionId)
if debug.Enabled {
fmt.Printf("REALITY hello.sessionId: %v\n", hello.SessionId)
fmt.Printf("REALITY uConn.AuthKey: %v\n", authKey)
}
return &utlsConnWrapper{uConn}, nil
}
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
e.uClient.config.SessionIDGenerator = generator
}
func (e *RealityClientConfig) Clone() Config {
return &RealityClientConfig{
e.uClient.Clone().(*UTLSClientConfig),
e.publicKey,
e.shortID,
}
}
type realityVerifier struct {
*utls.UConn
serverName string
authKey []byte
verified bool
}
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
h := hmac.New(sha512.New, c.authKey)
h.Write(pub)
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
c.verified = true
return nil
}
}
opts := x509.VerifyOptions{
DNSName: c.serverName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
if _, err := certs[0].Verify(opts); err != nil {
return err
}
if !c.verified {
return E.New("reality verification failed")
}
return nil
}

View File

@@ -0,0 +1,194 @@
//go:build with_reality_server
package tls
import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"net"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/nekohasekai/reality"
)
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
type RealityServerConfig struct {
config *reality.Config
}
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
var tlsConfig reality.Config
if options.ACME != nil && len(options.ACME.Domain) > 0 {
return nil, E.New("acme is unavailable in reality")
}
tlsConfig.Time = router.TimeFunc()
if options.ServerName != "" {
tlsConfig.ServerName = options.ServerName
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)
if err != nil {
return nil, E.Cause(err, "parse min_version")
}
tlsConfig.MinVersion = minVersion
}
if options.MaxVersion != "" {
maxVersion, err := ParseTLSVersion(options.MaxVersion)
if err != nil {
return nil, E.Cause(err, "parse max_version")
}
tlsConfig.MaxVersion = maxVersion
}
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range tls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
if options.Certificate != "" || options.CertificatePath != "" {
return nil, E.New("certificate is unavailable in reality")
}
if options.Key != "" || options.KeyPath != "" {
return nil, E.New("key is unavailable in reality")
}
tlsConfig.SessionTicketsDisabled = true
tlsConfig.Type = N.NetworkTCP
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
tlsConfig.ServerNames = map[string]bool{options.ServerName: true}
privateKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PrivateKey)
if err != nil {
return nil, E.Cause(err, "decode private key")
}
if len(privateKey) != 32 {
return nil, E.New("invalid private key")
}
tlsConfig.PrivateKey = privateKey
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
tlsConfig.ShortIds = make(map[[8]byte]bool)
for i, shortID := range options.Reality.ShortID {
var shortIDBytesArray [8]byte
decodedLen, err := hex.Decode(shortIDBytesArray[:], []byte(shortID))
if err != nil {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortID)
}
if decodedLen != 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortID)
}
tlsConfig.ShortIds[shortIDBytesArray] = true
}
handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
}
if debug.Enabled {
tlsConfig.Show = true
}
return &RealityServerConfig{&tlsConfig}, nil
}
func (c *RealityServerConfig) ServerName() string {
return c.config.ServerName
}
func (c *RealityServerConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
}
func (c *RealityServerConfig) NextProtos() []string {
return c.config.NextProtos
}
func (c *RealityServerConfig) SetNextProtos(nextProto []string) {
c.config.NextProtos = nextProto
}
func (c *RealityServerConfig) Config() (*tls.Config, error) {
return nil, E.New("unsupported usage for reality")
}
func (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) {
return nil, os.ErrInvalid
}
func (c *RealityServerConfig) Start() error {
return nil
}
func (c *RealityServerConfig) Close() error {
return nil
}
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
return nil, os.ErrInvalid
}
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
tlsConn, err := reality.Server(ctx, conn, c.config)
if err != nil {
return nil, err
}
return &realityConnWrapper{Conn: tlsConn}, nil
}
func (c *RealityServerConfig) Clone() Config {
return &RealityServerConfig{
config: c.config.Clone(),
}
}
var _ Conn = (*realityConnWrapper)(nil)
type realityConnWrapper struct {
*reality.Conn
}
func (c *realityConnWrapper) ConnectionState() ConnectionState {
state := c.Conn.ConnectionState()
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,
DidResume: state.DidResume,
CipherSuite: state.CipherSuite,
NegotiatedProtocol: state.NegotiatedProtocol,
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
ServerName: state.ServerName,
PeerCertificates: state.PeerCertificates,
VerifiedChains: state.VerifiedChains,
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
OCSPResponse: state.OCSPResponse,
TLSUnique: state.TLSUnique,
}
}
func (c *realityConnWrapper) Upstream() any {
return c.Conn
}

View File

@@ -0,0 +1,16 @@
//go:build !with_reality_server
package tls
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
}

View File

@@ -5,24 +5,35 @@ import (
"crypto/tls"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
return NewSTDServer(ctx, logger, options)
if options.Reality != nil && options.Reality.Enabled {
return NewRealityServer(ctx, router, logger, options)
} else {
return NewSTDServer(ctx, router, logger, options)
}
}
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
tlsConn := config.Server(conn)
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
if compatServer, isCompat := config.(ServerConfigCompat); isCompat {
return compatServer.ServerHandshake(ctx, conn)
}
tlsConn, err := config.Server(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}

View File

@@ -7,6 +7,7 @@ import (
"net/netip"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
@@ -35,15 +36,15 @@ func (s *STDClientConfig) Config() (*STDConfig, error) {
return s.config, nil
}
func (s *STDClientConfig) Client(conn net.Conn) Conn {
return tls.Client(conn, s.config)
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
return tls.Client(conn, s.config), nil
}
func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
@@ -57,6 +58,7 @@ func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Conf
}
var tlsConfig tls.Config
tlsConfig.Time = router.TimeFunc()
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -48,12 +48,12 @@ func (c *STDServerConfig) Config() (*STDConfig, error) {
return c.config, nil
}
func (c *STDServerConfig) Client(conn net.Conn) Conn {
return tls.Client(conn, c.config)
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
return tls.Client(conn, c.config), nil
}
func (c *STDServerConfig) Server(conn net.Conn) Conn {
return tls.Server(conn, c.config)
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
return tls.Server(conn, c.config), nil
}
func (c *STDServerConfig) Clone() Config {
@@ -156,7 +156,7 @@ func (c *STDServerConfig) Close() error {
return nil
}
func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
@@ -175,6 +175,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
} else {
tlsConfig = &tls.Config{}
}
tlsConfig.Time = router.TimeFunc()
if options.ServerName != "" {
tlsConfig.ServerName = options.ServerName
}
@@ -230,7 +231,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
}
if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(info.ServerName)
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -12,8 +12,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
utls "github.com/refraction-networking/utls"
utls "github.com/sagernet/utls"
)
type UTLSClientConfig struct {
@@ -41,8 +40,19 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for uTLS")
}
func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
}
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
e.config.SessionIDGenerator = generator
}
func (e *UTLSClientConfig) Clone() Config {
return &UTLSClientConfig{
config: e.config.Clone(),
id: e.id,
}
}
type utlsConnWrapper struct {
@@ -67,14 +77,11 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
}
}
func (e *UTLSClientConfig) Clone() Config {
return &UTLSClientConfig{
config: e.config.Clone(),
id: e.id,
}
func (c *utlsConnWrapper) Upstream() any {
return c.UConn
}
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
@@ -88,6 +95,7 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
}
var tlsConfig utls.Config
tlsConfig.Time = router.TimeFunc()
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
@@ -144,28 +152,34 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
}
tlsConfig.RootCAs = certPool
}
var id utls.ClientHelloID
switch options.UTLS.Fingerprint {
case "chrome", "":
id = utls.HelloChrome_Auto
case "firefox":
id = utls.HelloFirefox_Auto
case "edge":
id = utls.HelloEdge_Auto
case "safari":
id = utls.HelloSafari_Auto
case "360":
id = utls.Hello360_Auto
case "qq":
id = utls.HelloQQ_Auto
case "ios":
id = utls.HelloIOS_Auto
case "android":
id = utls.HelloAndroid_11_OkHttp
case "random":
id = utls.HelloRandomized
default:
return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
if err != nil {
return nil, err
}
return &UTLSClientConfig{&tlsConfig, id}, nil
}
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
switch name {
case "chrome", "":
return utls.HelloChrome_Auto, nil
case "firefox":
return utls.HelloFirefox_Auto, nil
case "edge":
return utls.HelloEdge_Auto, nil
case "safari":
return utls.HelloSafari_Auto, nil
case "360":
return utls.Hello360_Auto, nil
case "qq":
return utls.HelloQQ_Auto, nil
case "ios":
return utls.HelloIOS_Auto, nil
case "android":
return utls.HelloAndroid_11_OkHttp, nil
case "random":
return utls.HelloRandomized, nil
default:
return utls.ClientHelloID{}, E.New("unknown uTLS fingerprint: ", name)
}
}

View File

@@ -11,3 +11,7 @@ import (
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
}
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
}

View File

@@ -3,13 +3,28 @@ package constant
import (
"os"
"path/filepath"
"strings"
"github.com/sagernet/sing/common/rw"
)
const dirName = "sing-box"
var resourcePaths []string
var (
basePath string
resourcePaths []string
)
func BasePath(name string) string {
if basePath == "" || strings.HasPrefix(name, "/") {
return name
}
return filepath.Join(basePath, name)
}
func SetBasePath(path string) {
basePath = path
}
func FindPath(name string) (string, bool) {
name = os.ExpandEnv(name)

View File

@@ -1,3 +1,3 @@
package constant
var Version = "1.1.5"
var Version = "1.2-beta5"

View File

@@ -1,3 +1,39 @@
#### 1.2-beta5
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
* Add [reality TLS](/configuration/shared/tls) support
* Fix match private address
#### 1.1.6
* Improve vmess request
* Fix ipv6 redirect on Linux
* Fix match geoip private
* Fix parse hysteria UDP message
* Fix socks connect response
* Disable vmess header protection if transport enabled
* Update QUIC v2 version number and initial salt
#### 1.2-beta4
* Add [NTP service](/configuration/ntp)
* Add Add multiple server names and multi-user support for shadowtls
* Add strict mode support for shadowtls v3
* Add uTLS support for shadowtls v3
#### 1.2-beta3
* Update QUIC v2 version number and initial salt
* Fix shadowtls v3 implementation
#### 1.2-beta2
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add fallback support for v2ray transport
* Fix parse hysteria UDP message
* Fix socks connect response
* Disable vmess header protection if transport enabled
#### 1.2-beta1
* Add [DHCP DNS server](/configuration/dns/server) support

View File

@@ -26,6 +26,8 @@
| `trojan` | [Trojan](./trojan) | TCP |
| `naive` | [Naive](./naive) | X |
| `hysteria` | [Hysteria](./hysteria) | X |
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
| `vless` | [VLESS](./vless) | TCP |
| `tun` | [Tun](./tun) | X |
| `redirect` | [Redirect](./redirect) | X |
| `tproxy` | [TProxy](./tproxy) | X |

View File

@@ -7,14 +7,29 @@
... // Listen Fields
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"users": [
{
"name": "sekai",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
],
"handshake": {
"server": "google.com",
"server_port": 443,
... // Dial Fields
}
},
"handshake_for_server_name": {
"example.com": {
"server": "example.com",
"server_port": 443,
... // Dial Fields
}
},
"strict_mode": false
}
```
@@ -32,15 +47,35 @@ ShadowTLS protocol version.
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
#### password
Set password.
ShadowTLS password.
Only available in the ShadowTLS v2 protocol.
Only available in the ShadowTLS protocol 2.
#### users
ShadowTLS users.
Only available in the ShadowTLS protocol 3.
#### handshake
==Required==
Handshake server address and [Dial options](/configuration/shared/dial).
Handshake server address and [Dial options](/configuration/shared/dial).
#### handshake
Handshake server address and [Dial options](/configuration/shared/dial) for specific server name.
Only available in the ShadowTLS protocol 2/3.
#### strict_mode
ShadowTLS strict mode.
Only available in the ShadowTLS protocol 3.

View File

@@ -7,14 +7,29 @@
... // 监听字段
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"users": [
{
"name": "sekai",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
],
"handshake": {
"server": "google.com",
"server_port": 443,
... // 拨号字段
}
},
"handshake_for_server_name": {
"example.com": {
"server": "example.com",
"server_port": 443,
... // 拨号字段
}
},
"strict_mode": false
}
```
@@ -32,15 +47,36 @@ ShadowTLS 协议版本。
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
#### password
设置密码。
ShadowTLS 密码。
仅在 ShadowTLS v2 协议中可用。
仅在 ShadowTLS 协议版本 2 中可用。
#### users
ShadowTLS 用户。
仅在 ShadowTLS 协议版本 3 中可用。
#### handshake
==必填==
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
#### handshake
==必填==
对于特定服务器名称的握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
仅在 ShadowTLS 协议版本 2/3 中可用。
#### strict_mode
ShadowTLS 严格模式。
仅在 ShadowTLS 协议版本 3 中可用。

View File

@@ -0,0 +1,39 @@
### Structure
```json
{
"type": "vless",
"tag": "vless-in",
... // Listen Fields
"users": [
{
"name": "sekai",
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
}
],
"tls": {},
"transport": {}
}
```
### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
#### users
==Required==
VLESS users.
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

View File

@@ -0,0 +1,39 @@
### 结构
```json
{
"type": "vless",
"tag": "vless-in",
... // 监听字段
"users": [
{
"name": "sekai",
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
}
],
"tls": {},
"transport": {}
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### users
==必填==
VLESS 用户。
#### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。

View File

@@ -8,6 +8,7 @@ sing-box uses JSON for configuration files.
{
"log": {},
"dns": {},
"ntp": {},
"inbounds": [],
"outbounds": [],
"route": {},
@@ -21,6 +22,7 @@ sing-box uses JSON for configuration files.
|----------------|--------------------------------|
| `log` | [Log](./log) |
| `dns` | [DNS](./dns) |
| `ntp` | [NTP](./ntp) |
| `inbounds` | [Inbound](./inbound) |
| `outbounds` | [Outbound](./outbound) |
| `route` | [Route](./route) |

View File

@@ -0,0 +1,50 @@
# NTP
Built-in NTP client service.
If enabled, it will provide time for protocols like TLS/Shadowsocks/VMess, which is useful for environments where time
synchronization is not possible.
### Structure
```json
{
"ntp": {
"enabled": false,
"server": "time.apple.com",
"server_port": 123,
"interval": "30m",
... // Dial Fields
}
}
```
### Fields
#### enabled
Enable NTP service.
#### server
==Required==
NTP server address.
#### server_port
NTP server port.
123 is used by default.
#### interval
Time synchronization interval.
30 minutes is used by default.
### Dial Fields
See [Dial Fields](/configuration/shared/dial) for details.

View File

@@ -0,0 +1,49 @@
# NTP
内建的 NTP 客户端服务。
如果启用,它将为像 TLS/Shadowsocks/VMess 这样的协议提供时间,这对于无法进行时间同步的环境很有用。
### 结构
```json
{
"ntp": {
"enabled": false,
"server": "time.apple.com",
"server_port": 123,
"interval": "30m",
... // 拨号字段
}
}
```
### 字段
#### enabled
启用 NTP 服务。
#### server
==必填==
NTP 服务器地址。
#### server_port
NTP 服务器端口。
默认使用 123。
#### interval
时间同步间隔。
默认使用 30 分钟。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/)。

View File

@@ -28,6 +28,7 @@
| `hysteria` | [Hysteria](./hysteria) |
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
| `vless` | [VLESS](./vless) |
| `shadowtls` | [ShadowTLS](./shadowtls) |
| `tor` | [Tor](./tor) |
| `ssh` | [SSH](./ssh) |
| `dns` | [DNS](./dns) |

View File

@@ -7,7 +7,7 @@
"server": "127.0.0.1",
"server_port": 1080,
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"tls": {},
@@ -37,12 +37,13 @@ ShadowTLS protocol version.
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
#### password
Set password.
Only available in the ShadowTLS v2 protocol.
Only available in the ShadowTLS v2/v3 protocol.
#### tls

View File

@@ -7,7 +7,7 @@
"server": "127.0.0.1",
"server_port": 1080,
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"tls": {},
@@ -37,12 +37,13 @@ ShadowTLS 协议版本。
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
#### password
设置密码。
仅在 ShadowTLS v2 协议中可用。
仅在 ShadowTLS v2/v3 协议中可用。
#### tls

View File

@@ -8,6 +8,7 @@
"server": "127.0.0.1",
"server_port": 1080,
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
"flow": "xtls-rprx-vision",
"network": "tcp",
"tls": {},
"packet_encoding": "",
@@ -17,10 +18,6 @@
}
```
!!! warning ""
The VLESS protocol is architecturally coupled to v2ray and is unmaintained. This outbound is provided for compatibility purposes only.
### Fields
#### server
@@ -41,6 +38,14 @@ The server port.
The VLESS user id.
#### flow
VLESS Sub-protocol.
Available values:
* `xtls-rprx-vision`
#### network
Enabled network

View File

@@ -8,6 +8,7 @@
"server": "127.0.0.1",
"server_port": 1080,
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
"flow": "xtls-rprx-vision",
"network": "tcp",
"tls": {},
"packet_encoding": "",
@@ -17,10 +18,6 @@
}
```
!!! warning ""
VLESS 协议与 v2ray 架构耦合且无人维护。 提供此出站仅出于兼容性目的。
### 字段
#### server
@@ -41,6 +38,14 @@
VLESS 用户 ID。
#### flow
VLESS 子协议。
可用值:
* `xtls-rprx-vision`
#### network
启用的网络协议。

View File

@@ -26,6 +26,20 @@
"key_id": "",
"mac_key": ""
}
},
"reality": {
"enabled": false,
"handshake": {
"server": "google.com",
"server_port": 443,
... // Dial Fields
},
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
"short_id": [
"0123456789abcdef"
],
"max_time_difference": "1m"
}
}
```
@@ -53,6 +67,11 @@
"utls": {
"enabled": false,
"fingerprint": ""
},
"reality": {
"enabled": false,
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
"short_id": "0123456789abcdef"
}
}
```
@@ -275,6 +294,54 @@ The key identifier.
The MAC key.
### Reality Fields
!!! warning ""
reality server is not included by default, see [Installation](/#installation).
!!! warning ""
uTLS, which is required by reality client is not included by default, see [Installation](/#installation).
#### handshake
==Server only==
==Required==
Handshake server address and [Dial options](/configuration/shared/dial).
#### private_key
==Server only==
==Required==
Private key, generated by `./xray x25519`.
#### public_key
==Client only==
==Required==
Public key, generated by `./xray x25519`.
#### short_id
==Required==
A 8-bit hex string.
#### max_time_difference
==Server only==
The maximum time difference between the server and the client.
Check disabled if empty.
### Reload
For server configuration, certificate and key will be automatically reloaded if modified.

View File

@@ -26,6 +26,20 @@
"key_id": "",
"mac_key": ""
}
},
"reality": {
"enabled": false,
"handshake": {
"server": "google.com",
"server_port": 443,
... // 拨号字段
},
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
"short_id": [
"0123456789abcdef"
],
"max_time_difference": "1m"
}
}
```
@@ -53,6 +67,11 @@
"utls": {
"enabled": false,
"fingerprint": ""
},
"reality": {
"enabled": false,
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
"short_id": "0123456789abcdef"
}
}
```
@@ -271,6 +290,52 @@ EAB外部帐户绑定包含将 ACME 帐户绑定或映射到其他已知
MAC 密钥。
### Reality 字段
!!! warning ""
默认安装不包含 reality 服务器,参阅 [安装](/zh/#_2)。
!!! warning ""
默认安装不包含被 reality 客户端需要的 uTLS, 参阅 [安装](/zh/#_2)。
#### handshake
==仅服务器==
==必填==
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
#### private_key
==仅服务器==
==必填==
私钥,由 `./xray x25519` 生成。
#### public_key
==仅客户端==
==必填==
公钥,由 `./xray x25519` 生成。
#### short_id
==必填==
一个八位十六进制的字符串。
#### max_time_difference
服务器与和客户端之间允许的最大时间差。
默认禁用检查。
### 重载
对于服务器配置,如果修改,证书和密钥将自动重新加载。

View File

@@ -7,8 +7,13 @@
"type": "shadowtls",
"listen": "::",
"listen_port": 4443,
"version": 2,
"password": "fuck me till the daylight",
"version": 3,
"users": [
{
"name": "sekai",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
],
"handshake": {
"server": "google.com",
"server_port": 443
@@ -47,11 +52,15 @@
"tag": "shadowtls-out",
"server": "127.0.0.1",
"server_port": 4443,
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"tls": {
"enabled": true,
"server_name": "google.com"
"server_name": "google.com",
"utls": {
"enabled": true,
"fingerprint": "chrome"
}
}
}
]

View File

@@ -42,7 +42,6 @@ type Server struct {
httpServer *http.Server
trafficManager *trafficontrol.Manager
urlTestHistory *urltest.HistoryStorage
tcpListener net.Listener
mode string
storeSelected bool
cacheFile adapter.ClashCacheFile
@@ -71,6 +70,11 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
if cachePath == "" {
cachePath = "cache.db"
}
if foundPath, loaded := C.FindPath(cachePath); loaded {
cachePath = foundPath
} else {
cachePath = C.BasePath(cachePath)
}
cacheFile, err := cachefile.Open(cachePath)
if err != nil {
return nil, E.Cause(err, "open cache file")
@@ -103,7 +107,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
})
if options.ExternalUI != "" {
chiRouter.Group(func(r chi.Router) {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(os.ExpandEnv(options.ExternalUI))))
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(C.BasePath(os.ExpandEnv(options.ExternalUI)))))
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
@@ -119,7 +123,6 @@ func (s *Server) Start() error {
return E.Cause(err, "external controller listen error")
}
s.logger.Info("restful api listening at ", listener.Addr())
s.tcpListener = listener
go func() {
err = s.httpServer.Serve(listener)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
@@ -132,7 +135,6 @@ func (s *Server) Start() error {
func (s *Server) Close() error {
return common.Close(
common.PtrOrNil(s.httpServer),
s.tcpListener,
s.trafficManager,
s.cacheFile,
)

View File

@@ -0,0 +1,15 @@
package libbox
import (
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func parseConfig(configContent string) (option.Options, error) {
var options option.Options
err := options.UnmarshalJSON([]byte(configContent))
if err != nil {
return option.Options{}, E.Cause(err, "decode config")
}
return options, nil
}

View File

@@ -0,0 +1,148 @@
package procfs
import (
"bufio"
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"net/netip"
"os"
"strconv"
"strings"
"unsafe"
N "github.com/sagernet/sing/common/network"
)
var (
netIndexOfLocal = -1
netIndexOfUid = -1
nativeEndian binary.ByteOrder
)
func init() {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
nativeEndian = binary.BigEndian
} else {
nativeEndian = binary.LittleEndian
}
}
func ResolveSocketByProcSearch(network string, source, _ netip.AddrPort) int32 {
if netIndexOfLocal < 0 || netIndexOfUid < 0 {
return -1
}
path := "/proc/net/"
if network == N.NetworkTCP {
path += "tcp"
} else {
path += "udp"
}
if source.Addr().Is6() {
path += "6"
}
sIP := source.Addr().AsSlice()
if len(sIP) == 0 {
return -1
}
var bytes [2]byte
binary.BigEndian.PutUint16(bytes[:], source.Port())
local := fmt.Sprintf("%s:%s", hex.EncodeToString(nativeEndianIP(sIP)), hex.EncodeToString(bytes[:]))
file, err := os.Open(path)
if err != nil {
return -1
}
defer file.Close()
reader := bufio.NewReader(file)
for {
row, _, err := reader.ReadLine()
if err != nil {
return -1
}
fields := strings.Fields(string(row))
if len(fields) <= netIndexOfLocal || len(fields) <= netIndexOfUid {
continue
}
if strings.EqualFold(local, fields[netIndexOfLocal]) {
uid, err := strconv.Atoi(fields[netIndexOfUid])
if err != nil {
return -1
}
return int32(uid)
}
}
}
func nativeEndianIP(ip net.IP) []byte {
result := make([]byte, len(ip))
for i := 0; i < len(ip); i += 4 {
value := binary.BigEndian.Uint32(ip[i:])
nativeEndian.PutUint32(result[i:], value)
}
return result
}
func init() {
file, err := os.Open("/proc/net/tcp")
if err != nil {
return
}
defer file.Close()
reader := bufio.NewReader(file)
header, _, err := reader.ReadLine()
if err != nil {
return
}
columns := strings.Fields(string(header))
var txQueue, rxQueue, tr, tmWhen bool
for idx, col := range columns {
offset := 0
if txQueue && rxQueue {
offset--
}
if tr && tmWhen {
offset--
}
switch col {
case "tx_queue":
txQueue = true
case "rx_queue":
rxQueue = true
case "tr":
tr = true
case "tm->when":
tmWhen = true
case "local_address":
netIndexOfLocal = idx + offset
case "uid":
netIndexOfUid = idx + offset
}
}
}

View File

@@ -0,0 +1,31 @@
package libbox
import "github.com/sagernet/sing/common"
type StringIterator interface {
Next() string
HasNext() bool
}
var _ StringIterator = (*iterator[string])(nil)
type iterator[T any] struct {
values []T
}
func newIterator[T any](values []T) *iterator[T] {
return &iterator[T]{values}
}
func (i *iterator[T]) Next() T {
if len(i.values) == 0 {
return common.DefaultValue[T]()
}
nextValue := i.values[0]
i.values = i.values[1:]
return nextValue
}
func (i *iterator[T]) HasNext() bool {
return len(i.values) > 0
}

View File

@@ -0,0 +1,54 @@
package libbox
import (
"bufio"
"log"
"os"
)
type StandardOutput interface {
WriteOutput(message string)
WriteErrorOutput(message string)
}
func SetOutput(output StandardOutput) {
log.SetOutput(logWriter{output})
pipeIn, pipeOut, err := os.Pipe()
if err != nil {
panic(err)
}
os.Stdout = os.NewFile(pipeOut.Fd(), "stdout")
go lineLog(pipeIn, output.WriteOutput)
pipeIn, pipeOut, err = os.Pipe()
if err != nil {
panic(err)
}
os.Stderr = os.NewFile(pipeOut.Fd(), "srderr")
go lineLog(pipeIn, output.WriteErrorOutput)
}
type logWriter struct {
output StandardOutput
}
func (w logWriter) Write(p []byte) (n int, err error) {
w.output.WriteOutput(string(p))
return len(p), nil
}
func lineLog(f *os.File, output func(string)) {
const logSize = 1024 // matches android/log.h.
r := bufio.NewReaderSize(f, logSize)
for {
line, _, err := r.ReadLine()
str := string(line)
if err != nil {
str += " " + err.Error()
}
output(str)
if err != nil {
break
}
}
}

View File

@@ -0,0 +1,59 @@
//go:build ios
package libbox
import (
"net"
"path/filepath"
"github.com/sagernet/sing/common"
)
type LogClient struct {
sockPath string
handler LogClientHandler
conn net.Conn
}
type LogClientHandler interface {
Connected()
Disconnected()
WriteLog(message string)
}
func NewLogClient(sharedDirectory string, handler LogClientHandler) *LogClient {
return &LogClient{
sockPath: filepath.Join(sharedDirectory, "log.sock"),
handler: handler,
}
}
func (c *LogClient) Connect() error {
conn, err := net.DialUnix("unix", nil, &net.UnixAddr{
Name: c.sockPath,
Net: "unix",
})
if err != nil {
return err
}
c.conn = conn
go c.loopConnection(&messageConn{conn})
return nil
}
func (c *LogClient) Disconnect() error {
return common.Close(c.conn)
}
func (c *LogClient) loopConnection(conn *messageConn) {
c.handler.Connected()
defer c.handler.Disconnected()
for {
message, err := conn.Read()
if err != nil {
c.handler.WriteLog("(log client error) " + err.Error())
return
}
c.handler.WriteLog(string(message))
}
}

View File

@@ -0,0 +1,139 @@
//go:build ios
package libbox
import (
"encoding/binary"
"io"
"net"
"os"
"path/filepath"
"sync"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/observable"
"github.com/sagernet/sing/common/x/list"
)
type LogServer struct {
sockPath string
listener net.Listener
access sync.Mutex
savedLines *list.List[string]
subscriber *observable.Subscriber[string]
observer *observable.Observer[string]
}
func NewLogServer(sharedDirectory string) *LogServer {
server := &LogServer{
sockPath: filepath.Join(sharedDirectory, "log.sock"),
savedLines: new(list.List[string]),
subscriber: observable.NewSubscriber[string](128),
}
server.observer = observable.NewObserver[string](server.subscriber, 64)
return server
}
func (s *LogServer) Start() error {
os.Remove(s.sockPath)
listener, err := net.ListenUnix("unix", &net.UnixAddr{
Name: s.sockPath,
Net: "unix",
})
if err != nil {
return err
}
go s.loopConnection(listener)
return nil
}
func (s *LogServer) Close() error {
return s.listener.Close()
}
func (s *LogServer) WriteMessage(message string) {
s.subscriber.Emit(message)
s.access.Lock()
s.savedLines.PushBack(message)
if s.savedLines.Len() > 100 {
s.savedLines.Remove(s.savedLines.Front())
}
s.access.Unlock()
}
func (s *LogServer) loopConnection(listener net.Listener) {
for {
conn, err := listener.Accept()
if err != nil {
return
}
go func() {
hErr := s.handleConnection(&messageConn{conn})
if hErr != nil && !E.IsClosed(err) {
log.Warn("log-server: process connection: ", hErr)
}
}()
}
}
func (s *LogServer) handleConnection(conn *messageConn) error {
var savedLines []string
s.access.Lock()
savedLines = make([]string, 0, s.savedLines.Len())
for element := s.savedLines.Front(); element != nil; element = element.Next() {
savedLines = append(savedLines, element.Value)
}
s.access.Unlock()
subscription, done, err := s.observer.Subscribe()
if err != nil {
return err
}
defer s.observer.UnSubscribe(subscription)
for _, line := range savedLines {
err = conn.Write([]byte(line))
if err != nil {
return err
}
}
for {
select {
case message := <-subscription:
err = conn.Write([]byte(message))
if err != nil {
return err
}
case <-done:
conn.Close()
return nil
}
}
}
type messageConn struct {
net.Conn
}
func (c *messageConn) Read() ([]byte, error) {
var messageLength uint16
err := binary.Read(c.Conn, binary.BigEndian, &messageLength)
if err != nil {
return nil, err
}
data := make([]byte, messageLength)
_, err = io.ReadFull(c.Conn, data)
if err != nil {
return nil, err
}
return data, nil
}
func (c *messageConn) Write(message []byte) error {
err := binary.Write(c.Conn, binary.BigEndian, uint16(len(message)))
if err != nil {
return err
}
_, err = c.Conn.Write(message)
return err
}

View File

@@ -0,0 +1,16 @@
package libbox
type PlatformInterface interface {
AutoDetectInterfaceControl(fd int32) error
OpenTun(options TunOptions) (TunInterface, error)
WriteLog(message string)
UseProcFS() bool
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
PackageNameByUid(uid int32) (string, error)
UIDByPackageName(packageName string) (int32, error)
}
type TunInterface interface {
FileDescriptor() int32
Close() error
}

View File

@@ -0,0 +1,16 @@
package platform
import (
"io"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
)
type Interface interface {
AutoDetectInterfaceControl() control.Func
OpenTun(options tun.Options) (tun.Tun, error)
process.Searcher
io.Writer
}

View File

@@ -0,0 +1,35 @@
//go:build debug
package libbox
import (
"net"
"net/http"
_ "net/http/pprof"
"strconv"
)
type PProfServer struct {
server *http.Server
}
func NewPProfServer(port int) *PProfServer {
return &PProfServer{
&http.Server{
Addr: ":" + strconv.Itoa(port),
},
}
}
func (s *PProfServer) Start() error {
ln, err := net.Listen("tcp", s.server.Addr)
if err != nil {
return err
}
go s.server.Serve(ln)
return nil
}
func (s *PProfServer) Close() error {
return s.server.Close()
}

View File

@@ -0,0 +1,21 @@
//go:build !debug
package libbox
import (
"os"
)
type PProfServer struct{}
func NewPProfServer(port int) *PProfServer {
return &PProfServer{}
}
func (s *PProfServer) Start() error {
return os.ErrInvalid
}
func (s *PProfServer) Close() error {
return os.ErrInvalid
}

View File

@@ -0,0 +1,122 @@
package libbox
import (
"context"
"net/netip"
"os"
"runtime"
"syscall"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
)
type BoxService struct {
ctx context.Context
cancel context.CancelFunc
instance *box.Box
}
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
options, err := parseConfig(configContent)
if err != nil {
return nil, err
}
platformInterface.WriteLog("Hello " + runtime.GOOS + "/" + runtime.GOARCH)
options.PlatformInterface = &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(ctx, options)
if err != nil {
cancel()
return nil, E.Cause(err, "create service")
}
return &BoxService{
ctx: ctx,
cancel: cancel,
instance: instance,
}, nil
}
func (s *BoxService) Start() error {
return s.instance.Start()
}
func (s *BoxService) Close() error {
s.cancel()
return s.instance.Close()
}
var _ platform.Interface = (*platformInterfaceWrapper)(nil)
type platformInterfaceWrapper struct {
iif PlatformInterface
useProcFS bool
}
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
return func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
return w.iif.AutoDetectInterfaceControl(int32(fd))
})
}
}
func (w *platformInterfaceWrapper) OpenTun(options tun.Options) (tun.Tun, error) {
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
return nil, E.New("android: unsupported uid options")
}
if len(options.IncludeAndroidUser) > 0 {
return nil, E.New("android: unsupported android_user option")
}
optionsWrapper := tunOptions(options)
tunInterface, err := w.iif.OpenTun(&optionsWrapper)
if err != nil {
return nil, err
}
tunFd := tunInterface.FileDescriptor()
return &nativeTun{
tunFd: int(tunFd),
tunFile: os.NewFile(uintptr(tunFd), "tun"),
tunMTU: options.MTU,
closer: tunInterface,
}, nil
}
func (w *platformInterfaceWrapper) Write(p []byte) (n int, err error) {
w.iif.WriteLog(string(p))
return len(p), nil
}
func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
var uid int32
if w.useProcFS {
uid = procfs.ResolveSocketByProcSearch(network, source, destination)
if uid == -1 {
return nil, E.New("procfs: not found")
}
} else {
var ipProtocol int32
switch N.NetworkName(network) {
case N.NetworkTCP:
ipProtocol = syscall.IPPROTO_TCP
case N.NetworkUDP:
ipProtocol = syscall.IPPROTO_UDP
default:
return nil, E.New("unknown network: ", network)
}
var err error
uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port()))
if err != nil {
return nil, err
}
}
packageName, _ := w.iif.PackageNameByUid(uid)
return &process.Info{UserId: uid, PackageName: packageName}, nil
}

View File

@@ -0,0 +1,11 @@
package libbox
import C "github.com/sagernet/sing-box/constant"
func SetBasePath(path string) {
C.SetBasePath(path)
}
func Version() string {
return C.Version
}

109
experimental/libbox/tun.go Normal file
View File

@@ -0,0 +1,109 @@
package libbox
import (
"io"
"net/netip"
"os"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type TunOptions interface {
GetInet4Address() RoutePrefixIterator
GetInet6Address() RoutePrefixIterator
GetDNSServerAddress() (string, error)
GetMTU() int32
GetAutoRoute() bool
GetStrictRoute() bool
GetInet4RouteAddress() RoutePrefixIterator
GetInet6RouteAddress() RoutePrefixIterator
GetIncludePackage() StringIterator
GetExcludePackage() StringIterator
}
type RoutePrefix struct {
Address string
Prefix int32
}
type RoutePrefixIterator interface {
Next() *RoutePrefix
HasNext() bool
}
func mapRoutePrefix(prefixes []netip.Prefix) RoutePrefixIterator {
return newIterator(common.Map(prefixes, func(prefix netip.Prefix) *RoutePrefix {
return &RoutePrefix{
Address: prefix.Addr().String(),
Prefix: int32(prefix.Bits()),
}
}))
}
var _ TunOptions = (*tunOptions)(nil)
type tunOptions tun.Options
func (o *tunOptions) GetInet4Address() RoutePrefixIterator {
return mapRoutePrefix(o.Inet4Address)
}
func (o *tunOptions) GetInet6Address() RoutePrefixIterator {
return mapRoutePrefix(o.Inet6Address)
}
func (o *tunOptions) GetDNSServerAddress() (string, error) {
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
return "", E.New("need one more IPv4 address for DNS hijacking")
}
return o.Inet4Address[0].Addr().Next().String(), nil
}
func (o *tunOptions) GetMTU() int32 {
return int32(o.MTU)
}
func (o *tunOptions) GetAutoRoute() bool {
return o.AutoRoute
}
func (o *tunOptions) GetStrictRoute() bool {
return o.StrictRoute
}
func (o *tunOptions) GetInet4RouteAddress() RoutePrefixIterator {
return mapRoutePrefix(o.Inet4RouteAddress)
}
func (o *tunOptions) GetInet6RouteAddress() RoutePrefixIterator {
return mapRoutePrefix(o.Inet6RouteAddress)
}
func (o *tunOptions) GetIncludePackage() StringIterator {
return newIterator(o.IncludePackage)
}
func (o *tunOptions) GetExcludePackage() StringIterator {
return newIterator(o.ExcludePackage)
}
type nativeTun struct {
tunFd int
tunFile *os.File
tunMTU uint32
closer io.Closer
}
func (t *nativeTun) Read(p []byte) (n int, err error) {
return t.tunFile.Read(p)
}
func (t *nativeTun) Write(p []byte) (n int, err error) {
return t.tunFile.Write(p)
}
func (t *nativeTun) Close() error {
return t.closer.Close()
}

View File

@@ -0,0 +1,19 @@
//go:build with_gvisor && linux
package libbox
import (
"github.com/sagernet/sing-tun"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
var _ tun.GVisorTun = (*nativeTun)(nil)
func (t *nativeTun) NewEndpoint() (stack.LinkEndpoint, error) {
return fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd},
MTU: t.tunMTU,
})
}

32
go.mod
View File

@@ -14,22 +14,25 @@ require (
github.com/go-chi/render v1.0.2
github.com/gofrs/uuid v4.4.0+incompatible
github.com/hashicorp/yamux v0.1.1
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
github.com/insomniacslk/dhcp v0.0.0-20230220010740-598984875576
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mholt/acmez v1.0.4
github.com/mholt/acmez v1.1.0
github.com/miekg/dns v1.1.50
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd
github.com/oschwald/maxminddb-golang v1.10.0
github.com/pires/go-proxyproto v0.6.2
github.com/refraction-networking/utls v1.2.1
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9
github.com/sagernet/sing-dns v0.1.3
github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7
github.com/sagernet/sing v0.1.8-0.20230221060643-3401d210384b
github.com/sagernet/sing-dns v0.1.4
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587
github.com/sagernet/sing-tun v0.1.1
github.com/sagernet/sing-vmess v0.1.1-0.20230207064843-983dde690564
github.com/sagernet/sing-vmess v0.1.2
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
github.com/spf13/cobra v1.6.1
@@ -38,20 +41,18 @@ require (
go.uber.org/atomic v1.10.0
go.uber.org/zap v1.24.0
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f
golang.org/x/crypto v0.5.0
golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3
golang.org/x/net v0.5.0
golang.org/x/crypto v0.6.0
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
golang.org/x/net v0.7.0
golang.org/x/sys v0.5.0
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.1
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
)
//replace github.com/sagernet/sing => ../sing
require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
@@ -66,6 +67,7 @@ require (
github.com/libdns/libdns v0.2.1 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
@@ -74,11 +76,11 @@ require (
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
github.com/u-root/uio v0.0.0-20230215032506-9aa6f7e2d72c // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect

78
go.sum
View File

@@ -4,10 +4,9 @@ github.com/Dreamacro/clash v1.13.0 h1:gF0E0TluE1LCmuhhg0/bjqABYDmSnXkUjXjRhZxyrm
github.com/Dreamacro/clash v1.13.0/go.mod h1:hf0RkWPsQ0e8oS8WVJBIRocY/1ILYzQQg9zeMwd8LsM=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -60,8 +59,8 @@ github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Go
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg=
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE=
github.com/insomniacslk/dhcp v0.0.0-20230220010740-598984875576 h1:ehee0+xI4xtJTRJhN+PVA2GzCLB6KRgHRoZMg2lHwJU=
github.com/insomniacslk/dhcp v0.0.0-20230220010740-598984875576/go.mod h1:yGKD3yZIGAjEZXiIjVQ0SQ09Y/RzETOoqEOi6nXqX0Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -75,7 +74,6 @@ github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -90,10 +88,12 @@ github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcK
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd h1:vd4qbG9ZTW10e1uqo8PDLshe5XL2yPhdINhGlJYaOoQ=
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd/go.mod h1:C+iqSNDBQ8qMhlNZ0JSUO9POEWq8qX87hukGfmO7/fA=
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.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
@@ -101,9 +101,10 @@ github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -115,33 +116,37 @@ github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/refraction-networking/utls v1.2.1 h1:BvjkQ5ar/45MuDZzqJlOU4a6JuwVH3AXcR8WqtRZ340=
github.com/refraction-networking/utls v1.2.1/go.mod h1:L1goe44KvhnTfctUffM2isnJpSjPlYShrhXDeZaoYKw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM=
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca h1:w56+kf8BeqLqllrRJ1tdwKc3sCdWOn/DuNHpY9fAiqs=
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8=
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9 h1:qnXh4RjHsNjdZXkfbqwVqAzYUfc160gfkS5gepmsA+A=
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9/go.mod h1:JLSXsPTGRJFo/3X7EcAOCUgJH2/gAoxSJgBsnCZRp/w=
github.com/sagernet/sing-dns v0.1.3 h1:PbBK7wqkvnwGguhWtTV88fKvSUUr62+ENWs234Ddvns=
github.com/sagernet/sing-dns v0.1.3/go.mod h1:+lFb7GBtFcuyt/mbf9M4Eal8xbycIhjBepGB+rckmBk=
github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7 h1:Plup6oEiyLzY3HDqQ+QsUBzgBGdVmcsgf3t8h940z9U=
github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7/go.mod h1:O5LtOs8Ivw686FqLpO0Zu+A0ROVE15VeqEK3yDRRAms=
github.com/sagernet/sing v0.1.8-0.20230221060643-3401d210384b h1:Ji2AfGlc4j9AitobOx4k3BCj7eS5nSxL1cgaL81zvlo=
github.com/sagernet/sing v0.1.8-0.20230221060643-3401d210384b/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0=
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9/go.mod h1:f3mHTy5shnVM9l8UocMlJgC/1G/zdj5FuEuVXhDinGU=
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587 h1:OjIXlHT2bblZfp+ciupM4xY9+Ccpj9FsuHRtKRBv+Pg=
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
github.com/sagernet/sing-tun v0.1.1 h1:2Hg3GAyJWzQ7Ua1j74dE+mI06vaqSBO9yD4tkTjggn4=
github.com/sagernet/sing-tun v0.1.1/go.mod h1:WzW/SkT+Nh9uJn/FIYUE2YJHYuPwfbh8sATOzU9QDGw=
github.com/sagernet/sing-vmess v0.1.1-0.20230207064843-983dde690564 h1:+CFee8wEc79nFDBV8tDm2yKPrAXb5Mrf2Q5kM2Bb/xo=
github.com/sagernet/sing-vmess v0.1.1-0.20230207064843-983dde690564/go.mod h1:9KkmnQzTL4Gvv8U2TRAH2BOITCGsGPpHtUPP5sxn5sY=
github.com/sagernet/sing-vmess v0.1.2 h1:RbOZNAId2LrCai8epMoQXlf0XTrou0bfcw08hNBg6lM=
github.com/sagernet/sing-vmess v0.1.2/go.mod h1:9NSj8mZTx1JIY/HF9LaYRppUsVkysIN5tEFpNZujXxY=
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01 h1:m4MI13+NRKddIvbdSN0sFHK8w5ROTa60Zi9diZ7EE08=
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
@@ -163,8 +168,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww=
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E=
github.com/u-root/uio v0.0.0-20230215032506-9aa6f7e2d72c h1:PHoGTnweZP+KIg/8Zc6+iOesrIF5yHkpb4GBDxHm7yE=
github.com/u-root/uio v0.0.0-20230215032506-9aa6f7e2d72c/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -174,10 +179,8 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f h1:ketMxHg+vWm3yccyYiq+uK8D3fRmna2Fcj+awpQp84s=
@@ -187,11 +190,10 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3 h1:fJwx88sMf5RXwDwziL0/Mn9Wqs+efMSo/RYcL+37W9c=
golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
@@ -209,9 +211,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
@@ -234,9 +235,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -244,22 +243,18 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
@@ -276,13 +271,10 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=

View File

@@ -5,18 +5,19 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Inbound) (adapter.Inbound, error) {
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) {
if options.Type == "" {
return nil, E.New("missing inbound type")
}
switch options.Type {
case C.TypeTun:
return NewTun(ctx, router, logger, options.Tag, options.TunOptions)
return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface)
case C.TypeRedirect:
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil
case C.TypeTProxy:
@@ -41,6 +42,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
case C.TypeShadowTLS:
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
case C.TypeVLESS:
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
default:
return nil, E.New("unknown inbound type: ", options.Type)
}

View File

@@ -44,7 +44,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
authenticator: auth.NewAuthenticator(options.Users),
}
if options.TLS != nil {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}

View File

@@ -126,7 +126,7 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
if len(options.TLS.ALPN) == 0 {
options.TLS.ALPN = []string{hysteria.DefaultALPN}
}
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}

View File

@@ -60,7 +60,7 @@ func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogg
return nil, E.New("missing users")
}
if options.TLS != nil {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}

View File

@@ -68,7 +68,7 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
case common.Contains(shadowaead.List, options.Method):
inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, udpTimeout, inbound.upstreamContextHandler())
case common.Contains(shadowaead_2022.List, options.Method):
inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, udpTimeout, inbound.upstreamContextHandler())
inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, udpTimeout, inbound.upstreamContextHandler(), router.TimeFunc())
default:
err = E.New("unsupported method: ", options.Method)
}

View File

@@ -57,6 +57,7 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
options.Password,
udpTimeout,
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
router.TimeFunc(),
)
if err != nil {
return nil, err

View File

@@ -1,35 +1,22 @@
package inbound
import (
"bytes"
"context"
"encoding/binary"
"io"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/shadowtls"
"github.com/sagernet/sing-shadowtls"
"github.com/sagernet/sing/common"
"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/task"
)
type ShadowTLS struct {
myInboundAdapter
handshakeDialer N.Dialer
handshakeAddr M.Socksaddr
v2 bool
password string
fallbackAfter int
service *shadowtls.Service
}
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (*ShadowTLS, error) {
@@ -43,138 +30,41 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
tag: tag,
listenOptions: options.ListenOptions,
},
handshakeDialer: dialer.New(router, options.Handshake.DialerOptions),
handshakeAddr: options.Handshake.ServerOptions.Build(),
password: options.Password,
}
switch options.Version {
case 0:
fallthrough
case 1:
case 2:
inbound.v2 = true
if options.FallbackAfter == nil {
inbound.fallbackAfter = 2
} else {
inbound.fallbackAfter = *options.FallbackAfter
var handshakeForServerName map[string]shadowtls.HandshakeConfig
if options.Version > 1 {
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
for serverName, serverOptions := range options.HandshakeForServerName {
handshakeForServerName[serverName] = shadowtls.HandshakeConfig{
Server: serverOptions.ServerOptions.Build(),
Dialer: dialer.New(router, serverOptions.DialerOptions),
}
}
default:
return nil, E.New("unknown shadowtls protocol version: ", options.Version)
}
service, err := shadowtls.NewService(shadowtls.ServiceConfig{
Version: options.Version,
Password: options.Password,
Users: common.Map(options.Users, func(it option.ShadowTLSUser) shadowtls.User {
return (shadowtls.User)(it)
}),
Handshake: shadowtls.HandshakeConfig{
Server: options.Handshake.ServerOptions.Build(),
Dialer: dialer.New(router, options.Handshake.DialerOptions),
},
HandshakeForServerName: handshakeForServerName,
StrictMode: options.StrictMode,
Handler: inbound.upstreamContextHandler(),
Logger: logger,
})
if err != nil {
return nil, err
}
inbound.service = service
inbound.connHandler = inbound
return inbound, nil
}
func (s *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
handshakeConn, err := s.handshakeDialer.DialContext(ctx, N.NetworkTCP, s.handshakeAddr)
if err != nil {
return err
}
if !s.v2 {
var handshake task.Group
handshake.Append("client handshake", func(ctx context.Context) error {
return s.copyUntilHandshakeFinished(handshakeConn, conn)
})
handshake.Append("server handshake", func(ctx context.Context) error {
return s.copyUntilHandshakeFinished(conn, handshakeConn)
})
handshake.FastFail()
handshake.Cleanup(func() {
handshakeConn.Close()
})
err = handshake.Run(ctx)
if err != nil {
return err
}
return s.newConnection(ctx, conn, metadata)
} else {
hashConn := shadowtls.NewHashWriteConn(conn, s.password)
go bufio.Copy(hashConn, handshakeConn)
var request *buf.Buffer
request, err = s.copyUntilHandshakeFinishedV2(ctx, handshakeConn, conn, hashConn, s.fallbackAfter)
if err == nil {
handshakeConn.Close()
return s.newConnection(ctx, bufio.NewCachedConn(shadowtls.NewConn(conn), request), metadata)
} else if err == os.ErrPermission {
s.logger.WarnContext(ctx, "fallback connection")
hashConn.Fallback()
return common.Error(bufio.Copy(handshakeConn, conn))
} else {
return err
}
}
}
func (s *ShadowTLS) copyUntilHandshakeFinished(dst io.Writer, src io.Reader) error {
const handshake = 0x16
const changeCipherSpec = 0x14
var hasSeenChangeCipherSpec bool
var tlsHdr [5]byte
for {
_, err := io.ReadFull(src, tlsHdr[:])
if err != nil {
return err
}
length := binary.BigEndian.Uint16(tlsHdr[3:])
_, err = io.Copy(dst, io.MultiReader(bytes.NewReader(tlsHdr[:]), io.LimitReader(src, int64(length))))
if err != nil {
return err
}
if tlsHdr[0] != handshake {
if tlsHdr[0] != changeCipherSpec {
return E.New("unexpected tls frame type: ", tlsHdr[0])
}
if !hasSeenChangeCipherSpec {
hasSeenChangeCipherSpec = true
continue
}
}
if hasSeenChangeCipherSpec {
return nil
}
}
}
func (s *ShadowTLS) copyUntilHandshakeFinishedV2(ctx context.Context, dst net.Conn, src io.Reader, hash *shadowtls.HashWriteConn, fallbackAfter int) (*buf.Buffer, error) {
const applicationData = 0x17
var tlsHdr [5]byte
var applicationDataCount int
for {
_, err := io.ReadFull(src, tlsHdr[:])
if err != nil {
return nil, err
}
length := binary.BigEndian.Uint16(tlsHdr[3:])
if tlsHdr[0] == applicationData {
data := buf.NewSize(int(length))
_, err = data.ReadFullFrom(src, int(length))
if err != nil {
data.Release()
return nil, err
}
if hash.HasContent() && length >= 8 {
checksum := hash.Sum()
if bytes.Equal(data.To(8), checksum) {
s.logger.TraceContext(ctx, "match current hashcode")
data.Advance(8)
return data, nil
} else if hash.LastSum() != nil && bytes.Equal(data.To(8), hash.LastSum()) {
s.logger.TraceContext(ctx, "match last hashcode")
data.Advance(8)
return data, nil
}
}
_, err = io.Copy(dst, io.MultiReader(bytes.NewReader(tlsHdr[:]), data))
data.Release()
applicationDataCount++
} else {
_, err = io.Copy(dst, io.MultiReader(bytes.NewReader(tlsHdr[:]), io.LimitReader(src, int64(length))))
}
if err != nil {
return nil, err
}
if applicationDataCount > fallbackAfter {
return nil, os.ErrPermission
}
}
func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
}

View File

@@ -100,9 +100,10 @@ type tproxyPacketWriter struct {
func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if w.destination == destination && w.conn != nil {
_, err := w.conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))
if err == nil {
conn := w.conn
if w.destination == destination && conn != nil {
_, err := conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))
if err != nil {
w.conn = nil
}
return err

View File

@@ -49,7 +49,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
users: options.Users,
}
if options.TLS != nil {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
@@ -89,7 +89,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
return nil, err
}
if options.Transport != nil {
inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newTransportConnection, nil, nil), inbound)
inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound))
if err != nil {
return nil, E.Cause(err, "create server transport: ", options.Transport.Type)
}
@@ -216,3 +216,21 @@ func (h *Trojan) newPacketConnection(ctx context.Context, conn N.PacketConn, met
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
return h.router.RoutePacketConnection(ctx, conn, metadata)
}
var _ adapter.V2RayServerTransportHandler = (*trojanTransportHandler)(nil)
type trojanTransportHandler Trojan
func (t *trojanTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
return (*Trojan)(t).newTransportConnection(ctx, conn, adapter.InboundContext{
Source: metadata.Source,
Destination: metadata.Destination,
})
}
func (t *trojanTransportHandler) FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
return (*Trojan)(t).fallbackConnection(ctx, conn, adapter.InboundContext{
Source: metadata.Source,
Destination: metadata.Destination,
})
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/canceler"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
@@ -34,9 +35,10 @@ type Tun struct {
stack string
tunIf tun.Tun
tunStack tun.Stack
platformInterface platform.Interface
}
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (*Tun, error) {
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
tunName := options.InterfaceName
if tunName == "" {
tunName = tun.CalculateInterfaceName("")
@@ -93,6 +95,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
endpointIndependentNat: options.EndpointIndependentNat,
udpTimeout: udpTimeout,
stack: options.Stack,
platformInterface: platformInterface,
}, nil
}
@@ -137,17 +140,25 @@ func (t *Tun) Tag() string {
}
func (t *Tun) Start() error {
if C.IsAndroid {
if C.IsAndroid && t.platformInterface == nil {
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
}
tunIf, err := tun.Open(t.tunOptions)
var (
tunInterface tun.Tun
err error
)
if t.platformInterface != nil {
tunInterface, err = t.platformInterface.OpenTun(t.tunOptions)
} else {
tunInterface, err = tun.Open(t.tunOptions)
}
if err != nil {
return E.Cause(err, "configure tun interface")
}
t.tunIf = tunIf
t.tunIf = tunInterface
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
Context: t.ctx,
Tun: tunIf,
Tun: tunInterface,
MTU: t.tunOptions.MTU,
Name: t.tunOptions.Name,
Inet4Address: t.tunOptions.Inet4Address,

193
inbound/vless.go Normal file
View File

@@ -0,0 +1,193 @@
package inbound
import (
"context"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"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/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var (
_ adapter.Inbound = (*VLESS)(nil)
_ adapter.InjectableInbound = (*VLESS)(nil)
)
type VLESS struct {
myInboundAdapter
ctx context.Context
users []option.VLESSUser
service *vless.Service[int]
tlsConfig tls.ServerConfig
transport adapter.V2RayServerTransport
}
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (*VLESS, error) {
inbound := &VLESS{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeVLESS,
network: []string{N.NetworkTCP},
ctx: ctx,
router: router,
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
ctx: ctx,
users: options.Users,
}
service := vless.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound))
service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int {
return index
}), common.Map(inbound.users, func(it option.VLESSUser) string {
return it.UUID
}))
inbound.service = service
var err error
if options.TLS != nil {
inbound.tlsConfig, err = tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
}
if options.Transport != nil {
inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound))
if err != nil {
return nil, E.Cause(err, "create server transport: ", options.Transport.Type)
}
}
inbound.connHandler = inbound
return inbound, nil
}
func (h *VLESS) Start() error {
err := common.Start(
h.service,
h.tlsConfig,
)
if err != nil {
return err
}
if h.transport == nil {
return h.myInboundAdapter.Start()
}
if common.Contains(h.transport.Network(), N.NetworkTCP) {
tcpListener, err := h.myInboundAdapter.ListenTCP()
if err != nil {
return err
}
go func() {
sErr := h.transport.Serve(tcpListener)
if sErr != nil && !E.IsClosed(sErr) {
h.logger.Error("transport serve error: ", sErr)
}
}()
}
if common.Contains(h.transport.Network(), N.NetworkUDP) {
udpConn, err := h.myInboundAdapter.ListenUDP()
if err != nil {
return err
}
go func() {
sErr := h.transport.ServePacket(udpConn)
if sErr != nil && !E.IsClosed(sErr) {
h.logger.Error("transport serve error: ", sErr)
}
}()
}
return nil
}
func (h *VLESS) Close() error {
return common.Close(
h.service,
&h.myInboundAdapter,
h.tlsConfig,
h.transport,
)
}
func (h *VLESS) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
h.injectTCP(conn, metadata)
return nil
}
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
var err error
if h.tlsConfig != nil && h.transport == nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
return err
}
}
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
}
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return os.ErrInvalid
}
func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded {
return os.ErrInvalid
}
user := h.users[userIndex].Name
if user == "" {
user = F.ToString(userIndex)
} else {
metadata.User = user
}
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
return h.router.RouteConnection(ctx, conn, metadata)
}
func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded {
return os.ErrInvalid
}
user := h.users[userIndex].Name
if user == "" {
user = F.ToString(userIndex)
} else {
metadata.User = user
}
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
metadata.Destination = M.Socksaddr{}
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
} else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
}
return h.router.RoutePacketConnection(ctx, conn, metadata)
}
var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil)
type vlessTransportHandler VLESS
func (t *vlessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
return (*VLESS)(t).newTransportConnection(ctx, conn, adapter.InboundContext{
Source: metadata.Source,
Destination: metadata.Destination,
})
}
func (t *vlessTransportHandler) FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
return os.ErrInvalid
}

View File

@@ -49,7 +49,14 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
ctx: ctx,
users: options.Users,
}
service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound))
var serviceOptions []vmess.ServiceOption
if timeFunc := router.TimeFunc(); timeFunc != nil {
serviceOptions = append(serviceOptions, vmess.ServiceWithTimeFunc(timeFunc))
}
if options.Transport != nil && options.Transport.Type != "" {
serviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection())
}
service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), serviceOptions...)
inbound.service = service
err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int {
return index
@@ -62,13 +69,13 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
return nil, err
}
if options.TLS != nil {
inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
inbound.tlsConfig, err = tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
}
if options.Transport != nil {
inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newTransportConnection, nil, nil), inbound)
inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound))
if err != nil {
return nil, E.Cause(err, "create server transport: ", options.Transport.Type)
}
@@ -179,3 +186,18 @@ func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, meta
}
return h.router.RoutePacketConnection(ctx, conn, metadata)
}
var _ adapter.V2RayServerTransportHandler = (*vmessTransportHandler)(nil)
type vmessTransportHandler VMess
func (t *vmessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
return (*VMess)(t).newTransportConnection(ctx, conn, adapter.InboundContext{
Source: metadata.Source,
Destination: metadata.Destination,
})
}
func (t *vmessTransportHandler) FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
return os.ErrInvalid
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2ray"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -24,7 +23,7 @@ func init() {
return nil, C.ErrQUICNotIncluded
})
v2ray.RegisterQUICConstructor(
func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {
return nil, C.ErrQUICNotIncluded
},
func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {

View File

@@ -6,22 +6,30 @@ import (
"os"
"time"
C "github.com/sagernet/sing-box/constant"
F "github.com/sagernet/sing/common/format"
)
var _ Factory = (*simpleFactory)(nil)
type simpleFactory struct {
formatter Formatter
writer io.Writer
level Level
formatter Formatter
platformFormatter Formatter
writer io.Writer
platformWriter io.Writer
level Level
}
func NewFactory(formatter Formatter, writer io.Writer) Factory {
func NewFactory(formatter Formatter, writer io.Writer, platformWriter io.Writer) Factory {
return &simpleFactory{
formatter: formatter,
writer: writer,
level: LevelTrace,
platformFormatter: Formatter{
BaseTime: formatter.BaseTime,
DisableColors: C.IsIos,
},
writer: writer,
platformWriter: platformWriter,
level: LevelTrace,
}
}
@@ -53,7 +61,8 @@ func (l *simpleLogger) Log(ctx context.Context, level Level, args []any) {
if level > l.level {
return
}
message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), time.Now())
nowTime := time.Now()
message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)
if level == LevelPanic {
panic(message)
}
@@ -61,6 +70,9 @@ func (l *simpleLogger) Log(ctx context.Context, level Level, args []any) {
if level == LevelFatal {
os.Exit(1)
}
if l.platformWriter != nil {
l.platformWriter.Write([]byte(l.platformFormatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)))
}
}
func (l *simpleLogger) Trace(args ...any) {

View File

@@ -9,7 +9,7 @@ import (
var std ContextLogger
func init() {
std = NewFactory(Formatter{BaseTime: time.Now()}, os.Stderr).Logger()
std = NewFactory(Formatter{BaseTime: time.Now()}, os.Stderr, nil).Logger()
}
func StdLogger() ContextLogger {

View File

@@ -14,19 +14,25 @@ import (
var _ Factory = (*observableFactory)(nil)
type observableFactory struct {
formatter Formatter
writer io.Writer
level Level
subscriber *observable.Subscriber[Entry]
observer *observable.Observer[Entry]
formatter Formatter
platformFormatter Formatter
writer io.Writer
platformWriter io.Writer
level Level
subscriber *observable.Subscriber[Entry]
observer *observable.Observer[Entry]
}
func NewObservableFactory(formatter Formatter, writer io.Writer) ObservableFactory {
func NewObservableFactory(formatter Formatter, writer io.Writer, platformWriter io.Writer) ObservableFactory {
factory := &observableFactory{
formatter: formatter,
writer: writer,
level: LevelTrace,
subscriber: observable.NewSubscriber[Entry](128),
formatter: formatter,
platformFormatter: Formatter{
BaseTime: formatter.BaseTime,
},
writer: writer,
platformWriter: platformWriter,
level: LevelTrace,
subscriber: observable.NewSubscriber[Entry](128),
}
factory.observer = observable.NewObserver[Entry](factory.subscriber, 64)
return factory
@@ -74,7 +80,8 @@ func (l *observableLogger) Log(ctx context.Context, level Level, args []any) {
if level > l.level {
return
}
message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), time.Now())
nowTime := time.Now()
message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime)
if level == LevelPanic {
panic(message)
}
@@ -83,6 +90,9 @@ func (l *observableLogger) Log(ctx context.Context, level Level, args []any) {
os.Exit(1)
}
l.subscriber.Emit(Entry{level, messageSimple})
if l.platformWriter != nil {
l.platformWriter.Write([]byte(l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)))
}
}
func (l *observableLogger) Trace(args ...any) {

View File

@@ -12,7 +12,7 @@ func ContextWithOverrideLevel(ctx context.Context, level Level) context.Context
func OverrideLevelFromContext(origin Level, ctx context.Context) Level {
level, loaded := ctx.Value((*overrideLevelKey)(nil)).(Level)
if !loaded || origin < level {
if !loaded || origin > level {
return origin
}
return level

View File

@@ -43,6 +43,8 @@ nav:
- configuration/dns/index.md
- DNS Server: configuration/dns/server.md
- DNS Rule: configuration/dns/rule.md
- NTP:
- configuration/ntp/index.md
- Route:
- configuration/route/index.md
- GeoIP: configuration/route/geoip.md
@@ -69,6 +71,7 @@ nav:
- Naive: configuration/inbound/naive.md
- Hysteria: configuration/inbound/hysteria.md
- ShadowTLS: configuration/inbound/shadowtls.md
- VLESS: configuration/inbound/vless.md
- Tun: configuration/inbound/tun.md
- Redirect: configuration/inbound/redirect.md
- TProxy: configuration/inbound/tproxy.md
@@ -149,21 +152,27 @@ plugins:
Features: 特性
Support: 支持
Change Log: 更新日志
Configuration: 配置
Log: 日志
DNS Server: DNS 服务器
DNS Rule: DNS 规则
Route: 路由
Route Rule: 路由规则
Protocol Sniff: 协议探测
Experimental: 实验性
Shared: 通用
Listen Fields: 监听字段
Dial Fields: 拨号字段
Multiplex: 多路复用
V2Ray Transport: V2Ray 传输层
Inbound: 入站
Outbound: 出站
FAQ: 常见问题
Known Issues: 已知问题
Examples: 示例

99
ntp/service.go Normal file
View File

@@ -0,0 +1,99 @@
package ntp
import (
"context"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
)
const timeLayout = "2006-01-02 15:04:05 -0700"
var _ adapter.TimeService = (*Service)(nil)
type Service struct {
ctx context.Context
cancel context.CancelFunc
server M.Socksaddr
dialer N.Dialer
logger logger.Logger
ticker *time.Ticker
clockOffset time.Duration
}
func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service {
ctx, cancel := context.WithCancel(ctx)
server := options.ServerOptions.Build()
if server.Port == 0 {
server.Port = 123
}
var interval time.Duration
if options.Interval > 0 {
interval = time.Duration(options.Interval)
} else {
interval = 30 * time.Minute
}
return &Service{
ctx: ctx,
cancel: cancel,
server: server,
dialer: dialer.New(router, options.DialerOptions),
logger: logger,
ticker: time.NewTicker(interval),
}
}
func (s *Service) Start() error {
err := s.update()
if err != nil {
return E.Cause(err, "initialize time")
}
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(timeLayout))
go s.loopUpdate()
return nil
}
func (s *Service) Close() error {
s.ticker.Stop()
s.cancel()
return nil
}
func (s *Service) TimeFunc() func() time.Time {
return func() time.Time {
return time.Now().Add(s.clockOffset)
}
}
func (s *Service) loopUpdate() {
for {
select {
case <-s.ctx.Done():
return
case <-s.ticker.C:
}
err := s.update()
if err == nil {
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(timeLayout))
} else {
s.logger.Warn("update time: ", err)
}
}
}
func (s *Service) update() error {
response, err := ntp.Exchange(s.ctx, s.dialer, s.server)
if err != nil {
return err
}
s.clockOffset = response.ClockOffset
return nil
}

View File

@@ -5,16 +5,19 @@ import (
"strings"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/experimental/libbox/platform"
E "github.com/sagernet/sing/common/exceptions"
)
type _Options struct {
Log *LogOptions `json:"log,omitempty"`
DNS *DNSOptions `json:"dns,omitempty"`
Inbounds []Inbound `json:"inbounds,omitempty"`
Outbounds []Outbound `json:"outbounds,omitempty"`
Route *RouteOptions `json:"route,omitempty"`
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
Log *LogOptions `json:"log,omitempty"`
DNS *DNSOptions `json:"dns,omitempty"`
NTP *NTPOptions `json:"ntp,omitempty"`
Inbounds []Inbound `json:"inbounds,omitempty"`
Outbounds []Outbound `json:"outbounds,omitempty"`
Route *RouteOptions `json:"route,omitempty"`
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
PlatformInterface platform.Interface `json:"-"`
}
type Options _Options

View File

@@ -22,6 +22,7 @@ type _Inbound struct {
NaiveOptions NaiveInboundOptions `json:"-"`
HysteriaOptions HysteriaInboundOptions `json:"-"`
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
VLESSOptions VLESSInboundOptions `json:"-"`
}
type Inbound _Inbound
@@ -55,6 +56,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
v = h.HysteriaOptions
case C.TypeShadowTLS:
v = h.ShadowTLSOptions
case C.TypeVLESS:
v = h.VLESSOptions
default:
return nil, E.New("unknown inbound type: ", h.Type)
}
@@ -94,6 +97,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
v = &h.HysteriaOptions
case C.TypeShadowTLS:
v = &h.ShadowTLSOptions
case C.TypeVLESS:
v = &h.VLESSOptions
default:
return E.New("unknown inbound type: ", h.Type)
}

8
option/ntp.go Normal file
View File

@@ -0,0 +1,8 @@
package option
type NTPOptions struct {
Enabled bool `json:"enabled"`
Interval Duration `json:"interval,omitempty"`
ServerOptions
DialerOptions
}

View File

@@ -2,10 +2,17 @@ package option
type ShadowTLSInboundOptions struct {
ListenOptions
Version int `json:"version,omitempty"`
Password string `json:"password,omitempty"`
FallbackAfter *int `json:"fallback_after,omitempty"`
Handshake ShadowTLSHandshakeOptions `json:"handshake"`
Version int `json:"version,omitempty"`
Password string `json:"password,omitempty"`
Users []ShadowTLSUser `json:"users,omitempty"`
Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"`
HandshakeForServerName map[string]ShadowTLSHandshakeOptions `json:"handshake_for_server_name,omitempty"`
StrictMode bool `json:"strict_mode,omitempty"`
}
type ShadowTLSUser struct {
Name string `json:"name,omitempty"`
Password string `json:"password,omitempty"`
}
type ShadowTLSHandshakeOptions struct {

View File

@@ -1,33 +1,48 @@
package option
type InboundTLSOptions struct {
Enabled bool `json:"enabled,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
ALPN Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"`
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
Certificate string `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
Key string `json:"key,omitempty"`
KeyPath string `json:"key_path,omitempty"`
ACME *InboundACMEOptions `json:"acme,omitempty"`
Enabled bool `json:"enabled,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
ALPN Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"`
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
Certificate string `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
Key string `json:"key,omitempty"`
KeyPath string `json:"key_path,omitempty"`
ACME *InboundACMEOptions `json:"acme,omitempty"`
Reality *InboundRealityOptions `json:"reality,omitempty"`
}
type OutboundTLSOptions struct {
Enabled bool `json:"enabled,omitempty"`
DisableSNI bool `json:"disable_sni,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
ALPN Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"`
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
Certificate string `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
ECH *OutboundECHOptions `json:"ech,omitempty"`
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
Enabled bool `json:"enabled,omitempty"`
DisableSNI bool `json:"disable_sni,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
ALPN Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"`
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
Certificate string `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
ECH *OutboundECHOptions `json:"ech,omitempty"`
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
Reality *OutboundRealityOptions `json:"reality,omitempty"`
}
type InboundRealityOptions struct {
Enabled bool `json:"enabled,omitempty"`
Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"`
PrivateKey string `json:"private_key,omitempty"`
ShortID Listable[string] `json:"short_id,omitempty"`
MaxTimeDifference Duration `json:"max_time_difference,omitempty"`
}
type InboundRealityHandshakeOptions struct {
ServerOptions
DialerOptions
}
type OutboundECHOptions struct {
@@ -41,3 +56,9 @@ type OutboundUTLSOptions struct {
Enabled bool `json:"enabled,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
}
type OutboundRealityOptions struct {
Enabled bool `json:"enabled,omitempty"`
PublicKey string `json:"public_key,omitempty"`
ShortID string `json:"short_id,omitempty"`
}

View File

@@ -1,9 +1,22 @@
package option
type VLESSInboundOptions struct {
ListenOptions
Users []VLESSUser `json:"users,omitempty"`
TLS *InboundTLSOptions `json:"tls,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"`
}
type VLESSUser struct {
Name string `json:"name"`
UUID string `json:"uuid"`
}
type VLESSOutboundOptions struct {
DialerOptions
ServerOptions
UUID string `json:"uuid"`
Flow string `json:"flow,omitempty"`
Network NetworkList `json:"network,omitempty"`
TLS *OutboundTLSOptions `json:"tls,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"`

View File

@@ -34,7 +34,7 @@ type Shadowsocks struct {
}
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) {
method, err := shadowimpl.FetchMethod(options.Method, options.Password)
method, err := shadowimpl.FetchMethod(options.Method, options.Password, router.TimeFunc())
if err != nil {
return nil, err
}

View File

@@ -11,9 +11,8 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/shadowtls"
"github.com/sagernet/sing-shadowtls"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -22,11 +21,7 @@ var _ adapter.Outbound = (*ShadowTLS)(nil)
type ShadowTLS struct {
myOutboundAdapter
dialer N.Dialer
serverAddr M.Socksaddr
tlsConfig tls.Config
v2 bool
password string
client *shadowtls.Client
}
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) {
@@ -38,56 +33,61 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
logger: logger,
tag: tag,
},
dialer: dialer.New(router, options.DialerOptions),
serverAddr: options.ServerOptions.Build(),
password: options.Password,
}
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}
switch options.Version {
case 0:
fallthrough
case 1:
if options.Version == 1 {
options.TLS.MinVersion = "1.2"
options.TLS.MaxVersion = "1.2"
case 2:
outbound.v2 = true
default:
return nil, E.New("unknown shadowtls protocol version: ", options.Version)
}
var err error
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
tlsConfig, err := tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
var tlsHandshakeFunc shadowtls.TLSHandshakeFunc
switch options.Version {
case 1, 2:
tlsHandshakeFunc = func(ctx context.Context, conn net.Conn, _ shadowtls.TLSSessionIDGeneratorFunc) error {
return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))
}
case 3:
if idConfig, loaded := tlsConfig.(tls.ConfigWithSessionIDGenerator); loaded {
tlsHandshakeFunc = func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
idConfig.SetSessionIDGenerator(sessionIDGenerator)
return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))
}
} else {
stdTLSConfig, err := tlsConfig.Config()
if err != nil {
return nil, err
}
tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig)
}
}
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
Version: options.Version,
Password: options.Password,
Server: options.ServerOptions.Build(),
Dialer: dialer.New(router, options.DialerOptions),
TLSHandshake: tlsHandshakeFunc,
Logger: logger,
})
if err != nil {
return nil, err
}
outbound.client = client
return outbound, nil
}
func (s *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP:
return s.client.DialContext(ctx)
default:
return nil, os.ErrInvalid
}
conn, err := s.dialer.DialContext(ctx, N.NetworkTCP, s.serverAddr)
if err != nil {
return nil, err
}
if !s.v2 {
_, err = tls.ClientHandshake(ctx, conn, s.tlsConfig)
if err != nil {
return nil, err
}
return conn, nil
} else {
hashConn := shadowtls.NewHashReadConn(conn, s.password)
_, err = tls.ClientHandshake(ctx, hashConn, s.tlsConfig)
if err != nil {
return nil, err
}
return shadowtls.NewClientConn(hashConn), nil
}
}
func (s *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {

View File

@@ -11,9 +11,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-dns"
"github.com/sagernet/sing-vmess/packetaddr"
"github.com/sagernet/sing-vmess/vless"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -67,7 +67,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
default:
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
}
outbound.client, err = vless.NewClient(options.UUID)
outbound.client, err = vless.NewClient(options.UUID, options.Flow)
if err != nil {
return nil, err
}
@@ -92,16 +92,12 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S
return nil, err
}
switch N.NetworkName(network) {
case N.NetworkTCP:
case N.NetworkUDP:
}
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
return h.client.DialEarlyConn(conn, destination), nil
return h.client.DialEarlyConn(conn, destination)
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return h.client.DialEarlyPacketConn(conn, destination), nil
return h.client.DialEarlyPacketConn(conn, destination)
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
@@ -126,11 +122,15 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
return nil, err
}
if h.xudp {
return h.client.DialEarlyXUDPPacketConn(conn, destination), nil
return h.client.DialEarlyXUDPPacketConn(conn, destination)
} else if h.packetAddr {
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
conn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress})
if err != nil {
return nil, err
}
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(conn, destination)), nil
} else {
return h.client.DialEarlyPacketConn(conn, destination), nil
return h.client.DialEarlyPacketConn(conn, destination)
}
}

View File

@@ -74,6 +74,9 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
}
var clientOptions []vmess.ClientOption
if timeFunc := router.TimeFunc(); timeFunc != nil {
clientOptions = append(clientOptions, vmess.ClientWithTimeFunc(timeFunc))
}
if options.GlobalPadding {
clientOptions = append(clientOptions, vmess.ClientWithGlobalPadding())
}

View File

@@ -1,3 +1,3 @@
#!/bin/sh
mkdir "/var/lib/sing-box"
mkdir -p /var/lib/sing-box

View File

@@ -1,3 +1,3 @@
#!/bin/sh
rm -rf "/var/lib/sing-box"
rm -rf /var/lib/sing-box

View File

@@ -5,8 +5,8 @@ After=network.target nss-lookup.target
[Service]
WorkingDirectory=/var/lib/sing-box
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/sing-box run -c /etc/sing-box/config.json
Restart=on-failure
RestartSec=10s

View File

@@ -5,8 +5,8 @@ After=network.target nss-lookup.target
[Service]
WorkingDirectory=/var/lib/sing-box-%i
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/sing-box run -c /etc/sing-box/%i.json
Restart=on-failure
RestartSec=10s

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