mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18162df6de | ||
|
|
0c48aaef08 | ||
|
|
871f4aebbd | ||
|
|
12ad50d427 | ||
|
|
14329ad62d | ||
|
|
eb15e885b9 | ||
|
|
a55726694a | ||
|
|
b0235c6257 | ||
|
|
9a01d52a2c | ||
|
|
2c0524578f | ||
|
|
b10a8d55f3 | ||
|
|
9a8de0f378 | ||
|
|
e5018383fe | ||
|
|
1e4f68b024 | ||
|
|
8c00885599 | ||
|
|
7cf5169c98 | ||
|
|
ce396466bd | ||
|
|
1635c98783 | ||
|
|
cbcaa0f590 | ||
|
|
763b93c021 | ||
|
|
00da3e0765 | ||
|
|
46d7a78158 | ||
|
|
de3f70195e | ||
|
|
3105b8c920 | ||
|
|
4c67ab1a54 | ||
|
|
84783c5359 | ||
|
|
22b16f82bd | ||
|
|
d2add33723 | ||
|
|
ab0daf31c1 | ||
|
|
3d94b948dd | ||
|
|
1659ae5d79 | ||
|
|
7279855b08 | ||
|
|
925fbca363 | ||
|
|
a5163e3e3c | ||
|
|
62859e0c6b | ||
|
|
a37cab48d2 | ||
|
|
c586c8f361 | ||
|
|
e68fa3e12d | ||
|
|
7f5b9e0e3b |
28
.github/renovate.json
vendored
28
.github/renovate.json
vendored
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
39
.github/workflows/debug.yml
vendored
39
.github/workflows/debug.yml
vendored
@@ -3,7 +3,8 @@ name: Debug build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main-next
|
- main
|
||||||
|
- dev
|
||||||
- dev-next
|
- dev-next
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
@@ -11,7 +12,8 @@ on:
|
|||||||
- '!.github/workflows/debug.yml'
|
- '!.github/workflows/debug.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main-next
|
- main
|
||||||
|
- dev
|
||||||
- dev-next
|
- dev-next
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -19,20 +21,24 @@ jobs:
|
|||||||
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@v4
|
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
|
||||||
@@ -54,21 +60,22 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.10
|
go-version: 1.18.7
|
||||||
- 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: go118-${{ hashFiles('**/go.sum') }}
|
key: go118-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Run Test
|
- name: Run Test
|
||||||
run: make
|
run: |
|
||||||
|
go test -v ./...
|
||||||
cross:
|
cross:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -185,19 +192,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@v4
|
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 +213,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*
|
||||||
|
|||||||
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@@ -1,5 +1,8 @@
|
|||||||
name: Build Docker Images
|
name: Build Docker Images
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -9,20 +12,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v1
|
||||||
- name: Setup QEMU for Docker Buildx
|
- name: Setup QEMU for Docker Buildx
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Docker metadata
|
- name: Docker metadata
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/sagernet/sing-box
|
images: ghcr.io/sagernet/sing-box
|
||||||
- name: Get tag to build
|
- name: Get tag to build
|
||||||
@@ -35,7 +38,7 @@ jobs:
|
|||||||
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
- name: Build and release Docker images
|
- name: Build and release Docker images
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||||
target: dist
|
target: dist
|
||||||
|
|||||||
23
.github/workflows/lint.yml
vendored
23
.github/workflows/lint.yml
vendored
@@ -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@v4
|
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:
|
||||||
|
|||||||
10
.github/workflows/mkdocs.yml
vendored
10
.github/workflows/mkdocs.yml
vendored
@@ -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
|
|
||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -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
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -5,11 +5,4 @@
|
|||||||
/site/
|
/site/
|
||||||
/bin/
|
/bin/
|
||||||
/dist/
|
/dist/
|
||||||
/sing-box
|
/sing-box
|
||||||
/sing-box.exe
|
|
||||||
/build/
|
|
||||||
/*.jar
|
|
||||||
/*.aar
|
|
||||||
/*.xcframework/
|
|
||||||
.DS_Store
|
|
||||||
/config.d/
|
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -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,17 +9,17 @@ builds:
|
|||||||
gcflags:
|
gcflags:
|
||||||
- all=-trimpath={{.Env.GOPATH}}
|
- all=-trimpath={{.Env.GOPATH}}
|
||||||
ldflags:
|
ldflags:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -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_reality_server
|
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
|
- android_arm64
|
||||||
|
- android_amd64
|
||||||
|
- android_amd64_v3
|
||||||
- linux_amd64_v1
|
- linux_amd64_v1
|
||||||
- linux_amd64_v3
|
- linux_amd64_v3
|
||||||
- linux_arm64
|
- linux_arm64
|
||||||
@@ -34,54 +33,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:
|
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -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:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.20-alpine AS builder
|
FROM golang:1.19-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
@@ -8,13 +8,13 @@ ENV CGO_ENABLED=0
|
|||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& go build -v -trimpath -tags 'no_gvisor,with_quic,with_wireguard,with_acme' \
|
||||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
|
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X github.com/sagernet/sing-box/constant.Commit=${COMMIT} -w -s -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM alpine AS dist
|
FROM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk upgrade \
|
&& apk upgrade \
|
||||||
&& apk add bash tzdata ca-certificates \
|
&& apk add bash tzdata ca-certificates \
|
||||||
|
|||||||
5
LICENSE
5
LICENSE
@@ -11,7 +11,4 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
|
||||||
with this application without prior consent.
|
|
||||||
46
Makefile
46
Makefile
@@ -1,15 +1,10 @@
|
|||||||
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_reality_server,with_clash_api
|
TAGS ?= with_quic,with_wireguard,with_clash_api
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
PARAMS = -v -trimpath -tags '$(TAGS)' -ldflags \
|
||||||
|
'-X "github.com/sagernet/sing-box/constant.Commit=$(COMMIT)" \
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
-w -s -buildid='
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
|
||||||
|
|
||||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
|
||||||
|
|
||||||
.PHONY: test release
|
.PHONY: test release
|
||||||
|
|
||||||
@@ -17,16 +12,16 @@ build:
|
|||||||
go build $(PARAMS) $(MAIN)
|
go build $(PARAMS) $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(PARAMS) $(MAIN)
|
go install $(PARAMS) $(MAIN)
|
||||||
|
|
||||||
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 ./...
|
||||||
@@ -48,14 +43,14 @@ 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:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
|
goreleaser release --rm-dist --skip-publish
|
||||||
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 3 $(shell git describe --tags) dist/release
|
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
|
||||||
@@ -66,27 +61,14 @@ release_install:
|
|||||||
go install -v github.com/tcnksm/ghr@latest
|
go install -v github.com/tcnksm/ghr@latest
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v ./... && \
|
@go test -v . && \
|
||||||
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" .
|
|
||||||
|
|
||||||
lib:
|
|
||||||
go run ./cmd/internal/build_libbox
|
|
||||||
|
|
||||||
lib_install:
|
|
||||||
go get -v -d
|
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
|
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
|
|
||||||
|
|
||||||
clean:
|
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:
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
The universal proxy platform.
|
The universal proxy platform.
|
||||||
|
|
||||||
[](https://repology.org/project/sing-box/versions)
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
https://sing-box.sagernet.org
|
https://sing-box.sagernet.org
|
||||||
@@ -25,7 +23,4 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
|
||||||
with this application without prior consent.
|
|
||||||
```
|
```
|
||||||
@@ -4,30 +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
|
||||||
PreStarter
|
TrafficController
|
||||||
Mode() string
|
|
||||||
StoreSelected() bool
|
|
||||||
CacheFile() ClashCacheFile
|
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClashCacheFile interface {
|
|
||||||
LoadSelected(group string) string
|
|
||||||
StoreSelected(group string, selected string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
Leave()
|
Leave()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TrafficController interface {
|
||||||
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
|
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
||||||
|
}
|
||||||
|
|
||||||
type OutboundGroup interface {
|
type OutboundGroup interface {
|
||||||
Now() string
|
Now() string
|
||||||
All() []string
|
All() []string
|
||||||
@@ -39,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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"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"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
@@ -38,18 +38,16 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
InboundDetour string
|
InboundDetour string
|
||||||
LastInbound string
|
LastInbound string
|
||||||
OriginDestination M.Socksaddr
|
OriginDestination M.Socksaddr
|
||||||
InboundOptions option.InboundOptions
|
DomainStrategy dns.DomainStrategy
|
||||||
DestinationAddresses []netip.Addr
|
SniffEnabled bool
|
||||||
SourceGeoIPCode string
|
SniffOverrideDestination bool
|
||||||
GeoIPCode string
|
DestinationAddresses []netip.Addr
|
||||||
ProcessInfo *process.Info
|
SourceGeoIPCode string
|
||||||
|
GeoIPCode string
|
||||||
// dns cache
|
ProcessInfo *process.Info
|
||||||
|
|
||||||
QueryType uint16
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type inboundContextKey struct{}
|
type inboundContextKey struct{}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
type PreStarter interface {
|
|
||||||
PreStart() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func PreStart(starter any) error {
|
|
||||||
if preService, ok := starter.(PreStarter); ok {
|
|
||||||
err := preService.PreStart()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -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,41 +27,19 @@ 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
|
||||||
AutoDetectInterfaceFunc() control.Func
|
|
||||||
DefaultMark() int
|
DefaultMark() int
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
SetTrafficController(controller TrafficController)
|
||||||
TimeService
|
|
||||||
|
|
||||||
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 {
|
||||||
@@ -77,7 +55,3 @@ type DNSRule interface {
|
|||||||
Rule
|
Rule
|
||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
|
||||||
InterfaceUpdated() error
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type TimeService interface {
|
|
||||||
Service
|
|
||||||
TimeFunc() func() time.Time
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
192
box.go
192
box.go
@@ -9,9 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/inbound"
|
"github.com/sagernet/sing-box/inbound"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -25,57 +23,42 @@ import (
|
|||||||
var _ adapter.Service = (*Box)(nil)
|
var _ adapter.Service = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
inbounds []adapter.Inbound
|
inbounds []adapter.Inbound
|
||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
logFile *os.File
|
logFile *os.File
|
||||||
preServices map[string]adapter.Service
|
clashServer adapter.ClashServer
|
||||||
postServices map[string]adapter.Service
|
done chan struct{}
|
||||||
done chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
|
func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
|
logOptions := common.PtrValueOrDefault(options.Log)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
|
||||||
|
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
||||||
if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
|
|
||||||
needClashAPI = true
|
needClashAPI = true
|
||||||
}
|
}
|
||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
|
||||||
needV2RayAPI = true
|
|
||||||
}
|
|
||||||
|
|
||||||
logOptions := common.PtrValueOrDefault(options.Log)
|
|
||||||
|
|
||||||
var logFactory log.Factory
|
var logFactory log.Factory
|
||||||
var observableLogFactory log.ObservableFactory
|
var observableLogFactory log.ObservableFactory
|
||||||
var logFile *os.File
|
var logFile *os.File
|
||||||
var logWriter io.Writer
|
|
||||||
if logOptions.Disabled {
|
if logOptions.Disabled {
|
||||||
observableLogFactory = log.NewNOPFactory()
|
observableLogFactory = log.NewNOPFactory()
|
||||||
logFactory = observableLogFactory
|
logFactory = observableLogFactory
|
||||||
} else {
|
} else {
|
||||||
|
var logWriter io.Writer
|
||||||
switch logOptions.Output {
|
switch logOptions.Output {
|
||||||
case "":
|
case "", "stderr":
|
||||||
if platformInterface != nil {
|
|
||||||
logWriter = io.Discard
|
|
||||||
} else {
|
|
||||||
logWriter = os.Stdout
|
|
||||||
}
|
|
||||||
case "stderr":
|
|
||||||
logWriter = os.Stderr
|
logWriter = os.Stderr
|
||||||
case "stdout":
|
case "stdout":
|
||||||
logWriter = os.Stdout
|
logWriter = os.Stdout
|
||||||
default:
|
default:
|
||||||
var err error
|
var err error
|
||||||
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
logFile, err = os.OpenFile(logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -89,10 +72,10 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
|||||||
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
|
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
|
||||||
logFactory = observableLogFactory
|
logFactory = observableLogFactory
|
||||||
} else {
|
} else {
|
||||||
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
|
logFactory = log.NewFactory(logFormatter, logWriter)
|
||||||
}
|
}
|
||||||
if logOptions.Level != "" {
|
if logOptions.Level != "" {
|
||||||
logLevel, err := log.ParseLevel(logOptions.Level)
|
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||||
@@ -107,12 +90,11 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
|||||||
|
|
||||||
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),
|
||||||
common.PtrValueOrDefault(options.NTP),
|
|
||||||
options.Inbounds,
|
options.Inbounds,
|
||||||
platformInterface,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse route options")
|
return nil, E.Cause(err, "parse route options")
|
||||||
@@ -132,7 +114,6 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
|||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
inboundOptions,
|
inboundOptions,
|
||||||
platformInterface,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
@@ -166,57 +147,28 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
preServices := make(map[string]adapter.Service)
|
|
||||||
postServices := make(map[string]adapter.Service)
|
var clashServer adapter.ClashServer
|
||||||
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)
|
||||||
preServices["clash api"] = 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)
|
|
||||||
preServices["v2ray api"] = v2rayServer
|
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
router: router,
|
router: router,
|
||||||
inbounds: inbounds,
|
inbounds: inbounds,
|
||||||
outbounds: outbounds,
|
outbounds: outbounds,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.NewLogger(""),
|
||||||
logFile: logFile,
|
logFile: logFile,
|
||||||
preServices: preServices,
|
clashServer: clashServer,
|
||||||
postServices: postServices,
|
done: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) PreStart() error {
|
|
||||||
err := s.preStart()
|
|
||||||
if err != nil {
|
|
||||||
// TODO: remove catch error
|
|
||||||
defer func() {
|
|
||||||
v := recover()
|
|
||||||
if v != nil {
|
|
||||||
log.Error(E.Cause(err, "origin error"))
|
|
||||||
debug.PrintStack()
|
|
||||||
panic("panic on early close: " + fmt.Sprint(v))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
s.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) Start() error {
|
func (s *Box) Start() error {
|
||||||
err := s.start()
|
err := s.start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -230,19 +182,11 @@ func (s *Box) Start() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
s.Close()
|
s.Close()
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
return err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) preStart() error {
|
func (s *Box) start() error {
|
||||||
for serviceName, service := range s.preServices {
|
|
||||||
err := adapter.PreStart(service)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "pre-start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, out := range s.outbounds {
|
for i, out := range s.outbounds {
|
||||||
if starter, isStarter := out.(common.Starter); isStarter {
|
if starter, isStarter := out.(common.Starter); isStarter {
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
@@ -257,20 +201,10 @@ func (s *Box) preStart() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.router.Start()
|
err := s.router.Start()
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) start() error {
|
|
||||||
err := s.preStart()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices {
|
|
||||||
err = service.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, in := range s.inbounds {
|
for i, in := range s.inbounds {
|
||||||
err = in.Start()
|
err = in.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -283,12 +217,13 @@ func (s *Box) start() error {
|
|||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.postServices {
|
if s.clashServer != nil {
|
||||||
err = service.Start()
|
err = s.clashServer.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", serviceName)
|
return E.Cause(err, "start clash api server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,45 +234,16 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
var errors error
|
for _, in := range s.inbounds {
|
||||||
for serviceName, service := range s.postServices {
|
in.Close()
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", serviceName)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
for i, in := range s.inbounds {
|
for _, out := range s.outbounds {
|
||||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
common.Close(out)
|
||||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
return common.Close(
|
||||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
s.router,
|
||||||
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
s.logFactory,
|
||||||
})
|
s.clashServer,
|
||||||
}
|
common.PtrOrNil(s.logFile),
|
||||||
if err := common.Close(s.router); err != nil {
|
)
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close router")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for serviceName, service := range s.preServices {
|
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", serviceName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err := common.Close(s.logFactory); err != nil {
|
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close log factory")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if s.logFile != nil {
|
|
||||||
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close log file")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) Router() adapter.Router {
|
|
||||||
return s.router
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
build_shared.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
_ "github.com/sagernet/gomobile/event/key"
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
debugEnabled bool
|
|
||||||
target string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
build_shared.FindMobile()
|
|
||||||
|
|
||||||
switch target {
|
|
||||||
case "android":
|
|
||||||
buildAndroid()
|
|
||||||
case "ios":
|
|
||||||
buildiOS()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
sharedFlags []string
|
|
||||||
debugFlags []string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
sharedFlags = append(sharedFlags, "-trimpath")
|
|
||||||
sharedFlags = append(sharedFlags, "-ldflags")
|
|
||||||
|
|
||||||
currentTag, err := build_shared.ReadTag()
|
|
||||||
if err != nil {
|
|
||||||
currentTag = "unknown"
|
|
||||||
}
|
|
||||||
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
|
||||||
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildAndroid() {
|
|
||||||
build_shared.FindSDK()
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"bind",
|
|
||||||
"-v",
|
|
||||||
"-androidapi", "21",
|
|
||||||
"-javapkg=io.nekohasekai",
|
|
||||||
"-libname=box",
|
|
||||||
}
|
|
||||||
if !debugEnabled {
|
|
||||||
args = append(args, sharedFlags...)
|
|
||||||
} else {
|
|
||||||
args = append(args, debugFlags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, "-tags")
|
|
||||||
if !debugEnabled {
|
|
||||||
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api")
|
|
||||||
} else {
|
|
||||||
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
|
|
||||||
}
|
|
||||||
args = append(args, "./experimental/libbox")
|
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
|
||||||
command.Stdout = os.Stdout
|
|
||||||
command.Stderr = os.Stderr
|
|
||||||
err := command.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = "libbox.aar"
|
|
||||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
|
||||||
if rw.FileExists(copyPath) {
|
|
||||||
copyPath, _ = filepath.Abs(copyPath)
|
|
||||||
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Info("copied to ", copyPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildiOS() {
|
|
||||||
args := []string{
|
|
||||||
"bind",
|
|
||||||
"-v",
|
|
||||||
"-target", "ios,iossimulator,macos",
|
|
||||||
"-libname=box",
|
|
||||||
}
|
|
||||||
if !debugEnabled {
|
|
||||||
args = append(args, sharedFlags...)
|
|
||||||
} else {
|
|
||||||
args = append(args, debugFlags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, "-tags")
|
|
||||||
if !debugEnabled {
|
|
||||||
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack")
|
|
||||||
} else {
|
|
||||||
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack,debug")
|
|
||||||
}
|
|
||||||
args = append(args, "./experimental/libbox")
|
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
|
||||||
command.Stdout = os.Stdout
|
|
||||||
command.Stderr = os.Stderr
|
|
||||||
err := command.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
copyPath := filepath.Join("..", "sing-box-for-ios")
|
|
||||||
if rw.FileExists(copyPath) {
|
|
||||||
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
|
||||||
targetDir, _ = filepath.Abs(targetDir)
|
|
||||||
os.RemoveAll(targetDir)
|
|
||||||
os.Rename("Libbox.xcframework", targetDir)
|
|
||||||
log.Info("copied to ", targetDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package build_shared
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/build"
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
var GoBinPath string
|
|
||||||
|
|
||||||
func FindMobile() {
|
|
||||||
goBin := filepath.Join(build.Default.GOPATH, "bin")
|
|
||||||
if !rw.FileExists(goBin + "/" + "gobind") {
|
|
||||||
log.Fatal("missing gomobile installation")
|
|
||||||
}
|
|
||||||
GoBinPath = goBin
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package build_shared
|
|
||||||
|
|
||||||
import "github.com/sagernet/sing/common/shell"
|
|
||||||
|
|
||||||
func ReadTag() (string, error) {
|
|
||||||
currentTag, err := shell.Exec("git", "describe", "--tags").ReadOutput()
|
|
||||||
if err != nil {
|
|
||||||
return currentTag, err
|
|
||||||
}
|
|
||||||
currentTagRev, _ := shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()
|
|
||||||
if currentTagRev == currentTag {
|
|
||||||
return currentTag[1:], nil
|
|
||||||
}
|
|
||||||
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
|
|
||||||
return currentTagRev[1:] + "-" + shortCommit, nil
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
currentTag, err := build_shared.ReadTag()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
_, err = os.Stdout.WriteString("unknown\n")
|
|
||||||
} else {
|
|
||||||
_, err = os.Stdout.WriteString(currentTag + "\n")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,15 +26,12 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func check() error {
|
func check() error {
|
||||||
options, err := readConfigAndMerge()
|
options, err := readConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
instance, err := box.New(ctx, options, nil)
|
_, err = box.New(ctx, options)
|
||||||
if err == nil {
|
|
||||||
instance.Close()
|
|
||||||
}
|
|
||||||
cancel()
|
cancel()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,44 +33,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func format() error {
|
func format() error {
|
||||||
optionsList, err := readConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, optionsEntry := range optionsList {
|
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
encoder := json.NewEncoder(buffer)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
err = encoder.Encode(optionsEntry.options)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "encode config")
|
|
||||||
}
|
|
||||||
outputPath, _ := filepath.Abs(optionsEntry.path)
|
|
||||||
if !commandFormatFlagWrite {
|
|
||||||
if len(optionsList) > 1 {
|
|
||||||
os.Stdout.WriteString(outputPath + "\n")
|
|
||||||
}
|
|
||||||
os.Stdout.WriteString(buffer.String() + "\n")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if bytes.Equal(optionsEntry.content, buffer.Bytes()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
output, err := os.Create(optionsEntry.path)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output")
|
|
||||||
}
|
|
||||||
_, err = output.Write(buffer.Bytes())
|
|
||||||
output.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "write output")
|
|
||||||
}
|
|
||||||
os.Stderr.WriteString(outputPath + "\n")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatOne(configPath string) error {
|
|
||||||
configContent, err := os.ReadFile(configPath)
|
configContent, err := os.ReadFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read config")
|
return E.Cause(err, "read config")
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandGenerate = &cobra.Command{
|
|
||||||
Use: "generate",
|
|
||||||
Short: "Generate things",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandGenerate.AddCommand(commandGenerateUUID)
|
|
||||||
commandGenerate.AddCommand(commandGenerateRandom)
|
|
||||||
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
|
|
||||||
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
|
|
||||||
mainCommand.AddCommand(commandGenerate)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
outputBase64 bool
|
|
||||||
outputHex bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandGenerateRandom = &cobra.Command{
|
|
||||||
Use: "rand <length>",
|
|
||||||
Short: "Generate random bytes",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := generateRandom(args)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandGenerateRandom.Flags().BoolVar(&outputBase64, "base64", false, "Generate base64 string")
|
|
||||||
commandGenerateRandom.Flags().BoolVar(&outputHex, "hex", false, "Generate hex string")
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateRandom(args []string) error {
|
|
||||||
length, err := strconv.Atoi(args[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
randomBytes := make([]byte, length)
|
|
||||||
_, err = rand.Read(randomBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if outputBase64 {
|
|
||||||
_, err = os.Stdout.WriteString(base64.StdEncoding.EncodeToString(randomBytes) + "\n")
|
|
||||||
} else if outputHex {
|
|
||||||
_, err = os.Stdout.WriteString(hex.EncodeToString(randomBytes) + "\n")
|
|
||||||
} else {
|
|
||||||
_, err = os.Stdout.Write(randomBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandGenerateUUID = &cobra.Command{
|
|
||||||
Use: "uuid",
|
|
||||||
Short: "Generate UUID string",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := generateUUID()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateUUID() error {
|
|
||||||
newUUID, err := uuid.NewV4()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandGenerateWireGuardKeyPair = &cobra.Command{
|
|
||||||
Use: "wg-keypair",
|
|
||||||
Short: "Generate WireGuard key pair",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := generateWireGuardKey()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateWireGuardKey() error {
|
|
||||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
|
|
||||||
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandGenerateRealityKeyPair = &cobra.Command{
|
|
||||||
Use: "reality-keypair",
|
|
||||||
Short: "Generate reality key pair",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := generateRealityKey()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateRealityKey() error {
|
|
||||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
publicKey := privateKey.PublicKey()
|
|
||||||
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
|
|
||||||
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -5,14 +5,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
runtimeDebug "runtime/debug"
|
runtimeDebug "runtime/debug"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/common/badjsonmerge"
|
|
||||||
"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"
|
||||||
@@ -35,88 +31,29 @@ func init() {
|
|||||||
mainCommand.AddCommand(commandRun)
|
mainCommand.AddCommand(commandRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionsEntry struct {
|
func readConfig() (option.Options, error) {
|
||||||
content []byte
|
|
||||||
path string
|
|
||||||
options option.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfigAt(path string) (*OptionsEntry, error) {
|
|
||||||
var (
|
var (
|
||||||
configContent []byte
|
configContent []byte
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if path == "stdin" {
|
if configPath == "stdin" {
|
||||||
configContent, err = io.ReadAll(os.Stdin)
|
configContent, err = io.ReadAll(os.Stdin)
|
||||||
} else {
|
} else {
|
||||||
configContent, err = os.ReadFile(path)
|
configContent, err = os.ReadFile(configPath)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read config at ", path)
|
return option.Options{}, E.Cause(err, "read config")
|
||||||
}
|
}
|
||||||
var options option.Options
|
var options option.Options
|
||||||
err = options.UnmarshalJSON(configContent)
|
err = options.UnmarshalJSON(configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode config at ", path)
|
return option.Options{}, E.Cause(err, "decode config")
|
||||||
}
|
}
|
||||||
return &OptionsEntry{
|
return options, nil
|
||||||
content: configContent,
|
|
||||||
path: path,
|
|
||||||
options: options,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfig() ([]*OptionsEntry, error) {
|
|
||||||
var optionsList []*OptionsEntry
|
|
||||||
for _, path := range configPaths {
|
|
||||||
optionsEntry, err := readConfigAt(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
optionsList = append(optionsList, optionsEntry)
|
|
||||||
}
|
|
||||||
for _, directory := range configDirectories {
|
|
||||||
entries, err := os.ReadDir(directory)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read config directory at ", directory)
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
optionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
optionsList = append(optionsList, optionsEntry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Slice(optionsList, func(i, j int) bool {
|
|
||||||
return optionsList[i].path < optionsList[j].path
|
|
||||||
})
|
|
||||||
return optionsList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfigAndMerge() (option.Options, error) {
|
|
||||||
optionsList, err := readConfig()
|
|
||||||
if err != nil {
|
|
||||||
return option.Options{}, err
|
|
||||||
}
|
|
||||||
if len(optionsList) == 1 {
|
|
||||||
return optionsList[0].options, nil
|
|
||||||
}
|
|
||||||
var mergedOptions option.Options
|
|
||||||
for _, options := range optionsList {
|
|
||||||
mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions)
|
|
||||||
if err != nil {
|
|
||||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mergedOptions, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func create() (*box.Box, context.CancelFunc, error) {
|
func create() (*box.Box, context.CancelFunc, error) {
|
||||||
options, err := readConfigAndMerge()
|
options, err := readConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -127,25 +64,11 @@ func create() (*box.Box, context.CancelFunc, error) {
|
|||||||
options.Log.DisableColor = true
|
options.Log.DisableColor = true
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
instance, err := box.New(ctx, options, nil)
|
instance, err := box.New(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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()
|
||||||
@@ -157,7 +80,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 {
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandToolsFlagOutbound string
|
|
||||||
|
|
||||||
var commandTools = &cobra.Command{
|
|
||||||
Use: "tools",
|
|
||||||
Short: "Experimental tools",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
|
|
||||||
mainCommand.AddCommand(commandTools)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPreStartedClient() (*box.Box, error) {
|
|
||||||
options, err := readConfigAndMerge()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
instance, err := box.New(context.Background(), options, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create service")
|
|
||||||
}
|
|
||||||
err = instance.PreStart()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "start service")
|
|
||||||
}
|
|
||||||
return instance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
|
||||||
if outboundTag == "" {
|
|
||||||
outbound := instance.Router().DefaultOutbound(N.NetworkName(network))
|
|
||||||
if outbound == nil {
|
|
||||||
return nil, E.New("missing default outbound")
|
|
||||||
}
|
|
||||||
return outbound, nil
|
|
||||||
} else {
|
|
||||||
outbound, loaded := instance.Router().Outbound(outboundTag)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound not found: ", outboundTag)
|
|
||||||
}
|
|
||||||
return outbound, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"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/sing/common/task"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandConnectFlagNetwork string
|
|
||||||
|
|
||||||
var commandConnect = &cobra.Command{
|
|
||||||
Use: "connect [address]",
|
|
||||||
Short: "Connect to an address",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := connect(args[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
|
|
||||||
commandTools.AddCommand(commandConnect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect(address string) error {
|
|
||||||
switch N.NetworkName(commandConnectFlagNetwork) {
|
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
|
||||||
default:
|
|
||||||
return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)
|
|
||||||
}
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "connect to server")
|
|
||||||
}
|
|
||||||
var group task.Group
|
|
||||||
group.Append("upload", func(ctx context.Context) error {
|
|
||||||
return common.Error(bufio.Copy(conn, os.Stdin))
|
|
||||||
})
|
|
||||||
group.Append("download", func(ctx context.Context) error {
|
|
||||||
return common.Error(bufio.Copy(os.Stdout, conn))
|
|
||||||
})
|
|
||||||
group.Cleanup(func() {
|
|
||||||
conn.Close()
|
|
||||||
})
|
|
||||||
err = group.Run(context.Background())
|
|
||||||
if E.IsClosed(err) {
|
|
||||||
log.Info(err)
|
|
||||||
} else {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandFetch = &cobra.Command{
|
|
||||||
Use: "fetch",
|
|
||||||
Short: "Fetch an URL",
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := fetch(args)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandTools.AddCommand(commandFetch)
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpClient *http.Client
|
|
||||||
|
|
||||||
func fetch(args []string) error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
for _, urlString := range args {
|
|
||||||
parsedURL, err := url.Parse(urlString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch parsedURL.Scheme {
|
|
||||||
case "":
|
|
||||||
parsedURL.Scheme = "http"
|
|
||||||
fallthrough
|
|
||||||
case "http", "https":
|
|
||||||
err = fetchHTTP(parsedURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchHTTP(parsedURL *url.URL) error {
|
|
||||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
request.Header.Add("User-Agent", "curl/7.88.0")
|
|
||||||
response, err := httpClient.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = bufio.Copy(os.Stdout, response.Body)
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/settings"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
commandSyncTimeFlagServer string
|
|
||||||
commandSyncTimeOutputFormat string
|
|
||||||
commandSyncTimeWrite bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandSyncTime = &cobra.Command{
|
|
||||||
Use: "synctime",
|
|
||||||
Short: "Sync time using the NTP protocol",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := syncTime()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandSyncTime.Flags().StringVarP(&commandSyncTimeFlagServer, "server", "s", "time.apple.com", "Set NTP server")
|
|
||||||
commandSyncTime.Flags().StringVarP(&commandSyncTimeOutputFormat, "format", "f", C.TimeLayout, "Set output format")
|
|
||||||
commandSyncTime.Flags().BoolVarP(&commandSyncTimeWrite, "write", "w", false, "Write time to system")
|
|
||||||
commandTools.AddCommand(commandSyncTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func syncTime() error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
|
||||||
if serverAddress.Port == 0 {
|
|
||||||
serverAddress.Port = 123
|
|
||||||
}
|
|
||||||
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if commandSyncTimeWrite {
|
|
||||||
err = settings.SetSystemTime(response.Time)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "write time to system")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Stdout.WriteString(response.Time.Local().Format(commandSyncTimeOutputFormat))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -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
|
if !nameOnly {
|
||||||
|
version += " ("
|
||||||
debugInfo, loaded := debug.ReadBuildInfo()
|
version += runtime.Version()
|
||||||
if loaded {
|
version += ", "
|
||||||
for _, setting := range debugInfo.Settings {
|
version += runtime.GOOS
|
||||||
switch setting.Key {
|
version += "/"
|
||||||
case "-tags":
|
version += runtime.GOARCH
|
||||||
tags = setting.Value
|
version += ", "
|
||||||
case "vcs.revision":
|
version += "CGO "
|
||||||
revision = setting.Value
|
if C.CGO_ENABLED {
|
||||||
}
|
version += "enabled"
|
||||||
|
} else {
|
||||||
|
version += "disabled"
|
||||||
}
|
}
|
||||||
|
version += ")"
|
||||||
}
|
}
|
||||||
|
version += "\n"
|
||||||
if tags != "" {
|
|
||||||
version += "Tags: " + tags + "\n"
|
|
||||||
}
|
|
||||||
if revision != "" {
|
|
||||||
version += "Revision: " + revision + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if C.CGO_ENABLED {
|
|
||||||
version += "CGO: enabled\n"
|
|
||||||
} else {
|
|
||||||
version += "CGO: disabled\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Stdout.WriteString(version)
|
os.Stdout.WriteString(version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ func init() {
|
|||||||
runtime.ReadMemStats(&memStats)
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
var memObject badjson.JSONObject
|
var memObject badjson.JSONObject
|
||||||
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
memObject.Put("heap", humanize.Bytes(memStats.HeapInuse))
|
||||||
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
memObject.Put("stack", humanize.Bytes(memStats.StackInuse))
|
||||||
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
memObject.Put("idle", humanize.Bytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
memObject.Put("rss", rusageMaxRSS())
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configPaths []string
|
configPath string
|
||||||
configDirectories []string
|
workingDir string
|
||||||
workingDir string
|
disableColor bool
|
||||||
disableColor bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var mainCommand = &cobra.Command{
|
var mainCommand = &cobra.Command{
|
||||||
@@ -23,8 +20,7 @@ var mainCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
mainCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
|
||||||
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
|
||||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||||
}
|
}
|
||||||
@@ -36,19 +32,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func preRun(cmd *cobra.Command, args []string) {
|
func preRun(cmd *cobra.Command, args []string) {
|
||||||
if disableColor {
|
|
||||||
log.SetStdLogger(log.NewFactory(log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, nil).Logger())
|
|
||||||
}
|
|
||||||
if workingDir != "" {
|
if workingDir != "" {
|
||||||
_, err := os.Stat(workingDir)
|
|
||||||
if err != nil {
|
|
||||||
os.MkdirAll(workingDir, 0o777)
|
|
||||||
}
|
|
||||||
if err := os.Chdir(workingDir); err != nil {
|
if err := os.Chdir(workingDir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
|
||||||
configPaths = append(configPaths, "config.json")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func WrapH2(err error) error {
|
|||||||
if err == io.ErrUnexpectedEOF {
|
if err == io.ErrUnexpectedEOF {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
if Contains(err, "client disconnected", "body closed by handler", "response body closed", "; CANCEL") {
|
if Contains(err, "client disconnected", "body closed by handler") {
|
||||||
return net.ErrClosed
|
return net.ErrClosed
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
package badjsonmerge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badjson"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MergeOptions(source option.Options, destination option.Options) (option.Options, error) {
|
|
||||||
rawSource, err := json.Marshal(source)
|
|
||||||
if err != nil {
|
|
||||||
return option.Options{}, E.Cause(err, "marshal source")
|
|
||||||
}
|
|
||||||
rawDestination, err := json.Marshal(destination)
|
|
||||||
if err != nil {
|
|
||||||
return option.Options{}, E.Cause(err, "marshal destination")
|
|
||||||
}
|
|
||||||
rawMerged, err := MergeJSON(rawSource, rawDestination)
|
|
||||||
if err != nil {
|
|
||||||
return option.Options{}, E.Cause(err, "merge options")
|
|
||||||
}
|
|
||||||
var merged option.Options
|
|
||||||
err = json.Unmarshal(rawMerged, &merged)
|
|
||||||
if err != nil {
|
|
||||||
return option.Options{}, E.Cause(err, "unmarshal merged options")
|
|
||||||
}
|
|
||||||
return merged, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeJSON(rawSource json.RawMessage, rawDestination json.RawMessage) (json.RawMessage, error) {
|
|
||||||
source, err := badjson.Decode(rawSource)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode source")
|
|
||||||
}
|
|
||||||
destination, err := badjson.Decode(rawDestination)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode destination")
|
|
||||||
}
|
|
||||||
merged, err := mergeJSON(source, destination)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return json.Marshal(merged)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeJSON(anySource any, anyDestination any) (any, error) {
|
|
||||||
switch destination := anyDestination.(type) {
|
|
||||||
case badjson.JSONArray:
|
|
||||||
switch source := anySource.(type) {
|
|
||||||
case badjson.JSONArray:
|
|
||||||
destination = append(destination, source...)
|
|
||||||
default:
|
|
||||||
destination = append(destination, source)
|
|
||||||
}
|
|
||||||
return destination, nil
|
|
||||||
case *badjson.JSONObject:
|
|
||||||
switch source := anySource.(type) {
|
|
||||||
case *badjson.JSONObject:
|
|
||||||
for _, entry := range source.Entries() {
|
|
||||||
oldValue, loaded := destination.Get(entry.Key)
|
|
||||||
if loaded {
|
|
||||||
var err error
|
|
||||||
entry.Value, err = mergeJSON(entry.Value, oldValue)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "merge object item ", entry.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destination.Put(entry.Key, entry.Value)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, E.New("cannot merge json object into ", reflect.TypeOf(destination))
|
|
||||||
}
|
|
||||||
return destination, nil
|
|
||||||
default:
|
|
||||||
return destination, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package badjsonmerge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMergeJSON(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
options := option.Options{
|
|
||||||
Log: &option.LogOptions{
|
|
||||||
Level: "info",
|
|
||||||
},
|
|
||||||
Route: &option.RouteOptions{
|
|
||||||
Rules: []option.Rule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultRule{
|
|
||||||
Network: N.NetworkTCP,
|
|
||||||
Outbound: "direct",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
anotherOptions := option.Options{
|
|
||||||
Outbounds: []option.Outbound{
|
|
||||||
{
|
|
||||||
Type: C.TypeDirect,
|
|
||||||
Tag: "direct",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
thirdOptions := option.Options{
|
|
||||||
Route: &option.RouteOptions{
|
|
||||||
Rules: []option.Rule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultRule{
|
|
||||||
Network: N.NetworkUDP,
|
|
||||||
Outbound: "direct",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
mergeOptions, err := MergeOptions(options, anotherOptions)
|
|
||||||
require.NoError(t, err)
|
|
||||||
mergeOptions, err = MergeOptions(thirdOptions, mergeOptions)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "info", mergeOptions.Log.Level)
|
|
||||||
require.Equal(t, 2, len(mergeOptions.Route.Rules))
|
|
||||||
require.Equal(t, C.TypeDirect, mergeOptions.Outbounds[0].Type)
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TLSConn interface {
|
|
||||||
net.Conn
|
|
||||||
HandshakeContext(ctx context.Context) error
|
|
||||||
ConnectionState() tls.ConnectionState
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
48
common/canceler/instance.go
Normal file
48
common/canceler/instance.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package canceler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Instance struct {
|
||||||
|
ctx context.Context
|
||||||
|
cancelFunc context.CancelFunc
|
||||||
|
timer *time.Timer
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, cancelFunc context.CancelFunc, timeout time.Duration) *Instance {
|
||||||
|
instance := &Instance{
|
||||||
|
ctx,
|
||||||
|
cancelFunc,
|
||||||
|
time.NewTimer(timeout),
|
||||||
|
timeout,
|
||||||
|
}
|
||||||
|
go instance.wait()
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Update() bool {
|
||||||
|
if !i.timer.Stop() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !i.timer.Reset(i.timeout) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) wait() {
|
||||||
|
select {
|
||||||
|
case <-i.timer.C:
|
||||||
|
case <-i.ctx.Done():
|
||||||
|
}
|
||||||
|
i.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Close() error {
|
||||||
|
i.timer.Stop()
|
||||||
|
i.cancelFunc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
49
common/canceler/packet.go
Normal file
49
common/canceler/packet.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package canceler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
N.PacketConn
|
||||||
|
instance *Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConn(ctx context.Context, conn N.PacketConn, timeout time.Duration) (context.Context, N.PacketConn) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
instance := New(ctx, cancel, timeout)
|
||||||
|
return ctx, &PacketConn{conn, instance}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
destination, err = c.PacketConn.ReadPacket(buffer)
|
||||||
|
if err == nil {
|
||||||
|
c.instance.Update()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
err := c.PacketConn.WritePacket(buffer, destination)
|
||||||
|
if err == nil {
|
||||||
|
c.instance.Update()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
c.PacketConn,
|
||||||
|
c.instance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
element *list.Element[io.Closer]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(conn net.Conn) (*Conn, error) {
|
|
||||||
connAccess.Lock()
|
|
||||||
element := openConnection.PushBack(conn)
|
|
||||||
connAccess.Unlock()
|
|
||||||
if KillerEnabled {
|
|
||||||
err := killerCheck()
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Conn{
|
|
||||||
Conn: conn,
|
|
||||||
element: element,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
if c.element.Value != nil {
|
|
||||||
connAccess.Lock()
|
|
||||||
if c.element.Value != nil {
|
|
||||||
openConnection.Remove(c.element)
|
|
||||||
c.element.Value = nil
|
|
||||||
}
|
|
||||||
connAccess.Unlock()
|
|
||||||
}
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
runtimeDebug "runtime/debug"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
KillerEnabled bool
|
|
||||||
MemoryLimit int64
|
|
||||||
killerLastCheck time.Time
|
|
||||||
)
|
|
||||||
|
|
||||||
func killerCheck() error {
|
|
||||||
if !KillerEnabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nowTime := time.Now()
|
|
||||||
if nowTime.Sub(killerLastCheck) < 3*time.Second {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
killerLastCheck = nowTime
|
|
||||||
var memStats runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&memStats)
|
|
||||||
inuseMemory := int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
|
|
||||||
if inuseMemory > MemoryLimit {
|
|
||||||
Close()
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
runtimeDebug.FreeOSMemory()
|
|
||||||
}()
|
|
||||||
return E.New("out of memory")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PacketConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
element *list.Element[io.Closer]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) {
|
|
||||||
connAccess.Lock()
|
|
||||||
element := openConnection.PushBack(conn)
|
|
||||||
connAccess.Unlock()
|
|
||||||
if KillerEnabled {
|
|
||||||
err := killerCheck()
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &PacketConn{
|
|
||||||
PacketConn: conn,
|
|
||||||
element: element,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Close() error {
|
|
||||||
if c.element.Value != nil {
|
|
||||||
connAccess.Lock()
|
|
||||||
if c.element.Value != nil {
|
|
||||||
openConnection.Remove(c.element)
|
|
||||||
c.element.Value = nil
|
|
||||||
}
|
|
||||||
connAccess.Unlock()
|
|
||||||
}
|
|
||||||
return c.PacketConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Upstream() any {
|
|
||||||
return c.PacketConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
connAccess sync.RWMutex
|
|
||||||
openConnection list.List[io.Closer]
|
|
||||||
)
|
|
||||||
|
|
||||||
func Count() int {
|
|
||||||
return openConnection.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func List() []io.Closer {
|
|
||||||
connAccess.RLock()
|
|
||||||
defer connAccess.RUnlock()
|
|
||||||
connList := make([]io.Closer, 0, openConnection.Len())
|
|
||||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
|
||||||
connList = append(connList, element.Value)
|
|
||||||
}
|
|
||||||
return connList
|
|
||||||
}
|
|
||||||
|
|
||||||
func Close() {
|
|
||||||
connAccess.Lock()
|
|
||||||
defer connAccess.Unlock()
|
|
||||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
|
||||||
common.Close(element.Value)
|
|
||||||
element.Value = nil
|
|
||||||
}
|
|
||||||
openConnection.Init()
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build !with_conntrack
|
|
||||||
|
|
||||||
package conntrack
|
|
||||||
|
|
||||||
const Enabled = false
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build with_conntrack
|
|
||||||
|
|
||||||
package conntrack
|
|
||||||
|
|
||||||
const Enabled = true
|
|
||||||
@@ -3,10 +3,10 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/common/warning"
|
"github.com/sagernet/sing-box/common/warning"
|
||||||
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"
|
||||||
@@ -14,7 +14,8 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
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(
|
||||||
@@ -53,13 +54,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 {
|
||||||
@@ -67,15 +65,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 := 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() {
|
||||||
bindFunc := router.AutoDetectInterfaceFunc()
|
if C.IsWindows {
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
bindFunc := control.BindToInterfaceIndexFunc(func(network, address string) int {
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
return router.InterfaceMonitor().DefaultInterfaceIndex(M.ParseSocksaddr(address).Addr)
|
||||||
|
})
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else {
|
||||||
|
bindFunc := control.BindToInterfaceFunc(router.InterfaceBindManager(), func(network, address string) string {
|
||||||
|
return router.InterfaceMonitor().DefaultInterfaceName(M.ParseSocksaddr(address).Addr)
|
||||||
|
})
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
}
|
||||||
} else if router.DefaultInterface() != "" {
|
} else if router.DefaultInterface() != "" {
|
||||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
|
bindFunc := 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)
|
||||||
}
|
}
|
||||||
@@ -104,47 +112,22 @@ 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
|
var bindAddress netip.Addr
|
||||||
} else {
|
if options.BindAddress != nil {
|
||||||
udpFragment = options.UDPFragmentDefault
|
bindAddress = options.BindAddress.Build()
|
||||||
}
|
}
|
||||||
if !udpFragment {
|
if bindAddress.IsValid() {
|
||||||
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
|
dialer.LocalAddr = &net.TCPAddr{
|
||||||
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
|
IP: bindAddress.AsSlice(),
|
||||||
}
|
}
|
||||||
var (
|
udpDialer.LocalAddr = &net.UDPAddr{
|
||||||
dialer4 = dialer
|
IP: bindAddress.AsSlice(),
|
||||||
udpDialer4 = dialer
|
}
|
||||||
udpAddr4 string
|
bindUDPAddr = M.SocksaddrFrom(bindAddress, 0).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) {
|
||||||
@@ -153,37 +136,11 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, 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 trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
|
||||||
} else {
|
|
||||||
return trackConn(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 trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
|
||||||
} else {
|
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
|
||||||
if !conntrack.Enabled || err != nil {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
return conntrack.NewConn(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
|
||||||
if !conntrack.Enabled || err != nil {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
return conntrack.NewPacketConn(conn)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,145 +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) NeedHandshake() 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)
|
|
||||||
}
|
|
||||||
@@ -1,55 +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"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
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, error) {
|
|
||||||
return tls.Client(conn, s.config), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *STDClientConfig) Clone() Config {
|
|
||||||
return &STDClientConfig{s.config.Clone()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSTDClient(router adapter.Router, 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +37,6 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
@@ -84,14 +62,14 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
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")
|
||||||
}
|
}
|
||||||
@@ -126,5 +104,42 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -488,7 +488,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,21 +26,14 @@ 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
|
var stream net.Conn
|
||||||
group.Append0(func(ctx context.Context) error {
|
for {
|
||||||
var stream net.Conn
|
stream, err = session.Accept()
|
||||||
for {
|
if err != nil {
|
||||||
stream, err = session.Accept()
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
|
|
||||||
}
|
}
|
||||||
})
|
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
|
||||||
group.Cleanup(func() {
|
}
|
||||||
session.Close()
|
|
||||||
})
|
|
||||||
return group.Run(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
|
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,40 +22,19 @@ 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, &Error{err}
|
return nil, err
|
||||||
}
|
}
|
||||||
if bufReader.Buffered() > 0 {
|
if bufReader.Buffered() > 0 {
|
||||||
cache := buf.NewSize(bufReader.Buffered())
|
cache := buf.NewSize(bufReader.Buffered())
|
||||||
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
|
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &Error{err}
|
return nil, err
|
||||||
}
|
}
|
||||||
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),
|
||||||
Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(),
|
Destination: M.SocksaddrFromNet(header.DestinationAddr),
|
||||||
Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(),
|
}}, nil
|
||||||
}}, nil
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Error = (*Error)(nil)
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Unwrap() error {
|
|
||||||
return e.error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Timeout() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Temporary() bool {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -26,9 +26,9 @@ func init() {
|
|||||||
|
|
||||||
func runAndroidShell(name string, args ...string) error {
|
func runAndroidShell(name string, args ...string) error {
|
||||||
if !useRish {
|
if !useRish {
|
||||||
return shell.Exec(name, args...).Attach().Run()
|
return common.Exec(name, args...).Attach().Run()
|
||||||
} else {
|
} else {
|
||||||
return shell.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
return common.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ 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(netip.IPv4Unspecified())
|
||||||
if p.interfaceName == newInterfaceName {
|
if p.interfaceName == newInterfaceName {
|
||||||
return nil
|
return nil
|
||||||
@@ -34,13 +34,13 @@ func (p *systemProxy) update(event int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if p.isMixed {
|
if p.isMixed {
|
||||||
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
err = common.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
err = common.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
err = common.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -51,19 +51,19 @@ func (p *systemProxy) unset() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if p.isMixed {
|
if p.isMixed {
|
||||||
err = shell.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
|
err = common.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = shell.Exec("networksetup", "-setwebproxystate", interfaceDisplayName, "off").Attach().Run()
|
err = common.Exec("networksetup", "-setwebproxystate", interfaceDisplayName, "off").Attach().Run()
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = shell.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
|
err = common.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInterfaceDisplayName(name string) (string, error) {
|
func getInterfaceDisplayName(name string) (string, error) {
|
||||||
content, err := shell.Exec("networksetup", "-listallhardwareports").ReadOutput()
|
content, err := common.Exec("networksetup", "-listallhardwareports").Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
|
|||||||
port: port,
|
port: port,
|
||||||
isMixed: isMixed,
|
isMixed: isMixed,
|
||||||
}
|
}
|
||||||
err := proxy.update(tun.EventInterfaceUpdate)
|
err := proxy.update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -28,9 +27,9 @@ func init() {
|
|||||||
|
|
||||||
func runAsUser(name string, args ...string) error {
|
func runAsUser(name string, args ...string) error {
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
return shell.Exec(name, args...).Attach().Run()
|
return common.Exec(name, args...).Attach().Run()
|
||||||
} else if sudoUser != "" {
|
} else if sudoUser != "" {
|
||||||
return shell.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
return common.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
||||||
} else {
|
} else {
|
||||||
return E.New("set system proxy: unable to set as root")
|
return E.New("set system proxy: unable to set as root")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !(windows || linux || darwin)
|
|
||||||
|
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build linux || darwin
|
|
||||||
|
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
|
||||||
return unix.Settimeofday(&timeVal)
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
var systemTime windows.Systemtime
|
|
||||||
systemTime.Year = uint16(nowTime.Year())
|
|
||||||
systemTime.Month = uint16(nowTime.Month())
|
|
||||||
systemTime.Day = uint16(nowTime.Day())
|
|
||||||
systemTime.Hour = uint16(nowTime.Hour())
|
|
||||||
systemTime.Minute = uint16(nowTime.Minute())
|
|
||||||
systemTime.Second = uint16(nowTime.Second())
|
|
||||||
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
|
|
||||||
|
|
||||||
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
proc := dllKernel32.NewProc("SetSystemTime")
|
|
||||||
|
|
||||||
_, _, err := proc.Call(
|
|
||||||
uintptr(unsafe.Pointer(&systemTime)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil && err.Error() != "The operation completed successfully." {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -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) {
|
||||||
@@ -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
6
common/sniff/domain.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package sniff
|
||||||
|
|
||||||
|
import _ "unsafe" // for linkname
|
||||||
|
|
||||||
|
//go:linkname IsDomainName net.isDomainName
|
||||||
|
func IsDomainName(domain string) bool
|
||||||
@@ -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 (
|
||||||
|
|||||||
@@ -18,11 +18,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
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"
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
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.Reality != nil && options.Reality.Enabled {
|
|
||||||
return NewRealityClient(router, serverAddress, options)
|
|
||||||
} else if options.UTLS != nil && options.UTLS.Enabled {
|
|
||||||
return NewUTLSClient(router, serverAddress, options)
|
|
||||||
} else {
|
|
||||||
return NewSTDClient(router, serverAddress, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
|
||||||
defer cancel()
|
|
||||||
return aTLS.ClientHandshake(ctx, conn, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Config = aTLS.Config
|
|
||||||
ConfigCompat = aTLS.ConfigCompat
|
|
||||||
ServerConfig = aTLS.ServerConfig
|
|
||||||
ServerConfigCompat = aTLS.ServerConfigCompat
|
|
||||||
WithSessionIDGenerator = aTLS.WithSessionIDGenerator
|
|
||||||
Conn = aTLS.Conn
|
|
||||||
|
|
||||||
STDConfig = tls.Config
|
|
||||||
STDConn = tls.Conn
|
|
||||||
ConnectionState = tls.ConnectionState
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseTLSVersion(version string) (uint16, error) {
|
|
||||||
switch version {
|
|
||||||
case "1.0":
|
|
||||||
return tls.VersionTLS10, nil
|
|
||||||
case "1.1":
|
|
||||||
return tls.VersionTLS11, nil
|
|
||||||
case "1.2":
|
|
||||||
return tls.VersionTLS12, nil
|
|
||||||
case "1.3":
|
|
||||||
return tls.VersionTLS13, nil
|
|
||||||
default:
|
|
||||||
return 0, E.New("unknown tls version:", version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,226 +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, error) {
|
|
||||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (c *echConnWrapper) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
|
||||||
var serverName string
|
|
||||||
if options.ServerName != "" {
|
|
||||||
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
|
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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`)
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"math/big"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
|
||||||
if timeFunc == nil {
|
|
||||||
timeFunc = time.Now
|
|
||||||
}
|
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
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: timeFunc().Add(time.Hour * -1),
|
|
||||||
NotAfter: timeFunc().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
|
|
||||||
}
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
//go:build with_utls
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/sha512"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
mRand "math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/debug"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/hkdf"
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
|
||||||
|
|
||||||
type RealityClientConfig struct {
|
|
||||||
uClient *UTLSClientConfig
|
|
||||||
publicKey []byte
|
|
||||||
shortID [8]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
|
|
||||||
if options.UTLS == nil || !options.UTLS.Enabled {
|
|
||||||
return nil, E.New("uTLS is required by reality client")
|
|
||||||
}
|
|
||||||
|
|
||||||
uClient, err := NewUTLSClient(router, serverAddress, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
publicKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode public_key")
|
|
||||||
}
|
|
||||||
if len(publicKey) != 32 {
|
|
||||||
return nil, E.New("invalid public_key")
|
|
||||||
}
|
|
||||||
var shortID [8]byte
|
|
||||||
decodedLen, err := hex.Decode(shortID[:], []byte(options.Reality.ShortID))
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode short_id")
|
|
||||||
}
|
|
||||||
if decodedLen > 8 {
|
|
||||||
return nil, E.New("invalid short_id")
|
|
||||||
}
|
|
||||||
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
|
||||||
return e.uClient.ServerName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) SetServerName(serverName string) {
|
|
||||||
e.uClient.SetServerName(serverName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) NextProtos() []string {
|
|
||||||
return e.uClient.NextProtos()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) SetNextProtos(nextProto []string) {
|
|
||||||
e.uClient.SetNextProtos(nextProto)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) Config() (*STDConfig, error) {
|
|
||||||
return nil, E.New("unsupported usage for reality")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {
|
|
||||||
return ClientHandshake(context.Background(), conn, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
|
||||||
verifier := &realityVerifier{
|
|
||||||
serverName: e.uClient.ServerName(),
|
|
||||||
}
|
|
||||||
uConfig := e.uClient.config.Clone()
|
|
||||||
uConfig.InsecureSkipVerify = true
|
|
||||||
uConfig.SessionTicketsDisabled = true
|
|
||||||
uConfig.VerifyPeerCertificate = verifier.VerifyPeerCertificate
|
|
||||||
uConn := utls.UClient(conn, uConfig, e.uClient.id)
|
|
||||||
verifier.UConn = uConn
|
|
||||||
err := uConn.BuildHandshakeState()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hello := uConn.HandshakeState.Hello
|
|
||||||
hello.SessionId = make([]byte, 32)
|
|
||||||
copy(hello.Raw[39:], hello.SessionId)
|
|
||||||
|
|
||||||
var nowTime time.Time
|
|
||||||
if uConfig.Time != nil {
|
|
||||||
nowTime = uConfig.Time()
|
|
||||||
} else {
|
|
||||||
nowTime = time.Now()
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
|
||||||
|
|
||||||
hello.SessionId[0] = 1
|
|
||||||
hello.SessionId[1] = 7
|
|
||||||
hello.SessionId[2] = 5
|
|
||||||
copy(hello.SessionId[8:], e.shortID[:])
|
|
||||||
|
|
||||||
if debug.Enabled {
|
|
||||||
fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16])
|
|
||||||
}
|
|
||||||
|
|
||||||
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(e.publicKey)
|
|
||||||
if authKey == nil {
|
|
||||||
return nil, E.New("nil auth_key")
|
|
||||||
}
|
|
||||||
verifier.authKey = authKey
|
|
||||||
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aesBlock, _ := aes.NewCipher(authKey)
|
|
||||||
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
|
||||||
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
|
||||||
copy(hello.Raw[39:], hello.SessionId)
|
|
||||||
if debug.Enabled {
|
|
||||||
fmt.Printf("REALITY hello.sessionId: %v\n", hello.SessionId)
|
|
||||||
fmt.Printf("REALITY uConn.AuthKey: %v\n", authKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = uConn.HandshakeContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug.Enabled {
|
|
||||||
fmt.Printf("REALITY Conn.Verified: %v\n", verifier.verified)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !verifier.verified {
|
|
||||||
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
|
|
||||||
return nil, E.New("reality verification failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &utlsConnWrapper{uConn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
|
||||||
defer uConn.Close()
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: &http2.Transport{
|
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
|
||||||
return uConn, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
|
||||||
request.Header.Set("User-Agent", fingerprint.Client)
|
|
||||||
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", mRand.Intn(32)+30)})
|
|
||||||
response, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, _ = io.Copy(io.Discard, response.Body)
|
|
||||||
response.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
|
||||||
e.uClient.config.SessionIDGenerator = generator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
|
||||||
return &RealityClientConfig{
|
|
||||||
e.uClient.Clone().(*UTLSClientConfig),
|
|
||||||
e.publicKey,
|
|
||||||
e.shortID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type realityVerifier struct {
|
|
||||||
*utls.UConn
|
|
||||||
serverName string
|
|
||||||
authKey []byte
|
|
||||||
verified bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
|
||||||
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
|
||||||
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
|
|
||||||
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
|
|
||||||
h := hmac.New(sha512.New, c.authKey)
|
|
||||||
h.Write(pub)
|
|
||||||
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
|
|
||||||
c.verified = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
DNSName: c.serverName,
|
|
||||||
Intermediates: x509.NewCertPool(),
|
|
||||||
}
|
|
||||||
for _, cert := range certs[1:] {
|
|
||||||
opts.Intermediates.AddCert(cert)
|
|
||||||
}
|
|
||||||
if _, err := certs[0].Verify(opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
//go:build with_reality_server
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/reality"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/debug"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
|
||||||
|
|
||||||
type RealityServerConfig struct {
|
|
||||||
config *reality.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
|
||||||
var tlsConfig reality.Config
|
|
||||||
|
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
|
||||||
return nil, E.New("acme is unavailable in reality")
|
|
||||||
}
|
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
if options.ServerName != "" {
|
|
||||||
tlsConfig.ServerName = options.ServerName
|
|
||||||
}
|
|
||||||
if len(options.ALPN) > 0 {
|
|
||||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
|
||||||
}
|
|
||||||
if options.MinVersion != "" {
|
|
||||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse min_version")
|
|
||||||
}
|
|
||||||
tlsConfig.MinVersion = minVersion
|
|
||||||
}
|
|
||||||
if options.MaxVersion != "" {
|
|
||||||
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse max_version")
|
|
||||||
}
|
|
||||||
tlsConfig.MaxVersion = maxVersion
|
|
||||||
}
|
|
||||||
if options.CipherSuites != nil {
|
|
||||||
find:
|
|
||||||
for _, cipherSuite := range options.CipherSuites {
|
|
||||||
for _, tlsCipherSuite := range tls.CipherSuites() {
|
|
||||||
if cipherSuite == tlsCipherSuite.Name {
|
|
||||||
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
|
||||||
continue find
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if options.Certificate != "" || options.CertificatePath != "" {
|
|
||||||
return nil, E.New("certificate is unavailable in reality")
|
|
||||||
}
|
|
||||||
if options.Key != "" || options.KeyPath != "" {
|
|
||||||
return nil, E.New("key is unavailable in reality")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig.SessionTicketsDisabled = true
|
|
||||||
tlsConfig.Type = N.NetworkTCP
|
|
||||||
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
|
||||||
|
|
||||||
tlsConfig.ServerNames = map[string]bool{options.ServerName: true}
|
|
||||||
privateKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode private key")
|
|
||||||
}
|
|
||||||
if len(privateKey) != 32 {
|
|
||||||
return nil, E.New("invalid private key")
|
|
||||||
}
|
|
||||||
tlsConfig.PrivateKey = privateKey
|
|
||||||
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
|
||||||
|
|
||||||
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
|
||||||
for i, shortIDString := range options.Reality.ShortID {
|
|
||||||
var shortID [8]byte
|
|
||||||
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
|
|
||||||
}
|
|
||||||
if decodedLen > 8 {
|
|
||||||
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
|
|
||||||
}
|
|
||||||
tlsConfig.ShortIds[shortID] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
|
|
||||||
tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug.Enabled {
|
|
||||||
tlsConfig.Show = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RealityServerConfig{&tlsConfig}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) ServerName() string {
|
|
||||||
return c.config.ServerName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) SetServerName(serverName string) {
|
|
||||||
c.config.ServerName = serverName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) NextProtos() []string {
|
|
||||||
return c.config.NextProtos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) SetNextProtos(nextProto []string) {
|
|
||||||
c.config.NextProtos = nextProto
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Config() (*tls.Config, error) {
|
|
||||||
return nil, E.New("unsupported usage for reality")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) {
|
|
||||||
return ClientHandshake(context.Background(), conn, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
|
||||||
return ServerHandshake(context.Background(), conn, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
|
||||||
tlsConn, err := reality.Server(ctx, conn, c.config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &realityConnWrapper{Conn: tlsConn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Clone() Config {
|
|
||||||
return &RealityServerConfig{
|
|
||||||
config: c.config.Clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Conn = (*realityConnWrapper)(nil)
|
|
||||||
|
|
||||||
type realityConnWrapper struct {
|
|
||||||
*reality.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
|
||||||
state := c.Conn.ConnectionState()
|
|
||||||
return tls.ConnectionState{
|
|
||||||
Version: state.Version,
|
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
|
||||||
DidResume: state.DidResume,
|
|
||||||
CipherSuite: state.CipherSuite,
|
|
||||||
NegotiatedProtocol: state.NegotiatedProtocol,
|
|
||||||
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
|
||||||
ServerName: state.ServerName,
|
|
||||||
PeerCertificates: state.PeerCertificates,
|
|
||||||
VerifiedChains: state.VerifiedChains,
|
|
||||||
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
|
||||||
OCSPResponse: state.OCSPResponse,
|
|
||||||
TLSUnique: state.TLSUnique,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityConnWrapper) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//go:build !with_reality_server
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
|
||||||
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
|
||||||
if !options.Enabled {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
|
||||||
return NewRealityServer(ctx, router, logger, options)
|
|
||||||
} else {
|
|
||||||
return NewSTDServer(ctx, router, logger, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
|
||||||
defer cancel()
|
|
||||||
return aTLS.ServerHandshake(ctx, conn, config)
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
//go:build with_utls
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"math/rand"
|
|
||||||
"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/sagernet/utls"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
|
||||||
nextProto = append(nextProto, "http/1.1")
|
|
||||||
}
|
|
||||||
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, error) {
|
|
||||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
|
||||||
e.config.SessionIDGenerator = generator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Clone() Config {
|
|
||||||
return &UTLSClientConfig{
|
|
||||||
config: e.config.Clone(),
|
|
||||||
id: e.id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
|
||||||
*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 (c *utlsConnWrapper) Upstream() any {
|
|
||||||
return c.UConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, 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
|
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &UTLSClientConfig{&tlsConfig, id}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
randomFingerprint utls.ClientHelloID
|
|
||||||
randomizedFingerprint utls.ClientHelloID
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
modernFingerprints := []utls.ClientHelloID{
|
|
||||||
utls.HelloChrome_Auto,
|
|
||||||
utls.HelloFirefox_Auto,
|
|
||||||
utls.HelloEdge_Auto,
|
|
||||||
utls.HelloSafari_Auto,
|
|
||||||
utls.HelloIOS_Auto,
|
|
||||||
}
|
|
||||||
randomFingerprint = modernFingerprints[rand.Intn(len(modernFingerprints))]
|
|
||||||
|
|
||||||
weights := utls.DefaultWeights
|
|
||||||
weights.TLSVersMax_Set_VersionTLS13 = 1
|
|
||||||
weights.FirstKeyShare_Set_CurveP256 = 0
|
|
||||||
randomizedFingerprint = utls.HelloRandomized
|
|
||||||
randomizedFingerprint.Seed, _ = utls.NewPRNGSeed()
|
|
||||||
randomizedFingerprint.Weights = &weights
|
|
||||||
}
|
|
||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
|
||||||
switch name {
|
|
||||||
case "chrome", "":
|
|
||||||
return utls.HelloChrome_Auto, nil
|
|
||||||
case "firefox":
|
|
||||||
return utls.HelloFirefox_Auto, nil
|
|
||||||
case "edge":
|
|
||||||
return utls.HelloEdge_Auto, nil
|
|
||||||
case "safari":
|
|
||||||
return utls.HelloSafari_Auto, nil
|
|
||||||
case "360":
|
|
||||||
return utls.Hello360_Auto, nil
|
|
||||||
case "qq":
|
|
||||||
return utls.HelloQQ_Auto, nil
|
|
||||||
case "ios":
|
|
||||||
return utls.HelloIOS_Auto, nil
|
|
||||||
case "android":
|
|
||||||
return utls.HelloAndroid_11_OkHttp, nil
|
|
||||||
case "random":
|
|
||||||
return randomFingerprint, nil
|
|
||||||
case "randomized":
|
|
||||||
return randomizedFingerprint, nil
|
|
||||||
default:
|
|
||||||
return utls.ClientHelloID{}, E.New("unknown uTLS fingerprint: ", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +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`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
|
||||||
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package constant
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
const (
|
|
||||||
DHCPTTL = time.Hour
|
|
||||||
DHCPTimeout = time.Minute
|
|
||||||
)
|
|
||||||
@@ -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`)
|
|
||||||
|
|||||||
@@ -3,28 +3,13 @@ package constant
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dirName = "sing-box"
|
const dirName = "sing-box"
|
||||||
|
|
||||||
var (
|
var resourcePaths []string
|
||||||
basePath string
|
|
||||||
resourcePaths []string
|
|
||||||
)
|
|
||||||
|
|
||||||
func BasePath(name string) string {
|
|
||||||
if basePath == "" || strings.HasPrefix(name, "/") {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return filepath.Join(basePath, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetBasePath(path string) {
|
|
||||||
basePath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindPath(name string) (string, bool) {
|
func FindPath(name string) (string, bool) {
|
||||||
name = os.ExpandEnv(name)
|
name = os.ExpandEnv(name)
|
||||||
|
|||||||
@@ -1,29 +1,26 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeTun = "tun"
|
TypeTun = "tun"
|
||||||
TypeRedirect = "redirect"
|
TypeRedirect = "redirect"
|
||||||
TypeTProxy = "tproxy"
|
TypeTProxy = "tproxy"
|
||||||
TypeDirect = "direct"
|
TypeDirect = "direct"
|
||||||
TypeBlock = "block"
|
TypeBlock = "block"
|
||||||
TypeDNS = "dns"
|
TypeDNS = "dns"
|
||||||
TypeSocks = "socks"
|
TypeSocks = "socks"
|
||||||
TypeHTTP = "http"
|
TypeHTTP = "http"
|
||||||
TypeMixed = "mixed"
|
TypeMixed = "mixed"
|
||||||
TypeShadowsocks = "shadowsocks"
|
TypeShadowsocks = "shadowsocks"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
|
||||||
TypeVLESS = "vless"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeSelector = "selector"
|
TypeSelector = "selector"
|
||||||
TypeURLTest = "urltest"
|
|
||||||
)
|
)
|
||||||
|
|||||||
5
constant/quic.go
Normal file
5
constant/quic.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build with_quic
|
||||||
|
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const QUIC_AVAILABLE = true
|
||||||
9
constant/quic_stub.go
Normal file
9
constant/quic_stub.go
Normal 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`)
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package constant
|
|
||||||
|
|
||||||
const TimeLayout = "2006-01-02 15:04:05 -0700"
|
|
||||||
@@ -3,11 +3,10 @@ package constant
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TCPTimeout = 5 * time.Second
|
TCPTimeout = 5 * time.Second
|
||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
DNSTimeout = 10 * time.Second
|
DNSTimeout = 10 * time.Second
|
||||||
QUICTimeout = 30 * time.Second
|
QUICTimeout = 30 * time.Second
|
||||||
STUNTimeout = 15 * time.Second
|
STUNTimeout = 15 * time.Second
|
||||||
UDPTimeout = 5 * time.Minute
|
UDPTimeout = 5 * time.Minute
|
||||||
DefaultURLTestInterval = 1 * time.Minute
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
var Version = "unknown"
|
var (
|
||||||
|
Version = "1.0.7"
|
||||||
|
Commit = ""
|
||||||
|
)
|
||||||
|
|||||||
35
debug.go
35
debug.go
@@ -1,35 +0,0 @@
|
|||||||
//go:build go1.19
|
|
||||||
|
|
||||||
package box
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) {
|
|
||||||
if options.GCPercent != nil {
|
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
|
||||||
}
|
|
||||||
if options.MaxStack != nil {
|
|
||||||
debug.SetMaxStack(*options.MaxStack)
|
|
||||||
}
|
|
||||||
if options.MaxThreads != nil {
|
|
||||||
debug.SetMaxThreads(*options.MaxThreads)
|
|
||||||
}
|
|
||||||
if options.PanicOnFault != nil {
|
|
||||||
debug.SetPanicOnFault(*options.PanicOnFault)
|
|
||||||
}
|
|
||||||
if options.TraceBack != "" {
|
|
||||||
debug.SetTraceback(options.TraceBack)
|
|
||||||
}
|
|
||||||
if options.MemoryLimit != 0 {
|
|
||||||
debug.SetMemoryLimit(int64(options.MemoryLimit))
|
|
||||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
|
||||||
}
|
|
||||||
if options.OOMKiller != nil {
|
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
//go:build !go1.19
|
|
||||||
|
|
||||||
package box
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) {
|
|
||||||
if options.GCPercent != nil {
|
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
|
||||||
}
|
|
||||||
if options.MaxStack != nil {
|
|
||||||
debug.SetMaxStack(*options.MaxStack)
|
|
||||||
}
|
|
||||||
if options.MaxThreads != nil {
|
|
||||||
debug.SetMaxThreads(*options.MaxThreads)
|
|
||||||
}
|
|
||||||
if options.PanicOnFault != nil {
|
|
||||||
debug.SetPanicOnFault(*options.PanicOnFault)
|
|
||||||
}
|
|
||||||
if options.TraceBack != "" {
|
|
||||||
debug.SetTraceback(options.TraceBack)
|
|
||||||
}
|
|
||||||
if options.MemoryLimit != 0 {
|
|
||||||
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
|
||||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
|
||||||
}
|
|
||||||
if options.OOMKiller != nil {
|
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<svg width="1027" height="1109" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden">
|
|
||||||
<defs>
|
|
||||||
<filter id="fx0" x="-10%" y="-10%" width="120%" height="120%" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
|
|
||||||
<feComponentTransfer color-interpolation-filters="sRGB">
|
|
||||||
<feFuncR type="discrete" tableValues="0 0" />
|
|
||||||
<feFuncG type="discrete" tableValues="0 0" />
|
|
||||||
<feFuncB type="discrete" tableValues="0 0" />
|
|
||||||
<feFuncA type="linear" slope="0.4" intercept="0" />
|
|
||||||
</feComponentTransfer>
|
|
||||||
<feGaussianBlur stdDeviation="4.58333 4.58333" />
|
|
||||||
</filter>
|
|
||||||
<clipPath id="clip1">
|
|
||||||
<rect x="692" y="855" width="1027" height="1109" />
|
|
||||||
</clipPath>
|
|
||||||
<clipPath id="clip2">
|
|
||||||
<rect x="-2" y="-2" width="541" height="786" />
|
|
||||||
</clipPath>
|
|
||||||
<clipPath id="clip3">
|
|
||||||
<rect x="0" y="0" width="535" height="782" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<g clip-path="url(#clip1)" transform="translate(-692 -855)">
|
|
||||||
<path d="M692 1191 692 1575.69C692 1640.41 731.499 1651.19 731.499 1651.19L1148.03 1931.62C1212.66 1974.77 1194.71 1881.29 1194.71 1881.29L1194.71 1528.96 692 1191Z" fill="#37474F" fill-rule="evenodd" />
|
|
||||||
<g clip-path="url(#clip2)" filter="url(#fx0)" transform="translate(1184 1182)">
|
|
||||||
<g clip-path="url(#clip3)">
|
|
||||||
<path d="M520.482 15.4819 520.482 400.176C520.482 464.89 480.983 475.676 480.983 475.676 480.983 475.676 129.086 712.963 64.4523 756.106-0.181814 799.25 17.7721 705.773 17.7721 705.773L17.7721 353.437 520.482 15.4819Z" fill="#455A64" fill-rule="evenodd" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<path d="M1698 1191 1698 1575.69C1698 1640.41 1658.5 1651.19 1658.5 1651.19 1658.5 1651.19 1306.6 1888.48 1241.97 1931.62 1177.34 1974.77 1195.29 1881.29 1195.29 1881.29L1195.29 1528.96 1698 1191Z" fill="#455A64" fill-rule="evenodd" />
|
|
||||||
<path d="M1241.71 868.473C1212.96 850.509 1169.85 850.509 1144.7 868.473L713.557 1163.07C684.814 1181.04 684.814 1213.37 713.557 1231.33L1144.7 1529.53C1173.44 1547.49 1216.56 1547.49 1241.71 1529.53L1676.44 1227.74C1705.19 1209.78 1705.19 1177.44 1676.44 1159.48L1241.71 868.473Z" fill="#546E7A" fill-rule="evenodd" />
|
|
||||||
<path d="M1195 1949C1173.4 1949 1159 1935.19 1159 1917.92L1159 1531.08C1159 1513.82 1173.4 1500 1195 1500 1216.6 1500 1231 1513.82 1231 1531.08L1231 1914.46C1231 1935.19 1216.6 1949 1195 1949Z" fill="#546E7A" fill-rule="evenodd" />
|
|
||||||
<path d="M1553.92 1435.92C1553.92 1471.89 1557.5 1486.27 1518.03 1511.45L1428.32 1568.99C1388.85 1594.17 1374.5 1572.59 1374.5 1540.22L1374.5 1446.71C1374.5 1439.52 1374.5 1435.92 1363.73 1428.73 1270.43 1363.99 911.591 1115.84 847 1069.09L1012.07 954C1058.72 982.772 1399.61 1209.35 1539.56 1306.45 1546.74 1310.05 1550.33 1317.24 1550.33 1320.84L1550.33 1435.92Z" fill="#99AAB5" fill-rule="evenodd" />
|
|
||||||
<path d="M1543.41 1310.21C1399.82 1213.17 1058.79 986.752 1015.72 958L951.103 997.534 847 1069.41C911.615 1116.14 1270.59 1360.53 1363.92 1425.22 1371.1 1428.81 1371.1 1432.41 1371.1 1436L1547 1313.8C1547 1313.8 1547 1310.21 1543.41 1310.21Z" fill="#CCD6DD" fill-rule="evenodd" />
|
|
||||||
<path d="M1554.9 1435.48 1554.9 1324.19C1554.9 1317.01 1551.3 1313.42 1544.11 1309.83 1400.28 1212.89 1058.67 986.721 1015.51 958L940 1008.26C1062.26 1090.83 1389.49 1306.24 1475.79 1367.27 1486.58 1374.45 1486.58 1381.63 1486.58 1385.22L1486.58 1536 1522.54 1510.87C1558.5 1485.74 1554.9 1467.79 1554.9 1435.48Z" fill="#CCD6DD" fill-rule="evenodd" />
|
|
||||||
<path d="M1543.23 1309.95C1399.6 1212.98 1058.49 986.731 1015.4 958L940 1008.28C1062.08 1090.88 1388.83 1306.36 1475.01 1367.41 1475.01 1367.41 1478.6 1371 1478.6 1371L1554 1317.13C1546.82 1313.54 1546.82 1309.95 1543.23 1309.95Z" fill="#E1E8ED" fill-rule="evenodd" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,220 +1,3 @@
|
|||||||
#### 1.2.2
|
|
||||||
|
|
||||||
* Accept `any` outbound in dns rule **1**
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
*1*:
|
|
||||||
|
|
||||||
Now you can use the `any` outbound rule to match server address queries instead of filling in all server domains to `domain` rule.
|
|
||||||
|
|
||||||
#### 1.2.1
|
|
||||||
|
|
||||||
* Fix missing default host in v2ray http transport`s request
|
|
||||||
* Flush DNS cache for macOS when tun start/close
|
|
||||||
* Fix tun's DNS hijacking compatibility with systemd-resolved
|
|
||||||
|
|
||||||
#### 1.2.0
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
Important changes since 1.1:
|
|
||||||
|
|
||||||
* Introducing our [new iOS client application](/installation/clients/sfi)
|
|
||||||
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
|
||||||
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
|
||||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
|
||||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
|
||||||
* Add [reality TLS](/configuration/shared/tls) support
|
|
||||||
* Add [NTP service](/configuration/ntp)
|
|
||||||
* 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 fallback support for v2ray transport
|
|
||||||
* Add custom TLS server support for http based v2ray transports
|
|
||||||
* Add health check support for http-based v2ray transports
|
|
||||||
* Add multiple configuration support
|
|
||||||
|
|
||||||
#### 1.2-rc1
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.2-beta10
|
|
||||||
|
|
||||||
* Add multiple configuration support **1**
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
*1*:
|
|
||||||
|
|
||||||
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`
|
|
||||||
to load all configuration files in a directory.
|
|
||||||
|
|
||||||
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file
|
|
||||||
name.
|
|
||||||
|
|
||||||
#### 1.1.7
|
|
||||||
|
|
||||||
* Improve the stability of the VMESS server
|
|
||||||
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.2-beta9
|
|
||||||
|
|
||||||
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
|
||||||
* Add health check support for http-based v2ray transports
|
|
||||||
* Remove length limit on short_id for reality TLS config
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.2-beta8
|
|
||||||
|
|
||||||
* Update reality and uTLS libraries
|
|
||||||
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
|
|
||||||
|
|
||||||
#### 1.2-beta7
|
|
||||||
|
|
||||||
* Fix the compatibility issue between VLESS's vision sub-protocol and the Xray-core client
|
|
||||||
* Improve the stability of the VMESS server
|
|
||||||
|
|
||||||
#### 1.2-beta6
|
|
||||||
|
|
||||||
* Introducing our [new iOS client application](/installation/clients/sfi)
|
|
||||||
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
|
||||||
* Add custom TLS server support for http based v2ray transports
|
|
||||||
* Add generate commands
|
|
||||||
* Enable XUDP by default in VLESS
|
|
||||||
* Update reality server
|
|
||||||
* Update vision protocol
|
|
||||||
* Fixed [user flow in vless server](/configuration/inbound/vless#usersflow)
|
|
||||||
* Bug fixes
|
|
||||||
* Update dependencies
|
|
||||||
|
|
||||||
#### 1.2-beta5
|
|
||||||
|
|
||||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
|
||||||
* Add [reality TLS](/configuration/shared/tls) support
|
|
||||||
* Fix match private address
|
|
||||||
|
|
||||||
#### 1.1.6
|
|
||||||
|
|
||||||
* Improve vmess request
|
|
||||||
* Fix ipv6 redirect on Linux
|
|
||||||
* Fix match geoip private
|
|
||||||
* Fix parse hysteria UDP message
|
|
||||||
* Fix socks connect response
|
|
||||||
* Disable vmess header protection if transport enabled
|
|
||||||
* Update QUIC v2 version number and initial salt
|
|
||||||
|
|
||||||
#### 1.2-beta4
|
|
||||||
|
|
||||||
* Add [NTP service](/configuration/ntp)
|
|
||||||
* Add Add multiple server names and multi-user support for shadowtls
|
|
||||||
* Add strict mode support for shadowtls v3
|
|
||||||
* Add uTLS support for shadowtls v3
|
|
||||||
|
|
||||||
#### 1.2-beta3
|
|
||||||
|
|
||||||
* Update QUIC v2 version number and initial salt
|
|
||||||
* Fix shadowtls v3 implementation
|
|
||||||
|
|
||||||
#### 1.2-beta2
|
|
||||||
|
|
||||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
|
||||||
* Add fallback support for v2ray transport
|
|
||||||
* Fix parse hysteria UDP message
|
|
||||||
* Fix socks connect response
|
|
||||||
* Disable vmess header protection if transport enabled
|
|
||||||
|
|
||||||
#### 1.2-beta1
|
|
||||||
|
|
||||||
* Add [DHCP DNS server](/configuration/dns/server) support
|
|
||||||
* 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
|
#### 1.0.7
|
||||||
|
|
||||||
* Add support for new x/h2 deadline
|
* Add support for new x/h2 deadline
|
||||||
@@ -228,83 +11,6 @@ Added [fallback_after](/configuration/inbound/shadowtls#fallback_after) option.
|
|||||||
* Fix udp connect for mux client
|
* Fix udp connect for mux client
|
||||||
* Fix default dns transport strategy
|
* 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
|
#### 1.0.6
|
||||||
|
|
||||||
* Fix ssh outbound
|
* Fix ssh outbound
|
||||||
@@ -312,168 +18,29 @@ The `strict_route` on windows is removed.
|
|||||||
* Fix naive overflow
|
* Fix naive overflow
|
||||||
* Check destination before udp connect
|
* 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
|
#### 1.0.5
|
||||||
|
|
||||||
* Fix missing source address from transport connection
|
* Fix missing source address from transport connection
|
||||||
* Fix fqdn socks5 outbound connection
|
* Fix fqdn socks5 outbound connection
|
||||||
* Fix read source address from grpc-go
|
* 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
|
#### 1.0.4
|
||||||
|
|
||||||
* Fix close grpc conn
|
* Fix close grpc conn
|
||||||
* Fix port rule match logic
|
* Fix port rule match logic
|
||||||
* Fix clash api proxy type
|
* 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
|
#### 1.0.3
|
||||||
|
|
||||||
* Fix socks4 client
|
* Fix socks4 client
|
||||||
* Fix hysteria inbound
|
* Fix hysteria inbound
|
||||||
* Fix concurrent write
|
* 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
|
#### 1.0.2
|
||||||
|
|
||||||
* Fix socks4 request
|
* Fix socks4 request
|
||||||
* Fix processing empty dns result
|
* 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
|
#### 1.0.1
|
||||||
|
|
||||||
* Fix match 4in6 address in ip_cidr
|
* Fix match 4in6 address in ip_cidr
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -78,7 +73,6 @@
|
|||||||
"user_id": [
|
"user_id": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -109,10 +103,8 @@
|
|||||||
|
|
||||||
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 +116,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`.
|
||||||
@@ -220,10 +208,6 @@ Match user name.
|
|||||||
|
|
||||||
Match user id.
|
Match user id.
|
||||||
|
|
||||||
#### clash_mode
|
|
||||||
|
|
||||||
Match Clash mode.
|
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
Invert match result.
|
Invert match result.
|
||||||
@@ -232,8 +216,6 @@ Invert match result.
|
|||||||
|
|
||||||
Match outbound.
|
Match outbound.
|
||||||
|
|
||||||
`any` can be used as a value to match any outbound.
|
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
@@ -256,4 +238,18 @@ Disable cache and save cache in this query.
|
|||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
Included default rules.
|
Included default rules.
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
Invert match result.
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Tag of the target dns server.
|
||||||
|
|
||||||
|
#### disable_cache
|
||||||
|
|
||||||
|
Disable cache and save cache in this query.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user