Compare commits

...
36 Commits
Author SHA1 Message Date
Meo597 dd3ced5894 refine key comments 2026-07-15 07:56:09 +08:00
Meo597 e77250a40e remove privacyGuard & deprecation freedom's outbound.targetStrategy & StreamSettingsFromContext() 2026-07-15 05:45:04 +08:00
Meo597 a659344f67 Merge commit 'd7fa2076c3e5401173bf9b58f7b07f9aa5174443' into finalrules 2026-07-07 20:12:58 +08:00
d7fa2076c3 Xray-core: Forbid unencrypted outbounds on public Internet for VLESS and Trojan; Remove "none/zero/plain" for VMess and Shadowsocks (#6303)
https://github.com/XTLS/Xray-core/pull/5640#issuecomment-4611416512
https://github.com/XTLS/Xray-core/pull/6303#issuecomment-4668055274

---------

Co-authored-by: Meow <197331664+Meo597@users.noreply.github.com>
2026-07-07 02:01:53 +00:00
fb548f54d2 infra/conf/transport_internet.go: Split into multiple files; Rename network to method (#6426)
https://github.com/XTLS/Xray-core/issues/6376#issuecomment-4817591444
https://github.com/XTLS/Xray-core/pull/6426#issuecomment-4899048255

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-07-07 01:26:15 +00:00
yiguodevandGitHub 0495b17650 TUN inbound: Refine gateway and autoSystemRoutingTable on macOS (#6434)
https://github.com/XTLS/Xray-core/pull/6434#issuecomment-4885521586
2026-07-07 00:28:50 +00:00
Meo597 36b75894c0 Merge commit '65f6f0a43b814b2980740e119aa96f1adcb82e9a' into finalrules 2026-07-06 07:37:39 +08:00
Meo597 dc70f480ae reject freedom + sockopt.addressPortStrategy 2026-07-06 07:18:16 +08:00
Meo597 a2f6a4b154 remove proxySettings 2026-07-06 07:14:42 +08:00
yiguodevandGitHub 65f6f0a43b TUN inbound: Refine gateway and autoSystemRoutingTable on Linux (#6398)
https://github.com/XTLS/Xray-core/pull/6398#issuecomment-4880261591
2026-07-04 02:25:15 +00:00
Jasper344612andGitHub 3263ae9255 TUN inbound: Fix autoOutboundsInterface on Linux (#6413)
Fixes https://github.com/XTLS/Xray-core/issues/6412
2026-07-04 01:38:25 +00:00
LjhAUMEMandGitHub 3dc8bf3d8b Hysteria inbound: Fix dynamic UUID not accepted (#6395)
Fixes https://github.com/XTLS/Xray-core/pull/6375#issuecomment-4825923934
2026-07-04 01:28:21 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
695e68ef9e Bump github.com/pires/go-proxyproto from 0.12.0 to 0.14.0 (#6420)
Bumps [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) from 0.12.0 to 0.14.0.
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.12.0...v0.14.0)

---
updated-dependencies:
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 17:53:15 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dfdbcf86cc Bump github.com/klauspost/cpuid/v2 from 2.3.0 to 2.4.0 (#6417)
Bumps [github.com/klauspost/cpuid/v2](https://github.com/klauspost/cpuid) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/klauspost/cpuid/releases)
- [Commits](https://github.com/klauspost/cpuid/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/klauspost/cpuid/v2
  dependency-version: 2.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 17:45:51 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2b828b7bc2 Bump google.golang.org/grpc from 1.81.1 to 1.82.0 (#6415)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.81.1 to 1.82.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.81.1...v1.82.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 17:45:47 +00:00
RPRXandGitHub 45cf2898ab Xray-core v26.6.27
Sponsor & Donation & NFTs: https://github.com/XTLS/Xray-core/issues/3668
Project X Channel: https://t.me/projectXtls

Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2026-06-27 13:18:03 +00:00
RPRXandGitHub 18b85adb4e XHTTP client: Change default maxConnections to 6 for anti-RKN
"xmux": {
    "maxConcurrency": 0,
    "maxConnections": "6",
    "cMaxReuseTimes": 0,
    "hMaxRequestTimes": "600-900",
    "hMaxReusableSecs": "1800-3000",
    "hKeepAlivePeriod": 0
}

Replaces https://github.com/XTLS/Xray-core/commit/9cc7907234a8297a87a0ff77fc40db373b74a0f2 and https://github.com/XTLS/Xray-core/commit/4ce65fc74c4c50919b10b3faff9725f75bba5d73

Closes https://github.com/XTLS/Xray-core/issues/6376#issuecomment-4817033592
2026-06-27 12:41:39 +00:00
452b719504 Hysteria inbound: Support routing's vlessRoute as well (#6375)
https://github.com/XTLS/Xray-core/pull/6375#issuecomment-4795522284

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-06-27 12:04:58 +00:00
345c76f9a8 WireGuard inbound: Support dynamic peer management (#6360)
https://github.com/XTLS/Xray-core/pull/6360#issuecomment-4780311547

Closes https://github.com/XTLS/Xray-core/issues/6314

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-06-27 11:41:22 +00:00
f496437b84 XHTTP server: Refactor upload_queue.go (#6372)
https://github.com/XTLS/Xray-core/pull/6372#issuecomment-4801395378

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-27 10:40:58 +00:00
b12bc504c8 README.md: Add Magic_V2Ray to Magisk in Installation (#6355)
https://github.com/XTLS/Xray-core/pull/6355#issuecomment-4793603321

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-24 21:06:24 +00:00
yiguodevandGitHub dda2b10c9d TUN inbound: Add traffic counters; Metrics: Rely on instance (#6349)
https://github.com/XTLS/Xray-core/pull/6349#issuecomment-4775121300
2026-06-24 12:00:22 +00:00
Jasper344612andGitHub 241aa38ac0 TUN inbound: Support autoSystemRoutingTable and autoOutboundsInterface on macOS and Linux as well (#6366)
https://github.com/XTLS/Xray-core/pull/6366#issuecomment-4788510365
2026-06-24 11:06:00 +00:00
风扇滑翔翼andRPRX 7e7e820763 Geodata: Apply uTLS Chrome fingerprint when downloading (#6371)
Closes https://github.com/XTLS/Xray-core/issues/6369
2026-06-24 10:52:28 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f9eb1597ad Bump actions/cache from 5 to 6 (#6368)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 10:48:21 +00:00
风扇滑翔翼andGitHub ac04c445bd DNS: Fix unexpected TTL clamp (#6363)
Fixes https://github.com/XTLS/Xray-core/issues/6359
2026-06-23 11:48:17 +00:00
e7e9254630 TUN inbound: Avoid panic on nil RemoteAddr due to quickly closed connection (#6365)
Fixes https://github.com/XTLS/Xray-core/issues/6364#issuecomment-4778262075

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-23 11:37:21 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fab4bcc1ed Bump github.com/cloudflare/circl from 1.6.3 to 1.6.4 (#6362)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.3 to 1.6.4.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.3...v1.6.4)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 10:34:07 +00:00
Meo597 622207bb96 refine 2026-05-20 05:30:45 +08:00
Meo597 6bc4d4a3e8 targetStrategy 2026-05-20 03:56:56 +08:00
Meo597 e33aad40d0 privacy guard 2026-05-19 19:06:34 +08:00
Meo597 105d4547f4 migrate legacy freedom domainStrategy for config compatibility 2026-05-19 17:48:21 +08:00
Meo597 1b28c19333 remove freedom.domainStrategy 2026-05-19 17:13:03 +08:00
Meo597 d39965f20d more compatibility 2026-05-19 03:38:36 +08:00
Meo597 81bcfcae77 fix udpdomain v4 issue 2026-05-19 03:36:54 +08:00
Meo597 e645ed69aa strategy 2026-05-19 03:36:49 +08:00
74 changed files with 4298 additions and 3517 deletions
+4 -4
View File
@@ -14,13 +14,13 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
- name: Restore Wintun Cache - name: Restore Wintun Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-wintun- key: xray-wintun-
@@ -119,13 +119,13 @@ jobs:
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main # go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
- name: Restore Wintun Cache - name: Restore Wintun Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-wintun- key: xray-wintun-
+4 -4
View File
@@ -14,13 +14,13 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
- name: Restore Wintun Cache - name: Restore Wintun Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-wintun- key: xray-wintun-
@@ -225,14 +225,14 @@ jobs:
fi fi
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
- name: Restore Wintun Cache - name: Restore Wintun Cache
if: matrix.goos == 'windows' if: matrix.goos == 'windows'
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-wintun- key: xray-wintun-
@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
@@ -59,7 +59,7 @@ jobs:
done done
- name: Save Geodat Cache - name: Save Geodat Cache
uses: actions/cache/save@v5 uses: actions/cache/save@v6
if: ${{ steps.update.outputs.unhit }} if: ${{ steps.update.outputs.unhit }}
with: with:
path: resources path: resources
@@ -73,7 +73,7 @@ jobs:
ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51 ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
steps: steps:
- name: Restore Wintun Cache - name: Restore Wintun Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-wintun- key: xray-wintun-
@@ -129,7 +129,7 @@ jobs:
fi fi
- name: Save Wintun Cache - name: Save Wintun Cache
uses: actions/cache/save@v5 uses: actions/cache/save@v6
if: ${{ steps.update.outputs.unhit }} if: ${{ steps.update.outputs.unhit }}
with: with:
path: resources path: resources
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
@@ -90,7 +90,7 @@ jobs:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
+1
View File
@@ -73,6 +73,7 @@
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless) - [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU) - [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
- Magisk - Magisk
- [Magic_V2Ray](https://github.com/vincentng295/Magic_V2Ray)
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk) - [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
- Homebrew - Homebrew
- `brew install xray` - `brew install xray`
+7 -2
View File
@@ -198,9 +198,14 @@ func parseResponse(payload []byte) (*IPRecord, error) {
ipRecord := &IPRecord{ ipRecord := &IPRecord{
ReqID: h.ID, ReqID: h.ID,
RCode: h.RCode, RCode: h.RCode,
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
RawHeader: &h, RawHeader: &h,
} }
defer func() {
// set to default TTL if no valid TTL is found
if ipRecord.Expire.IsZero() {
ipRecord.Expire = now.Add(time.Second * dns_feature.DefaultTTL)
}
}()
L: L:
for { for {
@@ -217,7 +222,7 @@ L:
ttl = 1 ttl = 1
} }
expire := now.Add(time.Duration(ttl) * time.Second) expire := now.Add(time.Duration(ttl) * time.Second)
if ipRecord.Expire.After(expire) { if ipRecord.Expire.IsZero() || ipRecord.Expire.After(expire) {
ipRecord.Expire = expire ipRecord.Expire = expire
} }
+85 -43
View File
@@ -2,6 +2,7 @@ package geodata
import ( import (
"context" "context"
"crypto/tls"
go_errors "errors" go_errors "errors"
"io" "io"
"net/http" "net/http"
@@ -9,6 +10,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
utls "github.com/refraction-networking/utls"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform/filesystem" "github.com/xtls/xray-core/common/platform/filesystem"
@@ -16,6 +18,7 @@ import (
"github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport/internet/tagged" "github.com/xtls/xray-core/transport/internet/tagged"
"golang.org/x/net/http2"
) )
const idleTimeout = 30 * time.Second const idleTimeout = 30 * time.Second
@@ -26,8 +29,9 @@ type stage struct {
} }
type downloader struct { type downloader struct {
ctx context.Context ctx context.Context
client *http.Client httpClient *http.Client
httpsClient *http.Client
} }
type idleConn struct { type idleConn struct {
@@ -53,52 +57,84 @@ func (c *idleConn) Write(b []byte) (int, error) {
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader { func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
return &downloader{ return &downloader{
ctx: ctx, ctx: ctx,
client: newClient(ctx, dispatcher, outbound), httpClient: newClient(ctx, dispatcher, outbound, false),
httpsClient: newClient(ctx, dispatcher, outbound, true),
} }
} }
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client { func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
return &http.Client{ dial := func(ctx context.Context, network, address string) (net.Conn, error) {
Transport: &http.Transport{ var conn net.Conn
Proxy: nil, err := task.Run(ctx, func() error {
DisableKeepAlives: true, if tagged.Dialer == nil {
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { return errors.New("tagged dialer is not initialized")
var conn net.Conn
err := task.Run(ctx, func() error {
if tagged.Dialer == nil {
return errors.New("tagged dialer is not initialized")
}
dest, err := net.ParseDestination(network + ":" + address)
if err != nil {
return errors.New("cannot understand address").Base(err)
}
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
if err != nil {
return errors.New("cannot dial remote address ", dest).Base(err)
}
conn = c
return nil
})
if err != nil {
return nil, errors.New("cannot finish connection").Base(err)
}
return &idleConn{
Conn: conn,
}, nil
},
TLSHandshakeTimeout: idleTimeout,
ResponseHeaderTimeout: idleTimeout,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
} }
if len(via) >= 10 { dest, err := net.ParseDestination(network + ":" + address)
return errors.New("stopped after 10 redirects") if err != nil {
return errors.New("cannot understand address").Base(err)
} }
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
if err != nil {
return errors.New("cannot dial remote address ", dest).Base(err)
}
conn = c
return nil return nil
}, })
if err != nil {
return nil, errors.New("cannot finish connection").Base(err)
}
return &idleConn{
Conn: conn,
}, nil
}
if isHTTPS {
return &http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) {
conn, err := dial(ctx, network, address)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(address)
tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloChrome_Auto)
handshakeCtx, cancel := context.WithTimeout(ctx, idleTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(handshakeCtx); err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
}
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
} else {
return &http.Client{
Transport: &http.Transport{
Proxy: nil,
DisableKeepAlives: true,
DialContext: dial,
ResponseHeaderTimeout: idleTimeout,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
}
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
} }
} }
@@ -160,7 +196,13 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
} }
utils.TryDefaultHeadersWith(req.Header, "nav") utils.TryDefaultHeadersWith(req.Header, "nav")
resp, err := d.client.Do(req) var client *http.Client
if req.URL.Scheme == "https" {
client = d.httpsClient
} else {
client = d.httpClient
}
resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
} }
+151 -59
View File
@@ -2,15 +2,18 @@ package metrics
import ( import (
"context" "context"
"encoding/json"
stderrors "errors"
"expvar" "expvar"
stdnet "net"
"net/http" "net/http"
_ "net/http/pprof" "net/http/pprof"
"strings" "strings"
"github.com/xtls/xray-core/app/observatory" "github.com/xtls/xray-core/app/observatory"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" xnet "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/extension" "github.com/xtls/xray-core/features/extension"
@@ -21,15 +24,17 @@ import (
type MetricsHandler struct { type MetricsHandler struct {
ohm outbound.Manager ohm outbound.Manager
statsManager feature_stats.Manager statsManager feature_stats.Manager
observatory extension.Observatory ctx context.Context
tag string tag string
listen string listen string
tcpListener net.Listener tcpListener xnet.Listener
listener *OutboundListener
} }
// NewMetricsHandler creates a new MetricsHandler based on the given config. // NewMetricsHandler creates a new MetricsHandler based on the given config.
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) { func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
c := &MetricsHandler{ c := &MetricsHandler{
ctx: ctx,
tag: config.Tag, tag: config.Tag,
listen: config.Listen, listen: config.Listen,
} }
@@ -37,46 +42,6 @@ func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, er
c.statsManager = sm c.statsManager = sm
c.ohm = om c.ohm = om
})) }))
expvar.Publish("stats", expvar.Func(func() interface{} {
resp := map[string]map[string]map[string]int64{
"inbound": {},
"outbound": {},
"user": {},
}
c.statsManager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
nameSplit := strings.Split(name, ">>>")
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
if item, found := resp[typeName][tagOrUser]; found {
item[direction] = counter.Value()
} else {
resp[typeName][tagOrUser] = map[string]int64{
direction: counter.Value(),
}
}
return true
})
return resp
}))
expvar.Publish("observatory", expvar.Func(func() interface{} {
if c.observatory == nil {
common.Must(core.RequireFeatures(ctx, func(observatory extension.Observatory) error {
c.observatory = observatory
return nil
}))
if c.observatory == nil {
return nil
}
}
resp := map[string]*observatory.OutboundStatus{}
if o, err := c.observatory.GetObservation(context.Background()); err != nil {
return err
} else {
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
resp[x.OutboundTag] = x
}
}
return resp
}))
return c, nil return c, nil
} }
@@ -85,45 +50,172 @@ func (p *MetricsHandler) Type() interface{} {
} }
func (p *MetricsHandler) Start() error { func (p *MetricsHandler) Start() error {
handler := p.httpHandler()
// direct listen a port if listen is set // direct listen a port if listen is set
if p.listen != "" { if p.listen != "" {
TCPlistener, err := net.Listen("tcp", p.listen) TCPlistener, err := xnet.Listen("tcp", p.listen)
if err != nil { if err != nil {
return err return err
} }
p.tcpListener = TCPlistener p.tcpListener = TCPlistener
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen) errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
go func() { go p.serve(TCPlistener, handler)
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil { }
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
} if p.tag == "" {
}() if p.tcpListener == nil {
return errors.New("metrics must have a tag or listen address")
}
return nil
} }
listener := &OutboundListener{ listener := &OutboundListener{
buffer: make(chan net.Conn, 4), buffer: make(chan xnet.Conn, 4),
done: done.New(), done: done.New(),
} }
p.listener = listener
go func() { go p.serve(listener, handler)
if err := http.Serve(listener, http.DefaultServeMux); err != nil {
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
}
}()
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil { if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
errors.LogInfo(context.Background(), "failed to remove existing handler") errors.LogInfo(context.Background(), "failed to remove existing handler")
} }
return p.ohm.AddHandler(context.Background(), &Outbound{ if err := p.ohm.AddHandler(context.Background(), &Outbound{
tag: p.tag, tag: p.tag,
listener: listener, listener: listener,
}) }); err != nil {
if closeErr := p.Close(); closeErr != nil {
errors.LogErrorInner(context.Background(), closeErr, "failed to close metrics server after start failure")
}
return err
}
return nil
} }
func (p *MetricsHandler) Close() error { func (p *MetricsHandler) Close() error {
return nil var errs []error
if p.tcpListener != nil {
errs = append(errs, p.tcpListener.Close())
p.tcpListener = nil
}
if p.listener != nil {
errs = append(errs, p.listener.Close())
p.listener = nil
}
if p.ohm != nil && p.tag != "" {
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
errors.LogInfo(context.Background(), "failed to remove metrics handler")
}
}
return errors.Combine(errs...)
}
func (p *MetricsHandler) serve(listener xnet.Listener, handler http.Handler) {
if err := http.Serve(listener, handler); err != nil && !isClosedListenerError(err) {
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
}
}
func isClosedListenerError(err error) bool {
if err == nil {
return true
}
if stderrors.Is(err, stdnet.ErrClosed) || stderrors.Is(err, http.ErrServerClosed) {
return true
}
errText := err.Error()
return strings.Contains(errText, "listen closed") ||
strings.Contains(errText, "use of closed network connection")
}
func (p *MetricsHandler) httpHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/debug/vars", p.handleDebugVars)
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
return mux
}
func (p *MetricsHandler) handleDebugVars(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
vars := map[string]json.RawMessage{}
expvar.Do(func(kv expvar.KeyValue) {
value := json.RawMessage(kv.Value.String())
if !json.Valid(value) {
value = json.RawMessage("null")
}
vars[kv.Key] = value
})
vars["stats"] = marshalJSON(p.stats())
vars["observatory"] = marshalJSON(p.observatoryStatus())
payload, err := json.Marshal(vars)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(payload)
}
func marshalJSON(value interface{}) json.RawMessage {
data, err := json.Marshal(value)
if err != nil {
return json.RawMessage("null")
}
return data
}
func (p *MetricsHandler) stats() map[string]map[string]map[string]int64 {
resp := map[string]map[string]map[string]int64{
"inbound": {},
"outbound": {},
"user": {},
}
p.statsManager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
nameSplit := strings.Split(name, ">>>")
if len(nameSplit) < 4 {
return true
}
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
items, found := resp[typeName]
if !found {
items = map[string]map[string]int64{}
resp[typeName] = items
}
if item, found := items[tagOrUser]; found {
item[direction] = counter.Value()
} else {
items[tagOrUser] = map[string]int64{
direction: counter.Value(),
}
}
return true
})
return resp
}
func (p *MetricsHandler) observatoryStatus() interface{} {
feature := core.MustFromContext(p.ctx).GetFeature(extension.ObservatoryType())
if feature == nil {
return nil
}
observatoryFeature := feature.(extension.Observatory)
resp := map[string]*observatory.OutboundStatus{}
if o, err := observatoryFeature.GetObservation(context.Background()); err != nil {
return err
} else {
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
resp[x.OutboundTag] = x
}
}
return resp
} }
func init() { func init() {
+161
View File
@@ -0,0 +1,161 @@
package metrics
import (
"context"
"encoding/json"
stdnet "net"
"net/http"
"net/http/httptest"
"testing"
"github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/app/proxyman"
_ "github.com/xtls/xray-core/app/proxyman/inbound"
_ "github.com/xtls/xray-core/app/proxyman/outbound"
appstats "github.com/xtls/xray-core/app/stats"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/core"
feature_outbound "github.com/xtls/xray-core/features/outbound"
)
func TestMetricsCanRestartInSameProcess(t *testing.T) {
for i := 0; i < 2; i++ {
server := startMetricsTestServer(t)
readMetricsVars(t, server)
readMetricsPprof(t, server)
if err := server.Close(); err != nil {
t.Fatalf("failed to close metrics server: %v", err)
}
}
}
func TestMetricsCanRunMultipleInstancesInSameProcess(t *testing.T) {
server1 := startMetricsTestServer(t)
t.Cleanup(func() {
_ = server1.Close()
})
server2 := startMetricsTestServer(t)
t.Cleanup(func() {
_ = server2.Close()
})
readMetricsVars(t, server1)
readMetricsVars(t, server2)
}
func TestMetricsListenOnlyWithoutTagDoesNotRegisterOutbound(t *testing.T) {
listen := pickMetricsListenAddress(t)
server := startMetricsTestServerWithMetricsConfig(t, &Config{
Listen: listen,
})
t.Cleanup(func() {
_ = server.Close()
})
response, err := http.Get("http://" + listen + "/debug/vars")
if err != nil {
t.Fatalf("failed to read listen-only metrics: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
t.Fatalf("unexpected listen-only metrics status: %d", response.StatusCode)
}
outboundManager := server.GetFeature(feature_outbound.ManagerType()).(feature_outbound.Manager)
if handlers := outboundManager.ListHandlers(context.Background()); len(handlers) != 0 {
t.Fatalf("listen-only metrics registered outbound handlers: got %d, want 0", len(handlers))
}
}
func startMetricsTestServer(t *testing.T) *core.Instance {
return startMetricsTestServerWithMetricsConfig(t, &Config{
Tag: "metrics_out",
})
}
func startMetricsTestServerWithMetricsConfig(t *testing.T, metricsConfig *Config) *core.Instance {
t.Helper()
server, err := core.New(metricsTestConfig(metricsConfig))
if err != nil {
t.Fatalf("failed to create metrics server: %v", err)
}
if err := server.Start(); err != nil {
_ = server.Close()
t.Fatalf("failed to start metrics server: %v", err)
}
return server
}
func metricsTestConfig(metricsConfig *Config) *core.Config {
return &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
serial.ToTypedMessage(&appstats.Config{}),
serial.ToTypedMessage(metricsConfig),
},
}
}
func pickMetricsListenAddress(t *testing.T) string {
t.Helper()
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to pick metrics listen address: %v", err)
}
defer listener.Close()
return listener.Addr().String()
}
func readMetricsVars(t *testing.T, server *core.Instance) {
t.Helper()
recorder := httptest.NewRecorder()
metricsHandler(t, server).httpHandler().ServeHTTP(
recorder,
httptest.NewRequest(http.MethodGet, "/debug/vars", nil),
)
if recorder.Code != http.StatusOK {
t.Fatalf("unexpected metrics vars status: %d", recorder.Code)
}
var payload map[string]interface{}
if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil {
t.Fatalf("failed to decode metrics vars: %v", err)
}
if _, found := payload["stats"]; !found {
t.Fatal("metrics vars missing stats")
}
if _, found := payload["observatory"]; !found {
t.Fatal("metrics vars missing observatory")
}
}
func readMetricsPprof(t *testing.T, server *core.Instance) {
t.Helper()
recorder := httptest.NewRecorder()
metricsHandler(t, server).httpHandler().ServeHTTP(
recorder,
httptest.NewRequest(http.MethodGet, "/debug/pprof/goroutine?debug=1", nil),
)
if recorder.Code != http.StatusOK {
t.Fatalf("unexpected metrics pprof status: %d", recorder.Code)
}
}
func metricsHandler(t *testing.T, server *core.Instance) *MetricsHandler {
t.Helper()
feature := server.GetFeature((*MetricsHandler)(nil))
handler, ok := feature.(*MetricsHandler)
if !ok || handler == nil {
t.Fatal("metrics handler not registered")
}
return handler
}
+11 -22
View File
@@ -330,7 +330,6 @@ type SenderConfig struct {
// Send traffic through the given IP. Only IP is allowed. // Send traffic through the given IP. Only IP is allowed.
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"` Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"` StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"`
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"` MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"` ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"` TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
@@ -382,13 +381,6 @@ func (x *SenderConfig) GetStreamSettings() *internet.StreamConfig {
return nil return nil
} }
func (x *SenderConfig) GetProxySettings() *internet.ProxyConfig {
if x != nil {
return x.ProxySettings
}
return nil
}
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig { func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
if x != nil { if x != nil {
return x.MultiplexSettings return x.MultiplexSettings
@@ -506,14 +498,13 @@ const file_app_proxyman_config_proto_rawDesc = "" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" + "\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" + "\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" + "\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
"\x0eOutboundConfig\"\x9d\x03\n" + "\x0eOutboundConfig\"\xd6\x02\n" +
"\fSenderConfig\x12-\n" + "\fSenderConfig\x12-\n" +
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" + "\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12K\n" + "\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12T\n" +
"\x0eproxy_settings\x18\x03 \x01(\v2$.xray.transport.internet.ProxyConfigR\rproxySettings\x12T\n" +
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" + "\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" + "\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategy\"\xa4\x01\n" + "\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategyJ\x04\b\x03\x10\x04\"\xa4\x01\n" +
"\x12MultiplexingConfig\x12\x18\n" + "\x12MultiplexingConfig\x12\x18\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" + "\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
@@ -548,8 +539,7 @@ var file_app_proxyman_config_proto_goTypes = []any{
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain (*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig (*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage (*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
(*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig (internet.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy
(internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy
} }
var file_app_proxyman_config_proto_depIdxs = []int32{ var file_app_proxyman_config_proto_depIdxs = []int32{
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule 7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
@@ -562,14 +552,13 @@ var file_app_proxyman_config_proto_depIdxs = []int32{
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage 12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain 10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig 11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
13, // 10: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig 6, // 10: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
6, // 11: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig 13, // 11: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
14, // 12: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy 12, // [12:12] is the sub-list for method output_type
13, // [13:13] is the sub-list for method output_type 12, // [12:12] is the sub-list for method input_type
13, // [13:13] is the sub-list for method input_type 12, // [12:12] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension type_name 12, // [12:12] is the sub-list for extension extendee
13, // [13:13] is the sub-list for extension extendee 0, // [0:12] is the sub-list for field type_name
0, // [0:13] is the sub-list for field type_name
} }
func init() { file_app_proxyman_config_proto_init() } func init() { file_app_proxyman_config_proto_init() }
+1 -1
View File
@@ -57,7 +57,7 @@ message SenderConfig {
// Send traffic through the given IP. Only IP is allowed. // Send traffic through the given IP. Only IP is allowed.
xray.common.net.IPOrDomain via = 1; xray.common.net.IPOrDomain via = 1;
xray.transport.internet.StreamConfig stream_settings = 2; xray.transport.internet.StreamConfig stream_settings = 2;
xray.transport.internet.ProxyConfig proxy_settings = 3; reserved 3;
MultiplexingConfig multiplex_settings = 4; MultiplexingConfig multiplex_settings = 4;
string via_cidr = 5; string via_cidr = 5;
xray.transport.internet.DomainStrategy target_strategy = 6; xray.transport.internet.DomainStrategy target_strategy = 6;
+11 -49
View File
@@ -15,7 +15,6 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/mux"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
@@ -26,8 +25,6 @@ import (
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
"github.com/xtls/xray-core/transport/pipe"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -63,7 +60,6 @@ type Handler struct {
streamSettings *internet.MemoryStreamConfig streamSettings *internet.MemoryStreamConfig
proxyConfig proto.Message proxyConfig proto.Message
proxy proxy.Outbound proxy proxy.Outbound
outboundManager outbound.Manager
mux *mux.ClientManager mux *mux.ClientManager
xudp *mux.ClientManager xudp *mux.ClientManager
udp443 string udp443 string
@@ -77,7 +73,6 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag) uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
h := &Handler{ h := &Handler{
tag: config.Tag, tag: config.Tag,
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
uplinkCounter: uplinkCounter, uplinkCounter: uplinkCounter,
downlinkCounter: downlinkCounter, downlinkCounter: downlinkCounter,
} }
@@ -108,7 +103,10 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
ctx = session.ContextWithFullHandler(ctx, h) ctx = session.ContextWithFullHandler(ctx, h)
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings) newCtx := ctx
if h.streamSettings != nil {
newCtx = session.ContextWithStreamSettings(newCtx, h.streamSettings)
}
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig) rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
if err != nil { if err != nil {
@@ -270,43 +268,10 @@ func (h *Handler) DestIpAddress() net.IP {
// Dial implements internet.Dialer. // Dial implements internet.Dialer.
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) { func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
if h.senderSettings != nil { if h.senderSettings != nil && h.senderSettings.Via != nil {
outbounds := session.OutboundsFromContext(ctx)
if h.senderSettings.ProxySettings.HasTag() { ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob)
tag := h.senderSettings.ProxySettings.Tag
handler := h.outboundManager.GetHandler(tag)
if handler != nil {
errors.LogDebug(ctx, "proxying to ", tag, " for dest ", dest)
outbounds := session.OutboundsFromContext(ctx)
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
Target: dest,
Tag: tag,
})) // add another outbound in session ctx
opts := pipe.OptionsFromContext(ctx)
uplinkReader, uplinkWriter := pipe.New(opts...)
downlinkReader, downlinkWriter := pipe.New(opts...)
go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
conn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))
if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
conn = tls.Client(conn, tlsConfig)
}
return h.getStatCouterConnection(conn), nil
}
errors.LogError(ctx, "failed to get outbound handler with tag: ", tag)
return nil, errors.New("failed to get outbound handler with tag: " + tag)
}
if h.senderSettings.Via != nil {
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob)
}
} }
conn, err := internet.Dial(ctx, dest, h.streamSettings) conn, err := internet.Dial(ctx, dest, h.streamSettings)
@@ -322,14 +287,14 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
} }
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) { func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil && !h.senderSettings.ProxySettings.HasTag() && (h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) { if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil &&
(h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
var domain string var domain string
addr := h.senderSettings.Via.AsAddress() addr := h.senderSettings.Via.AsAddress()
domain = h.senderSettings.Via.GetDomain() domain = h.senderSettings.Via.GetDomain()
switch { switch {
case h.senderSettings.ViaCidr != "": case h.senderSettings.ViaCidr != "":
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr) ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
case domain == "origin": case domain == "origin":
if inbound := session.InboundFromContext(ctx); inbound != nil { if inbound := session.InboundFromContext(ctx); inbound != nil {
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() { if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
@@ -344,12 +309,9 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String()) errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
} }
} }
// case addr.Family().IsDomain(): default: // case addr.Family().IsDomain():
default:
ob.Gateway = addr ob.Gateway = addr
} }
} }
} }
+49
View File
@@ -0,0 +1,49 @@
package geodata
import (
"sync"
"github.com/xtls/xray-core/common"
)
var privateIPMatcher = sync.OnceValue(func() IPMatcher {
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/3",
"::/127",
"fc00::/7",
"fe80::/10",
"ff00::/8",
}))))
})
func GetPrivateIPMatcher() IPMatcher { return privateIPMatcher() }
var privateDomainMatcher = sync.OnceValue(func() DomainMatcher {
return common.Must2(DomainReg.BuildDomainMatcher(common.Must2(ParseDomainRules([]string{
"lan",
"localdomain",
"example",
"invalid",
"localhost",
"test",
"local",
"home.arpa",
"internal",
"regexp:^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$", // Dotless domains
}, Domain_Domain))))
})
func GetPrivateDomainMatcher() DomainMatcher { return privateDomainMatcher() }
+2 -10
View File
@@ -28,8 +28,6 @@ const (
SecurityType_AUTO SecurityType = 2 SecurityType_AUTO SecurityType = 2
SecurityType_AES128_GCM SecurityType = 3 SecurityType_AES128_GCM SecurityType = 3
SecurityType_CHACHA20_POLY1305 SecurityType = 4 SecurityType_CHACHA20_POLY1305 SecurityType = 4
SecurityType_NONE SecurityType = 5 // [DEPRECATED 2023-06]
SecurityType_ZERO SecurityType = 6
) )
// Enum value maps for SecurityType. // Enum value maps for SecurityType.
@@ -39,16 +37,12 @@ var (
2: "AUTO", 2: "AUTO",
3: "AES128_GCM", 3: "AES128_GCM",
4: "CHACHA20_POLY1305", 4: "CHACHA20_POLY1305",
5: "NONE",
6: "ZERO",
} }
SecurityType_value = map[string]int32{ SecurityType_value = map[string]int32{
"UNKNOWN": 0, "UNKNOWN": 0,
"AUTO": 2, "AUTO": 2,
"AES128_GCM": 3, "AES128_GCM": 3,
"CHACHA20_POLY1305": 4, "CHACHA20_POLY1305": 4,
"NONE": 5,
"ZERO": 6,
} }
) )
@@ -129,15 +123,13 @@ const file_common_protocol_headers_proto_rawDesc = "" +
"\n" + "\n" +
"\x1dcommon/protocol/headers.proto\x12\x14xray.common.protocol\"H\n" + "\x1dcommon/protocol/headers.proto\x12\x14xray.common.protocol\"H\n" +
"\x0eSecurityConfig\x126\n" + "\x0eSecurityConfig\x126\n" +
"\x04type\x18\x01 \x01(\x0e2\".xray.common.protocol.SecurityTypeR\x04type*`\n" + "\x04type\x18\x01 \x01(\x0e2\".xray.common.protocol.SecurityTypeR\x04type*L\n" +
"\fSecurityType\x12\v\n" + "\fSecurityType\x12\v\n" +
"\aUNKNOWN\x10\x00\x12\b\n" + "\aUNKNOWN\x10\x00\x12\b\n" +
"\x04AUTO\x10\x02\x12\x0e\n" + "\x04AUTO\x10\x02\x12\x0e\n" +
"\n" + "\n" +
"AES128_GCM\x10\x03\x12\x15\n" + "AES128_GCM\x10\x03\x12\x15\n" +
"\x11CHACHA20_POLY1305\x10\x04\x12\b\n" + "\x11CHACHA20_POLY1305\x10\x04B^\n" +
"\x04NONE\x10\x05\x12\b\n" +
"\x04ZERO\x10\x06B^\n" +
"\x18com.xray.common.protocolP\x01Z)github.com/xtls/xray-core/common/protocol\xaa\x02\x14Xray.Common.Protocolb\x06proto3" "\x18com.xray.common.protocolP\x01Z)github.com/xtls/xray-core/common/protocol\xaa\x02\x14Xray.Common.Protocolb\x06proto3"
var ( var (
-2
View File
@@ -11,8 +11,6 @@ enum SecurityType {
AUTO = 2; AUTO = 2;
AES128_GCM = 3; AES128_GCM = 3;
CHACHA20_POLY1305 = 4; CHACHA20_POLY1305 = 4;
NONE = 5; // [DEPRECATED 2023-06]
ZERO = 6;
} }
message SecurityConfig { message SecurityConfig {
+1 -1
View File
@@ -70,7 +70,7 @@ type Outbound struct {
Tag string Tag string
// Name of the outbound proxy that handles the connection. // Name of the outbound proxy that handles the connection.
Name string Name string
// Unused. Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings // Unused. Conn is actually internet.Connection. May be nil.
Conn net.Conn Conn net.Conn
// CanSpliceCopy is a property for this connection // CanSpliceCopy is a property for this connection
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot // 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
+1 -1
View File
@@ -20,7 +20,7 @@ import (
var ( var (
Version_x byte = 26 Version_x byte = 26
Version_y byte = 6 Version_y byte = 6
Version_z byte = 22 Version_z byte = 27
) )
var ( var (
+6 -5
View File
@@ -4,16 +4,17 @@ go 1.26
require ( require (
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
github.com/cloudflare/circl v1.6.3 github.com/cloudflare/circl v1.6.4
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1 github.com/golang/mock v1.7.0-rc.1
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/klauspost/cpuid/v2 v2.3.0 github.com/klauspost/cpuid/v2 v2.4.0
github.com/miekg/dns v1.1.72 github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pion/stun/v3 v3.1.6 github.com/pion/stun/v3 v3.1.6
github.com/pires/go-proxyproto v0.12.0 github.com/pires/go-proxyproto v0.14.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/sagernet/sing v0.5.1 github.com/sagernet/sing v0.5.1
@@ -30,7 +31,7 @@ require (
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.0.1 golang.zx2c4.com/wireguard/windows v1.0.1
google.golang.org/grpc v1.81.1 google.golang.org/grpc v1.82.0
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3 h12.io/socks v1.0.3
@@ -55,7 +56,7 @@ require (
golang.org/x/text v0.38.0 // indirect golang.org/x/text v0.38.0 // indirect
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.45.0 // indirect golang.org/x/tools v0.45.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+10 -10
View File
@@ -4,8 +4,8 @@ github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkd
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA= github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -33,8 +33,8 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -53,8 +53,8 @@ github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8=
github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs= github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk= github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM= github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM= github.com/pires/go-proxyproto v0.14.0 h1:2vIGIfVG8eVRsKF0xukEoeT5RWhDXxBU0uv6smLOKdI=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI= github.com/pires/go-proxyproto v0.14.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
@@ -147,10 +147,10 @@ golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs= golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-41
View File
@@ -1,41 +0,0 @@
package conf
import (
"github.com/xtls/xray-core/transport/internet/grpc"
"google.golang.org/protobuf/proto"
)
type GRPCConfig struct {
Authority string `json:"authority"`
ServiceName string `json:"serviceName"`
MultiMode bool `json:"multiMode"`
IdleTimeout int32 `json:"idle_timeout"`
HealthCheckTimeout int32 `json:"health_check_timeout"`
PermitWithoutStream bool `json:"permit_without_stream"`
InitialWindowsSize int32 `json:"initial_windows_size"`
UserAgent string `json:"user_agent"`
}
func (g *GRPCConfig) Build() (proto.Message, error) {
if g.IdleTimeout <= 0 {
g.IdleTimeout = 0
}
if g.HealthCheckTimeout <= 0 {
g.HealthCheckTimeout = 0
}
if g.InitialWindowsSize < 0 {
// default window size of gRPC-go
g.InitialWindowsSize = 0
}
return &grpc.Config{
Authority: g.Authority,
ServiceName: g.ServiceName,
MultiMode: g.MultiMode,
IdleTimeout: g.IdleTimeout,
HealthCheckTimeout: g.HealthCheckTimeout,
PermitWithoutStream: g.PermitWithoutStream,
InitialWindowsSize: g.InitialWindowsSize,
UserAgent: g.UserAgent,
}, nil
}
+4 -1
View File
@@ -22,7 +22,6 @@ func (c *HysteriaClientConfig) Build() (proto.Message, error) {
} }
config := &hysteria.ClientConfig{} config := &hysteria.ClientConfig{}
config.Version = c.Version
config.Server = &protocol.ServerEndpoint{ config.Server = &protocol.ServerEndpoint{
Address: c.Address.Build(), Address: c.Address.Build(),
Port: uint32(c.Port), Port: uint32(c.Port),
@@ -44,6 +43,10 @@ type HysteriaServerConfig struct {
} }
func (c *HysteriaServerConfig) Build() (proto.Message, error) { func (c *HysteriaServerConfig) Build() (proto.Message, error) {
if c.Version != 2 {
return nil, errors.New("version != 2")
}
config := new(hysteria.ServerConfig) config := new(hysteria.ServerConfig)
if c.Clients != nil { if c.Clients != nil {
-2
View File
@@ -24,8 +24,6 @@ func cipherFromString(c string) shadowsocks.CipherType {
return shadowsocks.CipherType_CHACHA20_POLY1305 return shadowsocks.CipherType_CHACHA20_POLY1305
case "xchacha20-poly1305", "aead_xchacha20_poly1305", "xchacha20-ietf-poly1305": case "xchacha20-poly1305", "aead_xchacha20_poly1305", "xchacha20-ietf-poly1305":
return shadowsocks.CipherType_XCHACHA20_POLY1305 return shadowsocks.CipherType_XCHACHA20_POLY1305
case "none", "plain":
return shadowsocks.CipherType_NONE
default: default:
return shadowsocks.CipherType_UNKNOWN return shadowsocks.CipherType_UNKNOWN
} }
-208
View File
@@ -1,208 +0,0 @@
package conf
import (
"sort"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/transport/internet/headers/http"
"github.com/xtls/xray-core/transport/internet/headers/noop"
"google.golang.org/protobuf/proto"
)
type NoOpConnectionAuthenticator struct{}
func (NoOpConnectionAuthenticator) Build() (proto.Message, error) {
return new(noop.ConnectionConfig), nil
}
type AuthenticatorRequest struct {
Version string `json:"version"`
Method string `json:"method"`
Path StringList `json:"path"`
Headers map[string]*StringList `json:"headers"`
}
func sortMapKeys(m map[string]*StringList) []string {
var keys []string
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
config := &http.RequestConfig{
Uri: []string{"/"},
Header: []*http.Header{
{
Name: "Host",
Value: []string{"www.baidu.com", "www.bing.com"},
},
{
Name: "User-Agent",
Value: []string{utils.ChromeUA},
},
{
Name: "Sec-CH-UA",
Value: []string{utils.ChromeUACH},
},
{
Name: "Sec-CH-UA-Mobile",
Value: []string{"?0"},
},
{
Name: "Sec-CH-UA-Platform",
Value: []string{"Windows"},
},
{
Name: "Sec-Fetch-Mode",
Value: []string{"no-cors", "cors", "same-origin"},
},
{
Name: "Sec-Fetch-Dest",
Value: []string{"empty"},
},
{
Name: "Sec-Fetch-Site",
Value: []string{"none"},
},
{
Name: "Sec-Fetch-User",
Value: []string{"?1"},
},
{
Name: "Accept-Encoding",
Value: []string{"gzip, deflate"},
},
{
Name: "Connection",
Value: []string{"keep-alive"},
},
{
Name: "Pragma",
Value: []string{"no-cache"},
},
},
}
if len(v.Version) > 0 {
config.Version = &http.Version{Value: v.Version}
}
if len(v.Method) > 0 {
config.Method = &http.Method{Value: v.Method}
}
if len(v.Path) > 0 {
config.Uri = append([]string(nil), v.Path...)
}
if len(v.Headers) > 0 {
config.Header = make([]*http.Header, 0, len(v.Headers))
headerNames := sortMapKeys(v.Headers)
for _, key := range headerNames {
value := v.Headers[key]
if value == nil {
return nil, errors.New("empty HTTP header value: " + key).AtError()
}
config.Header = append(config.Header, &http.Header{
Name: key,
Value: append([]string(nil), (*value)...),
})
}
}
return config, nil
}
type AuthenticatorResponse struct {
Version string `json:"version"`
Status string `json:"status"`
Reason string `json:"reason"`
Headers map[string]*StringList `json:"headers"`
}
func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
config := &http.ResponseConfig{
Header: []*http.Header{
{
Name: "Content-Type",
Value: []string{"application/octet-stream", "video/mpeg"},
},
{
Name: "Transfer-Encoding",
Value: []string{"chunked"},
},
{
Name: "Connection",
Value: []string{"keep-alive"},
},
{
Name: "Pragma",
Value: []string{"no-cache"},
},
{
Name: "Cache-Control",
Value: []string{"private", "no-cache"},
},
},
}
if len(v.Version) > 0 {
config.Version = &http.Version{Value: v.Version}
}
if len(v.Status) > 0 || len(v.Reason) > 0 {
config.Status = &http.Status{
Code: "200",
Reason: "OK",
}
if len(v.Status) > 0 {
config.Status.Code = v.Status
}
if len(v.Reason) > 0 {
config.Status.Reason = v.Reason
}
}
if len(v.Headers) > 0 {
config.Header = make([]*http.Header, 0, len(v.Headers))
headerNames := sortMapKeys(v.Headers)
for _, key := range headerNames {
value := v.Headers[key]
if value == nil {
return nil, errors.New("empty HTTP header value: " + key).AtError()
}
config.Header = append(config.Header, &http.Header{
Name: key,
Value: append([]string(nil), (*value)...),
})
}
}
return config, nil
}
type Authenticator struct {
Request AuthenticatorRequest `json:"request"`
Response AuthenticatorResponse `json:"response"`
}
func (v *Authenticator) Build() (proto.Message, error) {
config := new(http.Config)
requestConfig, err := v.Request.Build()
if err != nil {
return nil, err
}
config.Request = requestConfig
responseConfig, err := v.Response.Build()
if err != nil {
return nil, err
}
config.Response = responseConfig
return config, nil
}
+871
View File
@@ -0,0 +1,871 @@
package conf
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"net/netip"
"net/url"
"regexp"
"strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"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/mkcp/aes128gcm"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header"
"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/realm"
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
"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/tls"
"google.golang.org/protobuf/proto"
)
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 (
customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
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) },
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
"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) },
"realm": func() interface{} { return new(Realm) },
}, "type", "settings")
)
type TCPItem struct {
Delay Int32Range `json:"delay"`
Rand int32 `json:"rand"`
RandRange *Int32Range `json:"randRange"`
Capture string `json:"capture"`
Type string `json:"type"`
Reuse string `json:"reuse"`
Transform *CustomTransform `json:"transform"`
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 err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
}
for _, value := range c.Servers {
for _, item := range value {
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
}
for _, value := range c.Errors {
for _, item := range value {
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
}
errInvalidRange := errors.New("invalid randRange")
clients := make([]*custom.TCPSequence, len(c.Clients))
for i, value := range c.Clients {
clients[i] = &custom.TCPSequence{}
for _, item := range value {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errInvalidRange
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if 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,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
}
servers := make([]*custom.TCPSequence, len(c.Servers))
for i, value := range c.Servers {
servers[i] = &custom.TCPSequence{}
for _, item := range value {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errInvalidRange
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if 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,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
}
errors := make([]*custom.TCPSequence, len(c.Errors))
for i, value := range c.Errors {
errors[i] = &custom.TCPSequence{}
for _, item := range value {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errInvalidRange
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if 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,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
}
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"`
Lengths []Int32Range `json:"lengths"`
Delays []Int32Range `json:"delays"`
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")
}
}
if len(c.Lengths) > 0 {
for _, r := range c.Lengths {
config.LengthsMin = append(config.LengthsMin, int64(r.From))
config.LengthsMax = append(config.LengthsMax, int64(r.To))
}
} else {
config.LengthsMin = append(config.LengthsMin, int64(c.Length.From))
config.LengthsMax = append(config.LengthsMax, int64(c.Length.To))
}
if config.LengthsMin[len(config.LengthsMin)-1] == 0 {
return nil, errors.New("last lengths entry min can't be 0")
}
if len(c.Delays) > 0 {
for _, r := range c.Delays {
config.DelaysMin = append(config.DelaysMin, int64(r.From))
config.DelaysMax = append(config.DelaysMax, int64(r.To))
}
} else {
config.DelaysMin = append(config.DelaysMin, int64(c.Delay.From))
config.DelaysMax = append(config.DelaysMax, 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"`
RandRange *Int32Range `json:"randRange"`
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 {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errors.New("invalid randRange")
}
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),
RandRangeMin: item.RandRange.From,
RandRangeMax: item.RandRange.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"`
RandRange *Int32Range `json:"randRange"`
Capture string `json:"capture"`
Type string `json:"type"`
Reuse string `json:"reuse"`
Transform *CustomTransform `json:"transform"`
Packet json.RawMessage `json:"packet"`
}
type CustomTransform struct {
Op string `json:"op"`
Args []CustomTransformArg `json:"args"`
}
type CustomTransformArg struct {
Type string `json:"type"`
Bytes json.RawMessage `json:"bytes"`
U64 *uint64 `json:"u64"`
Reuse string `json:"reuse"`
Metadata string `json:"metadata"`
Transform *CustomTransform `json:"transform"`
}
func validateCustomVarName(name string) error {
if name == "" {
return nil
}
if !customVarNamePattern.MatchString(name) {
return errors.New("invalid variable name")
}
return nil
}
func validateCustomItemSpec(capture string, packet json.RawMessage, rand int32, reuse string, transform *CustomTransform) error {
if err := validateCustomVarName(capture); err != nil {
return err
}
if err := validateCustomVarName(reuse); err != nil {
return err
}
kindCount := 0
if len(packet) > 0 {
kindCount++
}
if rand > 0 {
kindCount++
}
if reuse != "" {
kindCount++
}
if transform != nil {
kindCount++
}
if kindCount > 1 {
return errors.New("exactly one item kind must be set")
}
if kindCount == 0 && capture != "" {
return errors.New("exactly one item kind must be set")
}
return nil
}
func buildCustomTransform(transform *CustomTransform) (*custom.Expr, error) {
if transform == nil {
return nil, nil
}
if transform.Op == "" {
return nil, errors.New("transform op is required")
}
if len(transform.Args) == 0 {
return nil, errors.New("transform args are required")
}
args := make([]*custom.ExprArg, 0, len(transform.Args))
for _, arg := range transform.Args {
parsedArg, err := buildCustomTransformArg(arg)
if err != nil {
return nil, err
}
args = append(args, parsedArg)
}
return &custom.Expr{
Op: transform.Op,
Args: args,
}, nil
}
func buildCustomTransformArg(arg CustomTransformArg) (*custom.ExprArg, error) {
kindCount := 0
if len(arg.Bytes) > 0 {
kindCount++
}
if arg.U64 != nil {
kindCount++
}
if arg.Reuse != "" {
kindCount++
}
if arg.Metadata != "" {
kindCount++
}
if arg.Transform != nil {
kindCount++
}
if kindCount != 1 {
return nil, errors.New("transform arg must set exactly one value")
}
if len(arg.Bytes) > 0 {
value, err := PraseByteSlice(arg.Bytes, arg.Type)
if err != nil {
return nil, err
}
return &custom.ExprArg{
Value: &custom.ExprArg_Bytes{
Bytes: value,
},
}, nil
}
if arg.U64 != nil {
return &custom.ExprArg{
Value: &custom.ExprArg_U64{
U64: *arg.U64,
},
}, nil
}
if arg.Reuse != "" {
if err := validateCustomVarName(arg.Reuse); err != nil {
return nil, err
}
return &custom.ExprArg{
Value: &custom.ExprArg_Var{
Var: arg.Reuse,
},
}, nil
}
if arg.Metadata != "" {
return &custom.ExprArg{
Value: &custom.ExprArg_Metadata{
Metadata: arg.Metadata,
},
}, nil
}
parsedExpr, err := buildCustomTransform(arg.Transform)
if err != nil {
return nil, err
}
return &custom.ExprArg{
Value: &custom.ExprArg_Expr{
Expr: parsedExpr,
},
}, nil
}
type HeaderCustomUDP struct {
Mode string `json:"mode"`
Client []UDPItem `json:"client"`
Server []UDPItem `json:"server"`
}
func (c *HeaderCustomUDP) Build() (proto.Message, error) {
switch c.Mode {
case "", "prefix", "standalone":
default:
return nil, errors.New("unknown udp mode")
}
for _, item := range c.Client {
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
for _, item := range c.Server {
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
client := make([]*custom.UDPItem, 0, len(c.Client))
for _, item := range c.Client {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errors.New("invalid randRange")
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if err != nil {
return nil, err
}
client = append(client, &custom.UDPItem{
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
server := make([]*custom.UDPItem, 0, len(c.Server))
for _, item := range c.Server {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errors.New("invalid randRange")
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if err != nil {
return nil, err
}
server = append(server, &custom.UDPItem{
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
if c.Mode == "standalone" {
return &custom.UDPStandaloneConfig{
Client: client,
Server: server,
}, nil
} else {
return &custom.UDPConfig{
Client: client,
Server: server,
}, nil
}
}
type MkcpLegacy struct {
Header string `json:"header"`
Value string `json:"value"`
}
func (c *MkcpLegacy) Build() (proto.Message, error) {
if len(c.Header) == 0 {
if len(c.Value) == 0 {
return &original.Config{}, nil
} else {
return &aes128gcm.Config{Password: c.Value}, nil
}
}
switch strings.ToLower(c.Header) {
case "dns":
domain := c.Value
if len(domain) == 0 {
domain = "www.baidu.com"
}
return &header.Config{ID: 0, Domain: domain}, nil
case "dtls":
return &header.Config{ID: 1}, nil
case "srtp":
return &header.Config{ID: 2}, nil
case "utp":
return &header.Config{ID: 3}, nil
case "wechat":
return &header.Config{ID: 4}, nil
case "wireguard":
return &header.Config{ID: 5}, nil
default:
return nil, errors.New("invalid header ", c.Header)
}
}
type Salamander struct {
Password string `json:"password"`
PacketSize Int32Range `json:"packetSize"`
}
func (c *Salamander) Build() (proto.Message, error) {
if c.PacketSize.To > 0 {
if c.PacketSize.From <= 0 || c.PacketSize.To > 2048 {
return nil, errors.New("gecko: invalid min/max packet size")
}
return &salamander.GeckoConfig{
Password: c.Password,
MinPacketSize: c.PacketSize.From,
MaxPacketSize: c.PacketSize.To,
}, nil
}
return &salamander.Config{
Password: c.Password,
}, 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 &sudoku.Config{
Password: c.Password,
Ascii: c.ASCII,
CustomTable: customTable,
CustomTables: customTables,
PaddingMin: paddingMin,
PaddingMax: paddingMax,
}, nil
}
type Xdns struct {
Domain json.RawMessage `json:"domain"`
Domains []string `json:"domains"`
Resolvers []string `json:"resolvers"`
}
func (c *Xdns) Build() (proto.Message, error) {
if c.Domain != nil {
return nil, errors.PrintRemovedFeatureError("domain", "domains(server) & resolvers(client)")
}
if len(c.Domains) == 0 && len(c.Resolvers) == 0 {
return nil, errors.New("empty domains & empty resolvers")
}
for _, r := range c.Resolvers {
if !strings.Contains(r, "+udp://") {
return nil, errors.New("invalid resolver ", r)
}
}
return &xdns.Config{
Domains: c.Domains,
Resolvers: c.Resolvers,
}, nil
}
type Xicmp struct {
DGRAM bool `json:"dgram"`
IPs []string `json:"ips"`
}
func (c *Xicmp) Build() (proto.Message, error) {
for _, ip := range c.IPs {
if _, err := netip.ParseAddr(ip); err != nil {
return nil, err
}
}
config := &xicmp.Config{
DGRAM: c.DGRAM,
IPs: c.IPs,
}
return config, nil
}
type Realm struct {
Url string `json:"url"`
StunServers []string `json:"stunServers"`
TlsConfig *TLSConfig `json:"tlsConfig"`
}
func (c *Realm) Build() (proto.Message, error) {
var scheme, host, port, token, id string
var stunServers []string
var tlsConfig *tls.Config
u, err := url.Parse(c.Url)
if err != nil {
return nil, err
}
switch u.Scheme {
case "realm":
scheme = "https"
case "realm+http":
scheme = "http"
default:
return nil, errors.New("invalid scheme", u.Scheme)
}
host = u.Hostname()
if host == "" {
return nil, errors.New("invalid host", host)
}
port = u.Port()
if port == "" {
port = "443"
if scheme == "http" {
port = "80"
}
}
token, err = url.PathUnescape(u.User.String())
if err != nil {
return nil, err
}
if token == "" {
return nil, errors.New("invalid token", token)
}
id, err = url.PathUnescape(strings.TrimPrefix(u.EscapedPath(), "/"))
if err != nil {
return nil, err
}
if id == "" {
return nil, errors.New("invalid id", id)
}
if len(c.StunServers) == 0 {
return nil, errors.New("empty stunServers")
}
for _, s := range c.StunServers {
_, _, err = net.SplitHostPort(s)
if err != nil {
return nil, err
}
}
stunServers = c.StunServers
if c.TlsConfig != nil {
tc, err := c.TlsConfig.Build()
if err != nil {
return nil, err
}
tlsConfig = tc.(*tls.Config)
}
return &realm.Config{
Scheme: scheme,
Host: host,
Port: port,
Token: token,
ID: id,
StunServers: stunServers,
TlsConfig: tlsConfig,
}, nil
}
type Mask struct {
Type string `json:"type"`
Settings *json.RawMessage `json:"settings"`
}
func (c *Mask) Build(tcp bool) (proto.Message, error) {
loader := udpmaskLoader
if tcp {
loader = tcpmaskLoader
}
settings := []byte("{}")
if c.Settings != nil {
settings = ([]byte)(*c.Settings)
}
rawConfig, err := loader.LoadWithID(settings, c.Type)
if err != nil {
return nil, err
}
ts, err := rawConfig.(Buildable).Build()
if err != nil {
return nil, err
}
return ts, nil
}
type QuicParamsConfig struct {
Congestion string `json:"congestion"`
Debug bool `json:"debug"`
BbrProfile string `json:"bbrProfile"`
BrutalUp Bandwidth `json:"brutalUp"`
BrutalDown Bandwidth `json:"brutalDown"`
UdpHop UdpHop `json:"udpHop"`
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"`
MaxIdleTimeout int64 `json:"maxIdleTimeout"`
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"`
MaxIncomingStreams int64 `json:"maxIncomingStreams"`
}
type FinalMask struct {
Tcp []Mask `json:"tcp"`
Udp []Mask `json:"udp"`
QuicParams *QuicParamsConfig `json:"quicParams"`
}
File diff suppressed because it is too large Load Diff
+814
View File
@@ -0,0 +1,814 @@
package conf
import (
"context"
"encoding/json"
"math/big"
"net/url"
"sort"
"strconv"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/grpc"
"github.com/xtls/xray-core/transport/internet/headers/http"
"github.com/xtls/xray-core/transport/internet/headers/noop"
"github.com/xtls/xray-core/transport/internet/httpupgrade"
"github.com/xtls/xray-core/transport/internet/hysteria"
"github.com/xtls/xray-core/transport/internet/kcp"
"github.com/xtls/xray-core/transport/internet/splithttp"
"github.com/xtls/xray-core/transport/internet/tcp"
"github.com/xtls/xray-core/transport/internet/websocket"
"google.golang.org/protobuf/proto"
)
type NoOpConnectionAuthenticator struct{}
func (NoOpConnectionAuthenticator) Build() (proto.Message, error) {
return new(noop.ConnectionConfig), nil
}
type AuthenticatorRequest struct {
Version string `json:"version"`
Method string `json:"method"`
Path StringList `json:"path"`
Headers map[string]*StringList `json:"headers"`
}
func sortMapKeys(m map[string]*StringList) []string {
var keys []string
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
config := &http.RequestConfig{
Uri: []string{"/"},
Header: []*http.Header{
{
Name: "Host",
Value: []string{"www.baidu.com", "www.bing.com"},
},
{
Name: "User-Agent",
Value: []string{utils.ChromeUA},
},
{
Name: "Sec-CH-UA",
Value: []string{utils.ChromeUACH},
},
{
Name: "Sec-CH-UA-Mobile",
Value: []string{"?0"},
},
{
Name: "Sec-CH-UA-Platform",
Value: []string{"Windows"},
},
{
Name: "Sec-Fetch-Mode",
Value: []string{"no-cors", "cors", "same-origin"},
},
{
Name: "Sec-Fetch-Dest",
Value: []string{"empty"},
},
{
Name: "Sec-Fetch-Site",
Value: []string{"none"},
},
{
Name: "Sec-Fetch-User",
Value: []string{"?1"},
},
{
Name: "Accept-Encoding",
Value: []string{"gzip, deflate"},
},
{
Name: "Connection",
Value: []string{"keep-alive"},
},
{
Name: "Pragma",
Value: []string{"no-cache"},
},
},
}
if len(v.Version) > 0 {
config.Version = &http.Version{Value: v.Version}
}
if len(v.Method) > 0 {
config.Method = &http.Method{Value: v.Method}
}
if len(v.Path) > 0 {
config.Uri = append([]string(nil), v.Path...)
}
if len(v.Headers) > 0 {
config.Header = make([]*http.Header, 0, len(v.Headers))
headerNames := sortMapKeys(v.Headers)
for _, key := range headerNames {
value := v.Headers[key]
if value == nil {
return nil, errors.New("empty HTTP header value: " + key).AtError()
}
config.Header = append(config.Header, &http.Header{
Name: key,
Value: append([]string(nil), (*value)...),
})
}
}
return config, nil
}
type AuthenticatorResponse struct {
Version string `json:"version"`
Status string `json:"status"`
Reason string `json:"reason"`
Headers map[string]*StringList `json:"headers"`
}
func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
config := &http.ResponseConfig{
Header: []*http.Header{
{
Name: "Content-Type",
Value: []string{"application/octet-stream", "video/mpeg"},
},
{
Name: "Transfer-Encoding",
Value: []string{"chunked"},
},
{
Name: "Connection",
Value: []string{"keep-alive"},
},
{
Name: "Pragma",
Value: []string{"no-cache"},
},
{
Name: "Cache-Control",
Value: []string{"private", "no-cache"},
},
},
}
if len(v.Version) > 0 {
config.Version = &http.Version{Value: v.Version}
}
if len(v.Status) > 0 || len(v.Reason) > 0 {
config.Status = &http.Status{
Code: "200",
Reason: "OK",
}
if len(v.Status) > 0 {
config.Status.Code = v.Status
}
if len(v.Reason) > 0 {
config.Status.Reason = v.Reason
}
}
if len(v.Headers) > 0 {
config.Header = make([]*http.Header, 0, len(v.Headers))
headerNames := sortMapKeys(v.Headers)
for _, key := range headerNames {
value := v.Headers[key]
if value == nil {
return nil, errors.New("empty HTTP header value: " + key).AtError()
}
config.Header = append(config.Header, &http.Header{
Name: key,
Value: append([]string(nil), (*value)...),
})
}
}
return config, nil
}
type Authenticator struct {
Request AuthenticatorRequest `json:"request"`
Response AuthenticatorResponse `json:"response"`
}
func (v *Authenticator) Build() (proto.Message, error) {
config := new(http.Config)
requestConfig, err := v.Request.Build()
if err != nil {
return nil, err
}
config.Request = requestConfig
responseConfig, err := v.Response.Build()
if err != nil {
return nil, err
}
config.Response = responseConfig
return config, nil
}
var tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
"http": func() interface{} { return new(Authenticator) },
}, "type", "")
type TCPConfig struct {
HeaderConfig json.RawMessage `json:"header"`
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
}
// Build implements Buildable.
func (c *TCPConfig) Build() (proto.Message, error) {
config := new(tcp.Config)
if len(c.HeaderConfig) > 0 {
headerConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig)
if err != nil {
return nil, errors.New("invalid TCP header config").Base(err).AtError()
}
ts, err := headerConfig.(Buildable).Build()
if err != nil {
return nil, errors.New("invalid TCP header config").Base(err).AtError()
}
config.HeaderSettings = serial.ToTypedMessage(ts)
}
if c.AcceptProxyProtocol {
config.AcceptProxyProtocol = c.AcceptProxyProtocol
}
return config, nil
}
type SplitHTTPConfig struct {
Host string `json:"host"`
Path string `json:"path"`
Mode string `json:"mode"`
Headers map[string]string `json:"headers"`
XPaddingBytes Int32Range `json:"xPaddingBytes"`
XPaddingObfsMode bool `json:"xPaddingObfsMode"`
XPaddingKey string `json:"xPaddingKey"`
XPaddingHeader string `json:"xPaddingHeader"`
XPaddingPlacement string `json:"xPaddingPlacement"`
XPaddingMethod string `json:"xPaddingMethod"`
UplinkHTTPMethod string `json:"uplinkHTTPMethod"`
SessionIDPlacement string `json:"sessionIDPlacement"`
SessionIDKey string `json:"sessionIDKey"`
SessionIDTable string `json:"sessionIDTable"`
SessionIDLength Int32Range `json:"sessionIDLength"`
SeqPlacement string `json:"seqPlacement"`
SeqKey string `json:"seqKey"`
UplinkDataPlacement string `json:"uplinkDataPlacement"`
UplinkDataKey string `json:"uplinkDataKey"`
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"`
}
type XmuxConfig struct {
MaxConcurrency Int32Range `json:"maxConcurrency"`
MaxConnections Int32Range `json:"maxConnections"`
CMaxReuseTimes Int32Range `json:"cMaxReuseTimes"`
HMaxRequestTimes Int32Range `json:"hMaxRequestTimes"`
HMaxReusableSecs Int32Range `json:"hMaxReusableSecs"`
HKeepAlivePeriod int64 `json:"hKeepAlivePeriod"`
}
func newRangeConfig(input Int32Range) *splithttp.RangeConfig {
return &splithttp.RangeConfig{
From: input.From,
To: input.To,
}
}
// Build implements Buildable.
func (c *SplitHTTPConfig) Build() (proto.Message, error) {
if c.Extra != nil {
var extra SplitHTTPConfig
if err := json.Unmarshal(c.Extra, &extra); err != nil {
return nil, errors.New(`Failed to unmarshal "extra".`).Base(err)
}
extra.Host = c.Host
extra.Path = c.Path
extra.Mode = c.Mode
c = &extra
}
switch c.Mode {
case "":
c.Mode = "auto"
case "auto", "packet-up", "stream-up", "stream-one":
default:
return nil, errors.New("unsupported mode: " + c.Mode)
}
// Priority (client): host > serverName > address
for k := range c.Headers {
if strings.ToLower(k) == "host" {
return nil, errors.New(`"headers" can't contain "host"`)
}
}
if c.XPaddingBytes != (Int32Range{}) && (c.XPaddingBytes.From <= 0 || c.XPaddingBytes.To <= 0) {
return nil, errors.New("xPaddingBytes cannot be disabled")
}
if c.XPaddingKey == "" {
c.XPaddingKey = "x_padding"
}
if c.XPaddingHeader == "" {
c.XPaddingHeader = "X-Padding"
}
switch c.XPaddingPlacement {
case "":
c.XPaddingPlacement = "queryInHeader"
case "cookie", "header", "query", "queryInHeader":
default:
return nil, errors.New("unsupported padding placement: " + c.XPaddingPlacement)
}
switch c.XPaddingMethod {
case "":
c.XPaddingMethod = "repeat-x"
case "repeat-x", "tokenish":
default:
return nil, errors.New("unsupported padding method: " + c.XPaddingMethod)
}
switch c.UplinkDataPlacement {
case "":
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")
}
default:
return nil, errors.New("unsupported uplink data placement: " + c.UplinkDataPlacement)
}
if c.UplinkHTTPMethod == "" {
c.UplinkHTTPMethod = "POST"
}
c.UplinkHTTPMethod = strings.ToUpper(c.UplinkHTTPMethod)
if c.UplinkHTTPMethod == "GET" && c.Mode != "packet-up" {
return nil, errors.New("uplinkHTTPMethod can be GET only in packet-up mode")
}
switch c.SessionIDPlacement {
case "":
c.SessionIDPlacement = "path"
case "path", "cookie", "header", "query":
default:
return nil, errors.New("unsupported session placement: " + c.SessionIDPlacement)
}
switch c.SeqPlacement {
case "":
c.SeqPlacement = "path"
case "path", "cookie", "header", "query":
default:
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
}
if c.SessionIDPlacement != "path" && c.SessionIDKey == "" {
switch c.SessionIDPlacement {
case "cookie", "query":
c.SessionIDKey = "x_session"
case "header":
c.SessionIDKey = "X-Session"
}
}
if c.SessionIDTable != "" {
if predefined, ok := splithttp.PredefinedTable[c.SessionIDTable]; ok {
c.SessionIDTable = predefined
}
room := roomSize(len(c.SessionIDTable), c.SessionIDLength.From, c.SessionIDLength.To)
// 2.1B possiblities should be enough
if room.Cmp(big.NewInt(2<<30)) < 0 {
return nil, errors.New("sessionIDTable or sessionIDLength is too small")
}
if c.SessionIDLength.From <= 0 {
return nil, errors.New("sessionIDLength.from must be greater than 0")
}
for i := 0; i < len(c.SessionIDTable); i++ {
if c.SessionIDTable[i] >= 0x80 {
return nil, errors.New("sessionIDTable must contain only ASCII characters")
}
}
}
if c.SeqPlacement != "path" && c.SeqKey == "" {
switch c.SeqPlacement {
case "cookie", "query":
c.SeqKey = "x_seq"
case "header":
c.SeqKey = "X-Seq"
}
}
if c.UplinkDataPlacement != splithttp.PlacementBody && c.UplinkDataKey == "" {
switch c.UplinkDataPlacement {
case splithttp.PlacementCookie:
c.UplinkDataKey = "x_data"
case splithttp.PlacementAuto, splithttp.PlacementHeader:
c.UplinkDataKey = "X-Data"
}
}
if c.ServerMaxHeaderBytes < 0 {
return nil, errors.New("invalid negative value of maxHeaderBytes")
}
if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
}
if c.Xmux == (XmuxConfig{}) {
c.Xmux.MaxConnections.From = 6
c.Xmux.MaxConnections.To = 6
c.Xmux.HMaxRequestTimes.From = 600
c.Xmux.HMaxRequestTimes.To = 900
c.Xmux.HMaxReusableSecs.From = 1800
c.Xmux.HMaxReusableSecs.To = 3000
}
config := &splithttp.Config{
Host: c.Host,
Path: c.Path,
Mode: c.Mode,
Headers: c.Headers,
XPaddingBytes: newRangeConfig(c.XPaddingBytes),
XPaddingObfsMode: c.XPaddingObfsMode,
XPaddingKey: c.XPaddingKey,
XPaddingHeader: c.XPaddingHeader,
XPaddingPlacement: c.XPaddingPlacement,
XPaddingMethod: c.XPaddingMethod,
UplinkHTTPMethod: c.UplinkHTTPMethod,
SessionIDPlacement: c.SessionIDPlacement,
SeqPlacement: c.SeqPlacement,
SessionIDKey: c.SessionIDKey,
SeqKey: c.SeqKey,
UplinkDataPlacement: c.UplinkDataPlacement,
UplinkDataKey: c.UplinkDataKey,
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,
SessionIDTable: c.SessionIDTable,
SessionIDLength: newRangeConfig(c.SessionIDLength),
Xmux: &splithttp.XmuxConfig{
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
CMaxReuseTimes: newRangeConfig(c.Xmux.CMaxReuseTimes),
HMaxRequestTimes: newRangeConfig(c.Xmux.HMaxRequestTimes),
HMaxReusableSecs: newRangeConfig(c.Xmux.HMaxReusableSecs),
HKeepAlivePeriod: c.Xmux.HKeepAlivePeriod,
},
}
if c.DownloadSettings != nil {
if c.Mode == "stream-one" {
return nil, errors.New(`Can not use "downloadSettings" in "stream-one" mode.`)
}
var err error
if config.DownloadSettings, err = c.DownloadSettings.Build(); err != nil {
return nil, errors.New(`Failed to build "downloadSettings".`).Base(err)
}
}
return config, nil
}
func roomSize(tableSize int, min, max int32) *big.Int {
base := big.NewInt(int64(tableSize))
sum := new(big.Int)
term := new(big.Int)
for k := min; k <= max; k++ {
term.Exp(base, big.NewInt(int64(k)), nil)
sum.Add(sum, term)
}
return sum
}
type KCPConfig struct {
Mtu *uint32 `json:"mtu"`
Tti *uint32 `json:"tti"`
UpCap *uint32 `json:"uplinkCapacity"`
DownCap *uint32 `json:"downlinkCapacity"`
CwndMultiplier *uint32 `json:"cwndMultiplier"`
MaxSendingWindow *uint32 `json:"maxSendingWindow"`
HeaderConfig json.RawMessage `json:"header"`
Seed *string `json:"seed"`
}
// Build implements Buildable.
func (c *KCPConfig) Build() (proto.Message, error) {
if c.HeaderConfig != nil || c.Seed != nil {
return nil, errors.PrintRemovedFeatureError("mkcp header & seed", "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm")
}
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
if c.Mtu != nil {
config.Mtu = *c.Mtu
}
if c.Tti != nil {
config.Tti = *c.Tti
}
if c.UpCap != nil {
config.UplinkCapacity = *c.UpCap
}
if c.DownCap != nil {
config.DownlinkCapacity = *c.DownCap
}
if c.CwndMultiplier != nil {
config.CwndMultiplier = *c.CwndMultiplier
}
if c.MaxSendingWindow != nil {
config.MaxSendingWindow = *c.MaxSendingWindow
}
if config.Mtu < 21 {
return nil, errors.New("Mtu must be at least 21").AtError()
}
if config.Tti < 10 || config.Tti > 1000 {
return nil, errors.New("invalid mKCP TTI: ", c.Tti).AtError()
}
if config.CwndMultiplier < 1 {
return nil, errors.New("CwndMultiplier must be at least 1").AtError()
}
if config.GetSendingBufferSize() == 0 {
return nil, errors.New("MaxSendingWindow must be >= Mtu").AtError()
}
return config, nil
}
type GRPCConfig struct {
Authority string `json:"authority"`
ServiceName string `json:"serviceName"`
MultiMode bool `json:"multiMode"`
IdleTimeout int32 `json:"idle_timeout"`
HealthCheckTimeout int32 `json:"health_check_timeout"`
PermitWithoutStream bool `json:"permit_without_stream"`
InitialWindowsSize int32 `json:"initial_windows_size"`
UserAgent string `json:"user_agent"`
}
func (g *GRPCConfig) Build() (proto.Message, error) {
if g.IdleTimeout <= 0 {
g.IdleTimeout = 0
}
if g.HealthCheckTimeout <= 0 {
g.HealthCheckTimeout = 0
}
if g.InitialWindowsSize < 0 {
// default window size of gRPC-go
g.InitialWindowsSize = 0
}
return &grpc.Config{
Authority: g.Authority,
ServiceName: g.ServiceName,
MultiMode: g.MultiMode,
IdleTimeout: g.IdleTimeout,
HealthCheckTimeout: g.HealthCheckTimeout,
PermitWithoutStream: g.PermitWithoutStream,
InitialWindowsSize: g.InitialWindowsSize,
UserAgent: g.UserAgent,
}, nil
}
type WebSocketConfig struct {
Host string `json:"host"`
Path string `json:"path"`
Headers map[string]string `json:"headers"`
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
HeartbeatPeriod uint32 `json:"heartbeatPeriod"`
}
// Build implements Buildable.
func (c *WebSocketConfig) Build() (proto.Message, error) {
path := c.Path
var ed uint32
if u, err := url.Parse(path); err == nil {
if q := u.Query(); q.Get("ed") != "" {
Ed, _ := strconv.Atoi(q.Get("ed"))
ed = uint32(Ed)
q.Del("ed")
u.RawQuery = q.Encode()
path = u.String()
}
}
// Priority (client): host > serverName > address
for k, v := range c.Headers {
if strings.ToLower(k) == "host" {
errors.PrintDeprecatedFeatureWarning(`"host" in "headers"`, `independent "host"`)
if c.Host == "" {
c.Host = v
}
delete(c.Headers, k)
}
}
config := &websocket.Config{
Path: path,
Host: c.Host,
Header: c.Headers,
AcceptProxyProtocol: c.AcceptProxyProtocol,
Ed: ed,
HeartbeatPeriod: c.HeartbeatPeriod,
}
return config, nil
}
type HttpUpgradeConfig struct {
Host string `json:"host"`
Path string `json:"path"`
Headers map[string]string `json:"headers"`
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
}
// Build implements Buildable.
func (c *HttpUpgradeConfig) Build() (proto.Message, error) {
path := c.Path
var ed uint32
if u, err := url.Parse(path); err == nil {
if q := u.Query(); q.Get("ed") != "" {
Ed, _ := strconv.Atoi(q.Get("ed"))
ed = uint32(Ed)
q.Del("ed")
u.RawQuery = q.Encode()
path = u.String()
}
}
// Priority (client): host > serverName > address
for k := range c.Headers {
if strings.ToLower(k) == "host" {
return nil, errors.New(`"headers" can't contain "host"`)
}
}
config := &httpupgrade.Config{
Path: path,
Host: c.Host,
Header: c.Headers,
AcceptProxyProtocol: c.AcceptProxyProtocol,
Ed: ed,
}
return config, nil
}
const (
Byte = 1
Kilobyte = 1024 * Byte
Megabyte = 1024 * Kilobyte
Gigabyte = 1024 * Megabyte
Terabyte = 1024 * Gigabyte
)
type Bandwidth string
func (b Bandwidth) Bps() (uint64, error) {
s := strings.TrimSpace(strings.ToLower(string(b)))
if s == "" {
return 0, nil
}
idx := len(s)
for i, c := range s {
if (c < '0' || c > '9') && c != '.' {
idx = i
break
}
}
numStr := s[:idx]
unit := strings.TrimSpace(s[idx:])
val, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return 0, err
}
mul := uint64(1)
switch unit {
case "", "b", "bps":
mul = Byte
case "k", "kb", "kbps":
mul = Kilobyte
case "m", "mb", "mbps":
mul = Megabyte
case "g", "gb", "gbps":
mul = Gigabyte
case "t", "tb", "tbps":
mul = Terabyte
default:
return 0, errors.New("unsupported unit: " + unit)
}
return uint64(val*float64(mul)) / 8, nil
}
type UdpHop struct {
PortList PortList `json:"ports"`
Interval Int32Range `json:"interval"`
}
type Masquerade struct {
Type string `json:"type"`
Dir string `json:"dir"`
Url string `json:"url"`
RewriteHost bool `json:"rewriteHost"`
Insecure bool `json:"insecure"`
Content string `json:"content"`
Headers map[string]string `json:"headers"`
StatusCode int32 `json:"statusCode"`
}
type HysteriaConfig struct {
Version int32 `json:"version"`
Auth string `json:"auth"`
Congestion *string `json:"congestion"`
Up *Bandwidth `json:"up"`
Down *Bandwidth `json:"down"`
UdpHop *UdpHop `json:"udphop"`
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
Masquerade Masquerade `json:"masquerade"`
}
func (c *HysteriaConfig) Build() (proto.Message, error) {
if c.Version != 2 {
return nil, errors.New("version != 2")
}
if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {
errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams")
}
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
}
config := &hysteria.Config{}
config.Auth = c.Auth
config.UdpIdleTimeout = c.UdpIdleTimeout
config.MasqType = c.Masquerade.Type
config.MasqFile = c.Masquerade.Dir
config.MasqUrl = c.Masquerade.Url
config.MasqUrlRewriteHost = c.Masquerade.RewriteHost
config.MasqUrlInsecure = c.Masquerade.Insecure
config.MasqString = c.Masquerade.Content
config.MasqStringHeaders = c.Masquerade.Headers
config.MasqStringStatusCode = c.Masquerade.StatusCode
if config.UdpIdleTimeout == 0 {
config.UdpIdleTimeout = 60
}
return config, nil
}
func readFileOrString(f string, s []string) ([]byte, error) {
if len(f) > 0 {
return filesystem.ReadCert(f)
}
if len(s) > 0 {
return []byte(strings.Join(s, "\n")), nil
}
return nil, errors.New("both file and bytes are empty.")
}
+402
View File
@@ -0,0 +1,402 @@
package conf
import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"net/url"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/tls"
"google.golang.org/protobuf/proto"
)
type LimitFallback struct {
AfterBytes uint64
BytesPerSec uint64
BurstBytesPerSec uint64
}
type REALITYConfig struct {
MasterKeyLog string `json:"masterKeyLog"`
Show bool `json:"show"`
Target json.RawMessage `json:"target"`
Dest json.RawMessage `json:"dest"`
Type string `json:"type"`
Xver uint64 `json:"xver"`
ServerNames []string `json:"serverNames"`
PrivateKey string `json:"privateKey"`
MinClientVer string `json:"minClientVer"`
MaxClientVer string `json:"maxClientVer"`
MaxTimeDiff uint64 `json:"maxTimeDiff"`
ShortIds []string `json:"shortIds"`
Mldsa65Seed string `json:"mldsa65Seed"`
LimitFallbackUpload LimitFallback `json:"limitFallbackUpload"`
LimitFallbackDownload LimitFallback `json:"limitFallbackDownload"`
Fingerprint string `json:"fingerprint"`
ServerName string `json:"serverName"`
Password string `json:"password"`
PublicKey string `json:"publicKey"`
ShortId string `json:"shortId"`
Mldsa65Verify string `json:"mldsa65Verify"`
SpiderX string `json:"spiderX"`
}
func (c *REALITYConfig) Build() (proto.Message, error) {
config := new(reality.Config)
config.MasterKeyLog = c.MasterKeyLog
config.Show = c.Show
var err error
if c.Target != nil {
c.Dest = c.Target
}
if c.Dest != nil {
var i uint16
var s string
if err = json.Unmarshal(c.Dest, &i); err == nil {
s = strconv.Itoa(int(i))
} else {
_ = json.Unmarshal(c.Dest, &s)
}
if c.Type == "" && s != "" {
switch s[0] {
case '@', '/':
c.Type = "unix"
if s[0] == '@' && len(s) > 1 && s[1] == '@' && (runtime.GOOS == "linux" || runtime.GOOS == "android") {
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work with haproxy
copy(fullAddr, s[1:])
s = string(fullAddr)
}
default:
if _, err = strconv.Atoi(s); err == nil {
s = "localhost:" + s
}
if _, _, err = net.SplitHostPort(s); err == nil {
c.Type = "tcp"
}
}
}
if c.Type == "" {
return nil, errors.New(`please fill in a valid value for "target"`)
}
if c.Xver > 2 {
return nil, errors.New(`invalid PROXY protocol version, "xver" only accepts 0, 1, 2`)
}
if len(c.ServerNames) == 0 {
return nil, errors.New(`empty "serverNames"`)
}
if c.PrivateKey == "" {
return nil, errors.New(`empty "privateKey"`)
}
if config.PrivateKey, err = base64.RawURLEncoding.DecodeString(c.PrivateKey); err != nil || len(config.PrivateKey) != 32 {
return nil, errors.New(`invalid "privateKey": `, c.PrivateKey)
}
if c.MinClientVer != "" {
config.MinClientVer = make([]byte, 3)
var u uint64
for i, s := range strings.Split(c.MinClientVer, ".") {
if i == 3 {
return nil, errors.New(`invalid "minClientVer": `, c.MinClientVer)
}
if u, err = strconv.ParseUint(s, 10, 8); err != nil {
return nil, errors.New(`"minClientVer[`, i, `]" should be less than 256`)
} else {
config.MinClientVer[i] = byte(u)
}
}
}
if c.MaxClientVer != "" {
config.MaxClientVer = make([]byte, 3)
var u uint64
for i, s := range strings.Split(c.MaxClientVer, ".") {
if i == 3 {
return nil, errors.New(`invalid "maxClientVer": `, c.MaxClientVer)
}
if u, err = strconv.ParseUint(s, 10, 8); err != nil {
return nil, errors.New(`"maxClientVer[`, i, `]" should be less than 256`)
} else {
config.MaxClientVer[i] = byte(u)
}
}
}
if len(c.ShortIds) == 0 {
return nil, errors.New(`empty "shortIds"`)
}
config.ShortIds = make([][]byte, len(c.ShortIds))
for i, s := range c.ShortIds {
if len(s) > 16 {
return nil, errors.New(`too long "shortIds[`, i, `]": `, s)
}
config.ShortIds[i] = make([]byte, 8)
if _, err = hex.Decode(config.ShortIds[i], []byte(s)); err != nil {
return nil, errors.New(`invalid "shortIds[`, i, `]": `, s)
}
}
config.Dest = s
config.Type = c.Type
config.Xver = c.Xver
config.ServerNames = c.ServerNames
config.MaxTimeDiff = c.MaxTimeDiff
if c.Mldsa65Seed != "" {
if c.Mldsa65Seed == c.PrivateKey {
return nil, errors.New(`"mldsa65Seed" and "privateKey" can not be the same value: `, c.Mldsa65Seed)
}
if config.Mldsa65Seed, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Seed); err != nil || len(config.Mldsa65Seed) != 32 {
return nil, errors.New(`invalid "mldsa65Seed": `, c.Mldsa65Seed)
}
}
for _, sn := range config.ServerNames {
if strings.Contains(sn, "apple") || strings.Contains(sn, "icloud") {
errors.LogWarning(context.Background(), `REALITY: Choosing apple, icloud, etc. as the target may get your IP blocked by the GFW`)
}
}
config.LimitFallbackUpload = new(reality.LimitFallback)
config.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes
config.LimitFallbackUpload.BytesPerSec = c.LimitFallbackUpload.BytesPerSec
config.LimitFallbackUpload.BurstBytesPerSec = c.LimitFallbackUpload.BurstBytesPerSec
config.LimitFallbackDownload = new(reality.LimitFallback)
config.LimitFallbackDownload.AfterBytes = c.LimitFallbackDownload.AfterBytes
config.LimitFallbackDownload.BytesPerSec = c.LimitFallbackDownload.BytesPerSec
config.LimitFallbackDownload.BurstBytesPerSec = c.LimitFallbackDownload.BurstBytesPerSec
} else {
config.Fingerprint = strings.ToLower(c.Fingerprint)
if config.Fingerprint == "unsafe" || config.Fingerprint == "hellogolang" {
return nil, errors.New(`invalid "fingerprint": `, config.Fingerprint)
}
if tls.GetFingerprint(config.Fingerprint) == nil {
return nil, errors.New(`unknown "fingerprint": `, config.Fingerprint)
}
if len(c.ServerNames) != 0 {
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
}
if c.Password != "" {
c.PublicKey = c.Password
}
if c.PublicKey == "" {
return nil, errors.New(`empty "password"`)
}
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
return nil, errors.New(`invalid "password": `, c.PublicKey)
}
if len(c.ShortIds) != 0 {
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
}
if len(c.ShortId) > 16 {
return nil, errors.New(`too long "shortId": `, c.ShortId)
}
config.ShortId = make([]byte, 8)
if _, err = hex.Decode(config.ShortId, []byte(c.ShortId)); err != nil {
return nil, errors.New(`invalid "shortId": `, c.ShortId)
}
if c.Mldsa65Verify != "" {
if config.Mldsa65Verify, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Verify); err != nil || len(config.Mldsa65Verify) != 1952 {
return nil, errors.New(`invalid "mldsa65Verify": `, c.Mldsa65Verify)
}
}
if c.SpiderX == "" {
c.SpiderX = "/"
}
if c.SpiderX[0] != '/' {
return nil, errors.New(`invalid "spiderX": `, c.SpiderX)
}
config.SpiderY = make([]int64, 10)
u, _ := url.Parse(c.SpiderX)
q := u.Query()
parse := func(param string, index int) {
if q.Get(param) != "" {
s := strings.Split(q.Get(param), "-")
if len(s) == 1 {
config.SpiderY[index], _ = strconv.ParseInt(s[0], 10, 64)
config.SpiderY[index+1], _ = strconv.ParseInt(s[0], 10, 64)
} else {
config.SpiderY[index], _ = strconv.ParseInt(s[0], 10, 64)
config.SpiderY[index+1], _ = strconv.ParseInt(s[1], 10, 64)
}
}
q.Del(param)
}
parse("p", 0) // padding
parse("c", 2) // concurrency
parse("t", 4) // times
parse("i", 6) // interval
parse("r", 8) // return
u.RawQuery = q.Encode()
config.SpiderX = u.String()
config.ServerName = c.ServerName
}
return config, nil
}
type TLSCertConfig struct {
CertFile string `json:"certificateFile"`
CertStr []string `json:"certificate"`
KeyFile string `json:"keyFile"`
KeyStr []string `json:"key"`
Usage string `json:"usage"`
OcspStapling uint64 `json:"ocspStapling"`
OneTimeLoading bool `json:"oneTimeLoading"`
BuildChain bool `json:"buildChain"`
}
// Build implements Buildable.
func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
certificate := new(tls.Certificate)
cert, err := readFileOrString(c.CertFile, c.CertStr)
if err != nil {
return nil, errors.New("failed to parse certificate").Base(err)
}
certificate.Certificate = cert
certificate.CertificatePath = c.CertFile
if len(c.KeyFile) > 0 || len(c.KeyStr) > 0 {
key, err := readFileOrString(c.KeyFile, c.KeyStr)
if err != nil {
return nil, errors.New("failed to parse key").Base(err)
}
certificate.Key = key
certificate.KeyPath = c.KeyFile
}
switch strings.ToLower(c.Usage) {
case "encipherment":
certificate.Usage = tls.Certificate_ENCIPHERMENT
case "verify":
certificate.Usage = tls.Certificate_AUTHORITY_VERIFY
case "issue":
certificate.Usage = tls.Certificate_AUTHORITY_ISSUE
default:
certificate.Usage = tls.Certificate_ENCIPHERMENT
}
if certificate.KeyPath == "" && certificate.CertificatePath == "" {
certificate.OneTimeLoading = true
} else {
certificate.OneTimeLoading = c.OneTimeLoading
}
certificate.OcspStapling = c.OcspStapling
certificate.BuildChain = c.BuildChain
return certificate, nil
}
type TLSConfig struct {
AllowInsecure bool `json:"allowInsecure"`
Certs []*TLSCertConfig `json:"certificates"`
ServerName string `json:"serverName"`
ALPN *StringList `json:"alpn"`
EnableSessionResumption bool `json:"enableSessionResumption"`
DisableSystemRoot bool `json:"disableSystemRoot"`
MinVersion string `json:"minVersion"`
MaxVersion string `json:"maxVersion"`
CipherSuites string `json:"cipherSuites"`
Fingerprint string `json:"fingerprint"`
RejectUnknownSNI bool `json:"rejectUnknownSni"`
CurvePreferences *StringList `json:"curvePreferences"`
MasterKeyLog string `json:"masterKeyLog"`
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
VerifyPeerCertByName string `json:"verifyPeerCertByName"`
ECHServerKeys string `json:"echServerKeys"`
ECHConfigList string `json:"echConfigList"`
ECHSocketSettings *SocketConfig `json:"echSockopt"`
}
// Build implements Buildable.
func (c *TLSConfig) Build() (proto.Message, error) {
config := new(tls.Config)
config.Certificate = make([]*tls.Certificate, len(c.Certs))
for idx, certConf := range c.Certs {
cert, err := certConf.Build()
if err != nil {
return nil, err
}
config.Certificate[idx] = cert
}
serverName := c.ServerName
if len(c.ServerName) > 0 {
config.ServerName = serverName
}
if c.ALPN != nil && len(*c.ALPN) > 0 {
config.NextProtocol = []string(*c.ALPN)
}
if len(config.NextProtocol) > 1 {
for _, p := range config.NextProtocol {
if tls.IsFromMitm(p) {
return nil, errors.New(`only one element is allowed in "alpn" when using "fromMitm" in it`)
}
}
}
if c.CurvePreferences != nil && len(*c.CurvePreferences) > 0 {
config.CurvePreferences = []string(*c.CurvePreferences)
}
config.EnableSessionResumption = c.EnableSessionResumption
config.DisableSystemRoot = c.DisableSystemRoot
config.MinVersion = c.MinVersion
config.MaxVersion = c.MaxVersion
config.CipherSuites = c.CipherSuites
config.Fingerprint = strings.ToLower(c.Fingerprint)
if config.Fingerprint != "unsafe" && tls.GetFingerprint(config.Fingerprint) == nil {
return nil, errors.New(`unknown "fingerprint": `, config.Fingerprint)
}
config.RejectUnknownSni = c.RejectUnknownSNI
config.MasterKeyLog = c.MasterKeyLog
if c.AllowInsecure {
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn)`)
}
if c.PinnedPeerCertSha256 != "" {
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
v = strings.TrimSpace(v)
if v == "" {
continue
}
// remove colons for OpenSSL format
hashValue, err := hex.DecodeString(strings.ReplaceAll(v, ":", ""))
if err != nil {
return nil, err
}
if len(hashValue) != 32 {
return nil, errors.New("incorrect pinnedPeerCertSha256 length: ", v)
}
config.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)
}
}
if c.VerifyPeerCertByName != "" {
for v := range strings.SplitSeq(c.VerifyPeerCertByName, ",") {
v = strings.TrimSpace(v)
if v == "" {
continue
}
config.VerifyPeerCertByName = append(config.VerifyPeerCertByName, v)
}
}
if c.ECHServerKeys != "" {
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.ECHServerKeys)
if err != nil {
return nil, errors.New("invalid ECH Config", c.ECHServerKeys)
}
config.EchServerKeys = EchPrivateKey
}
config.EchConfigList = c.ECHConfigList
if c.ECHSocketSettings != nil {
ss, err := c.ECHSocketSettings.Build()
if err != nil {
return nil, errors.New("Failed to build ech sockopt.").Base(err)
}
config.EchSocketSettings = ss
}
return config, nil
}
+187
View File
@@ -0,0 +1,187 @@
package conf
import (
"encoding/json"
"math"
"strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet"
)
type CustomSockoptConfig struct {
Syetem string `json:"system"`
Network string `json:"network"`
Level string `json:"level"`
Opt string `json:"opt"`
Value string `json:"value"`
Type string `json:"type"`
}
type HappyEyeballsConfig struct {
PrioritizeIPv6 bool `json:"prioritizeIPv6"`
TryDelayMs uint64 `json:"tryDelayMs"`
Interleave uint32 `json:"interleave"`
MaxConcurrentTry uint32 `json:"maxConcurrentTry"`
}
func (h *HappyEyeballsConfig) UnmarshalJSON(data []byte) error {
innerHappyEyeballsConfig := struct {
PrioritizeIPv6 bool `json:"prioritizeIPv6"`
TryDelayMs uint64 `json:"tryDelayMs"`
Interleave uint32 `json:"interleave"`
MaxConcurrentTry uint32 `json:"maxConcurrentTry"`
}{PrioritizeIPv6: false, Interleave: 1, TryDelayMs: 0, MaxConcurrentTry: 4}
if err := json.Unmarshal(data, &innerHappyEyeballsConfig); err != nil {
return err
}
h.PrioritizeIPv6 = innerHappyEyeballsConfig.PrioritizeIPv6
h.TryDelayMs = innerHappyEyeballsConfig.TryDelayMs
h.Interleave = innerHappyEyeballsConfig.Interleave
h.MaxConcurrentTry = innerHappyEyeballsConfig.MaxConcurrentTry
return nil
}
type SocketConfig struct {
Mark int32 `json:"mark"`
TFO interface{} `json:"tcpFastOpen"`
TProxy string `json:"tproxy"`
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
DomainStrategy string `json:"domainStrategy"`
DialerProxy string `json:"dialerProxy"`
TCPKeepAliveInterval int32 `json:"tcpKeepAliveInterval"`
TCPKeepAliveIdle int32 `json:"tcpKeepAliveIdle"`
TCPCongestion string `json:"tcpCongestion"`
TCPWindowClamp int32 `json:"tcpWindowClamp"`
TCPMaxSeg int32 `json:"tcpMaxSeg"`
Penetrate bool `json:"penetrate"`
TCPUserTimeout int32 `json:"tcpUserTimeout"`
V6only bool `json:"v6only"`
Interface string `json:"interface"`
TcpMptcp bool `json:"tcpMptcp"`
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
AddressPortStrategy string `json:"addressPortStrategy"`
HappyEyeballsSettings *HappyEyeballsConfig `json:"happyEyeballs"`
TrustedXForwardedFor []string `json:"trustedXForwardedFor"`
}
// Build implements Buildable.
func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
tfo := int32(0) // don't invoke setsockopt() for TFO
if c.TFO != nil {
switch v := c.TFO.(type) {
case bool:
if v {
tfo = 256
} else {
tfo = -1 // TFO need to be disabled
}
case float64:
tfo = int32(math.Min(v, math.MaxInt32))
default:
return nil, errors.New("tcpFastOpen: only boolean and integer value is acceptable")
}
}
var tproxy internet.SocketConfig_TProxyMode
switch strings.ToLower(c.TProxy) {
case "tproxy":
tproxy = internet.SocketConfig_TProxy
case "redirect":
tproxy = internet.SocketConfig_Redirect
default:
tproxy = internet.SocketConfig_Off
}
dStrategy := internet.DomainStrategy_AS_IS
switch strings.ToLower(c.DomainStrategy) {
case "asis", "":
dStrategy = internet.DomainStrategy_AS_IS
case "useip":
dStrategy = internet.DomainStrategy_USE_IP
case "useipv4":
dStrategy = internet.DomainStrategy_USE_IP4
case "useipv6":
dStrategy = internet.DomainStrategy_USE_IP6
case "useipv4v6":
dStrategy = internet.DomainStrategy_USE_IP46
case "useipv6v4":
dStrategy = internet.DomainStrategy_USE_IP64
case "forceip":
dStrategy = internet.DomainStrategy_FORCE_IP
case "forceipv4":
dStrategy = internet.DomainStrategy_FORCE_IP4
case "forceipv6":
dStrategy = internet.DomainStrategy_FORCE_IP6
case "forceipv4v6":
dStrategy = internet.DomainStrategy_FORCE_IP46
case "forceipv6v4":
dStrategy = internet.DomainStrategy_FORCE_IP64
default:
return nil, errors.New("unsupported domain strategy: ", c.DomainStrategy)
}
var customSockopts []*internet.CustomSockopt
for _, copt := range c.CustomSockopt {
customSockopt := &internet.CustomSockopt{
System: copt.Syetem,
Network: copt.Network,
Level: copt.Level,
Opt: copt.Opt,
Value: copt.Value,
Type: copt.Type,
}
customSockopts = append(customSockopts, customSockopt)
}
addressPortStrategy := internet.AddressPortStrategy_None
switch strings.ToLower(c.AddressPortStrategy) {
case "none", "":
addressPortStrategy = internet.AddressPortStrategy_None
case "srvportonly":
addressPortStrategy = internet.AddressPortStrategy_SrvPortOnly
case "srvaddressonly":
addressPortStrategy = internet.AddressPortStrategy_SrvAddressOnly
case "srvportandaddress":
addressPortStrategy = internet.AddressPortStrategy_SrvPortAndAddress
case "txtportonly":
addressPortStrategy = internet.AddressPortStrategy_TxtPortOnly
case "txtaddressonly":
addressPortStrategy = internet.AddressPortStrategy_TxtAddressOnly
case "txtportandaddress":
addressPortStrategy = internet.AddressPortStrategy_TxtPortAndAddress
default:
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
}
happyEyeballs := &internet.HappyEyeballsConfig{Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 0, MaxConcurrentTry: 4}
if c.HappyEyeballsSettings != nil {
happyEyeballs.PrioritizeIpv6 = c.HappyEyeballsSettings.PrioritizeIPv6
happyEyeballs.Interleave = c.HappyEyeballsSettings.Interleave
happyEyeballs.TryDelayMs = c.HappyEyeballsSettings.TryDelayMs
happyEyeballs.MaxConcurrentTry = c.HappyEyeballsSettings.MaxConcurrentTry
}
return &internet.SocketConfig{
Mark: c.Mark,
Tfo: tfo,
Tproxy: tproxy,
DomainStrategy: dStrategy,
AcceptProxyProtocol: c.AcceptProxyProtocol,
DialerProxy: c.DialerProxy,
TcpKeepAliveInterval: c.TCPKeepAliveInterval,
TcpKeepAliveIdle: c.TCPKeepAliveIdle,
TcpCongestion: c.TCPCongestion,
TcpWindowClamp: c.TCPWindowClamp,
TcpMaxSeg: c.TCPMaxSeg,
Penetrate: c.Penetrate,
TcpUserTimeout: c.TCPUserTimeout,
V6Only: c.V6only,
Interface: c.Interface,
TcpMptcp: c.TcpMptcp,
CustomSockopt: customSockopts,
AddressPortStrategy: addressPortStrategy,
HappyEyeballs: happyEyeballs,
TrustedXForwardedFor: c.TrustedXForwardedFor,
}, nil
}
-4
View File
@@ -31,10 +31,6 @@ func (a *VMessAccount) Build() *vmess.Account {
st = protocol.SecurityType_CHACHA20_POLY1305 st = protocol.SecurityType_CHACHA20_POLY1305
case "auto": case "auto":
st = protocol.SecurityType_AUTO st = protocol.SecurityType_AUTO
case "none":
st = protocol.SecurityType_NONE
case "zero":
st = protocol.SecurityType_ZERO
default: default:
st = protocol.SecurityType_AUTO st = protocol.SecurityType_AUTO
} }
+27 -3
View File
@@ -7,6 +7,9 @@ import (
"strings" "strings"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/proxy/wireguard" "github.com/xtls/xray-core/proxy/wireguard"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -17,9 +20,12 @@ type WireGuardPeerConfig struct {
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
KeepAlive uint32 `json:"keepAlive"` KeepAlive uint32 `json:"keepAlive"`
AllowedIPs []string `json:"allowedIPs,omitempty"` AllowedIPs []string `json:"allowedIPs,omitempty"`
Level uint32 `json:"level"`
Email string `json:"email"`
} }
func (c *WireGuardPeerConfig) Build() (proto.Message, error) { func (c *WireGuardPeerConfig) Build() (*wireguard.PeerConfig, error) {
var err error var err error
config := new(wireguard.PeerConfig) config := new(wireguard.PeerConfig)
@@ -78,14 +84,32 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
config.Endpoint = c.Address config.Endpoint = c.Address
} }
if c.Peers != nil { if c.IsClient {
config.Peers = make([]*wireguard.PeerConfig, len(c.Peers)) config.Peers = make([]*wireguard.PeerConfig, len(c.Peers))
for i, p := range c.Peers { for i, p := range c.Peers {
msg, err := p.Build() msg, err := p.Build()
if err != nil { if err != nil {
return nil, err return nil, err
} }
config.Peers[i] = msg.(*wireguard.PeerConfig) config.Peers[i] = msg
}
} else {
config.Users = make([]*protocol.User, len(c.Peers))
processUser := func(idx int) error {
p := c.Peers[idx]
m, err := p.Build()
if err != nil {
return err
}
config.Users[idx] = &protocol.User{
Email: p.Email,
Level: p.Level,
Account: serial.ToTypedMessage(m),
}
return nil
}
if err := task.ParallelForN(len(c.Peers), processUser); err != nil {
return nil, err
} }
} }
-50
View File
@@ -1,50 +0,0 @@
package conf_test
import (
"testing"
. "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/proxy/wireguard"
)
func TestWireGuardConfig(t *testing.T) {
creator := func() Buildable {
return new(WireGuardConfig)
}
runMultiTestCase(t, []TestCase{
{
Input: `{
"secretKey": "uJv5tZMDltsiYEn+kUwb0Ll/CXWhMkaSCWWhfPEZM3A=",
"address": ["10.1.1.1", "fd59:7153:2388:b5fd:0000:0000:1234:0001"],
"peers": [
{
"publicKey": "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a",
"endpoint": "127.0.0.1:1234"
}
],
"mtu": 1300,
"workers": 2,
"domainStrategy": "ForceIPv6v4",
"noKernelTun": false
}`,
Parser: loadJSON(creator),
Output: &wireguard.DeviceConfig{
// key converted into hex form
SecretKey: "b89bf9b5930396db226049fe914c1bd0b97f0975a13246920965a17cf1193370",
Endpoint: []string{"10.1.1.1", "fd59:7153:2388:b5fd:0000:0000:1234:0001"},
Peers: []*wireguard.PeerConfig{
{
// also can read from hex form directly
PublicKey: "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a",
Endpoint: "127.0.0.1:1234",
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
},
},
Mtu: 1300,
DomainStrategy: wireguard.DeviceConfig_FORCE_IP64,
NoKernelTun: false,
},
},
})
}
+62 -28
View File
@@ -14,6 +14,7 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
core "github.com/xtls/xray-core/core" core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/freedom"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
) )
@@ -215,23 +216,51 @@ type OutboundDetourConfig struct {
Tag string `json:"tag"` Tag string `json:"tag"`
Settings *json.RawMessage `json:"settings"` Settings *json.RawMessage `json:"settings"`
StreamSetting *StreamConfig `json:"streamSettings"` StreamSetting *StreamConfig `json:"streamSettings"`
ProxySettings *ProxyConfig `json:"proxySettings"` ProxySettings *json.RawMessage `json:"proxySettings"`
MuxSettings *MuxConfig `json:"mux"` MuxSettings *MuxConfig `json:"mux"`
TargetStrategy string `json:"targetStrategy"` TargetStrategy string `json:"targetStrategy"`
} }
func (c *OutboundDetourConfig) checkChainProxyConfig() error { func requiresTransportSecurity(address *Address) bool {
if c.StreamSetting == nil || c.ProxySettings == nil || c.StreamSetting.SocketSettings == nil { if address == nil || address.Address == nil {
return false
}
if address.Family().IsIP() {
return !geodata.GetPrivateIPMatcher().Match(address.IP())
}
domain := strings.TrimSuffix(strings.ToLower(address.Domain()), ".")
return !geodata.GetPrivateDomainMatcher().MatchAny(domain)
}
func validateOutboundTransportSecurity(rawConfig interface{}, senderSettings *proxyman.SenderConfig) error {
if senderSettings.StreamSettings != nil && senderSettings.StreamSettings.GetSecurityType() != "" {
return nil return nil
} }
if len(c.ProxySettings.Tag) > 0 && len(c.StreamSetting.SocketSettings.DialerProxy) > 0 {
return errors.New("proxySettings.tag is conflicted with sockopt.dialerProxy").AtWarning() if vlessCfg, ok := rawConfig.(*VLessOutboundConfig); ok {
if vlessCfg.Encryption != "" && vlessCfg.Encryption != "none" {
return nil
}
if requiresTransportSecurity(vlessCfg.Address) {
return errors.New("vless without TLS or other encryption is prohibited unless the server address is a private IP or domain")
}
} }
if tjCfg, ok := rawConfig.(*TrojanClientConfig); ok {
if requiresTransportSecurity(tjCfg.Address) {
return errors.New("trojan without TLS is prohibited unless the server address is a private IP or domain")
}
}
return nil return nil
} }
// Build implements Buildable. // Build implements Buildable.
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if c.ProxySettings != nil {
return nil, errors.PrintRemovedFeatureError(`outbound "proxySettings"`, `"streamSettings.sockopt.dialerProxy"`)
}
senderSettings := &proxyman.SenderConfig{} senderSettings := &proxyman.SenderConfig{}
switch strings.ToLower(c.TargetStrategy) { switch strings.ToLower(c.TargetStrategy) {
case "asis", "": case "asis", "":
@@ -259,9 +288,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
default: default:
return nil, errors.New("unsupported target domain strategy: ", c.TargetStrategy) return nil, errors.New("unsupported target domain strategy: ", c.TargetStrategy)
} }
if err := c.checkChainProxyConfig(); err != nil {
return nil, err
}
if c.SendThrough != nil { if c.SendThrough != nil {
address := ParseSendThough(c.SendThrough) address := ParseSendThough(c.SendThrough)
@@ -287,26 +313,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
senderSettings.StreamSettings = ss senderSettings.StreamSettings = ss
} }
if c.ProxySettings != nil {
ps, err := c.ProxySettings.Build()
if err != nil {
return nil, errors.New("invalid outbound detour proxy settings").Base(err)
}
if ps.TransportLayerProxy {
if senderSettings.StreamSettings != nil {
if senderSettings.StreamSettings.SocketSettings != nil {
senderSettings.StreamSettings.SocketSettings.DialerProxy = ps.Tag
} else {
senderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{DialerProxy: ps.Tag}
}
} else {
senderSettings.StreamSettings = &internet.StreamConfig{SocketSettings: &internet.SocketConfig{DialerProxy: ps.Tag}}
}
ps = nil
}
senderSettings.ProxySettings = ps
}
if c.MuxSettings != nil { if c.MuxSettings != nil {
ms, err := c.MuxSettings.Build() ms, err := c.MuxSettings.Build()
if err != nil { if err != nil {
@@ -323,11 +329,39 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if err != nil { if err != nil {
return nil, errors.New("failed to load outbound detour config for protocol ", c.Protocol).Base(err) return nil, errors.New("failed to load outbound detour config for protocol ", c.Protocol).Base(err)
} }
if err := validateOutboundTransportSecurity(rawConfig, senderSettings); err != nil {
return nil, err
}
ts, err := rawConfig.(Buildable).Build() ts, err := rawConfig.(Buildable).Build()
if err != nil { if err != nil {
return nil, errors.New("failed to build outbound handler for protocol ", c.Protocol).Base(err) return nil, errors.New("failed to build outbound handler for protocol ", c.Protocol).Base(err)
} }
if fc, ok := ts.(*freedom.Config); ok {
if senderSettings.StreamSettings != nil &&
senderSettings.StreamSettings.SocketSettings != nil &&
senderSettings.StreamSettings.SocketSettings.AddressPortStrategy != internet.AddressPortStrategy_None {
return nil, errors.New(`freedom outbound does not support "sockopt.addressPortStrategy"`)
}
var strategy internet.DomainStrategy
if strategy = senderSettings.TargetStrategy; strategy != internet.DomainStrategy_AS_IS {
errors.LogWarning(context.Background(), `The "outbound.targetStrategy" setting is not supported directly by freedom and has been automatically migrated to "sockopt.domainStrategy" with no behavior change.`)
senderSettings.TargetStrategy = internet.DomainStrategy_AS_IS
} else if strategy = fc.DomainStrategy; strategy != internet.DomainStrategy_AS_IS {
errors.LogWarning(context.Background(), `The "freedom.domainStrategy" setting is deprecated and will be removed. For compatibility, its value has been automatically migrated to "sockopt.domainStrategy". Please update your config before removal.`)
}
if strategy != internet.DomainStrategy_AS_IS {
if senderSettings.StreamSettings == nil {
senderSettings.StreamSettings = &internet.StreamConfig{}
}
if senderSettings.StreamSettings.SocketSettings == nil {
senderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{}
}
senderSettings.StreamSettings.SocketSettings.DomainStrategy = strategy
}
}
return &core.OutboundHandlerConfig{ return &core.OutboundHandlerConfig{
SenderSettings: serial.ToTypedMessage(senderSettings), SenderSettings: serial.ToTypedMessage(senderSettings),
Tag: c.Tag, Tag: c.Tag,
+91 -121
View File
@@ -40,6 +40,10 @@ var (
func init() { func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
h := new(Handler) h := new(Handler)
if streamSettings, ok := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig); ok && streamSettings.SocketSettings != nil {
h.resolveStrategy = streamSettings.SocketSettings.DomainStrategy
h.usesDialerProxy = len(streamSettings.SocketSettings.DialerProxy) > 0
}
if err := core.RequireFeatures(ctx, func(pm policy.Manager) error { if err := core.RequireFeatures(ctx, func(pm policy.Manager) error {
return h.Init(config.(*Config), pm) return h.Init(config.(*Config), pm)
}); err != nil { }); err != nil {
@@ -62,26 +66,7 @@ func init() {
defaultBlockPrivateRule = &FinalRule{ defaultBlockPrivateRule = &FinalRule{
action: RuleAction_Block, action: RuleAction_Block,
network: allNetworks, network: allNetworks,
ip: common.Must2(geodata.IPReg.BuildIPMatcher(common.Must2(geodata.ParseIPRules([]string{ ip: geodata.GetPrivateIPMatcher(),
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/3",
"::/127",
"fc00::/7",
"fe80::/10",
"ff00::/8",
})))),
} }
defaultBlockAllRule = &FinalRule{ defaultBlockAllRule = &FinalRule{
@@ -100,9 +85,11 @@ type FinalRule struct {
// Handler handles Freedom connections. // Handler handles Freedom connections.
type Handler struct { type Handler struct {
policyManager policy.Manager policyManager policy.Manager
config *Config config *Config
finalRules []*FinalRule finalRules []*FinalRule
resolveStrategy internet.DomainStrategy
usesDialerProxy bool
} }
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) { func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
@@ -179,22 +166,6 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
return nil return nil
} }
func (h *Handler) shouldResolveDomainBeforeFinalRules(dialDest net.Destination, defaultRule *FinalRule) bool {
if !dialDest.Address.Family().IsDomain() {
return false
}
if len(h.finalRules) > 0 {
rule := h.finalRules[0]
if rule.action == RuleAction_Allow && rule.network[dialDest.Network] && len(rule.port) == 0 && rule.ip == nil {
return false
}
}
if defaultRule != nil || len(h.finalRules) > 0 {
return true
}
return false
}
func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule { func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule {
for _, rule := range h.finalRules { for _, rule := range h.finalRules {
if rule.Apply(network, address, port) { if rule.Apply(network, address, port) {
@@ -207,13 +178,6 @@ func (h *Handler) matchFinalRule(network net.Network, address net.Address, port
return nil return nil
} }
func (h *Handler) applyFinalRules(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) RuleAction {
if rule := h.matchFinalRule(network, address, port, defaultRule); rule != nil {
return rule.action
}
return RuleAction_Allow
}
// Init initializes the Handler with necessary parameters. // Init initializes the Handler with necessary parameters.
func (h *Handler) Init(config *Config, pm policy.Manager) error { func (h *Handler) Init(config *Config, pm policy.Manager) error {
h.config = config h.config = config
@@ -248,6 +212,20 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
} }
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
delay := h.blockDelay(rule)
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
timer := time.AfterFunc(delay, func() {
common.Interrupt(input)
common.Interrupt(output)
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
})
defer timer.Stop()
defer common.Close(output)
_ = buf.Copy(input, buf.Discard)
return nil
}
func isValidAddress(addr *net.IPOrDomain) bool { func isValidAddress(addr *net.IPOrDomain) bool {
if addr == nil { if addr == nil {
return false return false
@@ -295,61 +273,53 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
var conn stat.Connection var conn stat.Connection
var blockedDest *net.Destination var blockedDest *net.Destination
var blockedRule *FinalRule var blockedRule *FinalRule
firstResolve := true
err := retry.ExponentialBackoff(5, 100).On(func() error { err := retry.ExponentialBackoff(5, 100).On(func() error {
dialDest := destination if destination.Address.Family().IsDomain() {
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() { if defaultRule != nil || len(h.finalRules) > 0 {
strategy := h.config.DomainStrategy if strategy := h.resolveStrategy; strategy.HasStrategy() {
if destination.Network == net.Network_UDP && origTargetAddr != nil && outGateway == nil { ips, err := internet.LookupForIP(destination.Address.Domain(), strategy, outGateway)
strategy = strategy.GetDynamicStrategy(origTargetAddr.Family()) if err != nil { // non-force may still dial with system DNS
} errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", destination.Address.Domain())
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway) if strategy.ForceIP() {
if err != nil { return err // retry
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain()) }
if h.config.DomainStrategy.ForceIP() || h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { }
return err for _, ip := range ips {
if addr := net.IPAddress(ip); addr != nil {
if rule := h.matchFinalRule(destination.Network, addr, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &destination
blockedDest.Address = addr
blockedRule = rule
return nil
}
}
}
} else {
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, destination.Address.Domain())
if err != nil { // dialer may retry DNS
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", destination.Address.Domain())
}
for _, addr := range addrs {
if ipAddr := net.IPAddress(addr.IP); ipAddr != nil {
if rule := h.matchFinalRule(destination.Network, ipAddr, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &destination
blockedDest.Address = ipAddr
blockedRule = rule
return nil
}
}
}
} }
} else {
dialDest = net.Destination{
Network: dialDest.Network,
Address: net.IPAddress(ips[dice.Roll(len(ips))]),
Port: dialDest.Port,
}
errors.LogInfo(ctx, "dialing to ", dialDest)
} }
} else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules } else {
domain := dialDest.Address.Domain() if rule := h.matchFinalRule(destination.Network, destination.Address, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
var ips []net.IP blockedDest = &destination
if firstResolve { blockedRule = rule
firstResolve = false return nil
supportIPv4, supportIPv6 := utils.CheckRoutes()
if supportIPv4 {
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip4", domain)
}
if len(ips) == 0 && supportIPv6 {
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip6", domain)
}
if len(ips) == 0 {
return errors.New("failed to get IP address for domain ", domain)
}
} else {
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip", domain)
} }
if len(ips) == 0 { // SRV/TXT, lookup failed
return errors.New("failed to get IP address for domain ", domain)
}
if addr := net.IPAddress(ips[dice.Roll(len(ips))]); addr != nil {
dialDest.Address = addr
errors.LogInfo(ctx, "dialing to ", dialDest)
}
}
if rule := h.matchFinalRule(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &dialDest
blockedRule = rule
return nil
} }
rawConn, err := dialer.Dial(ctx, dialDest) rawConn, err := dialer.Dial(ctx, destination)
if err != nil { if err != nil {
return err return err
} }
@@ -361,20 +331,21 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return errors.New("failed to open connection to ", destination).Base(err) return errors.New("failed to open connection to ", destination).Base(err)
} }
if blockedDest != nil { if blockedDest != nil {
delay := h.blockDelay(blockedRule) return h.blackhole(ctx, input, output, blockedRule, blockedDest)
errors.LogInfo(ctx, "blocked target: ", *blockedDest, ", blackholing connection for ", delay)
timer := time.AfterFunc(delay, func() {
common.Interrupt(input)
common.Interrupt(output)
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *blockedDest)
})
defer timer.Stop()
defer common.Close(output)
if err := buf.Copy(input, buf.Discard); err != nil {
return nil
}
return nil
} }
if destination.Address.Family().IsDomain() && (defaultRule != nil || len(h.finalRules) > 0) {
if h.usesDialerProxy {
errors.LogInfo(ctx, "skipping final rule check for proxied remote endpoint, original target: ", destination)
} else {
// pre-check may fail or dialer may select another IP
remoteDest := net.DestinationFromAddr(conn.RemoteAddr())
if rule := h.matchFinalRule(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
conn.Close()
return h.blackhole(ctx, input, output, rule, &remoteDest)
}
}
}
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 { if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
version := byte(h.config.ProxyProtocol) version := byte(h.config.ProxyProtocol)
srcAddr := inbound.Source.RawNetAddr() srcAddr := inbound.Source.RawNetAddr()
@@ -419,7 +390,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
writer = buf.NewWriter(conn) writer = buf.NewWriter(conn)
} }
} else { } else {
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination) writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination, outGateway)
if h.config.Noises != nil { if h.config.Noises != nil {
errors.LogDebug(ctx, "NOISE", h.config.Noises) errors.LogDebug(ctx, "NOISE", h.config.Noises)
writer = &NoisePacketWriter{ writer = &NoisePacketWriter{
@@ -523,7 +494,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
} }
udpAddr := d.(*net.UDPAddr) udpAddr := d.(*net.UDPAddr)
sourceAddr := net.IPAddress(udpAddr.IP) sourceAddr := net.IPAddress(udpAddr.IP)
if r.Handler.applyFinalRules(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule) == RuleAction_Block { if rule := r.Handler.matchFinalRule(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule); rule != nil && rule.action == RuleAction_Block {
continue continue
} }
b.Resize(0, int32(n)) b.Resize(0, int32(n))
@@ -548,7 +519,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
} }
// DialDest means the dial target used in the dialer when creating conn // DialDest means the dial target used in the dialer when creating conn
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Writer { func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination, outGateway net.Address) buf.Writer {
iConn := conn iConn := conn
statConn, ok := iConn.(*stat.CounterConnection) statConn, ok := iConn.(*stat.CounterConnection)
if ok { if ok {
@@ -572,9 +543,8 @@ func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverr
DefaultRule: defaultRule, DefaultRule: defaultRule,
UDPOverride: UDPOverride, UDPOverride: UDPOverride,
ResolvedUDPAddr: resolvedUDPAddr, ResolvedUDPAddr: resolvedUDPAddr,
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address, OutGateway: outGateway,
} }
} }
return &buf.SequentialWriter{Writer: conn} return &buf.SequentialWriter{Writer: conn}
} }
@@ -591,7 +561,7 @@ type PacketWriter struct {
// Resulting in these packets being sent to many different IPs randomly // Resulting in these packets being sent to many different IPs randomly
// So, cache and keep the resolve result // So, cache and keep the resolve result
ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address] ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address]
LocalAddr net.Address OutGateway net.Address
} }
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
@@ -614,21 +584,21 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok { if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
b.UDP.Address = ip b.UDP.Address = ip
} else { } else {
ShouldUseSystemResolver := true shouldUseSystemResolver := true
if w.Handler.config.DomainStrategy.HasStrategy() { if strategy := w.Handler.resolveStrategy; strategy.HasStrategy() {
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), w.Handler.config.DomainStrategy, w.LocalAddr) ips, err := internet.LookupForIP(b.UDP.Address.Domain(), strategy, w.OutGateway)
if err != nil { if err != nil {
// drop packet if resolve failed when forceIP // drop packet if resolve failed when forceIP
if w.Handler.config.DomainStrategy.ForceIP() { if strategy.ForceIP() {
b.Release() b.Release()
continue continue
} }
} else { } else {
ip = net.IPAddress(ips[dice.Roll(len(ips))]) ip = net.IPAddress(ips[dice.Roll(len(ips))])
ShouldUseSystemResolver = false shouldUseSystemResolver = false
} }
} }
if ShouldUseSystemResolver { if shouldUseSystemResolver {
udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr()) udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
if err != nil { if err != nil {
b.Release() b.Release()
@@ -642,7 +612,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
} }
} }
} }
if w.applyFinalRules(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule) == RuleAction_Block { if rule := w.matchFinalRule(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule); rule != nil && rule.action == RuleAction_Block {
b.Release() b.Release()
continue continue
} }
+94 -79
View File
@@ -3,25 +3,32 @@ package account
import ( import (
"sync" "sync"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/google/uuid"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
func (a *Account) AsAccount() (protocol.Account, error) { func (a *Account) AsAccount() (protocol.Account, error) {
var VR net.Port
if id, err := uuid.Parse(a.Auth); err == nil {
VR = net.PortFromBytes(id[6:8])
}
return &MemoryAccount{ return &MemoryAccount{
Auth: a.Auth, Auth: a.Auth,
VR: VR,
}, nil }, nil
} }
type MemoryAccount struct { type MemoryAccount struct {
Auth string Auth string
VR net.Port
} }
func (a *MemoryAccount) Equals(another protocol.Account) bool { func (a *MemoryAccount) Equals(other protocol.Account) bool {
if account, ok := another.(*MemoryAccount); ok { if b, ok := other.(*MemoryAccount); ok {
return a.Auth == account.Auth return a.Auth == b.Auth
} }
return false return false
} }
@@ -33,97 +40,105 @@ func (a *MemoryAccount) ToProto() proto.Message {
} }
type Validator struct { type Validator struct {
emails map[string]struct{} users sync.Map
users map[string]*protocol.MemoryUser ids sync.Map
mu sync.Mutex
mutex sync.Mutex
} }
func NewValidator() *Validator { func NewValidator() *Validator {
return &Validator{ return &Validator{}
emails: make(map[string]struct{}),
users: make(map[string]*protocol.MemoryUser),
}
} }
func (v *Validator) Add(u *protocol.MemoryUser) error { func (v *Validator) Add(user *protocol.MemoryUser) (err error) {
v.mutex.Lock() v.mu.Lock()
defer v.mutex.Unlock() v.users.Store(user.Account.(*MemoryAccount).Auth, user)
if id, err := uuid.Parse(user.Account.(*MemoryAccount).Auth); err == nil {
if u.Email != "" { id[6] = 0
if _, ok := v.emails[u.Email]; ok { id[7] = 0
return errors.New("User ", u.Email, " already exists.") v.ids.Store(id, user)
}
v.emails[u.Email] = struct{}{}
} }
v.users[u.Account.(*MemoryAccount).Auth] = u v.mu.Unlock()
return
return nil
} }
func (v *Validator) Del(email string) error { func (v *Validator) DelByEmail(email string) (err error) {
if email == "" { v.mu.Lock()
return errors.New("Email must not be empty.") if user := v.GetByEmail(email); user != nil {
} auth := user.Account.(*MemoryAccount).Auth
v.users.Delete(auth)
v.mutex.Lock() if id, err := uuid.Parse(auth); err == nil {
defer v.mutex.Unlock() id[6] = 0
id[7] = 0
if _, ok := v.emails[email]; !ok { v.ids.Delete(id)
return errors.New("User ", email, " not found.")
}
delete(v.emails, email)
for key, user := range v.users {
if user.Email == email {
delete(v.users, key)
break
} }
} }
v.mu.Unlock()
return nil return
} }
func (v *Validator) Get(auth string) *protocol.MemoryUser { func (v *Validator) Get(auth string) (user *protocol.MemoryUser) {
v.mutex.Lock() if id, err := uuid.Parse(auth); err == nil {
defer v.mutex.Unlock() if user = v.GetByID(id); user != nil {
VR := net.PortFromBytes(id[6:8])
return v.users[auth] if user.Account.(*MemoryAccount).VR != VR {
} user = &protocol.MemoryUser{
Email: user.Email,
func (v *Validator) GetByEmail(email string) *protocol.MemoryUser { Level: user.Level,
if email == "" { Account: &MemoryAccount{
return nil Auth: auth,
} VR: VR,
},
v.mutex.Lock() }
defer v.mutex.Unlock()
if _, ok := v.emails[email]; ok {
for _, user := range v.users {
if user.Email == email {
return user
} }
} }
return
} }
if value, ok := v.users.Load(auth); ok {
return nil user = value.(*protocol.MemoryUser)
}
func (v *Validator) GetAll() []*protocol.MemoryUser {
v.mutex.Lock()
defer v.mutex.Unlock()
users := make([]*protocol.MemoryUser, 0, len(v.users))
for _, user := range v.users {
users = append(users, user)
} }
return
return users
} }
func (v *Validator) GetCount() int64 { func (v *Validator) GetByID(id uuid.UUID) (user *protocol.MemoryUser) {
v.mutex.Lock() id[6] = 0
defer v.mutex.Unlock() id[7] = 0
if value, ok := v.ids.Load(id); ok {
return int64(len(v.users)) user = value.(*protocol.MemoryUser)
}
return
}
func (v *Validator) GetByEmail(email string) (user *protocol.MemoryUser) {
v.users.Range(func(key, value any) bool {
if value.(*protocol.MemoryUser).Email == email {
user = value.(*protocol.MemoryUser)
return false
}
return true
})
return
}
func (v *Validator) GetAll() (users []*protocol.MemoryUser) {
v.users.Range(func(key, value any) bool {
users = append(users, value.(*protocol.MemoryUser))
return true
})
return
}
func (v *Validator) GetCount() (count int64) {
v.users.Range(func(key, value any) bool {
count++
return true
})
return
}
func (v *Validator) NotEmpty() (not_empty bool) {
v.users.Range(func(key, value any) bool {
not_empty = true
return false
})
return
} }
+4 -13
View File
@@ -24,8 +24,7 @@ const (
type ClientConfig struct { type ClientConfig struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
Server *protocol.ServerEndpoint `protobuf:"bytes,2,opt,name=server,proto3" json:"server,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -60,13 +59,6 @@ func (*ClientConfig) Descriptor() ([]byte, []int) {
return file_proxy_hysteria_config_proto_rawDescGZIP(), []int{0} return file_proxy_hysteria_config_proto_rawDescGZIP(), []int{0}
} }
func (x *ClientConfig) GetVersion() int32 {
if x != nil {
return x.Version
}
return 0
}
func (x *ClientConfig) GetServer() *protocol.ServerEndpoint { func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
if x != nil { if x != nil {
return x.Server return x.Server
@@ -122,10 +114,9 @@ var File_proxy_hysteria_config_proto protoreflect.FileDescriptor
const file_proxy_hysteria_config_proto_rawDesc = "" + const file_proxy_hysteria_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\x1a\x1acommon/protocol/user.proto\"f\n" + "\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\x1a\x1acommon/protocol/user.proto\"L\n" +
"\fClientConfig\x12\x18\n" + "\fClientConfig\x12<\n" +
"\aversion\x18\x01 \x01(\x05R\aversion\x12<\n" + "\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"@\n" +
"\x06server\x18\x02 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"@\n" +
"\fServerConfig\x120\n" + "\fServerConfig\x120\n" +
"\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05usersB[\n" + "\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05usersB[\n" +
"\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3" "\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3"
+1 -2
View File
@@ -10,8 +10,7 @@ import "common/protocol/server_spec.proto";
import "common/protocol/user.proto"; import "common/protocol/user.proto";
message ClientConfig { message ClientConfig {
int32 version = 1; xray.common.protocol.ServerEndpoint server = 1;
xray.common.protocol.ServerEndpoint server = 2;
} }
message ServerConfig { message ServerConfig {
+10 -7
View File
@@ -59,12 +59,12 @@ func (s *Server) HysteriaInboundValidator() *account.Validator {
return s.validator return s.validator
} }
func (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error { func (s *Server) AddUser(ctx context.Context, user *protocol.MemoryUser) error {
return s.validator.Add(u) return s.validator.Add(user)
} }
func (s *Server) RemoveUser(ctx context.Context, e string) error { func (s *Server) RemoveUser(ctx context.Context, email string) error {
return s.validator.Del(e) return s.validator.DelByEmail(email)
} }
func (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser { func (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser {
@@ -91,9 +91,12 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
iConn := stat.TryUnwrapStatsConn(conn) iConn := stat.TryUnwrapStatsConn(conn)
type User interface{ User() *protocol.MemoryUser } if v, ok := iConn.(interface{ User() *protocol.MemoryUser }); ok {
if v, ok := iConn.(User); ok && v.User() != nil { user := v.User()
inbound.User = v.User() if user != nil {
inbound.User = user
inbound.VlessRoute = user.Account.(*account.MemoryAccount).VR
}
} }
if _, ok := iConn.(*hysteria.InterConn); ok { if _, ok := iConn.(*hysteria.InterConn); ok {
-26
View File
@@ -85,8 +85,6 @@ func (a *Account) getCipher() (Cipher, error) {
IVBytes: 32, IVBytes: 32,
AEADAuthCreator: createXChaCha20Poly1305, AEADAuthCreator: createXChaCha20Poly1305,
}, nil }, nil
case CipherType_NONE:
return NoneCipher{}, nil
default: default:
return nil, errors.New("Unsupported cipher.") return nil, errors.New("Unsupported cipher.")
} }
@@ -186,30 +184,6 @@ func (c *AEADCipher) DecodePacket(key []byte, b *buf.Buffer) error {
return nil return nil
} }
type NoneCipher struct{}
func (NoneCipher) KeySize() int32 { return 0 }
func (NoneCipher) IVSize() int32 { return 0 }
func (NoneCipher) IsAEAD() bool {
return false
}
func (NoneCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) {
return buf.NewReader(reader), nil
}
func (NoneCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) {
return buf.NewWriter(writer), nil
}
func (NoneCipher) EncodePacket(key []byte, b *buf.Buffer) error {
return nil
}
func (NoneCipher) DecodePacket(key []byte, b *buf.Buffer) error {
return nil
}
func passwordToCipherKey(password []byte, keySize int32) []byte { func passwordToCipherKey(password []byte, keySize int32) []byte {
key := make([]byte, 0, keySize) key := make([]byte, 0, keySize)
+2 -6
View File
@@ -31,7 +31,6 @@ const (
CipherType_AES_256_GCM CipherType = 6 CipherType_AES_256_GCM CipherType = 6
CipherType_CHACHA20_POLY1305 CipherType = 7 CipherType_CHACHA20_POLY1305 CipherType = 7
CipherType_XCHACHA20_POLY1305 CipherType = 8 CipherType_XCHACHA20_POLY1305 CipherType = 8
CipherType_NONE CipherType = 9
) )
// Enum value maps for CipherType. // Enum value maps for CipherType.
@@ -42,7 +41,6 @@ var (
6: "AES_256_GCM", 6: "AES_256_GCM",
7: "CHACHA20_POLY1305", 7: "CHACHA20_POLY1305",
8: "XCHACHA20_POLY1305", 8: "XCHACHA20_POLY1305",
9: "NONE",
} }
CipherType_value = map[string]int32{ CipherType_value = map[string]int32{
"UNKNOWN": 0, "UNKNOWN": 0,
@@ -50,7 +48,6 @@ var (
"AES_256_GCM": 6, "AES_256_GCM": 6,
"CHACHA20_POLY1305": 7, "CHACHA20_POLY1305": 7,
"XCHACHA20_POLY1305": 8, "XCHACHA20_POLY1305": 8,
"NONE": 9,
} }
) )
@@ -251,15 +248,14 @@ const file_proxy_shadowsocks_config_proto_rawDesc = "" +
"\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x122\n" + "\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x122\n" +
"\anetwork\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\anetwork\"L\n" + "\anetwork\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\anetwork\"L\n" +
"\fClientConfig\x12<\n" + "\fClientConfig\x12<\n" +
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server*t\n" + "\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server*j\n" +
"\n" + "\n" +
"CipherType\x12\v\n" + "CipherType\x12\v\n" +
"\aUNKNOWN\x10\x00\x12\x0f\n" + "\aUNKNOWN\x10\x00\x12\x0f\n" +
"\vAES_128_GCM\x10\x05\x12\x0f\n" + "\vAES_128_GCM\x10\x05\x12\x0f\n" +
"\vAES_256_GCM\x10\x06\x12\x15\n" + "\vAES_256_GCM\x10\x06\x12\x15\n" +
"\x11CHACHA20_POLY1305\x10\a\x12\x16\n" + "\x11CHACHA20_POLY1305\x10\a\x12\x16\n" +
"\x12XCHACHA20_POLY1305\x10\b\x12\b\n" + "\x12XCHACHA20_POLY1305\x10\bBd\n" +
"\x04NONE\x10\tBd\n" +
"\x1acom.xray.proxy.shadowsocksP\x01Z+github.com/xtls/xray-core/proxy/shadowsocks\xaa\x02\x16Xray.Proxy.Shadowsocksb\x06proto3" "\x1acom.xray.proxy.shadowsocksP\x01Z+github.com/xtls/xray-core/proxy/shadowsocks\xaa\x02\x16Xray.Proxy.Shadowsocksb\x06proto3"
var ( var (
-1
View File
@@ -23,7 +23,6 @@ enum CipherType {
AES_256_GCM = 6; AES_256_GCM = 6;
CHACHA20_POLY1305 = 7; CHACHA20_POLY1305 = 7;
XCHACHA20_POLY1305 = 8; XCHACHA20_POLY1305 = 8;
NONE = 9;
} }
message ServerConfig { message ServerConfig {
-17
View File
@@ -38,19 +38,6 @@ func TestUDPEncodingDecoding(t *testing.T) {
}), }),
}, },
}, },
{
Version: Version,
Command: protocol.RequestCommandUDP,
Address: net.LocalHostIP,
Port: 1234,
User: &protocol.MemoryUser{
Email: "love@example.com",
Account: toAccount(&Account{
Password: "123",
CipherType: CipherType_NONE,
}),
},
},
} }
for _, request := range testRequests { for _, request := range testRequests {
@@ -80,10 +67,6 @@ func TestUDPDecodingWithPayloadTooShort(t *testing.T) {
Password: "password", Password: "password",
CipherType: CipherType_AES_128_GCM, CipherType: CipherType_AES_128_GCM,
}), }),
toAccount(&Account{
Password: "password",
CipherType: CipherType_NONE,
}),
} }
for _, account := range testAccounts { for _, account := range testAccounts {
-1
View File
@@ -145,7 +145,6 @@ func (v *Validator) Get(bs []byte, command protocol.RequestCommand) (u *protocol
} else { } else {
u = user u = user
ivLen = user.Account.(*MemoryAccount).Cipher.IVSize() ivLen = user.Account.(*MemoryAccount).Cipher.IVSize()
// err = user.Account.(*MemoryAccount).CheckIV(bs[:ivLen]) // The IV size of None Cipher is 0.
return return
} }
} }
+12 -5
View File
@@ -14,11 +14,12 @@ Plainly enabling it in the config probably will result nothing, or lock your rou
## DETAILS ## DETAILS
Current implementation does not contain options to configure network level addresses, routing or rules. By default, enabling the feature will only bring the tun interface up. \
Enabling the feature will result only tun interface up, and that's it. \ When configured explicitly, Windows and Linux can apply interface addresses from `gateway`, while macOS uses the first IPv4 prefix from `gateway` to configure the utun point-to-point address. \
This is explicit decision, significantly simplifying implementation, and allowing any number of custom configurations, consumers could come up with. Network interface is OS level entity, and OS is what should manage it. \ Windows, Linux and macOS can also apply system routes from `autoSystemRoutingTable`.
Working configuration, is tun enabled in Xray config with specific name (e.g. xray0), and OS level configuration to manage "xray0" interface, applying routing and rules on interface up. Linux and macOS do not configure system DNS from the `dns` field; system DNS remains managed by the OS or distribution-specific network services. \
This way consistency of system level routing and rules is ensured from single place of responsibility - the OS itself. \ For more advanced routing policies or rules, OS level configuration can still manage the named interface (e.g. xray0) when it appears.
This keeps complex system level routing and rules in a single place of responsibility - the OS itself. \
Examples of how to achieve this on a simple Linux system (Ubuntu with systemd-networkd) can be found at the end of this README. Examples of how to achieve this on a simple Linux system (Ubuntu with systemd-networkd) can be found at the end of this README.
Due to this inbound not actually being a proxy, the configuration ignore required listen and port options, and never listen on any port. \ Due to this inbound not actually being a proxy, the configuration ignore required listen and port options, and never listen on any port. \
@@ -206,6 +207,11 @@ ifconfig
Produced list will have all system interfaces listed, from which you will see how many "utun" ones already exists. Produced list will have all system interfaces listed, from which you will see how many "utun" ones already exists.
It's not required to select next available number, e.g. if you have utun1-utun7 interfaces, it's not required to have "utun8" in the config. You can choose any available name, even utun20, to get surely available interface number. It's not required to select next available number, e.g. if you have utun1-utun7 interfaces, it's not required to have "utun8" in the config. You can choose any available name, even utun20, to get surely available interface number.
macOS requires the utun interface to have a point-to-point IPv4 address before IPv4 routes can use it. \
By default Xray uses `169.254.10.1/30` as the remote gateway address and assigns the next address in the prefix to the local utun side. \
You can override this by setting `gateway`; macOS uses the first IPv4 prefix in the list. IPv6 `gateway` entries are not used for utun addressing, and IPv6 routes use the interface route instead. \
The `dns` field does not change macOS system DNS.
To attach routing to the interface, route command like following can be executed: To attach routing to the interface, route command like following can be executed:
``` ```
sudo route add -net 1.1.1.0/24 -iface utun10 sudo route add -net 1.1.1.0/24 -iface utun10
@@ -214,6 +220,7 @@ sudo route add -net 1.1.1.0/24 -iface utun10
sudo route add -inet6 -host 2606:4700:4700::1111 -iface utun10 sudo route add -inet6 -host 2606:4700:4700::1111 -iface utun10
sudo route add -inet6 -host 2606:4700:4700::1001 -iface utun10 sudo route add -inet6 -host 2606:4700:4700::1001 -iface utun10
``` ```
Alternatively, configure `autoSystemRoutingTable` and Xray will add and remove those system routes while it is running.
Important to remember that everything written above about Linux routing concept, also apply to Mac OS X. If you simply route default route through utun interface, that will result network loop and immediate network failure. Important to remember that everything written above about Linux routing concept, also apply to Mac OS X. If you simply route default route through utun interface, that will result network loop and immediate network failure.
## ANDROID SUPPORT ## ANDROID SUPPORT
+7 -81
View File
@@ -3,8 +3,6 @@ package tun
import ( import (
"context" "context"
"net" "net"
"sort"
"strings"
"sync" "sync"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -31,95 +29,23 @@ func (updater *InterfaceUpdater) Update() {
updater.Lock() updater.Lock()
defer updater.Unlock() defer updater.Unlock()
if updater.iface != nil { got, err := findOutboundInterface(updater.tunIndex, updater.fixedName)
iface, err := net.InterfaceByIndex(updater.iface.Index)
if err == nil && iface.Name == updater.iface.Name {
return
}
}
updater.iface = nil
interfaces, err := net.Interfaces()
if err != nil { if err != nil {
errors.LogInfoInner(context.Background(), err, "[tun] failed to update interface") errors.LogInfoInner(context.Background(), err, "[tun] failed to update interface")
updater.iface = nil
return return
} }
var got *net.Interface
if updater.fixedName != "" {
for _, iface := range interfaces {
if iface.Index == updater.tunIndex {
continue
}
if iface.Name == updater.fixedName {
got = &iface
break
}
}
} else {
var ifs []struct {
index int
score int
}
for i, iface := range interfaces {
if iface.Index == updater.tunIndex {
continue
}
if strings.Contains(iface.Name, "vEthernet") {
continue
}
if iface.Flags&net.FlagUp == 0 {
continue
}
if iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil || len(addrs) == 0 {
continue
}
ifs = append(ifs, struct {
index int
score int
}{i, score(&iface, addrs)})
}
sort.Slice(ifs, func(i, j int) bool {
if ifs[i].score != ifs[j].score {
return ifs[i].score > ifs[j].score
}
return interfaces[ifs[i].index].Name < interfaces[ifs[j].index].Name
})
if len(ifs) > 0 {
iface := interfaces[ifs[0].index]
got = &iface
}
}
if got == nil { if got == nil {
errors.LogInfo(context.Background(), "[tun] failed to update interface > got == nil") errors.LogInfo(context.Background(), "[tun] failed to update interface > got == nil")
updater.iface = nil
return
}
if updater.iface != nil && updater.iface.Index == got.Index && updater.iface.Name == got.Name {
return return
} }
updater.iface = got updater.iface = got
errors.LogInfo(context.Background(), "[tun] update interface ", got.Name, " ", got.Index) errors.LogInfo(context.Background(), "[tun] update interface ", got.Name, " ", got.Index)
} }
func score(iface *net.Interface, addrs []net.Addr) int {
score := 0
name := strings.ToLower(iface.Name)
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
score += 2
}
for _, addr := range addrs {
if strings.HasPrefix(addr.String(), "192.168.") {
score += 1
break
}
}
return score
}
+36 -1
View File
@@ -17,6 +17,7 @@ import (
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
@@ -32,6 +33,8 @@ type Handler struct {
dispatcher routing.Dispatcher dispatcher routing.Dispatcher
tag string tag string
sniffingRequest session.SniffingRequest sniffingRequest session.SniffingRequest
uplinkCounter stats.Counter
downlinkCounter stats.Counter
} }
// ConnectionHandler interface with the only method that stack is going to push new connections to // ConnectionHandler interface with the only method that stack is going to push new connections to
@@ -59,6 +62,23 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin
t.policyManager = pm t.policyManager = pm
t.dispatcher = dispatcher t.dispatcher = dispatcher
if len(t.tag) > 0 && pm.ForSystem().Stats.InboundUplink {
statsManager := core.MustFromContext(ctx).GetFeature(stats.ManagerType()).(stats.Manager)
name := "inbound>>>" + t.tag + ">>>traffic>>>uplink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
if c != nil {
t.uplinkCounter = c
}
}
if len(t.tag) > 0 && pm.ForSystem().Stats.InboundDownlink {
statsManager := core.MustFromContext(ctx).GetFeature(stats.ManagerType()).(stats.Manager)
name := "inbound>>>" + t.tag + ">>>traffic>>>downlink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
if c != nil {
t.downlinkCounter = c
}
}
return nil return nil
} }
@@ -143,7 +163,22 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
defer cancel() defer cancel()
ctx = c.ContextWithID(ctx, session.NewID()) ctx = c.ContextWithID(ctx, session.NewID())
source := net.DestinationFromAddr(conn.RemoteAddr()) // if the connection is already closed, conn.RemoteAddr() will be nil
// due to gvisor weird behavior
remote := conn.RemoteAddr()
if remote == nil {
errors.LogInfo(t.ctx, "dropped quickly closed connection")
return
}
source := net.DestinationFromAddr(remote)
if t.uplinkCounter != nil || t.downlinkCounter != nil {
conn = &stat.CounterConnection{
Connection: conn,
ReadCounter: t.uplinkCounter,
WriteCounter: t.downlinkCounter,
}
}
inbound := session.Inbound{ inbound := session.Inbound{
Name: "tun", Name: "tun",
Tag: t.tag, Tag: t.tag,
+133
View File
@@ -0,0 +1,133 @@
package tun
import (
"bytes"
"context"
"net"
"sync/atomic"
"testing"
"time"
"github.com/xtls/xray-core/common/buf"
xnet "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport"
)
type testCounter struct {
value int64
}
func (c *testCounter) Value() int64 {
return atomic.LoadInt64(&c.value)
}
func (c *testCounter) Set(value int64) int64 {
return atomic.SwapInt64(&c.value, value)
}
func (c *testCounter) Add(value int64) int64 {
return atomic.AddInt64(&c.value, value) - value
}
type testConn struct {
reader *bytes.Reader
writer bytes.Buffer
}
func newTestConn(input []byte) *testConn {
return &testConn{reader: bytes.NewReader(input)}
}
func (c *testConn) Read(payload []byte) (int, error) {
return c.reader.Read(payload)
}
func (c *testConn) Write(payload []byte) (int, error) {
return c.writer.Write(payload)
}
func (c *testConn) Close() error {
return nil
}
func (c *testConn) LocalAddr() net.Addr {
return &net.TCPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 1080}
}
func (c *testConn) RemoteAddr() net.Addr {
return &net.TCPAddr{IP: net.IPv4(10, 0, 0, 2), Port: 12345}
}
func (c *testConn) SetDeadline(time.Time) error {
return nil
}
func (c *testConn) SetReadDeadline(time.Time) error {
return nil
}
func (c *testConn) SetWriteDeadline(time.Time) error {
return nil
}
type testDispatcher struct {
writePayload []byte
readBytes int32
}
func (d *testDispatcher) Type() interface{} {
return routing.DispatcherType()
}
func (d *testDispatcher) Start() error {
return nil
}
func (d *testDispatcher) Close() error {
return nil
}
func (d *testDispatcher) Dispatch(context.Context, xnet.Destination) (*transport.Link, error) {
return nil, nil
}
func (d *testDispatcher) DispatchLink(ctx context.Context, dest xnet.Destination, link *transport.Link) error {
mb, err := link.Reader.ReadMultiBuffer()
if err != nil {
return err
}
atomic.StoreInt32(&d.readBytes, mb.Len())
buf.ReleaseMulti(mb)
return link.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(d.writePayload)})
}
func TestHandlerCountsTunConnectionTraffic(t *testing.T) {
uplinkCounter := new(testCounter)
downlinkCounter := new(testCounter)
dispatcher := &testDispatcher{writePayload: []byte("downlink")}
conn := newTestConn([]byte("uplink"))
handler := &Handler{
ctx: context.Background(),
config: &Config{},
dispatcher: dispatcher,
uplinkCounter: uplinkCounter,
downlinkCounter: downlinkCounter,
}
handler.HandleConnection(conn, xnet.TCPDestination(xnet.LocalHostIP, 443))
if got := uplinkCounter.Value(); got != int64(len("uplink")) {
t.Fatalf("unexpected uplink counter: got %d, want %d", got, len("uplink"))
}
if got := downlinkCounter.Value(); got != int64(len("downlink")) {
t.Fatalf("unexpected downlink counter: got %d, want %d", got, len("downlink"))
}
if got := int(atomic.LoadInt32(&dispatcher.readBytes)); got != len("uplink") {
t.Fatalf("dispatcher read unexpected bytes: got %d, want %d", got, len("uplink"))
}
if got := conn.writer.String(); got != "downlink" {
t.Fatalf("connection write mismatch: got %q, want %q", got, "downlink")
}
}
+14
View File
@@ -81,3 +81,17 @@ func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
func setinterface(network, address string, fd uintptr, iface *net.Interface) error { func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
return unix.BindToDevice(int(fd), iface.Name) return unix.BindToDevice(int(fd), iface.Name)
} }
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
if fixedName == "" {
return nil, errors.New("automatic outbound interface selection is not supported on this platform")
}
iface, err := net.InterfaceByName(fixedName)
if err != nil {
return nil, err
}
if iface.Index == tunIndex {
return nil, errors.New("outbound interface cannot be the TUN interface")
}
return iface, nil
}
+327 -15
View File
@@ -3,16 +3,20 @@
package tun package tun
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"os" "os"
"strconv" "strconv"
"sync"
"unsafe" "unsafe"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
xerrors "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
"golang.org/x/net/route"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
@@ -20,11 +24,11 @@ import (
) )
const ( const (
utunControlName = "com.apple.net.utun_control" utunControlName = "com.apple.net.utun_control"
sysprotoControl = 2 sysprotoControl = 2
gateway = "169.254.10.1/30" defaultDarwinGateway = "169.254.10.1/30"
utunHeaderSize = 4 utunHeaderSize = 4
UTUN_OPT_IFNAME = 2 UTUN_OPT_IFNAME = 2
) )
const ( const (
@@ -42,6 +46,11 @@ type DarwinTun struct {
options *Config options *Config
tunFd int tunFd int
ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system) ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system)
routeMonitor *os.File
routeMonitorOnce sync.Once
systemRoutes []netip.Prefix
gateway netip.Prefix
} }
var ( var (
@@ -77,7 +86,13 @@ func NewTun(options *Config) (Tun, error) {
return nil, err return nil, err
} }
err = setup(options.Name, options.MTU) gateway, err := selectDarwinGateway(options.Gateway)
if err != nil {
_ = tunFile.Close()
return nil, err
}
err = setup(options.Name, options.MTU, gateway)
if err != nil { if err != nil {
_ = tunFile.Close() _ = tunFile.Close()
return nil, err return nil, err
@@ -88,19 +103,58 @@ func NewTun(options *Config) (Tun, error) {
options: options, options: options,
tunFd: int(tunFile.Fd()), tunFd: int(tunFile.Fd()),
ownsFd: true, ownsFd: true,
gateway: gateway,
}, nil }, nil
} }
func (t *DarwinTun) Start() error { func (t *DarwinTun) Start() error {
if !t.ownsFd {
return nil
}
if err := t.setSystemRoutes(); err != nil {
return err
}
if updater != nil {
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
if err != nil {
_ = t.unsetSystemRoutes()
return err
}
t.routeMonitor = os.NewFile(uintptr(fd), "xray-route-monitor")
go t.monitorRouteChanges()
}
return nil return nil
} }
func (t *DarwinTun) Close() error { func (t *DarwinTun) Close() error {
t.routeMonitorOnce.Do(func() {
if t.routeMonitor != nil {
_ = t.routeMonitor.Close()
}
})
routeErr := t.unsetSystemRoutes()
if t.ownsFd { if t.ownsFd {
return t.tunFile.Close() return xerrors.Combine(routeErr, t.tunFile.Close())
} }
// iOS: don't close the fd, it's owned by NetworkExtension // iOS: don't close the fd, it's owned by NetworkExtension
return nil return routeErr
}
func (t *DarwinTun) monitorRouteChanges() {
buffer := make([]byte, 64*1024)
for {
if _, err := t.routeMonitor.Read(buffer); err != nil {
if !errors.Is(err, os.ErrClosed) {
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to monitor route changes")
}
return
}
if updater != nil {
updater.Update()
}
}
} }
func (t *DarwinTun) Name() (string, error) { func (t *DarwinTun) Name() (string, error) {
@@ -235,24 +289,56 @@ func open(name string) (*os.File, error) {
} }
// setup the interface by name // setup the interface by name
func setup(name string, MTU uint32) error { func setup(name string, MTU uint32, gateway netip.Prefix) error {
if err := setMTU(name, MTU); err != nil { if err := setMTU(name, MTU); err != nil {
return err return err
} }
/* /*
* Darwin routing require tunnel type interface to have local and remote address, to be routable. * Darwin routing require tunnel type interface to have local and remote address, to be routable.
* To simplify inevitable task, assign the interface static ip address, which in current implementation * To simplify inevitable task, assign the interface static ip address.
* is just some random ip from link-local pool, allowing to not bother about existing routing intersection.
*/ */
syntheticIP, _ := netip.ParsePrefix(gateway) if err := setIPAddress(name, gateway); err != nil {
if err := setIPAddress(name, syntheticIP); err != nil {
return err return err
} }
return nil return nil
} }
func selectDarwinGateway(configured []string) (netip.Prefix, error) {
if len(configured) == 0 {
return netip.ParsePrefix(defaultDarwinGateway)
}
for _, value := range configured {
prefix, err := netip.ParsePrefix(value)
if err != nil {
return netip.Prefix{}, xerrors.New("invalid macOS gateway ", value).Base(err)
}
if !prefix.Addr().Is4() {
continue
}
local, ok := nextDarwinLocalIPv4(prefix)
if !ok || !prefix.Contains(local) {
return netip.Prefix{}, xerrors.New("macOS gateway ", value, " must contain at least one usable local IPv4 address after the gateway address")
}
return prefix, nil
}
return netip.Prefix{}, xerrors.New("macOS gateway requires at least one IPv4 prefix")
}
func nextDarwinLocalIPv4(gateway netip.Prefix) (netip.Addr, bool) {
local4 := gateway.Addr().As4()
for i := len(local4) - 1; i >= 0; i-- {
local4[i]++
if local4[i] != 0 {
return netip.AddrFrom4(local4), true
}
}
return netip.Addr{}, false
}
// setMTU sets MTU on the interface by given name // setMTU sets MTU on the interface by given name
func setMTU(name string, mtu uint32) error { func setMTU(name string, mtu uint32) error {
socket, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) socket, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
@@ -298,8 +384,11 @@ func setIPAddress(name string, gateway netip.Prefix) error {
defer unix.Close(socket4) defer unix.Close(socket4)
// assume local ip address is next one from the remote address // assume local ip address is next one from the remote address
local4 := gateway.Addr().As4() local, ok := nextDarwinLocalIPv4(gateway)
local4[3]++ if !ok || !gateway.Contains(local) {
return xerrors.New("macOS gateway ", gateway.String(), " must contain at least one usable local IPv4 address after the gateway address")
}
local4 := local.As4()
// fill the configuration for ipv4 // fill the configuration for ipv4
ifReq4 := ifAliasReq4{ ifReq4 := ifAliasReq4{
@@ -388,3 +477,226 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
return errors.Join(err1, err2) return errors.Join(err1, err2)
} }
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
if fixedName != "" {
iface, err := net.InterfaceByName(fixedName)
if err != nil {
return nil, err
}
if iface.Index == tunIndex {
return nil, errors.New("outbound interface cannot be the TUN interface")
}
return iface, nil
}
rib, err := route.FetchRIB(unix.AF_UNSPEC, route.RIBTypeRoute, 0)
if err != nil {
return nil, err
}
messages, err := route.ParseRIB(route.RIBTypeRoute, rib)
if err != nil {
return nil, err
}
var ipv6Index int
for _, message := range messages {
routeMessage, ok := message.(*route.RouteMessage)
if !ok || routeMessage.Index == tunIndex {
continue
}
if routeMessage.Flags&unix.RTF_UP == 0 || routeMessage.Flags&unix.RTF_GATEWAY == 0 {
continue
}
family, ok := defaultRouteFamily(routeMessage)
if !ok {
continue
}
if family == unix.AF_INET {
return usableDarwinInterface(routeMessage.Index)
}
if family == unix.AF_INET6 && ipv6Index == 0 {
ipv6Index = routeMessage.Index
}
}
if ipv6Index != 0 {
return usableDarwinInterface(ipv6Index)
}
return nil, errors.New("default route not found")
}
func defaultRouteFamily(message *route.RouteMessage) (int, bool) {
if len(message.Addrs) <= unix.RTAX_NETMASK {
return 0, false
}
switch destination := message.Addrs[unix.RTAX_DST].(type) {
case *route.Inet4Addr:
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr)
if !ok || destination.IP != netip.IPv4Unspecified().As4() {
return 0, false
}
ones, bits := net.IPMask(mask.IP[:]).Size()
return unix.AF_INET, ones == 0 && bits == 32
case *route.Inet6Addr:
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet6Addr)
if !ok || destination.IP != netip.IPv6Unspecified().As16() {
return 0, false
}
ones, bits := net.IPMask(mask.IP[:]).Size()
return unix.AF_INET6, ones == 0 && bits == 128
default:
return 0, false
}
}
func usableDarwinInterface(index int) (*net.Interface, error) {
iface, err := net.InterfaceByIndex(index)
if err != nil {
return nil, err
}
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
return nil, errors.New("default route interface is not usable")
}
return iface, nil
}
func (t *DarwinTun) setSystemRoutes() error {
routes, err := buildDarwinSystemRoutes(t.options.AutoSystemRoutingTable)
if err != nil {
return err
}
if len(routes) == 0 {
return nil
}
tunIndex, err := t.Index()
if err != nil {
return err
}
for _, destination := range routes {
if err := execDarwinRoute(unix.RTM_ADD, tunIndex, destination, t.gateway); err != nil {
_ = t.unsetSystemRoutes()
return xerrors.New("failed to add system route ", destination).Base(err)
}
t.systemRoutes = append(t.systemRoutes, destination)
}
return nil
}
func (t *DarwinTun) unsetSystemRoutes() error {
var errs []error
tunIndex, indexErr := t.Index()
if indexErr != nil && len(t.systemRoutes) > 0 {
errs = append(errs, indexErr)
}
for i := len(t.systemRoutes) - 1; i >= 0; i-- {
destination := t.systemRoutes[i]
if err := execDarwinRoute(unix.RTM_DELETE, tunIndex, destination, t.gateway); err != nil && !errors.Is(err, unix.ESRCH) {
errs = append(errs, xerrors.New("failed to delete system route ", destination).Base(err))
}
}
t.systemRoutes = nil
return xerrors.Combine(errs...)
}
func buildDarwinSystemRoutes(configured []string) ([]netip.Prefix, error) {
routes := make([]netip.Prefix, 0, len(configured))
seen := make(map[netip.Prefix]struct{})
appendRoute := func(prefix netip.Prefix) {
prefix = prefix.Masked()
if _, found := seen[prefix]; found {
return
}
seen[prefix] = struct{}{}
routes = append(routes, prefix)
}
for _, value := range configured {
prefix, err := netip.ParsePrefix(value)
if err != nil {
return nil, xerrors.New("invalid system route ", value).Base(err)
}
prefix = prefix.Masked()
if prefix.Bits() == 0 {
for _, protected := range darwinProtectedDefaultRoutes(prefix.Addr().Is4()) {
appendRoute(protected)
}
continue
}
appendRoute(prefix)
}
return routes, nil
}
func darwinProtectedDefaultRoutes(ipv4 bool) []netip.Prefix {
routes := make([]netip.Prefix, 0, 8)
for i := 0; i < 8; i++ {
if ipv4 {
var address [4]byte
address[0] = 1 << i
routes = append(routes, netip.PrefixFrom(netip.AddrFrom4(address), 8-i))
} else {
var address [16]byte
address[0] = 1 << i
routes = append(routes, netip.PrefixFrom(netip.AddrFrom16(address), 8-i))
}
}
return routes
}
func execDarwinRoute(messageType int, interfaceIndex int, destination netip.Prefix, gateway netip.Prefix) error {
message := route.RouteMessage{
Type: messageType,
Version: unix.RTM_VERSION,
Flags: unix.RTF_STATIC | unix.RTF_GATEWAY,
Seq: 1,
}
if messageType == unix.RTM_ADD {
message.Flags |= unix.RTF_UP
}
if destination.Addr().Is4() {
message.Addrs = []route.Addr{
unix.RTAX_DST: &route.Inet4Addr{IP: destination.Addr().As4()},
unix.RTAX_NETMASK: &route.Inet4Addr{IP: prefixMask4(destination.Bits())},
unix.RTAX_GATEWAY: &route.Inet4Addr{IP: gateway.Addr().As4()},
}
} else {
message.Flags &^= unix.RTF_GATEWAY
message.Index = interfaceIndex
message.Addrs = []route.Addr{
unix.RTAX_DST: &route.Inet6Addr{IP: destination.Addr().As16()},
unix.RTAX_NETMASK: &route.Inet6Addr{IP: prefixMask6(destination.Bits())},
unix.RTAX_GATEWAY: &route.LinkAddr{Index: interfaceIndex},
}
}
request, err := message.Marshal()
if err != nil {
return err
}
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
if err != nil {
return err
}
defer unix.Close(fd)
_, err = unix.Write(fd, request)
return err
}
func prefixMask4(bits int) [4]byte {
var mask [4]byte
copy(mask[:], net.CIDRMask(bits, 32))
return mask
}
func prefixMask6(bits int) [16]byte {
var mask [16]byte
copy(mask[:], net.CIDRMask(bits, 128))
return mask
}
+49
View File
@@ -0,0 +1,49 @@
//go:build darwin
package tun
import (
"testing"
)
func TestSelectDarwinGatewayDefault(t *testing.T) {
gateway, err := selectDarwinGateway(nil)
if err != nil {
t.Fatal(err)
}
if got := gateway.String(); got != defaultDarwinGateway {
t.Fatalf("unexpected default gateway: got %s, want %s", got, defaultDarwinGateway)
}
}
func TestSelectDarwinGatewayConfiguredIPv4(t *testing.T) {
gateway, err := selectDarwinGateway([]string{"198.18.0.1/15"})
if err != nil {
t.Fatal(err)
}
if got := gateway.String(); got != "198.18.0.1/15" {
t.Fatalf("unexpected gateway: got %s", got)
}
}
func TestSelectDarwinGatewaySkipsIPv6(t *testing.T) {
gateway, err := selectDarwinGateway([]string{"fc00::1/64", "198.18.0.1/15"})
if err != nil {
t.Fatal(err)
}
if got := gateway.String(); got != "198.18.0.1/15" {
t.Fatalf("unexpected gateway: got %s", got)
}
}
func TestSelectDarwinGatewayRequiresIPv4(t *testing.T) {
if _, err := selectDarwinGateway([]string{"fc00::1/64"}); err == nil {
t.Fatal("expected error")
}
}
func TestSelectDarwinGatewayRequiresUsableLocalAddress(t *testing.T) {
if _, err := selectDarwinGateway([]string{"198.18.0.1/32"}); err == nil {
t.Fatal("expected error")
}
}
+14
View File
@@ -42,3 +42,17 @@ func (t *DefaultTun) newEndpoint() (stack.LinkEndpoint, error) {
func setinterface(string, string, uintptr, *net.Interface) error { func setinterface(string, string, uintptr, *net.Interface) error {
return errors.New("Tun is not supported on your platform") return errors.New("Tun is not supported on your platform")
} }
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
if fixedName == "" {
return nil, errors.New("automatic outbound interface selection is not supported on this platform")
}
iface, err := net.InterfaceByName(fixedName)
if err != nil {
return nil, err
}
if iface.Index == tunIndex {
return nil, errors.New("outbound interface cannot be the TUN interface")
}
return iface, nil
}
+14
View File
@@ -147,3 +147,17 @@ func (t *FreeBSDTun) newEndpoint() (stack.LinkEndpoint, error) {
func setinterface(network, address string, fd uintptr, iface *net.Interface) error { func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
return nil return nil
} }
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
if fixedName == "" {
return nil, errors.New("automatic outbound interface selection is not supported on this platform")
}
iface, err := net.InterfaceByName(fixedName)
if err != nil {
return nil, err
}
if iface.Index == tunIndex {
return nil, errors.New("outbound interface cannot be the TUN interface")
}
return iface, nil
}
+207 -2
View File
@@ -3,8 +3,11 @@
package tun package tun
import ( import (
"context"
"net" "net"
"net/netip"
"strconv" "strconv"
"sync"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -22,6 +25,11 @@ type LinuxTun struct {
tunLink netlink.Link tunLink netlink.Link
options *Config options *Config
ownsTun bool ownsTun bool
interfaceAddresses []netlink.Addr
systemRoutes []netlink.Route
routeMonitorStop chan struct{}
routeMonitorOnce sync.Once
} }
// LinuxTun implements Tun // LinuxTun implements Tun
@@ -161,16 +169,40 @@ func (t *LinuxTun) Start() error {
return nil return nil
} }
err := netlink.LinkSetUp(t.tunLink) if err := netlink.LinkSetUp(t.tunLink); err != nil {
if err != nil {
return err return err
} }
if err := t.setInterfaceAddresses(); err != nil {
_ = netlink.LinkSetDown(t.tunLink)
return err
}
if err := t.setSystemRoutes(); err != nil {
_ = t.unsetInterfaceAddresses()
_ = netlink.LinkSetDown(t.tunLink)
return err
}
if updater != nil {
t.routeMonitorStop = make(chan struct{})
go t.monitorRouteChanges()
}
return nil return nil
} }
// Close is called to shut down the tun interface // Close is called to shut down the tun interface
func (t *LinuxTun) Close() error { func (t *LinuxTun) Close() error {
t.routeMonitorOnce.Do(func() {
if t.routeMonitorStop != nil {
close(t.routeMonitorStop)
}
})
_ = t.unsetSystemRoutes()
_ = t.unsetInterfaceAddresses()
if t.ownsTun { if t.ownsTun {
_ = netlink.LinkSetDown(t.tunLink) _ = netlink.LinkSetDown(t.tunLink)
} }
@@ -199,3 +231,176 @@ func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
func setinterface(network, address string, fd uintptr, iface *net.Interface) error { func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
return unix.BindToDevice(int(fd), iface.Name) return unix.BindToDevice(int(fd), iface.Name)
} }
func (t *LinuxTun) setInterfaceAddresses() error {
if len(t.options.Gateway) == 0 {
return nil
}
for _, address := range t.options.Gateway {
addr, err := netlink.ParseAddr(address)
if err != nil {
_ = t.unsetInterfaceAddresses()
return errors.New("invalid interface address ", address).Base(err)
}
if err := netlink.AddrAdd(t.tunLink, addr); err != nil {
_ = t.unsetInterfaceAddresses()
return errors.New("failed to add interface address ", address).Base(err)
}
t.interfaceAddresses = append(t.interfaceAddresses, *addr)
}
return nil
}
func (t *LinuxTun) unsetInterfaceAddresses() error {
var errs []error
for i := len(t.interfaceAddresses) - 1; i >= 0; i-- {
address := t.interfaceAddresses[i]
if err := netlink.AddrDel(t.tunLink, &address); err != nil {
errs = append(errs, errors.New("failed to delete interface address ", address.String()).Base(err))
}
}
t.interfaceAddresses = nil
return errors.Combine(errs...)
}
func (t *LinuxTun) setSystemRoutes() error {
if len(t.options.AutoSystemRoutingTable) == 0 {
return nil
}
tunIndex := t.tunLink.Attrs().Index
for _, cidr := range t.options.AutoSystemRoutingTable {
prefix, err := netip.ParsePrefix(cidr)
if err != nil {
return errors.New("invalid system route ", cidr).Base(err)
}
prefix = prefix.Masked()
_, ipNet, _ := net.ParseCIDR(prefix.String())
route := netlink.Route{
LinkIndex: tunIndex,
Dst: ipNet,
Priority: 1,
}
if err := netlink.RouteAdd(&route); err != nil {
_ = t.unsetSystemRoutes()
return errors.New("failed to add system route ", cidr).Base(err)
}
t.systemRoutes = append(t.systemRoutes, route)
}
return nil
}
func (t *LinuxTun) unsetSystemRoutes() error {
var errs []error
for i := len(t.systemRoutes) - 1; i >= 0; i-- {
route := t.systemRoutes[i]
if err := netlink.RouteDel(&route); err != nil {
errs = append(errs, errors.New("failed to delete system route").Base(err))
}
}
t.systemRoutes = nil
return errors.Combine(errs...)
}
func (t *LinuxTun) monitorRouteChanges() {
routeCh := make(chan netlink.RouteUpdate)
if err := netlink.RouteSubscribe(routeCh, t.routeMonitorStop); err != nil {
errors.LogInfoInner(context.Background(), err, "[tun] failed to subscribe route changes")
return
}
linkCh := make(chan netlink.LinkUpdate)
if err := netlink.LinkSubscribe(linkCh, t.routeMonitorStop); err != nil {
errors.LogInfoInner(context.Background(), err, "[tun] failed to subscribe link changes")
return
}
for {
select {
case _, ok := <-routeCh:
if !ok {
return
}
if updater != nil {
updater.Update()
}
case _, ok := <-linkCh:
if !ok {
return
}
if updater != nil {
updater.Update()
}
case <-t.routeMonitorStop:
return
}
}
}
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
if fixedName != "" {
iface, err := net.InterfaceByName(fixedName)
if err != nil {
return nil, err
}
if iface.Index == tunIndex {
return nil, errors.New("outbound interface cannot be the TUN interface")
}
return iface, nil
}
for _, family := range []int{
netlink.FAMILY_V4,
netlink.FAMILY_V6,
} {
iface, err := findDefaultInterface(family, tunIndex)
if err == nil {
return iface, nil
}
}
return nil, errors.New("no usable outbound interface found")
}
func findDefaultInterface(family int, tunIndex int) (*net.Interface, error) {
routes, err := netlink.RouteList(nil, family)
if err != nil {
return nil, err
}
var selected *net.Interface
selectedMetric := -1
for _, route := range routes {
if route.Dst != nil {
ones, _ := route.Dst.Mask.Size()
if ones != 0 {
continue
}
}
if route.LinkIndex == 0 || route.LinkIndex == tunIndex {
continue
}
iface, err := net.InterfaceByIndex(route.LinkIndex)
if err != nil {
continue
}
if iface.Flags&net.FlagUp == 0 ||
iface.Flags&net.FlagLoopback != 0 {
continue
}
if selected == nil || route.Priority < selectedMetric {
selected = iface
selectedMetric = route.Priority
}
}
if selected == nil {
return nil, errors.New("physical default route not found")
}
return selected, nil
}
+76
View File
@@ -8,6 +8,8 @@ import (
go_errors "errors" go_errors "errors"
"net" "net"
"net/netip" "net/netip"
"sort"
"strings"
"sync" "sync"
"unsafe" "unsafe"
@@ -307,3 +309,77 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
return errors.Combine(err1, err2, err3, err4) return errors.Combine(err1, err2, err3, err4)
} }
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
if fixedName != "" {
for _, iface := range interfaces {
if iface.Index != tunIndex && iface.Name == fixedName {
return &iface, nil
}
}
return nil, nil
}
var candidates []struct {
index int
score int
}
for i, iface := range interfaces {
if iface.Index == tunIndex {
continue
}
if strings.Contains(iface.Name, "vEthernet") {
continue
}
if iface.Flags&net.FlagUp == 0 {
continue
}
if iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil || len(addrs) == 0 {
continue
}
candidates = append(candidates, struct {
index int
score int
}{i, scoreWindowsInterface(&iface, addrs)})
}
sort.Slice(candidates, func(i, j int) bool {
if candidates[i].score != candidates[j].score {
return candidates[i].score > candidates[j].score
}
return interfaces[candidates[i].index].Name < interfaces[candidates[j].index].Name
})
if len(candidates) == 0 {
return nil, nil
}
iface := interfaces[candidates[0].index]
return &iface, nil
}
func scoreWindowsInterface(iface *net.Interface, addrs []net.Addr) int {
score := 0
name := strings.ToLower(iface.Name)
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
score += 2
}
for _, addr := range addrs {
if strings.HasPrefix(addr.String(), "192.168.") {
score++
break
}
}
return score
}
-21
View File
@@ -17,27 +17,6 @@ func Authenticate(b []byte) uint32 {
return fnv1hash.Sum32() return fnv1hash.Sum32()
} }
// [DEPRECATED 2023-06]
type NoOpAuthenticator struct{}
func (NoOpAuthenticator) NonceSize() int {
return 0
}
func (NoOpAuthenticator) Overhead() int {
return 0
}
// Seal implements AEAD.Seal().
func (NoOpAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
return append(dst[:0], plaintext...)
}
// Open implements AEAD.Open().
func (NoOpAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
return append(dst[:0], ciphertext...), nil
}
// GenerateChacha20Poly1305Key generates a 32-byte key from a given 16-byte array. // GenerateChacha20Poly1305Key generates a 32-byte key from a given 16-byte array.
func GenerateChacha20Poly1305Key(b []byte) []byte { func GenerateChacha20Poly1305Key(b []byte) []byte {
key := make([]byte, 32) key := make([]byte, 32)
-30
View File
@@ -116,20 +116,6 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write
} }
switch request.Security { switch request.Security {
case protocol.SecurityType_NONE:
if request.Option.Has(protocol.RequestOptionChunkStream) {
if request.Command.TransferType() == protocol.TransferTypeStream {
return crypto.NewChunkStreamWriter(sizeParser, writer), nil
}
auth := &crypto.AEADAuthenticator{
AEAD: new(NoOpAuthenticator),
NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
}
return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding), nil
}
return buf.NewWriter(writer), nil
case protocol.SecurityType_AES128_GCM: case protocol.SecurityType_AES128_GCM:
aead := crypto.NewAesGcm(c.requestBodyKey[:]) aead := crypto.NewAesGcm(c.requestBodyKey[:])
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
@@ -267,22 +253,6 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read
} }
switch request.Security { switch request.Security {
case protocol.SecurityType_NONE:
if request.Option.Has(protocol.RequestOptionChunkStream) {
if request.Command.TransferType() == protocol.TransferTypeStream {
return crypto.NewChunkStreamReader(sizeParser, reader), nil
}
auth := &crypto.AEADAuthenticator{
AEAD: new(NoOpAuthenticator),
NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
}
return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding), nil
}
return buf.NewReader(reader), nil
case protocol.SecurityType_AES128_GCM: case protocol.SecurityType_AES128_GCM:
aead := crypto.NewAesGcm(c.responseBodyKey[:]) aead := crypto.NewAesGcm(c.responseBodyKey[:])
-30
View File
@@ -262,21 +262,6 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade
} }
switch request.Security { switch request.Security {
case protocol.SecurityType_NONE:
if request.Option.Has(protocol.RequestOptionChunkStream) {
if request.Command.TransferType() == protocol.TransferTypeStream {
return crypto.NewChunkStreamReader(sizeParser, reader), nil
}
auth := &crypto.AEADAuthenticator{
AEAD: new(NoOpAuthenticator),
NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
}
return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding), nil
}
return buf.NewReader(reader), nil
case protocol.SecurityType_AES128_GCM: case protocol.SecurityType_AES128_GCM:
aead := crypto.NewAesGcm(s.requestBodyKey[:]) aead := crypto.NewAesGcm(s.requestBodyKey[:])
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
@@ -384,21 +369,6 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ
} }
switch request.Security { switch request.Security {
case protocol.SecurityType_NONE:
if request.Option.Has(protocol.RequestOptionChunkStream) {
if request.Command.TransferType() == protocol.TransferTypeStream {
return crypto.NewChunkStreamWriter(sizeParser, writer), nil
}
auth := &crypto.AEADAuthenticator{
AEAD: new(NoOpAuthenticator),
NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
}
return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding), nil
}
return buf.NewWriter(writer), nil
case protocol.SecurityType_AES128_GCM: case protocol.SecurityType_AES128_GCM:
aead := crypto.NewAesGcm(s.responseBodyKey[:]) aead := crypto.NewAesGcm(s.responseBodyKey[:])
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
+1 -7
View File
@@ -105,7 +105,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
account := request.User.Account.(*vmess.MemoryAccount) account := request.User.Account.(*vmess.MemoryAccount)
request.Security = account.Security request.Security = account.Security
if request.Security == protocol.SecurityType_AES128_GCM || request.Security == protocol.SecurityType_NONE || request.Security == protocol.SecurityType_CHACHA20_POLY1305 { if request.Security == protocol.SecurityType_AES128_GCM || request.Security == protocol.SecurityType_CHACHA20_POLY1305 {
request.Option.Set(protocol.RequestOptionChunkMasking) request.Option.Set(protocol.RequestOptionChunkMasking)
} }
@@ -113,12 +113,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
request.Option.Set(protocol.RequestOptionGlobalPadding) request.Option.Set(protocol.RequestOptionGlobalPadding)
} }
if request.Security == protocol.SecurityType_ZERO {
request.Security = protocol.SecurityType_NONE
request.Option.Clear(protocol.RequestOptionChunkStream)
request.Option.Clear(protocol.RequestOptionChunkMasking)
}
if account.AuthenticatedLengthExperiment { if account.AuthenticatedLengthExperiment {
request.Option.Set(protocol.RequestOptionAuthenticatedLength) request.Option.Set(protocol.RequestOptionAuthenticatedLength)
} }
+59
View File
@@ -1 +1,60 @@
package wireguard package wireguard
import (
"encoding/hex"
"net/netip"
"github.com/xtls/xray-core/common/protocol"
"google.golang.org/protobuf/proto"
)
func (p *PeerConfig) AsAccount() (protocol.Account, error) {
pub, err := ParseKey(p.PublicKey)
if err != nil {
return nil, err
}
allowedIPs := make([]netip.Prefix, 0, len(p.AllowedIps))
for i := range p.AllowedIps {
p, err := netip.ParsePrefix(p.AllowedIps[i])
if err != nil {
return nil, err
}
allowedIPs = append(allowedIPs, p)
}
return &MemoryAccount{
Pub: *pub,
AllowedIPs: allowedIPs,
PreSharedKey: p.PreSharedKey,
KeepAlive: p.KeepAlive,
}, nil
}
type MemoryAccount struct {
Pub [32]byte
AllowedIPs []netip.Prefix
PreSharedKey string
KeepAlive string
}
func (a *MemoryAccount) Equals(other protocol.Account) bool {
if b, ok := other.(*MemoryAccount); ok {
return a.Pub == b.Pub
}
return false
}
func (a *MemoryAccount) ToProto() proto.Message {
allowedIPs := make([]string, 0, len(a.AllowedIPs))
for i := range a.AllowedIPs {
allowedIPs = append(allowedIPs, a.AllowedIPs[i].String())
}
return &PeerConfig{
PublicKey: hex.EncodeToString(a.Pub[:]),
AllowedIps: allowedIPs,
PreSharedKey: a.PreSharedKey,
KeepAlive: a.KeepAlive,
}
}
+21 -9
View File
@@ -7,6 +7,7 @@
package wireguard package wireguard
import ( import (
protocol "github.com/xtls/xray-core/common/protocol"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
@@ -157,6 +158,7 @@ type DeviceConfig struct {
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"` SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"` Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"` Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"` Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"` Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"` DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"`
@@ -217,6 +219,13 @@ func (x *DeviceConfig) GetPeers() []*PeerConfig {
return nil return nil
} }
func (x *DeviceConfig) GetUsers() []*protocol.User {
if x != nil {
return x.Users
}
return nil
}
func (x *DeviceConfig) GetMtu() int32 { func (x *DeviceConfig) GetMtu() int32 {
if x != nil { if x != nil {
return x.Mtu return x.Mtu
@@ -256,7 +265,7 @@ var File_proxy_wireguard_config_proto protoreflect.FileDescriptor
const file_proxy_wireguard_config_proto_rawDesc = "" + const file_proxy_wireguard_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x1cproxy/wireguard/config.proto\x12\x14xray.proxy.wireguard\"\xad\x01\n" + "\x1cproxy/wireguard/config.proto\x12\x14xray.proxy.wireguard\x1a\x1acommon/protocol/user.proto\"\xad\x01\n" +
"\n" + "\n" +
"PeerConfig\x12\x1d\n" + "PeerConfig\x12\x1d\n" +
"\n" + "\n" +
@@ -266,12 +275,13 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\n" + "\n" +
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" + "keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
"\vallowed_ips\x18\x05 \x03(\tR\n" + "\vallowed_ips\x18\x05 \x03(\tR\n" +
"allowedIps\"\xaa\x03\n" + "allowedIps\"\xdc\x03\n" +
"\fDeviceConfig\x12\x1d\n" + "\fDeviceConfig\x12\x1d\n" +
"\n" + "\n" +
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" + "secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
"\bendpoint\x18\x02 \x03(\tR\bendpoint\x126\n" + "\bendpoint\x18\x02 \x03(\tR\bendpoint\x126\n" +
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x12\x10\n" + "\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" +
"\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" +
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" + "\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" + "\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" +
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" + "\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
@@ -305,15 +315,17 @@ var file_proxy_wireguard_config_proto_goTypes = []any{
(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy (DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy
(*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig (*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig
(*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig (*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig
(*protocol.User)(nil), // 3: xray.common.protocol.User
} }
var file_proxy_wireguard_config_proto_depIdxs = []int32{ var file_proxy_wireguard_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig 1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
0, // 1: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy 3, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
2, // [2:2] is the sub-list for method output_type 0, // 2: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy
2, // [2:2] is the sub-list for method input_type 3, // [3:3] is the sub-list for method output_type
2, // [2:2] is the sub-list for extension type_name 3, // [3:3] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension extendee 3, // [3:3] is the sub-list for extension type_name
0, // [0:2] is the sub-list for field type_name 3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
} }
func init() { file_proxy_wireguard_config_proto_init() } func init() { file_proxy_wireguard_config_proto_init() }
+3
View File
@@ -6,6 +6,8 @@ option go_package = "github.com/xtls/xray-core/proxy/wireguard";
option java_package = "com.xray.proxy.wireguard"; option java_package = "com.xray.proxy.wireguard";
option java_multiple_files = true; option java_multiple_files = true;
import "common/protocol/user.proto";
message PeerConfig { message PeerConfig {
string public_key = 1; string public_key = 1;
string pre_shared_key = 2; string pre_shared_key = 2;
@@ -25,6 +27,7 @@ message DeviceConfig {
string secret_key = 1; string secret_key = 1;
repeated string endpoint = 2; repeated string endpoint = 2;
repeated PeerConfig peers = 3; repeated PeerConfig peers = 3;
repeated xray.common.protocol.User users = 5;
int32 mtu = 4; int32 mtu = 4;
bytes reserved = 6; bytes reserved = 6;
+156 -16
View File
@@ -2,16 +2,20 @@ package wireguard
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"net/netip" "net/netip"
"reflect"
"strings" "strings"
"sync" "sync"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
c "github.com/xtls/xray-core/common/ctx" c "github.com/xtls/xray-core/common/ctx"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
@@ -20,6 +24,7 @@ import (
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"golang.org/x/crypto/curve25519"
"golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -42,6 +47,9 @@ type Server struct {
stack *stack.Stack stack *stack.Stack
dev *device.Device dev *device.Device
mu sync.Mutex mu sync.Mutex
pub [32]byte
users *sync.Map
} }
func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) { func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
@@ -72,15 +80,6 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
} }
} }
if len(conf.Peers) == 0 {
return nil, errors.New("empty peers")
}
for _, peer := range conf.Peers {
if peer.PublicKey == "" {
return nil, errors.New("peer without publickey")
}
}
localAddresses := make([]netip.Addr, 0, len(conf.Endpoint)) localAddresses := make([]netip.Addr, 0, len(conf.Endpoint))
for _, localaddress := range conf.Endpoint { for _, localaddress := range conf.Endpoint {
addr, err := netip.ParseAddr(localaddress) addr, err := netip.ParseAddr(localaddress)
@@ -101,6 +100,19 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
return nil, err return nil, err
} }
pri := common.Must2(ParseKey(conf.SecretKey))
var pub [32]byte
curve25519.ScalarBaseMult(&pub, pri)
users := &sync.Map{}
for _, u := range conf.Users {
user, err := u.ToMemoryUser()
if err != nil {
return nil, err
}
users.Store(user.Account.(*MemoryAccount).Pub, user)
}
return &Server{ return &Server{
conf: conf, conf: conf,
ctx: core.ToBackgroundDetachedContext(ctx), ctx: core.ToBackgroundDetachedContext(ctx),
@@ -116,9 +128,100 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
tun: tun, tun: tun,
stack: stack, stack: stack,
pub: pub,
users: users,
}, nil }, nil
} }
func (s *Server) AddUser(ctx context.Context, user *protocol.MemoryUser) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.dev == nil {
return errors.New("too early")
}
peer := user.Account.(*MemoryAccount)
if peer.Pub == s.pub {
return errors.New("invalid public key")
}
var sb strings.Builder
sb.WriteString("public_key=" + hex.EncodeToString(peer.Pub[:]) + "\n")
sb.WriteString("replace_allowed_ips=true\n")
for i := range peer.AllowedIPs {
sb.WriteString("allowed_ip=" + peer.AllowedIPs[i].String() + "\n")
}
if peer.PreSharedKey != "" {
sb.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
}
if peer.KeepAlive != "" {
sb.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
}
err := s.dev.IpcSet(sb.String())
if err != nil {
return err
}
s.users.Store(peer.Pub, user)
return nil
}
func (s *Server) RemoveUser(ctx context.Context, email string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.dev == nil {
return errors.New("too early")
}
if user := s.GetUser(ctx, email); user != nil {
peer := user.Account.(*MemoryAccount)
err := s.dev.IpcSet("public_key=" + hex.EncodeToString(peer.Pub[:]) + "\nremove=true\n")
if err != nil {
return err
}
s.users.Delete(peer.Pub)
}
return nil
}
func (s *Server) GetUser(ctx context.Context, email string) (user *protocol.MemoryUser) {
s.users.Range(func(key, value any) bool {
if value.(*protocol.MemoryUser).Email == email {
user = value.(*protocol.MemoryUser)
return false
}
return true
})
return
}
func (s *Server) GetUserByAddr(ctx context.Context, addr netip.Addr) (user *protocol.MemoryUser) {
s.users.Range(func(key, value any) bool {
peer := value.(*protocol.MemoryUser).Account.(*MemoryAccount)
for i := range peer.AllowedIPs {
if peer.AllowedIPs[i].Contains(addr) {
user = value.(*protocol.MemoryUser)
return false
}
}
return true
})
return
}
func (s *Server) GetUsers(ctx context.Context) (users []*protocol.MemoryUser) {
s.users.Range(func(key, value interface{}) bool {
users = append(users, value.(*protocol.MemoryUser))
return true
})
return
}
func (s *Server) GetUsersCount(context.Context) (count int64) {
s.users.Range(func(key, value interface{}) bool {
count++
return true
})
return
}
// Network implements proxy.Inbound.Network. // Network implements proxy.Inbound.Network.
func (*Server) Network() []net.Network { func (*Server) Network() []net.Network {
return []net.Network{} return []net.Network{}
@@ -196,18 +299,20 @@ func (s *Server) Start() error {
dev := device.NewDevice(s.tun, bind, logger) dev := device.NewDevice(s.tun, bind, logger)
var cfg strings.Builder var cfg strings.Builder
cfg.WriteString("private_key=" + s.conf.SecretKey + "\n") cfg.WriteString("private_key=" + s.conf.SecretKey + "\n")
for _, peer := range s.conf.Peers { s.users.Range(func(key, value any) bool {
cfg.WriteString("public_key=" + peer.PublicKey + "\n") peer := value.(*protocol.MemoryUser).Account.(*MemoryAccount)
cfg.WriteString("public_key=" + hex.EncodeToString(peer.Pub[:]) + "\n")
for i := range peer.AllowedIPs {
cfg.WriteString("allowed_ip=" + peer.AllowedIPs[i].String() + "\n")
}
if peer.PreSharedKey != "" { if peer.PreSharedKey != "" {
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n") cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
} }
for _, ip := range peer.AllowedIps {
cfg.WriteString("allowed_ip=" + ip + "\n")
}
if peer.KeepAlive != "" { if peer.KeepAlive != "" {
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n") cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
} }
} return true
})
err := dev.IpcSet(cfg.String()) err := dev.IpcSet(cfg.String())
if err != nil { if err != nil {
return err return err
@@ -227,12 +332,36 @@ func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
defer cancel() defer cancel()
ctx = c.ContextWithID(ctx, session.NewID()) ctx = c.ContextWithID(ctx, session.NewID())
source := net.DestinationFromAddr(conn.RemoteAddr()) remote := conn.RemoteAddr()
if remote == nil {
errors.LogError(context.Background(), "nil remote")
return
}
var addr netip.Addr
switch v := remote.(type) {
case *net.TCPAddr:
addr, _ = netip.AddrFromSlice(v.IP)
case *net.UDPAddr:
addr, _ = netip.AddrFromSlice(v.IP)
default:
errors.LogError(context.Background(), "invalid addr type ", reflect.TypeOf(v))
return
}
user := s.GetUserByAddr(context.TODO(), addr)
if user == nil {
errors.LogError(context.Background(), "nil user form ", remote, " to ", dest)
return
}
source := net.DestinationFromAddr(remote)
inbound := session.Inbound{ inbound := session.Inbound{
Name: "wireguard", Name: "wireguard",
Tag: s.tag, Tag: s.tag,
CanSpliceCopy: 3, CanSpliceCopy: 3,
Source: source, Source: source,
User: user,
} }
ctx = session.ContextWithInbound(ctx, &inbound) ctx = session.ContextWithInbound(ctx, &inbound)
@@ -257,3 +386,14 @@ func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
errors.LogError(ctx, errors.New("connection closed").Base(err)) errors.LogError(ctx, errors.New("connection closed").Base(err))
} }
} }
func ParseKey(str string) (*[32]byte, error) {
slice, err := hex.DecodeString(str)
if err != nil {
return nil, err
}
if len(slice) != 32 {
return nil, errors.New("len(slice) != 32")
}
return (*[32]byte)(slice), nil
}
+7 -2
View File
@@ -82,9 +82,14 @@ func TestResolveIP(t *testing.T) {
}, },
{ {
Tag: "direct", Tag: "direct",
ProxySettings: serial.ToTypedMessage(&freedom.Config{ SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
DomainStrategy: internet.DomainStrategy_USE_IP, StreamSettings: &internet.StreamConfig{
SocketSettings: &internet.SocketConfig{
DomainStrategy: internet.DomainStrategy_USE_IP,
},
},
}), }),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
}, },
}, },
} }
+9 -7
View File
@@ -92,7 +92,7 @@ func TestPassiveConnection(t *testing.T) {
} }
} }
func TestProxy(t *testing.T) { func TestDialerProxy(t *testing.T) {
tcpServer := tcp.Server{ tcpServer := tcp.Server{
MsgProcessor: xor, MsgProcessor: xor,
} }
@@ -187,8 +187,10 @@ func TestProxy(t *testing.T) {
}, },
}), }),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
ProxySettings: &internet.ProxyConfig{ StreamSettings: &internet.StreamConfig{
Tag: "proxy", SocketSettings: &internet.SocketConfig{
DialerProxy: "proxy",
},
}, },
}), }),
}, },
@@ -218,7 +220,7 @@ func TestProxy(t *testing.T) {
} }
} }
func TestProxyOverKCP(t *testing.T) { func TestDialerProxyOverKCP(t *testing.T) {
tcpServer := tcp.Server{ tcpServer := tcp.Server{
MsgProcessor: xor, MsgProcessor: xor,
} }
@@ -321,11 +323,11 @@ func TestProxyOverKCP(t *testing.T) {
}, },
}), }),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
ProxySettings: &internet.ProxyConfig{
Tag: "proxy",
},
StreamSettings: &internet.StreamConfig{ StreamSettings: &internet.StreamConfig{
ProtocolName: "mkcp", ProtocolName: "mkcp",
SocketSettings: &internet.SocketConfig{
DialerProxy: "proxy",
},
}, },
}), }),
}, },
-85
View File
@@ -390,88 +390,3 @@ func TestShadowsocksAES128GCMUDPMux(t *testing.T) {
t.Error(err) t.Error(err)
} }
} }
func TestShadowsocksNone(t *testing.T) {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
dest, err := tcpServer.Start()
common.Must(err)
defer tcpServer.Close()
account := serial.ToTypedMessage(&shadowsocks.Account{
Password: "shadowsocks-password",
CipherType: shadowsocks.CipherType_NONE,
})
serverPort := tcp.PickPort()
serverConfig := &core.Config{
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{
Users: []*protocol.User{{
Account: account,
Level: 1,
}},
Network: []net.Network{net.Network_TCP},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
}),
},
},
}
clientPort := tcp.PickPort()
clientConfig := &core.Config{
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
RewriteAddress: net.NewIPOrDomain(dest.Address),
RewritePort: uint32(dest.Port),
AllowedNetworks: []net.Network{net.Network_TCP},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
Account: account,
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
common.Must(err)
defer CloseAllServers(servers)
var errGroup errgroup.Group
for range 3 {
errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
}
if err := errGroup.Wait(); err != nil {
t.Fatal(err)
}
}
-194
View File
@@ -423,103 +423,6 @@ func TestVMessChacha20(t *testing.T) {
} }
} }
func TestVMessNone(t *testing.T) {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
dest, err := tcpServer.Start()
common.Must(err)
defer tcpServer.Close()
userID := protocol.NewID(uuid.New())
serverPort := tcp.PickPort()
serverConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogLevel: clog.Severity_Debug,
ErrorLogType: log.LogType_Console,
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&inbound.Config{
User: []*protocol.User{
{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
},
},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
}),
},
},
}
clientPort := tcp.PickPort()
clientConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogLevel: clog.Severity_Debug,
ErrorLogType: log.LogType_Console,
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
RewriteAddress: net.NewIPOrDomain(dest.Address),
RewritePort: uint32(dest.Port),
AllowedNetworks: []net.Network{net.Network_TCP},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&outbound.Config{
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
SecuritySettings: &protocol.SecurityConfig{
Type: protocol.SecurityType_NONE,
},
}),
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
common.Must(err)
defer CloseAllServers(servers)
var errg errgroup.Group
for range 3 {
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
}
if err := errg.Wait(); err != nil {
t.Error(err)
}
}
func TestVMessKCP(t *testing.T) { func TestVMessKCP(t *testing.T) {
tcpServer := tcp.Server{ tcpServer := tcp.Server{
MsgProcessor: xor, MsgProcessor: xor,
@@ -970,103 +873,6 @@ func TestVMessGCMMuxUDP(t *testing.T) {
}() }()
} }
func TestVMessZero(t *testing.T) {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
dest, err := tcpServer.Start()
common.Must(err)
defer tcpServer.Close()
userID := protocol.NewID(uuid.New())
serverPort := tcp.PickPort()
serverConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogLevel: clog.Severity_Debug,
ErrorLogType: log.LogType_Console,
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&inbound.Config{
User: []*protocol.User{
{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
},
},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
}),
},
},
}
clientPort := tcp.PickPort()
clientConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogLevel: clog.Severity_Debug,
ErrorLogType: log.LogType_Console,
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
RewriteAddress: net.NewIPOrDomain(dest.Address),
RewritePort: uint32(dest.Port),
AllowedNetworks: []net.Network{net.Network_TCP},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&outbound.Config{
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
SecuritySettings: &protocol.SecurityConfig{
Type: protocol.SecurityType_ZERO,
},
}),
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
common.Must(err)
defer CloseAllServers(servers)
var errg errgroup.Group
for range 3 {
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
}
if err := errg.Wait(); err != nil {
t.Error(err)
}
}
func TestVMessGCMLengthAuth(t *testing.T) { func TestVMessGCMLengthAuth(t *testing.T) {
tcpServer := tcp.Server{ tcpServer := tcp.Server{
MsgProcessor: xor, MsgProcessor: xor,
+1 -7
View File
@@ -10,7 +10,7 @@ type ConfigCreator func() interface{}
var globalTransportConfigCreatorCache = make(map[string]ConfigCreator) var globalTransportConfigCreatorCache = make(map[string]ConfigCreator)
var strategy = [][]byte{ var strategy = [11][3]byte{
// name strategy, prefer, fallback // name strategy, prefer, fallback
{0, 0, 0}, // AsIs none, /, / {0, 0, 0}, // AsIs none, /, /
{1, 0, 0}, // UseIP use, both, none {1, 0, 0}, // UseIP use, both, none
@@ -25,8 +25,6 @@ var strategy = [][]byte{
{2, 6, 4}, // ForceIPv6v4 force, 6, 4 {2, 6, 4}, // ForceIPv6v4 force, 6, 4
} }
const unknownProtocol = "unknown"
func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error { func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error {
if _, found := globalTransportConfigCreatorCache[name]; found { if _, found := globalTransportConfigCreatorCache[name]; found {
return errors.New("protocol ", name, " is already registered").AtError() return errors.New("protocol ", name, " is already registered").AtError()
@@ -91,10 +89,6 @@ func (c *StreamConfig) HasSecuritySettings() bool {
return len(c.SecuritySettings) > 0 return len(c.SecuritySettings) > 0
} }
func (c *ProxyConfig) HasTag() bool {
return c != nil && len(c.Tag) > 0
}
func (m SocketConfig_TProxyMode) IsEnabled() bool { func (m SocketConfig_TProxyMode) IsEnabled() bool {
return m != SocketConfig_Off return m != SocketConfig_Off
} }
+26 -82
View File
@@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead. // Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) { func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{6, 0} return file_transport_internet_config_proto_rawDescGZIP(), []int{5, 0}
} }
type TransportConfig struct { type TransportConfig struct {
@@ -582,58 +582,6 @@ func (x *QuicParams) GetMaxIncomingStreams() int64 {
return 0 return 0
} }
type ProxyConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
TransportLayerProxy bool `protobuf:"varint,2,opt,name=transportLayerProxy,proto3" json:"transportLayerProxy,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProxyConfig) Reset() {
*x = ProxyConfig{}
mi := &file_transport_internet_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProxyConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProxyConfig) ProtoMessage() {}
func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_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 ProxyConfig.ProtoReflect.Descriptor instead.
func (*ProxyConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
}
func (x *ProxyConfig) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
func (x *ProxyConfig) GetTransportLayerProxy() bool {
if x != nil {
return x.TransportLayerProxy
}
return false
}
type CustomSockopt struct { type CustomSockopt struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"` System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"`
@@ -648,7 +596,7 @@ type CustomSockopt struct {
func (x *CustomSockopt) Reset() { func (x *CustomSockopt) Reset() {
*x = CustomSockopt{} *x = CustomSockopt{}
mi := &file_transport_internet_config_proto_msgTypes[5] mi := &file_transport_internet_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -660,7 +608,7 @@ func (x *CustomSockopt) String() string {
func (*CustomSockopt) ProtoMessage() {} func (*CustomSockopt) ProtoMessage() {}
func (x *CustomSockopt) ProtoReflect() protoreflect.Message { func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[5] mi := &file_transport_internet_config_proto_msgTypes[4]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -673,7 +621,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead. // Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
func (*CustomSockopt) Descriptor() ([]byte, []int) { func (*CustomSockopt) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{5} return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
} }
func (x *CustomSockopt) GetSystem() string { func (x *CustomSockopt) GetSystem() string {
@@ -753,7 +701,7 @@ type SocketConfig struct {
func (x *SocketConfig) Reset() { func (x *SocketConfig) Reset() {
*x = SocketConfig{} *x = SocketConfig{}
mi := &file_transport_internet_config_proto_msgTypes[6] mi := &file_transport_internet_config_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -765,7 +713,7 @@ func (x *SocketConfig) String() string {
func (*SocketConfig) ProtoMessage() {} func (*SocketConfig) ProtoMessage() {}
func (x *SocketConfig) ProtoReflect() protoreflect.Message { func (x *SocketConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[6] mi := &file_transport_internet_config_proto_msgTypes[5]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -778,7 +726,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead. // Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
func (*SocketConfig) Descriptor() ([]byte, []int) { func (*SocketConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{6} return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
} }
func (x *SocketConfig) GetMark() int32 { func (x *SocketConfig) GetMark() int32 {
@@ -940,7 +888,7 @@ type HappyEyeballsConfig struct {
func (x *HappyEyeballsConfig) Reset() { func (x *HappyEyeballsConfig) Reset() {
*x = HappyEyeballsConfig{} *x = HappyEyeballsConfig{}
mi := &file_transport_internet_config_proto_msgTypes[7] mi := &file_transport_internet_config_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -952,7 +900,7 @@ func (x *HappyEyeballsConfig) String() string {
func (*HappyEyeballsConfig) ProtoMessage() {} func (*HappyEyeballsConfig) ProtoMessage() {}
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message { func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[7] mi := &file_transport_internet_config_proto_msgTypes[6]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -965,7 +913,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead. // Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) { func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{7} return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
} }
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool { func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
@@ -1040,10 +988,7 @@ const file_transport_internet_config_proto_rawDesc = "" +
" \x01(\x03R\x0emaxIdleTimeout\x12*\n" + " \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x11keep_alive_period\x18\v \x01(\x03R\x0fkeepAlivePeriod\x12;\n" + "\x11keep_alive_period\x18\v \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x1adisable_path_mtu_discovery\x18\f \x01(\bR\x17disablePathMtuDiscovery\x120\n" + "\x1adisable_path_mtu_discovery\x18\f \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
"\x14max_incoming_streams\x18\r \x01(\x03R\x12maxIncomingStreams\"Q\n" + "\x14max_incoming_streams\x18\r \x01(\x03R\x12maxIncomingStreams\"\x93\x01\n" +
"\vProxyConfig\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
"\rCustomSockopt\x12\x16\n" + "\rCustomSockopt\x12\x16\n" +
"\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" + "\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" +
"\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" + "\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" +
@@ -1127,7 +1072,7 @@ 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_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_transport_internet_config_proto_goTypes = []any{ var file_transport_internet_config_proto_goTypes = []any{
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy (DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy (AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
@@ -1136,28 +1081,27 @@ var file_transport_internet_config_proto_goTypes = []any{
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig (*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
(*UdpHop)(nil), // 5: xray.transport.internet.UdpHop (*UdpHop)(nil), // 5: xray.transport.internet.UdpHop
(*QuicParams)(nil), // 6: xray.transport.internet.QuicParams (*QuicParams)(nil), // 6: xray.transport.internet.QuicParams
(*ProxyConfig)(nil), // 7: xray.transport.internet.ProxyConfig (*CustomSockopt)(nil), // 7: xray.transport.internet.CustomSockopt
(*CustomSockopt)(nil), // 8: xray.transport.internet.CustomSockopt (*SocketConfig)(nil), // 8: xray.transport.internet.SocketConfig
(*SocketConfig)(nil), // 9: xray.transport.internet.SocketConfig (*HappyEyeballsConfig)(nil), // 9: xray.transport.internet.HappyEyeballsConfig
(*HappyEyeballsConfig)(nil), // 10: xray.transport.internet.HappyEyeballsConfig (*serial.TypedMessage)(nil), // 10: xray.common.serial.TypedMessage
(*serial.TypedMessage)(nil), // 11: xray.common.serial.TypedMessage (*net.IPOrDomain)(nil), // 11: xray.common.net.IPOrDomain
(*net.IPOrDomain)(nil), // 12: xray.common.net.IPOrDomain
} }
var file_transport_internet_config_proto_depIdxs = []int32{ var file_transport_internet_config_proto_depIdxs = []int32{
11, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage 10, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
12, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain 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 3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
11, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage 10, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
11, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage 10, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
11, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage 10, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
6, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams 6, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
9, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig 8, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
5, // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop 5, // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop
2, // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode 2, // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
0, // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy 0, // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
8, // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt 7, // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
1, // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy 1, // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
10, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig 9, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
14, // [14:14] is the sub-list for method output_type 14, // [14:14] is the sub-list for method output_type
14, // [14:14] is the sub-list for method input_type 14, // [14:14] is the sub-list for method input_type
14, // [14:14] is the sub-list for extension type_name 14, // [14:14] is the sub-list for extension type_name
@@ -1176,7 +1120,7 @@ func file_transport_internet_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
NumEnums: 3, NumEnums: 3,
NumMessages: 8, NumMessages: 7,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
-5
View File
@@ -86,11 +86,6 @@ message QuicParams {
int64 max_incoming_streams = 13; int64 max_incoming_streams = 13;
} }
message ProxyConfig {
string tag = 1;
bool transportLayerProxy = 2;
}
message CustomSockopt { message CustomSockopt {
string system = 1; string system = 1;
string network = 2; string network = 2;
@@ -372,7 +372,6 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
{ {
ProtocolName: "hysteria", ProtocolName: "hysteria",
Settings: serial.ToTypedMessage(&hytransport.Config{ Settings: serial.ToTypedMessage(&hytransport.Config{
Version: 2,
Auth: auth, Auth: auth,
UdpIdleTimeout: 60, UdpIdleTimeout: 60,
}), }),
@@ -421,7 +420,6 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
Outbound: []*core.OutboundHandlerConfig{ Outbound: []*core.OutboundHandlerConfig{
{ {
ProxySettings: serial.ToTypedMessage(&hyproxy.ClientConfig{ ProxySettings: serial.ToTypedMessage(&hyproxy.ClientConfig{
Version: 2,
Server: &protocol.ServerEndpoint{ Server: &protocol.ServerEndpoint{
Address: xnet.NewIPOrDomain(xnet.LocalHostIP), Address: xnet.NewIPOrDomain(xnet.LocalHostIP),
Port: uint32(relayPort), Port: uint32(relayPort),
@@ -437,7 +435,6 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
{ {
ProtocolName: "hysteria", ProtocolName: "hysteria",
Settings: serial.ToTypedMessage(&hytransport.Config{ Settings: serial.ToTypedMessage(&hytransport.Config{
Version: 2,
Auth: auth, Auth: auth,
UdpIdleTimeout: 60, UdpIdleTimeout: 60,
}), }),
+3 -12
View File
@@ -23,7 +23,6 @@ const (
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
Auth string `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"` Auth string `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"`
UdpIdleTimeout int64 `protobuf:"varint,3,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3" json:"udp_idle_timeout,omitempty"` UdpIdleTimeout int64 `protobuf:"varint,3,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3" json:"udp_idle_timeout,omitempty"`
MasqType string `protobuf:"bytes,4,opt,name=masq_type,json=masqType,proto3" json:"masq_type,omitempty"` MasqType string `protobuf:"bytes,4,opt,name=masq_type,json=masqType,proto3" json:"masq_type,omitempty"`
@@ -68,13 +67,6 @@ func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_hysteria_config_proto_rawDescGZIP(), []int{0} return file_transport_internet_hysteria_config_proto_rawDescGZIP(), []int{0}
} }
func (x *Config) GetVersion() int32 {
if x != nil {
return x.Version
}
return 0
}
func (x *Config) GetAuth() string { func (x *Config) GetAuth() string {
if x != nil { if x != nil {
return x.Auth return x.Auth
@@ -149,9 +141,8 @@ var File_transport_internet_hysteria_config_proto protoreflect.FileDescriptor
const file_transport_internet_hysteria_config_proto_rawDesc = "" + const file_transport_internet_hysteria_config_proto_rawDesc = "" +
"\n" + "\n" +
"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xa3\x04\n" + "(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\x8f\x04\n" +
"\x06Config\x12\x18\n" + "\x06Config\x12\x12\n" +
"\aversion\x18\x01 \x01(\x05R\aversion\x12\x12\n" +
"\x04auth\x18\x02 \x01(\tR\x04auth\x12(\n" + "\x04auth\x18\x02 \x01(\tR\x04auth\x12(\n" +
"\x10udp_idle_timeout\x18\x03 \x01(\x03R\x0eudpIdleTimeout\x12\x1b\n" + "\x10udp_idle_timeout\x18\x03 \x01(\x03R\x0eudpIdleTimeout\x12\x1b\n" +
"\tmasq_type\x18\x04 \x01(\tR\bmasqType\x12\x1b\n" + "\tmasq_type\x18\x04 \x01(\tR\bmasqType\x12\x1b\n" +
@@ -166,7 +157,7 @@ const file_transport_internet_hysteria_config_proto_rawDesc = "" +
"\x17masq_string_status_code\x18\v \x01(\x05R\x14masqStringStatusCode\x1aD\n" + "\x17masq_string_status_code\x18\v \x01(\x05R\x14masqStringStatusCode\x1aD\n" +
"\x16MasqStringHeadersEntry\x12\x10\n" + "\x16MasqStringHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x82\x01\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02B\x82\x01\n" +
"$com.xray.transport.internet.hysteriaP\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\xaa\x02 Xray.Transport.Internet.Hysteriab\x06proto3" "$com.xray.transport.internet.hysteriaP\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\xaa\x02 Xray.Transport.Internet.Hysteriab\x06proto3"
var ( var (
+1 -1
View File
@@ -7,7 +7,7 @@ option java_package = "com.xray.transport.internet.hysteria";
option java_multiple_files = true; option java_multiple_files = true;
message Config { message Config {
int32 version = 1; reserved 1;
string auth = 2; string auth = 2;
int64 udp_idle_timeout = 3; int64 udp_idle_timeout = 3;
+1 -1
View File
@@ -58,7 +58,7 @@ func (h *httpHandler) AuthHTTP(w http.ResponseWriter, r *http.Request) bool {
var user *protocol.MemoryUser var user *protocol.MemoryUser
var ok bool var ok bool
if h.validator != nil && h.validator.GetCount() > 0 { if h.validator != nil && h.validator.NotEmpty() {
user = h.validator.Get(auth) user = h.validator.Get(auth)
} else if h.config.Auth != "" { } else if h.config.Auth != "" {
ok = auth == h.config.Auth ok = auth == h.config.Auth
+1 -3
View File
@@ -584,9 +584,7 @@ func (ln *Listener) Addr() net.Addr {
// Close implements net.Listener.Close(). // Close implements net.Listener.Close().
func (ln *Listener) Close() error { func (ln *Listener) Close() error {
if ln.h3server != nil { if ln.h3server != nil {
if err := ln.h3server.Close(); err != nil { return ln.h3server.Close()
return err
}
} else if ln.listener != nil { } else if ln.listener != nil {
return ln.listener.Close() return ln.listener.Close()
} }
+36 -57
View File
@@ -6,27 +6,25 @@ package splithttp
import ( import (
"container/heap" "container/heap"
"io" "io"
"runtime" "sync/atomic"
"sync"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/signal/done"
) )
type Packet struct { type Packet struct {
Reader io.ReadCloser Reader *httpServerConn
Payload []byte Payload []byte
Seq uint64 Seq uint64
} }
type uploadQueue struct { type uploadQueue struct {
reader io.ReadCloser reader atomic.Pointer[httpServerConn]
nomore bool pushedPackets chan Packet
pushedPackets chan Packet heap uploadHeap
writeCloseMutex sync.Mutex nextSeq uint64
heap uploadHeap maxPackets int
nextSeq uint64 closed *done.Instance
closed bool
maxPackets int
} }
func NewUploadQueue(maxPackets int) *uploadQueue { func NewUploadQueue(maxPackets int) *uploadQueue {
@@ -34,73 +32,53 @@ func NewUploadQueue(maxPackets int) *uploadQueue {
pushedPackets: make(chan Packet, maxPackets), pushedPackets: make(chan Packet, maxPackets),
heap: uploadHeap{}, heap: uploadHeap{},
nextSeq: 0, nextSeq: 0,
closed: false, closed: done.New(),
maxPackets: maxPackets, maxPackets: maxPackets,
} }
} }
func (h *uploadQueue) Push(p Packet) error { func (h *uploadQueue) Push(p Packet) error {
h.writeCloseMutex.Lock() if h.reader.Load() != nil || (p.Reader != nil && !h.reader.CompareAndSwap(nil, p.Reader)) {
defer h.writeCloseMutex.Unlock()
if h.closed {
return errors.New("packet queue closed")
}
if h.nomore {
return errors.New("h.reader already exists") return errors.New("h.reader already exists")
} }
if p.Reader != nil { select {
h.nomore = true case h.pushedPackets <- p: // no panic
if h.closed.Done() {
return errors.New("packet queue closed")
}
return nil
case <-h.closed.Wait():
return errors.New("packet queue closed")
} }
h.pushedPackets <- p
return nil
} }
func (h *uploadQueue) Close() error { func (h *uploadQueue) Close() error {
h.writeCloseMutex.Lock() h.closed.Close()
defer h.writeCloseMutex.Unlock() if reader := h.reader.Load(); reader != nil {
return reader.Close()
if !h.closed {
h.closed = true
runtime.Gosched() // hope Read() gets the packet
f:
for {
select {
case p := <-h.pushedPackets:
if p.Reader != nil {
h.reader = p.Reader
}
default:
break f
}
}
close(h.pushedPackets)
}
if h.reader != nil {
return h.reader.Close()
} }
return nil return nil
} }
func (h *uploadQueue) Read(b []byte) (int, error) { func (h *uploadQueue) Read(b []byte) (int, error) {
if h.reader != nil { if reader := h.reader.Load(); reader != nil {
return h.reader.Read(b) return reader.Read(b)
} }
if h.closed { if h.closed.Done() {
return 0, io.EOF return 0, io.EOF
} }
if len(h.heap) == 0 { if len(h.heap) == 0 {
packet, more := <-h.pushedPackets select {
if !more { case p := <-h.pushedPackets:
if p.Reader != nil {
return p.Reader.Read(b)
}
heap.Push(&h.heap, p)
case <-h.closed.Wait():
return 0, io.EOF return 0, io.EOF
} }
if packet.Reader != nil {
h.reader = packet.Reader
return h.reader.Read(b)
}
heap.Push(&h.heap, packet)
} }
for len(h.heap) > 0 { for len(h.heap) > 0 {
@@ -131,11 +109,12 @@ func (h *uploadQueue) Read(b []byte) (int, error) {
return 0, errors.New("packet queue is too large") return 0, errors.New("packet queue is too large")
} }
heap.Push(&h.heap, packet) heap.Push(&h.heap, packet)
packet2, more := <-h.pushedPackets select {
if !more { case p := <-h.pushedPackets:
heap.Push(&h.heap, p)
case <-h.closed.Wait():
return 0, io.EOF return 0, io.EOF
} }
heap.Push(&h.heap, packet2)
} }
} }