Compare commits

..

1 Commits

Author SHA1 Message Date
世界
f48f8c5d1c Add daemon support 2022-08-25 11:07:41 +08:00
457 changed files with 7264 additions and 41724 deletions

View File

@@ -1,5 +1,6 @@
name: Bug Report name: Bug Report
description: "Create a report to help us improve." description: "Create a report to help us improve."
labels: [ bug ]
body: body:
- type: checkboxes - type: checkboxes
id: terms id: terms
@@ -12,7 +13,7 @@ body:
required: true required: true
- label: Yes, I've searched similar issues on GitHub and didn't find any. - label: Yes, I've searched similar issues on GitHub and didn't find any.
required: true required: true
- label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc). - label: Yes, I've included all information below (version, config, log, etc).
required: true required: true
- type: textarea - type: textarea
@@ -31,7 +32,7 @@ body:
<details> <details>
```console ```console
$ sing-box version $ sing-box --version
# Paste output here # Paste output here
``` ```
@@ -55,7 +56,7 @@ body:
required: true required: true
- type: textarea - type: textarea
id: log id: config
attributes: attributes:
label: Server and client log file label: Server and client log file
value: |- value: |-

28
.github/renovate.json vendored
View File

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

View File

@@ -1,5 +1,10 @@
#!/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

View File

@@ -3,36 +3,38 @@ name: Debug build
on: on:
push: push:
branches: branches:
- main-next - 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-next - dev
- dev-next
jobs: jobs:
build: build:
name: Debug build name: Debug build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get latest go version - name: Get latest go version
id: version id: version
run: | run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT 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 - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v2
with: with:
go-version: ${{ steps.version.outputs.go_version }} go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module - name: Cache go module
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: | path: |
~/go/pkg/mod ~/go/pkg/mod
@@ -49,26 +51,6 @@ jobs:
- name: Run Test - name: Run Test
run: | run: |
go test -v ./... go test -v ./...
build_go118:
name: Debug build (Go 1.18)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.18.10
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make
cross: cross:
strategy: strategy:
matrix: matrix:
@@ -142,9 +124,6 @@ jobs:
- name: linux-mips64el - name: linux-mips64el
goos: linux goos: linux
goarch: mips64le goarch: mips64le
- name: linux-s390x
goos: linux
goarch: s390x
# darwin # darwin
- name: darwin-amd64 - name: darwin-amd64
goos: darwin goos: darwin
@@ -185,19 +164,19 @@ jobs:
TAGS: with_clash_api,with_quic TAGS: with_clash_api,with_quic
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get latest go version - name: Get latest go version
id: version id: version
run: | run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT 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 - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v2
with: with:
go-version: ${{ steps.version.outputs.go_version }} go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module - name: Cache go module
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: | path: |
~/go/pkg/mod ~/go/pkg/mod
@@ -206,7 +185,7 @@ jobs:
id: build id: build
run: make run: make
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: sing-box-${{ matrix.name }} name: sing-box-${{ matrix.name }}
path: sing-box* path: sing-box*

View File

@@ -1,45 +0,0 @@
name: Build Docker Images
on:
workflow_dispatch:
inputs:
tag:
description: "The tag version you want to build"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Setup QEMU for Docker Buildx
uses: docker/setup-qemu-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: metadata
uses: docker/metadata-action@v4
with:
images: ghcr.io/sagernet/sing-box
- name: Get tag to build
id: tag
run: |
echo "latest=ghcr.io/sagernet/sing-box:latest" >> $GITHUB_OUTPUT
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.ref_name }}" >> $GITHUB_OUTPUT
else
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
fi
- name: Build and release Docker images
uses: docker/build-push-action@v4
with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
target: dist
tags: |
${{ steps.tag.outputs.latest }}
${{ steps.tag.outputs.versioned }}
push: true

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

@@ -5,4 +5,3 @@
/site/ /site/
/bin/ /bin/
/dist/ /dist/
/sing-box

View File

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

View File

@@ -1,7 +1,6 @@
project_name: sing-box project_name: sing-box
builds: builds:
- id: main - main: ./cmd/sing-box
main: ./cmd/sing-box
flags: flags:
- -v - -v
- -trimpath - -trimpath
@@ -10,12 +9,11 @@ builds:
gcflags: gcflags:
- all=-trimpath={{.Env.GOPATH}} - all=-trimpath={{.Env.GOPATH}}
ldflags: ldflags:
- -s -w -buildid= - -X github.com/sagernet/sing-box/constant.Commit={{ .ShortCommit }} -s -w -buildid=
tags: tags:
- with_gvisor
- with_quic - with_quic
- with_wireguard - with_wireguard
- with_utls - with_acme
- with_clash_api - with_clash_api
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
@@ -24,7 +22,6 @@ builds:
- linux_amd64_v3 - linux_amd64_v3
- linux_arm64 - linux_arm64
- linux_arm_7 - linux_arm_7
- linux_s390x
- windows_amd64_v1 - windows_amd64_v1
- windows_amd64_v3 - windows_amd64_v3
- windows_386 - windows_386
@@ -33,54 +30,6 @@ builds:
- darwin_amd64_v3 - darwin_amd64_v3
- darwin_arm64 - darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
- id: android
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_wireguard
- with_utls
- with_clash_api
env:
- CGO_ENABLED=1
overrides:
- goos: android
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi19-clang
- CXX=armv7a-linux-androideabi19-clang++
- goos: android
goarch: arm64
env:
- CC=aarch64-linux-android21-clang
- CXX=aarch64-linux-android21-clang++
- goos: android
goarch: 386
env:
- CC=i686-linux-android19-clang
- CXX=i686-linux-android19-clang++
- goos: android
goarch: amd64
goamd64: v1
env:
- CC=x86_64-linux-android21-clang
- CXX=x86_64-linux-android21-clang++
targets:
- android_arm_7
- android_arm64
- android_386
- android_amd64
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot: snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}" name_template: "{{ .Version }}.{{ .ShortCommit }}"
archives: archives:
@@ -116,9 +65,6 @@ nfpms:
dst: /etc/systemd/system/sing-box@.service dst: /etc/systemd/system/sing-box@.service
- src: LICENSE - src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE dst: /usr/share/licenses/sing-box/LICENSE
scripts:
postinstall: release/config/postinstall.sh
postremove: release/config/postremove.sh
source: source:
enabled: false enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source' name_template: '{{ .ProjectName }}-{{ .Version }}.source'

View File

