Compare commits
137 Commits
v1.0-beta2
...
v1.1-beta9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39c141651a | ||
|
|
b0ad9bb6f1 | ||
|
|
d135d0f287 | ||
|
|
b183ccf23d | ||
|
|
c2969bc186 | ||
|
|
bd86bfcd22 | ||
|
|
8aec64b855 | ||
|
|
1445bdba37 | ||
|
|
29d08e63b5 | ||
|
|
1173fdea64 | ||
|
|
968430c338 | ||
|
|
3e5bee6faf | ||
|
|
aa613cba73 | ||
|
|
1e510511ae | ||
|
|
1b44faed17 | ||
|
|
c7a485815c | ||
|
|
7f9c870bba | ||
|
|
b5564ef3d3 | ||
|
|
8ce244dd04 | ||
|
|
0f57b93925 | ||
|
|
c90a77a185 | ||
|
|
c6586f19fa | ||
|
|
cbab86ae38 | ||
|
|
17b5f031f1 | ||
|
|
b00b6b9e25 | ||
|
|
fb6b3b0401 | ||
|
|
22ea878fe9 | ||
|
|
abe3dc6039 | ||
|
|
852829b9dc | ||
|
|
407509c985 | ||
|
|
9856b73cb5 | ||
|
|
f42356fbcb | ||
|
|
d0b467671a | ||
|
|
c18c545798 | ||
|
|
693ef293ac | ||
|
|
a006627795 | ||
|
|
0738b184e4 | ||
|
|
42524ba04e | ||
|
|
63fc95b96d | ||
|
|
ab436fc137 | ||
|
|
1546770bfd | ||
|
|
f4b2099488 | ||
|
|
a2c4d68031 | ||
|
|
cfe14f2817 | ||
|
|
a5402ffb69 | ||
|
|
4d24cf5ec4 | ||
|
|
668d354771 | ||
|
|
ad14719b14 | ||
|
|
d9aa0a67d6 | ||
|
|
92bf784f4f | ||
|
|
395b13103a | ||
|
|
628cf56d3c | ||
|
|
ac5582537f | ||
|
|
9aa7a20d96 | ||
|
|
189f02c802 | ||
|
|
2373281c41 | ||
|
|
e8f4c2d36f | ||
|
|
07b6db23c1 | ||
|
|
9a3360e5d0 | ||
|
|
007a278ac8 | ||
|
|
1db7f45370 | ||
|
|
b271e19a23 | ||
|
|
79b6bdfda1 | ||
|
|
38088f28b0 | ||
|
|
dfb8b5f2fa | ||
|
|
9913e0e025 | ||
|
|
ce567ffdde | ||
|
|
5a9913eca5 | ||
|
|
eaf1ace681 | ||
|
|
a2d1f89922 | ||
|
|
7e09beb0c3 | ||
|
|
ebf5cbf1b9 | ||
|
|
d727710d60 | ||
|
|
0e31aeea00 | ||
|
|
2f437a0382 | ||
|
|
3ad4370fa5 | ||
|
|
a3bb9c2877 | ||
|
|
ee7e976084 | ||
|
|
099358d3e5 | ||
|
|
5297273937 | ||
|
|
80cfc9a25b | ||
|
|
2ae4da524e | ||
|
|
bbe7f28545 | ||
|
|
78ddd497ee | ||
|
|
8d044232af | ||
|
|
aa7e85caa7 | ||
|
|
46a8f24400 | ||
|
|
87bc292296 | ||
|
|
ac539ace70 | ||
|
|
a15b13978f | ||
|
|
0c975db0a6 | ||
|
|
cb4fea0240 | ||
|
|
8e7957d440 | ||
|
|
f7bed32c6f | ||
|
|
ef7f2d82c0 | ||
|
|
7aa97a332e | ||
|
|
7c30dde96b | ||
|
|
9cef2a0a8f | ||
|
|
f376683fc3 | ||
|
|
4b61d6e875 | ||
|
|
7d83e350fd | ||
|
|
500ba69548 | ||
|
|
9a422549b1 | ||
|
|
3b48fa455e | ||
|
|
ef013e0639 | ||
|
|
8f8437a88d | ||
|
|
1b091c9b07 | ||
|
|
4801b6f057 | ||
|
|
9078bc2de5 | ||
|
|
b69464dfe9 | ||
|
|
62fa48293a | ||
|
|
b206d0889b | ||
|
|
ee691d81bf | ||
|
|
56876a67cc | ||
|
|
4a0df713aa | ||
|
|
ef801cbfbe | ||
|
|
9378fc88d2 | ||
|
|
f46bfcc3d8 | ||
|
|
ccdb238843 | ||
|
|
f1f61b4e2b | ||
|
|
a44cb745d9 | ||
|
|
f5f5cb023c | ||
|
|
5813e0ce7a | ||
|
|
5a9c2b1e80 | ||
|
|
bda34fdb3b | ||
|
|
426b677eb8 | ||
|
|
67c7e9fd86 | ||
|
|
d8028a8632 | ||
|
|
374743d022 | ||
|
|
cd98ea5008 | ||
|
|
dbda0ed98a | ||
|
|
f5e0ead01c | ||
|
|
44818701bc | ||
|
|
e0f7387dff | ||
|
|
d440a01792 | ||
|
|
665c84ee42 | ||
|
|
e0de96eb4c |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -31,7 +31,7 @@ body:
|
|||||||
<details>
|
<details>
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ sing-box --version
|
$ sing-box version
|
||||||
# Paste output here
|
# Paste output here
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
7
.github/update_dependencies.sh
vendored
7
.github/update_dependencies.sh
vendored
@@ -1,10 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
PROJECTS=$(dirname "$0")/../..
|
PROJECTS=$(dirname "$0")/../..
|
||||||
|
go get -x github.com/sagernet/$1@$(git -C $PROJECTS/$1 rev-parse HEAD)
|
||||||
go get -x github.com/sagernet/sing@$(git -C $PROJECTS/sing rev-parse HEAD)
|
|
||||||
go get -x github.com/sagernet/sing-dns@$(git -C $PROJECTS/sing-dns rev-parse HEAD)
|
|
||||||
go get -x github.com/sagernet/sing-tun@$(git -C $PROJECTS/sing-tun rev-parse HEAD)
|
|
||||||
go get -x github.com/sagernet/sing-shadowsocks@$(git -C $PROJECTS/sing-shadowsocks rev-parse HEAD)
|
|
||||||
go get -x github.com/sagernet/sing-vmess@$(git -C $PROJECTS/sing-vmess rev-parse HEAD)
|
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|||||||
4
.github/workflows/debug.yml
vendored
4
.github/workflows/debug.yml
vendored
@@ -3,14 +3,18 @@ name: Debug build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
- main
|
||||||
- dev
|
- dev
|
||||||
|
- dev-next
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- '!.github/workflows/debug.yml'
|
- '!.github/workflows/debug.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
|
- main
|
||||||
- dev
|
- dev
|
||||||
|
- dev-next
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
2
.github/workflows/mkdocs.yml
vendored
2
.github/workflows/mkdocs.yml
vendored
@@ -2,7 +2,7 @@ name: Generate Documents
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- dev
|
||||||
paths:
|
paths:
|
||||||
- docs/**
|
- docs/**
|
||||||
- .github/workflows/mkdocs.yml
|
- .github/workflows/mkdocs.yml
|
||||||
|
|||||||
34
.github/workflows/test.yml
vendored
Normal file
34
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: Test build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
- dev-next
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Debug build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
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')
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: go-${{ hashFiles('**/go.sum') }}
|
||||||
|
- name: Run Test
|
||||||
|
run: make test
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,4 +4,5 @@
|
|||||||
/*.db
|
/*.db
|
||||||
/site/
|
/site/
|
||||||
/bin/
|
/bin/
|
||||||
/dist/
|
/dist/
|
||||||
|
/sing-box
|
||||||
@@ -7,6 +7,10 @@ linters:
|
|||||||
- staticcheck
|
- staticcheck
|
||||||
- paralleltest
|
- paralleltest
|
||||||
|
|
||||||
|
run:
|
||||||
|
skip-dirs:
|
||||||
|
- transport/cloudflaretls
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
# gci:
|
# gci:
|
||||||
# sections:
|
# sections:
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ builds:
|
|||||||
gcflags:
|
gcflags:
|
||||||
- all=-trimpath={{.Env.GOPATH}}
|
- all=-trimpath={{.Env.GOPATH}}
|
||||||
ldflags:
|
ldflags:
|
||||||
- -X github.com/sagernet/sing-box/constant.Commit={{ .ShortCommit }} -s -w -buildid=
|
- -s -w -buildid=
|
||||||
tags:
|
tags:
|
||||||
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ ENV GOPROXY ${GOPROXY}
|
|||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& go build -v -trimpath -tags 'with_quic,with_acme,with_wireguard,with_clash_api' \
|
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X github.com/sagernet/sing-box/constant.Commit=${COMMIT} -w -s -buildid=" \
|
-ldflags "-s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM alpine AS dist
|
FROM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
|||||||
22
Makefile
22
Makefile
@@ -1,9 +1,8 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_quic,with_wireguard,with_clash_api
|
TAGS ?= with_gvisor,with_quic,with_wireguard,with_clash_api
|
||||||
PARAMS = -v -trimpath -tags '$(TAGS)' -ldflags \
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
||||||
'-X "github.com/sagernet/sing-box/constant.Commit=$(COMMIT)" \
|
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||||
-w -s -buildid='
|
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
|
|
||||||
.PHONY: test release
|
.PHONY: test release
|
||||||
@@ -61,14 +60,19 @@ release_install:
|
|||||||
go install -v github.com/tcnksm/ghr@latest
|
go install -v github.com/tcnksm/ghr@latest
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v . && \
|
@go test -v ./... && \
|
||||||
pushd test && \
|
cd test && \
|
||||||
go mod tidy && \
|
go mod tidy && \
|
||||||
go test -v -tags with_quic,with_wireguard,with_grpc . && \
|
go test -v -tags "$(TAGS_TEST)" .
|
||||||
popd
|
|
||||||
|
test_stdio:
|
||||||
|
@go test -v ./... && \
|
||||||
|
cd test && \
|
||||||
|
go mod tidy && \
|
||||||
|
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
|
|
||||||
update:
|
update:
|
||||||
|
|||||||
@@ -4,23 +4,29 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServer interface {
|
type ClashServer interface {
|
||||||
Service
|
Service
|
||||||
TrafficController
|
Mode() string
|
||||||
|
StoreSelected() bool
|
||||||
|
CacheFile() ClashCacheFile
|
||||||
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
|
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClashCacheFile interface {
|
||||||
|
LoadSelected(group string) string
|
||||||
|
StoreSelected(group string, selected string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
Leave()
|
Leave()
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrafficController interface {
|
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundGroup interface {
|
type OutboundGroup interface {
|
||||||
Now() string
|
Now() string
|
||||||
All() []string
|
All() []string
|
||||||
@@ -32,3 +38,13 @@ func OutboundTag(detour Outbound) string {
|
|||||||
}
|
}
|
||||||
return detour.Tag()
|
return detour.Tag()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type V2RayServer interface {
|
||||||
|
Service
|
||||||
|
StatsService() V2RayStatsService
|
||||||
|
}
|
||||||
|
|
||||||
|
type V2RayStatsService interface {
|
||||||
|
RoutedConnection(inbound string, outbound string, conn net.Conn) net.Conn
|
||||||
|
RoutedPacketConnection(inbound string, outbound string, conn N.PacketConn) N.PacketConn
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
@@ -15,6 +17,13 @@ type Inbound interface {
|
|||||||
Tag() string
|
Tag() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InjectableInbound interface {
|
||||||
|
Inbound
|
||||||
|
Network() []string
|
||||||
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
@@ -29,6 +38,8 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
|
InboundDetour string
|
||||||
|
LastInbound string
|
||||||
OriginDestination M.Socksaddr
|
OriginDestination M.Socksaddr
|
||||||
DomainStrategy dns.DomainStrategy
|
DomainStrategy dns.DomainStrategy
|
||||||
SniffEnabled bool
|
SniffEnabled bool
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
mdns "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
@@ -27,11 +27,11 @@ type Router interface {
|
|||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
|
|
||||||
Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error)
|
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
||||||
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||||
|
|
||||||
InterfaceBindManager() control.BindManager
|
InterfaceFinder() control.InterfaceFinder
|
||||||
DefaultInterface() string
|
DefaultInterface() string
|
||||||
AutoDetectInterface() bool
|
AutoDetectInterface() bool
|
||||||
DefaultMark() int
|
DefaultMark() int
|
||||||
@@ -39,7 +39,12 @@ type Router interface {
|
|||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
SetTrafficController(controller TrafficController)
|
|
||||||
|
ClashServer() ClashServer
|
||||||
|
SetClashServer(server ClashServer)
|
||||||
|
|
||||||
|
V2RayServer() V2RayServer
|
||||||
|
SetV2RayServer(server V2RayServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
|
|||||||
@@ -38,13 +38,25 @@ type myUpstreamHandlerWrapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
w.metadata.Destination = metadata.Destination
|
myMetadata := w.metadata
|
||||||
return w.connectionHandler(ctx, conn, w.metadata)
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.connectionHandler(ctx, conn, myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
w.metadata.Destination = metadata.Destination
|
myMetadata := w.metadata
|
||||||
return w.packetHandler(ctx, conn, w.metadata)
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.packetHandler(ctx, conn, myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
@@ -78,13 +90,23 @@ func NewUpstreamContextHandler(
|
|||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
myMetadata.Destination = metadata.Destination
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
myMetadata.Destination = metadata.Destination
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
return w.packetHandler(ctx, conn, *myMetadata)
|
return w.packetHandler(ctx, conn, *myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
box.go
37
box.go
@@ -31,6 +31,7 @@ type Box struct {
|
|||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
logFile *os.File
|
logFile *os.File
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
|
v2rayServer adapter.V2RayServer
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,8 +40,14 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
logOptions := common.PtrValueOrDefault(options.Log)
|
logOptions := common.PtrValueOrDefault(options.Log)
|
||||||
|
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
var needV2RayAPI bool
|
||||||
needClashAPI = true
|
if options.Experimental != nil {
|
||||||
|
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
||||||
|
needClashAPI = true
|
||||||
|
}
|
||||||
|
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
|
||||||
|
needV2RayAPI = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var logFactory log.Factory
|
var logFactory log.Factory
|
||||||
@@ -138,7 +145,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, out)
|
||||||
}
|
}
|
||||||
err = router.Initialize(outbounds, func() adapter.Outbound {
|
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
|
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
|
||||||
common.Must(oErr)
|
common.Must(oErr)
|
||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, out)
|
||||||
@@ -149,12 +156,20 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var clashServer adapter.ClashServer
|
var clashServer adapter.ClashServer
|
||||||
|
var v2rayServer adapter.V2RayServer
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
router.SetTrafficController(clashServer)
|
router.SetClashServer(clashServer)
|
||||||
|
}
|
||||||
|
if needV2RayAPI {
|
||||||
|
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create v2ray api server")
|
||||||
|
}
|
||||||
|
router.SetV2RayServer(v2rayServer)
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
router: router,
|
router: router,
|
||||||
@@ -162,9 +177,10 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
outbounds: outbounds,
|
outbounds: outbounds,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.NewLogger(""),
|
logger: logFactory.Logger(),
|
||||||
logFile: logFile,
|
logFile: logFile,
|
||||||
clashServer: clashServer,
|
clashServer: clashServer,
|
||||||
|
v2rayServer: v2rayServer,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -223,6 +239,12 @@ func (s *Box) start() error {
|
|||||||
return E.Cause(err, "start clash api server")
|
return E.Cause(err, "start clash api server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if s.v2rayServer != nil {
|
||||||
|
err = s.v2rayServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start v2ray api server")
|
||||||
|
}
|
||||||
|
}
|
||||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -244,6 +266,11 @@ func (s *Box) Close() error {
|
|||||||
s.router,
|
s.router,
|
||||||
s.logFactory,
|
s.logFactory,
|
||||||
s.clashServer,
|
s.clashServer,
|
||||||
|
s.v2rayServer,
|
||||||
common.PtrOrNil(s.logFile),
|
common.PtrOrNil(s.logFile),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Box) Router() adapter.Router {
|
||||||
|
return s.router
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func format() error {
|
|||||||
return E.Cause(err, "read config")
|
return E.Cause(err, "read config")
|
||||||
}
|
}
|
||||||
var options option.Options
|
var options option.Options
|
||||||
err = json.Unmarshal(configContent, &options)
|
err = options.UnmarshalJSON(configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "decode config")
|
return E.Cause(err, "decode config")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -46,7 +45,7 @@ func readConfig() (option.Options, error) {
|
|||||||
return option.Options{}, E.Cause(err, "read config")
|
return option.Options{}, E.Cause(err, "read config")
|
||||||
}
|
}
|
||||||
var options option.Options
|
var options option.Options
|
||||||
err = json.Unmarshal(configContent, &options)
|
err = options.UnmarshalJSON(configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "decode config")
|
return option.Options{}, E.Cause(err, "decode config")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -25,30 +25,40 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printVersion(cmd *cobra.Command, args []string) {
|
func printVersion(cmd *cobra.Command, args []string) {
|
||||||
var version string
|
if nameOnly {
|
||||||
if !nameOnly {
|
os.Stdout.WriteString(C.Version + "\n")
|
||||||
version = "sing-box "
|
return
|
||||||
}
|
}
|
||||||
version += F.ToString(C.Version)
|
version := "sing-box version " + C.Version + "\n\n"
|
||||||
if C.Commit != "" {
|
version += "Environment: " + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH + "\n"
|
||||||
version += "." + C.Commit
|
|
||||||
}
|
var tags string
|
||||||
if !nameOnly {
|
var revision string
|
||||||
version += " ("
|
|
||||||
version += runtime.Version()
|
debugInfo, loaded := debug.ReadBuildInfo()
|
||||||
version += ", "
|
if loaded {
|
||||||
version += runtime.GOOS
|
for _, setting := range debugInfo.Settings {
|
||||||
version += "/"
|
switch setting.Key {
|
||||||
version += runtime.GOARCH
|
case "-tags":
|
||||||
version += ", "
|
tags = setting.Value
|
||||||
version += "CGO "
|
case "vcs.revision":
|
||||||
if C.CGO_ENABLED {
|
revision = setting.Value
|
||||||
version += "enabled"
|
}
|
||||||
} else {
|
|
||||||
version += "disabled"
|
|
||||||
}
|
}
|
||||||
version += ")"
|
|
||||||
}
|
}
|
||||||
version += "\n"
|
|
||||||
|
if tags != "" {
|
||||||
|
version += "Tags: " + tags + "\n"
|
||||||
|
}
|
||||||
|
if revision != "" {
|
||||||
|
version += "Revision: " + revision + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if C.CGO_ENABLED {
|
||||||
|
version += "CGO: enabled\n"
|
||||||
|
} else {
|
||||||
|
version += "CGO: disabled\n"
|
||||||
|
}
|
||||||
|
|
||||||
os.Stdout.WriteString(version)
|
os.Stdout.WriteString(version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|||||||
62
common/baderror/baderror.go
Normal file
62
common/baderror/baderror.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package baderror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Contains(err error, msgList ...string) bool {
|
||||||
|
for _, msg := range msgList {
|
||||||
|
if strings.Contains(err.Error(), msg) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapH2(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = E.Unwrap(err)
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
if Contains(err, "client disconnected", "body closed by handler", "response body closed", "; CANCEL") {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapGRPC(err error) error {
|
||||||
|
// grpc uses stupid internal error types
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if Contains(err, "EOF") {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
if Contains(err, "Canceled") {
|
||||||
|
return context.Canceled
|
||||||
|
}
|
||||||
|
if Contains(err,
|
||||||
|
"the client connection is closing",
|
||||||
|
"server closed the stream without sending trailers") {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapQUIC(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if Contains(err, "canceled with error code 0") {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
210
common/badtls/badtls.go
Normal file
210
common/badtls/badtls.go
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
//go:build go1.19 && !go1.20
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
*tls.Conn
|
||||||
|
writer N.ExtendedWriter
|
||||||
|
activeCall *int32
|
||||||
|
closeNotifySent *bool
|
||||||
|
version *uint16
|
||||||
|
rand io.Reader
|
||||||
|
halfAccess *sync.Mutex
|
||||||
|
halfError *error
|
||||||
|
cipher cipher.AEAD
|
||||||
|
explicitNonceLen int
|
||||||
|
halfPtr uintptr
|
||||||
|
halfSeq []byte
|
||||||
|
halfScratchBuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(conn *tls.Conn) (TLSConn, error) {
|
||||||
|
if !handshakeComplete(conn) {
|
||||||
|
return nil, E.New("handshake not finished")
|
||||||
|
}
|
||||||
|
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||||
|
rawActiveCall := rawConn.FieldByName("activeCall")
|
||||||
|
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 {
|
||||||
|
return nil, E.New("badtls: invalid active call")
|
||||||
|
}
|
||||||
|
activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
||||||
|
rawHalfConn := rawConn.FieldByName("out")
|
||||||
|
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid half conn")
|
||||||
|
}
|
||||||
|
rawVersion := rawConn.FieldByName("vers")
|
||||||
|
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
|
||||||
|
return nil, E.New("badtls: invalid version")
|
||||||
|
}
|
||||||
|
version := (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
|
||||||
|
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
|
||||||
|
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
|
||||||
|
return nil, E.New("badtls: invalid notify")
|
||||||
|
}
|
||||||
|
closeNotifySent := (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
|
||||||
|
rawConfig := reflect.Indirect(rawConn.FieldByName("config"))
|
||||||
|
if !rawConfig.IsValid() || rawConfig.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: bad config")
|
||||||
|
}
|
||||||
|
config := (*tls.Config)(unsafe.Pointer(rawConfig.UnsafeAddr()))
|
||||||
|
randReader := config.Rand
|
||||||
|
if randReader == nil {
|
||||||
|
randReader = rand.Reader
|
||||||
|
}
|
||||||
|
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
|
||||||
|
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid half mutex")
|
||||||
|
}
|
||||||
|
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
|
||||||
|
rawHalfError := rawHalfConn.FieldByName("err")
|
||||||
|
if !rawHalfError.IsValid() || rawHalfError.Kind() != reflect.Interface {
|
||||||
|
return nil, E.New("badtls: invalid half error")
|
||||||
|
}
|
||||||
|
halfError := (*error)(unsafe.Pointer(rawHalfError.UnsafeAddr()))
|
||||||
|
rawHalfCipherInterface := rawHalfConn.FieldByName("cipher")
|
||||||
|
if !rawHalfCipherInterface.IsValid() || rawHalfCipherInterface.Kind() != reflect.Interface {
|
||||||
|
return nil, E.New("badtls: invalid cipher interface")
|
||||||
|
}
|
||||||
|
rawHalfCipher := rawHalfCipherInterface.Elem()
|
||||||
|
aeadCipher, loaded := valueInterface(rawHalfCipher, false).(cipher.AEAD)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("badtls: invalid AEAD cipher")
|
||||||
|
}
|
||||||
|
var explicitNonceLen int
|
||||||
|
switch cipherName := reflect.Indirect(rawHalfCipher).Type().String(); cipherName {
|
||||||
|
case "tls.prefixNonceAEAD":
|
||||||
|
explicitNonceLen = aeadCipher.NonceSize()
|
||||||
|
case "tls.xorNonceAEAD":
|
||||||
|
default:
|
||||||
|
return nil, E.New("badtls: unknown cipher type: ", cipherName)
|
||||||
|
}
|
||||||
|
rawHalfSeq := rawHalfConn.FieldByName("seq")
|
||||||
|
if !rawHalfSeq.IsValid() || rawHalfSeq.Kind() != reflect.Array {
|
||||||
|
return nil, E.New("badtls: invalid seq")
|
||||||
|
}
|
||||||
|
halfSeq := rawHalfSeq.Bytes()
|
||||||
|
rawHalfScratchBuf := rawHalfConn.FieldByName("scratchBuf")
|
||||||
|
if !rawHalfScratchBuf.IsValid() || rawHalfScratchBuf.Kind() != reflect.Array {
|
||||||
|
return nil, E.New("badtls: invalid scratchBuf")
|
||||||
|
}
|
||||||
|
halfScratchBuf := rawHalfScratchBuf.Bytes()
|
||||||
|
return &Conn{
|
||||||
|
Conn: conn,
|
||||||
|
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
||||||
|
activeCall: activeCall,
|
||||||
|
closeNotifySent: closeNotifySent,
|
||||||
|
version: version,
|
||||||
|
halfAccess: halfAccess,
|
||||||
|
halfError: halfError,
|
||||||
|
cipher: aeadCipher,
|
||||||
|
explicitNonceLen: explicitNonceLen,
|
||||||
|
rand: randReader,
|
||||||
|
halfPtr: rawHalfConn.UnsafeAddr(),
|
||||||
|
halfSeq: halfSeq,
|
||||||
|
halfScratchBuf: halfScratchBuf,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
if buffer.Len() > maxPlaintext {
|
||||||
|
defer buffer.Release()
|
||||||
|
return common.Error(c.Write(buffer.Bytes()))
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
x := atomic.LoadInt32(c.activeCall)
|
||||||
|
if x&1 != 0 {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer atomic.AddInt32(c.activeCall, -2)
|
||||||
|
c.halfAccess.Lock()
|
||||||
|
defer c.halfAccess.Unlock()
|
||||||
|
if err := *c.halfError; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *c.closeNotifySent {
|
||||||
|
return errShutdown
|
||||||
|
}
|
||||||
|
dataLen := buffer.Len()
|
||||||
|
dataBytes := buffer.Bytes()
|
||||||
|
outBuf := buffer.ExtendHeader(recordHeaderLen + c.explicitNonceLen)
|
||||||
|
outBuf[0] = 23
|
||||||
|
version := *c.version
|
||||||
|
if version == 0 {
|
||||||
|
version = tls.VersionTLS10
|
||||||
|
} else if version == tls.VersionTLS13 {
|
||||||
|
version = tls.VersionTLS12
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16(outBuf[1:], version)
|
||||||
|
var nonce []byte
|
||||||
|
if c.explicitNonceLen > 0 {
|
||||||
|
nonce = outBuf[5 : 5+c.explicitNonceLen]
|
||||||
|
if c.explicitNonceLen < 16 {
|
||||||
|
copy(nonce, c.halfSeq)
|
||||||
|
} else {
|
||||||
|
if _, err := io.ReadFull(c.rand, nonce); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(nonce) == 0 {
|
||||||
|
nonce = c.halfSeq
|
||||||
|
}
|
||||||
|
if *c.version == tls.VersionTLS13 {
|
||||||
|
buffer.FreeBytes()[0] = 23
|
||||||
|
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+1+c.cipher.Overhead()))
|
||||||
|
c.cipher.Seal(outBuf, nonce, outBuf[recordHeaderLen:recordHeaderLen+c.explicitNonceLen+dataLen+1], outBuf[:recordHeaderLen])
|
||||||
|
buffer.Extend(1 + c.cipher.Overhead())
|
||||||
|
} else {
|
||||||
|
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen))
|
||||||
|
additionalData := append(c.halfScratchBuf[:0], c.halfSeq...)
|
||||||
|
additionalData = append(additionalData, outBuf[:recordHeaderLen]...)
|
||||||
|
c.cipher.Seal(outBuf, nonce, dataBytes, additionalData)
|
||||||
|
buffer.Extend(c.cipher.Overhead())
|
||||||
|
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
|
||||||
|
}
|
||||||
|
incSeq(c.halfPtr)
|
||||||
|
return c.writer.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) FrontHeadroom() int {
|
||||||
|
return recordHeaderLen + c.explicitNonceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) RearHeadroom() int {
|
||||||
|
return 1 + c.cipher.Overhead()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriterMTU() int {
|
||||||
|
return maxPlaintext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) UpstreamWriter() any {
|
||||||
|
return c.NetConn()
|
||||||
|
}
|
||||||
12
common/badtls/badtls_stub.go
Normal file
12
common/badtls/badtls_stub.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !go1.19 || go1.20
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Create(conn *tls.Conn) (TLSConn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
13
common/badtls/conn.go
Normal file
13
common/badtls/conn.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TLSConn interface {
|
||||||
|
net.Conn
|
||||||
|
HandshakeContext(ctx context.Context) error
|
||||||
|
ConnectionState() tls.ConnectionState
|
||||||
|
}
|
||||||
26
common/badtls/link.go
Normal file
26
common/badtls/link.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//go:build go1.19 && !go.1.20
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"reflect"
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxPlaintext = 16384 // maximum plaintext payload length
|
||||||
|
recordHeaderLen = 5 // record header length
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname errShutdown crypto/tls.errShutdown
|
||||||
|
var errShutdown error
|
||||||
|
|
||||||
|
//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
|
||||||
|
func handshakeComplete(conn *tls.Conn) bool
|
||||||
|
|
||||||
|
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
|
||||||
|
func incSeq(conn uintptr)
|
||||||
|
|
||||||
|
//go:linkname valueInterface reflect.valueInterface
|
||||||
|
func valueInterface(v reflect.Value, safe bool) any
|
||||||
@@ -11,10 +11,11 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"github.com/database64128/tfo-go"
|
"github.com/database64128/tfo-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
||||||
@@ -64,25 +65,23 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
var listener net.ListenConfig
|
var listener net.ListenConfig
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
warnBindInterfaceOnUnsupportedPlatform.Check()
|
warnBindInterfaceOnUnsupportedPlatform.Check()
|
||||||
bindFunc := control.BindToInterface(router.InterfaceBindManager(), options.BindInterface)
|
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if router.AutoDetectInterface() {
|
} else if router.AutoDetectInterface() {
|
||||||
if C.IsWindows {
|
const useInterfaceName = C.IsLinux
|
||||||
bindFunc := control.BindToInterfaceIndexFunc(func(network, address string) int {
|
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
|
||||||
return router.InterfaceMonitor().DefaultInterfaceIndex(M.ParseSocksaddr(address).Addr)
|
remoteAddr := M.ParseSocksaddr(address).Addr
|
||||||
})
|
if C.IsLinux {
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
} else {
|
||||||
} else {
|
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
|
||||||
bindFunc := control.BindToInterfaceFunc(router.InterfaceBindManager(), func(network, address string) string {
|
}
|
||||||
return router.InterfaceMonitor().DefaultInterfaceName(M.ParseSocksaddr(address).Addr)
|
})
|
||||||
})
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
|
||||||
}
|
|
||||||
} else if router.DefaultInterface() != "" {
|
} else if router.DefaultInterface() != "" {
|
||||||
bindFunc := control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface())
|
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
@@ -111,6 +110,16 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
if options.TCPFastOpen {
|
if options.TCPFastOpen {
|
||||||
warnTFOOnUnsupportedPlatform.Check()
|
warnTFOOnUnsupportedPlatform.Check()
|
||||||
}
|
}
|
||||||
|
var udpFragment bool
|
||||||
|
if options.UDPFragment != nil {
|
||||||
|
udpFragment = *options.UDPFragment
|
||||||
|
} else {
|
||||||
|
udpFragment = options.UDPFragmentDefault
|
||||||
|
}
|
||||||
|
if !udpFragment {
|
||||||
|
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
|
||||||
|
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
|
||||||
|
}
|
||||||
var bindUDPAddr string
|
var bindUDPAddr string
|
||||||
udpDialer := dialer
|
udpDialer := dialer
|
||||||
var bindAddress netip.Addr
|
var bindAddress netip.Addr
|
||||||
@@ -130,11 +139,14 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||||
|
if !address.IsValid() {
|
||||||
|
return nil, E.New("invalid address")
|
||||||
|
}
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
return d.udpDialer.DialContext(ctx, network, address.String())
|
return d.udpDialer.DialContext(ctx, network, address.String())
|
||||||
}
|
}
|
||||||
return d.dialer.DialContext(ctx, network, address.Unwrap().String())
|
return DialSlowContext(&d.dialer, ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
|||||||
@@ -10,15 +10,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
|
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
|
||||||
|
var dialer N.Dialer
|
||||||
if options.Detour == "" {
|
if options.Detour == "" {
|
||||||
return NewDefault(router, options)
|
dialer = NewDefault(router, options)
|
||||||
} else {
|
} else {
|
||||||
return NewDetour(router, options.Detour)
|
dialer = NewDetour(router, options.Detour)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func NewOutbound(router adapter.Router, options option.OutboundDialerOptions) N.Dialer {
|
|
||||||
dialer := New(router, options.DialerOptions)
|
|
||||||
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
|
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
|
||||||
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
||||||
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
|
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if !destination.IsFqdn() || destination.Fqdn == "" {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
ctx, metadata := adapter.AppendContext(ctx)
|
||||||
|
|||||||
135
common/dialer/tfo.go
Normal file
135
common/dialer/tfo.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/database64128/tfo-go/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type slowOpenConn struct {
|
||||||
|
dialer *tfo.Dialer
|
||||||
|
ctx context.Context
|
||||||
|
network string
|
||||||
|
destination M.Socksaddr
|
||||||
|
conn net.Conn
|
||||||
|
create chan struct{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
||||||
|
return dialer.DialContext(ctx, network, destination.String(), nil)
|
||||||
|
}
|
||||||
|
return &slowOpenConn{
|
||||||
|
dialer: dialer,
|
||||||
|
ctx: ctx,
|
||||||
|
network: network,
|
||||||
|
destination: destination,
|
||||||
|
create: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
||||||
|
if c.conn == nil {
|
||||||
|
select {
|
||||||
|
case <-c.create:
|
||||||
|
if c.err != nil {
|
||||||
|
return 0, c.err
|
||||||
|
}
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
return 0, c.ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||||
|
if c.conn == nil {
|
||||||
|
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
|
c.err = err
|
||||||
|
close(c.create)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) Close() error {
|
||||||
|
return common.Close(c.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
|
if c.conn == nil {
|
||||||
|
return M.Socksaddr{}
|
||||||
|
}
|
||||||
|
return c.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
||||||
|
if c.conn == nil {
|
||||||
|
return M.Socksaddr{}
|
||||||
|
}
|
||||||
|
return c.conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
||||||
|
if c.conn == nil {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
return c.conn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
||||||
|
if c.conn == nil {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
return c.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
if c.conn == nil {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
return c.conn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) Upstream() any {
|
||||||
|
return c.conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) ReaderReplaceable() bool {
|
||||||
|
return c.conn != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) WriterReplaceable() bool {
|
||||||
|
return c.conn != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
if c.conn != nil {
|
||||||
|
return bufio.Copy(c.conn, r)
|
||||||
|
}
|
||||||
|
return bufio.ReadFrom0(c, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if c.conn == nil {
|
||||||
|
select {
|
||||||
|
case <-c.create:
|
||||||
|
if c.err != nil {
|
||||||
|
return 0, c.err
|
||||||
|
}
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
return 0, c.ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bufio.Copy(w, c.conn)
|
||||||
|
}
|
||||||
128
common/json/comment.go
Normal file
128
common/json/comment.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// kanged from v2ray
|
||||||
|
|
||||||
|
type commentFilterState = byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
commentFilterStateContent commentFilterState = iota
|
||||||
|
commentFilterStateEscape
|
||||||
|
commentFilterStateDoubleQuote
|
||||||
|
commentFilterStateDoubleQuoteEscape
|
||||||
|
commentFilterStateSingleQuote
|
||||||
|
commentFilterStateSingleQuoteEscape
|
||||||
|
commentFilterStateComment
|
||||||
|
commentFilterStateSlash
|
||||||
|
commentFilterStateMultilineComment
|
||||||
|
commentFilterStateMultilineCommentStar
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommentFilter struct {
|
||||||
|
br *bufio.Reader
|
||||||
|
state commentFilterState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommentFilter(reader io.Reader) io.Reader {
|
||||||
|
return &CommentFilter{br: bufio.NewReader(reader)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *CommentFilter) Read(b []byte) (int, error) {
|
||||||
|
p := b[:0]
|
||||||
|
for len(p) < len(b)-2 {
|
||||||
|
x, err := v.br.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
switch v.state {
|
||||||
|
case commentFilterStateContent:
|
||||||
|
switch x {
|
||||||
|
case '"':
|
||||||
|
v.state = commentFilterStateDoubleQuote
|
||||||
|
p = append(p, x)
|
||||||
|
case '\'':
|
||||||
|
v.state = commentFilterStateSingleQuote
|
||||||
|
p = append(p, x)
|
||||||
|
case '\\':
|
||||||
|
v.state = commentFilterStateEscape
|
||||||
|
case '#':
|
||||||
|
v.state = commentFilterStateComment
|
||||||
|
case '/':
|
||||||
|
v.state = commentFilterStateSlash
|
||||||
|
default:
|
||||||
|
p = append(p, x)
|
||||||
|
}
|
||||||
|
case commentFilterStateEscape:
|
||||||
|
p = append(p, '\\', x)
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
case commentFilterStateDoubleQuote:
|
||||||
|
switch x {
|
||||||
|
case '"':
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
p = append(p, x)
|
||||||
|
case '\\':
|
||||||
|
v.state = commentFilterStateDoubleQuoteEscape
|
||||||
|
default:
|
||||||
|
p = append(p, x)
|
||||||
|
}
|
||||||
|
case commentFilterStateDoubleQuoteEscape:
|
||||||
|
p = append(p, '\\', x)
|
||||||
|
v.state = commentFilterStateDoubleQuote
|
||||||
|
case commentFilterStateSingleQuote:
|
||||||
|
switch x {
|
||||||
|
case '\'':
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
p = append(p, x)
|
||||||
|
case '\\':
|
||||||
|
v.state = commentFilterStateSingleQuoteEscape
|
||||||
|
default:
|
||||||
|
p = append(p, x)
|
||||||
|
}
|
||||||
|
case commentFilterStateSingleQuoteEscape:
|
||||||
|
p = append(p, '\\', x)
|
||||||
|
v.state = commentFilterStateSingleQuote
|
||||||
|
case commentFilterStateComment:
|
||||||
|
if x == '\n' {
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
p = append(p, '\n')
|
||||||
|
}
|
||||||
|
case commentFilterStateSlash:
|
||||||
|
switch x {
|
||||||
|
case '/':
|
||||||
|
v.state = commentFilterStateComment
|
||||||
|
case '*':
|
||||||
|
v.state = commentFilterStateMultilineComment
|
||||||
|
default:
|
||||||
|
p = append(p, '/', x)
|
||||||
|
}
|
||||||
|
case commentFilterStateMultilineComment:
|
||||||
|
switch x {
|
||||||
|
case '*':
|
||||||
|
v.state = commentFilterStateMultilineCommentStar
|
||||||
|
case '\n':
|
||||||
|
p = append(p, '\n')
|
||||||
|
}
|
||||||
|
case commentFilterStateMultilineCommentStar:
|
||||||
|
switch x {
|
||||||
|
case '/':
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
case '*':
|
||||||
|
// Stay
|
||||||
|
case '\n':
|
||||||
|
p = append(p, '\n')
|
||||||
|
default:
|
||||||
|
v.state = commentFilterStateMultilineComment
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("Unknown state.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
@@ -466,10 +466,7 @@ func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if buffer.FreeLen() < int(length) {
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
return destination, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
_, err = io.ReadFull(c.ExtendedConn, buffer.Extend(int(length)))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package mux
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
|
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
@@ -26,14 +26,21 @@ func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var stream net.Conn
|
var group task.Group
|
||||||
for {
|
group.Append0(func(ctx context.Context) error {
|
||||||
stream, err = session.Accept()
|
var stream net.Conn
|
||||||
if err != nil {
|
for {
|
||||||
return err
|
stream, err = session.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
|
||||||
}
|
}
|
||||||
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
|
})
|
||||||
}
|
group.Cleanup(func() {
|
||||||
|
session.Close()
|
||||||
|
})
|
||||||
|
return group.Run(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
|
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
|
||||||
@@ -158,9 +165,6 @@ func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksad
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if buffer.FreeLen() < int(length) {
|
|
||||||
return destination, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -223,9 +227,6 @@ func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if buffer.FreeLen() < int(length) {
|
|
||||||
return destination, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -28,11 +28,5 @@ type Info struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
info, err := findProcessInfo(searcher, ctx, network, source, destination)
|
return findProcessInfo(searcher, ctx, network, source, destination)
|
||||||
if err != nil {
|
|
||||||
if source.Addr().Is4In6() {
|
|
||||||
info, err = findProcessInfo(searcher, ctx, network, netip.AddrPortFrom(netip.AddrFrom4(source.Addr().As4()), source.Port()), destination)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info, err
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid); loaded {
|
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
|
||||||
return &Info{
|
return &Info{
|
||||||
UserId: int32(uid),
|
UserId: int32(uid),
|
||||||
PackageName: sharedPackage,
|
PackageName: sharedPackage,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
if packageName, loaded := s.packageManager.PackageByID(uid); loaded {
|
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
|
||||||
return &Info{
|
return &Info{
|
||||||
UserId: int32(uid),
|
UserId: int32(uid),
|
||||||
PackageName: packageName,
|
PackageName: packageName,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
|
AcceptNoHeader bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) Accept() (net.Conn, error) {
|
func (l *Listener) Accept() (net.Conn, error) {
|
||||||
@@ -22,7 +23,7 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
bufReader := std_bufio.NewReader(conn)
|
bufReader := std_bufio.NewReader(conn)
|
||||||
header, err := proxyproto.Read(bufReader)
|
header, err := proxyproto.Read(bufReader)
|
||||||
if err != nil {
|
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if bufReader.Buffered() > 0 {
|
if bufReader.Buffered() > 0 {
|
||||||
@@ -33,8 +34,11 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
conn = bufio.NewCachedConn(conn, cache)
|
conn = bufio.NewCachedConn(conn, cache)
|
||||||
}
|
}
|
||||||
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
|
if header != nil {
|
||||||
Source: M.SocksaddrFromNet(header.SourceAddr),
|
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
|
||||||
Destination: M.SocksaddrFromNet(header.DestinationAddr),
|
Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(),
|
||||||
}}, nil
|
Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(),
|
||||||
|
}}, nil
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|||||||
64
common/redir/redir_darwin.go
Normal file
64
common/redir/redir_darwin.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PF_OUT = 0x2
|
||||||
|
DIOCNATLOOK = 0xc0544417
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {
|
||||||
|
fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY)
|
||||||
|
if err != nil {
|
||||||
|
return netip.AddrPort{}, err
|
||||||
|
}
|
||||||
|
defer syscall.Close(fd)
|
||||||
|
nl := struct {
|
||||||
|
saddr, daddr, rsaddr, rdaddr [16]byte
|
||||||
|
sxport, dxport, rsxport, rdxport [4]byte
|
||||||
|
af, proto, protoVariant, direction uint8
|
||||||
|
}{
|
||||||
|
af: syscall.AF_INET,
|
||||||
|
proto: syscall.IPPROTO_TCP,
|
||||||
|
direction: PF_OUT,
|
||||||
|
}
|
||||||
|
la := conn.LocalAddr().(*net.TCPAddr)
|
||||||
|
ra := conn.RemoteAddr().(*net.TCPAddr)
|
||||||
|
raIP, laIP := ra.IP, la.IP
|
||||||
|
raPort, laPort := ra.Port, la.Port
|
||||||
|
switch {
|
||||||
|
case raIP.To4() != nil:
|
||||||
|
copy(nl.saddr[:net.IPv4len], raIP.To4())
|
||||||
|
copy(nl.daddr[:net.IPv4len], laIP.To4())
|
||||||
|
nl.af = syscall.AF_INET
|
||||||
|
default:
|
||||||
|
copy(nl.saddr[:], raIP.To16())
|
||||||
|
copy(nl.daddr[:], laIP.To16())
|
||||||
|
nl.af = syscall.AF_INET6
|
||||||
|
}
|
||||||
|
nl.sxport[0], nl.sxport[1] = byte(raPort>>8), byte(raPort)
|
||||||
|
nl.dxport[0], nl.dxport[1] = byte(laPort>>8), byte(laPort)
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 {
|
||||||
|
return netip.AddrPort{}, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
switch nl.af {
|
||||||
|
case syscall.AF_INET:
|
||||||
|
ip = make(net.IP, net.IPv4len)
|
||||||
|
copy(ip, nl.rdaddr[:net.IPv4len])
|
||||||
|
case syscall.AF_INET6:
|
||||||
|
ip = make(net.IP, net.IPv6len)
|
||||||
|
copy(ip, nl.rdaddr[:])
|
||||||
|
}
|
||||||
|
port := uint16(nl.rdxport[0])<<8 | uint16(nl.rdxport[1])
|
||||||
|
destination = netip.AddrPortFrom(M.AddrFromIP(ip), port)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !linux
|
//go:build !linux && !darwin
|
||||||
|
|
||||||
package redir
|
package redir
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ package redir
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@@ -32,6 +29,18 @@ func TProxy(fd uintptr, isIPv6 bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TProxyWriteBack() control.Func {
|
||||||
|
return func(network, address string, conn syscall.RawConn) error {
|
||||||
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
|
if M.ParseSocksaddr(address).Addr.Is6() {
|
||||||
|
return syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
||||||
|
} else {
|
||||||
|
return syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
||||||
controlMessages, err := unix.ParseSocketControlMessage(oob)
|
controlMessages, err := unix.ParseSocketControlMessage(oob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,79 +55,3 @@ func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
|||||||
}
|
}
|
||||||
return netip.AddrPort{}, E.New("not found")
|
return netip.AddrPort{}, E.New("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialUDP(lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
|
||||||
rSockAddr, err := udpAddrToSockAddr(rAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lSockAddr, err := udpAddrToSockAddr(lAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := syscall.Socket(udpAddrFamily(lAddr, rAddr), syscall.SOCK_DGRAM, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = syscall.Bind(fd, lSockAddr); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = syscall.Connect(fd, rSockAddr); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fdFile := os.NewFile(uintptr(fd), F.ToString("net-udp-dial-", rAddr))
|
|
||||||
defer fdFile.Close()
|
|
||||||
|
|
||||||
c, err := net.FileConn(fdFile)
|
|
||||||
if err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.(*net.UDPConn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
|
|
||||||
switch {
|
|
||||||
case addr.IP.To4() != nil:
|
|
||||||
ip := [4]byte{}
|
|
||||||
copy(ip[:], addr.IP.To4())
|
|
||||||
|
|
||||||
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
ip := [16]byte{}
|
|
||||||
copy(ip[:], addr.IP.To16())
|
|
||||||
|
|
||||||
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
zoneID = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func udpAddrFamily(lAddr, rAddr *net.UDPAddr) int {
|
|
||||||
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
|
|
||||||
return syscall.AF_INET
|
|
||||||
}
|
|
||||||
return syscall.AF_INET6
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,19 +3,20 @@
|
|||||||
package redir
|
package redir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TProxy(fd uintptr, isIPv6 bool) error {
|
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TProxyWriteBack() control.Func {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
||||||
return netip.AddrPort{}, os.ErrInvalid
|
return netip.AddrPort{}, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialUDP(lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type systemProxy struct {
|
|||||||
isMixed bool
|
isMixed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *systemProxy) update() error {
|
func (p *systemProxy) update(event int) error {
|
||||||
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||||
if p.interfaceName == newInterfaceName {
|
if p.interfaceName == newInterfaceName {
|
||||||
return nil
|
return nil
|
||||||
@@ -88,7 +88,7 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
|
|||||||
port: port,
|
port: port,
|
||||||
isMixed: isMixed,
|
isMixed: isMixed,
|
||||||
}
|
}
|
||||||
err := proxy.update()
|
err := proxy.update(tun.EventInterfaceUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/task"
|
"github.com/sagernet/sing/common/task"
|
||||||
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
||||||
@@ -44,18 +45,13 @@ func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContext, error) {
|
func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||||
var parser dnsmessage.Parser
|
var msg mDNS.Msg
|
||||||
_, err := parser.Start(packet)
|
err := msg.Unpack(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
question, err := parser.Question()
|
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
|
||||||
if err != nil {
|
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
domain := question.Name.String()
|
return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil
|
||||||
if question.Class == dnsmessage.ClassINET && IsDomainName(domain) {
|
|
||||||
return &adapter.InboundContext{Protocol: C.ProtocolDNS /*, Domain: domain*/}, nil
|
|
||||||
}
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package sniff
|
|
||||||
|
|
||||||
import _ "unsafe" // for linkname
|
|
||||||
|
|
||||||
//go:linkname IsDomainName net.isDomainName
|
|
||||||
func IsDomainName(domain string) bool
|
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
//go:build with_acme
|
//go:build with_acme
|
||||||
|
|
||||||
package inbound
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/certmagic"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/mholt/acmez/acme"
|
"github.com/mholt/acmez/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//go:build !with_acme
|
//go:build !with_acme
|
||||||
|
|
||||||
package inbound
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
75
common/tls/client.go
Normal file
75
common/tls/client.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"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/option"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
|
config, err := NewClient(router, serverAddress, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewDialer(dialer, config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
|
return newECHClient(router, serverAddress, options)
|
||||||
|
} else if options.UTLS != nil && options.UTLS.Enabled {
|
||||||
|
return newUTLSClient(router, serverAddress, options)
|
||||||
|
} else {
|
||||||
|
return newStdClient(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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
|
||||||
|
var badConn badtls.TLSConn
|
||||||
|
badConn, err = badtls.Create(stdConn)
|
||||||
|
if err == nil {
|
||||||
|
return badConn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dialer struct {
|
||||||
|
dialer N.Dialer
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDialer(dialer N.Dialer, config Config) N.Dialer {
|
||||||
|
return &Dialer{dialer, config}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
if network != N.NetworkTCP {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
conn, err := d.dialer.DialContext(ctx, network, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ClientHandshake(ctx, conn, d.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
12
common/tls/common.go
Normal file
12
common/tls/common.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
const (
|
||||||
|
VersionTLS10 = 0x0301
|
||||||
|
VersionTLS11 = 0x0302
|
||||||
|
VersionTLS12 = 0x0303
|
||||||
|
VersionTLS13 = 0x0304
|
||||||
|
|
||||||
|
// Deprecated: SSLv3 is cryptographically broken, and is no longer
|
||||||
|
// supported by this package. See golang.org/issue/32716.
|
||||||
|
VersionSSL30 = 0x0300
|
||||||
|
)
|
||||||
49
common/tls/config.go
Normal file
49
common/tls/config.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
STDConfig = tls.Config
|
||||||
|
STDConn = tls.Conn
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config interface {
|
||||||
|
NextProtos() []string
|
||||||
|
SetNextProtos(nextProto []string)
|
||||||
|
Config() (*STDConfig, error)
|
||||||
|
Client(conn net.Conn) Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig interface {
|
||||||
|
Config
|
||||||
|
adapter.Service
|
||||||
|
Server(conn net.Conn) Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conn interface {
|
||||||
|
net.Conn
|
||||||
|
HandshakeContext(ctx context.Context) error
|
||||||
|
ConnectionState() tls.ConnectionState
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseTLSVersion(version string) (uint16, error) {
|
||||||
|
switch version {
|
||||||
|
case "1.0":
|
||||||
|
return tls.VersionTLS10, nil
|
||||||
|
case "1.1":
|
||||||
|
return tls.VersionTLS11, nil
|
||||||
|
case "1.2":
|
||||||
|
return tls.VersionTLS12, nil
|
||||||
|
case "1.3":
|
||||||
|
return tls.VersionTLS13, nil
|
||||||
|
default:
|
||||||
|
return 0, E.New("unknown tls version:", version)
|
||||||
|
}
|
||||||
|
}
|
||||||
207
common/tls/ech_client.go
Normal file
207
common/tls/ech_client.go
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
//go:build with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
cftls "github.com/sagernet/sing-box/transport/cloudflaretls"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type echClientConfig struct {
|
||||||
|
config *cftls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *echClientConfig) NextProtos() []string {
|
||||||
|
return e.config.NextProtos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *echClientConfig) SetNextProtos(nextProto []string) {
|
||||||
|
e.config.NextProtos = nextProto
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type echConnWrapper struct {
|
||||||
|
*cftls.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echConnWrapper) ConnectionState() tls.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 newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
|
var serverName string
|
||||||
|
if options.ServerName != "" {
|
||||||
|
serverName = options.ServerName
|
||||||
|
} else if serverAddress != "" {
|
||||||
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
|
serverName = serverAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if serverName == "" && !options.Insecure {
|
||||||
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsConfig cftls.Config
|
||||||
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
tlsConfig.ServerName = serverName
|
||||||
|
}
|
||||||
|
if options.Insecure {
|
||||||
|
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||||
|
} else if options.DisableSNI {
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
tlsConfig.VerifyConnection = func(state cftls.ConnectionState) error {
|
||||||
|
verifyOptions := x509.VerifyOptions{
|
||||||
|
DNSName: serverName,
|
||||||
|
Intermediates: x509.NewCertPool(),
|
||||||
|
}
|
||||||
|
for _, cert := range state.PeerCertificates[1:] {
|
||||||
|
verifyOptions.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(options.ALPN) > 0 {
|
||||||
|
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 cftls.CipherSuites() {
|
||||||
|
if cipherSuite == tlsCipherSuite.Name {
|
||||||
|
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||||
|
continue find
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var certificate []byte
|
||||||
|
if options.Certificate != "" {
|
||||||
|
certificate = []byte(options.Certificate)
|
||||||
|
} else if options.CertificatePath != "" {
|
||||||
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read certificate")
|
||||||
|
}
|
||||||
|
certificate = content
|
||||||
|
}
|
||||||
|
if len(certificate) > 0 {
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
if !certPool.AppendCertsFromPEM(certificate) {
|
||||||
|
return nil, E.New("failed to parse certificate:\n\n", certificate)
|
||||||
|
}
|
||||||
|
tlsConfig.RootCAs = certPool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECH Config
|
||||||
|
|
||||||
|
tlsConfig.ECHEnabled = true
|
||||||
|
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
||||||
|
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
||||||
|
if options.ECH.Config != "" {
|
||||||
|
clientConfigContent, err := base64.StdEncoding.DecodeString(options.ECH.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientConfig, err := cftls.UnmarshalECHConfigs(clientConfigContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.ClientECHConfigs = clientConfig
|
||||||
|
} else {
|
||||||
|
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
|
||||||
|
}
|
||||||
|
return &echClientConfig{&tlsConfig}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
|
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
|
message := &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
RecursionDesired: true,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{
|
||||||
|
{
|
||||||
|
Name: serverName + ".",
|
||||||
|
Qtype: mDNS.TypeHTTPS,
|
||||||
|
Qclass: mDNS.ClassINET,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
response, err := router.Exchange(ctx, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if response.Rcode != mDNS.RcodeSuccess {
|
||||||
|
return nil, dns.RCodeError(response.Rcode)
|
||||||
|
}
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
switch resource := rr.(type) {
|
||||||
|
case *mDNS.HTTPS:
|
||||||
|
for _, value := range resource.Value {
|
||||||
|
if value.Key().String() == "ech" {
|
||||||
|
echConfig, err := base64.StdEncoding.DecodeString(value.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode ECH config")
|
||||||
|
}
|
||||||
|
return cftls.UnmarshalECHConfigs(echConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown resource record type: ", resource.Header().Rrtype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.New("no ECH config found")
|
||||||
|
}
|
||||||
|
}
|
||||||
13
common/tls/ech_stub.go
Normal file
13
common/tls/ech_stub.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
|
return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
|
||||||
|
}
|
||||||
50
common/tls/mkcert.go
Normal file
50
common/tls/mkcert.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
template := &x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
NotBefore: time.Now().Add(time.Hour * -1),
|
||||||
|
NotAfter: time.Now().Add(time.Hour),
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: serverName,
|
||||||
|
},
|
||||||
|
DNSNames: []string{serverName},
|
||||||
|
}
|
||||||
|
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privateDer, err := x509.MarshalPKCS8PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
publicPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer})
|
||||||
|
privPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer})
|
||||||
|
keyPair, err := tls.X509KeyPair(publicPem, privPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &keyPair, err
|
||||||
|
}
|
||||||
34
common/tls/server.go
Normal file
34
common/tls/server.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"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) {
|
||||||
|
return newSTDServer(ctx, 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 err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
|
||||||
|
var badConn badtls.TLSConn
|
||||||
|
badConn, err = badtls.Create(stdConn)
|
||||||
|
if err == nil {
|
||||||
|
return badConn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
|
}
|
||||||
@@ -1,34 +1,26 @@
|
|||||||
package dialer
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TLSDialer struct {
|
type stdClientConfig struct {
|
||||||
dialer N.Dialer
|
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func TLSConfig(serverAddress string, options option.OutboundTLSOptions) (*tls.Config, error) {
|
func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
if !options.Enabled {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
if _, err := netip.ParseAddr(serverName); err == nil {
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
serverName = serverAddress
|
serverName = serverAddress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,14 +54,14 @@ func TLSConfig(serverAddress string, options option.OutboundTLSOptions) (*tls.Co
|
|||||||
tlsConfig.NextProtos = options.ALPN
|
tlsConfig.NextProtos = options.ALPN
|
||||||
}
|
}
|
||||||
if options.MinVersion != "" {
|
if options.MinVersion != "" {
|
||||||
minVersion, err := option.ParseTLSVersion(options.MinVersion)
|
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse min_version")
|
return nil, E.Cause(err, "parse min_version")
|
||||||
}
|
}
|
||||||
tlsConfig.MinVersion = minVersion
|
tlsConfig.MinVersion = minVersion
|
||||||
}
|
}
|
||||||
if options.MaxVersion != "" {
|
if options.MaxVersion != "" {
|
||||||
maxVersion, err := option.ParseTLSVersion(options.MaxVersion)
|
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse max_version")
|
return nil, E.Cause(err, "parse max_version")
|
||||||
}
|
}
|
||||||
@@ -104,42 +96,21 @@ func TLSConfig(serverAddress string, options option.OutboundTLSOptions) (*tls.Co
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
return &tlsConfig, nil
|
return &stdClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
func (s *stdClientConfig) NextProtos() []string {
|
||||||
if !options.Enabled {
|
return s.config.NextProtos
|
||||||
return dialer, nil
|
|
||||||
}
|
|
||||||
tlsConfig, err := TLSConfig(serverAddress, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &TLSDialer{
|
|
||||||
dialer: dialer,
|
|
||||||
config: tlsConfig,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TLSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (s *stdClientConfig) SetNextProtos(nextProto []string) {
|
||||||
if network != N.NetworkTCP {
|
s.config.NextProtos = nextProto
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
conn, err := d.dialer.DialContext(ctx, network, destination)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return TLSClient(ctx, conn, d.config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TLSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (s *stdClientConfig) Config() (*STDConfig, error) {
|
||||||
return nil, os.ErrInvalid
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TLSClient(ctx context.Context, conn net.Conn, tlsConfig *tls.Config) (*tls.Conn, error) {
|
func (s *stdClientConfig) Client(conn net.Conn) Conn {
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
return tls.Client(conn, s.config)
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
|
||||||
defer cancel()
|
|
||||||
err := tlsConn.HandshakeContext(ctx)
|
|
||||||
return tlsConn, err
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package inbound
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -14,9 +15,7 @@ import (
|
|||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Service = (*TLSConfig)(nil)
|
type STDServerConfig struct {
|
||||||
|
|
||||||
type TLSConfig struct {
|
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
acmeService adapter.Service
|
acmeService adapter.Service
|
||||||
@@ -27,105 +26,17 @@ type TLSConfig struct {
|
|||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TLSConfig) Config() *tls.Config {
|
func (c *STDServerConfig) NextProtos() []string {
|
||||||
return c.config
|
return c.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TLSConfig) Start() error {
|
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||||
if c.acmeService != nil {
|
c.config.NextProtos = nextProto
|
||||||
return c.acmeService.Start()
|
|
||||||
} else {
|
|
||||||
if c.certificatePath == "" && c.keyPath == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := c.startWatcher()
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn("create fsnotify watcher: ", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TLSConfig) startWatcher() error {
|
var errInsecureUnused = E.New("tls: insecure unused")
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c.certificatePath != "" {
|
|
||||||
err = watcher.Add(c.certificatePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.keyPath != "" {
|
|
||||||
err = watcher.Add(c.keyPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.watcher = watcher
|
|
||||||
go c.loopUpdate()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TLSConfig) loopUpdate() {
|
func newSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event, ok := <-c.watcher.Events:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if event.Op&fsnotify.Write != fsnotify.Write {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := c.reloadKeyPair()
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Error(E.Cause(err, "reload TLS key pair"))
|
|
||||||
}
|
|
||||||
case err, ok := <-c.watcher.Errors:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.logger.Error(E.Cause(err, "fsnotify error"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TLSConfig) reloadKeyPair() error {
|
|
||||||
if c.certificatePath != "" {
|
|
||||||
certificate, err := os.ReadFile(c.certificatePath)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "reload certificate from ", c.certificatePath)
|
|
||||||
}
|
|
||||||
c.certificate = certificate
|
|
||||||
}
|
|
||||||
if c.keyPath != "" {
|
|
||||||
key, err := os.ReadFile(c.keyPath)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "reload key from ", c.keyPath)
|
|
||||||
}
|
|
||||||
c.key = key
|
|
||||||
}
|
|
||||||
keyPair, err := tls.X509KeyPair(c.certificate, c.key)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "reload key pair")
|
|
||||||
}
|
|
||||||
c.config.Certificates = []tls.Certificate{keyPair}
|
|
||||||
c.logger.Info("reloaded TLS certificate")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TLSConfig) Close() error {
|
|
||||||
if c.acmeService != nil {
|
|
||||||
return c.acmeService.Close()
|
|
||||||
}
|
|
||||||
if c.watcher != nil {
|
|
||||||
return c.watcher.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTLSConfig(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*TLSConfig, error) {
|
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -134,9 +45,13 @@ func NewTLSConfig(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||||
|
//nolint:staticcheck
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if options.Insecure {
|
||||||
|
return nil, errInsecureUnused
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{}
|
tlsConfig = &tls.Config{}
|
||||||
}
|
}
|
||||||
@@ -147,14 +62,14 @@ func NewTLSConfig(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||||
}
|
}
|
||||||
if options.MinVersion != "" {
|
if options.MinVersion != "" {
|
||||||
minVersion, err := option.ParseTLSVersion(options.MinVersion)
|
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse min_version")
|
return nil, E.Cause(err, "parse min_version")
|
||||||
}
|
}
|
||||||
tlsConfig.MinVersion = minVersion
|
tlsConfig.MinVersion = minVersion
|
||||||
}
|
}
|
||||||
if options.MaxVersion != "" {
|
if options.MaxVersion != "" {
|
||||||
maxVersion, err := option.ParseTLSVersion(options.MaxVersion)
|
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse max_version")
|
return nil, E.Cause(err, "parse max_version")
|
||||||
}
|
}
|
||||||
@@ -193,19 +108,25 @@ func NewTLSConfig(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
}
|
}
|
||||||
key = content
|
key = content
|
||||||
}
|
}
|
||||||
if certificate == nil {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
return nil, E.New("missing certificate")
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
return GenerateKeyPair(info.ServerName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if certificate == nil {
|
||||||
|
return nil, E.New("missing certificate")
|
||||||
|
} else if key == nil {
|
||||||
|
return nil, E.New("missing key")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPair, err := tls.X509KeyPair(certificate, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse x509 key pair")
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
||||||
}
|
}
|
||||||
if key == nil {
|
|
||||||
return nil, E.New("missing key")
|
|
||||||
}
|
|
||||||
keyPair, err := tls.X509KeyPair(certificate, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse x509 key pair")
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
|
||||||
}
|
}
|
||||||
return &TLSConfig{
|
return &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
@@ -215,3 +136,109 @@ func NewTLSConfig(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
keyPath: options.KeyPath,
|
keyPath: options.KeyPath,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) Server(conn net.Conn) Conn {
|
||||||
|
return tls.Server(conn, c.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *STDServerConfig) Start() error {
|
||||||
|
if c.acmeService != nil {
|
||||||
|
return c.acmeService.Start()
|
||||||
|
} else {
|
||||||
|
if c.certificatePath == "" && c.keyPath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := c.startWatcher()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *STDServerConfig) startWatcher() error {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.certificatePath != "" {
|
||||||
|
err = watcher.Add(c.certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.keyPath != "" {
|
||||||
|
err = watcher.Add(c.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.watcher = watcher
|
||||||
|
go c.loopUpdate()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *STDServerConfig) loopUpdate() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-c.watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := c.reloadKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error(E.Cause(err, "reload TLS key pair"))
|
||||||
|
}
|
||||||
|
case err, ok := <-c.watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.logger.Error(E.Cause(err, "fsnotify error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *STDServerConfig) reloadKeyPair() error {
|
||||||
|
if c.certificatePath != "" {
|
||||||
|
certificate, err := os.ReadFile(c.certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload certificate from ", c.certificatePath)
|
||||||
|
}
|
||||||
|
c.certificate = certificate
|
||||||
|
}
|
||||||
|
if c.keyPath != "" {
|
||||||
|
key, err := os.ReadFile(c.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload key from ", c.keyPath)
|
||||||
|
}
|
||||||
|
c.key = key
|
||||||
|
}
|
||||||
|
keyPair, err := tls.X509KeyPair(c.certificate, c.key)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload key pair")
|
||||||
|
}
|
||||||
|
c.config.Certificates = []tls.Certificate{keyPair}
|
||||||
|
c.logger.Info("reloaded TLS certificate")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *STDServerConfig) Close() error {
|
||||||
|
if c.acmeService != nil {
|
||||||
|
return c.acmeService.Close()
|
||||||
|
}
|
||||||
|
if c.watcher != nil {
|
||||||
|
return c.watcher.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
151
common/tls/utls_client.go
Normal file
151
common/tls/utls_client.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
//go:build with_utls
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type utlsClientConfig struct {
|
||||||
|
config *utls.Config
|
||||||
|
id utls.ClientHelloID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *utlsClientConfig) NextProtos() []string {
|
||||||
|
return e.config.NextProtos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *utlsClientConfig) SetNextProtos(nextProto []string) {
|
||||||
|
e.config.NextProtos = nextProto
|
||||||
|
}
|
||||||
|
|
||||||
|
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, e.id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type utlsConnWrapper struct {
|
||||||
|
*utls.UConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *utlsConnWrapper) HandshakeContext(ctx context.Context) error {
|
||||||
|
return c.Conn.Handshake()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *utlsConnWrapper) ConnectionState() tls.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 newUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
|
var serverName string
|
||||||
|
if options.ServerName != "" {
|
||||||
|
serverName = options.ServerName
|
||||||
|
} else if serverAddress != "" {
|
||||||
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
|
serverName = serverAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if serverName == "" && !options.Insecure {
|
||||||
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsConfig utls.Config
|
||||||
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
tlsConfig.ServerName = serverName
|
||||||
|
}
|
||||||
|
if options.Insecure {
|
||||||
|
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||||
|
} else if options.DisableSNI {
|
||||||
|
return nil, E.New("disable_sni is unsupported in uTLS")
|
||||||
|
}
|
||||||
|
if len(options.ALPN) > 0 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var certificate []byte
|
||||||
|
if options.Certificate != "" {
|
||||||
|
certificate = []byte(options.Certificate)
|
||||||
|
} else if options.CertificatePath != "" {
|
||||||
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read certificate")
|
||||||
|
}
|
||||||
|
certificate = content
|
||||||
|
}
|
||||||
|
if len(certificate) > 0 {
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
if !certPool.AppendCertsFromPEM(certificate) {
|
||||||
|
return nil, E.New("failed to parse certificate:\n\n", certificate)
|
||||||
|
}
|
||||||
|
tlsConfig.RootCAs = certPool
|
||||||
|
}
|
||||||
|
var id utls.ClientHelloID
|
||||||
|
switch options.UTLS.Fingerprint {
|
||||||
|
case "chrome", "":
|
||||||
|
id = utls.HelloChrome_Auto
|
||||||
|
case "firefox":
|
||||||
|
id = utls.HelloFirefox_Auto
|
||||||
|
case "ios":
|
||||||
|
id = utls.HelloIOS_Auto
|
||||||
|
case "android":
|
||||||
|
id = utls.HelloAndroid_11_OkHttp
|
||||||
|
case "random":
|
||||||
|
id = utls.HelloRandomized
|
||||||
|
}
|
||||||
|
return &utlsClientConfig{&tlsConfig, id}, nil
|
||||||
|
}
|
||||||
13
common/tls/utls_stub.go
Normal file
13
common/tls/utls_stub.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !with_utls
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
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`)
|
||||||
|
}
|
||||||
@@ -3,3 +3,5 @@ package constant
|
|||||||
import E "github.com/sagernet/sing/common/exceptions"
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
var ErrTLSRequired = E.New("TLS required")
|
var ErrTLSRequired = E.New("TLS required")
|
||||||
|
|
||||||
|
var ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)
|
||||||
|
|||||||
@@ -1,25 +1,29 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeTun = "tun"
|
TypeTun = "tun"
|
||||||
TypeRedirect = "redirect"
|
TypeRedirect = "redirect"
|
||||||
TypeTProxy = "tproxy"
|
TypeTProxy = "tproxy"
|
||||||
TypeDirect = "direct"
|
TypeDirect = "direct"
|
||||||
TypeBlock = "block"
|
TypeBlock = "block"
|
||||||
TypeDNS = "dns"
|
TypeDNS = "dns"
|
||||||
TypeSocks = "socks"
|
TypeSocks = "socks"
|
||||||
TypeHTTP = "http"
|
TypeHTTP = "http"
|
||||||
TypeMixed = "mixed"
|
TypeMixed = "mixed"
|
||||||
TypeShadowsocks = "shadowsocks"
|
TypeShadowsocks = "shadowsocks"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
|
TypeShadowTLS = "shadowtls"
|
||||||
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
|
TypeVLESS = "vless"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeSelector = "selector"
|
TypeSelector = "selector"
|
||||||
|
TypeURLTest = "urltest"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build with_quic
|
|
||||||
|
|
||||||
package constant
|
|
||||||
|
|
||||||
const QUIC_AVAILABLE = true
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
//go:build !with_quic
|
|
||||||
|
|
||||||
package constant
|
|
||||||
|
|
||||||
import E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
const QUIC_AVAILABLE = false
|
|
||||||
|
|
||||||
var ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)
|
|
||||||
@@ -3,10 +3,11 @@ package constant
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TCPTimeout = 5 * time.Second
|
TCPTimeout = 5 * time.Second
|
||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
DNSTimeout = 10 * time.Second
|
DNSTimeout = 10 * time.Second
|
||||||
QUICTimeout = 30 * time.Second
|
QUICTimeout = 30 * time.Second
|
||||||
STUNTimeout = 15 * time.Second
|
STUNTimeout = 15 * time.Second
|
||||||
UDPTimeout = 5 * time.Minute
|
UDPTimeout = 5 * time.Minute
|
||||||
|
DefaultURLTestInterval = 1 * time.Minute
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
var (
|
var Version = "1.1-beta9"
|
||||||
Version = "1.0-beta2"
|
|
||||||
Commit = ""
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,3 +1,202 @@
|
|||||||
|
#### 1.1-beta9
|
||||||
|
|
||||||
|
* Fix windows route **1**
|
||||||
|
* Add [v2ray statistics api](/configuration/experimental#v2ray-api-fields)
|
||||||
|
* Add ShadowTLS v2 support **2**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
* Fix DNS leak caused by
|
||||||
|
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
||||||
|
* Flush Windows DNS cache when start/close
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
See [ShadowTLS inbound](/configuration/inbound/shadowtls#version) and [ShadowTLS outbound](/configuration/outbound/shadowtls#version)
|
||||||
|
|
||||||
|
#### 1.1-beta8
|
||||||
|
|
||||||
|
* Fix leaks on close
|
||||||
|
* Improve websocket writer
|
||||||
|
* Refine tproxy write back
|
||||||
|
* Refine 4in6 processing
|
||||||
|
* Fix shadowsocks plugins
|
||||||
|
* Fix missing source address from transport connection
|
||||||
|
* Fix fqdn socks5 outbound connection
|
||||||
|
* Fix read source address from grpc-go
|
||||||
|
|
||||||
|
#### 1.0.5
|
||||||
|
|
||||||
|
* Fix missing source address from transport connection
|
||||||
|
* Fix fqdn socks5 outbound connection
|
||||||
|
* Fix read source address from grpc-go
|
||||||
|
|
||||||
|
#### 1.1-beta7
|
||||||
|
|
||||||
|
* Add v2ray mux and XUDP support for VMess inbound
|
||||||
|
* Add XUDP support for VMess outbound
|
||||||
|
* Disable DF on direct outbound by default
|
||||||
|
* Fix bugs in 1.1-beta6
|
||||||
|
|
||||||
|
#### 1.1-beta6
|
||||||
|
|
||||||
|
* Add [URLTest outbound](/configuration/outbound/urltest)
|
||||||
|
* Fix bugs in 1.1-beta5
|
||||||
|
|
||||||
|
#### 1.1-beta5
|
||||||
|
|
||||||
|
* Print tags in version command
|
||||||
|
* Redirect clash hello to external ui
|
||||||
|
* Move shadowsocksr implementation to clash
|
||||||
|
* Make gVisor optional **1**
|
||||||
|
* Refactor to miekg/dns
|
||||||
|
* Refactor bind control
|
||||||
|
* Fix build on go1.18
|
||||||
|
* Fix clash store-selected
|
||||||
|
* Fix close grpc conn
|
||||||
|
* Fix port rule match logic
|
||||||
|
* Fix clash api proxy type
|
||||||
|
|
||||||
|
*1*:
|
||||||
|
|
||||||
|
The build tag `no_gvisor` is replaced by `with_gvisor`.
|
||||||
|
|
||||||
|
The default tun stack is changed to system.
|
||||||
|
|
||||||
|
#### 1.0.4
|
||||||
|
|
||||||
|
* Fix close grpc conn
|
||||||
|
* Fix port rule match logic
|
||||||
|
* Fix clash api proxy type
|
||||||
|
|
||||||
|
#### 1.1-beta4
|
||||||
|
|
||||||
|
* Add internal simple-obfs and v2ray-plugin [Shadowsocks plugins](/configuration/outbound/shadowsocks#plugin)
|
||||||
|
* Add [ShadowsocksR outbound](/configuration/outbound/shadowsocksr)
|
||||||
|
* Add [VLESS outbound and XUDP](/configuration/outbound/vless)
|
||||||
|
* Skip wait for hysteria tcp handshake response
|
||||||
|
* Fix socks4 client
|
||||||
|
* Fix hysteria inbound
|
||||||
|
* Fix concurrent write
|
||||||
|
|
||||||
|
#### 1.0.3
|
||||||
|
|
||||||
|
* Fix socks4 client
|
||||||
|
* Fix hysteria inbound
|
||||||
|
* Fix concurrent write
|
||||||
|
|
||||||
|
#### 1.1-beta3
|
||||||
|
|
||||||
|
* Fix using custom TLS client in http2 client
|
||||||
|
* Fix bugs in 1.1-beta2
|
||||||
|
|
||||||
|
#### 1.1-beta2
|
||||||
|
|
||||||
|
* Add Clash mode and persistence support **1**
|
||||||
|
* Add TLS ECH and uTLS support for outbound TLS options **2**
|
||||||
|
* Fix socks4 request
|
||||||
|
* Fix processing empty dns result
|
||||||
|
|
||||||
|
*1*:
|
||||||
|
|
||||||
|
Switching modes using the Clash API, and `store-selected` are now supported,
|
||||||
|
see [Experimental](/configuration/experimental).
|
||||||
|
|
||||||
|
*2*:
|
||||||
|
|
||||||
|
ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello
|
||||||
|
message, see [TLS#ECH](/configuration/shared/tls#ech).
|
||||||
|
|
||||||
|
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance,
|
||||||
|
see [TLS#uTLS](/configuration/shared/tls#utls).
|
||||||
|
|
||||||
|
#### 1.0.2
|
||||||
|
|
||||||
|
* Fix socks4 request
|
||||||
|
* Fix processing empty dns result
|
||||||
|
|
||||||
|
#### 1.1-beta1
|
||||||
|
|
||||||
|
* Add support for use with android VPNService **1**
|
||||||
|
* Add tun support for WireGuard outbound **2**
|
||||||
|
* Add system tun stack **3**
|
||||||
|
* Add comment filter for config **4**
|
||||||
|
* Add option for allow optional proxy protocol header
|
||||||
|
* Add half close for smux
|
||||||
|
* Set UDP DF by default **5**
|
||||||
|
* Set default tun mtu to 9000
|
||||||
|
* Update gVisor to 20220905.0
|
||||||
|
|
||||||
|
*1*:
|
||||||
|
|
||||||
|
In previous versions, Android VPN would not work with tun enabled.
|
||||||
|
|
||||||
|
The usage of tun over VPN and VPN over tun is now supported, see [Tun Inbound](/configuration/inbound/tun#auto_route).
|
||||||
|
|
||||||
|
*2*:
|
||||||
|
|
||||||
|
In previous releases, WireGuard outbound support was backed by the lower performance gVisor virtual interface.
|
||||||
|
|
||||||
|
It achieves the same performance as wireguard-go by providing automatic system interface support.
|
||||||
|
|
||||||
|
*3*:
|
||||||
|
|
||||||
|
It does not depend on gVisor and has better performance in some cases.
|
||||||
|
|
||||||
|
It is less compatible and may not be available in some environments.
|
||||||
|
|
||||||
|
*4*:
|
||||||
|
|
||||||
|
Annotated json configuration files are now supported.
|
||||||
|
|
||||||
|
*5*:
|
||||||
|
|
||||||
|
UDP fragmentation is now blocked by default.
|
||||||
|
|
||||||
|
Including shadowsocks-libev, shadowsocks-rust and quic-go all disable segmentation by default.
|
||||||
|
|
||||||
|
See [Dial Fields](/configuration/shared/dial#udp_fragment)
|
||||||
|
and [Listen Fields](/configuration/shared/listen#udp_fragment).
|
||||||
|
|
||||||
|
#### 1.0.1
|
||||||
|
|
||||||
|
* Fix match 4in6 address in ip_cidr
|
||||||
|
* Fix clash api log level format error
|
||||||
|
* Fix clash api unknown proxy type
|
||||||
|
|
||||||
|
#### 1.0
|
||||||
|
|
||||||
|
* Fix wireguard reconnect
|
||||||
|
* Fix naive inbound
|
||||||
|
* Fix json format error message
|
||||||
|
* Fix processing vmess termination signal
|
||||||
|
* Fix hysteria stream error
|
||||||
|
* Fix listener close when proxyproto failed
|
||||||
|
|
||||||
|
#### 1.0-rc1
|
||||||
|
|
||||||
|
* Fix write log timestamp
|
||||||
|
* Fix write zero
|
||||||
|
* Fix dial parallel in direct outbound
|
||||||
|
* Fix write trojan udp
|
||||||
|
* Fix DNS routing
|
||||||
|
* Add attribute support for geosite
|
||||||
|
* Update documentation for [Dial Fields](/configuration/shared/dial)
|
||||||
|
|
||||||
|
#### 1.0-beta3
|
||||||
|
|
||||||
|
* Add [chained inbound](/configuration/shared/listen#detour) support
|
||||||
|
* Add process_path rule item
|
||||||
|
* Add macOS redirect support
|
||||||
|
* Add ShadowTLS [Inbound](/configuration/inbound/shadowtls), [Outbound](/configuration/outbound/shadowtls)
|
||||||
|
and [Examples](/examples/shadowtls)
|
||||||
|
* Fix search android package in non-owner users
|
||||||
|
* Fix socksaddr type condition
|
||||||
|
* Fix smux session status
|
||||||
|
* Refactor inbound and outbound documentation
|
||||||
|
* Minor fixes
|
||||||
|
|
||||||
#### 1.0-beta2
|
#### 1.0-beta2
|
||||||
|
|
||||||
* Add strict_route option for [Tun inbound](/configuration/inbound/tun#strict_route)
|
* Add strict_route option for [Tun inbound](/configuration/inbound/tun#strict_route)
|
||||||
@@ -111,4 +310,4 @@
|
|||||||
|
|
||||||
No changelog before.
|
No changelog before.
|
||||||
|
|
||||||
[#9]: https://github.com/SagerNet/sing-box/pull/9
|
[#9]: https://github.com/SagerNet/sing-box/pull/9
|
||||||
|
|||||||
@@ -61,6 +61,9 @@
|
|||||||
"process_name": [
|
"process_name": [
|
||||||
"curl"
|
"curl"
|
||||||
],
|
],
|
||||||
|
"process_path": [
|
||||||
|
"/usr/bin/curl"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -70,6 +73,7 @@
|
|||||||
"user_id": [
|
"user_id": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
|
"clash_mode": "direct",
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -100,8 +104,10 @@
|
|||||||
|
|
||||||
The default rule uses the following matching logic:
|
The default rule uses the following matching logic:
|
||||||
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
|
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
|
||||||
|
(`port` || `port_range`) &&
|
||||||
(`source_geoip` || `source_ip_cidr`) &&
|
(`source_geoip` || `source_ip_cidr`) &&
|
||||||
`other fields`
|
(`source_port` || `source_port_range`) &&
|
||||||
|
`other fields`
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
@@ -177,6 +183,14 @@ Match port range.
|
|||||||
|
|
||||||
Match process name.
|
Match process name.
|
||||||
|
|
||||||
|
#### process_path
|
||||||
|
|
||||||
|
!!! error ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match process path.
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
@@ -197,6 +211,10 @@ Match user name.
|
|||||||
|
|
||||||
Match user id.
|
Match user id.
|
||||||
|
|
||||||
|
#### clash_mode
|
||||||
|
|
||||||
|
Match Clash mode.
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
Invert match result.
|
Invert match result.
|
||||||
|
|||||||
@@ -60,6 +60,9 @@
|
|||||||
"process_name": [
|
"process_name": [
|
||||||
"curl"
|
"curl"
|
||||||
],
|
],
|
||||||
|
"process_path": [
|
||||||
|
"/usr/bin/curl"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -69,6 +72,7 @@
|
|||||||
"user_id": [
|
"user_id": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
|
"clash_mode": "direct",
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -99,8 +103,10 @@
|
|||||||
|
|
||||||
默认规则使用以下匹配逻辑:
|
默认规则使用以下匹配逻辑:
|
||||||
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
|
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
|
||||||
|
(`port` || `port_range`) &&
|
||||||
(`source_geoip` || `source_ip_cidr`) &&
|
(`source_geoip` || `source_ip_cidr`) &&
|
||||||
`other fields`
|
(`source_port` || `source_port_range`) &&
|
||||||
|
`other fields`
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
@@ -176,6 +182,14 @@
|
|||||||
|
|
||||||
匹配进程名称。
|
匹配进程名称。
|
||||||
|
|
||||||
|
#### process_path
|
||||||
|
|
||||||
|
!!! error ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配进程路径。
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
@@ -196,6 +210,10 @@
|
|||||||
|
|
||||||
匹配用户 ID。
|
匹配用户 ID。
|
||||||
|
|
||||||
|
#### clash_mode
|
||||||
|
|
||||||
|
匹配 Clash 模式。
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
反选匹配结果。
|
反选匹配结果。
|
||||||
|
|||||||
@@ -8,25 +8,43 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
"secret": ""
|
"secret": "",
|
||||||
|
"direct_io": false,
|
||||||
|
"default_mode": "rule",
|
||||||
|
"store_selected": false,
|
||||||
|
"cache_file": "cache.db"
|
||||||
|
},
|
||||||
|
"v2ray_api": {
|
||||||
|
"listen": "127.0.0.1:8080",
|
||||||
|
"stats": {
|
||||||
|
"enabled": true,
|
||||||
|
"direct_io": false,
|
||||||
|
"inbounds": [
|
||||||
|
"socks-in"
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
"proxy",
|
||||||
|
"direct"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
Traffic statistics and connection management can degrade performance.
|
||||||
|
|
||||||
### Clash API Fields
|
### Clash API Fields
|
||||||
|
|
||||||
!!! error ""
|
!!! error ""
|
||||||
|
|
||||||
Clash API is not included by default, see [Installation](/#installation).
|
Clash API is not included by default, see [Installation](/#installation).
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
Traffic statistics and connection management will disable TCP splice in linux and reduce performance, use at your own risk.
|
|
||||||
|
|
||||||
#### external_controller
|
#### external_controller
|
||||||
|
|
||||||
RESTful web API listening address. Disabled if empty.
|
RESTful web API listening address. Clash API will be disabled if empty.
|
||||||
|
|
||||||
#### external_ui
|
#### external_ui
|
||||||
|
|
||||||
@@ -38,4 +56,56 @@ serve it at `http://{{external-controller}}/ui`.
|
|||||||
|
|
||||||
Secret for the RESTful API (optional)
|
Secret for the RESTful API (optional)
|
||||||
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
||||||
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
||||||
|
|
||||||
|
#### direct_io
|
||||||
|
|
||||||
|
Allows lossless relays like splice without real-time traffic reporting.
|
||||||
|
|
||||||
|
#### default_mode
|
||||||
|
|
||||||
|
Default mode in clash, `rule` will be used if empty.
|
||||||
|
|
||||||
|
This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
|
||||||
|
|
||||||
|
#### store_selected
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
The tag must be set for target outbounds.
|
||||||
|
|
||||||
|
Store selected outbound for the `Selector` outbound in cache file.
|
||||||
|
|
||||||
|
#### cache_file
|
||||||
|
|
||||||
|
Cache file path, `cache.db` will be used if empty.
|
||||||
|
|
||||||
|
### V2Ray API Fields
|
||||||
|
|
||||||
|
!!! error ""
|
||||||
|
|
||||||
|
V2Ray API is not included by default, see [Installation](/#installation).
|
||||||
|
|
||||||
|
#### listen
|
||||||
|
|
||||||
|
gRPC API listening address. V2Ray API will be disabled if empty.
|
||||||
|
|
||||||
|
#### stats
|
||||||
|
|
||||||
|
Traffic statistics service settings.
|
||||||
|
|
||||||
|
#### stats.enabled
|
||||||
|
|
||||||
|
Enable statistics service.
|
||||||
|
|
||||||
|
#### stats.direct_io
|
||||||
|
|
||||||
|
Allows lossless relays like splice without real-time traffic reporting.
|
||||||
|
|
||||||
|
#### stats.inbounds
|
||||||
|
|
||||||
|
Inbound list to count traffic.
|
||||||
|
|
||||||
|
#### stats.outbounds
|
||||||
|
|
||||||
|
Outbound list to count traffic.
|
||||||
|
|||||||
@@ -8,25 +8,43 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
"secret": ""
|
"secret": "",
|
||||||
|
"direct_io": false,
|
||||||
|
"default_mode": "rule",
|
||||||
|
"store_selected": false,
|
||||||
|
"cache_file": "cache.db"
|
||||||
|
},
|
||||||
|
"v2ray_api": {
|
||||||
|
"listen": "127.0.0.1:8080",
|
||||||
|
"stats": {
|
||||||
|
"enabled": true,
|
||||||
|
"direct_io": false,
|
||||||
|
"inbounds": [
|
||||||
|
"socks-in"
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
"proxy",
|
||||||
|
"direct"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
流量统计和连接管理会降低性能。
|
||||||
|
|
||||||
### Clash API 字段
|
### Clash API 字段
|
||||||
|
|
||||||
!!! error ""
|
!!! error ""
|
||||||
|
|
||||||
默认安装不包含 Clash API,参阅 [安装](/zh/#_2)。
|
默认安装不包含 Clash API,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
流量统计和连接管理将禁用 Linux 中的 TCP splice 并降低性能,使用风险自负。
|
|
||||||
|
|
||||||
#### external_controller
|
#### external_controller
|
||||||
|
|
||||||
RESTful web API 监听地址。
|
RESTful web API 监听地址。如果为空,则禁用 Clash API。
|
||||||
|
|
||||||
#### external_ui
|
#### external_ui
|
||||||
|
|
||||||
@@ -36,4 +54,56 @@ RESTful web API 监听地址。
|
|||||||
|
|
||||||
RESTful API 的密钥(可选)
|
RESTful API 的密钥(可选)
|
||||||
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
|
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
|
||||||
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
|
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
|
||||||
|
|
||||||
|
#### direct_io
|
||||||
|
|
||||||
|
允许像 splice 这样的没有实时流量报告的无损中继。
|
||||||
|
|
||||||
|
#### default_mode
|
||||||
|
|
||||||
|
Clash 中的默认模式,默认使用 `rule`。
|
||||||
|
|
||||||
|
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
|
||||||
|
|
||||||
|
#### store_selected
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
必须为目标出站设置标签。
|
||||||
|
|
||||||
|
将 `Selector` 中出站的选定的目标出站存储在缓存文件中。
|
||||||
|
|
||||||
|
#### cache_file
|
||||||
|
|
||||||
|
缓存文件路径,默认使用`cache.db`。
|
||||||
|
|
||||||
|
### V2Ray API 字段
|
||||||
|
|
||||||
|
!!! error ""
|
||||||
|
|
||||||
|
默认安装不包含 V2Ray API,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
#### listen
|
||||||
|
|
||||||
|
gRPC API 监听地址。如果为空,则禁用 V2Ray API。
|
||||||
|
|
||||||
|
#### stats
|
||||||
|
|
||||||
|
流量统计服务设置。
|
||||||
|
|
||||||
|
#### stats.enabled
|
||||||
|
|
||||||
|
启用统计服务。
|
||||||
|
|
||||||
|
#### stats.direct_io
|
||||||
|
|
||||||
|
允许像 splice 这样的没有实时流量报告的无损中继。
|
||||||
|
|
||||||
|
#### stats.inbounds
|
||||||
|
|
||||||
|
统计流量的入站列表。
|
||||||
|
|
||||||
|
#### stats.outbounds
|
||||||
|
|
||||||
|
统计流量的出站列表。
|
||||||
|
|||||||
@@ -4,29 +4,22 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "direct",
|
||||||
{
|
"tag": "direct-in",
|
||||||
"type": "direct",
|
|
||||||
"tag": "direct-in",
|
... // Listen Fields
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 5353,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"udp_timeout": 300,
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"network": "udp",
|
"network": "udp",
|
||||||
"override_address": "1.0.0.1",
|
"override_address": "1.0.0.1",
|
||||||
"override_port": 53
|
"override_port": 53
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Direct Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
@@ -40,50 +33,4 @@ Override the connection destination address.
|
|||||||
|
|
||||||
#### override_port
|
#### override_port
|
||||||
|
|
||||||
Override the connection destination port.
|
Override the connection destination port.
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
Enable tcp fast open for listener.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|
||||||
#### udp_timeout
|
|
||||||
|
|
||||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
|
||||||
@@ -4,29 +4,22 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "direct",
|
||||||
{
|
"tag": "direct-in",
|
||||||
"type": "direct",
|
|
||||||
"tag": "direct-in",
|
|
||||||
|
|
||||||
"listen": "::",
|
... // 监听字段
|
||||||
"listen_port": 5353,
|
|
||||||
"tcp_fast_open": false,
|
"network": "udp",
|
||||||
"sniff": false,
|
"override_address": "1.0.0.1",
|
||||||
"sniff_override_destination": false,
|
"override_port": 53
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"udp_timeout": 300,
|
|
||||||
|
|
||||||
"network": "udp",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
"override_address": "1.0.0.1",
|
|
||||||
"override_port": 53
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Direct 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
@@ -42,48 +35,3 @@
|
|||||||
|
|
||||||
覆盖连接目标端口。
|
覆盖连接目标端口。
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
为监听器启用 TCP 快速打开。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### udp_timeout
|
|
||||||
|
|
||||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
|
||||||
|
|||||||
@@ -2,33 +2,27 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "http",
|
||||||
|
"tag": "http-in",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"users": [
|
||||||
{
|
{
|
||||||
"type": "http",
|
"username": "admin",
|
||||||
"tag": "http-in",
|
"password": "admin"
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"password": "admin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls": {},
|
|
||||||
"set_system_proxy": false
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tls": {},
|
||||||
|
"set_system_proxy": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### HTTP Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
@@ -47,45 +41,3 @@ No authentication required if empty.
|
|||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|
||||||
Automatically set system proxy configuration when start and clean up when stop.
|
Automatically set system proxy configuration when start and clean up when stop.
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
Enable tcp fast open for listener.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
|
||||||
@@ -2,33 +2,27 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "http",
|
||||||
|
"tag": "http-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"users": [
|
||||||
{
|
{
|
||||||
"type": "http",
|
"username": "admin",
|
||||||
"tag": "http-in",
|
"password": "admin"
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"password": "admin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls": {},
|
|
||||||
"set_system_proxy": false
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tls": {},
|
||||||
|
"set_system_proxy": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### HTTP 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
@@ -38,7 +32,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
|||||||
|
|
||||||
HTTP 用户
|
HTTP 用户
|
||||||
|
|
||||||
默认不需要验证。
|
如果为空则不需要验证。
|
||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
@@ -46,46 +40,4 @@ HTTP 用户
|
|||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
启动时自动设置系统代理,停止时自动清理。
|
启动时自动设置系统代理,停止时自动清理。
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
为监听器启用 TCP 快速打开。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
|
||||||
@@ -2,31 +2,23 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "hysteria",
|
||||||
{
|
"tag": "hysteria-in",
|
||||||
"type": "hysteria",
|
|
||||||
"tag": "hysteria-in",
|
... // Listen Fields
|
||||||
|
|
||||||
"listen": "::",
|
"up": "100 Mbps",
|
||||||
"listen_port": 443,
|
"up_mbps": 100,
|
||||||
"sniff": false,
|
"down": "100 Mbps",
|
||||||
"sniff_override_destination": false,
|
"down_mbps": 100,
|
||||||
"domain_strategy": "prefer_ipv6",
|
"obfs": "fuck me till the daylight",
|
||||||
|
"auth": "",
|
||||||
"up": "100 Mbps",
|
"auth_str": "password",
|
||||||
"up_mbps": 100,
|
"recv_window_conn": 0,
|
||||||
"down": "100 Mbps",
|
"recv_window_client": 0,
|
||||||
"down_mbps": 100,
|
"max_conn_client": 0,
|
||||||
"obfs": "fuck me till the daylight",
|
"disable_mtu_discovery": false,
|
||||||
"auth": "",
|
"tls": {}
|
||||||
"auth_str": "password",
|
|
||||||
"recv_window_conn": 0,
|
|
||||||
"recv_window_client": 0,
|
|
||||||
"max_conn_client": 0,
|
|
||||||
"disable_mtu_discovery": false,
|
|
||||||
"tls": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -34,7 +26,11 @@
|
|||||||
|
|
||||||
QUIC, which is required by hysteria is not included by default, see [Installation](/#installation).
|
QUIC, which is required by hysteria is not included by default, see [Installation](/#installation).
|
||||||
|
|
||||||
### Hysteria Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### up, down
|
#### up, down
|
||||||
|
|
||||||
@@ -101,38 +97,4 @@ Force enabled on for systems other than Linux and Windows (according to upstream
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
@@ -2,31 +2,23 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "hysteria",
|
||||||
{
|
"tag": "hysteria-in",
|
||||||
"type": "hysteria",
|
|
||||||
"tag": "hysteria-in",
|
... // 监听字段
|
||||||
|
|
||||||
"listen": "::",
|
"up": "100 Mbps",
|
||||||
"listen_port": 443,
|
"up_mbps": 100,
|
||||||
"sniff": false,
|
"down": "100 Mbps",
|
||||||
"sniff_override_destination": false,
|
"down_mbps": 100,
|
||||||
"domain_strategy": "prefer_ipv6",
|
"obfs": "fuck me till the daylight",
|
||||||
|
"auth": "",
|
||||||
"up": "100 Mbps",
|
"auth_str": "password",
|
||||||
"up_mbps": 100,
|
"recv_window_conn": 0,
|
||||||
"down": "100 Mbps",
|
"recv_window_client": 0,
|
||||||
"down_mbps": 100,
|
"max_conn_client": 0,
|
||||||
"obfs": "fuck me till the daylight",
|
"disable_mtu_discovery": false,
|
||||||
"auth": "",
|
"tls": {}
|
||||||
"auth_str": "password",
|
|
||||||
"recv_window_conn": 0,
|
|
||||||
"recv_window_client": 0,
|
|
||||||
"max_conn_client": 0,
|
|
||||||
"disable_mtu_discovery": false,
|
|
||||||
"tls": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -34,7 +26,11 @@
|
|||||||
|
|
||||||
默认安装不包含被 Hysteria 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
默认安装不包含被 Hysteria 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
### Hysteria 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### up, down
|
#### up, down
|
||||||
|
|
||||||
@@ -101,38 +97,4 @@ base64 编码的认证密码。
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
@@ -15,20 +15,20 @@
|
|||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
| Type | Format |
|
| Type | Format | Injectable |
|
||||||
|---------------|------------------------------|
|
|---------------|------------------------------|------------|
|
||||||
| `direct` | [Direct](./direct) |
|
| `direct` | [Direct](./direct) | X |
|
||||||
| `mixed` | [Mixed](./mixed) |
|
| `mixed` | [Mixed](./mixed) | TCP |
|
||||||
| `socks` | [SOCKS](./socks) |
|
| `socks` | [SOCKS](./socks) | TCP |
|
||||||
| `http` | [HTTP](./http) |
|
| `http` | [HTTP](./http) | TCP |
|
||||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
| `shadowsocks` | [Shadowsocks](./shadowsocks) | TCP |
|
||||||
| `vmess` | [VMess](./vmess) |
|
| `vmess` | [VMess](./vmess) | TCP |
|
||||||
| `trojan` | [Trojan](./trojan) |
|
| `trojan` | [Trojan](./trojan) | TCP |
|
||||||
| `naive` | [Naive](./naive) |
|
| `naive` | [Naive](./naive) | X |
|
||||||
| `hysteria` | [Hysteria](./hysteria) |
|
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||||
| `tun` | [Tun](./tun) |
|
| `tun` | [Tun](./tun) | X |
|
||||||
| `redirect` | [Redirect](./redirect) |
|
| `redirect` | [Redirect](./redirect) | X |
|
||||||
| `tproxy` | [TProxy](./tproxy) |
|
| `tproxy` | [TProxy](./tproxy) | X |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -15,20 +15,20 @@
|
|||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 | 注入支持 |
|
||||||
|---------------|------------------------------|
|
|---------------|------------------------------|------|
|
||||||
| `direct` | [Direct](./direct) |
|
| `direct` | [Direct](./direct) | X |
|
||||||
| `mixed` | [Mixed](./mixed) |
|
| `mixed` | [Mixed](./mixed) | TCP |
|
||||||
| `socks` | [SOCKS](./socks) |
|
| `socks` | [SOCKS](./socks) | TCP |
|
||||||
| `http` | [HTTP](./http) |
|
| `http` | [HTTP](./http) | TCP |
|
||||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
| `shadowsocks` | [Shadowsocks](./shadowsocks) | TCP |
|
||||||
| `vmess` | [VMess](./vmess) |
|
| `vmess` | [VMess](./vmess) | TCP |
|
||||||
| `trojan` | [Trojan](./trojan) |
|
| `trojan` | [Trojan](./trojan) | TCP |
|
||||||
| `naive` | [Naive](./naive) |
|
| `naive` | [Naive](./naive) | X |
|
||||||
| `hysteria` | [Hysteria](./hysteria) |
|
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||||
| `tun` | [Tun](./tun) |
|
| `tun` | [Tun](./tun) | X |
|
||||||
| `redirect` | [Redirect](./redirect) |
|
| `redirect` | [Redirect](./redirect) | X |
|
||||||
| `tproxy` | [TProxy](./tproxy) |
|
| `tproxy` | [TProxy](./tproxy) | X |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -4,32 +4,26 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"users": [
|
||||||
{
|
{
|
||||||
"type": "mixed",
|
"username": "admin",
|
||||||
"tag": "mixed-in",
|
"password": "admin"
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"password": "admin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"set_system_proxy": false
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"set_system_proxy": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mixed Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### users
|
#### users
|
||||||
|
|
||||||
@@ -39,52 +33,6 @@ No authentication required if empty.
|
|||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
!!! error ""
|
|
||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
|
||||||
|
|
||||||
Automatically set system proxy configuration when start and clean up when stop.
|
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
Enable tcp fast open for listener.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|
||||||
#### set_system_proxy
|
|
||||||
|
|
||||||
!!! error ""
|
!!! error ""
|
||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|||||||
@@ -4,38 +4,32 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"users": [
|
||||||
{
|
{
|
||||||
"type": "mixed",
|
"username": "admin",
|
||||||
"tag": "mixed-in",
|
"password": "admin"
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"password": "admin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"set_system_proxy": false
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"set_system_proxy": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mixed 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### users
|
#### users
|
||||||
|
|
||||||
SOCKS 和 HTTP 用户
|
SOCKS 和 HTTP 用户
|
||||||
|
|
||||||
默认不需要验证。
|
如果为空则不需要验证。
|
||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
@@ -43,46 +37,4 @@ SOCKS 和 HTTP 用户
|
|||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
启动时自动设置系统代理,停止时自动清理。
|
启动时自动设置系统代理,停止时自动清理。
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
为监听器启用 TCP 快速打开。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
|
||||||
@@ -2,29 +2,19 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "naive",
|
||||||
|
"tag": "naive-in",
|
||||||
|
"network": "udp",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"users": [
|
||||||
{
|
{
|
||||||
"type": "naive",
|
"username": "sekai",
|
||||||
"tag": "naive-in",
|
"password": "password"
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 443,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"network": "udp",
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"username": "sekai",
|
|
||||||
"password": "password"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls": {}
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tls": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -32,7 +22,11 @@
|
|||||||
|
|
||||||
HTTP3 transport is not included by default, see [Installation](/#installation).
|
HTTP3 transport is not included by default, see [Installation](/#installation).
|
||||||
|
|
||||||
### Naive Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
@@ -48,46 +42,4 @@ Naive users.
|
|||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
Enable tcp fast open for listener.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
|
||||||
@@ -2,29 +2,19 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "naive",
|
||||||
{
|
"tag": "naive-in",
|
||||||
"type": "naive",
|
"network": "udp",
|
||||||
"tag": "naive-in",
|
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 443,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"network": "udp",
|
... // 监听字段
|
||||||
"users": [
|
|
||||||
{
|
"users": [
|
||||||
"username": "sekai",
|
{
|
||||||
"password": "password"
|
"username": "sekai",
|
||||||
}
|
"password": "password"
|
||||||
],
|
|
||||||
"tls": {}
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tls": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -32,7 +22,11 @@
|
|||||||
|
|
||||||
默认安装不包含 HTTP3 传输层, 参阅 [安装](/zh/#_2)。
|
默认安装不包含 HTTP3 传输层, 参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
### Naive 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
@@ -48,46 +42,4 @@ Naive 用户。
|
|||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
为监听器启用 TCP 快速打开。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
|
||||||
@@ -1,52 +1,18 @@
|
|||||||
|
!!! error ""
|
||||||
|
|
||||||
|
Only supported on Linux and macOS.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "redirect",
|
||||||
{
|
"tag": "redirect-in",
|
||||||
"type": "redirect",
|
|
||||||
"tag": "redirect-in",
|
... // Listen Fields
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 5353,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
#### listen
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|||||||
@@ -1,52 +1,17 @@
|
|||||||
|
!!! error ""
|
||||||
|
|
||||||
|
仅支持 Linux 和 macOS。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "redirect",
|
||||||
{
|
"tag": "redirect-in",
|
||||||
"type": "redirect",
|
|
||||||
"tag": "redirect-in",
|
... // 监听字段
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 5353,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 监听字段
|
### 监听字段
|
||||||
|
|
||||||
#### listen
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|||||||
@@ -2,25 +2,13 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "shadowsocks",
|
||||||
{
|
"tag": "ss-in",
|
||||||
"type": "shadowsocks",
|
|
||||||
"tag": "ss-in",
|
... // Listen Fields
|
||||||
|
|
||||||
"listen": "::",
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
"listen_port": 5353,
|
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"udp_timeout": 300,
|
|
||||||
"network": "udp",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -28,17 +16,12 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
|
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||||
|
"users": [
|
||||||
{
|
{
|
||||||
"type": "shadowsocks",
|
"name": "sekai",
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"name": "sekai",
|
|
||||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -48,25 +31,25 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "shadowsocks",
|
||||||
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
|
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||||
|
"destinations": [
|
||||||
{
|
{
|
||||||
"type": "shadowsocks",
|
"name": "test",
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
"server": "example.com",
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
"server_port": 8080,
|
||||||
"destinations": [
|
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||||
{
|
|
||||||
"name": "test",
|
|
||||||
"server": "example.com",
|
|
||||||
"server_port": 8080,
|
|
||||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shadowsocks Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,13 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "shadowsocks",
|
||||||
{
|
"tag": "ss-in",
|
||||||
"type": "shadowsocks",
|
|
||||||
"tag": "ss-in",
|
... // 监听字段
|
||||||
|
|
||||||
"listen": "::",
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
"listen_port": 5353,
|
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"udp_timeout": 300,
|
|
||||||
"network": "udp",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -28,17 +16,12 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
|
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||||
|
"users": [
|
||||||
{
|
{
|
||||||
"type": "shadowsocks",
|
"name": "sekai",
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"name": "sekai",
|
|
||||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -48,25 +31,25 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "shadowsocks",
|
||||||
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
|
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||||
|
"destinations": [
|
||||||
{
|
{
|
||||||
"type": "shadowsocks",
|
"name": "test",
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
"server": "example.com",
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
"server_port": 8080,
|
||||||
"destinations": [
|
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||||
{
|
|
||||||
"name": "test",
|
|
||||||
"server": "example.com",
|
|
||||||
"server_port": 8080,
|
|
||||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shadowsocks 字段
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
@@ -98,50 +81,4 @@
|
|||||||
|---------------|-------------------------------|
|
|---------------|-------------------------------|
|
||||||
| none | / |
|
| none | / |
|
||||||
| 2022 methods | `openssl rand -base64 <密钥长度>` |
|
| 2022 methods | `openssl rand -base64 <密钥长度>` |
|
||||||
| other methods | 任意字符串 |
|
| other methods | 任意字符串 |
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
为监听器启用 TCP 快速打开。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### udp_timeout
|
|
||||||
|
|
||||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
|
||||||
46
docs/configuration/inbound/shadowtls.md
Normal file
46
docs/configuration/inbound/shadowtls.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "shadowtls",
|
||||||
|
"tag": "st-in",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"version": 2,
|
||||||
|
"password": "fuck me till the daylight",
|
||||||
|
"handshake": {
|
||||||
|
"server": "google.com",
|
||||||
|
"server_port": 443,
|
||||||
|
|
||||||
|
... // Dial Fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### version
|
||||||
|
|
||||||
|
ShadowTLS protocol version.
|
||||||
|
|
||||||
|
| Value | 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) |
|
||||||
|
|
||||||
|
#### password
|
||||||
|
|
||||||
|
Set password.
|
||||||
|
|
||||||
|
Only available in the ShadowTLS v2 protocol.
|
||||||
|
|
||||||
|
#### handshake
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||||
46
docs/configuration/inbound/shadowtls.zh.md
Normal file
46
docs/configuration/inbound/shadowtls.zh.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "shadowtls",
|
||||||
|
"tag": "st-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"version": 2,
|
||||||
|
"password": "fuck me till the daylight",
|
||||||
|
"handshake": {
|
||||||
|
"server": "google.com",
|
||||||
|
"server_port": 443,
|
||||||
|
|
||||||
|
... // 拨号字段
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### version
|
||||||
|
|
||||||
|
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) |
|
||||||
|
|
||||||
|
#### password
|
||||||
|
|
||||||
|
设置密码。
|
||||||
|
|
||||||
|
仅在 ShadowTLS v2 协议中可用。
|
||||||
|
|
||||||
|
#### handshake
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||||
@@ -4,76 +4,28 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "socks",
|
||||||
{
|
"tag": "socks-in",
|
||||||
"type": "socks",
|
|
||||||
"tag": "socks-in",
|
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
... // Listen Fields
|
||||||
{
|
|
||||||
"username": "admin",
|
"users": [
|
||||||
"password": "admin"
|
{
|
||||||
}
|
"username": "admin",
|
||||||
]
|
"password": "admin"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### SOCKS Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### users
|
#### users
|
||||||
|
|
||||||
SOCKS users.
|
SOCKS users.
|
||||||
|
|
||||||
No authentication required if empty.
|
No authentication required if empty.
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
Enable tcp fast open for listener.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
|
||||||
|
|||||||
@@ -4,76 +4,28 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "socks",
|
||||||
{
|
"tag": "socks-in",
|
||||||
"type": "socks",
|
|
||||||
"tag": "socks-in",
|
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
... // 监听字段
|
||||||
{
|
|
||||||
"username": "admin",
|
"users": [
|
||||||
"password": "admin"
|
{
|
||||||
}
|
"username": "admin",
|
||||||
]
|
"password": "admin"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### SOCKS 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### users
|
#### users
|
||||||
|
|
||||||
SOCKS 用户
|
SOCKS 用户
|
||||||
|
|
||||||
默认不需要验证。
|
如果为空则不需要验证。
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
为监听器启用 TCP 快速打开。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
|
||||||
|
|||||||
@@ -1,67 +1,28 @@
|
|||||||
|
!!! error ""
|
||||||
|
|
||||||
|
Only supported on Linux.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "tproxy",
|
||||||
{
|
"tag": "tproxy-in",
|
||||||
"type": "tproxy",
|
|
||||||
"tag": "tproxy-in",
|
... // Listen Fields
|
||||||
|
|
||||||
"listen": "::",
|
"network": "udp"
|
||||||
"listen_port": 5353,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"udp_timeout": 300,
|
|
||||||
|
|
||||||
"network": "udp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### TProxy Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
Listen network, one of `tcp` `udp`.
|
Listen network, one of `tcp` `udp`.
|
||||||
|
|
||||||
Both if empty.
|
Both if empty.
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|
||||||
#### udp_timeout
|
|
||||||
|
|
||||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
|
||||||
|
|||||||
@@ -1,67 +1,28 @@
|
|||||||
|
!!! error ""
|
||||||
|
|
||||||
|
仅支持 Linux。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "tproxy",
|
||||||
{
|
"tag": "tproxy-in",
|
||||||
"type": "tproxy",
|
|
||||||
"tag": "tproxy-in",
|
... // 监听字段
|
||||||
|
|
||||||
"listen": "::",
|
"network": "udp"
|
||||||
"listen_port": 5353,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"udp_timeout": 300,
|
|
||||||
|
|
||||||
"network": "udp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### TProxy 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
监听的网络协议,`tcp` `udp` 之一。
|
监听的网络协议,`tcp` `udp` 之一。
|
||||||
|
|
||||||
默认所有。
|
默认所有。
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### udp_timeout
|
|
||||||
|
|
||||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
|
||||||
@@ -2,43 +2,37 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "trojan",
|
||||||
{
|
"tag": "trojan-in",
|
||||||
"type": "trojan",
|
|
||||||
"tag": "trojan-in",
|
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
... // Listen Fields
|
||||||
{
|
|
||||||
"name": "sekai",
|
"users": [
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
{
|
||||||
}
|
"name": "sekai",
|
||||||
],
|
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||||
"tls": {},
|
|
||||||
"fallback": {
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 8080
|
|
||||||
},
|
|
||||||
"fallback_for_alpn": {
|
|
||||||
"http/1.1": {
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 8081
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"transport": {}
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tls": {},
|
||||||
|
"fallback": {
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 8080
|
||||||
|
},
|
||||||
|
"fallback_for_alpn": {
|
||||||
|
"http/1.1": {
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 8081
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Trojan Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### users
|
#### users
|
||||||
|
|
||||||
@@ -67,45 +61,3 @@ If not empty, TLS fallback requests with ALPN not in this table will be rejected
|
|||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
Enable tcp fast open for listener.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
|
||||||
@@ -2,41 +2,37 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "trojan",
|
||||||
|
"tag": "trojan-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"users": [
|
||||||
{
|
{
|
||||||
"type": "trojan",
|
"name": "sekai",
|
||||||
"tag": "trojan-in",
|
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"name": "sekai",
|
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls": {},
|
|
||||||
"fallback": {
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 8080
|
|
||||||
},
|
|
||||||
"fallback_for_alpn": {
|
|
||||||
"http/1.1": {
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 8081
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"transport": {}
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tls": {},
|
||||||
|
"fallback": {
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 8080
|
||||||
|
},
|
||||||
|
"fallback_for_alpn": {
|
||||||
|
"http/1.1": {
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 8081
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Trojan 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### users
|
#### users
|
||||||
|
|
||||||
@@ -54,7 +50,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
|||||||
|
|
||||||
!!! error ""
|
!!! error ""
|
||||||
|
|
||||||
没有证据表明 GFW 基于 HTTP 响应检测并阻止木马服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。
|
没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。
|
||||||
|
|
||||||
回退服务器配置。如果 `fallback` 和 `fallback_for_alpn` 为空,则禁用回退。
|
回退服务器配置。如果 `fallback` 和 `fallback_for_alpn` 为空,则禁用回退。
|
||||||
|
|
||||||
@@ -66,46 +62,4 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
|||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
为监听器启用 TCP 快速打开。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
|
||||||
@@ -6,48 +6,40 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "tun",
|
||||||
{
|
"tag": "tun-in",
|
||||||
"type": "tun",
|
"interface_name": "tun0",
|
||||||
"tag": "tun-in",
|
"inet4_address": "172.19.0.1/30",
|
||||||
"interface_name": "tun0",
|
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||||
"inet4_address": "172.19.0.1/30",
|
"mtu": 9000,
|
||||||
"inet6_address": "fdfe:dcba:9876::1/128",
|
"auto_route": true,
|
||||||
"mtu": 1500,
|
"strict_route": true,
|
||||||
"auto_route": true,
|
"endpoint_independent_nat": false,
|
||||||
"strict_route": true,
|
"stack": "system",
|
||||||
"endpoint_independent_nat": false,
|
"include_uid": [
|
||||||
"udp_timeout": 300,
|
0
|
||||||
"stack": "gvisor",
|
],
|
||||||
"include_uid": [
|
"include_uid_range": [
|
||||||
0
|
"1000-99999"
|
||||||
],
|
],
|
||||||
"include_uid_range": [
|
"exclude_uid": [
|
||||||
[
|
1000
|
||||||
"1000-99999"
|
],
|
||||||
]
|
"exclude_uid_range": [
|
||||||
],
|
"1000-99999"
|
||||||
"exclude_uid": [
|
],
|
||||||
1000
|
"include_android_user": [
|
||||||
],
|
0,
|
||||||
"exclude_uid_range": [
|
10
|
||||||
"1000-99999"
|
],
|
||||||
],
|
"include_package": [
|
||||||
"include_android_user": [
|
"com.android.chrome"
|
||||||
0,
|
],
|
||||||
10
|
"exclude_package": [
|
||||||
],
|
"com.android.captiveportallogin"
|
||||||
"include_package": [
|
],
|
||||||
"com.android.chrome"
|
...
|
||||||
],
|
// Listen Fields
|
||||||
"exclude_package": [
|
|
||||||
"com.android.captiveportallogin"
|
|
||||||
],
|
|
||||||
"sniff": true,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -59,7 +51,7 @@
|
|||||||
|
|
||||||
If tun is running in non-privileged mode, addresses and MTU will not be configured automatically, please make sure the settings are accurate.
|
If tun is running in non-privileged mode, addresses and MTU will not be configured automatically, please make sure the settings are accurate.
|
||||||
|
|
||||||
### Tun Fields
|
### Fields
|
||||||
|
|
||||||
#### interface_name
|
#### interface_name
|
||||||
|
|
||||||
@@ -87,9 +79,15 @@ Set the default route to the Tun.
|
|||||||
|
|
||||||
To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface`
|
To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface`
|
||||||
|
|
||||||
|
!!! note "Use with Android VPN"
|
||||||
|
|
||||||
|
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
|
||||||
|
|
||||||
#### strict_route
|
#### strict_route
|
||||||
|
|
||||||
Enforce strict routing rules in Linux when `auto_route` is enabled:
|
*In Linux*:
|
||||||
|
|
||||||
|
Enforce strict routing rules when `auto_route` is enabled:
|
||||||
|
|
||||||
* Let unsupported network unreachable
|
* Let unsupported network unreachable
|
||||||
* Route all connections to tun
|
* Route all connections to tun
|
||||||
@@ -97,8 +95,16 @@ Enforce strict routing rules in Linux when `auto_route` is enabled:
|
|||||||
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
|
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
|
||||||
not be accessible by others.
|
not be accessible by others.
|
||||||
|
|
||||||
|
*In Windows*:
|
||||||
|
|
||||||
|
Use segmented `auto_route` routing settings, which may help if you're using a dial-up network.
|
||||||
|
|
||||||
#### endpoint_independent_nat
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
|
||||||
|
This item is only available on the gvisor stack, other stacks are endpoint-independent NAT by default.
|
||||||
|
|
||||||
Enable endpoint-independent NAT.
|
Enable endpoint-independent NAT.
|
||||||
|
|
||||||
Performance may degrade slightly, so it is not recommended to enable on when it is not needed.
|
Performance may degrade slightly, so it is not recommended to enable on when it is not needed.
|
||||||
@@ -111,14 +117,15 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
|||||||
|
|
||||||
TCP/IP stack.
|
TCP/IP stack.
|
||||||
|
|
||||||
| Stack | Upstream | Status |
|
| Stack | Description | Status |
|
||||||
|------------------|-----------------------------------------------------------------------|-------------------|
|
|------------------|----------------------------------------------------------------------------------|-------------------|
|
||||||
| gVisor (default) | [google/gvisor](https://github.com/google/gvisor) | recommended |
|
| system (default) | Sometimes better performance | recommended |
|
||||||
| LWIP | [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
|
| gVisor | Better compatibility, based on [google/gvisor](https://github.com/google/gvisor) | recommended |
|
||||||
|
| LWIP | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
The LWIP stack is not included by default, see [Installation](/#installation).
|
gVisor and LWIP stacks is not included by default, see [Installation](/#installation).
|
||||||
|
|
||||||
#### include_uid
|
#### include_uid
|
||||||
|
|
||||||
@@ -163,22 +170,4 @@ Exclude android packages in route.
|
|||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
#### sniff
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|||||||
@@ -6,48 +6,41 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "tun",
|
||||||
{
|
"tag": "tun-in",
|
||||||
"type": "tun",
|
|
||||||
"tag": "tun-in",
|
"interface_name": "tun0",
|
||||||
"interface_name": "tun0",
|
"inet4_address": "172.19.0.1/30",
|
||||||
"inet4_address": "172.19.0.1/30",
|
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||||
"inet6_address": "fdfe:dcba:9876::1/128",
|
"mtu": 9000,
|
||||||
"mtu": 1500,
|
"auto_route": true,
|
||||||
"auto_route": true,
|
"strict_route": true,
|
||||||
"strict_route": true,
|
"endpoint_independent_nat": false,
|
||||||
"endpoint_independent_nat": false,
|
"stack": "system",
|
||||||
"udp_timeout": 300,
|
"include_uid": [
|
||||||
"stack": "gvisor",
|
0
|
||||||
"include_uid": [
|
],
|
||||||
0
|
"include_uid_range": [
|
||||||
],
|
"1000-99999"
|
||||||
"include_uid_range": [
|
],
|
||||||
[
|
"exclude_uid": [
|
||||||
"1000-99999"
|
1000
|
||||||
]
|
],
|
||||||
],
|
"exclude_uid_range": [
|
||||||
"exclude_uid": [
|
"1000-99999"
|
||||||
1000
|
],
|
||||||
],
|
"include_android_user": [
|
||||||
"exclude_uid_range": [
|
0,
|
||||||
"1000-99999"
|
10
|
||||||
],
|
],
|
||||||
"include_android_user": [
|
"include_package": [
|
||||||
0,
|
"com.android.chrome"
|
||||||
10
|
],
|
||||||
],
|
"exclude_package": [
|
||||||
"include_package": [
|
"com.android.captiveportallogin"
|
||||||
"com.android.chrome"
|
],
|
||||||
],
|
|
||||||
"exclude_package": [
|
... // 监听字段
|
||||||
"com.android.captiveportallogin"
|
|
||||||
],
|
|
||||||
"sniff": true,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -87,15 +80,25 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
为避免流量环回,请设置 `route.auto_detect_interface` 或 `route.default_interface` 或 `outbound.bind_interface`。
|
为避免流量环回,请设置 `route.auto_detect_interface` 或 `route.default_interface` 或 `outbound.bind_interface`。
|
||||||
|
|
||||||
|
!!! note "与 Android VPN 一起使用"
|
||||||
|
|
||||||
|
VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。
|
||||||
|
|
||||||
#### strict_route
|
#### strict_route
|
||||||
|
|
||||||
在 Linux 中启用 `auto_route` 时执行严格的路由规则。
|
*在 Linux 中*:
|
||||||
|
|
||||||
|
启用 `auto_route` 时执行严格的路由规则。
|
||||||
|
|
||||||
* 让不支持的网络无法到达
|
* 让不支持的网络无法到达
|
||||||
* 将所有连接路由到 tun
|
* 将所有连接路由到 tun
|
||||||
|
|
||||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
|
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
|
||||||
|
|
||||||
|
*在 Windows 中*:
|
||||||
|
|
||||||
|
使用分段的 `auto_route` 路由设置,如果您使用的是拨号网络,这可能会有所帮助。
|
||||||
|
|
||||||
#### endpoint_independent_nat
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
启用独立于端点的 NAT。
|
启用独立于端点的 NAT。
|
||||||
@@ -110,14 +113,15 @@ UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
|||||||
|
|
||||||
TCP/IP 栈。
|
TCP/IP 栈。
|
||||||
|
|
||||||
| 栈 | 上游 | 状态 |
|
| 栈 | 描述 | 状态 |
|
||||||
|------------------|-----------------------------------------------------------------------|-------|
|
|-------------|--------------------------------------------------------------------------|-------|
|
||||||
| gVisor (default) | [google/gvisor](https://github.com/google/gvisor) | 推荐 |
|
| system (默认) | 有时性能更好 | 推荐 |
|
||||||
| LWIP | [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 |
|
| gVisor | 兼容性较好,基于 [google/gvisor](https://github.com/google/gvisor) | 推荐 |
|
||||||
|
| LWIP | 基于 [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
默认安装不包含 LWIP 栈,请参阅 [安装](/zh/#_2)。
|
默认安装不包含 gVisor 和 LWIP 栈,请参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
#### include_uid
|
#### include_uid
|
||||||
|
|
||||||
@@ -147,10 +151,10 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
限制被路由的 Android 用户。
|
限制被路由的 Android 用户。
|
||||||
|
|
||||||
| 常用用户 | ID |
|
| 常用用户 | ID |
|
||||||
|--|-----|
|
|--|-----|
|
||||||
| 您 | 0 |
|
| 您 | 0 |
|
||||||
| 工作资料 | 10 |
|
| 工作资料 | 10 |
|
||||||
|
|
||||||
#### include_package
|
#### include_package
|
||||||
|
|
||||||
@@ -162,22 +166,4 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
### 监听字段
|
### 监听字段
|
||||||
|
|
||||||
#### sniff
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
@@ -2,34 +2,28 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "vmess",
|
||||||
{
|
"tag": "vmess-in",
|
||||||
"type": "vmess",
|
|
||||||
"tag": "vmess-in",
|
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
... // Listen Fields
|
||||||
{
|
|
||||||
"name": "sekai",
|
"users": [
|
||||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
{
|
||||||
"alterId": 0
|
"name": "sekai",
|
||||||
}
|
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||||
],
|
"alterId": 0
|
||||||
"tls": {},
|
|
||||||
"transport": {}
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tls": {},
|
||||||
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### VMess Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
#### users
|
#### users
|
||||||
|
|
||||||
@@ -53,45 +47,3 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
|||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
Enable tcp fast open for listener.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
|
||||||
|
|
||||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
|
||||||
@@ -2,34 +2,28 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"type": "vmess",
|
||||||
{
|
"tag": "vmess-in",
|
||||||
"type": "vmess",
|
|
||||||
"tag": "vmess-in",
|
|
||||||
|
|
||||||
"listen": "::",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"sniff": false,
|
|
||||||
"sniff_override_destination": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"proxy_protocol": false,
|
|
||||||
|
|
||||||
"users": [
|
... // 监听字段
|
||||||
{
|
|
||||||
"name": "sekai",
|
"users": [
|
||||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
{
|
||||||
"alterId": 0
|
"name": "sekai",
|
||||||
}
|
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||||
],
|
"alterId": 0
|
||||||
"tls": {},
|
|
||||||
"transport": {}
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tls": {},
|
||||||
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### VMess 字段
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
#### users
|
#### users
|
||||||
|
|
||||||
@@ -53,45 +47,3 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
|||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听地址。
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
监听端口。
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
为监听器启用 TCP 快速打开。
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
启用协议探测。
|
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)。
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
|
||||||
|
|
||||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ sing-box uses JSON for configuration files.
|
|||||||
{
|
{
|
||||||
"log": {},
|
"log": {},
|
||||||
"dns": {},
|
"dns": {},
|
||||||
"inbounds": {},
|
"inbounds": [],
|
||||||
"outbounds": {},
|
"outbounds": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
"experimental": {}
|
"experimental": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
{
|
{
|
||||||
"log": {},
|
"log": {},
|
||||||
"dns": {},
|
"dns": {},
|
||||||
"inbounds": {},
|
"inbounds": [],
|
||||||
"outbounds": {},
|
"outbounds": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
"experimental": {}
|
"experimental": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# Log
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# 日志
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -4,12 +4,8 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"outbounds": [
|
"type": "block",
|
||||||
{
|
"tag": "block"
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,8 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"outbounds": [
|
"type": "block",
|
||||||
{
|
"tag": "block"
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,30 +4,18 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"outbounds": [
|
"type": "direct",
|
||||||
{
|
"tag": "direct-out",
|
||||||
"type": "direct",
|
|
||||||
"tag": "direct-out",
|
"override_address": "1.0.0.1",
|
||||||
|
"override_port": 53,
|
||||||
"override_address": "1.0.0.1",
|
"proxy_protocol": 0,
|
||||||
"override_port": 53,
|
|
||||||
"proxy_protocol": 0,
|
... // Dial Fields
|
||||||
|
|
||||||
"detour": "upstream-out",
|
|
||||||
"bind_interface": "en0",
|
|
||||||
"bind_address": "0.0.0.0",
|
|
||||||
"routing_mark": 1234,
|
|
||||||
"reuse_addr": false,
|
|
||||||
"connect_timeout": "5s",
|
|
||||||
"tcp_fast_open": false,
|
|
||||||
"domain_strategy": "prefer_ipv6",
|
|
||||||
"fallback_delay": "300ms"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Direct Fields
|
### Fields
|
||||||
|
|
||||||
#### override_address
|
#### override_address
|
||||||
|
|
||||||
@@ -45,54 +33,4 @@ Protocol value can be `1` or `2`.
|
|||||||
|
|
||||||
### Dial Fields
|
### Dial Fields
|
||||||
|
|
||||||
#### detour
|
See [Dial Fields](/configuration/shared/dial) for details.
|
||||||
|
|
||||||
The tag of the upstream outbound.
|
|
||||||
|
|
||||||
Other dial fields will be ignored when enabled.
|
|
||||||
|
|
||||||
#### bind_interface
|
|
||||||
|
|
||||||
The network interface to bind to.
|
|
||||||
|
|
||||||
#### bind_address
|
|
||||||
|
|
||||||
The address to bind to.
|
|
||||||
|
|
||||||
#### routing_mark
|
|
||||||
|
|
||||||
!!! error ""
|
|
||||||
|
|
||||||
Only supported on Linux.
|
|
||||||
|
|
||||||
Set netfilter routing mark.
|
|
||||||
|
|
||||||
#### reuse_addr
|
|
||||||
|
|
||||||
Reuse listener address.
|
|
||||||
|
|
||||||
#### connect_timeout
|
|
||||||
|
|
||||||
Connect timeout, in golang's Duration format.
|
|
||||||
|
|
||||||
A duration string is a possibly signed sequence of
|
|
||||||
decimal numbers, each with optional fraction and a unit suffix,
|
|
||||||
such as "300ms", "-1.5h" or "2h45m".
|
|
||||||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before connect.
|
|
||||||
|
|
||||||
`dns.strategy` will be used if empty.
|
|
||||||
|
|
||||||
#### fallback_delay
|
|
||||||
|
|
||||||
The length of time to wait before spawning a RFC 6555 Fast Fallback connection.
|
|
||||||
That is, is the amount of time to wait for IPv6 to succeed before assuming
|
|
||||||
that IPv6 is misconfigured and falling back to IPv4 if `prefer_ipv4` is set.
|
|
||||||
If zero, a default delay of 300ms is used.
|
|
||||||
|
|
||||||
Only take effect when `domain_strategy` is `prefer_ipv4` or `prefer_ipv6`.
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user