Compare commits

...

175 Commits

Author SHA1 Message Date
世界
32875e7cbc Fix conntrack return pointer 2023-04-13 13:23:04 +08:00
世界
3de56620ce android: Remove Seq.Delete warning 2023-04-13 13:23:04 +08:00
世界
3651605d3b Use HTTPS URLTest source 2023-04-13 11:02:00 +08:00
世界
7d1174e545 Print sniff result 2023-04-13 09:03:52 +08:00
世界
bd9e6e5cd9 Fix processing domain address in packet 2023-04-12 20:49:45 +08:00
世界
ed37cb858b Update documentation 2023-04-12 16:17:01 +08:00
世界
62bcf22c26 Fix parsing query in http path 2023-04-12 15:58:03 +08:00
Xiaokang Wang (Shelikhoo)
84bd997742 Fix '?' at end of WebSocket path get escaped
This fix align sing-box's behaviour with V2Ray when it comes to processing ? at the end of WebSocket's path.
2023-04-12 15:58:03 +08:00
H1JK
a548e45ad7 Add BaseContext to http servers 2023-04-11 18:00:43 +08:00
H1JK
5c1de2bb06 Fix gRPC service name escape 2023-04-11 18:00:43 +08:00
Hellojack
e5f0add1ab Remove TLS requirement for gRPC client 2023-04-11 18:00:43 +08:00
PuerNya
70e47df295 Add headers option for HTTP outbound 2023-04-11 17:49:37 +08:00
世界
f20642d6fd Fix workflows 2023-04-11 17:44:24 +08:00
世界
73fa926b48 clash-api: Add Clash.Meta APIs 2023-04-11 17:44:24 +08:00
世界
5d9dce8078 Fix find process user 2023-04-11 14:40:10 +08:00
世界
e20e2d57c9 Fix set HTTP TLS ALPN 2023-04-11 14:40:10 +08:00
世界
25f31890ed deadline: Fix disable pipe 2023-04-11 14:13:22 +08:00
世界
194b36b987 Fix v2ray http transport server read request 2023-04-11 10:30:10 +08:00
世界
1e39196bc9 Update cancel context usage 2023-04-10 17:01:21 +08:00
世界
da82a41697 Fix write log to stderr 2023-04-10 08:48:58 +08:00
世界
aceb82a75e Fix fallback packet conn 2023-04-09 22:53:06 +08:00
世界
f2749bc29d platform: Add stderr redirect 2023-04-09 22:52:45 +08:00
世界
55afaa87da Fix write http status after response sent 2023-04-09 22:52:45 +08:00
世界
d77940ab39 Fix deadline not initialized 2023-04-09 22:52:45 +08:00
世界
1eea446e45 documentation: Update external-ui download fields 2023-04-09 15:44:19 +08:00
世界
19c6241e10 Update dependencies 2023-04-09 15:37:06 +08:00
世界
b290d0ed32 documentation: Update changelog 2023-04-09 15:06:20 +08:00
世界
2afe662646 clash api: download clash-dashboard if external-ui directory is empty 2023-04-09 12:39:33 +08:00
世界
107a9a3b51 Fix read deadline implementation 2023-04-09 12:39:33 +08:00
世界
3d0c64f523 Replace usages of uber/atomic 2023-04-09 12:39:33 +08:00
世界
422ca34ac2 Fix timeout error check 2023-04-08 12:25:51 +08:00
世界
6d63f9255f documentation: Update changelog 2023-04-08 09:37:58 +08:00
世界
6f2cc9761d Add multi-peer support for wireguard outbound 2023-04-08 09:37:58 +08:00
世界
b484d9bca6 Add fakeip support 2023-04-08 09:37:58 +08:00
世界
58c4fd745a Add L3 routing support 2023-04-08 09:17:12 +08:00
世界
7d1e6affb3 Add dns reverse mapping 2023-04-08 09:17:03 +08:00
世界
6843970536 Add loopback check 2023-04-08 09:13:50 +08:00
世界
62425ad3e4 Add close monitor 2023-04-08 08:10:03 +08:00
世界
e1e217854e Add start and close track message 2023-04-08 08:09:28 +08:00
世界
5bf177b021 platform: Fix build on windows 2023-04-07 21:10:16 +08:00
世界
72dbf2e2b4 documentation: Update changelog 2023-04-07 19:18:26 +08:00
世界
46c318c6fe Fix v2ray HTTP/1.1 transport compatibility 2023-04-07 18:20:07 +08:00
世界
05bb1b88c3 dns: Fix rewrite TTL 2023-04-07 16:19:34 +08:00
世界
5176ea9fe0 Update dependencies 2023-04-07 16:19:34 +08:00
世界
36d349acd2 dns: Fix calculate TTL 2023-04-07 13:12:16 +08:00
世界
4feee983b5 Update reality protocol 2023-04-06 19:05:05 +08:00
世界
9b12e3e389 Update client documentation 2023-04-06 12:51:26 +08:00
世界
afd3464216 Minor fixes 2023-04-05 21:41:06 +08:00
世界
8b64446274 platform: Fixes and improvements 2023-04-05 19:54:20 +08:00
世界
28aa4c4d1f Refactor log factory constructor 2023-04-03 20:24:13 +08:00
世界
0be3cdc8fb platform: Add http client 2023-04-03 15:12:44 +08:00
armv9
f8be484019 conntrack: Fix missing tracking for udp conn 2023-04-02 12:06:03 +08:00
世界
35f03f092d Improve UDP domain destination NAT 2023-04-02 12:05:59 +08:00
世界
c3d7401ead platform: Add check config func 2023-04-02 10:35:03 +08:00
世界
4db7eb9d9e documentation: Update changelog 2023-03-31 16:29:08 +08:00
世界
fd4efd6104 Fix dns transport read 2023-03-31 14:31:35 +08:00
世界
19a35ec6a4 Fix http2 transport close 2023-03-31 14:31:35 +08:00
世界
2012c0ca1e Update release scripts 2023-03-31 14:31:35 +08:00
世界
187421c754 Append time to session log 2023-03-31 14:31:35 +08:00
世界
b3fb86d415 Accept "any" outbound in dns rule 2023-03-31 14:31:35 +08:00
世界
88fafd4e30 Fix dns routing context 2023-03-31 09:14:04 +08:00
世界
8056932f9c Update documentation 2023-03-27 08:23:01 +08:00
世界
c8af003bfc Update dependencies 2023-03-27 08:22:56 +08:00
世界
4999441a85 Fix missing default host in v2ray http transport`s request 2023-03-27 08:20:59 +08:00
世界
09b001e795 Revert remove install shell 2023-03-27 08:20:55 +08:00
世界
3b3a251008 Update LICENSE 2023-03-27 08:20:51 +08:00
世界
2e4eb9aa39 Update dockerfile 2023-03-24 08:29:11 +08:00
世界
77fd284703 documentation: Update changelog 2023-03-24 08:04:36 +08:00
世界
0a4517f4b7 Update dependencies 2023-03-24 07:06:45 +08:00
世界
4395db3206 documentation: Update set_system_proxy usage 2023-03-23 21:27:50 +08:00
世界
dd5b0abc67 Fix slow open 2023-03-23 17:14:38 +08:00
世界
466800aa3a Fix wireguard mutex 2023-03-23 15:43:17 +08:00
世界
4328c535a9 Improve timeout canceler 2023-03-23 15:39:12 +08:00
世界
f9516709da Update documentation 2023-03-23 07:54:24 +08:00
世界
5dce722879 Update dependencies 2023-03-23 07:49:14 +08:00
世界
9324a39d4e Fix import format 2023-03-20 23:01:54 +08:00
世界
84904c5206 Create working directory if not exists 2023-03-20 19:33:00 +08:00
世界
fe4b429fc2 hysteria: Accept inbound configuration without users 2023-03-20 19:22:46 +08:00
世界
f680d0acaf Add with_reality_server to release build tags 2023-03-20 17:36:59 +08:00
世界
4baff5aeb1 documentation: Update changelog 2023-03-20 17:32:59 +08:00
世界
f25296fb23 Update dependencies 2023-03-20 17:27:48 +08:00
世界
e717852c73 Fix optional listen address 2023-03-19 20:46:22 +08:00
世界
13dc70f649 Fix make build 2023-03-19 16:57:07 +08:00
世界
46040a71c3 Fix vision padding overflow 2023-03-19 10:25:35 +08:00
世界
0558b3fc5c ntp: Add write_to_system service option 2023-03-18 23:11:40 +08:00
世界
99b2ab5526 Add command to fetch a URL 2023-03-18 21:02:29 +08:00
世界
e5f3bb6344 Add command to connect an address 2023-03-18 20:27:38 +08:00
世界
c7f89ad88e Add multiple configuration support 2023-03-18 20:27:38 +08:00
世界
e0d9f79445 Fix test 2023-03-18 17:02:55 +08:00
世界
b6dbb69fc4 Fix write nil in buffered vectorised writer 2023-03-18 16:32:28 +08:00
世界
b76fabee65 documentation: Fix broken link 2023-03-18 16:31:15 +08:00
世界
872bcfd1c0 readme: Add packaging status 2023-03-17 17:58:08 +08:00
世界
b033c13ca2 documentation: Update stable changelog 2023-03-17 17:58:08 +08:00
renovate[bot]
2db188f3a1 dependencies: Update actions/setup-go action to v4
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 17:58:08 +08:00
世界
11de271c8f Add experimental debug options 2023-03-17 17:58:08 +08:00
世界
40c800c57c documentation: Fix typo 2023-03-17 13:34:36 +08:00
世界
91b0540e95 documentation: Update UoT application support status 2023-03-17 13:28:05 +08:00
世界
ce6d186345 Update documentation 2023-03-17 13:07:22 +08:00
世界
32bc4450a7 Update dependencies 2023-03-17 12:59:12 +08:00
世界
43f31b40ba Update UoT protocol 2023-03-17 12:57:48 +08:00
世界
a3a5185b15 platform: Fix bytes format 2023-03-16 11:28:54 +08:00
世界
14a0f180c8 ios: Add with_quic tag in build 2023-03-16 11:28:54 +08:00
世界
cc9cb0b477 platform: Add oom killer 2023-03-16 11:28:54 +08:00
世界
2cb0e37f50 platform: Add low memory interface 2023-03-16 00:36:04 +08:00
世界
dbd5be55b0 tun: Create gVisor stack by default in Apple Network Extension 2023-03-15 21:50:18 +08:00
世界
f674b4fbd5 Fix build embed tor for mobile 2023-03-15 20:59:45 +08:00
世界
5a4e8fea81 Fix lint 2023-03-15 14:56:06 +08:00
世界
78e02b52ca Update UoT protocol 2023-03-15 14:52:32 +08:00
世界
ffdaae90d7 Update dependencies 2023-03-15 11:59:15 +08:00
世界
c77681ea17 Fix close platform tun 2023-03-13 19:47:00 +08:00
世界
d824390167 Fix cross make build 2023-03-13 19:46:20 +08:00
世界
70cf681ff2 Remove length limit on short_id for reality TLS config 2023-03-13 19:46:16 +08:00
wwqgtxx
b004b9ec81 Fix stack wireguard device returning non-nil interface containing nil pointer 2023-03-13 19:46:16 +08:00
世界
657b05fd96 Print command to shell error 2023-03-13 19:46:16 +08:00
世界
caad60da45 Apply --disable-color to global logger 2023-03-13 11:23:00 +08:00
世界
7d22cf9b45 Support $schema in configuration file 2023-03-13 10:58:29 +08:00
世界
5cb178ca93 Update documentation 2023-03-12 23:07:38 +08:00
世界
16788008b6 Update dependencies 2023-03-12 23:07:24 +08:00
世界
6ec7a33046 Fix make install 2023-03-11 19:24:19 +08:00
世界
6af9c2b3ca Add health check support for http-based v2ray transport 2023-03-11 15:49:02 +08:00
世界
bdc620dab1 Fix http server usage 2023-03-11 15:05:07 +08:00
世界
a88820af31 Fix missing default shadowtls version 2023-03-11 10:12:46 +08:00
世界
3688f2e114 Update documentation 2023-03-10 11:15:00 +08:00
世界
a183958d53 Update dependencies 2023-03-09 23:24:05 +08:00
世界
1c8a9e91b7 Generate version during compilation 2023-03-09 23:24:05 +08:00
世界
325f6c71ff Fix windows interface monitor 2023-03-09 16:00:57 +08:00
世界
6c6c0792ad Update reality and uTLS 2023-03-09 10:51:01 +08:00
世界
9264f2307c Fix link broken in installation documentation page 2023-03-08 15:56:41 +08:00
世界
87dd328700 Fix build check 2023-03-08 15:23:46 +08:00
世界
0cec92dd0f Update documentation 2023-03-08 14:59:09 +08:00
世界
e88afa9665 Fix vmess server buffer 2023-03-07 17:07:37 +08:00
世界
3883a81315 Check constant.Version before build release 2023-03-06 16:34:44 +08:00
Dmitry R
c919ad079a systemd: Add reload command 2023-03-06 16:32:54 +08:00
世界
83593aee70 Fix vless read cache 2023-03-06 11:19:38 +08:00
世界
ac7cc09694 Update documentation 2023-03-05 23:37:12 +08:00
世界
d032e3568b Update dependencies 2023-03-05 21:38:02 +08:00
世界
c24df037ac Add documentation for tun platform options 2023-03-05 15:19:13 +08:00
世界
a2d43b3746 Fix open cache file 2023-03-05 14:57:50 +08:00
世界
5b3b74bd0f Fix vision read 2023-03-05 14:57:50 +08:00
世界
d24d3b26dc Fix uTLS randomized fingerprint 2023-03-05 14:57:50 +08:00
seiuneko
5db3cd7781 Fix documentation typo 2023-03-05 14:57:50 +08:00
世界
c88af8b081 Fix documentation 2023-03-05 14:57:50 +08:00
世界
45852ca3e7 Fix check config 2023-03-05 14:57:50 +08:00
Hellojack
03ce555104 Add generate commands 2023-03-05 11:21:32 +08:00
世界
dd0a07624e Add stop platform command 2023-03-04 00:40:47 +08:00
世界
b9b2b77814 Add reload platform command 2023-03-03 21:59:54 +08:00
世界
2366835121 Fix close conn 2023-03-03 19:27:30 +08:00
database64128
42e1dea7d2 Update .gitignore 2023-03-03 18:51:33 +08:00
Ella Hollywood
13d7716b02 Fix documentation typo 2023-03-03 16:35:06 +08:00
世界
7ecb9fc738 Minor fixes 2023-03-03 16:31:07 +08:00
世界
19b15e0d10 Fix UoT UDP address 2023-03-03 11:34:51 +08:00
database64128
0b15de461b Update tfo-go 2023-03-03 10:16:38 +08:00
世界
27aba99e6c Fix command client connect 2023-03-02 16:40:28 +08:00
世界
8151bcfd6b Add ios memory limit 2023-03-02 15:04:59 +08:00
世界
e8802357e1 Fix vless tests 2023-03-02 00:31:56 +08:00
世界
6e22c004f6 Improve server error handling 2023-03-02 00:18:35 +08:00
世界
20e1caa531 Fix custom tls server listener 2023-03-02 00:01:40 +08:00
世界
32ad3c3db3 Remove okhttp form modern fingerprint list 2023-03-01 21:17:30 +08:00
世界
1f5f8a7dde Fixed user flow in vless server 2023-03-01 20:28:40 +08:00
世界
6da1460795 Fix geo resource download path 2023-03-01 19:09:21 +08:00
世界
b14ae51f71 Fix create badhttp2 server 2023-03-01 19:09:21 +08:00
世界
5af8d001ae Refactor platform command api 2023-03-01 19:09:21 +08:00
世界
0ca344df5f Fix uTLS ALPN 2023-02-28 21:16:31 +08:00
世界
49f568abbd Separate uTLS random fingerprint 2023-02-28 21:10:11 +08:00
世界
3b4e811907 Add reality client fallback 2023-02-28 20:55:14 +08:00
世界
d0e9443031 Enable XUDP by default in VLESS 2023-02-28 20:52:26 +08:00
世界
f7e9d9ab1f Fix check early conn 2023-02-28 20:16:15 +08:00
世界
7834d6bca7 Add tun platform options 2023-02-28 19:02:27 +08:00
世界
ed50257735 Add custom TLS server support for http based v2ray transports 2023-02-28 13:03:44 +08:00
世界
f15f525c5c Merge tls interface to library 2023-02-28 11:30:46 +08:00
世界
e4bff0460d Update vision protocol 2023-02-27 15:07:15 +08:00
世界
5ce3ddee9b Add early conn interface 2023-02-26 23:08:20 +08:00
世界
22bf7a9509 Update reality server 2023-02-26 20:55:36 +08:00
世界
842730707c Update TUN creation 2023-02-26 20:55:15 +08:00
世界
a8f13bd956 Fix documentation 2023-02-25 17:25:56 +08:00
344 changed files with 9029 additions and 3301 deletions

View File

@@ -28,15 +28,9 @@ jobs:
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
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
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
run: |
version=`git rev-parse HEAD`
@@ -58,7 +52,7 @@ jobs:
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.18.10
- name: Cache go module
@@ -193,15 +187,9 @@ jobs:
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
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
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
id: build
run: make

View File

@@ -28,15 +28,9 @@ jobs:
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
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
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
uses: golangci/golangci-lint-action@v3
with:

2
.gitignore vendored
View File

@@ -6,8 +6,10 @@
/bin/
/dist/
/sing-box
/sing-box.exe
/build/
/*.jar
/*.aar
/*.xcframework/
.DS_Store
/config.d/

View File

@@ -10,12 +10,13 @@ builds:
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -s -w -buildid=
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_wireguard
- with_utls
- with_reality_server
- with_clash_api
env:
- CGO_ENABLED=0
@@ -43,7 +44,7 @@ builds:
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -s -w -buildid=
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
- with_gvisor
- with_quic
@@ -116,9 +117,6 @@ nfpms:
dst: /etc/systemd/system/sing-box@.service
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
scripts:
postinstall: release/config/postinstall.sh
postremove: release/config/postremove.sh
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'

View File

@@ -8,9 +8,10 @@ ENV CGO_ENABLED=0
RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
-o /go/bin/sing-box \
-ldflags "-s -w -buildid=" \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box
FROM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"

View File

@@ -11,4 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
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.

View File

@@ -2,8 +2,14 @@ NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
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 -tags "$(TAGS)" -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
.PHONY: test release
@@ -11,7 +17,7 @@ build:
go build $(PARAMS) $(MAIN)
install:
go install $(PARAMS) $(MAIN)
go build -o $(PREFIX)/bin/$(NAME) $(PARAMS) $(MAIN)
fmt:
@gofumpt -l -w .
@@ -71,13 +77,20 @@ test_stdio:
go mod tidy && \
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
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-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230413023804-244d7ff07035
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230413023804-244d7ff07035
clean:
rm -rf bin dist sing-box

View File

@@ -2,6 +2,8 @@
The universal proxy platform.
[![Packaging status](https://repology.org/badge/vertical-allrepos/sing-box.svg)](https://repology.org/project/sing-box/versions)
## Documentation
https://sing-box.sagernet.org
@@ -23,4 +25,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
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.
```