@@ -1,22 +0,0 @@
FROM golang:1.20-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box
ARG GOPROXY=""
ENV GOPROXY ${GOPROXY}
ENV CGO_ENABLED=0
RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \
-o /go/bin/sing-box \
-ldflags "-s -w -buildid=" \
./cmd/sing-box
FROM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& apk upgrade \
&& apk add bash tzdata ca-certificates \
&& rm -rf /var/cache/apk/*
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]

View File

@@ -1,8 +1,9 @@
NAME = sing-box NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD) COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api TAGS ?= with_quic,with_wireguard,with_clash_api,with_daemon
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr PARAMS = -v -trimpath -tags '$(TAGS)' -ldflags \
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid=" '-X "github.com/sagernet/sing-box/constant.Commit=$(COMMIT)" \
-w -s -buildid='
MAIN = ./cmd/sing-box MAIN = ./cmd/sing-box
.PHONY: test release .PHONY: test release
@@ -16,11 +17,11 @@ install:
fmt: fmt:
@gofumpt -l -w . @gofumpt -l -w .
@gofmt -s -w . @gofmt -s -w .
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" . @gci write -s "standard,prefix(github.com/sagernet/),default" .
fmt_install: fmt_install:
go install -v mvdan.cc/gofumpt@latest go install -v mvdan.cc/gofumpt@latest
go install -v github.com/daixiang0/gci@latest go install -v github.com/daixiang0/gci@v0.4.0
lint: lint:
GOOS=linux golangci-lint run ./... GOOS=linux golangci-lint run ./...
@@ -42,37 +43,25 @@ proto_install:
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
snapshot: snapshot:
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1 goreleaser release --rm-dist --snapshot
mkdir dist/release mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 1 nightly dist/release ghr --delete --draft --prerelease -p 1 nightly dist/release
rm -r dist rm -r dist
release: snapshot_install:
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
rm -r dist
release_install:
go install -v github.com/goreleaser/goreleaser@latest go install -v github.com/goreleaser/goreleaser@latest
go install -v github.com/tcnksm/ghr@latest go install -v github.com/tcnksm/ghr@latest
test: test:
@go test -v ./... && \ @go test -v . && \
cd test && \ pushd test && \
go mod tidy && \ go mod tidy && \
go test -v -tags "$(TAGS_TEST)" . go test -v -tags with_quic,with_wireguard,with_grpc . && \
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 sing-box rm -rf bin dist
rm -f $(shell go env GOPATH)/sing-box rm -f $(shell go env GOPATH)/sing-box
update: update:

View File

@@ -4,29 +4,23 @@ 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
Mode() string TrafficController
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
@@ -38,13 +32,3 @@ 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, user string, conn net.Conn) net.Conn
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
}

View File

@@ -2,13 +2,11 @@ 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-box/option" "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 {
@@ -17,13 +15,6 @@ 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
@@ -38,18 +29,14 @@ type InboundContext struct {
// cache // cache
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr OriginDestination M.Socksaddr
InboundOptions option.InboundOptions DomainStrategy dns.DomainStrategy
SniffEnabled bool
SniffOverrideDestination bool
DestinationAddresses []netip.Addr DestinationAddresses []netip.Addr
SourceGeoIPCode string SourceGeoIPCode string
GeoIPCode string GeoIPCode string
ProcessInfo *process.Info ProcessInfo *process.Info
// dns cache
QueryType uint16
} }
type inboundContextKey struct{} type inboundContextKey struct{}

View File

@@ -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"
mdns "github.com/miekg/dns" "golang.org/x/net/dns/dnsmessage"
) )
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 *mdns.Msg) (*mdns.Msg, error) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, 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)
InterfaceFinder() control.InterfaceFinder InterfaceBindManager() control.BindManager
DefaultInterface() string DefaultInterface() string
AutoDetectInterface() bool AutoDetectInterface() bool
DefaultMark() int DefaultMark() int
@@ -39,26 +39,7 @@ 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 routerContextKey struct{}
func ContextWithRouter(ctx context.Context, router Router) context.Context {
return context.WithValue(ctx, (*routerContextKey)(nil), router)
}
func RouterFromContext(ctx context.Context) Router {
metadata := ctx.Value((*routerContextKey)(nil))
if metadata == nil {
return nil
}
return metadata.(Router)
} }
type Rule interface { type Rule interface {
@@ -74,7 +55,3 @@ type DNSRule interface {
Rule Rule
DisableCache() bool DisableCache() bool
} }
type InterfaceUpdateListener interface {
InterfaceUpdated() error
}

View File

@@ -38,25 +38,13 @@ 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 {
myMetadata := w.metadata w.metadata.Destination = metadata.Destination
if metadata.Source.IsValid() { return w.connectionHandler(ctx, conn, w.metadata)
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 {
myMetadata := w.metadata w.metadata.Destination = metadata.Destination
if metadata.Source.IsValid() { return w.packetHandler(ctx, conn, w.metadata)
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) {
@@ -90,23 +78,13 @@ 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)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination 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)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, *myMetadata) return w.packetHandler(ctx, conn, *myMetadata)
} }

View File

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

38
box.go
View File

@@ -31,7 +31,6 @@ 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{}
} }
@@ -40,15 +39,9 @@ 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
var needV2RayAPI bool if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
if options.Experimental != nil {
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
needClashAPI = true needClashAPI = true
} }
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
}
var logFactory log.Factory var logFactory log.Factory
var observableLogFactory log.ObservableFactory var observableLogFactory log.ObservableFactory
@@ -97,7 +90,8 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
router, err := route.NewRouter( router, err := route.NewRouter(
ctx, ctx,
logFactory, logFactory.NewLogger("router"),
logFactory.NewLogger("dns"),
common.PtrValueOrDefault(options.Route), common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.DNS),
options.Inbounds, options.Inbounds,
@@ -144,7 +138,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
} }
outbounds = append(outbounds, out) outbounds = append(outbounds, out)
} }
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { err = router.Initialize(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)
@@ -155,20 +149,12 @@ 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.SetClashServer(clashServer) router.SetTrafficController(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,
@@ -176,10 +162,9 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
outbounds: outbounds, outbounds: outbounds,
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.Logger(), logger: logFactory.NewLogger(""),
logFile: logFile, logFile: logFile,
clashServer: clashServer, clashServer: clashServer,
v2rayServer: v2rayServer,
done: make(chan struct{}), done: make(chan struct{}),
}, nil }, nil
} }
@@ -238,12 +223,6 @@ 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
} }
@@ -265,11 +244,6 @@ 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
}

View File

@@ -1,20 +0,0 @@
package main
import (
"os"
"os/exec"
"github.com/sagernet/sing-box/log"
)
func main() {
findSDK()
command := exec.Command(os.Args[1], os.Args[2:]...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
}

View File

@@ -1,81 +0,0 @@
package main
import (
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/rw"
)
var (
androidSDKPath string
androidNDKPath string
)
func findSDK() {
searchPath := []string{
"$ANDROID_HOME",
"$HOME/Android/Sdk",
"$HOME/.local/lib/android/sdk",
"$HOME/Library/Android/sdk",
}
for _, path := range searchPath {
path = os.ExpandEnv(path)
if rw.FileExists(path + "/licenses/android-sdk-license") {
androidSDKPath = path
break
}
}
if androidSDKPath == "" {
log.Fatal("android SDK not found")
}
if !findNDK() {
log.Fatal("android NDK not found")
}
os.Setenv("ANDROID_HOME", androidSDKPath)
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
os.Setenv("NDK", androidNDKPath)
os.Setenv("PATH", os.Getenv("PATH")+":"+filepath.Join(androidNDKPath, "toolchains", "llvm", "prebuilt", runtime.GOOS+"-x86_64", "bin"))
}
func findNDK() bool {
if rw.FileExists(androidSDKPath + "/ndk/25.1.8937393") {
androidNDKPath = androidSDKPath + "/ndk/25.1.8937393"
return true
}
ndkVersions, err := os.ReadDir(androidSDKPath + "/ndk")
if err != nil {
return false
}
versionNames := common.Map(ndkVersions, os.DirEntry.Name)
if len(versionNames) == 0 {
return false
}
sort.Slice(versionNames, func(i, j int) bool {
iVersions := strings.Split(versionNames[i], ".")
jVersions := strings.Split(versionNames[j], ".")
for k := 0; k < len(iVersions) && k < len(jVersions); k++ {
iVersion, _ := strconv.Atoi(iVersions[k])
jVersion, _ := strconv.Atoi(jVersions[k])
if iVersion != jVersion {
return iVersion > jVersion
}
}
return true
})
for _, versionName := range versionNames {
if rw.FileExists(androidSDKPath + "/ndk/" + versionName) {
androidNDKPath = androidSDKPath + "/ndk/" + versionName
return true
}
}
return false
}

272
cmd/sing-box/cmd_daemon.go Normal file
View File

@@ -0,0 +1,272 @@
//go:build with_daemon
package main
import (
"bytes"
"io"
"net"
"net/http"
"net/url"
"os"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/experimental/daemon"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/spf13/cobra"
)
var commandDaemon = &cobra.Command{
Use: "daemon",
}
func init() {
commandDaemon.AddCommand(commandDaemonInstall)
commandDaemon.AddCommand(commandDaemonUninstall)
commandDaemon.AddCommand(commandDaemonStart)
commandDaemon.AddCommand(commandDaemonStop)
commandDaemon.AddCommand(commandDaemonRestart)
commandDaemon.AddCommand(commandDaemonRun)
mainCommand.AddCommand(commandDaemon)
mainCommand.AddCommand(commandStart)
mainCommand.AddCommand(commandStop)
mainCommand.AddCommand(commandStatus)
}
var commandDaemonInstall = &cobra.Command{
Use: "install",
Short: "Install daemon",
Run: func(cmd *cobra.Command, args []string) {
err := installDaemon()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
var commandDaemonUninstall = &cobra.Command{
Use: "uninstall",
Short: "Uninstall daemon",
Run: func(cmd *cobra.Command, args []string) {
err := uninstallDaemon()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
var commandDaemonStart = &cobra.Command{
Use: "start",
Short: "Start daemon",
Run: func(cmd *cobra.Command, args []string) {
err := startDaemon()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
var commandDaemonStop = &cobra.Command{
Use: "stop",
Short: "Stop daemon",
Run: func(cmd *cobra.Command, args []string) {
err := stopDaemon()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
var commandDaemonRestart = &cobra.Command{
Use: "restart",
Short: "Restart daemon",
Run: func(cmd *cobra.Command, args []string) {
err := restartDaemon()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
var commandDaemonRun = &cobra.Command{
Use: "run",
Short: "Run daemon",
Run: func(cmd *cobra.Command, args []string) {
err := runDaemon()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
func installDaemon() error {
instance, err := daemon.New()
if err != nil {
return err
}
return instance.Install()
}
func uninstallDaemon() error {
instance, err := daemon.New()
if err != nil {
return err
}
return instance.Uninstall()
}
func startDaemon() error {
instance, err := daemon.New()
if err != nil {
return err
}
return instance.Start()
}
func stopDaemon() error {
instance, err := daemon.New()
if err != nil {
return err
}
return instance.Stop()
}
func restartDaemon() error {
instance, err := daemon.New()
if err != nil {
return err
}
return instance.Restart()
}
func runDaemon() error {
instance, err := daemon.New()
if err != nil {
return err
}
return instance.Run()
}
var commandStart = &cobra.Command{
Use: "start",
Short: "Start service",
Run: func(cmd *cobra.Command, args []string) {
err := startService()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
var commandStop = &cobra.Command{
Use: "stop",
Short: "Stop service",
Run: func(cmd *cobra.Command, args []string) {
err := stopService()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
var commandStatus = &cobra.Command{
Use: "status",
Short: "Check service",
Run: func(cmd *cobra.Command, args []string) {
err := checkService()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
func doRequest(method string, path string, params url.Values, body io.ReadCloser) ([]byte, error) {
requestURL := url.URL{
Scheme: "http",
Path: path,
Host: net.JoinHostPort("127.0.0.1", F.ToString(daemon.DefaultDaemonPort)),
}
if params != nil {
requestURL.RawQuery = params.Encode()
}
request, err := http.NewRequest(method, requestURL.String(), body)
if err != nil {
return nil, err
}
response, err := http.DefaultClient.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
var content []byte
if response.StatusCode != http.StatusNoContent {
content, err = io.ReadAll(response.Body)
if err != nil {
return nil, err
}
}
if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNoContent {
return nil, E.New(string(content))
}
return content, nil
}
func ping() error {
response, err := doRequest("GET", "/ping", nil, nil)
if err != nil || string(response) != "pong" {
return E.New("daemon not running")
}
return nil
}
func startService() error {
if err := ping(); err != nil {
return err
}
configContent, err := os.ReadFile(configPath)
if err != nil {
return E.Cause(err, "read config")
}
return common.Error(doRequest("POST", "/run", nil, io.NopCloser(bytes.NewReader(configContent))))
}
func stopService() error {
if err := ping(); err != nil {
return err
}
return common.Error(doRequest("GET", "/stop", nil, nil))
}
func checkService() error {
if err := ping(); err != nil {
return err
}
response, err := doRequest("GET", "/status", nil, nil)
if err != nil {
return err
}
var statusResponse daemon.StatusResponse
err = json.Unmarshal(response, &statusResponse)
if err != nil {
return err
}
if statusResponse.Running {
log.Info("service running")
} else {
log.Info("service stopped")
}
return nil
}

View File

@@ -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 = options.UnmarshalJSON(configContent) err = json.Unmarshal(configContent, &options)
if err != nil { if err != nil {
return E.Cause(err, "decode config") return E.Cause(err, "decode config")
} }

View File

@@ -9,6 +9,7 @@ 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"
@@ -45,7 +46,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 = options.UnmarshalJSON(configContent) err = json.Unmarshal(configContent, &options)
if err != nil { if err != nil {
return option.Options{}, E.Cause(err, "decode config") return option.Options{}, E.Cause(err, "decode config")
} }
@@ -69,20 +70,6 @@ func create() (*box.Box, context.CancelFunc, error) {
cancel() cancel()
return nil, nil, E.Cause(err, "create service") return nil, nil, E.Cause(err, "create service")
} }
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer func() {
signal.Stop(osSignals)
close(osSignals)
}()
go func() {
_, loaded := <-osSignals
if loaded {
cancel()
}
}()
err = instance.Start() err = instance.Start()
if err != nil { if err != nil {
cancel() cancel()
@@ -94,7 +81,6 @@ func create() (*box.Box, context.CancelFunc, error) {
func run() error { func run() error {
osSignals := make(chan os.Signal, 1) osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer signal.Stop(osSignals)
for { for {
instance, cancel, err := create() instance, cancel, err := create()
if err != nil { if err != nil {

View File

@@ -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,40 +25,30 @@ func init() {
} }
func printVersion(cmd *cobra.Command, args []string) { func printVersion(cmd *cobra.Command, args []string) {
if nameOnly { var version string
os.Stdout.WriteString(C.Version + "\n") if !nameOnly {
return version = "sing-box "
} }
version := "sing-box version " + C.Version + "\n\n" version += F.ToString(C.Version)
version += "Environment: " + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH + "\n" if C.Commit != "" {
version += "." + C.Commit
var tags string
var revision string
debugInfo, loaded := debug.ReadBuildInfo()
if loaded {
for _, setting := range debugInfo.Settings {
switch setting.Key {
case "-tags":
tags = setting.Value
case "vcs.revision":
revision = setting.Value
} }
} if !nameOnly {
} version += " ("
version += runtime.Version()
if tags != "" { version += ", "
version += "Tags: " + tags + "\n" version += runtime.GOOS
} version += ", "
if revision != "" { version += runtime.GOARCH
version += "Revision: " + revision + "\n" version += ", "
} version += "CGO "
if C.CGO_ENABLED { if C.CGO_ENABLED {
version += "CGO: enabled\n" version += "enabled"
} else { } else {
version += "CGO: disabled\n" version += "disabled"
} }
version += ")"
}
version += "\n"
os.Stdout.WriteString(version) os.Stdout.WriteString(version)
} }

View File

@@ -3,7 +3,6 @@ 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"

View File

@@ -1,62 +0,0 @@
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
}

View File

@@ -1,210 +0,0 @@
//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()
}

View File

@@ -1,12 +0,0 @@
//go:build !go1.19 || go1.20
package badtls
import (
"crypto/tls"
"os"
)
func Create(conn *tls.Conn) (TLSConn, error) {
return nil, os.ErrInvalid
}

View File

@@ -1,13 +0,0 @@
package badtls
import (
"context"
"crypto/tls"
"net"
)
type TLSConn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() tls.ConnectionState
}

View File

@@ -1,26 +0,0 @@
//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

19
common/dialer/bind.go Normal file
View File

@@ -0,0 +1,19 @@
package dialer
import (
"syscall"
"github.com/sagernet/sing/common/control"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func skipIfPrivate(next control.Func) control.Func {
return func(network, address string, conn syscall.RawConn) error {
destination := M.ParseSocksaddr(address)
if !N.IsPublicAddr(destination.Addr) {
return nil
}
return next(network, address, conn)
}
}

View File

@@ -3,6 +3,7 @@ package dialer
import ( import (
"context" "context"
"net" "net"
"net/netip"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -10,10 +11,10 @@ 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/sagernet/tfo-go"
"github.com/database64128/tfo-go"
) )
var warnBindInterfaceOnUnsupportedPlatform = warning.New( var warnBindInterfaceOnUnsupportedPlatform = warning.New(
@@ -52,13 +53,10 @@ var warnTFOOnUnsupportedPlatform = warning.New(
) )
type DefaultDialer struct { type DefaultDialer struct {
dialer4 tfo.Dialer dialer tfo.Dialer
dialer6 tfo.Dialer udpDialer net.Dialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig udpListener net.ListenConfig
udpAddr4 string bindUDPAddr string
udpAddr6 string
} }
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer { func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
@@ -66,23 +64,25 @@ 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.InterfaceFinder(), options.BindInterface, -1) bindFunc := skipIfPrivate(control.BindToInterface(router.InterfaceBindManager(), options.BindInterface))
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() {
const useInterfaceName = C.IsLinux if C.IsWindows {
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) { bindFunc := skipIfPrivate(control.BindToInterfaceIndexFunc(func() int {
remoteAddr := M.ParseSocksaddr(address).Addr return router.InterfaceMonitor().DefaultInterfaceIndex()
if C.IsLinux { }))
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
} else {
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
}
})
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 {
bindFunc := skipIfPrivate(control.BindToInterfaceFunc(router.InterfaceBindManager(), func() string {
return router.InterfaceMonitor().DefaultInterfaceName()
}))
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
} else if router.DefaultInterface() != "" { } else if router.DefaultInterface() != "" {
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1) bindFunc := skipIfPrivate(control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface()))
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,72 +111,29 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
if options.TCPFastOpen { if options.TCPFastOpen {
warnTFOOnUnsupportedPlatform.Check() warnTFOOnUnsupportedPlatform.Check()
} }
var udpFragment bool var bindUDPAddr string
if options.UDPFragment != nil { udpDialer := dialer
udpFragment = *options.UDPFragment bindAddress := netip.Addr(options.BindAddress)
} else { if bindAddress.IsValid() {
udpFragment = options.UDPFragmentDefault dialer.LocalAddr = &net.TCPAddr{
IP: bindAddress.AsSlice(),
} }
if !udpFragment { udpDialer.LocalAddr = &net.UDPAddr{
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment()) IP: bindAddress.AsSlice(),
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
} }
var ( bindUDPAddr = M.SocksaddrFrom(bindAddress, 0).String()
dialer4 = dialer
udpDialer4 = dialer
udpAddr4 string
)
if options.Inet4BindAddress != nil {
bindAddr := options.Inet4BindAddress.Build()
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
}
var (
dialer6 = dialer
udpDialer6 = dialer
udpAddr6 string
)
if options.Inet6BindAddress != nil {
bindAddr := options.Inet6BindAddress.Build()
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
}
return &DefaultDialer{
tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen},
tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen},
udpDialer4,
udpDialer6,
listener,
udpAddr4,
udpAddr6,
} }
return &DefaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, udpDialer, listener, bindUDPAddr}
} }
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:
if !address.IsIPv6() { return d.udpDialer.DialContext(ctx, network, address.String())
return d.udpDialer4.DialContext(ctx, network, address.String())
} else {
return d.udpDialer6.DialContext(ctx, network, address.String())
}
}
if !address.IsIPv6() {
return DialSlowContext(&d.dialer4, ctx, network, address)
} else {
return DialSlowContext(&d.dialer6, ctx, network, address)
} }
return d.dialer.DialContext(ctx, network, address.Unwrap().String())
} }
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) {
if !destination.IsIPv6() { return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.bindUDPAddr)
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
} else {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
}
} }

View File

@@ -10,12 +10,15 @@ 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 == "" {
dialer = NewDefault(router, options) return NewDefault(router, options)
} else { } else {
dialer = NewDetour(router, options.Detour) return 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))

View File

@@ -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() { if !destination.IsFqdn() || destination.Fqdn == "" {
return d.dialer.ListenPacket(ctx, destination) return d.dialer.ListenPacket(ctx, destination)
} }
ctx, metadata := adapter.AppendContext(ctx) ctx, metadata := adapter.AppendContext(ctx)

View File

@@ -1,141 +0,0 @@
package dialer
import (
"context"
"io"
"net"
"os"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/tfo-go"
)
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)
if err != nil {
c.err = E.Cause(err, "dial tcp fast open")
}
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) LazyHeadroom() 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)
}

View File

@@ -1,54 +1,34 @@
package tls package dialer
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 STDClientConfig struct { type TLSDialer struct {
dialer N.Dialer
config *tls.Config config *tls.Config
} }
func (s *STDClientConfig) ServerName() string { func TLSConfig(serverAddress string, options option.OutboundTLSOptions) (*tls.Config, error) {
return s.config.ServerName if !options.Enabled {
return nil, nil
} }
func (s *STDClientConfig) SetServerName(serverName string) {
s.config.ServerName = serverName
}
func (s *STDClientConfig) NextProtos() []string {
return s.config.NextProtos
}
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
s.config.NextProtos = nextProto
}
func (s *STDClientConfig) Config() (*STDConfig, error) {
return s.config, nil
}
func (s *STDClientConfig) Client(conn net.Conn) Conn {
return tls.Client(conn, s.config)
}
func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
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
} }
} }
@@ -82,14 +62,14 @@ func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Conf
tlsConfig.NextProtos = options.ALPN tlsConfig.NextProtos = options.ALPN
} }
if options.MinVersion != "" { if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion) minVersion, err := option.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 := ParseTLSVersion(options.MaxVersion) maxVersion, err := option.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")
} }
@@ -124,5 +104,42 @@ func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Conf
} }
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = certPool
} }
return &STDClientConfig{&tlsConfig}, nil return &tlsConfig, nil
}
func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
if !options.Enabled {
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) {
if network != N.NetworkTCP {
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) {
return nil, os.ErrInvalid
}
func TLSClient(ctx context.Context, conn net.Conn, tlsConfig *tls.Config) (*tls.Conn, error) {
tlsConn := tls.Client(conn, tlsConfig)
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
return tlsConn, err
} }

View File

@@ -4,6 +4,7 @@ import (
"net/netip" "net/netip"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
) )
@@ -30,5 +31,8 @@ func (r *Reader) Lookup(addr netip.Addr) string {
if code != "" { if code != "" {
return code return code
} }
if !N.IsPublicAddr(addr) {
return "private"
}
return "unknown" return "unknown"
} }

View File

@@ -1,128 +0,0 @@
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
}

View File

@@ -329,23 +329,6 @@ func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
return c.ExtendedConn.Write(b) return c.ExtendedConn.Write(b)
} }
func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
return
}
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error { func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
if !c.requestWrite { if !c.requestWrite {
defer buffer.Release() defer buffer.Release()
@@ -360,11 +343,6 @@ func (c *ClientPacketConn) FrontHeadroom() int {
return 2 return 2
} }
func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
err = c.ReadBuffer(buffer)
return
}
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return c.WriteBuffer(buffer) return c.WriteBuffer(buffer)
} }
@@ -488,7 +466,10 @@ func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
if err != nil { if err != nil {
return return
} }
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length)) if buffer.FreeLen() < int(length) {
return destination, io.ErrShortBuffer
}
_, err = io.ReadFull(c.ExtendedConn, buffer.Extend(int(length)))
return return
} }

View File

@@ -43,7 +43,7 @@ func ParseProtocol(name string) (Protocol, error) {
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) { func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
switch p { switch p {
case ProtocolSMux: case ProtocolSMux:
session, err := smux.Server(conn, smuxConfig()) session, err := smux.Server(conn, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -58,7 +58,7 @@ func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) { func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
switch p { switch p {
case ProtocolSMux: case ProtocolSMux:
session, err := smux.Client(conn, smuxConfig()) session, err := smux.Client(conn, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -70,12 +70,6 @@ func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
} }
} }
func smuxConfig() *smux.Config {
config := smux.DefaultConfig()
config.KeepAliveDisabled = true
return config
}
func yaMuxConfig() *yamux.Config { func yaMuxConfig() *yamux.Config {
config := yamux.DefaultConfig() config := yamux.DefaultConfig()
config.LogOutput = io.Discard config.LogOutput = io.Discard

View File

@@ -3,6 +3,7 @@ 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"
@@ -14,7 +15,6 @@ 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,8 +26,6 @@ func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
if err != nil { if err != nil {
return err return err
} }
var group task.Group
group.Append0(func(ctx context.Context) error {
var stream net.Conn var stream net.Conn
for { for {
stream, err = session.Accept() stream, err = session.Accept()
@@ -36,11 +34,6 @@ func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
} }
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) {
@@ -165,6 +158,9 @@ 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
@@ -227,6 +223,9 @@ 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

View File

@@ -28,5 +28,11 @@ 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) {
return findProcessInfo(searcher, ctx, network, source, destination) info, err := 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
} }

View File

@@ -18,21 +18,21 @@ func NewSearcher(config Config) (Searcher, error) {
} }
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
_, uid, err := resolveSocketByNetlink(network, source, destination) socket, err := resolveSocketByNetlink(network, source, destination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded { if sharedPackage, loaded := s.packageManager.SharedPackageByID(socket.UID); loaded {
return &Info{ return &Info{
UserId: int32(uid), UserId: int32(socket.UID),
PackageName: sharedPackage, PackageName: sharedPackage,
}, nil }, nil
} }
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded { if packageName, loaded := s.packageManager.PackageByID(socket.UID); loaded {
return &Info{ return &Info{
UserId: int32(uid), UserId: int32(socket.UID),
PackageName: packageName, PackageName: packageName,
}, nil }, nil
} }
return &Info{UserId: int32(uid)}, nil return &Info{UserId: int32(socket.UID)}, nil
} }

View File

@@ -5,8 +5,6 @@ import (
"encoding/binary" "encoding/binary"
"net/netip" "net/netip"
"os" "os"
"strconv"
"strings"
"syscall" "syscall"
"unsafe" "unsafe"
@@ -31,22 +29,6 @@ func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, so
return &Info{ProcessPath: processName, UserId: -1}, nil return &Info{ProcessPath: processName, UserId: -1}, nil
} }
var structSize = func() int {
value, _ := syscall.Sysctl("kern.osrelease")
major, _, _ := strings.Cut(value, ".")
n, _ := strconv.ParseInt(major, 10, 64)
switch true {
case n >= 22:
return 408
default:
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
// size/offset are round up (aligned) to 8 bytes in darwin
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
return 384
}
}()
func findProcessName(network string, ip netip.Addr, port int) (string, error) { func findProcessName(network string, ip netip.Addr, port int) (string, error) {
var spath string var spath string
switch network { switch network {
@@ -71,7 +53,7 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
// size/offset are round up (aligned) to 8 bytes in darwin // size/offset are round up (aligned) to 8 bytes in darwin
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
itemSize := structSize itemSize := 384
if network == N.NetworkTCP { if network == N.NetworkTCP {
// rup8(sizeof(xtcpcb_n)) // rup8(sizeof(xtcpcb_n))
itemSize += 208 itemSize += 208

View File

@@ -20,16 +20,16 @@ func NewSearcher(config Config) (Searcher, error) {
} }
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
inode, uid, err := resolveSocketByNetlink(network, source, destination) socket, err := resolveSocketByNetlink(network, source, destination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
processPath, err := resolveProcessNameByProcSearch(inode, uid) processPath, err := resolveProcessNameByProcSearch(socket.INode, socket.UID)
if err != nil { if err != nil {
s.logger.DebugContext(ctx, "find process path: ", err) s.logger.DebugContext(ctx, "find process path: ", err)
} }
return &Info{ return &Info{
UserId: int32(uid), UserId: int32(socket.UID),
ProcessPath: processPath, ProcessPath: processPath,
}, nil }, nil
} }

View File

@@ -6,7 +6,6 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net"
"net/netip" "net/netip"
"os" "os"
"path" "path"
@@ -15,9 +14,7 @@ import (
"unicode" "unicode"
"unsafe" "unsafe"
"github.com/sagernet/sing/common" "github.com/sagernet/netlink"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@@ -37,7 +34,7 @@ const (
pathProc = "/proc" pathProc = "/proc"
) )
func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) { func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (*netlink.Socket, error) {
var family uint8 var family uint8
var protocol uint8 var protocol uint8
@@ -47,110 +44,28 @@ func resolveSocketByNetlink(network string, source netip.AddrPort, destination n
case N.NetworkUDP: case N.NetworkUDP:
protocol = syscall.IPPROTO_UDP protocol = syscall.IPPROTO_UDP
default: default:
return 0, 0, os.ErrInvalid return nil, os.ErrInvalid
} }
if source.Addr().Is4() { if source.Addr().Is4() {
family = syscall.AF_INET family = syscall.AF_INET
} else { } else {
family = syscall.AF_INET6 family = syscall.AF_INET6
} }
sockets, err := netlink.SocketGet(family, protocol, source, netip.AddrPortFrom(netip.IPv6Unspecified(), 0))
req := packSocketDiagRequest(family, protocol, source) if err == nil {
sockets, err = netlink.SocketGet(family, protocol, source, destination)
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) }
if err != nil { if err != nil {
return 0, 0, E.Cause(err, "dial netlink") return nil, err
} }
defer syscall.Close(socket) if len(sockets) > 1 {
for _, socket := range sockets {
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100}) if socket.ID.DestinationPort == destination.Port() {
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100}) return socket, nil
err = syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
Pad: 0,
Pid: 0,
Groups: 0,
})
if err != nil {
return
} }
_, err = syscall.Write(socket, req)
if err != nil {
return 0, 0, E.Cause(err, "write netlink request")
} }
_buffer := buf.StackNew()
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
n, err := syscall.Read(socket, buffer.FreeBytes())
if err != nil {
return 0, 0, E.Cause(err, "read netlink response")
} }
return sockets[0], nil
buffer.Truncate(n)
messages, err := syscall.ParseNetlinkMessage(buffer.Bytes())
if err != nil {
return 0, 0, E.Cause(err, "parse netlink message")
} else if len(messages) == 0 {
return 0, 0, E.New("unexcepted netlink response")
}
message := messages[0]
if message.Header.Type&syscall.NLMSG_ERROR != 0 {
return 0, 0, E.New("netlink message: NLMSG_ERROR")
}
inode, uid = unpackSocketDiagResponse(&messages[0])
return
}
func packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte {
s := make([]byte, 16)
copy(s, source.Addr().AsSlice())
buf := make([]byte, sizeOfSocketDiagRequest)
nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
nativeEndian.PutUint32(buf[8:12], 0)
nativeEndian.PutUint32(buf[12:16], 0)
buf[16] = family
buf[17] = protocol
buf[18] = 0
buf[19] = 0
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
binary.BigEndian.PutUint16(buf[24:26], source.Port())
binary.BigEndian.PutUint16(buf[26:28], 0)
copy(buf[28:44], s)
copy(buf[44:60], net.IPv6zero)
nativeEndian.PutUint32(buf[60:64], 0)
nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)
return buf
}
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
if len(msg.Data) < 72 {
return 0, 0
}
data := msg.Data
uid = nativeEndian.Uint32(data[64:68])
inode = nativeEndian.Uint32(data[68:72])
return
} }
func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) { func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {

View File

@@ -13,7 +13,6 @@ 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) {
@@ -23,7 +22,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 && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) { if err != nil {
return nil, err return nil, err
} }
if bufReader.Buffered() > 0 { if bufReader.Buffered() > 0 {
@@ -34,11 +33,8 @@ func (l *Listener) Accept() (net.Conn, error) {
} }
conn = bufio.NewCachedConn(conn, cache) conn = bufio.NewCachedConn(conn, cache)
} }
if header != nil {
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{ return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(), Source: M.SocksaddrFromNet(header.SourceAddr),
Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(), Destination: M.SocksaddrFromNet(header.DestinationAddr),
}}, nil }}, nil
} }
return conn, nil
}

View File

@@ -1,64 +0,0 @@
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
}

View File

@@ -1,7 +1,6 @@
package redir package redir
import ( import (
"encoding/binary"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@@ -30,9 +29,7 @@ func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err erro
if err != nil { if err != nil {
return err return err
} }
var port [2]byte destination = netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), raw.Addr.Port)
binary.BigEndian.PutUint16(port[:], raw.Addr.Port)
destination = netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), binary.LittleEndian.Uint16(port[:]))
} }
return nil return nil
}) })

View File

@@ -1,4 +1,4 @@
//go:build !linux && !darwin //go:build !linux
package redir package redir

View File

@@ -2,11 +2,14 @@ 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"
@@ -29,18 +32,6 @@ 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 {
@@ -55,3 +46,79 @@ 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
}

View File

@@ -3,20 +3,19 @@
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
}

View File

@@ -1,7 +1,6 @@
package settings package settings
import ( import (
"net/netip"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -20,8 +19,8 @@ type systemProxy struct {
isMixed bool isMixed bool
} }
func (p *systemProxy) update(event int) error { func (p *systemProxy) update() error {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified()) newInterfaceName := p.monitor.DefaultInterfaceName()
if p.interfaceName == newInterfaceName { if p.interfaceName == newInterfaceName {
return nil return nil
} }
@@ -88,7 +87,7 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
port: port, port: port,
isMixed: isMixed, isMixed: isMixed,
} }
err := proxy.update(tun.EventInterfaceUpdate) err := proxy.update()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -7,7 +7,7 @@ import (
) )
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) { func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "") err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "<local>")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -11,10 +11,9 @@ 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"
mDNS "github.com/miekg/dns" "golang.org/x/net/dns/dnsmessage"
) )
func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) { func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) {
@@ -23,7 +22,7 @@ func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.
if err != nil { if err != nil {
return nil, err return nil, err
} }
if length == 0 { if length > 512 {
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
_buffer := buf.StackNewSize(int(length)) _buffer := buf.StackNewSize(int(length))
@@ -45,13 +44,18 @@ 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 msg mDNS.Msg var parser dnsmessage.Parser
err := msg.Unpack(packet) _, err := parser.Start(packet)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) { question, err := parser.Question()
if err != nil {
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil domain := question.Name.String()
if question.Class == dnsmessage.ClassINET && IsDomainName(domain) {
return &adapter.InboundContext{Protocol: C.ProtocolDNS /*, Domain: domain*/}, nil
}
return nil, os.ErrInvalid
} }

