mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
1 Commits
v1.4.0-rc.
...
dev-ssm-ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d34091f8fc |
111
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
111
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,77 +1,70 @@
|
|||||||
name: Bug report
|
name: Bug Report
|
||||||
description: "Report sing-box bug"
|
description: "Create a report to help us improve."
|
||||||
body:
|
body:
|
||||||
- type: dropdown
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
attributes:
|
attributes:
|
||||||
label: Operating system
|
label: Welcome
|
||||||
description: Operating system type
|
|
||||||
options:
|
options:
|
||||||
- iOS
|
- label: Yes, I'm using the latest major release. Only such installations are supported.
|
||||||
- macOS
|
required: true
|
||||||
- Apple tvOS
|
- label: Yes, I'm using the latest Golang release. Only such installations are supported.
|
||||||
- Android
|
required: true
|
||||||
- Windows
|
- label: Yes, I've searched similar issues on GitHub and didn't find any.
|
||||||
- Linux
|
required: true
|
||||||
- Others
|
- label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).
|
||||||
validations:
|
required: true
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: System version
|
|
||||||
description: Please provide the operating system version
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: Installation type
|
|
||||||
description: Please provide the sing-box installation type
|
|
||||||
options:
|
|
||||||
- Original sing-box Command Line
|
|
||||||
- sing-box for iOS Graphical Client
|
|
||||||
- sing-box for macOS Graphical Client
|
|
||||||
- sing-box for Apple tvOS Graphical Client
|
|
||||||
- sing-box for Android Graphical Client
|
|
||||||
- Third-party graphical clients that advertise themselves as using sing-box (Windows)
|
|
||||||
- Third-party graphical clients that advertise themselves as using sing-box (Android)
|
|
||||||
- Others
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
description: Graphical client version
|
|
||||||
label: If you are using a graphical client, please provide the version of the client.
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
id: problem
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Description of the problem
|
||||||
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
|
placeholder: Your problem description
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Version of sing-box
|
||||||
value: |-
|
value: |-
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# Replace this line with the output
|
$ sing-box version
|
||||||
|
# Paste output here
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: Please provide a detailed description of the error.
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
id: config
|
||||||
attributes:
|
attributes:
|
||||||
label: Reproduction
|
label: Server and client configuration file
|
||||||
description: Please provide the steps to reproduce the error, including the configuration files and procedures that can locally (not dependent on the remote server) reproduce the error using the original command line program of sing-box.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Logs
|
|
||||||
description: |-
|
|
||||||
If you encounter a crash with the graphical client, please provide crash logs.
|
|
||||||
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
|
|
||||||
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
|
||||||
value: |-
|
value: |-
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# Replace this line with logs
|
# paste json here
|
||||||
```
|
```
|
||||||
</details>
|
|
||||||
|
</details>
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: log
|
||||||
|
attributes:
|
||||||
|
label: Server and client log file
|
||||||
|
value: |-
|
||||||
|
<details>
|
||||||
|
|
||||||
|
```console
|
||||||
|
# paste log here
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|||||||
77
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
77
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
@@ -1,77 +0,0 @@
|
|||||||
name: 错误反馈
|
|
||||||
description: "提交 sing-box 漏洞"
|
|
||||||
body:
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: 操作系统
|
|
||||||
description: 请提供操作系统类型
|
|
||||||
options:
|
|
||||||
- iOS
|
|
||||||
- macOS
|
|
||||||
- Apple tvOS
|
|
||||||
- Android
|
|
||||||
- Windows
|
|
||||||
- Linux
|
|
||||||
- 其他
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: 系统版本
|
|
||||||
description: 请提供操作系统版本
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: 安装类型
|
|
||||||
description: 请提供该 sing-box 安装类型
|
|
||||||
options:
|
|
||||||
- sing-box 原始命令行程序
|
|
||||||
- sing-box for iOS 图形客户端程序
|
|
||||||
- sing-box for macOS 图形客户端程序
|
|
||||||
- sing-box for Apple tvOS 图形客户端程序
|
|
||||||
- sing-box for Android 图形客户端程序
|
|
||||||
- 宣传使用 sing-box 的第三方图形客户端程序 (Windows)
|
|
||||||
- 宣传使用 sing-box 的第三方图形客户端程序 (Android)
|
|
||||||
- 其他
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
description: 图形客户端版本
|
|
||||||
label: 如果您使用图形客户端程序,请提供该程序版本。
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 版本
|
|
||||||
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
|
|
||||||
value: |-
|
|
||||||
<details>
|
|
||||||
```console
|
|
||||||
# 使用输出内容覆盖此行
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 描述
|
|
||||||
description: 请提供错误的详细描述。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 重现方式
|
|
||||||
description: 请提供重现错误的步骤,必须包括可以在本地(不依赖与远程服务器)使用 sing-box 原始命令行程序重现错误的配置文件与流程。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 日志
|
|
||||||
description: |-
|
|
||||||
如果您遭遇图形界面应用程序崩溃,请提供崩溃日志。
|
|
||||||
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
|
|
||||||
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
|
||||||
value: |-
|
|
||||||
<details>
|
|
||||||
```console
|
|
||||||
# 使用日志内容覆盖此行
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
40
.github/workflows/debug.yml
vendored
40
.github/workflows/debug.yml
vendored
@@ -28,9 +28,15 @@ jobs:
|
|||||||
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 go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: go-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Add cache to Go proxy
|
- name: Add cache to Go proxy
|
||||||
run: |
|
run: |
|
||||||
version=`git rev-parse HEAD`
|
version=`git rev-parse HEAD`
|
||||||
@@ -52,7 +58,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.10
|
go-version: 1.18.10
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
@@ -62,27 +68,7 @@ jobs:
|
|||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
key: go118-${{ hashFiles('**/go.sum') }}
|
key: go118-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Run Test
|
- name: Run Test
|
||||||
run: make ci_build_go118
|
run: make
|
||||||
build_go120:
|
|
||||||
name: Debug build (Go 1.20)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: 1.20.7
|
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go118-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: Run Test
|
|
||||||
run: make ci_build
|
|
||||||
cross:
|
cross:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -207,9 +193,15 @@ jobs:
|
|||||||
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 go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: go-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
run: make
|
run: make
|
||||||
|
|||||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -28,9 +28,15 @@ jobs:
|
|||||||
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 go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: go-${{ hashFiles('**/go.sum') }}
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
18
.github/workflows/mkdocs.yml
vendored
Normal file
18
.github/workflows/mkdocs.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Generate Documents
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- docs/**
|
||||||
|
- .github/workflows/mkdocs.yml
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
- run: pip install mkdocs-material mkdocs-static-i18n
|
||||||
|
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
||||||
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/
|
|
||||||
@@ -10,15 +10,14 @@ 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=
|
- -s -w -buildid=
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_ssm_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
@@ -45,14 +44,14 @@ 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=
|
- -s -w -buildid=
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_ssm_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
overrides:
|
overrides:
|
||||||
@@ -119,6 +118,9 @@ nfpms:
|
|||||||
dst: /etc/systemd/system/sing-box@.service
|
dst: /etc/systemd/system/sing-box@.service
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
scripts:
|
||||||
|
postinstall: release/config/postinstall.sh
|
||||||
|
postremove: release/config/postremove.sh
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.21-alpine AS builder
|
FROM golang:1.20-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,10 +8,9 @@ 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 with_quic,with_wireguard,with_acme \
|
||||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_dhcp,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 "-s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM alpine AS dist
|
FROM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
|||||||
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,39 +1,22 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,with_ssm_api
|
||||||
TAGS_GO120 = with_quic
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
|
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
|
||||||
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
|
||||||
|
|
||||||
.PHONY: test release
|
.PHONY: test release
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
|
||||||
|
|
||||||
ci_build_go118:
|
|
||||||
go build $(PARAMS) $(MAIN)
|
go build $(PARAMS) $(MAIN)
|
||||||
go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN)
|
|
||||||
|
|
||||||
ci_build:
|
|
||||||
go build $(PARAMS) $(MAIN)
|
|
||||||
go build $(MAIN_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 -s "prefix(github.com/sagernet/)" -s "default" .
|
@gci write --custom-order -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
|
||||||
@@ -59,14 +42,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 --clean --snapshot || exit 1
|
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1
|
||||||
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 --clean --skip-publish || exit 1
|
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
|
||||||
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
|
||||||
@@ -88,21 +71,6 @@ test_stdio:
|
|||||||
go mod tidy && \
|
go mod tidy && \
|
||||||
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||||
|
|
||||||
android:
|
|
||||||
go run ./cmd/internal/build_libbox -target android
|
|
||||||
|
|
||||||
ios:
|
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
|
||||||
|
|
||||||
lib:
|
|
||||||
go run ./cmd/internal/build_libbox -target android
|
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
|
||||||
|
|
||||||
lib_install:
|
|
||||||
go get -v -d
|
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230728014906-3de089147f59
|
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230728014906-3de089147f59
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
|
|||||||
@@ -2,16 +2,10 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
https://community.sagernet.org/c/sing-box/
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -29,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.
|
|
||||||
```
|
```
|
||||||
@@ -10,11 +10,8 @@ import (
|
|||||||
|
|
||||||
type ClashServer interface {
|
type ClashServer interface {
|
||||||
Service
|
Service
|
||||||
PreStarter
|
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
|
||||||
StoreSelected() bool
|
StoreSelected() bool
|
||||||
StoreFakeIP() bool
|
|
||||||
CacheFile() ClashCacheFile
|
CacheFile() ClashCacheFile
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
@@ -22,13 +19,8 @@ type ClashServer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ClashCacheFile interface {
|
type ClashCacheFile interface {
|
||||||
LoadMode() string
|
|
||||||
StoreMode(mode string) error
|
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
|
||||||
StoreGroupExpand(group string, expand bool) error
|
|
||||||
FakeIPStorage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
@@ -36,16 +28,10 @@ type Tracker interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OutboundGroup interface {
|
type OutboundGroup interface {
|
||||||
Outbound
|
|
||||||
Now() string
|
Now() string
|
||||||
All() []string
|
All() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type URLTestGroup interface {
|
|
||||||
OutboundGroup
|
|
||||||
URLTest(ctx context.Context, url string) (map[string]uint16, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func OutboundTag(detour Outbound) string {
|
func OutboundTag(detour Outbound) string {
|
||||||
if group, isGroup := detour.(OutboundGroup); isGroup {
|
if group, isGroup := detour.(OutboundGroup); isGroup {
|
||||||
return group.Now()
|
return group.Now()
|
||||||
@@ -62,3 +48,16 @@ type V2RayStatsService interface {
|
|||||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
||||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SSMServer interface {
|
||||||
|
Service
|
||||||
|
RoutedConnection(metadata InboundContext, conn net.Conn) net.Conn
|
||||||
|
RoutedPacketConnection(metadata InboundContext, conn N.PacketConn) N.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagedShadowsocksServer interface {
|
||||||
|
Inbound
|
||||||
|
Method() string
|
||||||
|
Password() string
|
||||||
|
UpdateUsers(users []string, uPSKs []string) error
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeIPStore interface {
|
|
||||||
Service
|
|
||||||
Contains(address netip.Addr) bool
|
|
||||||
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
|
||||||
Lookup(address netip.Addr) (string, bool)
|
|
||||||
Reset() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type FakeIPStorage interface {
|
|
||||||
FakeIPMetadata() *FakeIPMetadata
|
|
||||||
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
|
|
||||||
FakeIPSaveMetadataAsync(metadata *FakeIPMetadata)
|
|
||||||
FakeIPStore(address netip.Addr, domain string) error
|
|
||||||
FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger)
|
|
||||||
FakeIPLoad(address netip.Addr) (string, bool)
|
|
||||||
FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bool)
|
|
||||||
FakeIPReset() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type FakeIPTransport interface {
|
|
||||||
dns.Transport
|
|
||||||
Store() FakeIPStore
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeIPMetadata struct {
|
|
||||||
Inet4Range netip.Prefix
|
|
||||||
Inet6Range netip.Prefix
|
|
||||||
Inet4Current netip.Addr
|
|
||||||
Inet6Current netip.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
for _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {
|
|
||||||
data, err = marshaler.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
common.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))
|
|
||||||
buffer.Write(data)
|
|
||||||
}
|
|
||||||
data = buffer.Bytes()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {
|
|
||||||
reader := bytes.NewReader(data)
|
|
||||||
for _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {
|
|
||||||
var length uint16
|
|
||||||
common.Must(binary.Read(reader, binary.BigEndian, &length))
|
|
||||||
element := make([]byte, length)
|
|
||||||
_, err := io.ReadFull(reader, element)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = unmarshaler.UnmarshalBinary(element)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ type InjectableInbound interface {
|
|||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion uint8
|
IPVersion int
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
@@ -46,7 +46,6 @@ type InboundContext struct {
|
|||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *process.Info
|
ProcessInfo *process.Info
|
||||||
FakeIP bool
|
|
||||||
|
|
||||||
// dns cache
|
// dns cache
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ type Outbound interface {
|
|||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Network() []string
|
Network() []string
|
||||||
Dependencies() []string
|
|
||||||
N.Dialer
|
N.Dialer
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
type PreStarter interface {
|
|
||||||
PreStart() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostStarter interface {
|
|
||||||
PostStart() error
|
|
||||||
}
|
|
||||||
@@ -17,12 +17,11 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
Service
|
||||||
|
|
||||||
|
Inbound(tag string) (Inbound, bool)
|
||||||
Outbounds() []Outbound
|
Outbounds() []Outbound
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) Outbound
|
||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
|
||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
|
||||||
@@ -32,28 +31,24 @@ type Router interface {
|
|||||||
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
||||||
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||||
ClearDNSCache()
|
|
||||||
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
UpdateInterfaces() error
|
|
||||||
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
|
||||||
|
|
||||||
TimeService
|
|
||||||
|
|
||||||
ClashServer() ClashServer
|
ClashServer() ClashServer
|
||||||
SetClashServer(server ClashServer)
|
SetClashServer(server ClashServer)
|
||||||
|
|
||||||
V2RayServer() V2RayServer
|
V2RayServer() V2RayServer
|
||||||
SetV2RayServer(server V2RayServer)
|
SetV2RayServer(server V2RayServer)
|
||||||
|
|
||||||
ResetNetwork() error
|
SSMServer() SSMServer
|
||||||
|
SetSSMServer(server SSMServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
type routerContextKey struct{}
|
type routerContextKey struct{}
|
||||||
@@ -82,9 +77,8 @@ type Rule interface {
|
|||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
RewriteTTL() *uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
InterfaceUpdated()
|
InterfaceUpdated() error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type TimeService interface {
|
|
||||||
Service
|
|
||||||
TimeFunc() func() time.Time
|
|
||||||
}
|
|
||||||
340
box.go
340
box.go
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"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"
|
||||||
@@ -19,69 +18,94 @@ 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/service/pause"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
preServices map[string]adapter.Service
|
logFile *os.File
|
||||||
postServices map[string]adapter.Service
|
clashServer adapter.ClashServer
|
||||||
done chan struct{}
|
v2rayServer adapter.V2RayServer
|
||||||
|
ssmServer adapter.SSMServer
|
||||||
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||||
option.Options
|
|
||||||
Context context.Context
|
|
||||||
PlatformInterface platform.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(options Options) (*Box, error) {
|
|
||||||
ctx := options.Context
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
ctx = pause.ContextWithDefaultManager(ctx)
|
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
logOptions := common.PtrValueOrDefault(options.Log)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
if experimentalOptions.ClashAPI != nil || options.PlatformInterface != nil {
|
var needSSMAPI bool
|
||||||
needClashAPI = true
|
if options.Experimental != nil {
|
||||||
|
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
||||||
|
needClashAPI = true
|
||||||
|
}
|
||||||
|
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
|
||||||
|
needV2RayAPI = true
|
||||||
|
}
|
||||||
|
if options.Experimental.SSMAPI != nil && options.Experimental.SSMAPI.Listen != "" {
|
||||||
|
needSSMAPI = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
|
||||||
needV2RayAPI = true
|
var logFactory log.Factory
|
||||||
}
|
var observableLogFactory log.ObservableFactory
|
||||||
var defaultLogWriter io.Writer
|
var logFile *os.File
|
||||||
if options.PlatformInterface != nil {
|
if logOptions.Disabled {
|
||||||
defaultLogWriter = io.Discard
|
observableLogFactory = log.NewNOPFactory()
|
||||||
}
|
logFactory = observableLogFactory
|
||||||
logFactory, err := log.New(log.Options{
|
} else {
|
||||||
Context: ctx,
|
var logWriter io.Writer
|
||||||
Options: common.PtrValueOrDefault(options.Log),
|
switch logOptions.Output {
|
||||||
Observable: needClashAPI,
|
case "", "stderr":
|
||||||
DefaultWriter: defaultLogWriter,
|
logWriter = os.Stderr
|
||||||
BaseTime: createdAt,
|
case "stdout":
|
||||||
PlatformWriter: options.PlatformInterface,
|
logWriter = os.Stdout
|
||||||
})
|
default:
|
||||||
if err != nil {
|
var err error
|
||||||
return nil, E.Cause(err, "create log factory")
|
logFile, err = os.OpenFile(logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logWriter = logFile
|
||||||
|
}
|
||||||
|
logFormatter := log.Formatter{
|
||||||
|
BaseTime: createdAt,
|
||||||
|
DisableColors: logOptions.DisableColor || logFile != nil,
|
||||||
|
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
|
||||||
|
FullTimestamp: logOptions.Timestamp,
|
||||||
|
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||||
|
}
|
||||||
|
if needClashAPI {
|
||||||
|
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
|
||||||
|
logFactory = observableLogFactory
|
||||||
|
} else {
|
||||||
|
logFactory = log.NewFactory(logFormatter, logWriter)
|
||||||
|
}
|
||||||
|
if logOptions.Level != "" {
|
||||||
|
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse log level")
|
||||||
|
}
|
||||||
|
logFactory.SetLevel(logLevel)
|
||||||
|
} else {
|
||||||
|
logFactory.SetLevel(log.LevelTrace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router, err := route.NewRouter(
|
router, err := route.NewRouter(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory,
|
logFactory,
|
||||||
common.PtrValueOrDefault(options.Route),
|
common.PtrValueOrDefault(options.Route),
|
||||||
common.PtrValueOrDefault(options.DNS),
|
common.PtrValueOrDefault(options.DNS),
|
||||||
common.PtrValueOrDefault(options.NTP),
|
|
||||||
options.Inbounds,
|
options.Inbounds,
|
||||||
options.PlatformInterface,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse route options")
|
return nil, E.Cause(err, "parse route options")
|
||||||
@@ -101,7 +125,6 @@ func New(options Options) (*Box, error) {
|
|||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
inboundOptions,
|
inboundOptions,
|
||||||
options.PlatformInterface,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
@@ -120,7 +143,6 @@ func New(options Options) (*Box, error) {
|
|||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
|
||||||
outboundOptions)
|
outboundOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||||
@@ -128,7 +150,7 @@ func New(options Options) (*Box, error) {
|
|||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, out)
|
||||||
}
|
}
|
||||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
|
||||||
common.Must(oErr)
|
common.Must(oErr)
|
||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, out)
|
||||||
return out
|
return out
|
||||||
@@ -136,64 +158,46 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.PlatformInterface != nil {
|
|
||||||
err = options.PlatformInterface.Initialize(ctx, router)
|
var clashServer adapter.ClashServer
|
||||||
if err != nil {
|
var v2rayServer adapter.V2RayServer
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
var ssmServer adapter.SSMServer
|
||||||
}
|
|
||||||
}
|
|
||||||
preServices := make(map[string]adapter.Service)
|
|
||||||
postServices := make(map[string]adapter.Service)
|
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
||||||
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
|
||||||
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
|
|
||||||
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.SetClashServer(clashServer)
|
||||||
preServices["clash api"] = clashServer
|
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create v2ray api server")
|
return nil, E.Cause(err, "create v2ray api server")
|
||||||
}
|
}
|
||||||
router.SetV2RayServer(v2rayServer)
|
router.SetV2RayServer(v2rayServer)
|
||||||
preServices["v2ray api"] = v2rayServer
|
}
|
||||||
|
if needSSMAPI {
|
||||||
|
ssmServer, err = experimental.NewSSMServer(router, logFactory.NewLogger("ssm-api"), common.PtrValueOrDefault(options.Experimental.SSMAPI))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create ssm api server")
|
||||||
|
}
|
||||||
|
router.SetSSMServer(ssmServer)
|
||||||
}
|
}
|
||||||
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.Logger(),
|
||||||
preServices: preServices,
|
logFile: logFile,
|
||||||
postServices: postServices,
|
clashServer: clashServer,
|
||||||
done: make(chan struct{}),
|
v2rayServer: v2rayServer,
|
||||||
|
ssmServer: ssmServer,
|
||||||
|
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 {
|
||||||
@@ -207,74 +211,60 @@ 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 {
|
|
||||||
for serviceName, service := range s.preServices {
|
|
||||||
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
|
||||||
s.logger.Trace("pre-start ", serviceName)
|
|
||||||
err := preService.PreStart()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "pre-starting ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := s.startOutbounds()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.router.Start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) start() error {
|
func (s *Box) start() error {
|
||||||
err := s.preStart()
|
for i, out := range s.outbounds {
|
||||||
if err != nil {
|
if starter, isStarter := out.(common.Starter); isStarter {
|
||||||
return err
|
err := starter.Start()
|
||||||
}
|
|
||||||
for serviceName, service := range s.preServices {
|
|
||||||
s.logger.Trace("starting ", serviceName)
|
|
||||||
err = service.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, in := range s.inbounds {
|
|
||||||
var tag string
|
|
||||||
if in.Tag() == "" {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
tag = in.Tag()
|
|
||||||
}
|
|
||||||
s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
|
|
||||||
err = in.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) postStart() error {
|
|
||||||
for serviceName, service := range s.postServices {
|
|
||||||
s.logger.Trace("starting ", service)
|
|
||||||
err := service.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for serviceName, service := range s.outbounds {
|
|
||||||
if lateService, isLateService := service.(adapter.PostStarter); isLateService {
|
|
||||||
s.logger.Trace("post-starting ", service)
|
|
||||||
err := lateService.PostStart()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "post-start ", serviceName)
|
var tag string
|
||||||
|
if out.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = out.Tag()
|
||||||
|
}
|
||||||
|
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err := s.router.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, in := range s.inbounds {
|
||||||
|
err = in.Start()
|
||||||
|
if err != nil {
|
||||||
|
var tag string
|
||||||
|
if in.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = in.Tag()
|
||||||
|
}
|
||||||
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.clashServer != nil {
|
||||||
|
err = s.clashServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start clash api server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.v2rayServer != nil {
|
||||||
|
err = s.v2rayServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start v2ray api server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.ssmServer != nil {
|
||||||
|
err = s.ssmServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start ssm api server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,44 +275,20 @@ 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()
|
||||||
s.logger.Trace("closing ", serviceName)
|
|
||||||
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 {
|
||||||
s.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
|
common.Close(out)
|
||||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
return common.Close(
|
||||||
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
|
s.router,
|
||||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
s.logFactory,
|
||||||
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
|
s.clashServer,
|
||||||
})
|
s.v2rayServer,
|
||||||
}
|
s.ssmServer,
|
||||||
s.logger.Trace("closing router")
|
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 {
|
|
||||||
s.logger.Trace("closing ", serviceName)
|
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", serviceName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
s.logger.Trace("closing log factory")
|
|
||||||
if err := common.Close(s.logFactory); err != nil {
|
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close log factory")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Router() adapter.Router {
|
func (s *Box) Router() adapter.Router {
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package box
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Box) startOutbounds() error {
|
|
||||||
outboundTags := make(map[adapter.Outbound]string)
|
|
||||||
outbounds := make(map[string]adapter.Outbound)
|
|
||||||
for i, outboundToStart := range s.outbounds {
|
|
||||||
var outboundTag string
|
|
||||||
if outboundToStart.Tag() == "" {
|
|
||||||
outboundTag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
outboundTag = outboundToStart.Tag()
|
|
||||||
}
|
|
||||||
if _, exists := outbounds[outboundTag]; exists {
|
|
||||||
return E.New("outbound tag ", outboundTag, " duplicated")
|
|
||||||
}
|
|
||||||
outboundTags[outboundToStart] = outboundTag
|
|
||||||
outbounds[outboundTag] = outboundToStart
|
|
||||||
}
|
|
||||||
started := make(map[string]bool)
|
|
||||||
for {
|
|
||||||
canContinue := false
|
|
||||||
startOne:
|
|
||||||
for _, outboundToStart := range s.outbounds {
|
|
||||||
outboundTag := outboundTags[outboundToStart]
|
|
||||||
if started[outboundTag] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dependencies := outboundToStart.Dependencies()
|
|
||||||
for _, dependency := range dependencies {
|
|
||||||
if !started[dependency] {
|
|
||||||
continue startOne
|
|
||||||
}
|
|
||||||
}
|
|
||||||
started[outboundTag] = true
|
|
||||||
canContinue = true
|
|
||||||
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
|
|
||||||
s.logger.Trace("initializing outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
err := starter.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(started) == len(s.outbounds) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if canContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
|
|
||||||
return !started[outboundTags[it]]
|
|
||||||
})
|
|
||||||
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
|
||||||
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
|
||||||
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
|
||||||
return !started[it]
|
|
||||||
})
|
|
||||||
if common.Contains(oTree, problemOutboundTag) {
|
|
||||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
|
||||||
}
|
|
||||||
problemOutbound := outbounds[problemOutboundTag]
|
|
||||||
if problemOutbound == nil {
|
|
||||||
return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]")
|
|
||||||
}
|
|
||||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
|
||||||
}
|
|
||||||
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/build"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
build_shared.FindSDK()
|
findSDK()
|
||||||
|
|
||||||
if os.Getenv("build.Default.GOPATH") == "" {
|
|
||||||
os.Setenv("GOPATH", build.Default.GOPATH)
|
|
||||||
}
|
|
||||||
|
|
||||||
command := exec.Command(os.Args[1], os.Args[2:]...)
|
command := exec.Command(os.Args[1], os.Args[2:]...)
|
||||||
command.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package build_shared
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/build"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -19,7 +18,7 @@ var (
|
|||||||
androidNDKPath string
|
androidNDKPath string
|
||||||
)
|
)
|
||||||
|
|
||||||
func FindSDK() {
|
func findSDK() {
|
||||||
searchPath := []string{
|
searchPath := []string{
|
||||||
"$ANDROID_HOME",
|
"$ANDROID_HOME",
|
||||||
"$HOME/Android/Sdk",
|
"$HOME/Android/Sdk",
|
||||||
@@ -80,13 +79,3 @@ func findNDK() bool {
|
|||||||
}
|
}
|
||||||
return false
|
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,144 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
_ "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
|
|
||||||
sharedTags []string
|
|
||||||
iosTags []string
|
|
||||||
debugTags []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)
|
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
|
||||||
debugTags = append(debugTags, "debug")
|
|
||||||
}
|
|
||||||
|
|
||||||
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, strings.Join(sharedTags, ","))
|
|
||||||
} else {
|
|
||||||
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
|
|
||||||
}
|
|
||||||
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,tvos,tvossimulator,macos",
|
|
||||||
"-libname=box",
|
|
||||||
}
|
|
||||||
if !debugEnabled {
|
|
||||||
args = append(args, sharedFlags...)
|
|
||||||
} else {
|
|
||||||
args = append(args, debugFlags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := append(sharedTags, iosTags...)
|
|
||||||
args = append(args, "-tags")
|
|
||||||
if !debugEnabled {
|
|
||||||
args = append(args, strings.Join(tags, ","))
|
|
||||||
} else {
|
|
||||||
args = append(args, strings.Join(append(tags, debugTags...), ","))
|
|
||||||
}
|
|
||||||
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-apple")
|
|
||||||
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,23 +0,0 @@
|
|||||||
package build_shared
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/common/badversion"
|
|
||||||
"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()
|
|
||||||
version := badversion.Parse(currentTagRev[1:])
|
|
||||||
if version.PreReleaseIdentifier == "" {
|
|
||||||
version.Patch++
|
|
||||||
}
|
|
||||||
return version.String() + "-" + 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,18 +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(box.Options{
|
_, err = box.New(ctx, options)
|
||||||
Context: ctx,
|
|
||||||
Options: 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/v5"
|
|
||||||
"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,15 +5,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
runtimeDebug "runtime/debug"
|
runtimeDebug "runtime/debug"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
||||||
@@ -36,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
|
||||||
}
|
}
|
||||||
@@ -128,10 +64,7 @@ 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(box.Options{
|
instance, err := box.New(ctx, options)
|
||||||
Context: ctx,
|
|
||||||
Options: 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")
|
||||||
@@ -178,10 +111,7 @@ func run() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
closeCtx, closed := context.WithCancel(context.Background())
|
|
||||||
go closeMonitor(closeCtx)
|
|
||||||
instance.Close()
|
instance.Close()
|
||||||
closed()
|
|
||||||
if osSignal != syscall.SIGHUP {
|
if osSignal != syscall.SIGHUP {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -189,13 +119,3 @@ func run() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeMonitor(ctx context.Context) {
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
log.Fatal("sing-box did not close!")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"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(box.Options{Options: options})
|
|
||||||
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
|
|
||||||
}
|
|
||||||
44
cmd/sing-box/debug.go
Normal file
44
cmd/sing-box/debug.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//go:build debug
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/badjson"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.HandleFunc("/debug/gc", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusNoContent)
|
||||||
|
go debug.FreeOSMemory()
|
||||||
|
})
|
||||||
|
http.HandleFunc("/debug/memory", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var memStats runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
|
var memObject badjson.JSONObject
|
||||||
|
memObject.Put("heap", humanize.Bytes(memStats.HeapInuse))
|
||||||
|
memObject.Put("stack", humanize.Bytes(memStats.StackInuse))
|
||||||
|
memObject.Put("idle", humanize.Bytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(writer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
encoder.Encode(memObject)
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
err := http.ListenAndServe("0.0.0.0:8964", nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
package box
|
//go:build debug
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//go:build !linux
|
//go:build debug && !linux
|
||||||
|
|
||||||
package box
|
package main
|
||||||
|
|
||||||
func rusageMaxRSS() float64 {
|
func rusageMaxRSS() float64 {
|
||||||
return -1
|
return -1
|
||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "github.com/sagernet/sing-box/include"
|
_ "github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -11,10 +10,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
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 +21,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 +33,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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func WrapQUIC(err error) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if Contains(err, "canceled by local with error code 0") {
|
if Contains(err, "canceled with error code 0") {
|
||||||
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: []string{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: []string{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,4 +1,4 @@
|
|||||||
//go:build go1.20 && !go1.21
|
//go:build go1.19 && !go1.20
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
@@ -14,60 +14,39 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
*tls.Conn
|
*tls.Conn
|
||||||
writer N.ExtendedWriter
|
writer N.ExtendedWriter
|
||||||
isHandshakeComplete *atomic.Bool
|
activeCall *int32
|
||||||
activeCall *atomic.Int32
|
closeNotifySent *bool
|
||||||
closeNotifySent *bool
|
version *uint16
|
||||||
version *uint16
|
rand io.Reader
|
||||||
rand io.Reader
|
halfAccess *sync.Mutex
|
||||||
halfAccess *sync.Mutex
|
halfError *error
|
||||||
halfError *error
|
cipher cipher.AEAD
|
||||||
cipher cipher.AEAD
|
explicitNonceLen int
|
||||||
explicitNonceLen int
|
halfPtr uintptr
|
||||||
halfPtr uintptr
|
halfSeq []byte
|
||||||
halfSeq []byte
|
halfScratchBuf []byte
|
||||||
halfScratchBuf []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TryCreate(conn aTLS.Conn) aTLS.Conn {
|
func Create(conn *tls.Conn) (TLSConn, error) {
|
||||||
tlsConn, ok := conn.(*tls.Conn)
|
if !handshakeComplete(conn) {
|
||||||
if !ok {
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
badConn, err := Create(tlsConn)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("initialize badtls: ", err)
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
return badConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func Create(conn *tls.Conn) (aTLS.Conn, error) {
|
|
||||||
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
|
||||||
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
|
|
||||||
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid isHandshakeComplete")
|
|
||||||
}
|
|
||||||
isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
|
|
||||||
if !isHandshakeComplete.Load() {
|
|
||||||
return nil, E.New("handshake not finished")
|
return nil, E.New("handshake not finished")
|
||||||
}
|
}
|
||||||
|
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||||
rawActiveCall := rawConn.FieldByName("activeCall")
|
rawActiveCall := rawConn.FieldByName("activeCall")
|
||||||
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
|
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 {
|
||||||
return nil, E.New("badtls: invalid active call")
|
return nil, E.New("badtls: invalid active call")
|
||||||
}
|
}
|
||||||
activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
||||||
rawHalfConn := rawConn.FieldByName("out")
|
rawHalfConn := rawConn.FieldByName("out")
|
||||||
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||||
return nil, E.New("badtls: invalid half conn")
|
return nil, E.New("badtls: invalid half conn")
|
||||||
@@ -129,20 +108,19 @@ func Create(conn *tls.Conn) (aTLS.Conn, error) {
|
|||||||
}
|
}
|
||||||
halfScratchBuf := rawHalfScratchBuf.Bytes()
|
halfScratchBuf := rawHalfScratchBuf.Bytes()
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
||||||
isHandshakeComplete: isHandshakeComplete,
|
activeCall: activeCall,
|
||||||
activeCall: activeCall,
|
closeNotifySent: closeNotifySent,
|
||||||
closeNotifySent: closeNotifySent,
|
version: version,
|
||||||
version: version,
|
halfAccess: halfAccess,
|
||||||
halfAccess: halfAccess,
|
halfError: halfError,
|
||||||
halfError: halfError,
|
cipher: aeadCipher,
|
||||||
cipher: aeadCipher,
|
explicitNonceLen: explicitNonceLen,
|
||||||
explicitNonceLen: explicitNonceLen,
|
rand: randReader,
|
||||||
rand: randReader,
|
halfPtr: rawHalfConn.UnsafeAddr(),
|
||||||
halfPtr: rawHalfConn.UnsafeAddr(),
|
halfSeq: halfSeq,
|
||||||
halfSeq: halfSeq,
|
halfScratchBuf: halfScratchBuf,
|
||||||
halfScratchBuf: halfScratchBuf,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,15 +130,15 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
return common.Error(c.Write(buffer.Bytes()))
|
return common.Error(c.Write(buffer.Bytes()))
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
x := c.activeCall.Load()
|
x := atomic.LoadInt32(c.activeCall)
|
||||||
if x&1 != 0 {
|
if x&1 != 0 {
|
||||||
return net.ErrClosed
|
return net.ErrClosed
|
||||||
}
|
}
|
||||||
if c.activeCall.CompareAndSwap(x, x+2) {
|
if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer c.activeCall.Add(-2)
|
defer atomic.AddInt32(c.activeCall, -2)
|
||||||
c.halfAccess.Lock()
|
c.halfAccess.Lock()
|
||||||
defer c.halfAccess.Unlock()
|
defer c.halfAccess.Unlock()
|
||||||
if err := *c.halfError; err != nil {
|
if err := *c.halfError; err != nil {
|
||||||
@@ -208,7 +186,6 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
|
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
|
||||||
}
|
}
|
||||||
incSeq(c.halfPtr)
|
incSeq(c.halfPtr)
|
||||||
log.Trace("badtls write ", buffer.Len())
|
|
||||||
return c.writer.WriteBuffer(buffer)
|
return c.writer.WriteBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
//go:build !go1.19 || go1.21
|
//go:build !go1.19 || go1.20
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Create(conn *tls.Conn) (aTLS.Conn, error) {
|
func Create(conn *tls.Conn) (TLSConn, error) {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|||||||
13
common/badtls/conn.go
Normal file
13
common/badtls/conn.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TLSConn interface {
|
||||||
|
net.Conn
|
||||||
|
HandshakeContext(ctx context.Context) error
|
||||||
|
ConnectionState() tls.ConnectionState
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
//go:build go1.20 && !go.1.21
|
//go:build go1.19 && !go.1.20
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"reflect"
|
"reflect"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
@@ -15,6 +16,9 @@ const (
|
|||||||
//go:linkname errShutdown crypto/tls.errShutdown
|
//go:linkname errShutdown crypto/tls.errShutdown
|
||||||
var errShutdown error
|
var errShutdown error
|
||||||
|
|
||||||
|
//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
|
||||||
|
func handshakeComplete(conn *tls.Conn) bool
|
||||||
|
|
||||||
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
|
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
|
||||||
func incSeq(conn uintptr)
|
func incSeq(conn uintptr)
|
||||||
|
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Version struct {
|
|
||||||
Major int
|
|
||||||
Minor int
|
|
||||||
Patch int
|
|
||||||
Commit string
|
|
||||||
PreReleaseIdentifier string
|
|
||||||
PreReleaseVersion int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) After(anotherVersion Version) bool {
|
|
||||||
if v.Major > anotherVersion.Major {
|
|
||||||
return true
|
|
||||||
} else if v.Major < anotherVersion.Major {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.Minor > anotherVersion.Minor {
|
|
||||||
return true
|
|
||||||
} else if v.Minor < anotherVersion.Minor {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.Patch > anotherVersion.Patch {
|
|
||||||
return true
|
|
||||||
} else if v.Patch < anotherVersion.Patch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier == "" && anotherVersion.PreReleaseIdentifier != "" {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" {
|
|
||||||
if v.PreReleaseIdentifier == anotherVersion.PreReleaseIdentifier {
|
|
||||||
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" {
|
|
||||||
return false
|
|
||||||
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) String() string {
|
|
||||||
version := F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
|
||||||
if v.PreReleaseIdentifier != "" {
|
|
||||||
version = F.ToString(version, "-", v.PreReleaseIdentifier, ".", v.PreReleaseVersion)
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) BadString() string {
|
|
||||||
version := F.ToString(v.Major, ".", v.Minor)
|
|
||||||
if v.Patch > 0 {
|
|
||||||
version = F.ToString(version, ".", v.Patch)
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier != "" {
|
|
||||||
version = F.ToString(version, "-", v.PreReleaseIdentifier)
|
|
||||||
if v.PreReleaseVersion > 0 {
|
|
||||||
version = F.ToString(version, v.PreReleaseVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(versionName string) (version Version) {
|
|
||||||
if strings.HasPrefix(versionName, "v") {
|
|
||||||
versionName = versionName[1:]
|
|
||||||
}
|
|
||||||
if strings.Contains(versionName, "-") {
|
|
||||||
parts := strings.Split(versionName, "-")
|
|
||||||
versionName = parts[0]
|
|
||||||
identifier := parts[1]
|
|
||||||
if strings.Contains(identifier, ".") {
|
|
||||||
identifierParts := strings.Split(identifier, ".")
|
|
||||||
version.PreReleaseIdentifier = identifierParts[0]
|
|
||||||
if len(identifierParts) >= 2 {
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifierParts[1])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if strings.HasPrefix(identifier, "alpha") {
|
|
||||||
version.PreReleaseIdentifier = "alpha"
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifier[5:])
|
|
||||||
} else if strings.HasPrefix(identifier, "beta") {
|
|
||||||
version.PreReleaseIdentifier = "beta"
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])
|
|
||||||
} else {
|
|
||||||
version.Commit = identifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
versionElements := strings.Split(versionName, ".")
|
|
||||||
versionLen := len(versionElements)
|
|
||||||
if versionLen >= 1 {
|
|
||||||
version.Major, _ = strconv.Atoi(versionElements[0])
|
|
||||||
}
|
|
||||||
if versionLen >= 2 {
|
|
||||||
version.Minor, _ = strconv.Atoi(versionElements[1])
|
|
||||||
}
|
|
||||||
if versionLen >= 3 {
|
|
||||||
version.Patch, _ = strconv.Atoi(versionElements[2])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import "github.com/sagernet/sing-box/common/json"
|
|
||||||
|
|
||||||
func (v Version) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(v.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Version) UnmarshalJSON(data []byte) error {
|
|
||||||
var version string
|
|
||||||
err := json.Unmarshal(data, &version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = Parse(version)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCompareVersion(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
|
||||||
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
|
||||||
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
|
|
||||||
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
|
|
||||||
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
|
|
||||||
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
|
|
||||||
require.True(t, Parse("1.4").After(Parse("1.3")))
|
|
||||||
}
|
|
||||||
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
|
||||||
|
}
|
||||||
87
common/debugio/log.go
Normal file
87
common/debugio/log.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package debugio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
logger log.Logger
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogConn(conn net.Conn, logger log.Logger, prefix string) N.ExtendedConn {
|
||||||
|
return &LogConn{bufio.NewExtendedConn(conn), logger, prefix}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogConn) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = c.ExtendedConn.Read(p)
|
||||||
|
if n > 0 {
|
||||||
|
c.logger.Debug(c.prefix, " read ", buf.EncodeHexString(p[:n]))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogConn) Write(p []byte) (n int, err error) {
|
||||||
|
c.logger.Debug(c.prefix, " write ", buf.EncodeHexString(p))
|
||||||
|
return c.ExtendedConn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogConn) ReadBuffer(buffer *buf.Buffer) error {
|
||||||
|
err := c.ExtendedConn.ReadBuffer(buffer)
|
||||||
|
if err == nil {
|
||||||
|
c.logger.Debug(c.prefix, " read buffer ", buf.EncodeHexString(buffer.Bytes()))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
c.logger.Debug(c.prefix, " write buffer ", buf.EncodeHexString(buffer.Bytes()))
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogPacketConn struct {
|
||||||
|
N.NetPacketConn
|
||||||
|
logger log.Logger
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogPacketConn(conn net.PacketConn, logger log.Logger, prefix string) N.NetPacketConn {
|
||||||
|
return &LogPacketConn{bufio.NewPacketConn(conn), logger, prefix}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
n, addr, err = c.NetPacketConn.ReadFrom(p)
|
||||||
|
if n > 0 {
|
||||||
|
c.logger.Debug(c.prefix, " read from ", addr, " ", buf.EncodeHexString(p[:n]))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
c.logger.Debug(c.prefix, " write to ", addr, " ", buf.EncodeHexString(p))
|
||||||
|
return c.NetPacketConn.WriteTo(p, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
destination, err = c.NetPacketConn.ReadPacket(buffer)
|
||||||
|
if err == nil {
|
||||||
|
c.logger.Debug(c.prefix, " read packet from ", destination, " ", buf.EncodeHexString(buffer.Bytes()))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
c.logger.Debug(c.prefix, " write packet to ", destination, " ", buf.EncodeHexString(buffer.Bytes()))
|
||||||
|
return c.NetPacketConn.WritePacket(buffer, destination)
|
||||||
|
}
|
||||||
19
common/debugio/print.go
Normal file
19
common/debugio/print.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package debugio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrintUpstream(obj any) {
|
||||||
|
for obj != nil {
|
||||||
|
fmt.Println(reflect.TypeOf(obj))
|
||||||
|
if u, ok := obj.(common.WithUpstream); !ok {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
obj = u.Upstream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
common/debugio/race.go
Normal file
48
common/debugio/race.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package debugio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RaceConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
readAccess sync.Mutex
|
||||||
|
writeAccess sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRaceConn(conn net.Conn) N.ExtendedConn {
|
||||||
|
return &RaceConn{ExtendedConn: bufio.NewExtendedConn(conn)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RaceConn) Read(p []byte) (n int, err error) {
|
||||||
|
c.readAccess.Lock()
|
||||||
|
defer c.readAccess.Unlock()
|
||||||
|
return c.ExtendedConn.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RaceConn) Write(p []byte) (n int, err error) {
|
||||||
|
c.writeAccess.Lock()
|
||||||
|
defer c.writeAccess.Unlock()
|
||||||
|
return c.ExtendedConn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RaceConn) ReadBuffer(buffer *buf.Buffer) error {
|
||||||
|
c.readAccess.Lock()
|
||||||
|
defer c.readAccess.Unlock()
|
||||||
|
return c.ExtendedConn.ReadBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RaceConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
c.writeAccess.Lock()
|
||||||
|
defer c.writeAccess.Unlock()
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RaceConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
@@ -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) (net.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,55 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PacketConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
element *list.Element[io.Closer]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPacketConn(conn net.PacketConn) (net.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 bufio.NewPacketConn(c.PacketConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,47 +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 {
|
|
||||||
if !Enabled {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return openConnection.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func List() []io.Closer {
|
|
||||||
if !Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
if !Enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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
|
|
||||||
@@ -6,18 +6,54 @@ import (
|
|||||||
"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"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !(C.IsLinux || C.IsWindows || C.IsDarwin)
|
||||||
|
},
|
||||||
|
"outbound option `bind_interface` is only supported on Linux and Windows",
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnRoutingMarkOnUnsupportedPlatform = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !C.IsLinux
|
||||||
|
},
|
||||||
|
"outbound option `routing_mark` is only supported on Linux",
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnReuseAdderOnUnsupportedPlatform = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !(C.IsDarwin || C.IsDragonfly || C.IsFreebsd || C.IsLinux || C.IsNetbsd || C.IsOpenbsd || C.IsSolaris || C.IsWindows)
|
||||||
|
},
|
||||||
|
"outbound option `reuse_addr` is unsupported on current platform",
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnProtectPathOnNonAndroid = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !C.IsAndroid
|
||||||
|
},
|
||||||
|
"outbound option `protect_path` is only supported on Android",
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnTFOOnUnsupportedPlatform = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !(C.IsDarwin || C.IsFreebsd || C.IsLinux || C.IsWindows)
|
||||||
|
},
|
||||||
|
"outbound option `tcp_fast_open` is unsupported on current platform",
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
dialer4 tcpDialer
|
dialer4 tfo.Dialer
|
||||||
dialer6 tcpDialer
|
dialer6 tfo.Dialer
|
||||||
udpDialer4 net.Dialer
|
udpDialer4 net.Dialer
|
||||||
udpDialer6 net.Dialer
|
udpDialer6 net.Dialer
|
||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
@@ -25,15 +61,24 @@ type DefaultDialer struct {
|
|||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
|
||||||
var dialer net.Dialer
|
var dialer net.Dialer
|
||||||
var listener net.ListenConfig
|
var listener net.ListenConfig
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
|
warnBindInterfaceOnUnsupportedPlatform.Check()
|
||||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if router.AutoDetectInterface() {
|
} else if router.AutoDetectInterface() {
|
||||||
bindFunc := router.AutoDetectInterfaceFunc()
|
const useInterfaceName = C.IsLinux
|
||||||
|
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
|
||||||
|
remoteAddr := M.ParseSocksaddr(address).Addr
|
||||||
|
if C.IsLinux {
|
||||||
|
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
|
||||||
|
} else {
|
||||||
|
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
|
||||||
|
}
|
||||||
|
})
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if router.DefaultInterface() != "" {
|
} else if router.DefaultInterface() != "" {
|
||||||
@@ -42,6 +87,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark != 0 {
|
if options.RoutingMark != 0 {
|
||||||
|
warnRoutingMarkOnUnsupportedPlatform.Check()
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||||
} else if router.DefaultMark() != 0 {
|
} else if router.DefaultMark() != 0 {
|
||||||
@@ -49,9 +95,11 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
|
warnReuseAdderOnUnsupportedPlatform.Check()
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
if options.ProtectPath != "" {
|
if options.ProtectPath != "" {
|
||||||
|
warnProtectPathOnNonAndroid.Check()
|
||||||
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
||||||
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
||||||
}
|
}
|
||||||
@@ -60,6 +108,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPTimeout
|
dialer.Timeout = C.TCPTimeout
|
||||||
}
|
}
|
||||||
|
if options.TCPFastOpen {
|
||||||
|
warnTFOOnUnsupportedPlatform.Check()
|
||||||
|
}
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
udpFragment = *options.UDPFragment
|
udpFragment = *options.UDPFragment
|
||||||
@@ -92,29 +143,15 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
}
|
}
|
||||||
if options.TCPMultiPath {
|
|
||||||
if !go121Available {
|
|
||||||
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
|
|
||||||
}
|
|
||||||
setMultiPathTCP(&dialer4)
|
|
||||||
}
|
|
||||||
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &DefaultDialer{
|
return &DefaultDialer{
|
||||||
tcpDialer4,
|
tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen},
|
||||||
tcpDialer6,
|
tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen},
|
||||||
udpDialer4,
|
udpDialer4,
|
||||||
udpDialer6,
|
udpDialer6,
|
||||||
listener,
|
listener,
|
||||||
udpAddr4,
|
udpAddr4,
|
||||||
udpAddr6,
|
udpAddr6,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -124,36 +161,22 @@ 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() {
|
if !address.IsIPv6() {
|
||||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
return d.udpDialer4.DialContext(ctx, network, address.String())
|
||||||
} else {
|
} else {
|
||||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
return d.udpDialer6.DialContext(ctx, network, address.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
return DialSlowContext(&d.dialer4, ctx, network, address)
|
||||||
} else {
|
} else {
|
||||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
return DialSlowContext(&d.dialer6, ctx, network, address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if !destination.IsIPv6() {
|
if !destination.IsIPv6() {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||||
} else {
|
} else {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
return 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,15 +0,0 @@
|
|||||||
//go:build go1.20
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/tfo-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpDialer = tfo.Dialer
|
|
||||||
|
|
||||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
|
||||||
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build go1.21
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
const go121Available = true
|
|
||||||
|
|
||||||
func setMultiPathTCP(dialer *net.Dialer) {
|
|
||||||
dialer.SetMultipathTCP(true)
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//go:build !go1.20
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpDialer = net.Dialer
|
|
||||||
|
|
||||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
|
||||||
if tfoEnabled {
|
|
||||||
return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
|
|
||||||
}
|
|
||||||
return dialer, nil
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !go1.21
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
const go121Available = false
|
|
||||||
|
|
||||||
func setMultiPathTCP(dialer *net.Dialer) {
|
|
||||||
}
|
|
||||||
@@ -6,24 +6,13 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MustNew(router adapter.Router, options option.DialerOptions) N.Dialer {
|
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
|
||||||
return common.Must1(New(router, options))
|
var dialer N.Dialer
|
||||||
}
|
|
||||||
|
|
||||||
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
|
|
||||||
var (
|
|
||||||
dialer N.Dialer
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if options.Detour == "" {
|
if options.Detour == "" {
|
||||||
dialer, err = NewDefault(router, options)
|
dialer = NewDefault(router, options)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
dialer = NewDetour(router, options.Detour)
|
dialer = NewDetour(router, options.Detour)
|
||||||
}
|
}
|
||||||
@@ -31,5 +20,5 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
|
|||||||
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
||||||
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
|
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
|
||||||
}
|
}
|
||||||
return dialer, nil
|
return dialer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
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"
|
||||||
)
|
)
|
||||||
@@ -69,11 +68,11 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
conn, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
return NewResolvePacketConn(ctx, d.router, d.strategy, conn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResolveDialer) Upstream() any {
|
func (d *ResolveDialer) Upstream() any {
|
||||||
|
|||||||
84
common/dialer/resolve_conn.go
Normal file
84
common/dialer/resolve_conn.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewResolvePacketConn(ctx context.Context, router adapter.Router, strategy dns.DomainStrategy, conn net.PacketConn) N.NetPacketConn {
|
||||||
|
if udpConn, ok := conn.(*net.UDPConn); ok {
|
||||||
|
return &ResolveUDPConn{udpConn, ctx, router, strategy}
|
||||||
|
} else {
|
||||||
|
return &ResolvePacketConn{conn, ctx, router, strategy}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolveUDPConn struct {
|
||||||
|
*net.UDPConn
|
||||||
|
ctx context.Context
|
||||||
|
router adapter.Router
|
||||||
|
strategy dns.DomainStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolveUDPConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||||
|
n, addr, err := w.ReadFromUDPAddrPort(buffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
return M.Socksaddr{}, err
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
return M.SocksaddrFromNetIP(addr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolveUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
if destination.IsFqdn() {
|
||||||
|
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).AddrPort()))
|
||||||
|
}
|
||||||
|
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolveUDPConn) Upstream() any {
|
||||||
|
return w.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolvePacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
ctx context.Context
|
||||||
|
router adapter.Router
|
||||||
|
strategy dns.DomainStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolvePacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||||
|
_, addr, err := buffer.ReadPacketFrom(w)
|
||||||
|
if err != nil {
|
||||||
|
return M.Socksaddr{}, err
|
||||||
|
}
|
||||||
|
return M.SocksaddrFromNet(addr), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolvePacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
if destination.IsFqdn() {
|
||||||
|
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return common.Error(w.WriteTo(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).UDPAddr()))
|
||||||
|
}
|
||||||
|
return common.Error(w.WriteTo(buffer.Bytes(), destination.UDPAddr()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolvePacketConn) Upstream() any {
|
||||||
|
return w.PacketConn
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build go1.20
|
|
||||||
|
|
||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -27,14 +25,9 @@ type slowOpenConn struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, 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 {
|
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
||||||
switch N.NetworkName(network) {
|
return dialer.DialContext(ctx, network, destination.String(), nil)
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
|
||||||
return dialer.Dialer.DialContext(ctx, network, destination.String())
|
|
||||||
default:
|
|
||||||
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &slowOpenConn{
|
return &slowOpenConn{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
@@ -63,7 +56,6 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
if c.conn == nil {
|
if c.conn == nil {
|
||||||
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.conn = nil
|
|
||||||
c.err = E.Cause(err, "dial tcp fast open")
|
c.err = E.Cause(err, "dial tcp fast open")
|
||||||
}
|
}
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@@ -127,8 +119,11 @@ func (c *slowOpenConn) LazyHeadroom() bool {
|
|||||||
return c.conn == nil
|
return c.conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) NeedHandshake() bool {
|
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
return c.conn == nil
|
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) {
|
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
//go:build !go1.20
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
|
||||||
return dialer.DialContext(ctx, network, destination.String())
|
|
||||||
default:
|
|
||||||
return dialer.DialContext(ctx, network, destination.AddrString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,519 @@
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-mux"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
|
var _ N.Dialer = (*Client)(nil)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
access sync.Mutex
|
||||||
|
connections list.List[abstractSession]
|
||||||
|
ctx context.Context
|
||||||
|
dialer N.Dialer
|
||||||
|
protocol Protocol
|
||||||
|
maxConnections int
|
||||||
|
minStreams int
|
||||||
|
maxStreams int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConnections int, minStreams int, maxStreams int) *Client {
|
||||||
|
return &Client{
|
||||||
|
ctx: ctx,
|
||||||
|
dialer: dialer,
|
||||||
|
protocol: protocol,
|
||||||
|
maxConnections: maxConnections,
|
||||||
|
minStreams: minStreams,
|
||||||
|
maxStreams: maxStreams,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (N.Dialer, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return mux.NewClient(mux.Options{
|
if options.MaxConnections == 0 && options.MaxStreams == 0 {
|
||||||
Dialer: dialer,
|
options.MinStreams = 8
|
||||||
Protocol: options.Protocol,
|
}
|
||||||
MaxConnections: options.MaxConnections,
|
protocol, err := ParseProtocol(options.Protocol)
|
||||||
MinStreams: options.MinStreams,
|
if err != nil {
|
||||||
MaxStreams: options.MaxStreams,
|
return nil, err
|
||||||
Padding: options.Padding,
|
}
|
||||||
})
|
return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
stream, err := c.openStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ClientConn{Conn: stream, destination: destination}, nil
|
||||||
|
case N.NetworkUDP:
|
||||||
|
stream, err := c.openStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
|
||||||
|
default:
|
||||||
|
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
stream, err := c.openStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) openStream() (net.Conn, error) {
|
||||||
|
var (
|
||||||
|
session abstractSession
|
||||||
|
stream net.Conn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for attempts := 0; attempts < 2; attempts++ {
|
||||||
|
session, err = c.offer()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stream, err = session.Open()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &wrapStream{stream}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) offer() (abstractSession, error) {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
|
sessions := make([]abstractSession, 0, c.maxConnections)
|
||||||
|
for element := c.connections.Front(); element != nil; {
|
||||||
|
if element.Value.IsClosed() {
|
||||||
|
nextElement := element.Next()
|
||||||
|
c.connections.Remove(element)
|
||||||
|
element = nextElement
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sessions = append(sessions, element.Value)
|
||||||
|
element = element.Next()
|
||||||
|
}
|
||||||
|
sLen := len(sessions)
|
||||||
|
if sLen == 0 {
|
||||||
|
return c.offerNew()
|
||||||
|
}
|
||||||
|
session := common.MinBy(sessions, abstractSession.NumStreams)
|
||||||
|
numStreams := session.NumStreams()
|
||||||
|
if numStreams == 0 {
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
if c.maxConnections > 0 {
|
||||||
|
if sLen >= c.maxConnections || numStreams < c.minStreams {
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.maxStreams > 0 && numStreams < c.maxStreams {
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.offerNew()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) offerNew() (abstractSession, error) {
|
||||||
|
conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if vectorisedWriter, isVectorised := bufio.CreateVectorisedWriter(conn); isVectorised {
|
||||||
|
conn = &vectorisedProtocolConn{protocolConn{Conn: conn, protocol: c.protocol}, vectorisedWriter}
|
||||||
|
} else {
|
||||||
|
conn = &protocolConn{Conn: conn, protocol: c.protocol}
|
||||||
|
}
|
||||||
|
session, err := c.protocol.newClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.connections.PushBack(session)
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
for _, session := range c.connections.Array() {
|
||||||
|
session.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConn struct {
|
||||||
|
net.Conn
|
||||||
|
destination M.Socksaddr
|
||||||
|
requestWrite bool
|
||||||
|
responseRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) readResponse() error {
|
||||||
|
response, err := ReadStreamResponse(c.Conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Status == statusError {
|
||||||
|
return E.New("remote error: ", response.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Read(b []byte) (n int, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
return c.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Write(b []byte) (n int, err error) {
|
||||||
|
if c.requestWrite {
|
||||||
|
return c.Conn.Write(b)
|
||||||
|
}
|
||||||
|
request := StreamRequest{
|
||||||
|
Network: N.NetworkTCP,
|
||||||
|
Destination: c.destination,
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(requestLen(request) + len(b))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeStreamRequest(request, buffer)
|
||||||
|
buffer.Write(b)
|
||||||
|
_, err = c.Conn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.requestWrite = true
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
if !c.requestWrite {
|
||||||
|
return bufio.ReadFrom0(c, r)
|
||||||
|
}
|
||||||
|
return bufio.Copy(c.Conn, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
return bufio.WriteTo0(c, w)
|
||||||
|
}
|
||||||
|
return bufio.Copy(w, c.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) LocalAddr() net.Addr {
|
||||||
|
return c.Conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) RemoteAddr() net.Addr {
|
||||||
|
return c.destination.TCPAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) ReaderReplaceable() bool {
|
||||||
|
return c.responseRead
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) WriterReplaceable() bool {
|
||||||
|
return c.requestWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientPacketConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
destination M.Socksaddr
|
||||||
|
requestWrite bool
|
||||||
|
responseRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) readResponse() error {
|
||||||
|
response, err := ReadStreamResponse(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Status == statusError {
|
||||||
|
return E.New("remote error: ", response.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) Read(b []byte) (n int, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(b) < int(length) {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
return io.ReadFull(c.ExtendedConn, b[:length])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) {
|
||||||
|
request := StreamRequest{
|
||||||
|
Network: N.NetworkUDP,
|
||||||
|
Destination: c.destination,
|
||||||
|
}
|
||||||
|
rLen := requestLen(request)
|
||||||
|
if len(payload) > 0 {
|
||||||
|
rLen += 2 + len(payload)
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(rLen)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeStreamRequest(request, buffer)
|
||||||
|
if len(payload) > 0 {
|
||||||
|
common.Must(
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
||||||
|
common.Error(buffer.Write(payload)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.requestWrite = true
|
||||||
|
return len(payload), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
|
||||||
|
if !c.requestWrite {
|
||||||
|
return c.writeRequest(b)
|
||||||
|
}
|
||||||
|
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(b)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
if !c.requestWrite {
|
||||||
|
defer buffer.Release()
|
||||||
|
return common.Error(c.writeRequest(buffer.Bytes()))
|
||||||
|
}
|
||||||
|
bLen := buffer.Len()
|
||||||
|
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(bLen))
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) FrontHeadroom() int {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
err = c.ReadBuffer(buffer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
return c.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) LocalAddr() net.Addr {
|
||||||
|
return c.ExtendedConn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) RemoteAddr() net.Addr {
|
||||||
|
return c.destination.UDPAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ N.NetPacketConn = (*ClientPacketAddrConn)(nil)
|
||||||
|
|
||||||
|
type ClientPacketAddrConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
destination M.Socksaddr
|
||||||
|
requestWrite bool
|
||||||
|
responseRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) readResponse() error {
|
||||||
|
response, err := ReadStreamResponse(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Status == statusError {
|
||||||
|
return E.New("remote error: ", response.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
destination, err := M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr = destination.UDPAddr()
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(p) < int(length) {
|
||||||
|
return 0, nil, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
n, err = io.ReadFull(c.ExtendedConn, p[:length])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksaddr) (n int, err error) {
|
||||||
|
request := StreamRequest{
|
||||||
|
Network: N.NetworkUDP,
|
||||||
|
Destination: c.destination,
|
||||||
|
PacketAddr: true,
|
||||||
|
}
|
||||||
|
rLen := requestLen(request)
|
||||||
|
if len(payload) > 0 {
|
||||||
|
rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(rLen)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeStreamRequest(request, buffer)
|
||||||
|
if len(payload) > 0 {
|
||||||
|
common.Must(
|
||||||
|
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
||||||
|
common.Error(buffer.Write(payload)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.requestWrite = true
|
||||||
|
return len(payload), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
if !c.requestWrite {
|
||||||
|
return c.writeRequest(p, M.SocksaddrFromNet(addr))
|
||||||
|
}
|
||||||
|
err = M.SocksaddrSerializer.WriteAddrPort(c.ExtendedConn, M.SocksaddrFromNet(addr))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
if !c.requestWrite {
|
||||||
|
defer buffer.Release()
|
||||||
|
return common.Error(c.writeRequest(buffer.Bytes(), destination))
|
||||||
|
}
|
||||||
|
bLen := buffer.Len()
|
||||||
|
header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 2))
|
||||||
|
common.Must(
|
||||||
|
M.SocksaddrSerializer.WriteAddrPort(header, destination),
|
||||||
|
binary.Write(header, binary.BigEndian, uint16(bLen)),
|
||||||
|
)
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) LocalAddr() net.Addr {
|
||||||
|
return c.ExtendedConn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) FrontHeadroom() int {
|
||||||
|
return 2 + M.MaxSocksaddrLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,240 @@
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-mux"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/smux"
|
||||||
|
|
||||||
|
"github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
var Destination = M.Socksaddr{
|
||||||
Client = mux.Client
|
Fqdn: "sp.mux.sing-box.arpa",
|
||||||
|
Port: 444,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtocolSMux Protocol = iota
|
||||||
|
ProtocolYAMux
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Protocol byte
|
||||||
Destination = mux.Destination
|
|
||||||
HandleConnection = mux.HandleConnection
|
func ParseProtocol(name string) (Protocol, error) {
|
||||||
|
switch name {
|
||||||
|
case "", "smux":
|
||||||
|
return ProtocolSMux, nil
|
||||||
|
case "yamux":
|
||||||
|
return ProtocolYAMux, nil
|
||||||
|
default:
|
||||||
|
return ProtocolYAMux, E.New("unknown multiplex protocol: ", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
|
||||||
|
switch p {
|
||||||
|
case ProtocolSMux:
|
||||||
|
session, err := smux.Server(conn, smuxConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &smuxSession{session}, nil
|
||||||
|
case ProtocolYAMux:
|
||||||
|
return yamux.Server(conn, yaMuxConfig())
|
||||||
|
default:
|
||||||
|
panic("unknown protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
|
||||||
|
switch p {
|
||||||
|
case ProtocolSMux:
|
||||||
|
session, err := smux.Client(conn, smuxConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &smuxSession{session}, nil
|
||||||
|
case ProtocolYAMux:
|
||||||
|
return yamux.Client(conn, yaMuxConfig())
|
||||||
|
default:
|
||||||
|
panic("unknown protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func smuxConfig() *smux.Config {
|
||||||
|
config := smux.DefaultConfig()
|
||||||
|
config.KeepAliveDisabled = true
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func yaMuxConfig() *yamux.Config {
|
||||||
|
config := yamux.DefaultConfig()
|
||||||
|
config.LogOutput = io.Discard
|
||||||
|
config.StreamCloseTimeout = C.TCPTimeout
|
||||||
|
config.StreamOpenTimeout = C.TCPTimeout
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Protocol) String() string {
|
||||||
|
switch p {
|
||||||
|
case ProtocolSMux:
|
||||||
|
return "smux"
|
||||||
|
case ProtocolYAMux:
|
||||||
|
return "yamux"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
version0 = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Protocol Protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadRequest(reader io.Reader) (*Request, error) {
|
||||||
|
version, err := rw.ReadByte(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version != version0 {
|
||||||
|
return nil, E.New("unsupported version: ", version)
|
||||||
|
}
|
||||||
|
protocol, err := rw.ReadByte(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if protocol > byte(ProtocolYAMux) {
|
||||||
|
return nil, E.New("unsupported protocol: ", protocol)
|
||||||
|
}
|
||||||
|
return &Request{Protocol: Protocol(protocol)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeRequest(buffer *buf.Buffer, request Request) {
|
||||||
|
buffer.WriteByte(version0)
|
||||||
|
buffer.WriteByte(byte(request.Protocol))
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagUDP = 1
|
||||||
|
flagAddr = 2
|
||||||
|
statusSuccess = 0
|
||||||
|
statusError = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type StreamRequest struct {
|
||||||
|
Network string
|
||||||
|
Destination M.Socksaddr
|
||||||
|
PacketAddr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadStreamRequest(reader io.Reader) (*StreamRequest, error) {
|
||||||
|
var flags uint16
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var network string
|
||||||
|
var udpAddr bool
|
||||||
|
if flags&flagUDP == 0 {
|
||||||
|
network = N.NetworkTCP
|
||||||
|
} else {
|
||||||
|
network = N.NetworkUDP
|
||||||
|
udpAddr = flags&flagAddr != 0
|
||||||
|
}
|
||||||
|
return &StreamRequest{network, destination, udpAddr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestLen(request StreamRequest) int {
|
||||||
|
var rLen int
|
||||||
|
rLen += 1 // version
|
||||||
|
rLen += 2 // flags
|
||||||
|
rLen += M.SocksaddrSerializer.AddrPortLen(request.Destination)
|
||||||
|
return rLen
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeStreamRequest(request StreamRequest, buffer *buf.Buffer) {
|
||||||
|
destination := request.Destination
|
||||||
|
var flags uint16
|
||||||
|
if request.Network == N.NetworkUDP {
|
||||||
|
flags |= flagUDP
|
||||||
|
}
|
||||||
|
if request.PacketAddr {
|
||||||
|
flags |= flagAddr
|
||||||
|
if !destination.IsValid() {
|
||||||
|
destination = Destination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.Must(
|
||||||
|
binary.Write(buffer, binary.BigEndian, flags),
|
||||||
|
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamResponse struct {
|
||||||
|
Status uint8
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadStreamResponse(reader io.Reader) (*StreamResponse, error) {
|
||||||
|
var response StreamResponse
|
||||||
|
status, err := rw.ReadByte(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.Status = status
|
||||||
|
if status == statusError {
|
||||||
|
response.Message, err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapStream struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapStream) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = w.Conn.Read(p)
|
||||||
|
err = wrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapStream) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = w.Conn.Write(p)
|
||||||
|
err = wrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapStream) WriteIsThreadUnsafe() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapStream) Upstream() any {
|
||||||
|
return w.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapError(err error) error {
|
||||||
|
switch err {
|
||||||
|
case yamux.ErrStreamClosed:
|
||||||
|
return io.EOF
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
257
common/mux/service.go
Normal file
257
common/mux/service.go
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/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 {
|
||||||
|
request, err := ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session, err := request.Protocol.newServer(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var group task.Group
|
||||||
|
group.Append0(func(ctx context.Context) error {
|
||||||
|
var stream net.Conn
|
||||||
|
for {
|
||||||
|
stream, err = session.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
stream = &wrapStream{stream}
|
||||||
|
request, err := ReadStreamRequest(stream)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorContext(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metadata.Destination = request.Destination
|
||||||
|
if request.Network == N.NetworkTCP {
|
||||||
|
logger.InfoContext(ctx, "inbound multiplex connection to ", metadata.Destination)
|
||||||
|
hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata)
|
||||||
|
stream.Close()
|
||||||
|
if hErr != nil {
|
||||||
|
errorHandler.NewError(ctx, hErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var packetConn N.PacketConn
|
||||||
|
if !request.PacketAddr {
|
||||||
|
logger.InfoContext(ctx, "inbound multiplex packet connection to ", metadata.Destination)
|
||||||
|
packetConn = &ServerPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: request.Destination}
|
||||||
|
} else {
|
||||||
|
logger.InfoContext(ctx, "inbound multiplex packet connection")
|
||||||
|
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
|
||||||
|
}
|
||||||
|
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
|
||||||
|
stream.Close()
|
||||||
|
if hErr != nil {
|
||||||
|
errorHandler.NewError(ctx, hErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ N.HandshakeConn = (*ServerConn)(nil)
|
||||||
|
|
||||||
|
type ServerConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
responseWrite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) HandshakeFailure(err error) error {
|
||||||
|
errMessage := err.Error()
|
||||||
|
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(statusError),
|
||||||
|
rw.WriteVString(_buffer, errMessage),
|
||||||
|
)
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) Write(b []byte) (n int, err error) {
|
||||||
|
if c.responseWrite {
|
||||||
|
return c.ExtendedConn.Write(b)
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(1 + len(b))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(statusSuccess),
|
||||||
|
common.Error(buffer.Write(b)),
|
||||||
|
)
|
||||||
|
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseWrite = true
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
if c.responseWrite {
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
buffer.ExtendHeader(1)[0] = statusSuccess
|
||||||
|
c.responseWrite = true
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) FrontHeadroom() int {
|
||||||
|
if !c.responseWrite {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ N.HandshakeConn = (*ServerPacketConn)(nil)
|
||||||
|
_ N.PacketConn = (*ServerPacketConn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerPacketConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
destination M.Socksaddr
|
||||||
|
responseWrite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) HandshakeFailure(err error) error {
|
||||||
|
errMessage := err.Error()
|
||||||
|
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(statusError),
|
||||||
|
rw.WriteVString(_buffer, errMessage),
|
||||||
|
)
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
destination = c.destination
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
pLen := buffer.Len()
|
||||||
|
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
|
||||||
|
if !c.responseWrite {
|
||||||
|
buffer.ExtendHeader(1)[0] = statusSuccess
|
||||||
|
c.responseWrite = true
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) FrontHeadroom() int {
|
||||||
|
if !c.responseWrite {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ N.HandshakeConn = (*ServerPacketAddrConn)(nil)
|
||||||
|
_ N.PacketConn = (*ServerPacketAddrConn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerPacketAddrConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
responseWrite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) HandshakeFailure(err error) error {
|
||||||
|
errMessage := err.Error()
|
||||||
|
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(statusError),
|
||||||
|
rw.WriteVString(_buffer, errMessage),
|
||||||
|
)
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
pLen := buffer.Len()
|
||||||
|
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
|
||||||
|
common.Must(M.SocksaddrSerializer.WriteAddrPort(buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination))), destination))
|
||||||
|
if !c.responseWrite {
|
||||||
|
buffer.ExtendHeader(1)[0] = statusSuccess
|
||||||
|
c.responseWrite = true
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) FrontHeadroom() int {
|
||||||
|
if !c.responseWrite {
|
||||||
|
return 3 + M.MaxSocksaddrLength
|
||||||
|
}
|
||||||
|
return 2 + M.MaxSocksaddrLength
|
||||||
|
}
|
||||||
91
common/mux/session.go
Normal file
91
common/mux/session.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/smux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type abstractSession interface {
|
||||||
|
Open() (net.Conn, error)
|
||||||
|
Accept() (net.Conn, error)
|
||||||
|
NumStreams() int
|
||||||
|
Close() error
|
||||||
|
IsClosed() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ abstractSession = (*smuxSession)(nil)
|
||||||
|
|
||||||
|
type smuxSession struct {
|
||||||
|
*smux.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smuxSession) Open() (net.Conn, error) {
|
||||||
|
return s.OpenStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smuxSession) Accept() (net.Conn, error) {
|
||||||
|
return s.AcceptStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
type protocolConn struct {
|
||||||
|
net.Conn
|
||||||
|
protocol Protocol
|
||||||
|
protocolWritten bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *protocolConn) Write(p []byte) (n int, err error) {
|
||||||
|
if c.protocolWritten {
|
||||||
|
return c.Conn.Write(p)
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(2 + len(p))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeRequest(buffer, Request{
|
||||||
|
Protocol: c.protocol,
|
||||||
|
})
|
||||||
|
common.Must(common.Error(buffer.Write(p)))
|
||||||
|
n, err = c.Conn.Write(buffer.Bytes())
|
||||||
|
if err == nil {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
c.protocolWritten = true
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *protocolConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
if !c.protocolWritten {
|
||||||
|
return bufio.ReadFrom0(c, r)
|
||||||
|
}
|
||||||
|
return bufio.Copy(c.Conn, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *protocolConn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type vectorisedProtocolConn struct {
|
||||||
|
protocolConn
|
||||||
|
N.VectorisedWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
|
||||||
|
if c.protocolWritten {
|
||||||
|
return c.VectorisedWriter.WriteVectorised(buffers)
|
||||||
|
}
|
||||||
|
c.protocolWritten = true
|
||||||
|
_buffer := buf.StackNewSize(2)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeRequest(buffer, Request{
|
||||||
|
Protocol: c.protocol,
|
||||||
|
})
|
||||||
|
return c.VectorisedWriter.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
|
||||||
|
}
|
||||||
@@ -3,12 +3,10 @@ package process
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/user"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Searcher interface {
|
type Searcher interface {
|
||||||
@@ -30,15 +28,5 @@ type Info struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
return findProcessInfo(searcher, ctx, network, source, destination)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if info.UserId != -1 {
|
|
||||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
|
||||||
if osUser != nil {
|
|
||||||
info.User = osUser.Username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -81,7 +82,9 @@ func resolveSocketByNetlink(network string, source netip.AddrPort, destination n
|
|||||||
return 0, 0, E.Cause(err, "write netlink request")
|
return 0, 0, E.Cause(err, "write netlink request")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := buf.New()
|
_buffer := buf.StackNew()
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
|
|
||||||
n, err := syscall.Read(socket, buffer.FreeBytes())
|
n, err := syscall.Read(socket, buffer.FreeBytes())
|
||||||
|
|||||||
25
common/process/searcher_with_name.go
Normal file
25
common/process/searcher_with_name.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//go:build linux && !android
|
||||||
|
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"os/user"
|
||||||
|
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
|
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if info.UserId != -1 {
|
||||||
|
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||||
|
if osUser != nil {
|
||||||
|
info.User = osUser.Username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
12
common/process/searcher_without_name.go
Normal file
12
common/process/searcher_without_name.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !linux || android
|
||||||
|
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
|
return searcher.FindProcessInfo(ctx, network, source, destination)
|
||||||
|
}
|
||||||
@@ -24,13 +24,13 @@ 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 && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -42,21 +42,3 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
return conn, 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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,10 +20,10 @@ type systemProxy struct {
|
|||||||
isMixed bool
|
isMixed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *systemProxy) update(event int) {
|
func (p *systemProxy) update(event int) error {
|
||||||
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||||
if p.interfaceName == newInterfaceName {
|
if p.interfaceName == newInterfaceName {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if p.interfaceName != "" {
|
if p.interfaceName != "" {
|
||||||
_ = p.unset()
|
_ = p.unset()
|
||||||
@@ -31,18 +31,18 @@ func (p *systemProxy) update(event int) {
|
|||||||
p.interfaceName = newInterfaceName
|
p.interfaceName = newInterfaceName
|
||||||
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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 {
|
||||||
_ = 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *systemProxy) unset() error {
|
func (p *systemProxy) unset() error {
|
||||||
@@ -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,10 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
|
|||||||
port: port,
|
port: port,
|
||||||
isMixed: isMixed,
|
isMixed: isMixed,
|
||||||
}
|
}
|
||||||
proxy.update(tun.EventInterfaceUpdate)
|
err := proxy.update(tun.EventInterfaceUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
||||||
return func() error {
|
return func() error {
|
||||||
interfaceMonitor.UnregisterCallback(proxy.element)
|
interfaceMonitor.UnregisterCallback(proxy.element)
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,9 @@ func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.
|
|||||||
if length == 0 {
|
if length == 0 {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
buffer := buf.NewSize(int(length))
|
_buffer := buf.StackNewSize(int(length))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
|
|
||||||
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
|
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
"github.com/sagernet/sing/protocol/http"
|
"github.com/sagernet/sing/protocol/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,5 +15,5 @@ func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil
|
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: request.Host}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package sniff_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/sniff"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSniffHTTP1(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n"
|
|
||||||
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, metadata.Domain, "www.google.com")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffHTTP1WithPort(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n"
|
|
||||||
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, metadata.Domain, "www.gov.cn")
|
|
||||||
}
|
|
||||||
@@ -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 (
|
||||||
|
|||||||
@@ -22,29 +22,23 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
|
|||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = C.ReadPayloadTimeout
|
timeout = C.ReadPayloadTimeout
|
||||||
}
|
}
|
||||||
deadline := time.Now().Add(timeout)
|
err := conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadOnceFrom(conn)
|
||||||
|
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var metadata *adapter.InboundContext
|
||||||
var errors []error
|
var errors []error
|
||||||
|
for _, sniffer := range sniffers {
|
||||||
for i := 0; i < 3; i++ {
|
metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes()))
|
||||||
err := conn.SetReadDeadline(deadline)
|
if metadata != nil {
|
||||||
if err != nil {
|
return metadata, nil
|
||||||
return nil, E.Cause(err, "set read deadline")
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadOnceFrom(conn)
|
|
||||||
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
|
|
||||||
if err != nil {
|
|
||||||
if i > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil, E.Cause(err, "read payload")
|
|
||||||
}
|
|
||||||
for _, sniffer := range sniffers {
|
|
||||||
metadata, err := sniffer(ctx, bytes.NewReader(buffer.Bytes()))
|
|
||||||
if metadata != nil {
|
|
||||||
return metadata, nil
|
|
||||||
}
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
}
|
||||||
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
return nil, E.Errors(errors...)
|
return nil, E.Errors(errors...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
type acmeWrapper struct {
|
type acmeWrapper struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cfg *certmagic.Config
|
cfg *certmagic.Config
|
||||||
cache *certmagic.Cache
|
|
||||||
domain []string
|
domain []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ func (w *acmeWrapper) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *acmeWrapper) Close() error {
|
func (w *acmeWrapper) Close() error {
|
||||||
w.cache.Stop()
|
w.cfg.Unmanage(w.domain)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +77,10 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
|||||||
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
|
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
|
||||||
}
|
}
|
||||||
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}
|
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}
|
||||||
cache := certmagic.NewCache(certmagic.CacheOptions{
|
config = certmagic.New(certmagic.NewCache(certmagic.CacheOptions{
|
||||||
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
},
|
},
|
||||||
})
|
}), *config)
|
||||||
config = certmagic.New(cache, *config)
|
return config.TLSConfig(), &acmeWrapper{ctx, config, options.Domain}, nil
|
||||||
return config.TLSConfig(), &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ package tls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
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"
|
||||||
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"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
@@ -30,19 +31,29 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
|
|||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return NewECHClient(router, serverAddress, options)
|
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 {
|
} else if options.UTLS != nil && options.UTLS.Enabled {
|
||||||
return NewUTLSClient(router, serverAddress, options)
|
return NewUTLSClient(router, serverAddress, options)
|
||||||
} else {
|
} else {
|
||||||
return NewSTDClient(router, serverAddress, options)
|
return NewSTDClient(serverAddress, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
||||||
|
tlsConn := config.Client(conn)
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return aTLS.ClientHandshake(ctx, conn, config)
|
err := tlsConn.HandshakeContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
|
||||||
|
var badConn badtls.TLSConn
|
||||||
|
badConn, err = badtls.Create(stdConn)
|
||||||
|
if err == nil {
|
||||||
|
return badConn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dialer struct {
|
type Dialer struct {
|
||||||
|
|||||||
@@ -1,25 +1,42 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Config = aTLS.Config
|
|
||||||
ConfigCompat = aTLS.ConfigCompat
|
|
||||||
ServerConfig = aTLS.ServerConfig
|
|
||||||
ServerConfigCompat = aTLS.ServerConfigCompat
|
|
||||||
WithSessionIDGenerator = aTLS.WithSessionIDGenerator
|
|
||||||
Conn = aTLS.Conn
|
|
||||||
|
|
||||||
STDConfig = tls.Config
|
STDConfig = tls.Config
|
||||||
STDConn = tls.Conn
|
STDConn = tls.Conn
|
||||||
ConnectionState = tls.ConnectionState
|
ConnectionState = tls.ConnectionState
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Config interface {
|
||||||
|
ServerName() string
|
||||||
|
SetServerName(serverName string)
|
||||||
|
NextProtos() []string
|
||||||
|
SetNextProtos(nextProto []string)
|
||||||
|
Config() (*STDConfig, error)
|
||||||
|
Client(conn net.Conn) Conn
|
||||||
|
Clone() Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig interface {
|
||||||
|
Config
|
||||||
|
adapter.Service
|
||||||
|
Server(conn net.Conn) Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conn interface {
|
||||||
|
net.Conn
|
||||||
|
HandshakeContext(ctx context.Context) error
|
||||||
|
ConnectionState() ConnectionState
|
||||||
|
}
|
||||||
|
|
||||||
func ParseTLSVersion(version string) (uint16, error) {
|
func ParseTLSVersion(version string) (uint16, error) {
|
||||||
switch version {
|
switch version {
|
||||||
case "1.0":
|
case "1.0":
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
|||||||
return nil, E.New("unsupported usage for ECH")
|
return nil, E.New("unsupported usage for ECH")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
|
||||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
return &echConnWrapper{cftls.Client(conn, e.config)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Clone() Config {
|
func (e *ECHClientConfig) Clone() Config {
|
||||||
@@ -76,10 +76,6 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *echConnWrapper) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
@@ -94,7 +90,6 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig cftls.Config
|
var tlsConfig cftls.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 {
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
|
||||||
if timeFunc == nil {
|
|
||||||
timeFunc = time.Now
|
|
||||||
}
|
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -25,8 +22,8 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certifi
|
|||||||
}
|
}
|
||||||
template := &x509.Certificate{
|
template := &x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
NotBefore: time.Now().Add(time.Hour * -1),
|
||||||
NotAfter: timeFunc().Add(time.Hour),
|
NotAfter: time.Now().Add(time.Hour),
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
|
|||||||
@@ -1,242 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(uConfig.NextProtos) > 0 {
|
|
||||||
for _, extension := range uConn.Extensions {
|
|
||||||
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
|
|
||||||
alpnExtension.AlpnProtocols = uConfig.NextProtos
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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] = 8
|
|
||||||
hello.SessionId[2] = 1
|
|
||||||
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
|
|
||||||
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,195 +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, err := dialer.New(router, options.Reality.Handshake.DialerOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
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`)
|
|
||||||
}
|
|
||||||
@@ -2,28 +2,36 @@ package tls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"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) {
|
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
return NewSTDServer(ctx, logger, options)
|
||||||
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) {
|
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
||||||
|
tlsConn := config.Server(conn)
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return aTLS.ServerHandshake(ctx, conn, config)
|
err := tlsConn.HandshakeContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
|
||||||
|
var badConn badtls.TLSConn
|
||||||
|
badConn, err = badtls.Create(stdConn)
|
||||||
|
if err == nil {
|
||||||
|
return badConn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@@ -36,15 +35,15 @@ func (s *STDClientConfig) Config() (*STDConfig, error) {
|
|||||||
return s.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (s *STDClientConfig) Client(conn net.Conn) Conn {
|
||||||
return tls.Client(conn, s.config), nil
|
return tls.Client(conn, s.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{s.config.Clone()}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@@ -58,7 +57,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 {
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ func (c *STDServerConfig) Config() (*STDConfig, error) {
|
|||||||
return c.config, nil
|
return c.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
|
func (c *STDServerConfig) Client(conn net.Conn) Conn {
|
||||||
return tls.Client(conn, c.config), nil
|
return tls.Client(conn, c.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
|
func (c *STDServerConfig) Server(conn net.Conn) Conn {
|
||||||
return tls.Server(conn, c.config), nil
|
return tls.Server(conn, c.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Clone() Config {
|
func (c *STDServerConfig) Clone() Config {
|
||||||
@@ -156,7 +156,7 @@ func (c *STDServerConfig) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -164,8 +164,8 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
var acmeService adapter.Service
|
var acmeService adapter.Service
|
||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
//nolint:staticcheck
|
|
||||||
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||||
|
//nolint:staticcheck
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -175,12 +175,11 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{}
|
tlsConfig = &tls.Config{}
|
||||||
}
|
}
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
if len(options.ALPN) > 0 {
|
if len(options.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||||
}
|
}
|
||||||
if options.MinVersion != "" {
|
if options.MinVersion != "" {
|
||||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||||
@@ -231,7 +230,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
|
return GenerateKeyPair(info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user