View File

@@ -10,8 +10,10 @@ import (
type ClashServer interface {
Service
PreStarter
Mode() string
StoreSelected() bool
StoreFakeIP() bool
CacheFile() ClashCacheFile
HistoryStorage() *urltest.HistoryStorage
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
@@ -21,6 +23,7 @@ type ClashServer interface {
type ClashCacheFile interface {
LoadSelected(group string) string
StoreSelected(group string, selected string) error
FakeIPStorage
}
type Tracker interface {
@@ -32,6 +35,11 @@ type OutboundGroup interface {
All() []string
}
type URLTestGroup interface {
OutboundGroup
URLTest(ctx context.Context, url string) (map[string]uint16, error)
}
func OutboundTag(detour Outbound) string {
if group, isGroup := detour.(OutboundGroup); isGroup {
return group.Now()

23
adapter/fakeip.go Normal file
View File

@@ -0,0 +1,23 @@
package adapter
import (
"net/netip"
"github.com/sagernet/sing-dns"
)
type FakeIPStore interface {
Service
Contains(address netip.Addr) bool
Create(domain string, strategy dns.DomainStrategy) (netip.Addr, error)
Lookup(address netip.Addr) (string, bool)
Reset() error
}
type FakeIPStorage interface {
FakeIPMetadata() *FakeIPMetadata
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
FakeIPStore(address netip.Addr, domain string) error
FakeIPLoad(address netip.Addr) (string, bool)
FakeIPReset() error
}

View File

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

View File

@@ -27,7 +27,7 @@ type InjectableInbound interface {
type InboundContext struct {
Inbound string
InboundType string
IPVersion int
IPVersion uint8
Network string
Source M.Socksaddr
Destination M.Socksaddr

View File

@@ -4,6 +4,7 @@ import (
"context"
"net"
"github.com/sagernet/sing-tun"
N "github.com/sagernet/sing/common/network"
)
@@ -17,3 +18,8 @@ type Outbound interface {
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
type IPOutbound interface {
Outbound
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
}

15
adapter/prestart.go Normal file
View File

@@ -0,0 +1,15 @@
package adapter
type PreStarter interface {
PreStart() error
}
func PreStart(starter any) error {
if preService, ok := starter.(PreStarter); ok {
err := preService.PreStart()
if err != nil {
return err
}
}
return nil
}

View File

@@ -21,8 +21,13 @@ type Router interface {
Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound
FakeIPStore() FakeIPStore
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
NatRequired(outbound string) bool
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
@@ -39,7 +44,9 @@ type Router interface {
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
IPRules() []IPRule
TimeService
@@ -76,6 +83,12 @@ type Rule interface {
type DNSRule interface {
Rule
DisableCache() bool
RewriteTTL() *uint32
}
type IPRule interface {
Rule
Action() tun.ActionType
}
type InterfaceUpdateListener interface {

264
box.go
View File

@@ -9,8 +9,8 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -24,84 +24,53 @@ import (
var _ adapter.Service = (*Box)(nil)
type Box struct {
createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
logFile *os.File
clashServer adapter.ClashServer
v2rayServer adapter.V2RayServer
done chan struct{}
createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
preServices map[string]adapter.Service
postServices map[string]adapter.Service
done chan struct{}
}
func New(ctx context.Context, options option.Options) (*Box, error) {
createdAt := time.Now()
logOptions := common.PtrValueOrDefault(options.Log)
type Options struct {
option.Options
Context context.Context
PlatformInterface platform.Interface
}
func New(options Options) (*Box, error) {
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
createdAt := time.Now()
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needClashAPI bool
var needV2RayAPI bool
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 experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
needClashAPI = true
}
var logFactory log.Factory
var observableLogFactory log.ObservableFactory
var logFile *os.File
var logWriter io.Writer
if logOptions.Disabled {
observableLogFactory = log.NewNOPFactory()
logFactory = observableLogFactory
} else {
switch logOptions.Output {
case "":
if options.PlatformInterface != nil {
logWriter = io.Discard
} else {
logWriter = os.Stdout
}
case "stderr":
logWriter = os.Stderr
case "stdout":
logWriter = os.Stdout
default:
var err error
logFile, err = os.OpenFile(C.BasePath(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, options.PlatformInterface)
logFactory = observableLogFactory
} else {
logFactory = log.NewFactory(logFormatter, logWriter, options.PlatformInterface)
}
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)
}
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
var defaultLogWriter io.Writer
if options.PlatformInterface != nil {
defaultLogWriter = io.Discard
}
logFactory, err := log.New(log.Options{
Options: common.PtrValueOrDefault(options.Log),
Observable: needClashAPI,
DefaultWriter: defaultLogWriter,
BaseTime: createdAt,
PlatformWriter: options.PlatformInterface,
})
if err != nil {
return nil, E.Cause(err, "create log factory")
}
router, err := route.NewRouter(
ctx,
logFactory,
@@ -148,6 +117,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
ctx,
router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
tag,
outboundOptions)
if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]")
@@ -155,7 +125,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
outbounds = append(outbounds, out)
}
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
common.Must(oErr)
outbounds = append(outbounds, out)
return out
@@ -163,37 +133,56 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
if err != nil {
return nil, err
}
var clashServer adapter.ClashServer
var v2rayServer adapter.V2RayServer
preServices := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
if needClashAPI {
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
clashServer, err := experimental.NewClashServer(router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
if err != nil {
return nil, E.Cause(err, "create clash api server")
}
router.SetClashServer(clashServer)
preServices["clash api"] = clashServer
}
if needV2RayAPI {
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
}
router.SetV2RayServer(v2rayServer)
preServices["v2ray api"] = v2rayServer
}
return &Box{
router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
logFile: logFile,
clashServer: clashServer,
v2rayServer: v2rayServer,
done: make(chan struct{}),
router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
preServices: preServices,
postServices: postServices,
done: make(chan struct{}),
}, 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 {
err := s.start()
if err != nil {
@@ -207,54 +196,70 @@ func (s *Box) Start() error {
}
}()
s.Close()
return err
}
return err
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) start() error {
func (s *Box) preStart() error {
for serviceName, service := range s.preServices {
s.logger.Trace("pre-start ", serviceName)
err := adapter.PreStart(service)
if err != nil {
return E.Cause(err, "pre-starting ", serviceName)
}
}
for i, out := range s.outbounds {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
if starter, isStarter := out.(common.Starter); isStarter {
s.logger.Trace("initializing outbound/", out.Type(), "[", tag, "]")
err := starter.Start()
if err != nil {
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()
return s.router.Start()
}
func (s *Box) start() error {
err := s.preStart()
if err != nil {
return err
}
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 {
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()
for serviceName, service := range s.postServices {
s.logger.Trace("starting ", service)
err = service.Start()
if err != nil {
return E.Cause(err, "start clash api server")
return E.Cause(err, "start ", serviceName)
}
}
if s.v2rayServer != nil {
err = s.v2rayServer.Start()
if err != nil {
return E.Cause(err, "start v2ray api server")
}
}
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
@@ -266,41 +271,42 @@ func (s *Box) Close() error {
close(s.done)
}
var errors error
for serviceName, service := range s.postServices {
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 {
s.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
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 {
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
})
}
s.logger.Trace("closing router")
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")
})
}
if err := common.Close(s.clashServer); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close clash api server")
})
}
if err := common.Close(s.v2rayServer); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close v2ray api server")
})
}
if s.logFile != nil {
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
return E.Cause(err, "close log file")
})
}
return errors
}

View File

@@ -6,7 +6,7 @@ import (
"os/exec"
"path/filepath"
_ "github.com/sagernet/gomobile/asset"
_ "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"
@@ -35,6 +35,23 @@ func main() {
}
}
var (
sharedFlags []string
debugFlags []string
)
func init() {
sharedFlags = append(sharedFlags, "-trimpath")
sharedFlags = append(sharedFlags, "-ldflags")
currentTag, err := build_shared.ReadTag()
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
}
func buildAndroid() {
build_shared.FindSDK()
@@ -46,14 +63,17 @@ func buildAndroid() {
"-libname=box",
}
if !debugEnabled {
args = append(args,
"-trimpath", "-ldflags=-s -w -buildid=",
"-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api",
)
args = append(args, sharedFlags...)
} else {
args = append(args, "-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
args = append(args, debugFlags...)
}
args = append(args, "-tags")
if !debugEnabled {
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api")
} else {
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
}
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
@@ -84,13 +104,17 @@ func buildiOS() {
"-libname=box",
}
if !debugEnabled {
args = append(args,
"-trimpath", "-ldflags=-s -w -buildid=",
)
args = append(args, sharedFlags...)
} else {
args = append(args, "-tags", "debug")
args = append(args, debugFlags...)
}
args = append(args, "-tags")
if !debugEnabled {
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack")
} else {
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack,debug")
}
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
@@ -101,7 +125,7 @@ func buildiOS() {
log.Fatal(err)
}
copyPath := filepath.Join("..", "sfi")
copyPath := filepath.Join("..", "sing-box-for-ios")
if rw.FileExists(copyPath) {
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)

View File

@@ -0,0 +1,16 @@
package build_shared
import "github.com/sagernet/sing/common/shell"
func ReadTag() (string, error) {
currentTag, err := shell.Exec("git", "describe", "--tags").ReadOutput()
if err != nil {
return currentTag, err
}
currentTagRev, _ := shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()
if currentTagRev == currentTag {
return currentTag[1:], nil
}
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
return currentTagRev[1:] + "-" + shortCommit, nil
}

View File

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

View File

@@ -26,12 +26,18 @@ func init() {
}
func check() error {
options, err := readConfig()
options, err := readConfigAndMerge()
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
_, err = box.New(ctx, options)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
if err == nil {
instance.Close()
}
cancel()
return err
}

View File

@@ -33,6 +33,44 @@ func init() {
}
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)
if err != nil {
return E.Cause(err, "read config")

View File

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

View File

@@ -5,10 +5,15 @@ import (
"io"
"os"
"os/signal"
"path/filepath"
runtimeDebug "runtime/debug"
"sort"
"strings"
"syscall"
"time"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/badjsonmerge"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
@@ -31,29 +36,88 @@ func init() {
mainCommand.AddCommand(commandRun)
}
func readConfig() (option.Options, error) {
type OptionsEntry struct {
content []byte
path string
options option.Options
}
func readConfigAt(path string) (*OptionsEntry, error) {
var (
configContent []byte
err error
)
if configPath == "stdin" {
if path == "stdin" {
configContent, err = io.ReadAll(os.Stdin)
} else {
configContent, err = os.ReadFile(configPath)
configContent, err = os.ReadFile(path)
}
if err != nil {
return option.Options{}, E.Cause(err, "read config")
return nil, E.Cause(err, "read config at ", path)
}
var options option.Options
err = options.UnmarshalJSON(configContent)
if err != nil {
return option.Options{}, E.Cause(err, "decode config")
return nil, E.Cause(err, "decode config at ", path)
}
return options, nil
return &OptionsEntry{
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) {
options, err := readConfig()
options, err := readConfigAndMerge()
if err != nil {
return nil, nil, err
}
@@ -64,7 +128,10 @@ func create() (*box.Box, context.CancelFunc, error) {
options.Log.DisableColor = true
}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(ctx, options)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
if err != nil {
cancel()
return nil, nil, E.Cause(err, "create service")
@@ -111,7 +178,10 @@ func run() error {
}
}
cancel()
closeCtx, closed := context.WithCancel(context.Background())
go closeMonitor(closeCtx)
instance.Close()
closed()
if osSignal != syscall.SIGHUP {
return nil
}
@@ -119,3 +189,13 @@ 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!")
}

53
cmd/sing-box/cmd_tools.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -25,9 +25,9 @@ func init() {
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("heap", humanize.IBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())

View File

@@ -2,6 +2,7 @@ package main
import (
"os"
"time"
_ "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
@@ -10,9 +11,10 @@ import (
)
var (
configPath string
workingDir string
disableColor bool
configPaths []string
configDirectories []string
workingDir string
disableColor bool
)
var mainCommand = &cobra.Command{
@@ -21,7 +23,8 @@ var mainCommand = &cobra.Command{
}
func init() {
mainCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "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().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
}
@@ -33,9 +36,19 @@ func main() {
}
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 != "" {
_, err := os.Stat(workingDir)
if err != nil {
os.MkdirAll(workingDir, 0o777)
}
if err := os.Chdir(workingDir); err != nil {
log.Fatal(err)
}
}
if len(configPaths) == 0 && len(configDirectories) == 0 {
configPaths = append(configPaths, "config.json")
}
}

View File

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

View File

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

View File

@@ -0,0 +1,114 @@
package badversion
import (
"strconv"
"strings"
F "github.com/sagernet/sing/common/format"
)
type Version struct {
Major int
Minor int
Patch int
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 == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
return true
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
return false
}
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
return true
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
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.PreReleaseIdentifier = 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
}

View File

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

View File

@@ -0,0 +1,18 @@
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")))
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
package conntrack
import (
"io"
"net"
"github.com/sagernet/sing/common/x/list"
)
type PacketConn struct {
net.PacketConn
element *list.Element[io.Closer]
}
func NewPacketConn(conn net.PacketConn) (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 c.PacketConn
}
func (c *PacketConn) ReaderReplaceable() bool {
return true
}
func (c *PacketConn) WriterReplaceable() bool {
return true
}

View File

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

View File

@@ -0,0 +1,5 @@
//go:build !with_conntrack
package conntrack
const Enabled = false

View File

@@ -0,0 +1,5 @@
//go:build with_conntrack
package conntrack
const Enabled = true

View File

@@ -6,6 +6,7 @@ import (
"time"
"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"
"github.com/sagernet/sing-box/option"
@@ -153,22 +154,36 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
return d.udpDialer4.DialContext(ctx, network, address.String())
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else {
return d.udpDialer6.DialContext(ctx, network, address.String())
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
}
}
if !address.IsIPv6() {
return DialSlowContext(&d.dialer4, ctx, network, address)
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else {
return DialSlowContext(&d.dialer6, ctx, network, address)
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
}
}
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.IsIPv6() {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
} else {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
}
}
func trackConn(conn net.Conn, err error) (net.Conn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return conntrack.NewConn(conn)
}
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return conntrack.NewPacketConn(conn)
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -68,11 +69,11 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if err != nil {
return nil, err
}
conn, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
if err != nil {
return nil, err
}
return NewResolvePacketConn(ctx, d.router, d.strategy, conn), nil
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
}
func (d *ResolveDialer) Upstream() any {

View File

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

View File

@@ -27,7 +27,12 @@ type slowOpenConn struct {
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
return dialer.DialContext(ctx, network, destination.String(), nil)
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
return dialer.Dialer.DialContext(ctx, network, destination.String())
default:
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
}
}
return &slowOpenConn{
dialer: dialer,
@@ -119,6 +124,10 @@ func (c *slowOpenConn) LazyHeadroom() bool {
return c.conn == nil
}
func (c *slowOpenConn) NeedHandshake() bool {
return c.conn == nil
}
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
if c.conn != nil {
return bufio.Copy(c.conn, r)

View File

@@ -11,6 +11,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -68,7 +69,7 @@ func (c *Client) DialContext(ctx context.Context, network string, destination M.
if err != nil {
return nil, err
}
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
return bufio.NewBindPacketConn(deadline.NewPacketConn(bufio.NewNetPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination})), destination), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
@@ -79,7 +80,7 @@ func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net
if err != nil {
return nil, err
}
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
return deadline.NewPacketConn(&ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
}
func (c *Client) openStream() (net.Conn, error) {
@@ -413,7 +414,11 @@ func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
if err != nil {
return
}
addr = destination.UDPAddr()
if destination.IsFqdn() {
addr = destination
} else {
addr = destination.UDPAddr()
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {

View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -67,7 +68,7 @@ func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
logger.InfoContext(ctx, "inbound multiplex packet connection")
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
}
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
hErr := router.RoutePacketConnection(ctx, deadline.NewPacketConn(bufio.NewNetPacketConn(packetConn)), metadata)
stream.Close()
if hErr != nil {
errorHandler.NewError(ctx, hErr)

View File

@@ -3,10 +3,12 @@ package process
import (
"context"
"net/netip"
"os/user"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
type Searcher interface {
@@ -28,5 +30,15 @@ type Info struct {
}
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
return findProcessInfo(searcher, ctx, network, source, destination)
info, err := 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
}

View File

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

View File

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

View File

@@ -24,13 +24,13 @@ func (l *Listener) Accept() (net.Conn, error) {
bufReader := std_bufio.NewReader(conn)
header, err := proxyproto.Read(bufReader)
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
return nil, err
return nil, &Error{err}
}
if bufReader.Buffered() > 0 {
cache := buf.NewSize(bufReader.Buffered())
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
if err != nil {
return nil, err
return nil, &Error{err}
}
conn = bufio.NewCachedConn(conn, cache)
}
@@ -42,3 +42,21 @@ func (l *Listener) Accept() (net.Conn, error) {
}
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
}

View File

@@ -6,8 +6,8 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/shell"
)
var (
@@ -26,9 +26,9 @@ func init() {
func runAndroidShell(name string, args ...string) error {
if !useRish {
return common.Exec(name, args...).Attach().Run()
return shell.Exec(name, args...).Attach().Run()
} else {
return common.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
return shell.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
}
}

View File

@@ -6,9 +6,9 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/shell"
"github.com/sagernet/sing/common/x/list"
)
@@ -34,13 +34,13 @@ func (p *systemProxy) update(event int) error {
return err
}
if p.isMixed {
err = common.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
err = common.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
err = common.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
err = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
return err
}
@@ -51,19 +51,19 @@ func (p *systemProxy) unset() error {
return err
}
if p.isMixed {
err = common.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
err = shell.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
}
if err == nil {
err = common.Exec("networksetup", "-setwebproxystate", interfaceDisplayName, "off").Attach().Run()
err = shell.Exec("networksetup", "-setwebproxystate", interfaceDisplayName, "off").Attach().Run()
}
if err == nil {
err = common.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
err = shell.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
}
return err
}
func getInterfaceDisplayName(name string) (string, error) {
content, err := common.Exec("networksetup", "-listallhardwareports").Read()
content, err := shell.Exec("networksetup", "-listallhardwareports").ReadOutput()
if err != nil {
return "", err
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/shell"
)
var (
@@ -27,9 +28,9 @@ func init() {
func runAsUser(name string, args ...string) error {
if os.Getuid() != 0 {
return common.Exec(name, args...).Attach().Run()
return shell.Exec(name, args...).Attach().Run()
} else if sudoUser != "" {
return common.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
return shell.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else {
return E.New("set system proxy: unable to set as root")
}

View File

@@ -0,0 +1,12 @@
//go:build !(windows || linux || darwin)
package settings
import (
"os"
"time"
)
func SetSystemTime(nowTime time.Time) error {
return os.ErrInvalid
}

View File

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

View File

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

View File

@@ -24,12 +24,12 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
}
err := conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil {
return nil, err
return nil, E.Cause(err, "set read deadline")
}
_, err = buffer.ReadOnceFrom(conn)
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
if err != nil {
return nil, err
return nil, E.Cause(err, "read payload")
}
var metadata *adapter.InboundContext
var errors []error

View File

@@ -2,16 +2,15 @@ package tls
import (
"context"
"crypto/tls"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
@@ -43,22 +42,7 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
tlsConn, err := config.Client(conn)
if err != nil {
return nil, err
}
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
return aTLS.ClientHandshake(ctx, conn, config)
}
type Dialer struct {

View File

@@ -1,51 +1,25 @@
package tls
import (
"context"
"crypto/tls"
"net"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
aTLS "github.com/sagernet/sing/common/tls"
)
type (
Config = aTLS.Config
ConfigCompat = aTLS.ConfigCompat
ServerConfig = aTLS.ServerConfig
ServerConfigCompat = aTLS.ServerConfigCompat
WithSessionIDGenerator = aTLS.WithSessionIDGenerator
Conn = aTLS.Conn
STDConfig = tls.Config
STDConn = tls.Conn
ConnectionState = tls.ConnectionState
)
type Config interface {
ServerName() string
SetServerName(serverName string)
NextProtos() []string
SetNextProtos(nextProto []string)
Config() (*STDConfig, error)
Client(conn net.Conn) (Conn, error)
Clone() Config
}
type ConfigWithSessionIDGenerator interface {
SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error)
}
type ServerConfig interface {
Config
adapter.Service
Server(conn net.Conn) (Conn, error)
}
type ServerConfigCompat interface {
ServerConfig
ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error)
}
type Conn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() ConnectionState
}
func ParseTLSVersion(version string) (uint16, error) {
switch version {
case "1.0":

View File

@@ -4,19 +4,25 @@ 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"
@@ -24,17 +30,19 @@ import (
"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 _ Config = (*RealityClientConfig)(nil)
var _ ConfigCompat = (*RealityClientConfig)(nil)
type RealityClientConfig struct {
uClient *UTLSClientConfig
publicKey []byte
shortID []byte
shortID [8]byte
}
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
@@ -54,11 +62,12 @@ func NewRealityClient(router adapter.Router, serverAddress string, options optio
if len(publicKey) != 32 {
return nil, E.New("invalid public_key")
}
shortID, err := hex.DecodeString(options.Reality.ShortID)
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 len(shortID) != 8 {
if decodedLen > 8 {
return nil, E.New("invalid short_id")
}
return &RealityClientConfig{uClient, publicKey, shortID}, nil
@@ -85,6 +94,10 @@ func (e *RealityClientConfig) Config() (*STDConfig, error) {
}
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(),
}
@@ -111,9 +124,10 @@ func (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
hello.SessionId[0] = 1
hello.SessionId[1] = 7
hello.SessionId[2] = 5
copy(hello.SessionId[8:], e.shortID)
hello.SessionId[1] = 8
hello.SessionId[2] = 0
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])
@@ -137,9 +151,43 @@ func (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {
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
}
@@ -180,8 +228,5 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
if _, err := certs[0].Verify(opts); err != nil {
return err
}
if !c.verified {
return E.New("reality verification failed")
}
return nil
}

View File

@@ -8,9 +8,9 @@ import (
"encoding/base64"
"encoding/hex"
"net"
"os"
"time"
"github.com/sagernet/reality"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/log"
@@ -19,8 +19,6 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/nekohasekai/reality"
)
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
@@ -91,16 +89,16 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
tlsConfig.ShortIds = make(map[[8]byte]bool)
for i, shortID := range options.Reality.ShortID {
var shortIDBytesArray [8]byte
decodedLen, err := hex.Decode(shortIDBytesArray[:], []byte(shortID))
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, "]: ", shortID)
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
}
if decodedLen != 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortID)
if decodedLen > 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
}
tlsConfig.ShortIds[shortIDBytesArray] = true
tlsConfig.ShortIds[shortID] = true
}
handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
@@ -136,7 +134,7 @@ func (c *RealityServerConfig) Config() (*tls.Config, error) {
}
func (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) {
return nil, os.ErrInvalid
return ClientHandshake(context.Background(), conn, c)
}
func (c *RealityServerConfig) Start() error {
@@ -148,7 +146,7 @@ func (c *RealityServerConfig) Close() error {
}
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
return nil, os.ErrInvalid
return ServerHandshake(context.Background(), conn, c)
}
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {

View File

@@ -2,14 +2,13 @@ package tls
import (
"context"
"crypto/tls"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
@@ -26,23 +25,5 @@ func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, op
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
if compatServer, isCompat := config.(ServerConfigCompat); isCompat {
return compatServer.ServerHandshake(ctx, conn)
}
tlsConn, err := config.Server(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
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
return aTLS.ServerHandshake(ctx, conn, config)
}

View File

@@ -180,7 +180,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
tlsConfig.ServerName = options.ServerName
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)

View File

@@ -5,6 +5,7 @@ package tls
import (
"crypto/tls"
"crypto/x509"
"math/rand"
"net"
"net/netip"
"os"
@@ -13,6 +14,8 @@ import (
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
utls "github.com/sagernet/utls"
"golang.org/x/net/http2"
)
type UTLSClientConfig struct {
@@ -33,6 +36,9 @@ func (e *UTLSClientConfig) NextProtos() []string {
}
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
nextProto = append(nextProto, "http/1.1")
}
e.config.NextProtos = nextProto
}
@@ -159,6 +165,29 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
return &UTLSClientConfig{&tlsConfig, id}, nil
}
var (
randomFingerprint utls.ClientHelloID
randomizedFingerprint utls.ClientHelloID
)
func init() {
modernFingerprints := []utls.ClientHelloID{
utls.HelloChrome_Auto,
utls.HelloFirefox_Auto,
utls.HelloEdge_Auto,
utls.HelloSafari_Auto,
utls.HelloIOS_Auto,
}
randomFingerprint = modernFingerprints[rand.Intn(len(modernFingerprints))]
weights := utls.DefaultWeights
weights.TLSVersMax_Set_VersionTLS13 = 1
weights.FirstKeyShare_Set_CurveP256 = 0
randomizedFingerprint = utls.HelloRandomized
randomizedFingerprint.Seed, _ = utls.NewPRNGSeed()
randomizedFingerprint.Weights = &weights
}
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
switch name {
case "chrome", "":
@@ -178,7 +207,9 @@ func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
case "android":
return utls.HelloAndroid_11_OkHttp, nil
case "random":
return utls.HelloRandomized, nil
return randomFingerprint, nil
case "randomized":
return randomizedFingerprint, nil
default:
return utls.ClientHelloID{}, E.New("unknown uTLS fingerprint: ", name)
}

View File

@@ -50,6 +50,9 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
}
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
if link == "" {
link = "https://www.gstatic.com/generate_204"
}
linkURL, err := url.Parse(link)
if err != nil {
return

View File

@@ -12,6 +12,7 @@ const dirName = "sing-box"
var (
basePath string
tempPath string
resourcePaths []string
)
@@ -22,10 +23,21 @@ func BasePath(name string) string {
return filepath.Join(basePath, name)
}
func CreateTemp(pattern string) (*os.File, error) {
if tempPath == "" {
tempPath = os.TempDir()
}
return os.CreateTemp(tempPath, pattern)
}
func SetBasePath(path string) {
basePath = path
}
func SetTempPath(path string) {
tempPath = path
}
func FindPath(name string) (string, bool) {
name = os.ExpandEnv(name)
if rw.FileExists(name) {

3
constant/time.go Normal file
View File

@@ -0,0 +1,3 @@
package constant
const TimeLayout = "2006-01-02 15:04:05 -0700"

View File

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

35
debug.go Normal file
View File

@@ -0,0 +1,35 @@
//go:build go1.19
package box
import (
"runtime/debug"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/option"
)
func applyDebugOptions(options option.DebugOptions) {
if options.GCPercent != nil {
debug.SetGCPercent(*options.GCPercent)
}
if options.MaxStack != nil {
debug.SetMaxStack(*options.MaxStack)
}
if options.MaxThreads != nil {
debug.SetMaxThreads(*options.MaxThreads)
}
if options.PanicOnFault != nil {
debug.SetPanicOnFault(*options.PanicOnFault)
}
if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(options.MemoryLimit))
conntrack.MemoryLimit = int64(options.MemoryLimit)
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller
}
}

35
debug_go118.go Normal file
View File

@@ -0,0 +1,35 @@
//go:build !go1.19
package box
import (
"runtime/debug"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/option"
)
func applyDebugOptions(options option.DebugOptions) {
if options.GCPercent != nil {
debug.SetGCPercent(*options.GCPercent)
}
if options.MaxStack != nil {
debug.SetMaxStack(*options.MaxStack)
}
if options.MaxThreads != nil {
debug.SetMaxThreads(*options.MaxThreads)
}
if options.PanicOnFault != nil {
debug.SetPanicOnFault(*options.PanicOnFault)
}
if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit != 0 {
// debug.SetMemoryLimit(int64(options.MemoryLimit))
conntrack.MemoryLimit = int64(options.MemoryLimit)
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller
}
}

37
docs/assets/icon.svg Normal file
View File

@@ -0,0 +1,37 @@
<svg width="1027" height="1109" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden">
<defs>
<filter id="fx0" x="-10%" y="-10%" width="120%" height="120%" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
<feComponentTransfer color-interpolation-filters="sRGB">
<feFuncR type="discrete" tableValues="0 0" />
<feFuncG type="discrete" tableValues="0 0" />
<feFuncB type="discrete" tableValues="0 0" />
<feFuncA type="linear" slope="0.4" intercept="0" />
</feComponentTransfer>
<feGaussianBlur stdDeviation="4.58333 4.58333" />
</filter>
<clipPath id="clip1">
<rect x="692" y="855" width="1027" height="1109" />
</clipPath>
<clipPath id="clip2">
<rect x="-2" y="-2" width="541" height="786" />
</clipPath>
<clipPath id="clip3">
<rect x="0" y="0" width="535" height="782" />
</clipPath>
</defs>
<g clip-path="url(#clip1)" transform="translate(-692 -855)">
<path d="M692 1191 692 1575.69C692 1640.41 731.499 1651.19 731.499 1651.19L1148.03 1931.62C1212.66 1974.77 1194.71 1881.29 1194.71 1881.29L1194.71 1528.96 692 1191Z" fill="#37474F" fill-rule="evenodd" />
<g clip-path="url(#clip2)" filter="url(#fx0)" transform="translate(1184 1182)">
<g clip-path="url(#clip3)">
<path d="M520.482 15.4819 520.482 400.176C520.482 464.89 480.983 475.676 480.983 475.676 480.983 475.676 129.086 712.963 64.4523 756.106-0.181814 799.25 17.7721 705.773 17.7721 705.773L17.7721 353.437 520.482 15.4819Z" fill="#455A64" fill-rule="evenodd" />
</g>
</g>
<path d="M1698 1191 1698 1575.69C1698 1640.41 1658.5 1651.19 1658.5 1651.19 1658.5 1651.19 1306.6 1888.48 1241.97 1931.62 1177.34 1974.77 1195.29 1881.29 1195.29 1881.29L1195.29 1528.96 1698 1191Z" fill="#455A64" fill-rule="evenodd" />
<path d="M1241.71 868.473C1212.96 850.509 1169.85 850.509 1144.7 868.473L713.557 1163.07C684.814 1181.04 684.814 1213.37 713.557 1231.33L1144.7 1529.53C1173.44 1547.49 1216.56 1547.49 1241.71 1529.53L1676.44 1227.74C1705.19 1209.78 1705.19 1177.44 1676.44 1159.48L1241.71 868.473Z" fill="#546E7A" fill-rule="evenodd" />
<path d="M1195 1949C1173.4 1949 1159 1935.19 1159 1917.92L1159 1531.08C1159 1513.82 1173.4 1500 1195 1500 1216.6 1500 1231 1513.82 1231 1531.08L1231 1914.46C1231 1935.19 1216.6 1949 1195 1949Z" fill="#546E7A" fill-rule="evenodd" />
<path d="M1553.92 1435.92C1553.92 1471.89 1557.5 1486.27 1518.03 1511.45L1428.32 1568.99C1388.85 1594.17 1374.5 1572.59 1374.5 1540.22L1374.5 1446.71C1374.5 1439.52 1374.5 1435.92 1363.73 1428.73 1270.43 1363.99 911.591 1115.84 847 1069.09L1012.07 954C1058.72 982.772 1399.61 1209.35 1539.56 1306.45 1546.74 1310.05 1550.33 1317.24 1550.33 1320.84L1550.33 1435.92Z" fill="#99AAB5" fill-rule="evenodd" />
<path d="M1543.41 1310.21C1399.82 1213.17 1058.79 986.752 1015.72 958L951.103 997.534 847 1069.41C911.615 1116.14 1270.59 1360.53 1363.92 1425.22 1371.1 1428.81 1371.1 1432.41 1371.1 1436L1547 1313.8C1547 1313.8 1547 1310.21 1543.41 1310.21Z" fill="#CCD6DD" fill-rule="evenodd" />
<path d="M1554.9 1435.48 1554.9 1324.19C1554.9 1317.01 1551.3 1313.42 1544.11 1309.83 1400.28 1212.89 1058.67 986.721 1015.51 958L940 1008.26C1062.26 1090.83 1389.49 1306.24 1475.79 1367.27 1486.58 1374.45 1486.58 1381.63 1486.58 1385.22L1486.58 1536 1522.54 1510.87C1558.5 1485.74 1554.9 1467.79 1554.9 1435.48Z" fill="#CCD6DD" fill-rule="evenodd" />
<path d="M1543.23 1309.95C1399.6 1212.98 1058.49 986.731 1015.4 958L940 1008.28C1062.08 1090.88 1388.83 1306.36 1475.01 1367.41 1475.01 1367.41 1478.6 1371 1478.6 1371L1554 1317.13C1546.82 1313.54 1546.82 1309.95 1543.23 1309.95Z" fill="#E1E8ED" fill-rule="evenodd" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,3 +1,137 @@
#### 1.3-beta5
* Add Clash.Meta API compatibility for Clash API
* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty
* Add path and headers option for HTTP outbound
* Fixes and improvements
#### 1.3-beta4
* Fix bugs
#### 1.3-beta2
* Download clash-dashboard if the specified Clash `external_ui` directory is empty
* Fix bugs and update dependencies
#### 1.3-beta1
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
* Add [L3 routing](/configuration/route/ip-rule) support **1**
* Add `rewrite_ttl` DNS rule action
* Add [FakeIP](/configuration/dns/fakeip) support **2**
* Add `store_fakeip` Clash API option
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
* Add loopback detect
*1*:
It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct) or block connections
at the IP layer.
*2*:
See [FAQ](/faq/fakeip) for more information.
#### 1.2.3
* Introducing our [new Android client application](/installation/clients/sfa)
* Improve UDP domain destination NAT
* Update reality protocol
* Fix TTL calculation for DNS response
* Fix v2ray HTTP transport compatibility
* Fix bugs and update dependencies
#### 1.2.2
* Accept `any` outbound in dns rule **1**
* Fix bugs and update dependencies
*1*:
Now you can use the `any` outbound rule to match server address queries instead of filling in all server domains
to `domain` rule.
#### 1.2.1
* Fix missing default host in v2ray http transport`s request
* Flush DNS cache for macOS when tun start/close
* Fix tun's DNS hijacking compatibility with systemd-resolved
#### 1.2.0
* Fix bugs and update dependencies
Important changes since 1.1:
* Introducing our [new iOS client application](/installation/clients/sfi)
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
* Add [reality TLS](/configuration/shared/tls) support
* Add [NTP service](/configuration/ntp)
* Add [DHCP DNS server](/configuration/dns/server) support
* Add SSH [host key validation](/configuration/outbound/ssh) support
* Add [query_type](/configuration/dns/rule) DNS rule item
* Add fallback support for v2ray transport
* Add custom TLS server support for http based v2ray transports
* Add health check support for http-based v2ray transports
* Add multiple configuration support
#### 1.2-rc1
* Fix bugs and update dependencies
#### 1.2-beta10
* Add multiple configuration support **1**
* Fix bugs and update dependencies
*1*:
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`
to load all configuration files in a directory.
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file
name.
#### 1.1.7
* Improve the stability of the VMESS server
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
* Fix bugs and update dependencies
#### 1.2-beta9
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
* Add health check support for http-based v2ray transports
* Remove length limit on short_id for reality TLS config
* Fix bugs and update dependencies
#### 1.2-beta8
* Update reality and uTLS libraries
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
#### 1.2-beta7
* Fix the compatibility issue between VLESS's vision sub-protocol and the Xray-core client
* Improve the stability of the VMESS server
#### 1.2-beta6
* Introducing our [new iOS client application](/installation/clients/sfi)
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
* Add custom TLS server support for http based v2ray transports
* Add generate commands
* Enable XUDP by default in VLESS
* Update reality server
* Update vision protocol
* Fixed [user flow in vless server](/configuration/inbound/vless#usersflow)
* Bug fixes
* Update dependencies
#### 1.2-beta5
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
@@ -92,7 +226,7 @@ Important changes since 1.0:
* Add VLESS outbound and XUDP
* Skip wait for hysteria tcp handshake response
* Add v2ray mux support for all inbound
* Add XUDP support for VMess
* Add XUDP support for VMess
* Improve websocket writer
* Refine tproxy write back
* Fix DNS leak caused by

View File

@@ -0,0 +1,25 @@
# FakeIP
### Structure
```json
{
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
```
### Fields
#### enabled
Enable FakeIP service.
#### inet4_range
IPv4 address range for FakeIP.
#### inet6_address
IPv6 address range for FakeIP.

View File

@@ -0,0 +1,25 @@
# FakeIP
### 结构
```json
{
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
```
### 字段
#### enabled
启用 FakeIP 服务。
#### inet4_range
用于 FakeIP 的 IPv4 地址范围。
#### inet6_range
用于 FakeIP 的 IPv6 地址范围。

View File

@@ -10,7 +10,9 @@
"final": "",
"strategy": "",
"disable_cache": false,
"disable_expire": false
"disable_expire": false,
"reverse_mapping": false,
"fakeip": {}
}
}
@@ -22,6 +24,7 @@
|----------|--------------------------------|
| `server` | List of [DNS Server](./server) |
| `rules` | List of [DNS Rule](./rule) |
| `fakeip` | [FakeIP](./fakeip) |
#### final
@@ -43,4 +46,15 @@ Disable dns cache.
#### disable_expire
Disable dns cache expire.
Disable dns cache expire.
#### reverse_mapping
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
Since this process relies on the act of resolving domain names by an application before making a request, it can be
problematic in environments such as macOS, where DNS is proxied and cached by the system.
#### fakeip
[FakeIP](./fakeip) settings.

View File

@@ -10,7 +10,9 @@
"final": "",
"strategy": "",
"disable_cache": false,
"disable_expire": false
"disable_expire": false,
"reverse_mapping": false,
"fakeip": {}
}
}
@@ -43,4 +45,14 @@
#### disable_expire
禁用 DNS 缓存过期。
禁用 DNS 缓存过期。
#### reverse_mapping
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
#### fakeip
[FakeIP](./fakeip) 设置。

View File

@@ -84,14 +84,16 @@
"direct"
],
"server": "local",
"disable_cache": false
"disable_cache": false,
"rewrite_ttl": 100
},
{
"type": "logical",
"mode": "and",
"rules": [],
"server": "local",
"disable_cache": false
"disable_cache": false,
"rewrite_ttl": 100
}
]
}
@@ -232,6 +234,8 @@ Invert match result.
Match outbound.
`any` can be used as a value to match any outbound.
#### server
==Required==
@@ -242,6 +246,10 @@ Tag of the target dns server.
Disable cache and save cache in this query.
#### rewrite_ttl
Rewrite TTL in DNS responses.
### Logical Fields
#### type
@@ -254,18 +262,4 @@ Disable cache and save cache in this query.
#### rules
Included default rules.
#### invert
Invert match result.
#### server
==Required==
Tag of the target dns server.
#### disable_cache
Disable cache and save cache in this query.
Included default rules.

View File

@@ -231,6 +231,8 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配出站。
`any` 可作为值用于匹配任意出站。
#### server
==必填==
@@ -241,6 +243,10 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
在此查询中禁用缓存。
#### rewrite_ttl
重写 DNS 回应中的 TTL。
### 逻辑字段
#### type
@@ -253,18 +259,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### rules
包括的默认规则。
#### invert
反选匹配结果。
#### server
==必填==
目标 DNS 服务器的标签。
#### disable_cache
在此查询中禁用缓存。
包括的默认规则。

View File

@@ -30,17 +30,18 @@ The tag of the dns server.
The address of the dns server.
| Protocol | Format |
|----------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
| Protocol | Format |
|---------------------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
| [FakeIP](./fakeip) | `fakeip` |
!!! warning ""

View File

@@ -30,17 +30,18 @@ DNS 服务器的标签。
DNS 服务器的地址。
| 协议 | 格式 |
|----------|------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` |
| 协议 | 格式 |
|--------------------|------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` |
| [FakeIP](./fakeip) | `fakeip` |
!!! warning ""

View File

@@ -8,6 +8,8 @@
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "folder",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "rule",
"store_selected": false,
@@ -53,6 +55,18 @@ A relative path to the configuration directory or an absolute path to a
directory in which you put some static web resource. sing-box will then
serve it at `http://{{external-controller}}/ui`.
#### external_ui_download_url
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
#### external_ui_download_detour
The tag of the outbound to download the external UI.
Default outbound will be used if empty.
#### secret
Secret for the RESTful API (optional)

View File

@@ -8,6 +8,8 @@
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "folder",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "rule",
"store_selected": false,
@@ -51,6 +53,18 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
#### external_ui_download_url
静态网页资源的 ZIP 下载 URL如果指定的 `external_ui` 目录为空,将使用。
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`
#### external_ui_download_detour
用于下载静态网页资源的出站的标签。
如果为空,将使用默认出站。
#### secret
RESTful API 的密钥(可选)

View File

@@ -40,4 +40,8 @@ No authentication required if empty.
Only supported on Linux, Android, Windows, and macOS.
!!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -40,4 +40,8 @@ HTTP 用户
仅支持 Linux、Android、Windows 和 macOS。
!!! warning ""
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -74,14 +74,10 @@ Hysteria users
#### users.auth
==Required if `auth_str` is empty==
Authentication password, in base64.
#### users.auth_str
==Required if `auth` is empty==
Authentication password.
#### recv_window_conn

View File

@@ -74,14 +74,10 @@ Hysteria 用户
#### users.auth
==与 auth_str 必填一个==
base64 编码的认证密码。
#### users.auth_str
==与 auth 必填一个==
认证密码。
#### recv_window_conn

View File

@@ -37,4 +37,8 @@ No authentication required if empty.
Only supported on Linux, Android, Windows, and macOS.
Automatically set system proxy configuration when start and clean up when stop.
!!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -37,4 +37,8 @@ SOCKS 和 HTTP 用户
仅支持 Linux、Android、Windows 和 macOS。
!!! warning ""
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -77,11 +77,11 @@ Both if empty.
==Required==
| Method | Password Format |
|---------------|-------------------------------------|
| none | / |
| 2022 methods | `openssl rand -base64 <Key Length>` |
| other methods | any string |
| Method | Password Format |
|---------------|------------------------------------------------|
| none | / |
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
| other methods | any string |
### Listen Fields

View File

@@ -77,8 +77,8 @@ See [Listen Fields](/configuration/shared/listen) for details.
==必填==
| 方法 | 密码格式 |
|---------------|-------------------------------|
| none | / |
| 2022 methods | `openssl rand -base64 <密钥长度>` |
| other methods | 任意字符串 |
| 方法 | 密码格式 |
|---------------|------------------------------------------|
| none | / |
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
| other methods | 任意字符串 |

View File

@@ -68,7 +68,7 @@ Only available in the ShadowTLS protocol 3.
Handshake server address and [Dial options](/configuration/shared/dial).
#### handshake
#### handshake_for_server_name
Handshake server address and [Dial options](/configuration/shared/dial) for specific server name.

View File

@@ -67,7 +67,7 @@ ShadowTLS 用户。
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
#### handshake
#### handshake_for_server_name
==必填==

View File

@@ -46,8 +46,15 @@
"exclude_package": [
"com.android.captiveportallogin"
],
...
// Listen Fields
"platform": {
"http_proxy": {
"enabled": false,
"server": "127.0.0.1",
"server_port": 8080
}
},
... // Listen Fields
}
```
@@ -100,8 +107,7 @@ Enforce strict routing rules when `auto_route` is enabled:
* Let unsupported network unreachable
* Route all connections to tun
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
not be accessible by others.
It prevents address leaks and makes DNS hijacking work on Android, but your device will not be accessible by others.
*In Windows*:
@@ -187,6 +193,14 @@ Limit android packages in route.
Exclude android packages in route.
#### platform
Platform-specific settings, provided by client applications.
#### platform.http_proxy
System HTTP proxy settings.
### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details.

View File

@@ -46,8 +46,15 @@
"exclude_package": [
"com.android.captiveportallogin"
],
...
// 监听字段
"platform": {
"http_proxy": {
"enabled": false,
"server": "127.0.0.1",
"server_port": 8080
}
},
... // 监听字段
}
```
@@ -100,7 +107,7 @@ tun 接口的 IPv6 前缀。
* 让不支持的网络无法到达
* 将所有连接路由到 tun
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作,但你的设备将无法其他设备被访问。
*在 Windows 中*:
@@ -148,19 +155,19 @@ TCP/IP 栈。
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`
限制被路由的用户。默认不限制。
限制被路由的用户。默认不限制。
#### include_uid_range
限制被路由的用户范围。
限制被路由的用户范围。
#### exclude_uid
排除路由的用户。
排除路由的用户。
#### exclude_uid_range
排除路由的用户范围。
排除路由的用户范围。
#### include_android_user
@@ -183,6 +190,14 @@ TCP/IP 栈。
排除路由的 Android 应用包名。
#### platform
平台特定的设置,由客户端应用提供。
#### platform.http_proxy
系统 HTTP 代理设置。
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。

View File

@@ -10,7 +10,8 @@
"users": [
{
"name": "sekai",
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
"flow": ""
}
],
"tls": {},
@@ -30,6 +31,20 @@ See [Listen Fields](/configuration/shared/listen) for details.
VLESS users.
#### users.uuid
==Required==
VLESS user id.
#### users.flow
VLESS Sub-protocol.
Available values:
* `xtls-rprx-vision`
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).

View File

@@ -10,7 +10,8 @@
"users": [
{
"name": "sekai",
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
"flow": ""
}
],
"tls": {},
@@ -30,6 +31,20 @@
VLESS 用户。
#### users.uuid
==必填==
VLESS 用户 ID。
#### users.flow
VLESS 子协议。
可用值:
* `xtls-rprx-vision`
#### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。

View File

@@ -11,6 +11,8 @@
"server_port": 1080,
"username": "sekai",
"password": "admin",
"path": "",
"headers": {},
"tls": {},
... // Dial Fields
@@ -39,6 +41,14 @@ Basic authorization username.
Basic authorization password.
#### path
Path of HTTP request.
#### headers
Extra headers of HTTP request.
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).

View File

@@ -11,6 +11,8 @@
"server_port": 1080,
"username": "sekai",
"password": "admin",
"path": "",
"headers": {},
"tls": {},
... // 拨号字段
@@ -39,6 +41,14 @@ Basic 认证用户名。
Basic 认证密码。
#### path
HTTP 请求路径。
#### headers
HTTP 请求的额外标头。
#### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。

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