6
common/sniff/domain.go Normal file
View File

@@ -0,0 +1,6 @@
package sniff
import _ "unsafe" // for linkname
//go:linkname IsDomainName net.isDomainName
func IsDomainName(domain string) bool

View File

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

View File

@@ -24,7 +24,8 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
if err != nil { if err != nil {
return nil, err return nil, err
} }
if typeByte&0x40 == 0 {
if typeByte&0x80 == 0 || typeByte&0x40 == 0 {
return nil, E.New("bad type byte") return nil, E.New("bad type byte")
} }
var versionNumber uint32 var versionNumber uint32
@@ -144,6 +145,9 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
default: default:
return nil, E.New("bad packet number length") return nil, E.New("bad packet number length")
} }
if packetNumber != 0 {
return nil, E.New("bad packet number: ", packetNumber)
}
extHdrLen := hdrLen + int(packetNumberLength) extHdrLen := hdrLen + int(packetNumberLength)
copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:]) copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:])
data := newPacket[extHdrLen : int(packetLen)+hdrLen] data := newPacket[extHdrLen : int(packetLen)+hdrLen]
@@ -168,76 +172,37 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
if err != nil { if err != nil {
return nil, err return nil, err
} }
var frameType byte
var frameLen uint64
var fragments []struct {
offset uint64
length uint64
payload []byte
}
decryptedReader := bytes.NewReader(decrypted) decryptedReader := bytes.NewReader(decrypted)
for { frameType, err := decryptedReader.ReadByte()
frameType, err = decryptedReader.ReadByte()
if err == io.EOF {
break
}
switch frameType {
case 0x0:
continue
case 0x1:
continue
case 0x6:
var offset uint64
offset, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err
}
var length uint64
length, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err
}
index := len(decrypted) - decryptedReader.Len()
fragments = append(fragments, struct {
offset uint64
length uint64
payload []byte
}{offset, length, decrypted[index : index+int(length)]})
frameLen += length
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
default: for frameType == 0x0 {
// ignore unknown frame type // skip padding
frameType, err = decryptedReader.ReadByte()
if err != nil {
return nil, err
} }
} }
if frameType != 0x6 {
// not crypto frame
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, nil
}
_, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
return nil, err
}
_, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
return nil, err
}
tlsHdr := make([]byte, 5) tlsHdr := make([]byte, 5)
tlsHdr[0] = 0x16 tlsHdr[0] = 0x16
binary.BigEndian.PutUint16(tlsHdr[1:], uint16(0x0303)) binary.BigEndian.PutUint16(tlsHdr[1:], uint16(0x0303))
binary.BigEndian.PutUint16(tlsHdr[3:], uint16(frameLen)) binary.BigEndian.PutUint16(tlsHdr[3:], uint16(decryptedReader.Len()))
var index uint64 metadata, err := TLSClientHello(ctx, io.MultiReader(bytes.NewReader(tlsHdr), decryptedReader))
var length int
var readers []io.Reader
readers = append(readers, bytes.NewReader(tlsHdr))
find:
for {
for _, fragment := range fragments {
if fragment.offset == index {
readers = append(readers, bytes.NewReader(fragment.payload))
index = fragment.offset + fragment.length
length++
continue find
}
}
if length == len(fragments) {
break
}
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, E.New("bad fragments")
}
metadata, err := TLSClientHello(ctx, io.MultiReader(readers...))
if err != nil { if err != nil {
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err return nil, err
} }
metadata.Protocol = C.ProtocolQUIC metadata.Protocol = C.ProtocolQUIC
return metadata, nil return metadata, nil

