Update On Sat Mar 7 19:46:43 CET 2026

This commit is contained in:
github-action[bot]
2026-03-07 19:46:44 +01:00
parent a8d097aeb5
commit 7f8e87395d
138 changed files with 8246 additions and 2320 deletions
+1
View File
@@ -1291,3 +1291,4 @@ Update On Mon Mar 2 20:01:19 CET 2026
Update On Wed Mar 4 20:06:13 CET 2026
Update On Thu Mar 5 20:25:40 CET 2026
Update On Fri Mar 6 20:05:10 CET 2026
Update On Sat Mar 7 19:46:34 CET 2026
+3 -3
View File
@@ -2,10 +2,10 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.20",
"mihomo_alpha": "alpha-b7b05e0",
"mihomo_alpha": "alpha-7f772de",
"clash_rs": "v0.9.4",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.9.4-alpha+sha.e11288b"
"clash_rs_alpha": "latest"
},
"arch_template": {
"mihomo": {
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2026-03-04T22:36:39.634Z"
"updated_at": "2026-03-06T22:22:44.355Z"
}
+3 -3
View File
@@ -92,14 +92,14 @@ jobs:
cache: "pnpm"
cache-dependency-path: "frontend/pnpm-lock.yaml"
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Install Task
uses: go-task/setup-task@v1
- run: task build:frontend
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Install Task
uses: go-task/setup-task@v1
- name: Build site
@@ -39,7 +39,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Install Task
uses: go-task/setup-task@v1
- name: Build site
+107 -107
View File
@@ -101,7 +101,7 @@ importers:
devDependencies:
'@intlify/unplugin-vue-i18n':
specifier: ^11.0.1
version: 11.0.7(@vue/compiler-dom@3.5.29)(eslint@10.0.2)(rollup@4.57.1)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
version: 11.0.7(@vue/compiler-dom@3.5.29)(eslint@10.0.3)(rollup@4.57.1)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
'@tsconfig/node24':
specifier: ^24.0.2
version: 24.0.4
@@ -113,7 +113,7 @@ importers:
version: 24.12.0
'@typescript-eslint/eslint-plugin':
specifier: ^8.37.0
version: 8.56.1(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(typescript@5.9.3)
version: 8.56.1(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)
'@vitejs/plugin-legacy':
specifier: ^7.2.1
version: 7.2.1(terser@5.46.0)(vite@7.3.1(@types/node@24.12.0)(terser@5.46.0)(yaml@2.8.2))
@@ -122,10 +122,10 @@ importers:
version: 6.0.4(vite@7.3.1(@types/node@24.12.0)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
'@vue/eslint-config-prettier':
specifier: ^10.2.0
version: 10.2.0(eslint@10.0.2)(prettier@3.8.1)
version: 10.2.0(eslint@10.0.3)(prettier@3.8.1)
'@vue/eslint-config-typescript':
specifier: ^14.6.0
version: 14.7.0(eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(vue-eslint-parser@10.4.0(eslint@10.0.2)))(eslint@10.0.2)(typescript@5.9.3)
version: 14.7.0(eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(vue-eslint-parser@10.4.0(eslint@10.0.3)))(eslint@10.0.3)(typescript@5.9.3)
'@vue/tsconfig':
specifier: ^0.9.0
version: 0.9.0(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
@@ -134,16 +134,16 @@ importers:
version: 10.4.27(postcss@8.5.8)
eslint:
specifier: ^10.0.0
version: 10.0.2
version: 10.0.3
eslint-config-prettier:
specifier: ^10.1.5
version: 10.1.8(eslint@10.0.2)
version: 10.1.8(eslint@10.0.3)
eslint-plugin-prettier:
specifier: ^5.5.1
version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.2))(eslint@10.0.2)(prettier@3.8.1)
version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.3))(eslint@10.0.3)(prettier@3.8.1)
eslint-plugin-vue:
specifier: ^10.5.1
version: 10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(vue-eslint-parser@10.4.0(eslint@10.0.2))
version: 10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(vue-eslint-parser@10.4.0(eslint@10.0.3))
postcss:
specifier: ^8.5.6
version: 8.5.8
@@ -990,24 +990,24 @@ packages:
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/config-array@0.23.2':
resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==}
'@eslint/config-array@0.23.3':
resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/config-helpers@0.5.2':
resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==}
'@eslint/config-helpers@0.5.3':
resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/core@1.1.0':
resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
'@eslint/core@1.1.1':
resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/object-schema@3.0.2':
resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==}
'@eslint/object-schema@3.0.3':
resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/plugin-kit@0.6.0':
resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==}
'@eslint/plugin-kit@0.6.1':
resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@humanfs/core@0.19.1':
@@ -1882,8 +1882,8 @@ packages:
'@typescript-eslint/parser':
optional: true
eslint-scope@9.1.1:
resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==}
eslint-scope@9.1.2:
resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint-visitor-keys@3.4.3:
@@ -1894,8 +1894,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint@10.0.2:
resolution: {integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==}
eslint@10.0.3:
resolution: {integrity: sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -1908,8 +1908,8 @@ packages:
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
engines: {node: '>=0.10'}
espree@11.1.1:
resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==}
espree@11.2.0:
resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
espree@9.6.1:
@@ -1997,8 +1997,8 @@ packages:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
flatted@3.3.4:
resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==}
focus-trap@8.0.0:
resolution: {integrity: sha512-Aa84FOGHs99vVwufDMdq2qgOwXPC2e9U66GcqBhn1/jEHPDhJaP8PYhkIbqG9lhfL5Kddk/567lj46LLHYCRUw==}
@@ -2236,8 +2236,8 @@ packages:
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
engines: {node: 18 || 20 || >=22}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
minimatch@9.0.9:
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
engines: {node: '>=16 || 14 >=14.17'}
mitt@3.0.1:
@@ -3595,34 +3595,34 @@ snapshots:
'@esbuild/win32-x64@0.27.3':
optional: true
'@eslint-community/eslint-utils@4.9.1(eslint@10.0.2)':
'@eslint-community/eslint-utils@4.9.1(eslint@10.0.3)':
dependencies:
eslint: 10.0.2
eslint: 10.0.3
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
'@eslint/config-array@0.23.2':
'@eslint/config-array@0.23.3':
dependencies:
'@eslint/object-schema': 3.0.2
'@eslint/object-schema': 3.0.3
debug: 4.4.3
minimatch: 10.2.4
transitivePeerDependencies:
- supports-color
'@eslint/config-helpers@0.5.2':
'@eslint/config-helpers@0.5.3':
dependencies:
'@eslint/core': 1.1.0
'@eslint/core': 1.1.1
'@eslint/core@1.1.0':
'@eslint/core@1.1.1':
dependencies:
'@types/json-schema': 7.0.15
'@eslint/object-schema@3.0.2': {}
'@eslint/object-schema@3.0.3': {}
'@eslint/plugin-kit@0.6.0':
'@eslint/plugin-kit@0.6.1':
dependencies:
'@eslint/core': 1.1.0
'@eslint/core': 1.1.1
levn: 0.4.1
'@humanfs/core@0.19.1': {}
@@ -3662,9 +3662,9 @@ snapshots:
'@intlify/shared@11.2.8': {}
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@10.0.2)(rollup@4.57.1)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))':
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@10.0.3)(rollup@4.57.1)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2)
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
'@intlify/bundle-utils': 11.0.7(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))
'@intlify/shared': 11.2.8
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.29)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
@@ -3845,15 +3845,15 @@ snapshots:
'@types/web-bluetooth@0.0.21': {}
'@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(typescript@5.9.3)':
'@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/parser': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.56.0
'@typescript-eslint/type-utils': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/type-utils': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.56.0
eslint: 10.0.2
eslint: 10.0.3
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -3861,15 +3861,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(typescript@5.9.3)':
'@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/parser': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.56.1
'@typescript-eslint/type-utils': 8.56.1(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.1(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/type-utils': 8.56.1(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.1(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.56.1
eslint: 10.0.2
eslint: 10.0.3
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -3877,14 +3877,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3)':
'@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.56.0
'@typescript-eslint/types': 8.56.0
'@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.56.0
debug: 4.4.3
eslint: 10.0.2
eslint: 10.0.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -3925,25 +3925,25 @@ snapshots:
dependencies:
typescript: 5.9.3
'@typescript-eslint/type-utils@8.56.0(eslint@10.0.2)(typescript@5.9.3)':
'@typescript-eslint/type-utils@8.56.0(eslint@10.0.3)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.56.0
'@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
debug: 4.4.3
eslint: 10.0.2
eslint: 10.0.3
ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/type-utils@8.56.1(eslint@10.0.2)(typescript@5.9.3)':
'@typescript-eslint/type-utils@8.56.1(eslint@10.0.3)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.1(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.1(eslint@10.0.3)(typescript@5.9.3)
debug: 4.4.3
eslint: 10.0.2
eslint: 10.0.3
ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
@@ -3960,7 +3960,7 @@ snapshots:
'@typescript-eslint/types': 8.56.0
'@typescript-eslint/visitor-keys': 8.56.0
debug: 4.4.3
minimatch: 9.0.5
minimatch: 9.0.9
semver: 7.7.4
tinyglobby: 0.2.15
ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -3983,24 +3983,24 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.56.0(eslint@10.0.2)(typescript@5.9.3)':
'@typescript-eslint/utils@8.56.0(eslint@10.0.3)(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2)
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
'@typescript-eslint/scope-manager': 8.56.0
'@typescript-eslint/types': 8.56.0
'@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
eslint: 10.0.2
eslint: 10.0.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.56.1(eslint@10.0.2)(typescript@5.9.3)':
'@typescript-eslint/utils@8.56.1(eslint@10.0.3)(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2)
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
'@typescript-eslint/scope-manager': 8.56.1
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
eslint: 10.0.2
eslint: 10.0.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -4182,23 +4182,23 @@ snapshots:
dependencies:
rfdc: 1.4.1
'@vue/eslint-config-prettier@10.2.0(eslint@10.0.2)(prettier@3.8.1)':
'@vue/eslint-config-prettier@10.2.0(eslint@10.0.3)(prettier@3.8.1)':
dependencies:
eslint: 10.0.2
eslint-config-prettier: 10.1.8(eslint@10.0.2)
eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.2))(eslint@10.0.2)(prettier@3.8.1)
eslint: 10.0.3
eslint-config-prettier: 10.1.8(eslint@10.0.3)
eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.3))(eslint@10.0.3)(prettier@3.8.1)
prettier: 3.8.1
transitivePeerDependencies:
- '@types/eslint'
'@vue/eslint-config-typescript@14.7.0(eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(vue-eslint-parser@10.4.0(eslint@10.0.2)))(eslint@10.0.2)(typescript@5.9.3)':
'@vue/eslint-config-typescript@14.7.0(eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(vue-eslint-parser@10.4.0(eslint@10.0.3)))(eslint@10.0.3)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/utils': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
eslint: 10.0.2
eslint-plugin-vue: 10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(vue-eslint-parser@10.4.0(eslint@10.0.2))
'@typescript-eslint/utils': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
eslint: 10.0.3
eslint-plugin-vue: 10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(vue-eslint-parser@10.4.0(eslint@10.0.3))
fast-glob: 3.3.3
typescript-eslint: 8.56.0(eslint@10.0.2)(typescript@5.9.3)
vue-eslint-parser: 10.4.0(eslint@10.0.2)
typescript-eslint: 8.56.0(eslint@10.0.3)(typescript@5.9.3)
vue-eslint-parser: 10.4.0(eslint@10.0.3)
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
@@ -4557,33 +4557,33 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
eslint-config-prettier@10.1.8(eslint@10.0.2):
eslint-config-prettier@10.1.8(eslint@10.0.3):
dependencies:
eslint: 10.0.2
eslint: 10.0.3
eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.2))(eslint@10.0.2)(prettier@3.8.1):
eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.3))(eslint@10.0.3)(prettier@3.8.1):
dependencies:
eslint: 10.0.2
eslint: 10.0.3
prettier: 3.8.1
prettier-linter-helpers: 1.0.1
synckit: 0.11.12
optionalDependencies:
eslint-config-prettier: 10.1.8(eslint@10.0.2)
eslint-config-prettier: 10.1.8(eslint@10.0.3)
eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(vue-eslint-parser@10.4.0(eslint@10.0.2)):
eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(vue-eslint-parser@10.4.0(eslint@10.0.3)):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2)
eslint: 10.0.2
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
eslint: 10.0.3
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 7.1.1
semver: 7.7.4
vue-eslint-parser: 10.4.0(eslint@10.0.2)
vue-eslint-parser: 10.4.0(eslint@10.0.3)
xml-name-validator: 4.0.0
optionalDependencies:
'@typescript-eslint/parser': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/parser': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
eslint-scope@9.1.1:
eslint-scope@9.1.2:
dependencies:
'@types/esrecurse': 4.3.1
'@types/estree': 1.0.8
@@ -4594,14 +4594,14 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
eslint@10.0.2:
eslint@10.0.3:
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2)
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.2
'@eslint/config-helpers': 0.5.2
'@eslint/core': 1.1.0
'@eslint/plugin-kit': 0.6.0
'@eslint/config-array': 0.23.3
'@eslint/config-helpers': 0.5.3
'@eslint/core': 1.1.1
'@eslint/plugin-kit': 0.6.1
'@humanfs/node': 0.16.7
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3
@@ -4610,9 +4610,9 @@ snapshots:
cross-spawn: 7.0.6
debug: 4.4.3
escape-string-regexp: 4.0.0
eslint-scope: 9.1.1
eslint-scope: 9.1.2
eslint-visitor-keys: 5.0.1
espree: 11.1.1
espree: 11.2.0
esquery: 1.7.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
@@ -4636,7 +4636,7 @@ snapshots:
event-emitter: 0.3.5
type: 2.7.3
espree@11.1.1:
espree@11.2.0:
dependencies:
acorn: 8.16.0
acorn-jsx: 5.3.2(acorn@8.16.0)
@@ -4716,10 +4716,10 @@ snapshots:
flat-cache@4.0.1:
dependencies:
flatted: 3.3.3
flatted: 3.3.4
keyv: 4.5.4
flatted@3.3.3: {}
flatted@3.3.4: {}
focus-trap@8.0.0:
dependencies:
@@ -4931,7 +4931,7 @@ snapshots:
dependencies:
brace-expansion: 5.0.4
minimatch@9.0.5:
minimatch@9.0.9:
dependencies:
brace-expansion: 2.0.2
@@ -5253,13 +5253,13 @@ snapshots:
type@2.7.3: {}
typescript-eslint@8.56.0(eslint@10.0.2)(typescript@5.9.3):
typescript-eslint@8.56.0(eslint@10.0.3)(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/parser': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
'@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/parser': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.0(eslint@10.0.2)(typescript@5.9.3)
eslint: 10.0.2
'@typescript-eslint/utils': 8.56.0(eslint@10.0.3)(typescript@5.9.3)
eslint: 10.0.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -5376,13 +5376,13 @@ snapshots:
vscode-uri@3.1.0: {}
vue-eslint-parser@10.4.0(eslint@10.0.2):
vue-eslint-parser@10.4.0(eslint@10.0.3):
dependencies:
debug: 4.4.3
eslint: 10.0.2
eslint-scope: 9.1.1
eslint: 10.0.3
eslint-scope: 9.1.2
eslint-visitor-keys: 5.0.1
espree: 11.1.1
espree: 11.2.0
esquery: 1.7.0
semver: 7.7.4
transitivePeerDependencies:
+4
View File
@@ -183,6 +183,10 @@ func (c *Counter) doRollUp(fromLabel, toLabel pb.RollUpLabel, rollUpDuration, tr
for _, h := range c.history {
// case 1: h should not be rolled up
if h.GetRollUp() != fromLabel {
if last != nil {
newHistory = append(newHistory, last)
last = nil
}
newHistory = append(newHistory, h)
continue
}
@@ -749,7 +749,6 @@ function gen_config(var)
local fakedns = nil
local routing = nil
local observatory = nil
local burstObservatory = nil
local strategy = nil
local inbounds = {}
local outbounds = {}
@@ -975,29 +974,19 @@ function gen_config(var)
fallbackTag = fallback_node_tag,
strategy = strategy
})
if _node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" or fallback_node_tag then
if _node.balancingStrategy == "leastLoad" then
if not burstObservatory then
burstObservatory = {
subjectSelector = { "blc-" },
pingConfig = {
destination = _node.useCustomProbeUrl and _node.probeUrl or nil,
interval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m",
sampling = 3,
timeout = "5s"
}
}
end
else
if not observatory then
observatory = {
subjectSelector = { "blc-" },
probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil,
probeInterval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m",
enableConcurrency = true
}
end
if not observatory and (_node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" or fallback_node_tag) then
local t = api.format_go_time(_node.probeInterval)
if t == "0s" then
t = "60s"
elseif not t:find("[hm]") and tonumber(t:match("%d+")) < 10 then
t = "10s"
end
observatory = {
subjectSelector = { "blc-" },
probeUrl = _node.useCustomProbeUrl and _node.probeUrl or "https://www.google.com/generate_204",
probeInterval = t,
enableConcurrency = true
}
end
local loopback_outbound = gen_loopback(loopback_tag, loopback_dst)
local inbound_tag = loopback_outbound.settings.inboundTag
@@ -1652,8 +1641,7 @@ function gen_config(var)
-- 传出连接
outbounds = outbounds,
-- 连接观测
observatory = (not burstObservatory) and observatory or nil,
burstObservatory = burstObservatory,
observatory = observatory,
-- 路由
routing = routing,
-- 本地策略
+1 -1
View File
@@ -1 +1 @@
cba7b9ac0399055aa49fbdc57c03c374f58e1597
d181863d6a4aa2e7bb7eaf67c1d512c5e4827fde
+3 -3
View File
@@ -1,5 +1,5 @@
VERSION_CODE=631
VERSION_NAME=1.13.1
GO_VERSION=go1.25.7
VERSION_CODE=632
VERSION_NAME=1.13.2
GO_VERSION=go1.25.8
@@ -60,10 +60,15 @@ public struct ConnectionListView: View {
#endif
.alert($viewModel.alert)
.onAppear {
if !environments.connectionSearchText.isEmpty {
viewModel.searchText = environments.connectionSearchText
viewModel.isSearching = true
}
viewModel.connect()
commandClient.connect()
}
.onDisappear {
environments.connectionSearchText = viewModel.searchText
viewModel.disconnect()
commandClient.disconnect()
}
@@ -15,15 +15,16 @@ public struct LogView: View {
public init() {}
public var body: some View {
LogViewContent(commandClient: environments.commandClient)
LogViewContent(commandClient: environments.commandClient, initialSearchText: environments.logSearchText)
}
}
private struct LogViewContent: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@StateObject private var viewModel: LogViewModel
init(commandClient: CommandClient) {
_viewModel = StateObject(wrappedValue: LogViewModel(commandClient: commandClient))
init(commandClient: CommandClient, initialSearchText: String = "") {
_viewModel = StateObject(wrappedValue: LogViewModel(commandClient: commandClient, searchText: initialSearchText))
}
var body: some View {
@@ -35,6 +36,9 @@ private struct LogViewContent: View {
toolbarButtons
}
}
.onDisappear {
environments.logSearchText = viewModel.searchText
}
.alert($viewModel.alert)
.background(
LogExportView(
@@ -18,6 +18,8 @@ public class LogDataModel: ObservableObject {
private let commandClient: CommandClient
private weak var viewModel: LogViewModel?
private var pausedLogSnapshot: [LogEntry]?
private var lastPaused = false
private var lastProcessedLogCount = 0
private var lastEffectiveLevel: Int?
private var lastSearchText = ""
@@ -48,38 +50,59 @@ public class LogDataModel: ObservableObject {
let debouncedSearchText = viewModel.$searchText
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
Publishers.CombineLatest4(
commandClient.$logList,
commandClient.$defaultLogLevel,
viewModel.$selectedLogLevel,
debouncedSearchText
Publishers.CombineLatest(
Publishers.CombineLatest4(
commandClient.$logList,
commandClient.$defaultLogLevel,
viewModel.$selectedLogLevel,
debouncedSearchText
),
viewModel.$isPaused
)
.receive(on: DispatchQueue.main)
.sink { [weak self] logList, defaultLogLevel, selectedLogLevel, searchText in
.sink { [weak self] combined, isPaused in
guard let self else { return }
let (logList, defaultLogLevel, selectedLogLevel, searchText) = combined
let effectiveLevel = selectedLogLevel ?? defaultLogLevel
if isPaused, !self.lastPaused {
self.pausedLogSnapshot = logList
self.lastProcessedLogCount = 0
} else if !isPaused, self.lastPaused {
self.pausedLogSnapshot = nil
self.lastProcessedLogCount = 0
}
self.lastPaused = isPaused
let sourceList = self.pausedLogSnapshot ?? logList
if isPaused, effectiveLevel == self.lastEffectiveLevel, searchText == self.lastSearchText,
self.lastProcessedLogCount > 0
{
return
}
let canIncrement = self.lastProcessedLogCount > 0 &&
logList.count > self.lastProcessedLogCount &&
sourceList.count > self.lastProcessedLogCount &&
effectiveLevel == self.lastEffectiveLevel &&
searchText == self.lastSearchText
if canIncrement {
let newLogs = logList[self.lastProcessedLogCount...]
let newLogs = sourceList[self.lastProcessedLogCount...]
let newFilteredLogs = newLogs.filter { log in
log.level <= effectiveLevel &&
(searchText.isEmpty || log.message.contains(searchText))
}
self.filteredLogs.append(contentsOf: newFilteredLogs)
} else {
self.filteredLogs = logList.filter { log in
self.filteredLogs = sourceList.filter { log in
log.level <= effectiveLevel &&
(searchText.isEmpty || log.message.contains(searchText))
}
}
self.updateVisibleLogs()
self.lastProcessedLogCount = logList.count
self.lastProcessedLogCount = sourceList.count
self.lastEffectiveLevel = effectiveLevel
self.lastSearchText = searchText
}
@@ -88,6 +111,8 @@ public class LogDataModel: ObservableObject {
public func clearLogs() {
viewModel?.isPaused = false
pausedLogSnapshot = nil
lastPaused = false
lastProcessedLogCount = 0
lastEffectiveLevel = nil
lastSearchText = ""
@@ -151,8 +176,10 @@ public class LogViewModel: BaseViewModel {
public let commandClient: CommandClient
public private(set) var dataModel: LogDataModel!
public init(commandClient: CommandClient) {
public init(commandClient: CommandClient, searchText: String = "") {
self.commandClient = commandClient
self.searchText = searchText
self.isSearching = !searchText.isEmpty
super.init()
dataModel = LogDataModel(commandClient: commandClient, viewModel: self)
}
@@ -19,6 +19,7 @@ public struct CoreView: View {
@State private var version = ""
@State private var dataSize: String?
@State private var dataSizeLoaded = false
#if os(macOS)
@State private var helperUnavailable = false
@@ -38,6 +39,12 @@ public struct CoreView: View {
FormTextItem("Version", version)
if let dataSize {
FormTextItem("Data Size", dataSize)
} else if !dataSizeLoaded {
HStack {
Text("Data Size")
Spacer()
ProgressView()
}
} else {
#if os(macOS)
HStack {
@@ -124,11 +131,6 @@ public struct CoreView: View {
} else {
await MainActor.run {
version = LibboxVersion()
#if os(macOS)
if Variant.useSystemExtension {
helperUnavailable = HelperServiceManager.rootHelperStatus != .enabled
}
#endif
isLoading = false
}
await loadSettingsBackground()
@@ -173,6 +175,7 @@ public struct CoreView: View {
self.helperUnavailable = helperUnavailable
#endif
self.dataSize = dataSize
self.dataSizeLoaded = true
}
}
@@ -29,6 +29,7 @@ public struct AppView: View {
@Environment(\.showMenuBarExtra) private var showMenuBarExtra
@Environment(\.menuBarExtraSpeedMode) private var menuBarExtraSpeedMode
@State private var menuBarExtraInBackground = false
@State private var helperStatusLoaded = false
@State private var rootHelperRegistrationStatus: SMAppService.Status = .notRegistered
#endif
@@ -108,7 +109,9 @@ public struct AppView: View {
}
Section {
if rootHelperRegistrationStatus == .enabled {
if !helperStatusLoaded {
ProgressView()
} else if rootHelperRegistrationStatus == .enabled {
FormButton {
Task {
do {
@@ -171,11 +174,14 @@ public struct AppView: View {
#if os(macOS)
startAtLogin = SMAppService.mainApp.status == .enabled
menuBarExtraInBackground = await SharedPreferences.menuBarExtraInBackground.get()
if Variant.useSystemExtension {
refreshHelperStatus()
}
#endif
isLoading = false
#if os(macOS)
if Variant.useSystemExtension {
refreshHelperStatus()
helperStatusLoaded = true
}
#endif
}
private static func currentLanguage() -> String? {
@@ -180,6 +180,9 @@ public class ExtensionEnvironments: ObservableObject {
@Published public var emptyProfiles = false
@Published public var pendingImportRemoteProfile: ImportRemoteProfileRequest?
public var logSearchText = ""
public var connectionSearchText = ""
public let profileUpdate = ObjectWillChangePublisher()
public let selectedProfileUpdate = ObjectWillChangePublisher()
public let openSettings = ObjectWillChangePublisher()
@@ -2235,7 +2235,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
SDKROOT = appletvos;
@@ -2269,7 +2269,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
SDKROOT = appletvos;
@@ -2662,7 +2662,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
OTHER_CODE_SIGN_FLAGS = "--deep";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
@@ -2704,7 +2704,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
OTHER_CODE_SIGN_FLAGS = "--deep";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
@@ -2744,7 +2744,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
@@ -2783,7 +2783,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
@@ -2925,7 +2925,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.system;
PRODUCT_NAME = "$(inherited)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2973,7 +2973,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.system;
PRODUCT_NAME = "$(inherited)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -3016,7 +3016,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.standalone;
PRODUCT_NAME = SFM;
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -3058,7 +3058,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = "1.13.1";
MARKETING_VERSION = "1.13.2";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.standalone;
PRODUCT_NAME = SFM;
PROVISIONING_PROFILE_SPECIFIER = "";
+29
View File
@@ -2,6 +2,35 @@
icon: material/alert-decagram
---
#### 1.14.0-alpha.1
* Add `source_mac_address` and `source_hostname` rule items **1**
* Add `include_mac_address` and `exclude_mac_address` TUN options **2**
* Update NaiveProxy to 145.0.7632.159 **3**
* Fixes and improvements
**1**:
New rule items for matching LAN devices by MAC address and hostname via neighbor resolution.
Supported on Linux, macOS, or in graphical clients on Android and macOS.
See [Route Rule](/configuration/route/rule/#source_mac_address), [DNS Rule](/configuration/dns/rule/#source_mac_address) and [Neighbor Resolution](/configuration/shared/neighbor/).
**2**:
Limit or exclude devices from TUN routing by MAC address.
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
See [TUN](/configuration/inbound/tun/#include_mac_address).
**3**:
This is not an official update from NaiveProxy. Instead, it's a Chromium codebase update maintained by Project S.
#### 1.13.2
* Fixes and improvements
#### 1.13.1
* Fixes and improvements
+2 -2
View File
@@ -425,7 +425,7 @@ Match default interface address.
!!! quote ""
Only supported on Linux and macOS, or in graphical clients on Android. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Match source device MAC address.
@@ -435,7 +435,7 @@ Match source device MAC address.
!!! quote ""
Only supported on Linux and macOS, or in graphical clients on Android. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Match source device hostname from DHCP leases.
+2 -2
View File
@@ -424,7 +424,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
!!! quote ""
仅支持 LinuxmacOS,或在 Android 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
仅支持 LinuxmacOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
匹配源设备 MAC 地址。
@@ -434,7 +434,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
!!! quote ""
仅支持 LinuxmacOS,或在 Android 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
仅支持 LinuxmacOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
匹配源设备从 DHCP 租约获取的主机名。
+2 -2
View File
@@ -466,7 +466,7 @@ Match specified outbounds' preferred routes.
!!! quote ""
Only supported on Linux and macOS, or in graphical clients on Android. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Match source device MAC address.
@@ -476,7 +476,7 @@ Match source device MAC address.
!!! quote ""
Only supported on Linux and macOS, or in graphical clients on Android. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Match source device hostname from DHCP leases.
+2 -2
View File
@@ -463,7 +463,7 @@ icon: material/new-box
!!! quote ""
仅支持 LinuxmacOS,或在 Android 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
仅支持 LinuxmacOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
匹配源设备 MAC 地址。
@@ -473,7 +473,7 @@ icon: material/new-box
!!! quote ""
仅支持 LinuxmacOS,或在 Android 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
仅支持 LinuxmacOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
匹配源设备从 DHCP 租约获取的主机名。
+32 -32
View File
@@ -29,13 +29,13 @@ require (
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cors v1.2.1
github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399
github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399
github.com/sagernet/cronet-go v0.0.0-20260306075351-e5943141aa40
github.com/sagernet/cronet-go/all v0.0.0-20260306075351-e5943141aa40
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.12
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
github.com/sagernet/sing v0.8.1
github.com/sagernet/sing v0.8.2
github.com/sagernet/sing-mux v0.3.4
github.com/sagernet/sing-quic v0.6.0
github.com/sagernet/sing-shadowsocks v0.2.8
@@ -105,35 +105,35 @@ require (
github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/spf13/pflag v1.0.9 // indirect
+64 -64
View File
@@ -162,68 +162,68 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399 h1:x3tVYQHdqqnKbEd9/H4KIGhtHTjA+KfiiaXedI3/w8Q=
github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399 h1:mD3ehudpYf1IFgCTv25d/B6KnBc/lLFq1jmSQIK24y0=
github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399/go.mod h1:MbYagcGGIaRo9tNrgafbCTO+Qc7eVEh32ZWMprSB8b0=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6 h1:ghRKgSaswefPwQF8AYtUlNyumILOB0ptJWxgZ8MFrEE=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:Behr7YCnQP2dsvzAJDIoMd5nTVU9/d6MMtk/S3MctwA=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6 h1:6UL9XdGU/44oTHj36e+EBDJ0RonFoObmd299NG/qQCU=
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:Q9apxjtkj6iMIBQlTo71QsOTrNlhHneaXQb1Q0IshU8=
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:0N+xlnMkFEeqgFe3X/PEvHt+/t+BPgxmbx7wzNcYppg=
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:7f2vTXtePikBSV1bdD0zs5/WuZM+bRuej3mREpWL/qQ=
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:HMlnhEYs+axOa0tAJ79se3QsYB8CpRCQo9mewWWFeeg=
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:Ux/U6vF+1AoGLSJK3jVa9Kqkn64MX4Ivv7fy0ikDrpQ=
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:5Dhuere2bQFzfGvKxA7TFgA5MoTtgcZMmJQuKwQKlyA=
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6 h1:aMRcLow4UpZWZ28fR9FjveTL/4okrigZySIkEVZnlgA=
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6 h1:y4g8oNtEfSdcKrBKsH5vMAjzGthvhHFNU80sanYDQEM=
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:CXN6OPILi5trwffmYiiJ9rqJL3XAWx1menLrBBwA0gU=
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:ZphFHQeFOTpqCWPwFcQRnrePXajml8LbKlYFJ5n0isU=
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6 h1:nKzFK84oANHz7I6bab+25bBY+pdpAbO0b3NJroyLldo=
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:HqqZUGRXcWvvwlbuvjk/efo8TKW1H/aHdqQTde+Xs9Q=
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:D2v9lZZG5sm4x/CkG7uqc6ZU3YlhFQ+GmJfvZMK0h/s=
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6 h1:TWveNeXHrA5r8XOlf+vw7U2b2M0ip6GNF89jcUi1ogw=
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6 h1:DVCBoXOZI4PNG0cbCLg8lrphRXoLFcAIDLNmzsCVg3I=
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:7s5xqNlBUWkIXdruPYi3/txXekQhGWxrYxbnB0cnARo=
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6 h1:eyEb+Q7VH4hpE1nV+EmEnN2XX5WilgBpIsfCw4C/7no=
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6 h1:9F1W7+z1hHST6GSzdpQ8Q0NCkneAL18dkRA1HfxH09A=
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6 h1:MmQIR3iJsdvw1ONBP3geK57i9c3+v9dXPMNdZYcYGKw=
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6 h1:j6Pk1Wsl+PCbKRXtp7a912D2D6zqX5Nk51wDQU9TEDc=
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:0DnFhbRfNqwguNCxiinA7BowQ/RaFt627sjW09JNp80=
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:3CZmlEk2/WW5UHLFJZxXPJ9IJxX3td8U3PyqWSGMl3c=
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:eHkVRptoZf3BuuskkjcclO2dwQrX4zluoVGODMrX7n0=
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:UgFmE0cZo9euu8/7sTAhj1G8lldavwXBdcPNyTE29CQ=
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:xbg3ZB9tLMGDQe4+aewG0Z4bEP/2pLtYBcDzILv5eEc=
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:M0bTSTSTnSMlPY2WaZT6fL5TFICqk8v4cm+QVf8Fcao=
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
github.com/sagernet/cronet-go v0.0.0-20260306075351-e5943141aa40 h1:A9P5YN0Tq+quO9vISIOL+PkExbGWAroyNIk9pI309ls=
github.com/sagernet/cronet-go v0.0.0-20260306075351-e5943141aa40/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
github.com/sagernet/cronet-go/all v0.0.0-20260306075351-e5943141aa40 h1:0W9yjyRZ/9peX7jFlruJgOhydBzqj0u7uRY+NUFlbCE=
github.com/sagernet/cronet-go/all v0.0.0-20260306075351-e5943141aa40/go.mod h1:U54HWP2v0xDyTEpAcof98Y923Lr1ymOvFWpa8aVBBAk=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260306074725-2e4f95b376d3 h1:Par4t1sZVTJodVxVoGoaSi4MTojaDrraHXCK5Xjt/rM=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260306074725-2e4f95b376d3 h1:Wg7qunP2EtGnQSHaAL2a/shion6Y5QatyFtAoMcZjdg=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260306074725-2e4f95b376d3 h1:JZSGrRe1y5yR+REJLK2X1ZxHcUnXc110m7rEuqkhurk=
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:DwgYmuUd36tXSJuu3wK1HntOifcRPifDc/s6X6LdVSQ=
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260306074725-2e4f95b376d3 h1:jjjSy31cytxMRYLoNlwA98YasRAe0P5EEsw5c4Pwvv0=
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:2b/N8xhl+MBRIg70sHYuJ/3V3gJu3F4aVTndxFnbICU=
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3 h1:CysHa5F+LqLumG3HUfUbQzWIbG13QMTUMkkc2DTHclU=
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:Lf9FtR/87jNgc+0yeCCxlvlu2RLSrlaaYfVlYCJeFq0=
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3 h1:1X6PNucfXzZB21EOP0aBn+m06UgL6e4oJZJ2bcqrbtM=
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260306074725-2e4f95b376d3 h1:XvHeLlblB6nXilTqfDI+SxyIuR2FUkpNkL9mXNt/wNg=
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260306074725-2e4f95b376d3 h1:ACHr8UvOHs/+S29L7UcCrTe3P53NuZbKzHmwCpteyoo=
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260306074725-2e4f95b376d3 h1:MzSFaCUaGn/a4jAGw7Qnm0t5ssnx1z87YEqwvG1ZhRU=
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260306074725-2e4f95b376d3 h1:i7lFKCd4AcKut4Co/jEzvb9d1d10K3t4un9NarqAyo0=
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260306074725-2e4f95b376d3 h1:aLBHE3UGmBf+f+Vf5ceYDzsKPufDfYoMILrMhqwsJYI=
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:5CZoDiP1u3REF7LcBYoQgBuWacnBcxWeERU5UrQDqHg=
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260306074725-2e4f95b376d3 h1:4W7D6UUZH5/636fE2VMHJ+YLofmYWaBhAlvaj23C20I=
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260306074725-2e4f95b376d3 h1:zK/9ebQ3Ykcvomc+JEIou8rgIxbU1O6bBB7z2A3irO4=
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260306074725-2e4f95b376d3 h1:q79ByUHlbxPcADvOZ2G8ayCnLBlF/fzHtvLennf2clo=
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260306074725-2e4f95b376d3 h1:3RNNwgX1rltXu7gIGD12gxlIJc1s8e2stB2BzMtl9tE=
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260306074725-2e4f95b376d3 h1:/hD/Vk7/Jlg07Ic1atNjU1mXii91ziN6e3zxFYTKqio=
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260306074725-2e4f95b376d3 h1:d7Z63bQ/U7ZmB1MkC1dtAtIn6h40WrHey9S/vnfDb5g=
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260306074725-2e4f95b376d3 h1:1c6ZqstM62BrbTFrCA4vINFTCooCM8uph6uIGfAEfqQ=
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260306074725-2e4f95b376d3 h1:ZTHDXreHG+9XT0hD+MIu1etqPQAfKBApFS8Z1XMT7Nw=
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260306074725-2e4f95b376d3 h1:ry0S9V5pSNTg2wXra1rBajSITvXRufgw0u3w/mE0GB4=
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3 h1:iO5cm5MiqvKQB7QkY2b8QFgnMt3jDdOiDopX2aNsFOM=
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:DC03qT5UTbDgUzJ78xajYXq5UYcFHBLHKIoH+PRpCf0=
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3 h1:uLqZSA2OAynMxrokxVO2pW3unWA8DNjion/I4ihX/84=
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260306074725-2e4f95b376d3 h1:WHTBryhjXaniv5fMjSr/FvWKyAhdomD7rLagh4ano10=
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:TmikX4Xtalpv2Jts/MuB5qwg+KmTKbrpPf5deZGLIqA=
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg=
@@ -236,8 +236,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
github.com/sagernet/sing v0.8.1 h1:Li+zg4xdiMsvdX4j50TPqmSG8LF/TB9US2qlAN40izU=
github.com/sagernet/sing v0.8.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.8.2 h1:kX1IH9SWJv4S0T9M8O+HNahWgbOuY1VauxbF7NU5lOg=
github.com/sagernet/sing v0.8.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
github.com/sagernet/sing-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM=
@@ -1,3 +1,5 @@
//go:build with_gvisor
package tailscale
import (
+2
View File
@@ -1,3 +1,5 @@
//go:build with_gvisor
package tailscale
import (
@@ -1,3 +1,5 @@
//go:build with_gvisor
package tailscale
import (
@@ -1,4 +1,4 @@
//go:build !android
//go:build with_gvisor && !android
package tailscale
@@ -1,4 +1,4 @@
//go:build !windows
//go:build with_gvisor && !windows
package tailscale
@@ -1,4 +1,4 @@
//go:build windows
//go:build with_gvisor && windows
package tailscale
+2
View File
@@ -1,3 +1,5 @@
//go:build with_gvisor
package derp
import (
@@ -749,7 +749,6 @@ function gen_config(var)
local fakedns = nil
local routing = nil
local observatory = nil
local burstObservatory = nil
local strategy = nil
local inbounds = {}
local outbounds = {}
@@ -975,29 +974,19 @@ function gen_config(var)
fallbackTag = fallback_node_tag,
strategy = strategy
})
if _node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" or fallback_node_tag then
if _node.balancingStrategy == "leastLoad" then
if not burstObservatory then
burstObservatory = {
subjectSelector = { "blc-" },
pingConfig = {
destination = _node.useCustomProbeUrl and _node.probeUrl or nil,
interval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m",
sampling = 3,
timeout = "5s"
}
}
end
else
if not observatory then
observatory = {
subjectSelector = { "blc-" },
probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil,
probeInterval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m",
enableConcurrency = true
}
end
if not observatory and (_node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" or fallback_node_tag) then
local t = api.format_go_time(_node.probeInterval)
if t == "0s" then
t = "60s"
elseif not t:find("[hm]") and tonumber(t:match("%d+")) < 10 then
t = "10s"
end
observatory = {
subjectSelector = { "blc-" },
probeUrl = _node.useCustomProbeUrl and _node.probeUrl or "https://www.google.com/generate_204",
probeInterval = t,
enableConcurrency = true
}
end
local loopback_outbound = gen_loopback(loopback_tag, loopback_dst)
local inbound_tag = loopback_outbound.settings.inboundTag
@@ -1652,8 +1641,7 @@ function gen_config(var)
-- 传出连接
outbounds = outbounds,
-- 连接观测
observatory = (not burstObservatory) and observatory or nil,
burstObservatory = burstObservatory,
observatory = observatory,
-- 路由
routing = routing,
-- 本地策略
+2 -2
View File
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=sing-box
PKG_VERSION:=1.13.1
PKG_VERSION:=1.13.2
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=62624d4c11e318606b0dc181d1da4b2b4d7e110f67c6fb15e1ba14bb88377f69
PKG_HASH:=04b72fcd355c36a85eb028f47986894e9cf4dadbea3fee79f6891481cabeb692
PKG_LICENSE:=GPL-3.0-or-later
PKG_LICENSE_FILES:=LICENSE
+2 -2
View File
@@ -21,13 +21,13 @@ define Download/geoip
HASH:=c6c1d1be0d28defef55b153e87cb430f94fb480c8f523bf901c5e4ca18d58a00
endef
GEOSITE_VER:=20260306064900
GEOSITE_VER:=20260307033015
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE)
HASH:=a599cbd88ee567d3e35346785924bf31d29a1eb4de798f6cf1fbf3ea55c996a1
HASH:=6e432a65a6deefa816555eb2c6bc83138479ead2bc88b01d2a51ccea6a9e7315
endef
GEOSITE_IRAN_VER:=202603020055
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
fetch-depth: '0'
- name: Setup .NET
uses: actions/setup-dotnet@v5.0.1
uses: actions/setup-dotnet@v5.2.0
with:
dotnet-version: '8.0.x'
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v5.0.1
uses: actions/setup-dotnet@v5.2.0
with:
dotnet-version: '8.0.x'
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v5.0.1
uses: actions/setup-dotnet@v5.2.0
with:
dotnet-version: '8.0.x'
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
uses: actions/checkout@v6.0.2
- name: Setup
uses: actions/setup-dotnet@v5.0.1
uses: actions/setup-dotnet@v5.2.0
with:
dotnet-version: '8.0.x'
@@ -268,7 +268,7 @@ public class CoreConfigContextBuilder
{
IndexId = $"inner-{Utils.GetGuid(false)}",
ConfigType = EConfigType.ProxyChain,
CoreType = node.CoreType ?? ECoreType.Xray,
CoreType = AppManager.Instance.GetCoreType(node, node.ConfigType),
};
List<string?> childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId];
var chainExtraItem = chainNode.GetProtocolExtra() with
@@ -21,7 +21,7 @@ public static class CoreConfigHandler
_ => await GenerateClientCustomConfig(node, fileName)
};
}
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
else if (context.RunCoreType == ECoreType.sing_box)
{
result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
}
@@ -128,12 +128,11 @@ public static class CoreConfigHandler
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName)
{
var result = new RetResult();
var node = context.Node;
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port;
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
if (context.RunCoreType == ECoreType.sing_box)
{
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port);
}
@@ -132,7 +132,7 @@ public class CoreManager
return null;
}
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreType = context.RunCoreType;
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
return await RunProcess(coreInfo, fileName, true, false);
}
+5 -5
View File
@@ -68,13 +68,13 @@ jobs:
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -82,7 +82,7 @@ jobs:
- name: Build Docker image (main architectures)
id: build_main_arches
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: .github/docker/Dockerfile
@@ -97,7 +97,7 @@ jobs:
- name: Build Docker image (additional architectures)
id: build_additional_arches
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: .github/docker/Dockerfile.usa
+4 -9
View File
@@ -183,12 +183,9 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
if p.Stats.UserOnline {
name := "user>>>" + user.Email + ">>>online"
if om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil {
sessionInbounds := session.InboundFromContext(ctx)
userIP := sessionInbounds.Source.Address.String()
userIP := sessionInbound.Source.Address.String()
om.AddIP(userIP)
// log Online user with ips
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
context.AfterFunc(ctx, func() { om.RemoveIP(userIP) })
}
}
}
@@ -225,11 +222,9 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
if p.Stats.UserOnline {
name := "user>>>" + user.Email + ">>>online"
if om, _ := stats.GetOrRegisterOnlineMap(statsManager, name); om != nil {
sessionInbounds := session.InboundFromContext(ctx)
userIP := sessionInbounds.Source.Address.String()
userIP := sessionInbound.Source.Address.String()
om.AddIP(userIP)
// log Online user with ips
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
context.AfterFunc(ctx, func() { om.RemoveIP(userIP) })
}
}
}
+1
View File
@@ -18,6 +18,7 @@ type Rule struct {
RuleTag string
Balancer *Balancer
Condition Condition
Webhook *WebhookNotifier
}
func (r *Rule) GetTag() (string, error) {
+127 -47
View File
@@ -129,7 +129,7 @@ func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
// Deprecated: Use Config_DomainStrategy.Descriptor instead.
func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{10, 0}
return file_app_router_config_proto_rawDescGZIP(), []int{11, 0}
}
// Domain for routing decision.
@@ -483,6 +483,7 @@ type RoutingRule struct {
LocalPortList *net.PortList `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_list,omitempty"`
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"`
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -647,6 +648,13 @@ func (x *RoutingRule) GetProcess() []string {
return nil
}
func (x *RoutingRule) GetWebhook() *WebhookConfig {
if x != nil {
return x.Webhook
}
return nil
}
type isRoutingRule_TargetTag interface {
isRoutingRule_TargetTag()
}
@@ -665,6 +673,66 @@ func (*RoutingRule_Tag) isRoutingRule_TargetTag() {}
func (*RoutingRule_BalancingTag) isRoutingRule_TargetTag() {}
type WebhookConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Deduplication uint32 `protobuf:"varint,2,opt,name=deduplication,proto3" json:"deduplication,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WebhookConfig) Reset() {
*x = WebhookConfig{}
mi := &file_app_router_config_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WebhookConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WebhookConfig) ProtoMessage() {}
func (x *WebhookConfig) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WebhookConfig.ProtoReflect.Descriptor instead.
func (*WebhookConfig) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{7}
}
func (x *WebhookConfig) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (x *WebhookConfig) GetDeduplication() uint32 {
if x != nil {
return x.Deduplication
}
return 0
}
func (x *WebhookConfig) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
type BalancingRule struct {
state protoimpl.MessageState `protogen:"open.v1"`
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
@@ -678,7 +746,7 @@ type BalancingRule struct {
func (x *BalancingRule) Reset() {
*x = BalancingRule{}
mi := &file_app_router_config_proto_msgTypes[7]
mi := &file_app_router_config_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -690,7 +758,7 @@ func (x *BalancingRule) String() string {
func (*BalancingRule) ProtoMessage() {}
func (x *BalancingRule) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[7]
mi := &file_app_router_config_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -703,7 +771,7 @@ func (x *BalancingRule) ProtoReflect() protoreflect.Message {
// Deprecated: Use BalancingRule.ProtoReflect.Descriptor instead.
func (*BalancingRule) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{7}
return file_app_router_config_proto_rawDescGZIP(), []int{8}
}
func (x *BalancingRule) GetTag() string {
@@ -752,7 +820,7 @@ type StrategyWeight struct {
func (x *StrategyWeight) Reset() {
*x = StrategyWeight{}
mi := &file_app_router_config_proto_msgTypes[8]
mi := &file_app_router_config_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -764,7 +832,7 @@ func (x *StrategyWeight) String() string {
func (*StrategyWeight) ProtoMessage() {}
func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[8]
mi := &file_app_router_config_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -777,7 +845,7 @@ func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
// Deprecated: Use StrategyWeight.ProtoReflect.Descriptor instead.
func (*StrategyWeight) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{8}
return file_app_router_config_proto_rawDescGZIP(), []int{9}
}
func (x *StrategyWeight) GetRegexp() bool {
@@ -819,7 +887,7 @@ type StrategyLeastLoadConfig struct {
func (x *StrategyLeastLoadConfig) Reset() {
*x = StrategyLeastLoadConfig{}
mi := &file_app_router_config_proto_msgTypes[9]
mi := &file_app_router_config_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -831,7 +899,7 @@ func (x *StrategyLeastLoadConfig) String() string {
func (*StrategyLeastLoadConfig) ProtoMessage() {}
func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[9]
mi := &file_app_router_config_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -844,7 +912,7 @@ func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use StrategyLeastLoadConfig.ProtoReflect.Descriptor instead.
func (*StrategyLeastLoadConfig) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{9}
return file_app_router_config_proto_rawDescGZIP(), []int{10}
}
func (x *StrategyLeastLoadConfig) GetCosts() []*StrategyWeight {
@@ -893,7 +961,7 @@ type Config struct {
func (x *Config) Reset() {
*x = Config{}
mi := &file_app_router_config_proto_msgTypes[10]
mi := &file_app_router_config_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -905,7 +973,7 @@ func (x *Config) String() string {
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[10]
mi := &file_app_router_config_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -918,7 +986,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{10}
return file_app_router_config_proto_rawDescGZIP(), []int{11}
}
func (x *Config) GetDomainStrategy() Config_DomainStrategy {
@@ -956,7 +1024,7 @@ type Domain_Attribute struct {
func (x *Domain_Attribute) Reset() {
*x = Domain_Attribute{}
mi := &file_app_router_config_proto_msgTypes[11]
mi := &file_app_router_config_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -968,7 +1036,7 @@ func (x *Domain_Attribute) String() string {
func (*Domain_Attribute) ProtoMessage() {}
func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[11]
mi := &file_app_router_config_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1066,7 +1134,7 @@ const file_app_router_config_proto_rawDesc = "" +
"\fcountry_code\x18\x01 \x01(\tR\vcountryCode\x12/\n" +
"\x06domain\x18\x02 \x03(\v2\x17.xray.app.router.DomainR\x06domain\"=\n" +
"\vGeoSiteList\x12.\n" +
"\x05entry\x18\x01 \x03(\v2\x18.xray.app.router.GeoSiteR\x05entry\"\x82\a\n" +
"\x05entry\x18\x01 \x03(\v2\x18.xray.app.router.GeoSiteR\x05entry\"\xbc\a\n" +
"\vRoutingRule\x12\x12\n" +
"\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" +
"\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" +
@@ -1090,12 +1158,20 @@ const file_app_router_config_proto_rawDesc = "" +
"localGeoip\x12A\n" +
"\x0flocal_port_list\x18\x12 \x01(\v2\x19.xray.common.net.PortListR\rlocalPortList\x12C\n" +
"\x10vless_route_list\x18\x14 \x01(\v2\x19.xray.common.net.PortListR\x0evlessRouteList\x12\x18\n" +
"\aprocess\x18\x15 \x03(\tR\aprocess\x1a=\n" +
"\aprocess\x18\x15 \x03(\tR\aprocess\x128\n" +
"\awebhook\x18\x16 \x01(\v2\x1e.xray.app.router.WebhookConfigR\awebhook\x1a=\n" +
"\x0fAttributesEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\f\n" +
"\n" +
"target_tag\"\xdc\x01\n" +
"target_tag\"\xca\x01\n" +
"\rWebhookConfig\x12\x10\n" +
"\x03url\x18\x01 \x01(\tR\x03url\x12$\n" +
"\rdeduplication\x18\x02 \x01(\rR\rdeduplication\x12E\n" +
"\aheaders\x18\x03 \x03(\v2+.xray.app.router.WebhookConfig.HeadersEntryR\aheaders\x1a:\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xdc\x01\n" +
"\rBalancingRule\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x12+\n" +
"\x11outbound_selector\x18\x02 \x03(\tR\x10outboundSelector\x12\x1a\n" +
@@ -1136,7 +1212,7 @@ func file_app_router_config_proto_rawDescGZIP() []byte {
}
var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_app_router_config_proto_goTypes = []any{
(Domain_Type)(0), // 0: xray.app.router.Domain.Type
(Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy
@@ -1147,43 +1223,47 @@ var file_app_router_config_proto_goTypes = []any{
(*GeoSite)(nil), // 6: xray.app.router.GeoSite
(*GeoSiteList)(nil), // 7: xray.app.router.GeoSiteList
(*RoutingRule)(nil), // 8: xray.app.router.RoutingRule
(*BalancingRule)(nil), // 9: xray.app.router.BalancingRule
(*StrategyWeight)(nil), // 10: xray.app.router.StrategyWeight
(*StrategyLeastLoadConfig)(nil), // 11: xray.app.router.StrategyLeastLoadConfig
(*Config)(nil), // 12: xray.app.router.Config
(*Domain_Attribute)(nil), // 13: xray.app.router.Domain.Attribute
nil, // 14: xray.app.router.RoutingRule.AttributesEntry
(*net.PortList)(nil), // 15: xray.common.net.PortList
(net.Network)(0), // 16: xray.common.net.Network
(*serial.TypedMessage)(nil), // 17: xray.common.serial.TypedMessage
(*WebhookConfig)(nil), // 9: xray.app.router.WebhookConfig
(*BalancingRule)(nil), // 10: xray.app.router.BalancingRule
(*StrategyWeight)(nil), // 11: xray.app.router.StrategyWeight
(*StrategyLeastLoadConfig)(nil), // 12: xray.app.router.StrategyLeastLoadConfig
(*Config)(nil), // 13: xray.app.router.Config
(*Domain_Attribute)(nil), // 14: xray.app.router.Domain.Attribute
nil, // 15: xray.app.router.RoutingRule.AttributesEntry
nil, // 16: xray.app.router.WebhookConfig.HeadersEntry
(*net.PortList)(nil), // 17: xray.common.net.PortList
(net.Network)(0), // 18: xray.common.net.Network
(*serial.TypedMessage)(nil), // 19: xray.common.serial.TypedMessage
}
var file_app_router_config_proto_depIdxs = []int32{
0, // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type
13, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute
14, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute
3, // 2: xray.app.router.GeoIP.cidr:type_name -> xray.app.router.CIDR
4, // 3: xray.app.router.GeoIPList.entry:type_name -> xray.app.router.GeoIP
2, // 4: xray.app.router.GeoSite.domain:type_name -> xray.app.router.Domain
6, // 5: xray.app.router.GeoSiteList.entry:type_name -> xray.app.router.GeoSite
2, // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain
4, // 7: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP
15, // 8: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
16, // 9: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
17, // 8: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
18, // 9: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
4, // 10: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP
15, // 11: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
14, // 12: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry
17, // 11: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
15, // 12: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry
4, // 13: xray.app.router.RoutingRule.local_geoip:type_name -> xray.app.router.GeoIP
15, // 14: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList
15, // 15: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList
17, // 16: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
10, // 17: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
1, // 18: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
8, // 19: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
9, // 20: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
21, // [21:21] is the sub-list for method output_type
21, // [21:21] is the sub-list for method input_type
21, // [21:21] is the sub-list for extension type_name
21, // [21:21] is the sub-list for extension extendee
0, // [0:21] is the sub-list for field type_name
17, // 14: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList
17, // 15: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList
9, // 16: xray.app.router.RoutingRule.webhook:type_name -> xray.app.router.WebhookConfig
16, // 17: xray.app.router.WebhookConfig.headers:type_name -> xray.app.router.WebhookConfig.HeadersEntry
19, // 18: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
11, // 19: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
1, // 20: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
8, // 21: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
10, // 22: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
23, // [23:23] is the sub-list for method output_type
23, // [23:23] is the sub-list for method input_type
23, // [23:23] is the sub-list for extension type_name
23, // [23:23] is the sub-list for extension extendee
0, // [0:23] is the sub-list for field type_name
}
func init() { file_app_router_config_proto_init() }
@@ -1195,7 +1275,7 @@ func file_app_router_config_proto_init() {
(*RoutingRule_Tag)(nil),
(*RoutingRule_BalancingTag)(nil),
}
file_app_router_config_proto_msgTypes[11].OneofWrappers = []any{
file_app_router_config_proto_msgTypes[12].OneofWrappers = []any{
(*Domain_Attribute_BoolValue)(nil),
(*Domain_Attribute_IntValue)(nil),
}
@@ -1205,7 +1285,7 @@ func file_app_router_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_router_config_proto_rawDesc), len(file_app_router_config_proto_rawDesc)),
NumEnums: 2,
NumMessages: 13,
NumMessages: 15,
NumExtensions: 0,
NumServices: 0,
},
+7
View File
@@ -114,6 +114,13 @@ message RoutingRule {
xray.common.net.PortList vless_route_list = 20;
repeated string process = 21;
WebhookConfig webhook = 22;
}
message WebhookConfig {
string url = 1;
uint32 deduplication = 2;
map<string, string> headers = 3;
}
message BalancingRule {
+60
View File
@@ -57,6 +57,7 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
for _, rule := range config.Rule {
cond, err := rule.BuildCondition()
if err != nil {
r.closeWebhooks()
return err
}
rr := &Rule{
@@ -64,10 +65,22 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
Tag: rule.GetTag(),
RuleTag: rule.GetRuleTag(),
}
if wh := rule.GetWebhook(); wh != nil {
notifier, err := NewWebhookNotifier(wh)
if err != nil {
r.closeWebhooks()
return err
}
rr.Webhook = notifier
}
btag := rule.GetBalancingTag()
if len(btag) > 0 {
brule, found := r.balancers[btag]
if !found {
if rr.Webhook != nil {
rr.Webhook.Close()
}
r.closeWebhooks()
return errors.New("balancer ", btag, " not found")
}
rr.Balancer = brule
@@ -80,6 +93,7 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
// PickRoute implements routing.Router.
func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
originalCtx := ctx
rule, ctx, err := r.pickRouteInternal(ctx)
if err != nil {
return nil, err
@@ -88,6 +102,9 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
if err != nil {
return nil, err
}
if rule.Webhook != nil {
rule.Webhook.Fire(originalCtx, tag)
}
return &Route{Context: ctx, outboundTag: tag, ruleTag: rule.RuleTag}, nil
}
@@ -109,6 +126,11 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
defer r.mu.Unlock()
if !shouldAppend {
for _, rule := range r.rules {
if rule.Webhook != nil {
rule.Webhook.Close()
}
}
r.balancers = make(map[string]*Balancer, len(config.BalancingRule))
r.rules = make([]*Rule, 0, len(config.Rule))
}
@@ -125,12 +147,24 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
r.balancers[rule.Tag] = balancer
}
startIdx := len(r.rules)
closeNewWebhooks := func() {
for i := startIdx; i < len(r.rules); i++ {
if r.rules[i].Webhook != nil {
r.rules[i].Webhook.Close()
}
}
r.rules = r.rules[:startIdx]
}
for _, rule := range config.Rule {
if r.RuleExists(rule.GetRuleTag()) {
closeNewWebhooks()
return errors.New("duplicate ruleTag ", rule.GetRuleTag())
}
cond, err := rule.BuildCondition()
if err != nil {
closeNewWebhooks()
return err
}
rr := &Rule{
@@ -138,10 +172,22 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
Tag: rule.GetTag(),
RuleTag: rule.GetRuleTag(),
}
if wh := rule.GetWebhook(); wh != nil {
notifier, err := NewWebhookNotifier(wh)
if err != nil {
closeNewWebhooks()
return err
}
rr.Webhook = notifier
}
btag := rule.GetBalancingTag()
if len(btag) > 0 {
brule, found := r.balancers[btag]
if !found {
if rr.Webhook != nil {
rr.Webhook.Close()
}
closeNewWebhooks()
return errors.New("balancer ", btag, " not found")
}
rr.Balancer = brule
@@ -173,6 +219,8 @@ func (r *Router) RemoveRule(tag string) error {
for _, rule := range r.rules {
if rule.RuleTag != tag {
newRules = append(newRules, rule)
} else if rule.Webhook != nil {
rule.Webhook.Close()
}
}
r.rules = newRules
@@ -233,8 +281,20 @@ func (r *Router) Start() error {
return nil
}
// closeWebhooks closes all webhook notifiers in the current rule set.
func (r *Router) closeWebhooks() {
for _, rule := range r.rules {
if rule.Webhook != nil {
rule.Webhook.Close()
}
}
}
// Close implements common.Closable.
func (r *Router) Close() error {
r.mu.Lock()
defer r.mu.Unlock()
r.closeWebhooks()
return nil
}
+287
View File
@@ -0,0 +1,287 @@
package router
import (
"bytes"
"context"
"encoding/json"
"io"
"net"
"net/http"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/features/routing"
routing_session "github.com/xtls/xray-core/features/routing/session"
)
// parseURL splits a webhook URL into an HTTP URL and an optional Unix socket
// path. For regular http/https URLs the input is returned unchanged with an
// empty socketPath. For Unix sockets the format is:
//
// /path/to/socket.sock:/http/path
// @abstract:/http/path
// @@padded:/http/path
//
// The :/ separator after the socket path delimits the HTTP request path.
// If omitted, "/" is used.
func parseURL(raw string) (httpURL, socketPath string) {
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
return raw, ""
}
if idx := strings.Index(raw, ":/"); idx >= 0 {
return "http://localhost" + raw[idx+1:], raw[:idx]
}
return "http://localhost/", raw
}
// resolveSocketPath applies platform-specific transformations to a Unix
// socket path, matching the behaviour of the listen side in
// transport/internet/system_listener.go.
//
// For abstract sockets (prefix @) on Linux/Android:
// - single @ — used as-is (lock-free abstract socket)
// - double @@ — stripped to single @ and padded to
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
func resolveSocketPath(path string) string {
if len(path) == 0 || path[0] != '@' {
return path
}
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
return path
}
if len(path) > 1 && path[1] == '@' {
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
copy(fullAddr, path[1:])
return string(fullAddr)
}
return path
}
func ptr[T any](v T) *T { return &v }
type event struct {
Email *string `json:"email"`
Level *uint32 `json:"level"`
Protocol *string `json:"protocol"`
Network *string `json:"network"`
Source *string `json:"source"`
Destination *string `json:"destination"`
OriginalTarget *string `json:"originalTarget"`
RouteTarget *string `json:"routeTarget"`
InboundTag *string `json:"inboundTag"`
InboundName *string `json:"inboundName"`
InboundLocal *string `json:"inboundLocal"`
OutboundTag *string `json:"outboundTag"`
Timestamp int64 `json:"ts"`
}
type WebhookNotifier struct {
url string
headers map[string]string
deduplication uint32
client *http.Client
seen sync.Map
done chan struct{}
wg sync.WaitGroup
closeOnce sync.Once
}
func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
if cfg == nil || cfg.Url == "" {
return nil, nil
}
httpURL, socketPath := parseURL(cfg.Url)
h := &WebhookNotifier{
url: httpURL,
deduplication: cfg.Deduplication,
client: &http.Client{
Timeout: 5 * time.Second,
},
done: make(chan struct{}),
}
if socketPath != "" {
dialAddr := resolveSocketPath(socketPath)
h.client.Transport = &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", dialAddr)
},
}
}
if len(cfg.Headers) > 0 {
h.headers = make(map[string]string, len(cfg.Headers))
for k, v := range cfg.Headers {
h.headers[k] = v
}
}
if h.deduplication > 0 {
h.wg.Add(1)
go h.cleanupLoop()
}
return h, nil
}
func (h *WebhookNotifier) Fire(ctx routing.Context, outboundTag string) {
ev := buildEvent(ctx, outboundTag)
email := ""
if ev.Email != nil {
email = *ev.Email
}
if h.isDuplicate(email) {
return
}
h.wg.Add(1)
select {
case <-h.done:
h.wg.Done()
return
default:
}
go func() {
defer h.wg.Done()
h.post(ev)
}()
}
func buildEvent(ctx routing.Context, outboundTag string) *event {
ev := &event{
Timestamp: time.Now().Unix(),
OutboundTag: ptr(outboundTag),
InboundTag: ptr(ctx.GetInboundTag()),
Protocol: ptr(ctx.GetProtocol()),
Network: ptr(ctx.GetNetwork().SystemString()),
}
if user := ctx.GetUser(); user != "" {
ev.Email = ptr(user)
}
if srcIPs := ctx.GetSourceIPs(); len(srcIPs) > 0 {
srcPort := ctx.GetSourcePort()
ev.Source = ptr(net.JoinHostPort(srcIPs[0].String(), srcPort.String()))
}
targetPort := ctx.GetTargetPort()
if domain := ctx.GetTargetDomain(); domain != "" {
ev.Destination = ptr(net.JoinHostPort(domain, targetPort.String()))
} else if targetIPs := ctx.GetTargetIPs(); len(targetIPs) > 0 {
ev.Destination = ptr(net.JoinHostPort(targetIPs[0].String(), targetPort.String()))
}
if localIPs := ctx.GetLocalIPs(); len(localIPs) > 0 {
localPort := ctx.GetLocalPort()
ev.InboundLocal = ptr(net.JoinHostPort(localIPs[0].String(), localPort.String()))
}
if sctx, ok := ctx.(*routing_session.Context); ok {
enrichFromSession(ev, sctx)
}
return ev
}
func enrichFromSession(ev *event, sctx *routing_session.Context) {
if sctx.Inbound != nil {
ev.InboundName = ptr(sctx.Inbound.Name)
if sctx.Inbound.User != nil {
ev.Level = ptr(sctx.Inbound.User.Level)
}
}
if sctx.Outbound != nil {
if sctx.Outbound.OriginalTarget.Address != nil {
ev.OriginalTarget = ptr(sctx.Outbound.OriginalTarget.String())
}
if sctx.Outbound.RouteTarget.Address != nil {
ev.RouteTarget = ptr(sctx.Outbound.RouteTarget.String())
}
}
}
func (h *WebhookNotifier) post(ev *event) {
body, err := json.Marshal(ev)
if err != nil {
errors.LogWarning(context.Background(), "webhook: marshal failed: ", err)
return
}
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, h.url, bytes.NewReader(body))
if err != nil {
errors.LogWarning(context.Background(), "webhook: request build failed: ", err)
return
}
req.Header.Set("Content-Type", "application/json")
for k, v := range h.headers {
req.Header.Set(k, v)
}
resp, err := h.client.Do(req)
if err != nil {
errors.LogInfo(context.Background(), "webhook: POST failed: ", err)
return
}
defer func() {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode >= 400 {
errors.LogWarning(context.Background(), "webhook: POST returned status ", resp.StatusCode)
}
}
func (h *WebhookNotifier) isDuplicate(email string) bool {
if h.deduplication == 0 || email == "" {
return false
}
ttl := time.Duration(h.deduplication) * time.Second
now := time.Now()
if v, loaded := h.seen.LoadOrStore(email, now); loaded {
if now.Sub(v.(time.Time)) < ttl {
return true
}
h.seen.Store(email, now)
}
return false
}
func (h *WebhookNotifier) cleanupLoop() {
defer h.wg.Done()
ttl := time.Duration(h.deduplication) * time.Second
ticker := time.NewTicker(ttl)
defer ticker.Stop()
for {
select {
case <-h.done:
return
case <-ticker.C:
now := time.Now()
h.seen.Range(func(key, value any) bool {
if now.Sub(value.(time.Time)) >= ttl {
h.seen.Delete(key)
}
return true
})
}
}
}
func (h *WebhookNotifier) Close() error {
h.closeOnce.Do(func() {
close(h.done)
})
h.wg.Wait()
h.client.CloseIdleConnections()
return nil
}
+1 -1
View File
@@ -70,7 +70,7 @@ func (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStat
}
ips := make(map[string]int64)
for ip, t := range c.IpTimeMap() {
for ip, t := range c.IPTimeMap() {
ips[ip] = t.Unix()
}
+73 -59
View File
@@ -2,84 +2,98 @@ package stats
import (
"sync"
"sync/atomic"
"time"
)
// OnlineMap is an implementation of stats.OnlineMap.
type OnlineMap struct {
ipList map[string]time.Time
access sync.RWMutex
lastCleanup time.Time
cleanupPeriod time.Duration
const (
localhostIPv4 = "127.0.0.1"
localhostIPv6 = "[::1]"
)
type ipEntry struct {
refCount int
lastSeen time.Time
}
// NewOnlineMap creates a new instance of OnlineMap.
// OnlineMap is a refcount-based implementation of stats.OnlineMap.
// IPs are tracked by reference counting: AddIP increments, RemoveIP decrements.
// An IP is removed from the map when its reference count reaches zero.
type OnlineMap struct {
entries map[string]*ipEntry
access sync.Mutex
count atomic.Int64
}
// NewOnlineMap creates a new OnlineMap instance.
func NewOnlineMap() *OnlineMap {
return &OnlineMap{
ipList: make(map[string]time.Time),
lastCleanup: time.Now(),
cleanupPeriod: 10 * time.Second,
entries: make(map[string]*ipEntry),
}
}
// AddIP implements stats.OnlineMap.
func (om *OnlineMap) AddIP(ip string) {
if ip == localhostIPv4 || ip == localhostIPv6 {
return
}
om.access.Lock()
defer om.access.Unlock()
if e, ok := om.entries[ip]; ok {
e.refCount++
e.lastSeen = time.Now()
} else {
om.entries[ip] = &ipEntry{
refCount: 1,
lastSeen: time.Now(),
}
om.count.Add(1)
}
}
// RemoveIP implements stats.OnlineMap.
func (om *OnlineMap) RemoveIP(ip string) {
om.access.Lock()
defer om.access.Unlock()
e, ok := om.entries[ip]
if !ok {
return
}
e.refCount--
if e.refCount <= 0 {
delete(om.entries, ip)
om.count.Add(-1)
}
}
// Count implements stats.OnlineMap.
func (c *OnlineMap) Count() int {
c.access.RLock()
defer c.access.RUnlock()
return len(c.ipList)
func (om *OnlineMap) Count() int {
return int(om.count.Load())
}
// List implements stats.OnlineMap.
func (c *OnlineMap) List() []string {
return c.GetKeys()
}
func (om *OnlineMap) List() []string {
om.access.Lock()
defer om.access.Unlock()
// AddIP implements stats.OnlineMap.
func (c *OnlineMap) AddIP(ip string) {
if ip == "127.0.0.1" {
return
}
c.access.Lock()
c.ipList[ip] = time.Now()
c.access.Unlock()
if time.Since(c.lastCleanup) > c.cleanupPeriod {
c.RemoveExpiredIPs()
c.lastCleanup = time.Now()
}
}
func (c *OnlineMap) GetKeys() []string {
c.access.RLock()
defer c.access.RUnlock()
keys := []string{}
for k := range c.ipList {
keys = append(keys, k)
keys := make([]string, 0, len(om.entries))
for ip := range om.entries {
keys = append(keys, ip)
}
return keys
}
func (c *OnlineMap) RemoveExpiredIPs() {
c.access.Lock()
defer c.access.Unlock()
// IPTimeMap implements stats.OnlineMap.
func (om *OnlineMap) IPTimeMap() map[string]time.Time {
om.access.Lock()
defer om.access.Unlock()
now := time.Now()
for k, t := range c.ipList {
diff := now.Sub(t)
if diff.Seconds() > 20 {
delete(c.ipList, k)
}
result := make(map[string]time.Time, len(om.entries))
for ip, e := range om.entries {
result[ip] = e.lastSeen
}
}
func (c *OnlineMap) IpTimeMap() map[string]time.Time {
if time.Since(c.lastCleanup) > c.cleanupPeriod {
c.RemoveExpiredIPs()
c.lastCleanup = time.Now()
}
return c.ipList
return result
}
+3 -3
View File
@@ -163,12 +163,12 @@ func (m *Manager) GetChannel(name string) stats.Channel {
// GetAllOnlineUsers implements stats.Manager.
func (m *Manager) GetAllOnlineUsers() []string {
m.access.Lock()
defer m.access.Unlock()
m.access.RLock()
defer m.access.RUnlock()
usersOnline := make([]string, 0, len(m.onlineMap))
for user, onlineMap := range m.onlineMap {
if len(onlineMap.IpTimeMap()) > 0 {
if onlineMap.Count() > 0 {
usersOnline = append(usersOnline, user)
}
}
+21 -21
View File
@@ -10,46 +10,46 @@ import (
"github.com/xtls/xray-core/common/signal/done"
)
type ConnectionOption func(*connection)
type ConnectionOption func(*Connection)
func ConnectionLocalAddr(a net.Addr) ConnectionOption {
return func(c *connection) {
return func(c *Connection) {
c.local = a
}
}
func ConnectionRemoteAddr(a net.Addr) ConnectionOption {
return func(c *connection) {
return func(c *Connection) {
c.remote = a
}
}
func ConnectionInput(writer io.Writer) ConnectionOption {
return func(c *connection) {
return func(c *Connection) {
c.writer = buf.NewWriter(writer)
}
}
func ConnectionInputMulti(writer buf.Writer) ConnectionOption {
return func(c *connection) {
return func(c *Connection) {
c.writer = writer
}
}
func ConnectionOutput(reader io.Reader) ConnectionOption {
return func(c *connection) {
return func(c *Connection) {
c.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)}
}
}
func ConnectionOutputMulti(reader buf.Reader) ConnectionOption {
return func(c *connection) {
return func(c *Connection) {
c.reader = &buf.BufferedReader{Reader: reader}
}
}
func ConnectionOutputMultiUDP(reader buf.Reader) ConnectionOption {
return func(c *connection) {
return func(c *Connection) {
c.reader = &buf.BufferedReader{
Reader: reader,
Splitter: buf.SplitFirstBytes,
@@ -58,13 +58,13 @@ func ConnectionOutputMultiUDP(reader buf.Reader) ConnectionOption {
}
func ConnectionOnClose(n io.Closer) ConnectionOption {
return func(c *connection) {
return func(c *Connection) {
c.onClose = n
}
}
func NewConnection(opts ...ConnectionOption) net.Conn {
c := &connection{
c := &Connection{
done: done.New(),
local: &net.TCPAddr{
IP: []byte{0, 0, 0, 0},
@@ -83,7 +83,7 @@ func NewConnection(opts ...ConnectionOption) net.Conn {
return c
}
type connection struct {
type Connection struct {
reader *buf.BufferedReader
writer buf.Writer
done *done.Instance
@@ -92,17 +92,17 @@ type connection struct {
remote net.Addr
}
func (c *connection) Read(b []byte) (int, error) {
func (c *Connection) Read(b []byte) (int, error) {
return c.reader.Read(b)
}
// ReadMultiBuffer implements buf.Reader.
func (c *connection) ReadMultiBuffer() (buf.MultiBuffer, error) {
func (c *Connection) ReadMultiBuffer() (buf.MultiBuffer, error) {
return c.reader.ReadMultiBuffer()
}
// Write implements net.Conn.Write().
func (c *connection) Write(b []byte) (int, error) {
func (c *Connection) Write(b []byte) (int, error) {
if c.done.Done() {
return 0, io.ErrClosedPipe
}
@@ -113,7 +113,7 @@ func (c *connection) Write(b []byte) (int, error) {
return l, c.writer.WriteMultiBuffer(mb)
}
func (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error {
func (c *Connection) WriteMultiBuffer(mb buf.MultiBuffer) error {
if c.done.Done() {
buf.ReleaseMulti(mb)
return io.ErrClosedPipe
@@ -123,7 +123,7 @@ func (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error {
}
// Close implements net.Conn.Close().
func (c *connection) Close() error {
func (c *Connection) Close() error {
common.Must(c.done.Close())
common.Interrupt(c.reader)
common.Close(c.writer)
@@ -135,26 +135,26 @@ func (c *connection) Close() error {
}
// LocalAddr implements net.Conn.LocalAddr().
func (c *connection) LocalAddr() net.Addr {
func (c *Connection) LocalAddr() net.Addr {
return c.local
}
// RemoteAddr implements net.Conn.RemoteAddr().
func (c *connection) RemoteAddr() net.Addr {
func (c *Connection) RemoteAddr() net.Addr {
return c.remote
}
// SetDeadline implements net.Conn.SetDeadline().
func (c *connection) SetDeadline(t time.Time) error {
func (c *Connection) SetDeadline(t time.Time) error {
return nil
}
// SetReadDeadline implements net.Conn.SetReadDeadline().
func (c *connection) SetReadDeadline(t time.Time) error {
func (c *Connection) SetReadDeadline(t time.Time) error {
return nil
}
// SetWriteDeadline implements net.Conn.SetWriteDeadline().
func (c *connection) SetWriteDeadline(t time.Time) error {
func (c *Connection) SetWriteDeadline(t time.Time) error {
return nil
}
+7 -5
View File
@@ -25,14 +25,16 @@ type Counter interface {
//
// xray:api:stable
type OnlineMap interface {
// Count is the current value of the OnlineMap.
// Count returns the number of unique online IPs.
Count() int
// AddIP adds a ip to the current OnlineMap.
// AddIP increments the reference count for the given IP.
AddIP(string)
// List is the current OnlineMap ip list.
// RemoveIP decrements the reference count for the given IP. Deletes at zero.
RemoveIP(string)
// List returns all currently online IPs.
List() []string
// IpTimeMap return client ips and their last access time.
IpTimeMap() map[string]time.Time
// IPTimeMap returns a snapshot copy of IPs to their last-seen times.
IPTimeMap() map[string]time.Time
}
// Channel is the interface for stats channel.
+1 -1
View File
@@ -10,8 +10,8 @@ import (
v2net "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/freedom"
"google.golang.org/protobuf/proto"
"github.com/xtls/xray-core/transport/internet"
"google.golang.org/protobuf/proto"
)
type FreedomConfig struct {
+31 -16
View File
@@ -522,25 +522,32 @@ func ToCidrList(ips StringList) ([]*router.GeoIP, error) {
return geoipList, nil
}
type WebhookRuleConfig struct {
URL string `json:"url"`
Deduplication uint32 `json:"deduplication"`
Headers map[string]string `json:"headers"`
}
func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
type RawFieldRule struct {
RouterRule
Domain *StringList `json:"domain"`
Domains *StringList `json:"domains"`
IP *StringList `json:"ip"`
Port *PortList `json:"port"`
Network *NetworkList `json:"network"`
SourceIP *StringList `json:"sourceIP"`
Source *StringList `json:"source"`
SourcePort *PortList `json:"sourcePort"`
User *StringList `json:"user"`
VlessRoute *PortList `json:"vlessRoute"`
InboundTag *StringList `json:"inboundTag"`
Protocols *StringList `json:"protocol"`
Attributes map[string]string `json:"attrs"`
LocalIP *StringList `json:"localIP"`
LocalPort *PortList `json:"localPort"`
Process *StringList `json:"process"`
Domain *StringList `json:"domain"`
Domains *StringList `json:"domains"`
IP *StringList `json:"ip"`
Port *PortList `json:"port"`
Network *NetworkList `json:"network"`
SourceIP *StringList `json:"sourceIP"`
Source *StringList `json:"source"`
SourcePort *PortList `json:"sourcePort"`
User *StringList `json:"user"`
VlessRoute *PortList `json:"vlessRoute"`
InboundTag *StringList `json:"inboundTag"`
Protocols *StringList `json:"protocol"`
Attributes map[string]string `json:"attrs"`
LocalIP *StringList `json:"localIP"`
LocalPort *PortList `json:"localPort"`
Process *StringList `json:"process"`
Webhook *WebhookRuleConfig `json:"webhook"`
}
rawFieldRule := new(RawFieldRule)
err := json.Unmarshal(msg, rawFieldRule)
@@ -657,6 +664,14 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
rule.Process = *rawFieldRule.Process
}
if rawFieldRule.Webhook != nil && rawFieldRule.Webhook.URL != "" {
rule.Webhook = &router.WebhookConfig{
Url: rawFieldRule.Webhook.URL,
Deduplication: rawFieldRule.Webhook.Deduplication,
Headers: rawFieldRule.Webhook.Headers,
}
}
return rule, nil
}
+367 -24
View File
@@ -18,6 +18,8 @@ import (
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dtls"
"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp"
@@ -26,7 +28,9 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
"github.com/xtls/xray-core/transport/internet/httpupgrade"
@@ -72,7 +76,7 @@ func (c *KCPConfig) Build() (proto.Message, error) {
}
if c.Tti != nil {
tti := *c.Tti
if tti < 10 || tti > 100 {
if tti < 10 || tti > 5000 {
return nil, errors.New("invalid mKCP TTI: ", tti).AtError()
}
config.Tti = &kcp.TTI{Value: tti}
@@ -230,13 +234,14 @@ type SplitHTTPConfig struct {
SeqKey string `json:"seqKey"`
UplinkDataPlacement string `json:"uplinkDataPlacement"`
UplinkDataKey string `json:"uplinkDataKey"`
UplinkChunkSize uint32 `json:"uplinkChunkSize"`
UplinkChunkSize Int32Range `json:"uplinkChunkSize"`
NoGRPCHeader bool `json:"noGRPCHeader"`
NoSSEHeader bool `json:"noSSEHeader"`
ScMaxEachPostBytes Int32Range `json:"scMaxEachPostBytes"`
ScMinPostsIntervalMs Int32Range `json:"scMinPostsIntervalMs"`
ScMaxBufferedPosts int64 `json:"scMaxBufferedPosts"`
ScStreamUpServerSecs Int32Range `json:"scStreamUpServerSecs"`
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
Xmux XmuxConfig `json:"xmux"`
DownloadSettings *StreamConfig `json:"downloadSettings"`
Extra json.RawMessage `json:"extra"`
@@ -316,9 +321,9 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
switch c.UplinkDataPlacement {
case "":
c.UplinkDataPlacement = "body"
case "body":
case "cookie", "header":
c.UplinkDataPlacement = splithttp.PlacementAuto
case splithttp.PlacementAuto, splithttp.PlacementBody:
case splithttp.PlacementCookie, splithttp.PlacementHeader:
if c.Mode != "packet-up" {
return nil, errors.New("UplinkDataPlacement can be " + c.UplinkDataPlacement + " only in packet-up mode")
}
@@ -347,9 +352,6 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
case "":
c.SeqPlacement = "path"
case "path", "cookie", "header", "query":
if c.SessionPlacement == "path" {
return nil, errors.New("SeqPlacement must be path when SessionPlacement is path")
}
default:
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
}
@@ -372,24 +374,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
}
}
if c.UplinkDataPlacement != "body" && c.UplinkDataKey == "" {
if c.UplinkDataPlacement != splithttp.PlacementBody && c.UplinkDataKey == "" {
switch c.UplinkDataPlacement {
case "cookie":
case splithttp.PlacementCookie:
c.UplinkDataKey = "x_data"
case "header":
case splithttp.PlacementAuto, splithttp.PlacementHeader:
c.UplinkDataKey = "X-Data"
}
}
if c.UplinkChunkSize == 0 {
switch c.UplinkDataPlacement {
case "cookie":
c.UplinkChunkSize = 3 * 1024 // 3KB
case "header":
c.UplinkChunkSize = 4 * 1024 // 4KB
}
} else if c.UplinkChunkSize < 64 {
c.UplinkChunkSize = 64
if c.ServerMaxHeaderBytes < 0 {
return nil, errors.New("invalid negative value of maxHeaderBytes")
}
if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
@@ -422,13 +417,14 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
SeqKey: c.SeqKey,
UplinkDataPlacement: c.UplinkDataPlacement,
UplinkDataKey: c.UplinkDataKey,
UplinkChunkSize: c.UplinkChunkSize,
UplinkChunkSize: newRangeConfig(c.UplinkChunkSize),
NoGRPCHeader: c.NoGRPCHeader,
NoSSEHeader: c.NoSSEHeader,
ScMaxEachPostBytes: newRangeConfig(c.ScMaxEachPostBytes),
ScMinPostsIntervalMs: newRangeConfig(c.ScMinPostsIntervalMs),
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
Xmux: &splithttp.XmuxConfig{
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
@@ -725,6 +721,11 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
return certificate, nil
}
type QuicParamsConfig struct {
Congestion string `json:"congestion"`
Up Bandwidth `json:"up"`
}
type TLSConfig struct {
AllowInsecure bool `json:"allowInsecure"`
Certs []*TLSCertConfig `json:"certificates"`
@@ -1276,8 +1277,49 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
}, nil
}
func PraseByteSlice(data json.RawMessage, typ string) ([]byte, error) {
switch strings.ToLower(typ) {
case "", "array":
if len(data) == 0 {
return data, nil
}
var packet []byte
if err := json.Unmarshal(data, &packet); err != nil {
return nil, err
}
return packet, nil
case "str":
var str string
if err := json.Unmarshal(data, &str); err != nil {
return nil, err
}
return []byte(str), nil
case "hex":
var str string
if err := json.Unmarshal(data, &str); err != nil {
return nil, err
}
return hex.DecodeString(str)
case "base64":
var str string
if err := json.Unmarshal(data, &str); err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(str)
default:
return nil, errors.New("unknown type")
}
}
var (
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
"fragment": func() interface{} { return new(FragmentMask) },
"sudoku": func() interface{} { return new(Sudoku) },
}, "type", "settings")
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
"header-dns": func() interface{} { return new(Dns) },
"header-dtls": func() interface{} { return new(Dtls) },
"header-srtp": func() interface{} { return new(Srtp) },
@@ -1286,12 +1328,246 @@ var (
"header-wireguard": func() interface{} { return new(Wireguard) },
"mkcp-original": func() interface{} { return new(Original) },
"mkcp-aes128gcm": func() interface{} { return new(Aes128Gcm) },
"noise": func() interface{} { return new(NoiseMask) },
"salamander": func() interface{} { return new(Salamander) },
"sudoku": func() interface{} { return new(Sudoku) },
"xdns": func() interface{} { return new(Xdns) },
"xicmp": func() interface{} { return new(Xicmp) },
}, "type", "settings")
)
type TCPItem struct {
Delay Int32Range `json:"delay"`
Rand int32 `json:"rand"`
Type string `json:"type"`
Packet json.RawMessage `json:"packet"`
}
type HeaderCustomTCP struct {
Clients [][]TCPItem `json:"clients"`
Servers [][]TCPItem `json:"servers"`
Errors [][]TCPItem `json:"errors"`
}
func (c *HeaderCustomTCP) Build() (proto.Message, error) {
for _, value := range c.Clients {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
}
for _, value := range c.Servers {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
}
for _, value := range c.Errors {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
}
clients := make([]*custom.TCPSequence, len(c.Clients))
for i, value := range c.Clients {
clients[i] = &custom.TCPSequence{}
for _, item := range value {
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
clients[i].Sequence = append(clients[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
Rand: item.Rand,
Packet: item.Packet,
})
}
}
servers := make([]*custom.TCPSequence, len(c.Servers))
for i, value := range c.Servers {
servers[i] = &custom.TCPSequence{}
for _, item := range value {
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
servers[i].Sequence = append(servers[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
Rand: item.Rand,
Packet: item.Packet,
})
}
}
errors := make([]*custom.TCPSequence, len(c.Errors))
for i, value := range c.Errors {
errors[i] = &custom.TCPSequence{}
for _, item := range value {
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
errors[i].Sequence = append(errors[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
Rand: item.Rand,
Packet: item.Packet,
})
}
}
return &custom.TCPConfig{
Clients: clients,
Servers: servers,
Errors: errors,
}, nil
}
type FragmentMask struct {
Packets string `json:"packets"`
Length Int32Range `json:"length"`
Delay Int32Range `json:"delay"`
MaxSplit Int32Range `json:"maxSplit"`
}
func (c *FragmentMask) Build() (proto.Message, error) {
config := &fragment.Config{}
switch strings.ToLower(c.Packets) {
case "tlshello":
config.PacketsFrom = 0
config.PacketsTo = 1
case "":
config.PacketsFrom = 0
config.PacketsTo = 0
default:
from, to, err := ParseRangeString(c.Packets)
if err != nil {
return nil, errors.New("Invalid PacketsFrom").Base(err)
}
config.PacketsFrom = int64(from)
config.PacketsTo = int64(to)
if config.PacketsFrom == 0 {
return nil, errors.New("PacketsFrom can't be 0")
}
}
config.LengthMin = int64(c.Length.From)
config.LengthMax = int64(c.Length.To)
if config.LengthMin == 0 {
return nil, errors.New("LengthMin can't be 0")
}
config.DelayMin = int64(c.Delay.From)
config.DelayMax = int64(c.Delay.To)
config.MaxSplitMin = int64(c.MaxSplit.From)
config.MaxSplitMax = int64(c.MaxSplit.To)
return config, nil
}
type NoiseItem struct {
Rand Int32Range `json:"rand"`
Type string `json:"type"`
Packet json.RawMessage `json:"packet"`
Delay Int32Range `json:"delay"`
}
type NoiseMask struct {
Reset Int32Range `json:"reset"`
Noise []NoiseItem `json:"noise"`
}
func (c *NoiseMask) Build() (proto.Message, error) {
for _, item := range c.Noise {
if len(item.Packet) > 0 && item.Rand.To > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand.To > 0")
}
}
noiseSlice := make([]*noise.Item, 0, len(c.Noise))
for _, item := range c.Noise {
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
noiseSlice = append(noiseSlice, &noise.Item{
RandMin: int64(item.Rand.From),
RandMax: int64(item.Rand.To),
Packet: item.Packet,
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
})
}
return &noise.Config{
ResetMin: int64(c.Reset.From),
ResetMax: int64(c.Reset.To),
Items: noiseSlice,
}, nil
}
type UDPItem struct {
Rand int32 `json:"rand"`
Type string `json:"type"`
Packet json.RawMessage `json:"packet"`
}
type HeaderCustomUDP struct {
Client []UDPItem `json:"client"`
Server []UDPItem `json:"server"`
}
func (c *HeaderCustomUDP) Build() (proto.Message, error) {
for _, item := range c.Client {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
for _, item := range c.Server {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
client := make([]*custom.UDPItem, 0, len(c.Client))
for _, item := range c.Client {
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
client = append(client, &custom.UDPItem{
Rand: item.Rand,
Packet: item.Packet,
})
}
server := make([]*custom.UDPItem, 0, len(c.Server))
for _, item := range c.Server {
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
server = append(server, &custom.UDPItem{
Rand: item.Rand,
Packet: item.Packet,
})
}
return &custom.UDPConfig{
Client: client,
Server: server,
}, nil
}
type Dns struct {
Domain string `json:"domain"`
}
@@ -1363,6 +1639,50 @@ func (c *Salamander) Build() (proto.Message, error) {
return config, nil
}
type Sudoku struct {
Password string `json:"password"`
ASCII string `json:"ascii"`
CustomTable string `json:"customTable"`
LegacyCustomTable string `json:"custom_table"`
CustomTables []string `json:"customTables"`
LegacyCustomSets []string `json:"custom_tables"`
PaddingMin uint32 `json:"paddingMin"`
LegacyPaddingMin uint32 `json:"padding_min"`
PaddingMax uint32 `json:"paddingMax"`
LegacyPaddingMax uint32 `json:"padding_max"`
}
func (c *Sudoku) Build() (proto.Message, error) {
customTable := c.CustomTable
if customTable == "" {
customTable = c.LegacyCustomTable
}
customTables := c.CustomTables
if len(customTables) == 0 {
customTables = c.LegacyCustomSets
}
paddingMin := c.PaddingMin
if paddingMin == 0 {
paddingMin = c.LegacyPaddingMin
}
paddingMax := c.PaddingMax
if paddingMax == 0 {
paddingMax = c.LegacyPaddingMax
}
return &finalsudoku.Config{
Password: c.Password,
Ascii: c.ASCII,
CustomTable: customTable,
CustomTables: customTables,
PaddingMin: paddingMin,
PaddingMax: paddingMax,
}, nil
}
type Xdns struct {
Domain string `json:"domain"`
}
@@ -1403,7 +1723,7 @@ type Mask struct {
func (c *Mask) Build(tcp bool) (proto.Message, error) {
loader := udpmaskLoader
if tcp {
return nil, errors.New("")
loader = tcpmaskLoader
}
settings := []byte("{}")
@@ -1422,8 +1742,9 @@ func (c *Mask) Build(tcp bool) (proto.Message, error) {
}
type FinalMask struct {
Tcp []Mask `json:"tcp"`
Udp []Mask `json:"udp"`
Tcp []Mask `json:"tcp"`
Udp []Mask `json:"udp"`
QuicParams *QuicParamsConfig `json:"quicParams"`
}
type StreamConfig struct {
@@ -1596,6 +1917,28 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
}
config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u))
}
if c.FinalMask.QuicParams != nil {
up, err := c.FinalMask.QuicParams.Up.Bps()
if err != nil {
return nil, err
}
if up > 0 && up < 65536 {
return nil, errors.New("Up must be at least 65536 bytes per second")
}
switch c.FinalMask.QuicParams.Congestion {
case "", "bbr", "reno":
case "force-brutal":
if up == 0 {
return nil, errors.New("force-brutal requires up")
}
default:
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: bbr, reno, force-brutal")
}
config.QuicParams = &internet.QuicParams{
Congestion: c.FinalMask.QuicParams.Congestion,
Up: up,
}
}
}
return config, nil
+7 -1
View File
@@ -28,6 +28,7 @@ import (
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
@@ -659,7 +660,7 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
}
}
// UnwrapRawConn support unwrap encryption, stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
// UnwrapRawConn support unwrap encryption, stats, mask wrappers, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
var readCounter, writerCounter stats.Counter
if conn != nil {
@@ -676,6 +677,7 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
readCounter = statConn.ReadCounter
writerCounter = statConn.WriteCounter
}
if !isEncryption { // avoids double penetration
if xc, ok := conn.(*tls.Conn); ok {
conn = xc.NetConn()
@@ -687,6 +689,9 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
conn = realityUConn.NetConn()
}
}
conn = finalmask.UnwrapTcpMask(conn)
if pc, ok := conn.(*proxyproto.Conn); ok {
conn = pc.Raw()
// 8192 > 4096, there is no need to process pc's bufReader
@@ -788,6 +793,7 @@ func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer sign
func IsRAWTransportWithoutSecurity(conn stat.Connection) bool {
iConn := stat.TryUnwrapStatsConn(conn)
iConn = finalmask.UnwrapTcpMask(iConn)
_, ok1 := iConn.(*proxyproto.Conn)
_, ok2 := iConn.(*net.TCPConn)
_, ok3 := iConn.(*internet.UnixConnWrapper)
@@ -22,6 +22,7 @@ type task struct {
Method string `json:"method"`
URL string `json:"url"`
Extra any `json:"extra,omitempty"`
StreamResponse bool `json:"streamResponse"`
}
var conns chan *websocket.Conn
@@ -52,6 +53,7 @@ func init() {
}
}
} else {
w.Header().Set("Access-Control-Allow-Origin", "*");
w.Write(webpage)
}
}))
@@ -70,6 +72,7 @@ func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
task := task{
Method: "WS",
URL: uri,
StreamResponse: true,
}
if ed != nil {
@@ -84,9 +87,10 @@ func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
type httpExtra struct {
Referrer string `json:"referrer,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Cookies map[string]string `json:"cookies,omitempty"`
}
func httpExtraFromHeaders(headers http.Header) *httpExtra {
func httpExtraFromHeadersAndCookies(headers http.Header, cookies []*http.Cookie) *httpExtra {
if len(headers) == 0 {
return nil
}
@@ -104,24 +108,37 @@ func httpExtraFromHeaders(headers http.Header) *httpExtra {
}
}
if len(cookies) > 0 {
extra.Cookies = make(map[string]string)
for _, cookie := range cookies {
extra.Cookies[cookie.Name] = cookie.Value
}
}
return &extra
}
func DialGet(uri string, headers http.Header) (*websocket.Conn, error) {
func DialGet(uri string, headers http.Header, cookies []*http.Cookie) (*websocket.Conn, error) {
task := task{
Method: "GET",
URL: uri,
Extra: httpExtraFromHeaders(headers),
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
StreamResponse: true,
}
return dialTask(task)
}
func DialPost(uri string, headers http.Header, payload []byte) error {
func DialPacket(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
return dialWithBody(method, uri, headers, cookies, payload)
}
func dialWithBody(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
task := task{
Method: "POST",
Method: method,
URL: uri,
Extra: httpExtraFromHeaders(headers),
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
StreamResponse: false,
}
conn, err := dialTask(task)
@@ -2,6 +2,7 @@
<html>
<head>
<title>Browser Dialer</title>
<link rel="icon" href="data:">
</head>
<body>
<script>
@@ -29,9 +30,37 @@
requestInit.headers = extra.headers;
}
if (extra.cookies) {
requestInit.credentials = 'include';
}
return requestInit;
}
function setCookiesFromTask(task) {
if (!task.extra.cookies) {
return;
}
const url = new URL(task.url);
for (const [name, value] of Object.entries(task.extra.cookies)) {
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + '; path=' + url.pathname;
}
}
function clearCookiesFromTask(task) {
if (!task.extra.cookies) {
return;
}
const url = new URL(task.url);
for (const [name, value] of Object.entries(task.extra.cookies)) {
document.cookie = encodeURIComponent(name) + '=; path=' + url.pathname + '; Max-Age=0';
}
}
let check = function () {
if (clientIdleCount > 0) {
return;
@@ -48,116 +77,121 @@
ws.onmessage = function (event) {
clientIdleCount -= 1;
let task = JSON.parse(event.data);
switch (task.method) {
case "WS": {
upstreamWsCount += 1;
console.log("Dial WS", task.url, task.extra.protocol);
const wss = new WebSocket(task.url, task.extra.protocol);
wss.binaryType = "arraybuffer";
let opened = false;
ws.onmessage = function (event) {
wss.send(event.data)
};
wss.onopen = function (event) {
opened = true;
ws.send("ok")
};
wss.onmessage = function (event) {
ws.send(event.data)
};
wss.onclose = function (event) {
upstreamWsCount -= 1;
console.log("Dial WS DONE, remaining: ", upstreamWsCount);
ws.close()
};
wss.onerror = function (event) {
!opened && ws.send("fail")
wss.close()
};
ws.onclose = function (event) {
wss.close()
};
break;
}
case "GET": {
(async () => {
const requestInit = prepareRequestInit(task.extra);
console.log("Dial GET", task.url);
ws.send("ok");
const controller = new AbortController();
/*
Aborting a streaming response in JavaScript
requires two levers to be pulled:
First, the streaming read itself has to be cancelled using
reader.cancel(), only then controller.abort() will actually work.
If controller.abort() alone is called while a
reader.read() is ongoing, it will block until the server closes the
response, the page is refreshed or the network connection is lost.
*/
let reader = null;
ws.onclose = (event) => {
try {
reader && reader.cancel();
} catch(e) {}
try {
controller.abort();
} catch(e) {}
};
try {
upstreamGetCount += 1;
requestInit.signal = controller.signal;
const response = await fetch(task.url, requestInit);
const body = await response.body;
reader = body.getReader();
while (true) {
const { done, value } = await reader.read();
if (value) ws.send(value); // don't send back "undefined" string when received nothing
if (done) break;
}
} finally {
upstreamGetCount -= 1;
console.log("Dial GET DONE, remaining: ", upstreamGetCount);
ws.close();
}
})();
break;
}
case "POST": {
upstreamPostCount += 1;
if (task.method == "WS") {
upstreamWsCount += 1;
console.log("Dial WS", task.url, task.extra.protocol);
const wss = new WebSocket(task.url, task.extra.protocol);
wss.binaryType = "arraybuffer";
let opened = false;
ws.onmessage = function (event) {
wss.send(event.data)
};
wss.onopen = function (event) {
opened = true;
ws.send("ok")
};
wss.onmessage = function (event) {
ws.send(event.data)
};
wss.onclose = function (event) {
upstreamWsCount -= 1;
console.log("Dial WS DONE, remaining: ", upstreamWsCount);
ws.close()
};
wss.onerror = function (event) {
!opened && ws.send("fail")
wss.close()
};
ws.onclose = function (event) {
wss.close()
};
}
else if (task.method == "GET" && task.streamResponse) {
(async () => {
const requestInit = prepareRequestInit(task.extra);
requestInit.method = "POST";
console.log("Dial POST", task.url);
console.log("Dial GET", task.url);
ws.send("ok");
ws.onmessage = async (event) => {
const controller = new AbortController();
/*
Aborting a streaming response in JavaScript
requires two levers to be pulled:
First, the streaming read itself has to be cancelled using
reader.cancel(), only then controller.abort() will actually work.
If controller.abort() alone is called while a
reader.read() is ongoing, it will block until the server closes the
response, the page is refreshed or the network connection is lost.
*/
let reader = null;
ws.onclose = (event) => {
try {
requestInit.body = event.data;
const response = await fetch(task.url, requestInit);
if (response.ok) {
ws.send("ok");
} else {
console.error("bad status code");
ws.send("fail");
}
} finally {
upstreamPostCount -= 1;
console.log("Dial POST DONE, remaining: ", upstreamPostCount);
ws.close();
}
reader && reader.cancel();
} catch(e) {}
try {
controller.abort();
} catch(e) {}
};
break;
}
try {
upstreamGetCount += 1;
requestInit.signal = controller.signal;
setCookiesFromTask(task);
const response = await fetch(task.url, requestInit);
clearCookiesFromTask(task);
const body = await response.body;
reader = body.getReader();
while (true) {
const { done, value } = await reader.read();
if (value) ws.send(value); // don't send back "undefined" string when received nothing
if (done) break;
}
} finally {
upstreamGetCount -= 1;
console.log("Dial GET DONE, remaining: ", upstreamGetCount);
ws.close();
}
})();
}
else if (!task.streamResponse) {
upstreamPostCount += 1;
const requestInit = prepareRequestInit(task.extra);
requestInit.method = task.method;
console.log("Dial", task.method, task.url);
ws.send("ok");
ws.onmessage = async (event) => {
try {
if (event.data.byteLength > 0) {
requestInit.body = event.data;
}
setCookiesFromTask(task);
const response = await fetch(task.url, requestInit);
clearCookiesFromTask(task);
if (response.ok) {
ws.send("ok");
} else {
console.error("bad status code");
ws.send("fail");
}
} finally {
upstreamPostCount -= 1;
console.log("Dial", task.method, "packet DONE, remaining: ", upstreamPostCount);
ws.close();
}
};
}
else {
console.error(`Incorrect task method=${task.method} streamResponse=${task.streamResponse}.`);
ws.close();
}
check();
+110 -40
View File
@@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4, 0}
return file_transport_internet_config_proto_rawDescGZIP(), []int{5, 0}
}
type TransportConfig struct {
@@ -276,6 +276,7 @@ type StreamConfig struct {
SecuritySettings []*serial.TypedMessage `protobuf:"bytes,4,rep,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"`
Udpmasks []*serial.TypedMessage `protobuf:"bytes,10,rep,name=udpmasks,proto3" json:"udpmasks,omitempty"`
Tcpmasks []*serial.TypedMessage `protobuf:"bytes,11,rep,name=tcpmasks,proto3" json:"tcpmasks,omitempty"`
QuicParams *QuicParams `protobuf:"bytes,12,opt,name=quic_params,json=quicParams,proto3" json:"quic_params,omitempty"`
SocketSettings *SocketConfig `protobuf:"bytes,6,opt,name=socket_settings,json=socketSettings,proto3" json:"socket_settings,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
@@ -367,6 +368,13 @@ func (x *StreamConfig) GetTcpmasks() []*serial.TypedMessage {
return nil
}
func (x *StreamConfig) GetQuicParams() *QuicParams {
if x != nil {
return x.QuicParams
}
return nil
}
func (x *StreamConfig) GetSocketSettings() *SocketConfig {
if x != nil {
return x.SocketSettings
@@ -374,6 +382,58 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
return nil
}
type QuicParams struct {
state protoimpl.MessageState `protogen:"open.v1"`
Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
Up uint64 `protobuf:"varint,2,opt,name=up,proto3" json:"up,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *QuicParams) Reset() {
*x = QuicParams{}
mi := &file_transport_internet_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *QuicParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QuicParams) ProtoMessage() {}
func (x *QuicParams) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.
func (*QuicParams) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
}
func (x *QuicParams) GetCongestion() string {
if x != nil {
return x.Congestion
}
return ""
}
func (x *QuicParams) GetUp() uint64 {
if x != nil {
return x.Up
}
return 0
}
type ProxyConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
@@ -384,7 +444,7 @@ type ProxyConfig struct {
func (x *ProxyConfig) Reset() {
*x = ProxyConfig{}
mi := &file_transport_internet_config_proto_msgTypes[2]
mi := &file_transport_internet_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -396,7 +456,7 @@ func (x *ProxyConfig) String() string {
func (*ProxyConfig) ProtoMessage() {}
func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[2]
mi := &file_transport_internet_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -409,7 +469,7 @@ func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead.
func (*ProxyConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
}
func (x *ProxyConfig) GetTag() string {
@@ -440,7 +500,7 @@ type CustomSockopt struct {
func (x *CustomSockopt) Reset() {
*x = CustomSockopt{}
mi := &file_transport_internet_config_proto_msgTypes[3]
mi := &file_transport_internet_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -452,7 +512,7 @@ func (x *CustomSockopt) String() string {
func (*CustomSockopt) ProtoMessage() {}
func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[3]
mi := &file_transport_internet_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -465,7 +525,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
func (*CustomSockopt) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
}
func (x *CustomSockopt) GetSystem() string {
@@ -547,7 +607,7 @@ type SocketConfig struct {
func (x *SocketConfig) Reset() {
*x = SocketConfig{}
mi := &file_transport_internet_config_proto_msgTypes[4]
mi := &file_transport_internet_config_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -559,7 +619,7 @@ func (x *SocketConfig) String() string {
func (*SocketConfig) ProtoMessage() {}
func (x *SocketConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[4]
mi := &file_transport_internet_config_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -572,7 +632,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
func (*SocketConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
}
func (x *SocketConfig) GetMark() int32 {
@@ -748,7 +808,7 @@ type HappyEyeballsConfig struct {
func (x *HappyEyeballsConfig) Reset() {
*x = HappyEyeballsConfig{}
mi := &file_transport_internet_config_proto_msgTypes[5]
mi := &file_transport_internet_config_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -760,7 +820,7 @@ func (x *HappyEyeballsConfig) String() string {
func (*HappyEyeballsConfig) ProtoMessage() {}
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[5]
mi := &file_transport_internet_config_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -773,7 +833,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
}
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
@@ -811,7 +871,7 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\x1ftransport/internet/config.proto\x12\x17xray.transport.internet\x1a!common/serial/typed_message.proto\x1a\x18common/net/address.proto\"t\n" +
"\x0fTransportConfig\x12#\n" +
"\rprotocol_name\x18\x03 \x01(\tR\fprotocolName\x12<\n" +
"\bsettings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\bsettings\"\x97\x04\n" +
"\bsettings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\bsettings\"\xdd\x04\n" +
"\fStreamConfig\x125\n" +
"\aaddress\x18\b \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" +
"\x04port\x18\t \x01(\rR\x04port\x12#\n" +
@@ -821,8 +881,16 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\x11security_settings\x18\x04 \x03(\v2 .xray.common.serial.TypedMessageR\x10securitySettings\x12<\n" +
"\budpmasks\x18\n" +
" \x03(\v2 .xray.common.serial.TypedMessageR\budpmasks\x12<\n" +
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12N\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"Q\n" +
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" +
"\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
"quicParams\x12N\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"<\n" +
"\n" +
"QuicParams\x12\x1e\n" +
"\n" +
"congestion\x18\x01 \x01(\tR\n" +
"congestion\x12\x0e\n" +
"\x02up\x18\x02 \x01(\x04R\x02up\"Q\n" +
"\vProxyConfig\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
@@ -911,38 +979,40 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
}
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_transport_internet_config_proto_goTypes = []any{
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
(*ProxyConfig)(nil), // 5: xray.transport.internet.ProxyConfig
(*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt
(*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig
(*HappyEyeballsConfig)(nil), // 8: xray.transport.internet.HappyEyeballsConfig
(*serial.TypedMessage)(nil), // 9: xray.common.serial.TypedMessage
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
(*QuicParams)(nil), // 5: xray.transport.internet.QuicParams
(*ProxyConfig)(nil), // 6: xray.transport.internet.ProxyConfig
(*CustomSockopt)(nil), // 7: xray.transport.internet.CustomSockopt
(*SocketConfig)(nil), // 8: xray.transport.internet.SocketConfig
(*HappyEyeballsConfig)(nil), // 9: xray.transport.internet.HappyEyeballsConfig
(*serial.TypedMessage)(nil), // 10: xray.common.serial.TypedMessage
(*net.IPOrDomain)(nil), // 11: xray.common.net.IPOrDomain
}
var file_transport_internet_config_proto_depIdxs = []int32{
9, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
10, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
10, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
11, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
9, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
9, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
9, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
7, // 6: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
2, // 7: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
0, // 8: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
6, // 9: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
1, // 10: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
8, // 11: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
10, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
10, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
10, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
5, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
8, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
2, // 8: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
0, // 9: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
7, // 10: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
1, // 11: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
9, // 12: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
func init() { file_transport_internet_config_proto_init() }
@@ -956,7 +1026,7 @@ func file_transport_internet_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
NumEnums: 3,
NumMessages: 6,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
@@ -59,9 +59,16 @@ message StreamConfig {
repeated xray.common.serial.TypedMessage udpmasks = 10;
repeated xray.common.serial.TypedMessage tcpmasks = 11;
QuicParams quic_params = 12;
SocketConfig socket_settings = 6;
}
message QuicParams {
string congestion = 1;
uint64 up = 2;
}
message ProxyConfig {
string tag = 1;
bool transportLayerProxy = 2;
@@ -1,18 +1,21 @@
package finalmask
import (
"context"
"net"
"github.com/xtls/xray-core/common/errors"
)
type ConnSize interface {
Size() int32
}
const (
UDPSize = 4096 + 123
)
type Udpmask interface {
UDP()
WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error)
WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error)
WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
}
type UdpmaskManager struct {
@@ -26,27 +29,23 @@ func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
}
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
leaveSize := int32(0)
var err error
for i, mask := range m.udpmasks {
raw, err = mask.WrapPacketConnClient(raw, i == len(m.udpmasks)-1, leaveSize, i == 0)
raw, err = mask.WrapPacketConnClient(raw, i, len(m.udpmasks)-1)
if err != nil {
return nil, err
}
leaveSize += raw.(ConnSize).Size()
}
return raw, nil
}
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
leaveSize := int32(0)
var err error
for i, mask := range m.udpmasks {
raw, err = mask.WrapPacketConnServer(raw, i == len(m.udpmasks)-1, leaveSize, i == 0)
raw, err = mask.WrapPacketConnServer(raw, i, len(m.udpmasks)-1)
if err != nil {
return nil, err
}
leaveSize += raw.(ConnSize).Size()
}
return raw, nil
}
@@ -89,3 +88,54 @@ func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
}
return raw, nil
}
func (m *TcpmaskManager) WrapListener(l net.Listener) (net.Listener, error) {
return NewTcpListener(m, l)
}
type tcpListener struct {
m *TcpmaskManager
net.Listener
}
func NewTcpListener(m *TcpmaskManager, l net.Listener) (net.Listener, error) {
return &tcpListener{
m: m,
Listener: l,
}, nil
}
func (l *tcpListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return conn, err
}
newConn, err := l.m.WrapConnServer(conn)
if err != nil {
errors.LogDebugInner(context.Background(), err, "mask err")
// conn.Close()
return conn, nil
}
return newConn, nil
}
type TcpMaskConn interface {
TcpMaskConn()
RawConn() net.Conn
Splice() bool
}
func UnwrapTcpMask(conn net.Conn) net.Conn {
for {
if v, ok := conn.(TcpMaskConn); ok {
if !v.Splice() {
return conn
}
conn = v.RawConn()
} else {
return conn
}
}
}
@@ -0,0 +1,14 @@
package fragment
import "net"
func (c *Config) TCP() {
}
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClient(c, raw, false)
}
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServer(c, raw, true)
}
@@ -0,0 +1,189 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: transport/internet/finalmask/fragment/config.proto
package fragment
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
PacketsFrom int64 `protobuf:"varint,1,opt,name=packets_from,json=packetsFrom,proto3" json:"packets_from,omitempty"`
PacketsTo int64 `protobuf:"varint,2,opt,name=packets_to,json=packetsTo,proto3" json:"packets_to,omitempty"`
LengthMin int64 `protobuf:"varint,3,opt,name=length_min,json=lengthMin,proto3" json:"length_min,omitempty"`
LengthMax int64 `protobuf:"varint,4,opt,name=length_max,json=lengthMax,proto3" json:"length_max,omitempty"`
DelayMin int64 `protobuf:"varint,5,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
DelayMax int64 `protobuf:"varint,6,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
MaxSplitMin int64 `protobuf:"varint,7,opt,name=max_split_min,json=maxSplitMin,proto3" json:"max_split_min,omitempty"`
MaxSplitMax int64 `protobuf:"varint,8,opt,name=max_split_max,json=maxSplitMax,proto3" json:"max_split_max,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_finalmask_fragment_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_fragment_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_fragment_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetPacketsFrom() int64 {
if x != nil {
return x.PacketsFrom
}
return 0
}
func (x *Config) GetPacketsTo() int64 {
if x != nil {
return x.PacketsTo
}
return 0
}
func (x *Config) GetLengthMin() int64 {
if x != nil {
return x.LengthMin
}
return 0
}
func (x *Config) GetLengthMax() int64 {
if x != nil {
return x.LengthMax
}
return 0
}
func (x *Config) GetDelayMin() int64 {
if x != nil {
return x.DelayMin
}
return 0
}
func (x *Config) GetDelayMax() int64 {
if x != nil {
return x.DelayMax
}
return 0
}
func (x *Config) GetMaxSplitMin() int64 {
if x != nil {
return x.MaxSplitMin
}
return 0
}
func (x *Config) GetMaxSplitMax() int64 {
if x != nil {
return x.MaxSplitMax
}
return 0
}
var File_transport_internet_finalmask_fragment_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_fragment_config_proto_rawDesc = "" +
"\n" +
"2transport/internet/finalmask/fragment/config.proto\x12*xray.transport.internet.finalmask.fragment\"\x8a\x02\n" +
"\x06Config\x12!\n" +
"\fpackets_from\x18\x01 \x01(\x03R\vpacketsFrom\x12\x1d\n" +
"\n" +
"packets_to\x18\x02 \x01(\x03R\tpacketsTo\x12\x1d\n" +
"\n" +
"length_min\x18\x03 \x01(\x03R\tlengthMin\x12\x1d\n" +
"\n" +
"length_max\x18\x04 \x01(\x03R\tlengthMax\x12\x1b\n" +
"\tdelay_min\x18\x05 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x06 \x01(\x03R\bdelayMax\x12\"\n" +
"\rmax_split_min\x18\a \x01(\x03R\vmaxSplitMin\x12\"\n" +
"\rmax_split_max\x18\b \x01(\x03R\vmaxSplitMaxB\xa0\x01\n" +
".com.xray.transport.internet.finalmask.fragmentP\x01Z?github.com/xtls/xray-core/transport/internet/finalmask/fragment\xaa\x02*Xray.Transport.Internet.Finalmask.Fragmentb\x06proto3"
var (
file_transport_internet_finalmask_fragment_config_proto_rawDescOnce sync.Once
file_transport_internet_finalmask_fragment_config_proto_rawDescData []byte
)
func file_transport_internet_finalmask_fragment_config_proto_rawDescGZIP() []byte {
file_transport_internet_finalmask_fragment_config_proto_rawDescOnce.Do(func() {
file_transport_internet_finalmask_fragment_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_fragment_config_proto_rawDesc), len(file_transport_internet_finalmask_fragment_config_proto_rawDesc)))
})
return file_transport_internet_finalmask_fragment_config_proto_rawDescData
}
var file_transport_internet_finalmask_fragment_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_transport_internet_finalmask_fragment_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.transport.internet.finalmask.fragment.Config
}
var file_transport_internet_finalmask_fragment_config_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_fragment_config_proto_init() }
func file_transport_internet_finalmask_fragment_config_proto_init() {
if File_transport_internet_finalmask_fragment_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_fragment_config_proto_rawDesc), len(file_transport_internet_finalmask_fragment_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_finalmask_fragment_config_proto_goTypes,
DependencyIndexes: file_transport_internet_finalmask_fragment_config_proto_depIdxs,
MessageInfos: file_transport_internet_finalmask_fragment_config_proto_msgTypes,
}.Build()
File_transport_internet_finalmask_fragment_config_proto = out.File
file_transport_internet_finalmask_fragment_config_proto_goTypes = nil
file_transport_internet_finalmask_fragment_config_proto_depIdxs = nil
}
@@ -0,0 +1,18 @@
syntax = "proto3";
package xray.transport.internet.finalmask.fragment;
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Fragment";
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/fragment";
option java_package = "com.xray.transport.internet.finalmask.fragment";
option java_multiple_files = true;
message Config {
int64 packets_from = 1;
int64 packets_to = 2;
int64 length_min = 3;
int64 length_max = 4;
int64 delay_min = 5;
int64 delay_max = 6;
int64 max_split_min = 7;
int64 max_split_max = 8;
}
@@ -0,0 +1,128 @@
package fragment
import (
"net"
"time"
"github.com/xtls/xray-core/common/crypto"
)
type fragmentConn struct {
net.Conn
config *Config
count uint64
server bool
}
func NewConnClient(c *Config, raw net.Conn, server bool) (net.Conn, error) {
conn := &fragmentConn{
Conn: raw,
config: c,
server: server,
}
return conn, nil
}
func NewConnServer(c *Config, raw net.Conn, server bool) (net.Conn, error) {
return NewConnClient(c, raw, server)
}
func (c *fragmentConn) TcpMaskConn() {}
func (c *fragmentConn) RawConn() net.Conn {
if c.server {
return c
}
return c.Conn
}
func (c *fragmentConn) Splice() bool {
if c.server {
return false
}
return true
}
func (c *fragmentConn) Write(p []byte) (n int, err error) {
c.count++
if c.config.PacketsFrom == 0 && c.config.PacketsTo == 1 {
if c.count != 1 || len(p) <= 5 || p[0] != 22 {
return c.Conn.Write(p)
}
recordLen := 5 + ((int(p[3]) << 8) | int(p[4]))
if len(p) < recordLen {
return c.Conn.Write(p)
}
data := p[5:recordLen]
buff := make([]byte, 2048)
var hello []byte
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
var splitNum int64
for from := 0; ; {
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))
splitNum++
if to > len(data) || (maxSplit > 0 && splitNum >= maxSplit) {
to = len(data)
}
l := to - from
if 5+l > len(buff) {
buff = make([]byte, 5+l)
}
copy(buff[:3], p)
copy(buff[5:], data[from:to])
from = to
buff[3] = byte(l >> 8)
buff[4] = byte(l)
if c.config.DelayMax == 0 {
hello = append(hello, buff[:5+l]...)
} else {
_, err := c.Conn.Write(buff[:5+l])
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)
if err != nil {
return 0, err
}
}
if from == len(data) {
if len(hello) > 0 {
_, err := c.Conn.Write(hello)
if err != nil {
return 0, err
}
}
if len(p) > recordLen {
n, err := c.Conn.Write(p[recordLen:])
if err != nil {
return recordLen + n, err
}
}
return len(p), nil
}
}
}
if c.config.PacketsFrom != 0 && (c.count < uint64(c.config.PacketsFrom) || c.count > uint64(c.config.PacketsTo)) {
return c.Conn.Write(p)
}
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
var splitNum int64
for from := 0; ; {
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))
splitNum++
if to > len(p) || (maxSplit > 0 && splitNum >= maxSplit) {
to = len(p)
}
n, err := c.Conn.Write(p[from:to])
from += n
if err != nil {
return from, err
}
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)
if from >= len(p) {
return from, nil
}
}
}
@@ -0,0 +1,27 @@
package custom
import (
"net"
)
func (c *TCPConfig) TCP() {
}
func (c *TCPConfig) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClientTCP(c, raw)
}
func (c *TCPConfig) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServerTCP(c, raw)
}
func (c *UDPConfig) UDP() {
}
func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClientUDP(c, raw)
}
func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServerUDP(c, raw)
}
@@ -0,0 +1,380 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: transport/internet/finalmask/header/custom/config.proto
package custom
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type TCPItem struct {
state protoimpl.MessageState `protogen:"open.v1"`
DelayMin int64 `protobuf:"varint,1,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
DelayMax int64 `protobuf:"varint,2,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
Rand int32 `protobuf:"varint,3,opt,name=rand,proto3" json:"rand,omitempty"`
Packet []byte `protobuf:"bytes,4,opt,name=packet,proto3" json:"packet,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TCPItem) Reset() {
*x = TCPItem{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TCPItem) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TCPItem) ProtoMessage() {}
func (x *TCPItem) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TCPItem.ProtoReflect.Descriptor instead.
func (*TCPItem) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{0}
}
func (x *TCPItem) GetDelayMin() int64 {
if x != nil {
return x.DelayMin
}
return 0
}
func (x *TCPItem) GetDelayMax() int64 {
if x != nil {
return x.DelayMax
}
return 0
}
func (x *TCPItem) GetRand() int32 {
if x != nil {
return x.Rand
}
return 0
}
func (x *TCPItem) GetPacket() []byte {
if x != nil {
return x.Packet
}
return nil
}
type TCPSequence struct {
state protoimpl.MessageState `protogen:"open.v1"`
Sequence []*TCPItem `protobuf:"bytes,1,rep,name=sequence,proto3" json:"sequence,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TCPSequence) Reset() {
*x = TCPSequence{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TCPSequence) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TCPSequence) ProtoMessage() {}
func (x *TCPSequence) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TCPSequence.ProtoReflect.Descriptor instead.
func (*TCPSequence) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{1}
}
func (x *TCPSequence) GetSequence() []*TCPItem {
if x != nil {
return x.Sequence
}
return nil
}
type TCPConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Clients []*TCPSequence `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
Servers []*TCPSequence `protobuf:"bytes,2,rep,name=servers,proto3" json:"servers,omitempty"`
Errors []*TCPSequence `protobuf:"bytes,3,rep,name=errors,proto3" json:"errors,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TCPConfig) Reset() {
*x = TCPConfig{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TCPConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TCPConfig) ProtoMessage() {}
func (x *TCPConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TCPConfig.ProtoReflect.Descriptor instead.
func (*TCPConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{2}
}
func (x *TCPConfig) GetClients() []*TCPSequence {
if x != nil {
return x.Clients
}
return nil
}
func (x *TCPConfig) GetServers() []*TCPSequence {
if x != nil {
return x.Servers
}
return nil
}
func (x *TCPConfig) GetErrors() []*TCPSequence {
if x != nil {
return x.Errors
}
return nil
}
type UDPItem struct {
state protoimpl.MessageState `protogen:"open.v1"`
Rand int32 `protobuf:"varint,1,opt,name=rand,proto3" json:"rand,omitempty"`
Packet []byte `protobuf:"bytes,2,opt,name=packet,proto3" json:"packet,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UDPItem) Reset() {
*x = UDPItem{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UDPItem) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UDPItem) ProtoMessage() {}
func (x *UDPItem) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UDPItem.ProtoReflect.Descriptor instead.
func (*UDPItem) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{3}
}
func (x *UDPItem) GetRand() int32 {
if x != nil {
return x.Rand
}
return 0
}
func (x *UDPItem) GetPacket() []byte {
if x != nil {
return x.Packet
}
return nil
}
type UDPConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Client []*UDPItem `protobuf:"bytes,1,rep,name=client,proto3" json:"client,omitempty"`
Server []*UDPItem `protobuf:"bytes,2,rep,name=server,proto3" json:"server,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UDPConfig) Reset() {
*x = UDPConfig{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UDPConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UDPConfig) ProtoMessage() {}
func (x *UDPConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UDPConfig.ProtoReflect.Descriptor instead.
func (*UDPConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{4}
}
func (x *UDPConfig) GetClient() []*UDPItem {
if x != nil {
return x.Client
}
return nil
}
func (x *UDPConfig) GetServer() []*UDPItem {
if x != nil {
return x.Server
}
return nil
}
var File_transport_internet_finalmask_header_custom_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_header_custom_config_proto_rawDesc = "" +
"\n" +
"7transport/internet/finalmask/header/custom/config.proto\x12/xray.transport.internet.finalmask.header.custom\"o\n" +
"\aTCPItem\x12\x1b\n" +
"\tdelay_min\x18\x01 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x02 \x01(\x03R\bdelayMax\x12\x12\n" +
"\x04rand\x18\x03 \x01(\x05R\x04rand\x12\x16\n" +
"\x06packet\x18\x04 \x01(\fR\x06packet\"c\n" +
"\vTCPSequence\x12T\n" +
"\bsequence\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.TCPItemR\bsequence\"\x91\x02\n" +
"\tTCPConfig\x12V\n" +
"\aclients\x18\x01 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\aclients\x12V\n" +
"\aservers\x18\x02 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\aservers\x12T\n" +
"\x06errors\x18\x03 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\x06errors\"5\n" +
"\aUDPItem\x12\x12\n" +
"\x04rand\x18\x01 \x01(\x05R\x04rand\x12\x16\n" +
"\x06packet\x18\x02 \x01(\fR\x06packet\"\xaf\x01\n" +
"\tUDPConfig\x12P\n" +
"\x06client\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06client\x12P\n" +
"\x06server\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06serverB\xaf\x01\n" +
"3com.xray.transport.internet.finalmask.header.customP\x01ZDgithub.com/xtls/xray-core/transport/internet/finalmask/header/custom\xaa\x02/Xray.Transport.Internet.Finalmask.Header.Customb\x06proto3"
var (
file_transport_internet_finalmask_header_custom_config_proto_rawDescOnce sync.Once
file_transport_internet_finalmask_header_custom_config_proto_rawDescData []byte
)
func file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP() []byte {
file_transport_internet_finalmask_header_custom_config_proto_rawDescOnce.Do(func() {
file_transport_internet_finalmask_header_custom_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_custom_config_proto_rawDesc), len(file_transport_internet_finalmask_header_custom_config_proto_rawDesc)))
})
return file_transport_internet_finalmask_header_custom_config_proto_rawDescData
}
var file_transport_internet_finalmask_header_custom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_transport_internet_finalmask_header_custom_config_proto_goTypes = []any{
(*TCPItem)(nil), // 0: xray.transport.internet.finalmask.header.custom.TCPItem
(*TCPSequence)(nil), // 1: xray.transport.internet.finalmask.header.custom.TCPSequence
(*TCPConfig)(nil), // 2: xray.transport.internet.finalmask.header.custom.TCPConfig
(*UDPItem)(nil), // 3: xray.transport.internet.finalmask.header.custom.UDPItem
(*UDPConfig)(nil), // 4: xray.transport.internet.finalmask.header.custom.UDPConfig
}
var file_transport_internet_finalmask_header_custom_config_proto_depIdxs = []int32{
0, // 0: xray.transport.internet.finalmask.header.custom.TCPSequence.sequence:type_name -> xray.transport.internet.finalmask.header.custom.TCPItem
1, // 1: xray.transport.internet.finalmask.header.custom.TCPConfig.clients:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
1, // 2: xray.transport.internet.finalmask.header.custom.TCPConfig.servers:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
1, // 3: xray.transport.internet.finalmask.header.custom.TCPConfig.errors:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
3, // 4: xray.transport.internet.finalmask.header.custom.UDPConfig.client:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
3, // 5: xray.transport.internet.finalmask.header.custom.UDPConfig.server:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_header_custom_config_proto_init() }
func file_transport_internet_finalmask_header_custom_config_proto_init() {
if File_transport_internet_finalmask_header_custom_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_custom_config_proto_rawDesc), len(file_transport_internet_finalmask_header_custom_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_finalmask_header_custom_config_proto_goTypes,
DependencyIndexes: file_transport_internet_finalmask_header_custom_config_proto_depIdxs,
MessageInfos: file_transport_internet_finalmask_header_custom_config_proto_msgTypes,
}.Build()
File_transport_internet_finalmask_header_custom_config_proto = out.File
file_transport_internet_finalmask_header_custom_config_proto_goTypes = nil
file_transport_internet_finalmask_header_custom_config_proto_depIdxs = nil
}
@@ -0,0 +1,34 @@
syntax = "proto3";
package xray.transport.internet.finalmask.header.custom;
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Header.Custom";
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/header/custom";
option java_package = "com.xray.transport.internet.finalmask.header.custom";
option java_multiple_files = true;
message TCPItem {
int64 delay_min = 1;
int64 delay_max = 2;
int32 rand = 3;
bytes packet = 4;
}
message TCPSequence {
repeated TCPItem sequence = 1;
}
message TCPConfig {
repeated TCPSequence clients = 1;
repeated TCPSequence servers = 2;
repeated TCPSequence errors = 3;
}
message UDPItem {
int32 rand = 1;
bytes packet = 2;
}
message UDPConfig {
repeated UDPItem client = 1;
repeated UDPItem server = 2;
}
@@ -0,0 +1,248 @@
package custom
import (
"bytes"
"crypto/rand"
"io"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
)
type tcpCustomClient struct {
clients []*TCPSequence
servers []*TCPSequence
}
type tcpCustomClientConn struct {
net.Conn
header *tcpCustomClient
auth bool
wg sync.WaitGroup
once sync.Once
}
func NewConnClientTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
conn := &tcpCustomClientConn{
Conn: raw,
header: &tcpCustomClient{
clients: c.Clients,
servers: c.Servers,
},
}
conn.wg.Add(1)
return conn, nil
}
func (c *tcpCustomClientConn) TcpMaskConn() {}
func (c *tcpCustomClientConn) RawConn() net.Conn {
// c.wg.Wait()
return c.Conn
}
func (c *tcpCustomClientConn) Splice() bool {
return true
}
func (c *tcpCustomClientConn) Read(p []byte) (n int, err error) {
c.wg.Wait()
if !c.auth {
return 0, errors.New("header auth failed")
}
return c.Conn.Read(p)
}
func (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {
c.once.Do(func() {
i := 0
j := 0
for i = range c.header.clients {
if !writeSequence(c.Conn, c.header.clients[i]) {
c.wg.Done()
return
}
if j < len(c.header.servers) {
if !readSequence(c.Conn, c.header.servers[j]) {
c.wg.Done()
return
}
j++
}
}
for j < len(c.header.servers) {
if !readSequence(c.Conn, c.header.servers[j]) {
c.wg.Done()
return
}
j++
}
c.auth = true
c.wg.Done()
})
c.wg.Wait()
if !c.auth {
return 0, errors.New("header auth failed")
}
return c.Conn.Write(p)
}
type tcpCustomServer struct {
clients []*TCPSequence
servers []*TCPSequence
errors []*TCPSequence
}
type tcpCustomServerConn struct {
net.Conn
header *tcpCustomServer
auth bool
wg sync.WaitGroup
once sync.Once
}
func NewConnServerTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
conn := &tcpCustomServerConn{
Conn: raw,
header: &tcpCustomServer{
clients: c.Clients,
servers: c.Servers,
errors: c.Errors,
},
}
conn.wg.Add(1)
return conn, nil
}
func (c *tcpCustomServerConn) TcpMaskConn() {}
func (c *tcpCustomServerConn) RawConn() net.Conn {
// c.wg.Wait()
return c.Conn
}
func (c *tcpCustomServerConn) Splice() bool {
return true
}
func (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {
c.once.Do(func() {
i := 0
j := 0
for i = range c.header.clients {
if !readSequence(c.Conn, c.header.clients[i]) {
if i < len(c.header.errors) {
writeSequence(c.Conn, c.header.errors[i])
}
c.wg.Done()
return
}
if j < len(c.header.servers) {
if !writeSequence(c.Conn, c.header.servers[j]) {
c.wg.Done()
return
}
j++
}
}
for j < len(c.header.servers) {
if !writeSequence(c.Conn, c.header.servers[j]) {
c.wg.Done()
return
}
j++
}
c.auth = true
c.wg.Done()
})
c.wg.Wait()
if !c.auth {
return 0, errors.New("header auth failed")
}
return c.Conn.Read(p)
}
func (c *tcpCustomServerConn) Write(p []byte) (n int, err error) {
c.wg.Wait()
if !c.auth {
return 0, errors.New("header auth failed")
}
return c.Conn.Write(p)
}
func readSequence(r io.Reader, sequence *TCPSequence) bool {
for _, item := range sequence.Sequence {
length := max(int(item.Rand), len(item.Packet))
buf := make([]byte, length)
n, err := io.ReadFull(r, buf)
if err != nil {
return false
}
if item.Rand > 0 && n != length {
return false
}
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, buf[:n]) {
return false
}
}
return true
}
func writeSequence(w io.Writer, sequence *TCPSequence) bool {
var merged []byte
for _, item := range sequence.Sequence {
if item.DelayMax > 0 {
if len(merged) > 0 {
_, err := w.Write(merged)
if err != nil {
return false
}
merged = nil
}
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
}
if item.Rand > 0 {
buf := make([]byte, item.Rand)
common.Must2(rand.Read(buf))
merged = append(merged, buf...)
} else {
merged = append(merged, item.Packet...)
}
}
if len(merged) > 0 {
_, err := w.Write(merged)
if err != nil {
return false
}
merged = nil
}
return true
}
@@ -0,0 +1,250 @@
package custom
import (
"bytes"
"context"
"crypto/rand"
"net"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type udpCustomClient struct {
client []*UDPItem
server []*UDPItem
merged []byte
}
func (h *udpCustomClient) Serialize(b []byte) {
index := 0
for _, item := range h.client {
if item.Rand > 0 {
common.Must2(rand.Read(h.merged[index : index+int(item.Rand)]))
index += int(item.Rand)
} else {
index += len(item.Packet)
}
}
copy(b, h.merged)
}
func (h *udpCustomClient) Match(b []byte) bool {
if len(b) < len(h.merged) {
return false
}
data := b
match := true
for _, item := range h.server {
length := max(int(item.Rand), len(item.Packet))
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
match = false
break
}
data = data[length:]
}
return match
}
type udpCustomClientConn struct {
net.PacketConn
header *udpCustomClient
}
func NewConnClientUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
conn := &udpCustomClientConn{
PacketConn: raw,
header: &udpCustomClient{
client: c.Client,
server: c.Server,
},
}
index := 0
for _, item := range conn.header.client {
if item.Rand > 0 {
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
index += int(item.Rand)
} else {
conn.header.merged = append(conn.header.merged, item.Packet...)
index += len(item.Packet)
}
}
return conn, nil
}
func (c *udpCustomClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if !c.header.Match(buf[:n]) {
errors.LogDebug(context.Background(), addr, " mask read err header mismatch")
return 0, addr, nil
}
if len(p) < n-len(c.header.merged) {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-len(c.header.merged))
return 0, addr, nil
}
copy(p, buf[len(c.header.merged):n])
return n - len(c.header.merged), addr, nil
}
func (c *udpCustomClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if len(c.header.merged)+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", len(c.header.merged)+len(p), " ", finalmask.UDPSize)
return 0, nil
}
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:len(c.header.merged)+len(p)]
}
copy(buf[len(c.header.merged):], p)
c.header.Serialize(buf)
_, err = c.PacketConn.WriteTo(buf[:len(c.header.merged)+len(p)], addr)
if err != nil {
return 0, err
}
return len(p), nil
}
type udpCustomServer struct {
client []*UDPItem
server []*UDPItem
merged []byte
}
func (h *udpCustomServer) Serialize(b []byte) {
index := 0
for _, item := range h.server {
if item.Rand > 0 {
common.Must2(rand.Read(h.merged[index : index+int(item.Rand)]))
index += int(item.Rand)
} else {
index += len(item.Packet)
}
}
copy(b, h.merged)
}
func (h *udpCustomServer) Match(b []byte) bool {
if len(b) < len(h.merged) {
return false
}
data := b
match := true
for _, item := range h.client {
length := max(int(item.Rand), len(item.Packet))
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
match = false
break
}
data = data[length:]
}
return match
}
type udpCustomServerConn struct {
net.PacketConn
header *udpCustomServer
}
func NewConnServerUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
conn := &udpCustomServerConn{
PacketConn: raw,
header: &udpCustomServer{
client: c.Client,
server: c.Server,
},
}
index := 0
for _, item := range conn.header.server {
if item.Rand > 0 {
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
index += int(item.Rand)
} else {
conn.header.merged = append(conn.header.merged, item.Packet...)
index += len(item.Packet)
}
}
return conn, nil
}
func (c *udpCustomServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if !c.header.Match(buf[:n]) {
errors.LogDebug(context.Background(), addr, " mask read err header mismatch")
return 0, addr, nil
}
if len(p) < n-len(c.header.merged) {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-len(c.header.merged))
return 0, addr, nil
}
copy(p, buf[len(c.header.merged):n])
return n - len(c.header.merged), addr, nil
}
func (c *udpCustomServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if len(c.header.merged)+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", len(c.header.merged)+len(p), " ", finalmask.UDPSize)
return 0, nil
}
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:len(c.header.merged)+len(p)]
}
copy(buf[len(c.header.merged):], p)
c.header.Serialize(buf)
_, err = c.PacketConn.WriteTo(buf[:len(c.header.merged)+len(p)], addr)
if err != nil {
return 0, err
}
return len(p), nil
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,14 +1,13 @@
package dns
import (
"context"
"encoding/binary"
"io"
"net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func packDomainName(s string, msg []byte) (off1 int, err error) {
@@ -81,8 +80,8 @@ type dns struct {
header []byte
}
func (h *dns) Size() int32 {
return int32(len(h.header))
func (h *dns) Size() int {
return len(h.header)
}
func (h *dns) Serialize(b []byte) {
@@ -91,19 +90,11 @@ func (h *dns) Serialize(b []byte) {
}
type dnsConn struct {
first bool
leaveSize int32
conn net.PacketConn
net.PacketConn
header *dns
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
var header []byte
header = binary.BigEndian.AppendUint16(header, 0x0000) // Transaction ID
header = binary.BigEndian.AppendUint16(header, 0x0100) // Flags: Standard query
@@ -121,121 +112,65 @@ func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (
header = binary.BigEndian.AppendUint16(header, 0x0001) // Class: IN
conn := &dnsConn{
first: first,
leaveSize: leaveSize,
conn: raw,
PacketConn: raw,
header: &dns{
header: header,
},
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
}
func (c *dnsConn) Size() int32 {
return c.header.Size()
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *dnsConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
if n < c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err header mismatch")
return 0, addr, nil
}
copy(p, p[c.Size():n])
if len(p) < n-c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-c.header.Size())
return 0, addr, nil
}
return n - int(c.Size()), addr, err
copy(p, buf[c.header.Size():n])
return n - c.header.Size(), addr, nil
}
func (c *dnsConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
if c.header.Size()+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", c.header.Size()+len(p), " ", finalmask.UDPSize)
return 0, nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:c.header.Size()+len(p)]
}
return c.conn.WriteTo(p, addr)
}
copy(buf[c.header.Size():], p)
c.header.Serialize(buf)
func (c *dnsConn) Close() error {
return c.conn.Close()
}
_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)
if err != nil {
return 0, err
}
func (c *dnsConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *dnsConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *dnsConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *dnsConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,13 +1,12 @@
package dtls
import (
"io"
"context"
"net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type dtls struct {
@@ -16,7 +15,7 @@ type dtls struct {
sequence uint32
}
func (*dtls) Size() int32 {
func (*dtls) Size() int {
return 1 + 2 + 2 + 6 + 2
}
@@ -42,24 +41,13 @@ func (h *dtls) Serialize(b []byte) {
}
type dtlsConn struct {
first bool
leaveSize int32
conn net.PacketConn
net.PacketConn
header *dtls
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &dtlsConn{
first: first,
leaveSize: leaveSize,
conn: raw,
PacketConn: raw,
header: &dtls{
epoch: dice.RollUint16(),
sequence: 0,
@@ -67,112 +55,59 @@ func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (
},
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
}
func (c *dtlsConn) Size() int32 {
return c.header.Size()
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *dtlsConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
if n < c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err header mismatch")
return 0, addr, nil
}
copy(p, p[c.Size():n])
if len(p) < n-c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-c.header.Size())
return 0, addr, nil
}
return n - int(c.Size()), addr, err
copy(p, buf[c.header.Size():n])
return n - c.header.Size(), addr, nil
}
func (c *dtlsConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
if c.header.Size()+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", c.header.Size()+len(p), " ", finalmask.UDPSize)
return 0, nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:c.header.Size()+len(p)]
}
return c.conn.WriteTo(p, addr)
}
copy(buf[c.header.Size():], p)
c.header.Serialize(buf)
func (c *dtlsConn) Close() error {
return c.conn.Close()
}
_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)
if err != nil {
return 0, err
}
func (c *dtlsConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *dtlsConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *dtlsConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *dtlsConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,14 +1,13 @@
package srtp
import (
"context"
"encoding/binary"
"io"
"net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type srtp struct {
@@ -16,7 +15,7 @@ type srtp struct {
number uint16
}
func (*srtp) Size() int32 {
func (*srtp) Size() int {
return 4
}
@@ -27,136 +26,72 @@ func (h *srtp) Serialize(b []byte) {
}
type srtpConn struct {
first bool
leaveSize int32
conn net.PacketConn
net.PacketConn
header *srtp
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &srtpConn{
first: first,
leaveSize: leaveSize,
conn: raw,
PacketConn: raw,
header: &srtp{
header: 0xB5E8,
number: dice.RollUint16(),
},
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
}
func (c *srtpConn) Size() int32 {
return c.header.Size()
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *srtpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
if n < c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err header mismatch")
return 0, addr, nil
}
copy(p, p[c.Size():n])
if len(p) < n-c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-c.header.Size())
return 0, addr, nil
}
return n - int(c.Size()), addr, err
copy(p, buf[c.header.Size():n])
return n - c.header.Size(), addr, nil
}
func (c *srtpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
if c.header.Size()+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", c.header.Size()+len(p), " ", finalmask.UDPSize)
return 0, nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:c.header.Size()+len(p)]
}
return c.conn.WriteTo(p, addr)
}
copy(buf[c.header.Size():], p)
c.header.Serialize(buf)
func (c *srtpConn) Close() error {
return c.conn.Close()
}
_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)
if err != nil {
return 0, err
}
func (c *srtpConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *srtpConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *srtpConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *srtpConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,14 +1,13 @@
package utp
import (
"context"
"encoding/binary"
"io"
"net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type utp struct {
@@ -17,7 +16,7 @@ type utp struct {
connectionID uint16
}
func (*utp) Size() int32 {
func (*utp) Size() int {
return 4
}
@@ -28,24 +27,13 @@ func (h *utp) Serialize(b []byte) {
}
type utpConn struct {
first bool
leaveSize int32
conn net.PacketConn
net.PacketConn
header *utp
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &utpConn{
first: first,
leaveSize: leaveSize,
conn: raw,
PacketConn: raw,
header: &utp{
header: 1,
extension: 0,
@@ -53,112 +41,59 @@ func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (
},
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
}
func (c *utpConn) Size() int32 {
return c.header.Size()
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *utpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
if n < c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err header mismatch")
return 0, addr, nil
}
copy(p, p[c.Size():n])
if len(p) < n-c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-c.header.Size())
return 0, addr, nil
}
return n - int(c.Size()), addr, err
copy(p, buf[c.header.Size():n])
return n - c.header.Size(), addr, nil
}
func (c *utpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
if c.header.Size()+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", c.header.Size()+len(p), " ", finalmask.UDPSize)
return 0, nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:c.header.Size()+len(p)]
}
return c.conn.WriteTo(p, addr)
}
copy(buf[c.header.Size():], p)
c.header.Serialize(buf)
func (c *utpConn) Close() error {
return c.conn.Close()
}
_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)
if err != nil {
return 0, err
}
func (c *utpConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *utpConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *utpConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *utpConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,21 +1,20 @@
package wechat
import (
"context"
"encoding/binary"
"io"
"net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type wechat struct {
sn uint32
}
func (*wechat) Size() int32 {
func (*wechat) Size() int {
return 13
}
@@ -34,135 +33,71 @@ func (h *wechat) Serialize(b []byte) {
}
type wechatConn struct {
first bool
leaveSize int32
conn net.PacketConn
net.PacketConn
header *wechat
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &wechatConn{
first: first,
leaveSize: leaveSize,
conn: raw,
PacketConn: raw,
header: &wechat{
sn: uint32(dice.RollUint16()),
},
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
}
func (c *wechatConn) Size() int32 {
return c.header.Size()
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *wechatConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
if n < c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err header mismatch")
return 0, addr, nil
}
copy(p, p[c.Size():n])
if len(p) < n-c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-c.header.Size())
return 0, addr, nil
}
return n - int(c.Size()), addr, err
copy(p, buf[c.header.Size():n])
return n - c.header.Size(), addr, nil
}
func (c *wechatConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
if c.header.Size()+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", c.header.Size()+len(p), " ", finalmask.UDPSize)
return 0, nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:c.header.Size()+len(p)]
}
return c.conn.WriteTo(p, addr)
}
copy(buf[c.header.Size():], p)
c.header.Serialize(buf)
func (c *wechatConn) Close() error {
return c.conn.Close()
}
_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)
if err != nil {
return 0, err
}
func (c *wechatConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *wechatConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *wechatConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *wechatConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,17 +1,16 @@
package wireguard
import (
"io"
"context"
"net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type wireguare struct{}
func (*wireguare) Size() int32 {
func (*wireguare) Size() int {
return 4
}
@@ -23,133 +22,69 @@ func (h *wireguare) Serialize(b []byte) {
}
type wireguareConn struct {
first bool
leaveSize int32
conn net.PacketConn
net.PacketConn
header *wireguare
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &wireguareConn{
first: first,
leaveSize: leaveSize,
conn: raw,
header: &wireguare{},
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
PacketConn: raw,
header: &wireguare{},
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
}
func (c *wireguareConn) Size() int32 {
return c.header.Size()
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *wireguareConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
if n < c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err header mismatch")
return 0, addr, nil
}
copy(p, p[c.Size():n])
if len(p) < n-c.header.Size() {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-c.header.Size())
return 0, addr, nil
}
return n - int(c.Size()), addr, err
copy(p, buf[c.header.Size():n])
return n - c.header.Size(), addr, nil
}
func (c *wireguareConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
if c.header.Size()+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", c.header.Size()+len(p), " ", finalmask.UDPSize)
return 0, nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:c.header.Size()+len(p)]
}
return c.conn.WriteTo(p, addr)
}
copy(buf[c.header.Size():], p)
c.header.Serialize(buf)
func (c *wireguareConn) Close() error {
return c.conn.Close()
}
_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)
if err != nil {
return 0, err
}
func (c *wireguareConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *wireguareConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *wireguareConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *wireguareConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,99 +1,77 @@
package aes128gcm
import (
"context"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"io"
"net"
sync "sync"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type aes128gcmConn struct {
first bool
leaveSize int32
conn net.PacketConn
net.PacketConn
aead cipher.AEAD
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
hashedPsk := sha256.Sum256([]byte(c.Password))
conn := &aes128gcmConn{
first: first,
leaveSize: leaveSize,
conn: raw,
aead: crypto.NewAesGcm(hashedPsk[:16]),
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
PacketConn: raw,
aead: crypto.NewAesGcm(hashedPsk[:16]),
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
}
func (c *aes128gcmConn) Size() int32 {
return int32(c.aead.NonceSize()) + int32(c.aead.Overhead())
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *aes128gcmConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
if len(p) < finalmask.UDPSize {
buf := make([]byte, finalmask.UDPSize)
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("aead").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("aead").Base(io.ErrShortBuffer)
if n < c.aead.NonceSize()+c.aead.Overhead() {
errors.LogDebug(context.Background(), addr, " mask read err aead short lenth ", n)
return 0, addr, nil
}
nonceSize := c.aead.NonceSize()
nonce := c.readBuf[:nonceSize]
ciphertext := c.readBuf[nonceSize:n]
_, err = c.aead.Open(p[:0], nonce, ciphertext, nil)
nonce := buf[:nonceSize]
ciphertext := buf[nonceSize:n]
plaintext, err := c.aead.Open(p[:0], nonce, ciphertext, nil)
if err != nil {
c.readMutex.Unlock()
return 0, addr, errors.New("aead open").Base(err)
errors.LogDebug(context.Background(), addr, " mask read err aead open ", err)
return 0, addr, nil
}
c.readMutex.Unlock()
return n - int(c.Size()), addr, nil
if len(plaintext) > len(p) {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", len(plaintext))
return 0, addr, nil
}
return n - c.aead.NonceSize() - c.aead.Overhead(), addr, nil
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(p)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("aead").Base(io.ErrShortBuffer)
if n < c.aead.NonceSize()+c.aead.Overhead() {
errors.LogDebug(context.Background(), addr, " mask read err aead short lenth ", n)
return 0, addr, nil
}
nonceSize := c.aead.NonceSize()
@@ -101,74 +79,40 @@ func (c *aes128gcmConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
ciphertext := p[nonceSize:n]
_, err = c.aead.Open(ciphertext[:0], nonce, ciphertext, nil)
if err != nil {
return 0, addr, errors.New("aead open").Base(err)
errors.LogDebug(context.Background(), addr, " mask read err aead open ", err)
return 0, addr, nil
}
copy(p, p[nonceSize:n-c.aead.Overhead()])
return n - int(c.Size()), addr, nil
return n - c.aead.NonceSize() - c.aead.Overhead(), addr, nil
}
func (c *aes128gcmConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
if c.aead.NonceSize()+c.aead.Overhead()+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", c.aead.NonceSize()+c.aead.Overhead()+len(p), " ", finalmask.UDPSize)
return 0, nil
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+int32(c.aead.NonceSize()):], p)
// n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
nonceSize := c.aead.NonceSize()
nonce := c.writeBuf[c.leaveSize : c.leaveSize+int32(nonceSize)]
common.Must2(rand.Read(nonce))
// copy(c.writeBuf[c.leaveSize+int32(nonceSize):], c.writeBuf[c.leaveSize+c.Size():n])
plaintext := c.writeBuf[c.leaveSize+int32(nonceSize) : n-c.aead.Overhead()]
_ = c.aead.Seal(plaintext[:0], nonce, plaintext, nil)
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:c.aead.NonceSize()+c.aead.Overhead()+len(p)]
copy(buf[c.aead.NonceSize():], p)
p = buf[c.aead.NonceSize() : c.aead.NonceSize()+len(p)]
}
nonceSize := c.aead.NonceSize()
nonce := p[c.leaveSize : c.leaveSize+int32(nonceSize)]
nonce := buf[:nonceSize]
common.Must2(rand.Read(nonce))
copy(p[c.leaveSize+int32(nonceSize):], p[c.leaveSize+c.Size():])
plaintext := p[c.leaveSize+int32(nonceSize) : len(p)-c.aead.Overhead()]
_ = c.aead.Seal(plaintext[:0], nonce, plaintext, nil)
ciphertext := buf[nonceSize : c.aead.NonceSize()+c.aead.Overhead()+len(p)]
_ = c.aead.Seal(ciphertext[:0], nonce, p, nil)
return c.conn.WriteTo(p, addr)
}
_, err = c.PacketConn.WriteTo(buf[:c.aead.NonceSize()+c.aead.Overhead()+len(p)], addr)
if err != nil {
return 0, err
}
func (c *aes128gcmConn) Close() error {
return c.conn.Close()
}
func (c *aes128gcmConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *aes128gcmConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *aes128gcmConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *aes128gcmConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,16 +1,15 @@
package original
import (
"context"
"crypto/cipher"
"encoding/binary"
"hash/fnv"
"io"
"net"
sync "sync"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type simple struct{}
@@ -75,151 +74,77 @@ func (a *simple) Open(dst, nonce, cipherText, extra []byte) ([]byte, error) {
}
type simpleConn struct {
first bool
leaveSize int32
conn net.PacketConn
net.PacketConn
aead cipher.AEAD
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &simpleConn{
first: first,
leaveSize: leaveSize,
conn: raw,
aead: &simple{},
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
PacketConn: raw,
aead: &simple{},
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
}
func (c *simpleConn) Size() int32 {
return int32(c.aead.NonceSize()) + int32(c.aead.Overhead())
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *simpleConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("aead").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("aead").Base(io.ErrShortBuffer)
}
ciphertext := c.readBuf[:n]
opened, err := c.aead.Open(nil, nil, ciphertext, nil)
if err != nil {
c.readMutex.Unlock()
return 0, addr, errors.New("aead open").Base(err)
}
copy(p, opened)
c.readMutex.Unlock()
return n - int(c.Size()), addr, nil
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("aead").Base(io.ErrShortBuffer)
if n < c.aead.Overhead() {
errors.LogDebug(context.Background(), addr, " mask read err aead short lenth ", n)
return 0, addr, nil
}
ciphertext := p[:n]
ciphertext := buf[:n]
opened, err := c.aead.Open(nil, nil, ciphertext, nil)
if err != nil {
c.readMutex.Unlock()
return 0, addr, errors.New("aead open").Base(err)
errors.LogDebug(context.Background(), addr, " mask read err aead open ", err)
return 0, addr, nil
}
if len(opened) > len(p) {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", len(opened))
return 0, addr, nil
}
copy(p, opened)
return n - int(c.Size()), addr, nil
return n - c.aead.Overhead(), addr, nil
}
func (c *simpleConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
plaintext := c.writeBuf[c.leaveSize+c.Size() : n]
sealed := c.aead.Seal(nil, nil, plaintext, nil)
copy(c.writeBuf[c.leaveSize:], sealed)
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
if c.aead.Overhead()+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", c.aead.Overhead()+len(p), " ", finalmask.UDPSize)
return 0, nil
}
plaintext := p[c.leaveSize+c.Size():]
sealed := c.aead.Seal(nil, nil, plaintext, nil)
copy(p[c.leaveSize:], sealed)
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:c.aead.Overhead()+len(p)]
copy(buf[c.aead.Overhead():], p)
p = buf[c.aead.Overhead() : c.aead.Overhead()+len(p)]
}
return c.conn.WriteTo(p, addr)
}
_ = c.aead.Seal(buf[:0], nil, p, nil)
func (c *simpleConn) Close() error {
return c.conn.Close()
}
_, err = c.PacketConn.WriteTo(buf[:c.aead.Overhead()+len(p)], addr)
if err != nil {
return 0, err
}
func (c *simpleConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *simpleConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *simpleConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *simpleConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -8,6 +8,22 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
)
func TestSimpleSealInPlace(t *testing.T) {
aead := original.NewSimple()
text := []byte("0123456789012")
buf := make([]byte, 8192)
copy(buf[aead.Overhead():], text)
plaintext := buf[aead.Overhead() : aead.Overhead()+len(text)]
sealed := aead.Seal(nil, nil, plaintext, nil)
_ = aead.Seal(buf[:0], nil, plaintext, nil)
assert.Equal(t, sealed, buf[:aead.Overhead()+len(text)])
}
func TestOriginalBounce(t *testing.T) {
aead := original.NewSimple()
buf := make([]byte, aead.NonceSize()+aead.Overhead())
@@ -0,0 +1,14 @@
package noise
import "net"
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -0,0 +1,225 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: transport/internet/finalmask/noise/config.proto
package noise
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Item struct {
state protoimpl.MessageState `protogen:"open.v1"`
RandMin int64 `protobuf:"varint,1,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
RandMax int64 `protobuf:"varint,2,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
Packet []byte `protobuf:"bytes,3,opt,name=packet,proto3" json:"packet,omitempty"`
DelayMin int64 `protobuf:"varint,4,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
DelayMax int64 `protobuf:"varint,5,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Item) Reset() {
*x = Item{}
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Item) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Item) ProtoMessage() {}
func (x *Item) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Item.ProtoReflect.Descriptor instead.
func (*Item) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{0}
}
func (x *Item) GetRandMin() int64 {
if x != nil {
return x.RandMin
}
return 0
}
func (x *Item) GetRandMax() int64 {
if x != nil {
return x.RandMax
}
return 0
}
func (x *Item) GetPacket() []byte {
if x != nil {
return x.Packet
}
return nil
}
func (x *Item) GetDelayMin() int64 {
if x != nil {
return x.DelayMin
}
return 0
}
func (x *Item) GetDelayMax() int64 {
if x != nil {
return x.DelayMax
}
return 0
}
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
ResetMin int64 `protobuf:"varint,1,opt,name=reset_min,json=resetMin,proto3" json:"reset_min,omitempty"`
ResetMax int64 `protobuf:"varint,2,opt,name=reset_max,json=resetMax,proto3" json:"reset_max,omitempty"`
Items []*Item `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{1}
}
func (x *Config) GetResetMin() int64 {
if x != nil {
return x.ResetMin
}
return 0
}
func (x *Config) GetResetMax() int64 {
if x != nil {
return x.ResetMax
}
return 0
}
func (x *Config) GetItems() []*Item {
if x != nil {
return x.Items
}
return nil
}
var File_transport_internet_finalmask_noise_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" +
"\n" +
"/transport/internet/finalmask/noise/config.proto\x12'xray.transport.internet.finalmask.noise\"\x8e\x01\n" +
"\x04Item\x12\x19\n" +
"\brand_min\x18\x01 \x01(\x03R\arandMin\x12\x19\n" +
"\brand_max\x18\x02 \x01(\x03R\arandMax\x12\x16\n" +
"\x06packet\x18\x03 \x01(\fR\x06packet\x12\x1b\n" +
"\tdelay_min\x18\x04 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x05 \x01(\x03R\bdelayMax\"\x87\x01\n" +
"\x06Config\x12\x1b\n" +
"\treset_min\x18\x01 \x01(\x03R\bresetMin\x12\x1b\n" +
"\treset_max\x18\x02 \x01(\x03R\bresetMax\x12C\n" +
"\x05items\x18\x03 \x03(\v2-.xray.transport.internet.finalmask.noise.ItemR\x05itemsB\x97\x01\n" +
"+com.xray.transport.internet.finalmask.noiseP\x01Z<github.com/xtls/xray-core/transport/internet/finalmask/noise\xaa\x02'Xray.Transport.Internet.Finalmask.Noiseb\x06proto3"
var (
file_transport_internet_finalmask_noise_config_proto_rawDescOnce sync.Once
file_transport_internet_finalmask_noise_config_proto_rawDescData []byte
)
func file_transport_internet_finalmask_noise_config_proto_rawDescGZIP() []byte {
file_transport_internet_finalmask_noise_config_proto_rawDescOnce.Do(func() {
file_transport_internet_finalmask_noise_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_noise_config_proto_rawDesc), len(file_transport_internet_finalmask_noise_config_proto_rawDesc)))
})
return file_transport_internet_finalmask_noise_config_proto_rawDescData
}
var file_transport_internet_finalmask_noise_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_transport_internet_finalmask_noise_config_proto_goTypes = []any{
(*Item)(nil), // 0: xray.transport.internet.finalmask.noise.Item
(*Config)(nil), // 1: xray.transport.internet.finalmask.noise.Config
}
var file_transport_internet_finalmask_noise_config_proto_depIdxs = []int32{
0, // 0: xray.transport.internet.finalmask.noise.Config.items:type_name -> xray.transport.internet.finalmask.noise.Item
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_noise_config_proto_init() }
func file_transport_internet_finalmask_noise_config_proto_init() {
if File_transport_internet_finalmask_noise_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_noise_config_proto_rawDesc), len(file_transport_internet_finalmask_noise_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_finalmask_noise_config_proto_goTypes,
DependencyIndexes: file_transport_internet_finalmask_noise_config_proto_depIdxs,
MessageInfos: file_transport_internet_finalmask_noise_config_proto_msgTypes,
}.Build()
File_transport_internet_finalmask_noise_config_proto = out.File
file_transport_internet_finalmask_noise_config_proto_goTypes = nil
file_transport_internet_finalmask_noise_config_proto_depIdxs = nil
}
@@ -0,0 +1,21 @@
syntax = "proto3";
package xray.transport.internet.finalmask.noise;
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Noise";
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/noise";
option java_package = "com.xray.transport.internet.finalmask.noise";
option java_multiple_files = true;
message Item {
int64 rand_min = 1;
int64 rand_max = 2;
bytes packet = 3;
int64 delay_min = 4;
int64 delay_max = 5;
}
message Config {
int64 reset_min = 1;
int64 reset_max = 2;
repeated Item items = 3;
}
@@ -0,0 +1,98 @@
package noise
import (
"crypto/rand"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto"
)
type noiseConn struct {
net.PacketConn
config *Config
m map[string]time.Time
stop chan struct{}
once sync.Once
mutex sync.RWMutex
}
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &noiseConn{
PacketConn: raw,
config: c,
m: make(map[string]time.Time),
stop: make(chan struct{}),
}
if conn.config.ResetMax > 0 {
go conn.reset()
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *noiseConn) reset() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
c.mutex.RLock()
now := time.Now()
timeOut := make([]string, 0, len(c.m))
for key, last := range c.m {
if now.After(last) {
timeOut = append(timeOut, key)
}
}
c.mutex.RUnlock()
for _, key := range timeOut {
c.mutex.Lock()
delete(c.m, key)
c.mutex.Unlock()
}
case <-c.stop:
return
}
}
}
func (c *noiseConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.mutex.RLock()
_, ready := c.m[addr.String()]
c.mutex.RUnlock()
if !ready {
c.mutex.Lock()
_, ready = c.m[addr.String()]
if !ready {
for _, item := range c.config.Items {
if item.RandMax > 0 {
item.Packet = make([]byte, crypto.RandBetween(item.RandMin, item.RandMax))
common.Must2(rand.Read(item.Packet))
}
c.PacketConn.WriteTo(item.Packet, addr)
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
}
c.m[addr.String()] = time.Now().Add(time.Duration(crypto.RandBetween(c.config.ResetMin, c.config.ResetMax)) * time.Second)
}
c.mutex.Unlock()
}
return c.PacketConn.WriteTo(p, addr)
}
func (c *noiseConn) Close() error {
c.once.Do(func() {
close(c.stop)
})
return c.PacketConn.Close()
}
@@ -7,10 +7,10 @@ import (
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,147 +1,83 @@
package salamander
import (
"io"
"context"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
type obfsPacketConn struct {
first bool
leaveSize int32
conn net.PacketConn
type salamanderConn struct {
net.PacketConn
obfs *SalamanderObfuscator
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
}
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
ob, err := NewSalamanderObfuscator([]byte(c.Password))
if err != nil {
return nil, errors.New("salamander err").Base(err)
}
conn := &obfsPacketConn{
first: first,
leaveSize: leaveSize,
conn: raw,
obfs: ob,
}
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
conn := &salamanderConn{
PacketConn: raw,
obfs: ob,
}
return conn, nil
}
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize)
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *obfsPacketConn) Size() int32 {
return smSaltLen
}
func (c *obfsPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first {
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("salamander").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("salamander").Base(io.ErrShortBuffer)
}
c.obfs.Deobfuscate(c.readBuf[:n], p)
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
func (c *salamanderConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buf := p
if len(p) < finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil || n == 0 {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("salamander").Base(io.ErrShortBuffer)
if n < smSaltLen {
errors.LogDebug(context.Background(), addr, " mask read err short lenth ", n)
return 0, addr, nil
}
c.obfs.Deobfuscate(p[:n], p)
return n - int(c.Size()), addr, err
}
func (c *obfsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first {
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock()
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.obfs.Obfuscate(c.writeBuf[c.leaveSize+c.Size():n], c.writeBuf[c.leaveSize:n])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
if len(p) < n-smSaltLen {
errors.LogDebug(context.Background(), addr, " mask read err short buffer ", len(p), " ", n-smSaltLen)
return 0, addr, nil
}
c.obfs.Obfuscate(p[c.leaveSize+c.Size():], p[c.leaveSize:])
c.obfs.Deobfuscate(buf[:n], p)
return c.conn.WriteTo(p, addr)
return n - smSaltLen, addr, nil
}
func (c *obfsPacketConn) Close() error {
return c.conn.Close()
}
func (c *salamanderConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if smSaltLen+len(p) > finalmask.UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write ", smSaltLen+len(p), " ", finalmask.UDPSize)
return 0, nil
}
func (c *obfsPacketConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
var buf []byte
if cap(p) != finalmask.UDPSize {
buf = make([]byte, finalmask.UDPSize)
} else {
buf = p[:smSaltLen+len(p)]
copy(buf[smSaltLen:], p)
p = buf[smSaltLen:]
}
func (c *obfsPacketConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
c.obfs.Obfuscate(p, buf)
func (c *obfsPacketConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
_, err = c.PacketConn.WriteTo(buf[:smSaltLen+len(p)], addr)
if err != nil {
return 0, err
}
func (c *obfsPacketConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
return len(p), nil
}
@@ -0,0 +1,163 @@
package sudoku
import (
"fmt"
"math/rand"
)
var perm4 = [24][4]byte{
{0, 1, 2, 3},
{0, 1, 3, 2},
{0, 2, 1, 3},
{0, 2, 3, 1},
{0, 3, 1, 2},
{0, 3, 2, 1},
{1, 0, 2, 3},
{1, 0, 3, 2},
{1, 2, 0, 3},
{1, 2, 3, 0},
{1, 3, 0, 2},
{1, 3, 2, 0},
{2, 0, 1, 3},
{2, 0, 3, 1},
{2, 1, 0, 3},
{2, 1, 3, 0},
{2, 3, 0, 1},
{2, 3, 1, 0},
{3, 0, 1, 2},
{3, 0, 2, 1},
{3, 1, 0, 2},
{3, 1, 2, 0},
{3, 2, 0, 1},
{3, 2, 1, 0},
}
type codec struct {
tables []*table
rng *rand.Rand
paddingChance int
tableIndex int
}
func newCodec(tables []*table, pMin, pMax int) *codec {
if len(tables) == 0 {
tables = nil
}
rng := newSeededRand()
return &codec{
tables: tables,
rng: rng,
paddingChance: pickPaddingChance(rng, pMin, pMax),
}
}
func pickPaddingChance(rng *rand.Rand, pMin, pMax int) int {
if pMin < 0 {
pMin = 0
}
if pMax < pMin {
pMax = pMin
}
if pMin > 100 {
pMin = 100
}
if pMax > 100 {
pMax = 100
}
if pMax == pMin {
return pMin
}
return pMin + rng.Intn(pMax-pMin+1)
}
func (c *codec) shouldPad() bool {
if c.paddingChance <= 0 {
return false
}
if c.paddingChance >= 100 {
return true
}
return c.rng.Intn(100) < c.paddingChance
}
func (c *codec) currentTable() *table {
if len(c.tables) == 0 {
return nil
}
return c.tables[c.tableIndex%len(c.tables)]
}
func (c *codec) randomPadding(t *table) byte {
pool := t.layout.paddingPool
return pool[c.rng.Intn(len(pool))]
}
func (c *codec) encode(in []byte) ([]byte, error) {
if len(in) == 0 {
return nil, nil
}
out := make([]byte, 0, len(in)*6+8)
for _, b := range in {
t := c.currentTable()
if t == nil {
return nil, fmt.Errorf("sudoku table set missing")
}
if c.shouldPad() {
out = append(out, c.randomPadding(t))
}
enc := t.encode[b]
if len(enc) == 0 {
return nil, fmt.Errorf("sudoku encode table missing for byte %d", b)
}
hints := enc[c.rng.Intn(len(enc))]
perm := perm4[c.rng.Intn(len(perm4))]
for _, idx := range perm {
if c.shouldPad() {
out = append(out, c.randomPadding(t))
}
out = append(out, hints[idx])
}
c.tableIndex++
}
if c.shouldPad() {
if t := c.currentTable(); t != nil {
out = append(out, c.randomPadding(t))
}
}
return out, nil
}
func decodeBytes(tables []*table, tableIndex *int, in []byte, hintBuf []byte, out []byte) ([]byte, []byte, error) {
if len(tables) == 0 {
return hintBuf, out, fmt.Errorf("sudoku table set missing")
}
for _, b := range in {
t := tables[*tableIndex%len(tables)]
if !t.layout.isHint(b) {
continue
}
hintBuf = append(hintBuf, b)
if len(hintBuf) < 4 {
continue
}
keyBytes := sort4([4]byte{hintBuf[0], hintBuf[1], hintBuf[2], hintBuf[3]})
key := packKey(keyBytes)
decoded, ok := t.decode[key]
if !ok {
return hintBuf[:0], out, fmt.Errorf("invalid sudoku hint tuple")
}
out = append(out, decoded)
hintBuf = hintBuf[:0]
*tableIndex++
}
return hintBuf, out, nil
}
@@ -0,0 +1,57 @@
package sudoku
import (
"net"
"github.com/xtls/xray-core/common/errors"
)
func (c *Config) TCP() {
}
func (c *Config) UDP() {
}
// Sudoku in finalmask mode is a pure appearance transform with no standalone handshake.
// TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes.
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(raw, c, true)
}
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(raw, c, false)
}
func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (net.Conn, error) {
pureReader, pureWriter, err := newPureReaderWriter(raw, config)
if err != nil {
return nil, err
}
packedReader, packedWriter, err := newPackedReaderWriter(raw, config)
if err != nil {
return nil, err
}
reader, writer := pureReader, pureWriter
if readPacked {
reader = packedReader
} else {
writer = packedWriter
}
return newWrappedConn(raw, reader, writer), nil
}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != levelCount {
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
}
return NewUDPConn(raw, c)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != levelCount {
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
}
return NewUDPConn(raw, c)
}
@@ -0,0 +1,170 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: transport/internet/finalmask/sudoku/config.proto
package sudoku
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"`
Ascii string `protobuf:"bytes,2,opt,name=ascii,proto3" json:"ascii,omitempty"`
CustomTable string `protobuf:"bytes,3,opt,name=custom_table,json=customTable,proto3" json:"custom_table,omitempty"`
PaddingMin uint32 `protobuf:"varint,4,opt,name=padding_min,json=paddingMin,proto3" json:"padding_min,omitempty"`
PaddingMax uint32 `protobuf:"varint,5,opt,name=padding_max,json=paddingMax,proto3" json:"padding_max,omitempty"`
CustomTables []string `protobuf:"bytes,7,rep,name=custom_tables,json=customTables,proto3" json:"custom_tables,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_finalmask_sudoku_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_sudoku_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_sudoku_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
func (x *Config) GetAscii() string {
if x != nil {
return x.Ascii
}
return ""
}
func (x *Config) GetCustomTable() string {
if x != nil {
return x.CustomTable
}
return ""
}
func (x *Config) GetPaddingMin() uint32 {
if x != nil {
return x.PaddingMin
}
return 0
}
func (x *Config) GetPaddingMax() uint32 {
if x != nil {
return x.PaddingMax
}
return 0
}
func (x *Config) GetCustomTables() []string {
if x != nil {
return x.CustomTables
}
return nil
}
var File_transport_internet_finalmask_sudoku_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_sudoku_config_proto_rawDesc = "" +
"\n" +
"0transport/internet/finalmask/sudoku/config.proto\x12(xray.transport.internet.finalmask.sudoku\"\xc4\x01\n" +
"\x06Config\x12\x1a\n" +
"\bpassword\x18\x01 \x01(\tR\bpassword\x12\x14\n" +
"\x05ascii\x18\x02 \x01(\tR\x05ascii\x12!\n" +
"\fcustom_table\x18\x03 \x01(\tR\vcustomTable\x12\x1f\n" +
"\vpadding_min\x18\x04 \x01(\rR\n" +
"paddingMin\x12\x1f\n" +
"\vpadding_max\x18\x05 \x01(\rR\n" +
"paddingMax\x12#\n" +
"\rcustom_tables\x18\a \x03(\tR\fcustomTablesB\x9a\x01\n" +
",com.xray.transport.internet.finalmask.sudokuP\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/sudoku\xaa\x02(Xray.Transport.Internet.Finalmask.Sudokub\x06proto3"
var (
file_transport_internet_finalmask_sudoku_config_proto_rawDescOnce sync.Once
file_transport_internet_finalmask_sudoku_config_proto_rawDescData []byte
)
func file_transport_internet_finalmask_sudoku_config_proto_rawDescGZIP() []byte {
file_transport_internet_finalmask_sudoku_config_proto_rawDescOnce.Do(func() {
file_transport_internet_finalmask_sudoku_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_sudoku_config_proto_rawDesc), len(file_transport_internet_finalmask_sudoku_config_proto_rawDesc)))
})
return file_transport_internet_finalmask_sudoku_config_proto_rawDescData
}
var file_transport_internet_finalmask_sudoku_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_transport_internet_finalmask_sudoku_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.transport.internet.finalmask.sudoku.Config
}
var file_transport_internet_finalmask_sudoku_config_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_sudoku_config_proto_init() }
func file_transport_internet_finalmask_sudoku_config_proto_init() {
if File_transport_internet_finalmask_sudoku_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_sudoku_config_proto_rawDesc), len(file_transport_internet_finalmask_sudoku_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_finalmask_sudoku_config_proto_goTypes,
DependencyIndexes: file_transport_internet_finalmask_sudoku_config_proto_depIdxs,
MessageInfos: file_transport_internet_finalmask_sudoku_config_proto_msgTypes,
}.Build()
File_transport_internet_finalmask_sudoku_config_proto = out.File
file_transport_internet_finalmask_sudoku_config_proto_goTypes = nil
file_transport_internet_finalmask_sudoku_config_proto_depIdxs = nil
}
@@ -0,0 +1,16 @@
syntax = "proto3";
package xray.transport.internet.finalmask.sudoku;
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Sudoku";
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/sudoku";
option java_package = "com.xray.transport.internet.finalmask.sudoku";
option java_multiple_files = true;
message Config {
string password = 1;
string ascii = 2;
string custom_table = 3;
uint32 padding_min = 4;
uint32 padding_max = 5;
repeated string custom_tables = 7;
}
@@ -0,0 +1,212 @@
package sudoku
import (
"bufio"
"io"
"net"
"sync"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
const ioBufferSize = 32 * 1024
var _ finalmask.TcpMaskConn = (*wrappedConn)(nil)
type streamDecoder interface {
decodeChunk(in []byte, pending []byte) ([]byte, error)
reset()
}
type streamReader struct {
reader *bufio.Reader
rawBuf []byte
pending []byte
decode streamDecoder
mu sync.Mutex
}
func newStreamReader(raw net.Conn, decode streamDecoder) io.Reader {
return &streamReader{
reader: bufio.NewReaderSize(raw, ioBufferSize),
rawBuf: make([]byte, ioBufferSize),
pending: make([]byte, 0, 4096),
decode: decode,
}
}
func (r *streamReader) Read(p []byte) (int, error) {
r.mu.Lock()
defer r.mu.Unlock()
if n, ok := drainPending(p, &r.pending); ok {
return n, nil
}
for len(r.pending) == 0 {
nr, rErr := r.reader.Read(r.rawBuf)
if nr > 0 {
var dErr error
r.pending, dErr = r.decode.decodeChunk(r.rawBuf[:nr], r.pending)
if dErr != nil {
return 0, dErr
}
}
if rErr != nil {
if rErr == io.EOF {
r.decode.reset()
if len(r.pending) > 0 {
break
}
}
return 0, rErr
}
}
n, _ := drainPending(p, &r.pending)
return n, nil
}
type streamWriter struct {
conn net.Conn
encode func([]byte) ([]byte, error)
mu sync.Mutex
}
func newStreamWriter(raw net.Conn, encode func([]byte) ([]byte, error)) io.Writer {
return &streamWriter{
conn: raw,
encode: encode,
}
}
func (w *streamWriter) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
w.mu.Lock()
defer w.mu.Unlock()
encoded, err := w.encode(p)
if err != nil {
return 0, err
}
if err := writeAll(w.conn, encoded); err != nil {
return 0, err
}
return len(p), nil
}
type wrappedConn struct {
net.Conn
reader io.Reader
writer io.Writer
}
type closeWriteConn interface {
CloseWrite() error
}
func newWrappedConn(raw net.Conn, reader io.Reader, writer io.Writer) net.Conn {
return &wrappedConn{
Conn: raw,
reader: reader,
writer: writer,
}
}
func (c *wrappedConn) Read(p []byte) (int, error) {
return c.reader.Read(p)
}
func (c *wrappedConn) Write(p []byte) (int, error) {
return c.writer.Write(p)
}
func (c *wrappedConn) TcpMaskConn() {}
func (c *wrappedConn) RawConn() net.Conn {
return c.Conn
}
func (c *wrappedConn) Splice() bool {
// Sudoku transforms the entire stream; bypassing it would disable masking.
return false
}
func (c *wrappedConn) CloseWrite() error {
if raw, ok := c.Conn.(closeWriteConn); ok {
return raw.CloseWrite()
}
return net.ErrClosed
}
func NewTCPConn(raw net.Conn, config *Config) (net.Conn, error) {
reader, writer, err := newPureReaderWriter(raw, config)
if err != nil {
return nil, err
}
return newWrappedConn(raw, reader, writer), nil
}
func newPureReaderWriter(raw net.Conn, config *Config) (io.Reader, io.Writer, error) {
tables, err := getTables(config)
if err != nil {
return nil, nil, err
}
pMin, pMax := normalizedPadding(config)
c := newCodec(tables, pMin, pMax)
return newStreamReader(raw, newHintStreamDecoder(tables)), newStreamWriter(raw, c.encode), nil
}
type hintStreamDecoder struct {
tables []*table
tableIndex int
hintBuf []byte
}
func newHintStreamDecoder(tables []*table) *hintStreamDecoder {
return &hintStreamDecoder{
tables: tables,
hintBuf: make([]byte, 0, 4),
}
}
func (d *hintStreamDecoder) decodeChunk(in []byte, pending []byte) ([]byte, error) {
var err error
d.hintBuf, pending, err = decodeBytes(d.tables, &d.tableIndex, in, d.hintBuf, pending)
return pending, err
}
func (d *hintStreamDecoder) reset() {}
func drainPending(p []byte, pending *[]byte) (int, bool) {
if len(*pending) == 0 {
return 0, false
}
n := copy(p, *pending)
if n >= len(*pending) {
*pending = (*pending)[:0]
return n, true
}
remaining := len(*pending) - n
copy(*pending, (*pending)[n:])
*pending = (*pending)[:remaining]
return n, true
}
func writeAll(conn net.Conn, b []byte) error {
for len(b) > 0 {
n, err := conn.Write(b)
if err != nil {
return err
}
b = b[n:]
}
return nil
}
@@ -0,0 +1,182 @@
package sudoku
import (
"fmt"
"io"
"net"
)
type packedEncoder struct {
layouts []*byteLayout
codec *codec
groupIndex int
}
func newPackedEncoder(tables []*table, pMin, pMax int) *packedEncoder {
layouts := make([]*byteLayout, 0, len(tables))
for _, t := range tables {
layouts = append(layouts, t.layout)
}
if len(layouts) == 0 {
layouts = append(layouts, entropyLayout())
}
return &packedEncoder{
layouts: layouts,
codec: newCodec(nil, pMin, pMax),
}
}
func (e *packedEncoder) encode(p []byte) ([]byte, error) {
out := make([]byte, 0, len(p)*2+8)
var bitBuf uint64
var bitCount uint8
for _, b := range p {
bitBuf = (bitBuf << 8) | uint64(b)
bitCount += 8
for bitCount >= 6 {
bitCount -= 6
layout := e.layouts[e.groupIndex%len(e.layouts)]
group := byte(bitBuf >> bitCount)
out = e.maybePad(out, layout)
out = append(out, layout.encodeGroup(group&0x3f))
e.groupIndex++
if bitCount > 0 {
bitBuf &= (uint64(1) << bitCount) - 1
} else {
bitBuf = 0
}
}
}
if bitCount > 0 {
layout := e.layouts[e.groupIndex%len(e.layouts)]
group := byte(bitBuf << (6 - bitCount))
out = e.maybePad(out, layout)
out = append(out, layout.encodeGroup(group&0x3f))
e.groupIndex++
nextLayout := e.layouts[e.groupIndex%len(e.layouts)]
out = append(out, nextLayout.padMarker)
}
out = e.maybePad(out, e.layouts[e.groupIndex%len(e.layouts)])
return out, nil
}
func (e *packedEncoder) maybePad(out []byte, layout *byteLayout) []byte {
if !e.codec.shouldPad() {
return out
}
if len(layout.paddingPool) == 1 {
return append(out, layout.paddingPool[0])
}
for {
b := layout.paddingPool[e.codec.rng.Intn(len(layout.paddingPool))]
if b != layout.padMarker {
return append(out, b)
}
}
}
type packedStreamDecoder struct {
layouts []*byteLayout
groupIndex int
bitBuf uint64
bitCount int
}
func (d *packedStreamDecoder) decodeChunk(in []byte, pending []byte) ([]byte, error) {
var err error
d.bitBuf, d.bitCount, d.groupIndex, pending, err = decodePackedBytes(
d.layouts,
in,
d.bitBuf,
d.bitCount,
d.groupIndex,
pending,
)
return pending, err
}
func (d *packedStreamDecoder) reset() {
d.bitBuf = 0
d.bitCount = 0
}
func NewPackedTCPConn(raw net.Conn, config *Config) (net.Conn, error) {
reader, writer, err := newPackedReaderWriter(raw, config)
if err != nil {
return nil, err
}
return newWrappedConn(raw, reader, writer), nil
}
func newPackedReaderWriter(raw net.Conn, config *Config) (io.Reader, io.Writer, error) {
tables, err := getTables(config)
if err != nil {
return nil, nil, err
}
pMin, pMax := normalizedPadding(config)
encoder := newPackedEncoder(tables, pMin, pMax)
decoder := &packedStreamDecoder{
layouts: tablesToLayouts(tables),
}
return newStreamReader(raw, decoder), newStreamWriter(raw, encoder.encode), nil
}
func tablesToLayouts(tables []*table) []*byteLayout {
layouts := make([]*byteLayout, 0, len(tables))
for _, t := range tables {
layouts = append(layouts, t.layout)
}
if len(layouts) == 0 {
layouts = append(layouts, entropyLayout())
}
return layouts
}
func decodePackedBytes(
layouts []*byteLayout,
in []byte,
bitBuf uint64,
bitCount int,
groupIndex int,
out []byte,
) (uint64, int, int, []byte, error) {
if len(layouts) == 0 {
return bitBuf, bitCount, groupIndex, out, fmt.Errorf("sudoku layout set missing")
}
for _, b := range in {
layout := layouts[groupIndex%len(layouts)]
if !layout.isHint(b) {
if b == layout.padMarker {
bitBuf = 0
bitCount = 0
}
continue
}
group, ok := layout.decodeGroup(b)
if !ok {
return bitBuf, bitCount, groupIndex, out, fmt.Errorf("invalid packed sudoku byte: %d", b)
}
groupIndex++
bitBuf = (bitBuf << 6) | uint64(group)
bitCount += 6
for bitCount >= 8 {
bitCount -= 8
out = append(out, byte(bitBuf>>bitCount))
if bitCount > 0 {
bitBuf &= (uint64(1) << bitCount) - 1
} else {
bitBuf = 0
}
}
}
return bitBuf, bitCount, groupIndex, out, nil
}
@@ -0,0 +1,106 @@
package sudoku
import (
"io"
"net"
"sync"
"time"
)
type udpConn struct {
conn net.PacketConn
tables []*table
pMin int
pMax int
readBuf []byte
readMu sync.Mutex
writeMu sync.Mutex
}
func NewUDPConn(raw net.PacketConn, config *Config) (net.PacketConn, error) {
tables, err := getTables(config)
if err != nil {
return nil, err
}
pMin, pMax := normalizedPadding(config)
return &udpConn{
conn: raw,
tables: tables,
pMin: pMin,
pMax: pMax,
readBuf: make([]byte, 65535),
}, nil
}
func (c *udpConn) Size() int32 {
return 0
}
func (c *udpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
c.readMu.Lock()
defer c.readMu.Unlock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
return n, addr, err
}
decoded := make([]byte, 0, n/4+1)
hints := make([]byte, 0, 4)
tableIndex := 0
hints, decoded, err = decodeBytes(c.tables, &tableIndex, c.readBuf[:n], hints, decoded)
if err != nil {
return 0, addr, err
}
if len(hints) != 0 {
return 0, addr, io.ErrUnexpectedEOF
}
if len(p) < len(decoded) {
return 0, addr, io.ErrShortBuffer
}
copy(p, decoded)
return len(decoded), addr, nil
}
func (c *udpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.writeMu.Lock()
defer c.writeMu.Unlock()
// UDP decoding restarts at table 0 for every datagram, so encoding must do the same.
encoded, err := newCodec(c.tables, c.pMin, c.pMax).encode(p)
if err != nil {
return 0, err
}
nn, err := c.conn.WriteTo(encoded, addr)
if err != nil {
return 0, err
}
if nn != len(encoded) {
return 0, io.ErrShortWrite
}
return len(p), nil
}
func (c *udpConn) Close() error {
return c.conn.Close()
}
func (c *udpConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *udpConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *udpConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *udpConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}

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