View File

@@ -19,15 +19,6 @@ func TestSniffQUICv1(t *testing.T) {
require.Equal(t, metadata.Domain, "cloudflare-quic.com") require.Equal(t, metadata.Domain, "cloudflare-quic.com")
} }
func TestSniffQUICFragment(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("cc00000001082e3d5d1b64040c55000044d0ccea69e773f6631c1d18b04ae9ee75fcfc34ef74fa62533c93534338a86f101a05d70e0697fb483063fa85db1c59ccfbda5c35234931d8524d8aac37eaaad649470a67794cd754b23c98695238b8363452333bc8c4858376b4166e001da2006e35cf98a91e11a56419b2786775284942d0f7163982f7c248867d12dd374957481dbc564013ff785e1916195eef671f725908f761099d992d69231336ba81d9e25fe2fa3a6eff4318a6ccf10176fc841a1b315f7b35c5b292266fc869d76ca533e7d14e86d82db2e22eacd350977e47d2e012d8a5891c5aaf2a0f4c2b2dae897c161e5b68cbb4dee952472bdc1e21504b8f02534ec4366ce3f8bf86efc78e0232778fbd554457567112abdcafcf6d4d8fcf35083c25d9495679614aba21696e338c62b585046cc55ba8c09c844361d889a47c3ea703b4e23545a9ab2c0bb369693a9ddfb5daffa85cf80fdd6ad66738664e5b0a551729b4955cff7255afcb04dee88c2f072c9de7400947a1bd9327ac5d012a33000ada021d4c03d249fb017d6ac9200b2f9436beab8183ddfbe2d8aee31ffb7df9e1cc181c1af80c39a89965d18ed12da8e3ebe2ae1fbe4b348f83ba19e3e3d1c9b22bcf03ab6ad9b30fe180623faa291ebad83bcd71d7b57f2f5e2f3b8e81d24fb70b2f2159239e8f21ffafef2747aba47d97ab4081e603c018b10678cf99cab1fb42156a14486fa435153979d7279fd22cd40af7088bfc7eff41af2f4b3c0c8864d0040d74dff427f7bffdb8c278474ea00311326cf4925471a8cf596cb92119f19e0f789490ba9cb77b98015a987d93e0324cf1a38b55109f00c3e6ddc5180fb107bf468323afec9bb49fd6a86418569789d66cafe3b8253c2aebb3af3782c1c54dd560487d031d28e6a6e23e159581bb1d47efc4da3fe1d169f9ffb0ca9ba61af0a38a92fde5bc5e6ec026e8378a6315a7b95abf1d2da790a391306ce74d0baf8e2ce648ca74c487f2c0a76a28a80cdf5bd34316eb607684fe7e6d9e83824a00e07660d0b90e3cddd61ebf10748263474afa88c300549e64ce2e90560bb1a12dee7e9484f729a8a4ee7c5651adb5194b3b3ae38e501567c7dbf36e7bb37a2c20b74655f47f2d9af18e52e9d4c9c9eee8e63745779b8f0b06f3a09d846ba62eb978ad77c85de1ee2fee3fbb4c2d283c73e1ccba56a4658e48a2665d200f7f9342f8e84c2ba490094a4f94feec89e42d2f654f564c2beb2997bafa1fc2c68ad8e160b63587d49abc31b834878d52acfb05fb73d0e059b206162e3c90b40c4bc08407ffcb3c08431895b691a3fea923f1f3b48db75d3e6b91fd319ffe4d486e0e14bd5c6affc838dee63d9e0b80f169b5e6c02c7321dcb20deb2b8e707b60e345a308d505bbf26a93d8f18b39d62632e9a77cbe48b3b32eb8819d6311a49820d40f5acbf0273c91c36b2269a03e72ee64df3dfb10ddefe73c64ef60870b2b77bd99dea655f5fe791b538a929a14d99f6d69685d72431ea5f0f4b27a044f2f575ab474fcc3857895934de1ca2581798eaef2c17fe5aaf2e6add97fa32997c7026f15c1b1ad0e6043ae506027a7c0242546fdc851cca39a204e56879f2cef838be8ec66e0f2292f8c862e06f810eb9b80c7a467ce6e90155206352c7f82b1173ba3b98d35bb72c259a60db20dd1a43fe6d7aef0265e6eaa5caafd9b64b448ff745a2046acbdb65cf2a5007809808a4828dc99097feedc734c236260c584")
require.NoError(t, err)
metadata, err := sniff.QUICClientHello(context.Background(), pkt)
require.NoError(t, err)
require.Equal(t, metadata.Domain, "cloudflare-quic.com")
}
func FuzzSniffQUIC(f *testing.F) { func FuzzSniffQUIC(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { f.Fuzz(func(t *testing.T, data []byte) {
sniff.QUICClientHello(context.Background(), data) sniff.QUICClientHello(context.Background(), data)

View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"io" "io"
"net" "net"
"os"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -18,11 +19,8 @@ type (
PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error) PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error)
) )
func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) (*adapter.InboundContext, error) { func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, sniffers ...StreamSniffer) (*adapter.InboundContext, error) {
if timeout == 0 { err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
timeout = C.ReadPayloadTimeout
}
err := conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -32,25 +30,23 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
return nil, err return nil, err
} }
var metadata *adapter.InboundContext var metadata *adapter.InboundContext
var errors []error
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes())) metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes()))
if metadata != nil { if err != nil {
continue
}
return metadata, nil return metadata, nil
} }
errors = append(errors, err) return nil, os.ErrInvalid
}
return nil, E.Errors(errors...)
} }
func PeekPacket(ctx context.Context, packet []byte, sniffers ...PacketSniffer) (*adapter.InboundContext, error) { func PeekPacket(ctx context.Context, packet []byte, sniffers ...PacketSniffer) (*adapter.InboundContext, error) {
var errors []error
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
metadata, err := sniffer(ctx, packet) sniffMetadata, err := sniffer(ctx, packet)
if metadata != nil { if err != nil {
return metadata, nil continue
} }
errors = append(errors, err) return sniffMetadata, nil
} }
return nil, E.Errors(errors...) return nil, os.ErrInvalid
} }

View File

@@ -1,81 +0,0 @@
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) {
if !options.Enabled {
return dialer, nil
}
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.Enabled {
return nil, nil
}
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
}

View File

@@ -1,12 +0,0 @@
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
)

View File

@@ -1,53 +0,0 @@
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
ConnectionState = tls.ConnectionState
)
type Config interface {
ServerName() string
SetServerName(serverName string)
NextProtos() []string
SetNextProtos(nextProto []string)
Config() (*STDConfig, error)
Client(conn net.Conn) Conn
Clone() Config
}
type ServerConfig interface {
Config
adapter.Service
Server(conn net.Conn) Conn
}
type Conn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() 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)
}
}

View File

@@ -1,221 +0,0 @@
//go:build with_ech
package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"net"
"net/netip"
"os"
cftls "github.com/sagernet/cloudflare-tls"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"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) ServerName() string {
return e.config.ServerName
}
func (e *ECHClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
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)}
}
func (e *ECHClientConfig) Clone() Config {
return &ECHClientConfig{
config: e.config.Clone(),
}
}
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")
}
}

View File

@@ -1,13 +0,0 @@
//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`)
}

View File

@@ -1,50 +0,0 @@
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
}

View File

@@ -1,37 +0,0 @@
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) {
if !options.Enabled {
return nil, nil
}
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
}

View File

@@ -1,171 +0,0 @@
//go:build with_utls
package tls
import (
"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) ServerName() string {
return e.config.ServerName
}
func (e *UTLSClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
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.Clone(), e.id)}
}
type utlsConnWrapper struct {
*utls.UConn
}
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 (e *UTLSClientConfig) Clone() Config {
return &UTLSClientConfig{
config: e.config.Clone(),
id: e.id,
}
}
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 "edge":
id = utls.HelloEdge_Auto
case "safari":
id = utls.HelloSafari_Auto
case "360":
id = utls.Hello360_Auto
case "qq":
id = utls.HelloQQ_Auto
case "ios":
id = utls.HelloIOS_Auto
case "android":
id = utls.HelloAndroid_11_OkHttp
case "random":
id = utls.HelloRandomized
default:
return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
}
return &UTLSClientConfig{&tlsConfig, id}, nil
}

View File

@@ -1,13 +0,0 @@
//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`)
}

View File

@@ -0,0 +1,145 @@
package trafficcontrol
import (
"io"
"net"
"sync"
"sync/atomic"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Manager[U comparable] struct {
access sync.Mutex
users map[U]*Traffic
}
type Traffic struct {
Upload uint64
Download uint64
}
func NewManager[U comparable]() *Manager[U] {
return &Manager[U]{
users: make(map[U]*Traffic),
}
}
func (m *Manager[U]) Reset() {
m.users = make(map[U]*Traffic)
}
func (m *Manager[U]) TrackConnection(user U, conn net.Conn) net.Conn {
m.access.Lock()
defer m.access.Unlock()
var traffic *Traffic
if t, loaded := m.users[user]; loaded {
traffic = t
} else {
traffic = new(Traffic)
m.users[user] = traffic
}
return &TrackConn{conn, traffic}
}
func (m *Manager[U]) TrackPacketConnection(user U, conn N.PacketConn) N.PacketConn {
m.access.Lock()
defer m.access.Unlock()
var traffic *Traffic
if t, loaded := m.users[user]; loaded {
traffic = t
} else {
traffic = new(Traffic)
m.users[user] = traffic
}
return &TrackPacketConn{conn, traffic}
}
func (m *Manager[U]) ReadTraffics() map[U]Traffic {
m.access.Lock()
defer m.access.Unlock()
trafficMap := make(map[U]Traffic)
for user, traffic := range m.users {
upload := atomic.SwapUint64(&traffic.Upload, 0)
download := atomic.SwapUint64(&traffic.Download, 0)
if upload == 0 && download == 0 {
continue
}
trafficMap[user] = Traffic{
Upload: upload,
Download: download,
}
}
return trafficMap
}
type TrackConn struct {
net.Conn
*Traffic
}
func (c *TrackConn) Read(p []byte) (n int, err error) {
n, err = c.Conn.Read(p)
if n > 0 {
atomic.AddUint64(&c.Upload, uint64(n))
}
return
}
func (c *TrackConn) Write(p []byte) (n int, err error) {
n, err = c.Conn.Write(p)
if n > 0 {
atomic.AddUint64(&c.Download, uint64(n))
}
return
}
func (c *TrackConn) WriteTo(w io.Writer) (n int64, err error) {
n, err = bufio.Copy(w, c.Conn)
if n > 0 {
atomic.AddUint64(&c.Upload, uint64(n))
}
return
}
func (c *TrackConn) ReadFrom(r io.Reader) (n int64, err error) {
n, err = bufio.Copy(c.Conn, r)
if n > 0 {
atomic.AddUint64(&c.Download, uint64(n))
}
return
}
func (c *TrackConn) Upstream() any {
return c.Conn
}
type TrackPacketConn struct {
N.PacketConn
*Traffic
}
func (c *TrackPacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
destination, err := c.PacketConn.ReadPacket(buffer)
if err == nil {
atomic.AddUint64(&c.Upload, uint64(buffer.Len()))
}
return destination, err
}
func (c *TrackPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
n := buffer.Len()
err := c.PacketConn.WritePacket(buffer, destination)
if err == nil {
atomic.AddUint64(&c.Download, uint64(n))
}
return err
}
func (c *TrackPacketConn) Upstream() any {
return c.PacketConn
}

View File

@@ -1,8 +0,0 @@
package constant
import "time"
const (
DHCPTTL = time.Hour
DHCPTimeout = time.Minute
)

View File

@@ -3,5 +3,3 @@ 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`)

View File

@@ -18,12 +18,8 @@ const (
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"
) )

5
constant/quic.go Normal file
View File

@@ -0,0 +1,5 @@
//go:build with_quic
package constant
const QUIC_AVAILABLE = true

9
constant/quic_stub.go Normal file
View File

@@ -0,0 +1,9 @@
//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`)

View File

@@ -9,5 +9,4 @@ const (
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
) )

View File

@@ -1,3 +1,6 @@
package constant package constant
var Version = "1.2-beta3" var (
Version = "1.0"
Commit = ""
)

View File

@@ -1,432 +1,4 @@
#### 1.2-beta3 #### 2022/08/24
* Update QUIC v2 version number and initial salt
* Fix shadowtls v3 implementation
#### 1.2-beta2
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add fallback support for v2ray transport
* Fix parse hysteria UDP message
* Fix socks connect response
* Disable vmess header protection if transport enabled
#### 1.2-beta1
* Add [DHCP DNS server](/configuration/dns/server) support
* Add SSH [host key validation](/configuration/outbound/ssh) support
* Add [query_type](/configuration/dns/rule) DNS rule item
* Add v2ray [user stats](/configuration/experimental#statsusers) api
* Add new clash DNS query api
* Improve vmess request
* Fix ipv6 redirect on Linux
* Fix match geoip private
#### 1.1.5
* Add Go 1.20 support
* Fix inbound default DF value
* Fix auth_user route for naive inbound
* Fix gRPC lite header
* Ignore domain case in route rules
#### 1.1.4
* Fix DNS log
* Fix write to h2 conn after closed
* Fix create UDP DNS transport from plain IPv6 address
#### 1.1.2
* Fix http proxy auth
* Fix user from stream packet conn
* Fix DNS response TTL
* Fix override packet conn
* Skip override system proxy bypass list
* Improve DNS log
#### 1.1.1
* Fix acme config
* Fix vmess packet conn
* Suppress quic-go set DF error
#### 1.1
* Fix close clash cache
Important changes since 1.0:
* Add support for use with android VPNService
* Add tun support for WireGuard outbound
* Add system tun stack
* Add comment filter for config
* Add option for allow optional proxy protocol header
* Add Clash mode and persistence support
* Add TLS ECH and uTLS support for outbound TLS options
* Add internal simple-obfs and v2ray-plugin
* Add ShadowsocksR outbound
* Add VLESS outbound and XUDP
* Skip wait for hysteria tcp handshake response
* Add v2ray mux support for all inbound
* Add XUDP support for VMess
* Improve websocket writer
* Refine tproxy write back
* Fix DNS leak caused by
Windows' ordinary multihomed DNS resolution behavior
* Add sniff_timeout listen option
* Add custom route support for tun
* Add option for custom wireguard reserved bytes
* Split bind_address into ipv4 and ipv6
* Add ShadowTLS v1 and v2 support
#### 1.1-rc1
* Fix TLS config for h2 server
* Fix crash when input bad method in shadowsocks multi-user inbound
* Fix listen UDP
* Fix check invalid packet on macOS
#### 1.1-beta18
* Enhance defense against active probe for shadowtls server **1**
**1**:
The `fallback_after` option has been removed.
#### 1.1-beta17
* Fix shadowtls server **1**
*1*:
Added [fallback_after](/configuration/inbound/shadowtls#fallback_after) option.
#### 1.0.7
* Add support for new x/h2 deadline
* Fix copy pipe
* Fix decrypt xplus packet
* Fix macOS Ventura process name match
* Fix smux keepalive
* Fix vmess request buffer
* Fix h2c transport
* Fix tor geoip
* Fix udp connect for mux client
* Fix default dns transport strategy
#### 1.1-beta16
* Improve shadowtls server
* Fix default dns transport strategy
* Update uTLS to v1.2.0
#### 1.1-beta15
* Add support for new x/h2 deadline
* Fix udp connect for mux client
* Fix dns buffer
* Fix quic dns retry
* Fix create TLS config
* Fix websocket alpn
* Fix tor geoip
#### 1.1-beta14
* Add multi-user support for hysteria inbound **1**
* Add custom tls client support for std grpc
* Fix smux keep alive
* Fix vmess request buffer
* Fix default local DNS server behavior
* Fix h2c transport
*1*:
The `auth` and `auth_str` fields have been replaced by the `users` field.
#### 1.1-beta13
* Add custom worker count option for WireGuard outbound
* Split bind_address into ipv4 and ipv6
* Move WFP manipulation to strict route
* Fix WireGuard outbound panic when close
* Fix macOS Ventura process name match
* Fix QUIC connection migration by @HyNetwork
* Fix handling QUIC client SNI by @HyNetwork
#### 1.1-beta12
* Fix uTLS config
* Update quic-go to v0.30.0
* Update cloudflare-tls to go1.18.7
#### 1.1-beta11
* Add option for custom wireguard reserved bytes
* Fix shadowtls v2
* Fix h3 dns transport
* Fix copy pipe
* Fix decrypt xplus packet
* Fix v2ray api
* Suppress no network error
* Improve local dns transport
#### 1.1-beta10
* Add [sniff_timeout](/configuration/shared/listen#sniff_timeout) listen option
* Add [custom route](/configuration/inbound/tun#inet4_route_address) support for tun **1**
* Fix interface monitor
* Fix websocket headroom
* Fix uTLS handshake
* Fix ssh outbound
* Fix sniff fragmented quic client hello
* Fix DF for hysteria
* Fix naive overflow
* Check destination before udp connect
* Update uTLS to v1.1.5
* Update tfo-go to v2.0.2
* Update fsnotify to v1.6.0
* Update grpc to v1.50.1
*1*:
The `strict_route` on windows is removed.
#### 1.0.6
* Fix ssh outbound
* Fix sniff fragmented quic client hello
* Fix naive overflow
* Check destination before udp connect
#### 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
* Add strict_route option for [Tun inbound](/configuration/inbound/tun#strict_route)
* Add packetaddr support for [VMess outbound](/configuration/outbound/vmess#packet_addr)
* Add better performing alternative gRPC implementation
* Add [docker image](https://github.com/SagerNet/sing-box/pkgs/container/sing-box)
* Fix sniff override destination
#### 1.0-beta1
* Initial release
##### 2022/08/26
* Fix ipv6 route on linux
* Fix read DNS message
##### 2022/08/25
* Let vmess use zero instead of auto if TLS enabled
* Add trojan fallback for ALPN
* Improve ip_cidr rule
* Fix format bind_address
* Fix http proxy with compressed response
* Fix route connections
##### 2022/08/24
* Fix naive padding * Fix naive padding
* Fix unix search path * Fix unix search path
@@ -435,7 +7,7 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
* Fix early close on windows and catch any * Fix early close on windows and catch any
* Initial zh-CN document translation * Initial zh-CN document translation
##### 2022/08/23 #### 2022/08/23
* Add [V2Ray Transport](/configuration/shared/v2ray-transport) support for VMess and Trojan * Add [V2Ray Transport](/configuration/shared/v2ray-transport) support for VMess and Trojan
* Allow plain http request in Naive inbound (It can now be used with nginx) * Allow plain http request in Naive inbound (It can now be used with nginx)
@@ -444,17 +16,17 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
* Parse X-Forward-For in HTTP requests * Parse X-Forward-For in HTTP requests
* Handle SIGHUP signal * Handle SIGHUP signal
##### 2022/08/22 #### 2022/08/22
* Add strategy setting for each [DNS server](/configuration/dns/server) * Add strategy setting for each [DNS server](/configuration/dns/server)
* Add bind address to outbound options * Add bind address to outbound options
##### 2022/08/21 #### 2022/08/21
* Add [Tor outbound](/configuration/outbound/tor) * Add [Tor outbound](/configuration/outbound/tor)
* Add [SSH outbound](/configuration/outbound/ssh) * Add [SSH outbound](/configuration/outbound/ssh)
##### 2022/08/20 #### 2022/08/20
* Attempt to unwrap ip-in-fqdn socksaddr * Attempt to unwrap ip-in-fqdn socksaddr
* Fix read packages in android 12 * Fix read packages in android 12
@@ -464,52 +36,52 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
* Skip bind connection with private destination to interface * Skip bind connection with private destination to interface
* Add [Trojan connection fallback](/configuration/inbound/trojan#fallback) * Add [Trojan connection fallback](/configuration/inbound/trojan#fallback)
##### 2022/08/19 #### 2022/08/19
* Add Hysteria [Inbound](/configuration/inbound/hysteria) and [Outbund](/configuration/outbound/hysteria) * Add Hysteria [Inbound](/configuration/inbound/hysteria) and [Outbund](/configuration/outbound/hysteria)
* Add [ACME TLS certificate issuer](/configuration/shared/tls) * Add [ACME TLS certificate issuer](/configuration/shared/tls)
* Allow read config from stdin (-c stdin) * Allow read config from stdin (-c stdin)
* Update gVisor to 20220815.0 * Update gVisor to 20220815.0
##### 2022/08/18 #### 2022/08/18
* Fix find process with lwip stack * Fix find process with lwip stack
* Fix crash on shadowsocks server * Fix crash on shadowsocks server
* Fix crash on darwin tun * Fix crash on darwin tun
* Fix write log to file * Fix write log to file
##### 2022/08/17 #### 2022/08/17
* Improve async dns transports * Improve async dns transports
##### 2022/08/16 #### 2022/08/16
* Add ip_version (route/dns) rule item * Add ip_version (route/dns) rule item
* Add [WireGuard](/configuration/outbound/wireguard) outbound * Add [WireGuard](/configuration/outbound/wireguard) outbound
##### 2022/08/15 #### 2022/08/15
* Add uid, android user and package rules support in [Tun](/configuration/inbound/tun) routing. * Add uid, android user and package rules support in [Tun](/configuration/inbound/tun) routing.
##### 2022/08/13 #### 2022/08/13
* Fix dns concurrent write * Fix dns concurrent write
##### 2022/08/12 #### 2022/08/12
* Performance improvements * Performance improvements
* Add UoT option for [SOCKS](/configuration/outbound/socks) outbound * Add UoT option for [SOCKS](/configuration/outbound/socks) outbound
##### 2022/08/11 #### 2022/08/11
* Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks) outbound, UoT support for all inbounds * Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks) outbound, UoT support for all inbounds
##### 2022/08/10 #### 2022/08/10
* Add full-featured [Naive](/configuration/inbound/naive) inbound * Add full-featured [Naive](/configuration/inbound/naive) inbound
* Fix default dns server option [#9] by iKirby * Fix default dns server option [#9] by iKirby
##### 2022/08/09 #### 2022/08/09
No changelog before. No changelog before.

View File

@@ -1,5 +1,3 @@
# DNS
### Structure ### Structure
```json ```json

View File

@@ -9,11 +9,6 @@
"mixed-in" "mixed-in"
], ],
"ip_version": 6, "ip_version": 6,
"query_type": [
"A",
"HTTPS",
32768
],
"network": "tcp", "network": "tcp",
"auth_user": [ "auth_user": [
"usera", "usera",
@@ -43,8 +38,7 @@
"private" "private"
], ],
"source_ip_cidr": [ "source_ip_cidr": [
"10.0.0.0/24", "10.0.0.0/24"
"192.168.0.1"
], ],
"source_port": [ "source_port": [
12345 12345
@@ -66,9 +60,6 @@
"process_name": [ "process_name": [
"curl" "curl"
], ],
"process_path": [
"/usr/bin/curl"
],
"package_name": [ "package_name": [
"com.termux" "com.termux"
], ],
@@ -78,7 +69,6 @@
"user_id": [ "user_id": [
1000 1000
], ],
"clash_mode": "direct",
"invert": false, "invert": false,
"outbound": [ "outbound": [
"direct" "direct"
@@ -109,9 +99,7 @@
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`) &&
(`source_port` || `source_port_range`) &&
`other fields` `other fields`
#### inbound #### inbound
@@ -124,10 +112,6 @@ Tags of [Inbound](/configuration/inbound).
Not limited if empty. Not limited if empty.
#### query_type
DNS query type. Values can be integers or type name strings.
#### network #### network
`tcp` or `udp`. `tcp` or `udp`.
@@ -192,14 +176,6 @@ 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.
@@ -220,10 +196,6 @@ Match user name.
Match user id. Match user id.
#### clash_mode
Match Clash mode.
#### invert #### invert
Invert match result. Invert match result.

View File

@@ -9,11 +9,6 @@
"mixed-in" "mixed-in"
], ],
"ip_version": 6, "ip_version": 6,
"query_type": [
"A",
"HTTPS",
32768
],
"network": "tcp", "network": "tcp",
"auth_user": [ "auth_user": [
"usera", "usera",
@@ -65,9 +60,6 @@
"process_name": [ "process_name": [
"curl" "curl"
], ],
"process_path": [
"/usr/bin/curl"
],
"package_name": [ "package_name": [
"com.termux" "com.termux"
], ],
@@ -77,7 +69,6 @@
"user_id": [ "user_id": [
1000 1000
], ],
"clash_mode": "direct",
"invert": false, "invert": false,
"outbound": [ "outbound": [
"direct" "direct"
@@ -108,9 +99,7 @@
默认规则使用以下匹配逻辑: 默认规则使用以下匹配逻辑:
(`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`) &&
(`source_port` || `source_port_range`) &&
`other fields` `other fields`
#### inbound #### inbound
@@ -123,10 +112,6 @@
默认不限制。 默认不限制。
#### query_type
DNS 查询类型。值可以为整数或者类型名称字符串。
#### network #### network
`tcp``udp` `tcp``udp`
@@ -191,14 +176,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配进程名称。 匹配进程名称。
#### process_path
!!! error ""
仅支持 Linux、Windows 和 macOS.
匹配进程路径。
#### package_name #### package_name
匹配 Android 应用包名。 匹配 Android 应用包名。
@@ -219,10 +196,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配用户 ID。 匹配用户 ID。
#### clash_mode
匹配 Clash 模式。
#### invert #### invert
反选匹配结果。 反选匹配结果。

View File

@@ -31,7 +31,7 @@ The tag of the dns server.
The address of the dns server. The address of the dns server.
| Protocol | Format | | Protocol | Format |
|----------|-------------------------------| |----------|-----------------------------|
| `System` | `local` | | `System` | `local` |
| `TCP` | `tcp://1.0.0.1` | | `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` | | `UDP` | `8.8.8.8` `udp://8.8.4.4` |
@@ -40,7 +40,6 @@ The address of the dns server.
| `QUIC` | `quic://dns.adguard.com` | | `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` | | `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` | | `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
!!! warning "" !!! warning ""
@@ -54,10 +53,6 @@ The address of the dns server.
the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option. the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option.
!!! warning ""
DHCP transport is not included by default, see [Installation](/#installation).
| RCode | Description | | RCode | Description |
|-------------------|-----------------------| |-------------------|-----------------------|
| `success` | `No error` | | `success` | `No error` |

View File

@@ -31,7 +31,7 @@ DNS 服务器的标签。
DNS 服务器的地址。 DNS 服务器的地址。
| 协议 | 格式 | | 协议 | 格式 |
|----------|------------------------------| |----------|-----------------------------|
| `System` | `local` | | `System` | `local` |
| `TCP` | `tcp://1.0.0.1` | | `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` | | `UDP` | `8.8.8.8` `udp://8.8.4.4` |
@@ -40,7 +40,6 @@ DNS 服务器的地址。
| `QUIC` | `quic://dns.adguard.com` | | `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` | | `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` | | `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` |
!!! warning "" !!! warning ""
@@ -48,16 +47,12 @@ DNS 服务器的地址。
!!! warning "" !!! warning ""
默认安装不包含 QUIC 和 HTTP3 传输层,请参阅 [安装](/zh/#_2)。 默认安装不包含 QUIC 和 HTTP3 传输层,请参阅 [安装](/zh/#installation)。
!!! info "" !!! info ""
RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。 RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。
!!! warning ""
默认安装不包含 DHCP 传输层,请参阅 [安装](/zh/#_2)。
| RCode | 描述 | | RCode | 描述 |
|-------------------|----------| |-------------------|----------|
| `success` | `无错误` | | `success` | `无错误` |

View File

@@ -1,5 +1,3 @@
# Experimental
### Structure ### Structure
```json ```json
@@ -8,44 +6,25 @@
"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": ""
"default_mode": "rule",
"store_selected": false,
"cache_file": "cache.db"
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
} }
} }
} }
``` ```
!!! 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. Clash API will be disabled if empty. RESTful web API listening address. Disabled if empty.
#### external_ui #### external_ui
@@ -58,51 +37,3 @@ 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
#### 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.inbounds
Inbound list to count traffic.
#### stats.outbounds
Outbound list to count traffic.
#### stats.users
User list to count traffic.

View File

@@ -8,44 +8,25 @@
"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": ""
"default_mode": "rule",
"store_selected": false,
"cache_file": "cache.db"
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
} }
} }
} }
``` ```
!!! note ""
流量统计和连接管理会降低性能。
### Clash API 字段 ### Clash API 字段
!!! error "" !!! error ""
默认安装不包含 Clash API参阅 [安装](/zh/#_2)。 默认安装不包含 Clash API参阅 [安装](/zh/#installation)。
!!! note ""
流量统计和连接管理将禁用 Linux 中的 TCP splice 并降低性能,使用风险自负。
#### external_controller #### external_controller
RESTful web API 监听地址。如果为空,则禁用 Clash API。 RESTful web API 监听地址。
#### external_ui #### external_ui
@@ -56,51 +37,3 @@ RESTful web API 监听地址。如果为空,则禁用 Clash 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,请始终设置一个密钥。
#### 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.inbounds
统计流量的入站列表。
#### stats.outbounds
统计流量的出站列表。
#### stats.users
统计流量的用户列表。

View File

@@ -3,23 +3,30 @@
### Structure ### Structure
```json ```json
{
"inbounds": [
{ {
"type": "direct", "type": "direct",
"tag": "direct-in", "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
} }
]
}
``` ```
### Listen Fields ### Direct Fields
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
#### network #### network
@@ -34,3 +41,49 @@ 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.

View File

@@ -3,23 +3,30 @@
### 结构 ### 结构
```json ```json
{
"inbounds": [
{ {
"type": "direct", "type": "direct",
"tag": "direct-in", "tag": "direct-in",
... // 监听字段 "listen": "::",
"listen_port": 5353,
"tcp_fast_open": false,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"udp_timeout": 300,
"network": "udp", "network": "udp",
"proxy_protocol": false,
"override_address": "1.0.0.1", "override_address": "1.0.0.1",
"override_port": 53 "override_port": 53
} }
]
}
``` ```
### 监听字段 ### Direct 字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### network #### network
@@ -35,3 +42,48 @@
覆盖连接目标端口。 覆盖连接目标端口。
### 监听字段
#### 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 过期时间,以秒为单位,默认为 3005 分钟)。
#### proxy_protocol
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。

View File

@@ -1,11 +1,19 @@
### Structure ### Structure
```json ```json
{
"inbounds": [
{ {
"type": "http", "type": "http",
"tag": "http-in", "tag": "http-in",
... // Listen Fields "listen": "::",
"listen_port": 2080,
"tcp_fast_open": false,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"proxy_protocol": false,
"users": [ "users": [
{ {
@@ -16,13 +24,11 @@
"tls": {}, "tls": {},
"set_system_proxy": false "set_system_proxy": false
} }
]
}
``` ```
### Listen Fields ### HTTP Fields
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
#### tls #### tls
@@ -41,3 +47,45 @@ 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.

View File

@@ -1,11 +1,19 @@
### 结构 ### 结构
```json ```json
{
"inbounds": [
{ {
"type": "http", "type": "http",
"tag": "http-in", "tag": "http-in",
... // 监听字段 "listen": "::",
"listen_port": 2080,
"tcp_fast_open": false,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"proxy_protocol": false,
"users": [ "users": [
{ {
@@ -16,13 +24,11 @@
"tls": {}, "tls": {},
"set_system_proxy": false "set_system_proxy": false
} }
]
}
``` ```
### 监听字段 ### HTTP 字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### tls #### tls
@@ -32,7 +38,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
HTTP 用户 HTTP 用户
如果为空则不需要验证。 默认不需要验证。
#### set_system_proxy #### set_system_proxy
@@ -41,3 +47,45 @@ 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)。

View File

@@ -1,43 +1,40 @@
### Structure ### Structure
```json ```json
{
"inbounds": [
{ {
"type": "hysteria", "type": "hysteria",
"tag": "hysteria-in", "tag": "hysteria-in",
... // Listen Fields "listen": "::",
"listen_port": 443,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"up": "100 Mbps", "up": "100 Mbps",
"up_mbps": 100, "up_mbps": 100,
"down": "100 Mbps", "down": "100 Mbps",
"down_mbps": 100, "down_mbps": 100,
"obfs": "fuck me till the daylight", "obfs": "fuck me till the daylight",
"users": [
{
"name": "sekai",
"auth": "", "auth": "",
"auth_str": "password" "auth_str": "password",
}
],
"recv_window_conn": 0, "recv_window_conn": 0,
"recv_window_client": 0, "recv_window_client": 0,
"max_conn_client": 0, "max_conn_client": 0,
"disable_mtu_discovery": false, "disable_mtu_discovery": false,
"tls": {} "tls": {}
} }
]
}
``` ```
!!! warning "" !!! warning ""
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).
### Listen Fields ### Hysteria Fields
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
#### up, down #### up, down
@@ -68,19 +65,11 @@ Supported units (case sensitive, b = bits, B = bytes, 8b=1B):
Obfuscated password. Obfuscated password.
#### users #### auth
Hysteria users
#### users.auth
==Required if `auth_str` is empty==
Authentication password, in base64. Authentication password, in base64.
#### users.auth_str #### auth_str
==Required if `auth` is empty==
Authentication password. Authentication password.
@@ -113,3 +102,37 @@ 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.

View File

@@ -1,43 +1,40 @@
### 结构 ### 结构
```json ```json
{
"inbounds": [
{ {
"type": "hysteria", "type": "hysteria",
"tag": "hysteria-in", "tag": "hysteria-in",
... // 监听字段 "listen": "::",
"listen_port": 443,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"up": "100 Mbps", "up": "100 Mbps",
"up_mbps": 100, "up_mbps": 100,
"down": "100 Mbps", "down": "100 Mbps",
"down_mbps": 100, "down_mbps": 100,
"obfs": "fuck me till the daylight", "obfs": "fuck me till the daylight",
"users": [
{
"name": "sekai",
"auth": "", "auth": "",
"auth_str": "password" "auth_str": "password",
}
],
"recv_window_conn": 0, "recv_window_conn": 0,
"recv_window_client": 0, "recv_window_client": 0,
"max_conn_client": 0, "max_conn_client": 0,
"disable_mtu_discovery": false, "disable_mtu_discovery": false,
"tls": {} "tls": {}
} }
]
}
``` ```
!!! warning "" !!! warning ""
默认安装不包含被 Hysteria 依赖的 QUIC参阅 [安装](/zh/#_2)。 默认安装不包含被 Hysteria 依赖的 QUIC参阅 [安装](/zh/#installation)。
### 监听字段 ### Hysteria 字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### up, down #### up, down
@@ -68,19 +65,11 @@
混淆密码。 混淆密码。
#### users #### auth
Hysteria 用户
#### users.auth
==与 auth_str 必填一个==
base64 编码的认证密码。 base64 编码的认证密码。
#### users.auth_str #### auth_str
==与 auth 必填一个==
认证密码。 认证密码。
@@ -113,3 +102,37 @@ 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` 生效,它的值将作为后备。

View File

@@ -1,5 +1,3 @@
# Inbound
### Structure ### Structure
```json ```json
@@ -15,20 +13,20 @@
### Fields ### Fields
| Type | Format | Injectable | | Type | Format |
|---------------|------------------------------|------------| |---------------|------------------------------|
| `direct` | [Direct](./direct) | X | | `direct` | [Direct](./direct) |
| `mixed` | [Mixed](./mixed) | TCP | | `mixed` | [Mixed](./mixed) |
| `socks` | [SOCKS](./socks) | TCP | | `socks` | [SOCKS](./socks) |
| `http` | [HTTP](./http) | TCP | | `http` | [HTTP](./http) |
| `shadowsocks` | [Shadowsocks](./shadowsocks) | TCP | | `shadowsocks` | [Shadowsocks](./shadowsocks) |
| `vmess` | [VMess](./vmess) | TCP | | `vmess` | [VMess](./vmess) |
| `trojan` | [Trojan](./trojan) | TCP | | `trojan` | [Trojan](./trojan) |
| `naive` | [Naive](./naive) | X | | `naive` | [Naive](./naive) |
| `hysteria` | [Hysteria](./hysteria) | X | | `hysteria` | [Hysteria](./hysteria) |
| `tun` | [Tun](./tun) | X | | `tun` | [Tun](./tun) |
| `redirect` | [Redirect](./redirect) | X | | `redirect` | [Redirect](./redirect) |
| `tproxy` | [TProxy](./tproxy) | X | | `tproxy` | [TProxy](./tproxy) |
#### tag #### tag

View File

@@ -15,20 +15,20 @@
### 字段 ### 字段
| 类型 | 格式 | 注入支持 | | 类型 | 格式 |
|---------------|------------------------------|------| |---------------|------------------------------|
| `direct` | [Direct](./direct) | X | | `direct` | [Direct](./direct) |
| `mixed` | [Mixed](./mixed) | TCP | | `mixed` | [Mixed](./mixed) |
| `socks` | [SOCKS](./socks) | TCP | | `socks` | [SOCKS](./socks) |
| `http` | [HTTP](./http) | TCP | | `http` | [HTTP](./http) |
| `shadowsocks` | [Shadowsocks](./shadowsocks) | TCP | | `shadowsocks` | [Shadowsocks](./shadowsocks) |
| `vmess` | [VMess](./vmess) | TCP | | `vmess` | [VMess](./vmess) |
| `trojan` | [Trojan](./trojan) | TCP | | `trojan` | [Trojan](./trojan) |
| `naive` | [Naive](./naive) | X | | `naive` | [Naive](./naive) |
| `hysteria` | [Hysteria](./hysteria) | X | | `hysteria` | [Hysteria](./hysteria) |
| `tun` | [Tun](./tun) | X | | `tun` | [Tun](./tun) |
| `redirect` | [Redirect](./redirect) | X | | `redirect` | [Redirect](./redirect) |
| `tproxy` | [TProxy](./tproxy) | X | | `tproxy` | [TProxy](./tproxy) |
#### tag #### tag

View File

@@ -3,11 +3,19 @@
### Structure ### Structure
```json ```json
{
"inbounds": [
{ {
"type": "mixed", "type": "mixed",
"tag": "mixed-in", "tag": "mixed-in",
... // Listen Fields "listen": "::",
"listen_port": 2080,
"tcp_fast_open": false,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"proxy_protocol": false,
"users": [ "users": [
{ {
@@ -17,13 +25,11 @@
], ],
"set_system_proxy": false "set_system_proxy": false
} }
]
}
``` ```
### Listen Fields ### Mixed Fields
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
#### users #### users
@@ -38,3 +44,49 @@ 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.
#### set_system_proxy
!!! error ""
Only supported on Linux, Android, Windows, and macOS.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -3,11 +3,19 @@
### 结构 ### 结构
```json ```json
{
"inbounds": [
{ {
"type": "mixed", "type": "mixed",
"tag": "mixed-in", "tag": "mixed-in",
... // 监听字段 "listen": "::",
"listen_port": 2080,
"tcp_fast_open": false,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"proxy_protocol": false,
"users": [ "users": [
{ {
@@ -17,19 +25,17 @@
], ],
"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
@@ -38,3 +44,45 @@ 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)。

View File

@@ -1,13 +1,21 @@
### Structure ### Structure
```json ```json
{
"inbounds": [
{ {
"type": "naive", "type": "naive",
"tag": "naive-in", "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", "network": "udp",
... // Listen Fields
"users": [ "users": [
{ {
"username": "sekai", "username": "sekai",
@@ -16,17 +24,15 @@
], ],
"tls": {} "tls": {}
} }
]
}
``` ```
!!! warning "" !!! warning ""
HTTP3 transport is not included by default, see [Installation](/#installation). HTTP3 transport is not included by default, see [Installation](/#installation).
### Listen Fields ### Naive Fields
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
#### network #### network
@@ -43,3 +49,45 @@ 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.

View File

@@ -1,13 +1,21 @@
### 结构 ### 结构
```json ```json
{
"inbounds": [
{ {
"type": "naive", "type": "naive",
"tag": "naive-in", "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", "network": "udp",
... // 监听字段
"users": [ "users": [
{ {
"username": "sekai", "username": "sekai",
@@ -16,17 +24,15 @@
], ],
"tls": {} "tls": {}
} }
]
}
``` ```
!!! warning "" !!! warning ""
默认安装不包含 HTTP3 传输层, 参阅 [安装](/zh/#_2)。 默认安装不包含 HTTP3 传输层, 参阅 [安装](/zh/#installation)。
### 监听字段 ### Naive 字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### network #### network
@@ -43,3 +49,45 @@ 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)。

View File

@@ -1,18 +1,52 @@
!!! error ""
Only supported on Linux and macOS.
### Structure ### Structure
```json ```json
{
"inbounds": [
{ {
"type": "redirect", "type": "redirect",
"tag": "redirect-in", "tag": "redirect-in",
... // Listen Fields "listen": "::",
"listen_port": 5353,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6"
}
]
} }
``` ```
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. #### 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.

View File

@@ -1,17 +1,52 @@
!!! error ""
仅支持 Linux 和 macOS。
### 结构 ### 结构
```json ```json
{
"inbounds": [
{ {
"type": "redirect", "type": "redirect",
"tag": "redirect-in", "tag": "redirect-in",
... // 监听字段 "listen": "::",
"listen_port": 5353,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6"
}
]
} }
``` ```
### 监听字段 ### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。 #### 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` 生效,它的值将作为后备。

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