Compare commits

...
27 Commits
Author SHA1 Message Date
Fangliding bbcf2c4e45 Rename 2026-06-09 20:12:54 +08:00
风扇滑翔翼andGitHub 3f4a4004f9 Reorganize predefined character sets in config.go 2026-06-09 04:29:57 +08:00
Fangliding 87c08d0d7a More table 2026-06-06 06:11:54 +08:00
Fangliding 0e6ab63b8a nil safe 2026-06-03 22:42:37 +08:00
Fangliding 2f121bd6a2 Custom sessionID 2026-06-03 22:42:20 +08:00
fdb9b616fc README.md: Add Hey to HarmonyOS in GUI Clients (#6244)
https://github.com/popsiclelmlm/Hey/blob/main/entry/src/main/cpp/README.md

---------

Co-authored-by: liumin01 <liumin01@roborock.com>
2026-06-02 23:58:56 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇andGitHub d792fba59c GitHub Actions CI: Builders run only once in pull requests in the same repository (#6222)
https://github.com/XTLS/Xray-core/pull/6221#issuecomment-4588919827

Completes https://github.com/XTLS/Xray-core/pull/6090#issuecomment-4572094191
2026-06-02 23:40:17 +00:00
55956f8d70 TLS config: Remove some deprecated fields (#6226)
https://t.me/projectXtls/1490

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-02 23:36:42 +00:00
RPRXandGitHub 94ffd50060 Xray-core v26.6.1
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-01 02:11:09 +00:00
风扇滑翔翼andGitHub c4dfcd4c1a Burst observatory: Fix init check (#6221)
https://github.com/XTLS/Xray-core/pull/6106#issuecomment-4587975865
2026-06-01 01:30:40 +00:00
cb8cd048c1 DNS outbound: Replace "reject" with "return" (rCode is 0 by default) (#6214)
https://github.com/XTLS/Xray-core/pull/6214#issuecomment-4587988752

Example: https://github.com/XTLS/Xray-core/pull/6214#issue-4553786283

---------

Co-authored-by: Meo597 <197331664+Meo597@users.noreply.github.com>
2026-06-01 01:25:47 +00:00
DavoyanandGitHub 455f6bc2d5 uTLS: Update ModernFingerprints map and OtherFingerprints map (#6181)
Paying attention to https://github.com/XTLS/Xray-core/pull/6181#issuecomment-4567373533

And a real issue: https://github.com/XTLS/Xray-core/pull/6181#issuecomment-4583018017
2026-05-30 17:29:33 +00:00
LjhAUMEMandGitHub ba538619eb Realm finalmask: Fix client punch peers (#6213)
Fixes https://github.com/XTLS/Xray-core/pull/6137
2026-05-30 09:30:23 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇andGitHub d43a808ea5 GitHub Action CI: Add Go source file format check (#6090)
https://github.com/XTLS/Xray-core/pull/6057#issuecomment-4364819830

And https://github.com/XTLS/Xray-core/pull/6149#issuecomment-4546876261
2026-05-29 15:04:59 +00:00
ca4b156b57 Burst observatory: Fix time compare, cancel pending ping on instance close or new schedule started (#6106)
https://github.com/XTLS/Xray-core/pull/6106#issuecomment-4448117773

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-05-29 11:59:24 +00:00
LjhAUMEMandRPRX aba22722a6 Finalmask: Add mkcp-legacy (UDP) to replace mkcp-* and legacy header-* (#6201)
https://github.com/XTLS/Xray-core/pull/6193#issuecomment-4543737699

https://github.com/XTLS/Xray-core/pull/6201#issuecomment-4574213827

Example: https://github.com/XTLS/Xray-core/pull/6201#issue-4525483699
2026-05-29 11:57:32 +00:00
569459c54c Sudoku finalmask: Harden UDP ReadFrom() against invalid packets (#6185)
https://github.com/XTLS/Xray-core/issues/6184#issuecomment-4573949756

Fixes https://github.com/XTLS/Xray-core/issues/6184

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-05-29 10:47:58 +00:00
LjhAUMEMandGitHub 2b42699623 XICMP finalmask: Refactor & Speed up; Add multi ips and dgram mode (client) (#6168)
https://github.com/XTLS/Xray-core/pull/5872#issuecomment-4198514120
https://github.com/XTLS/Xray-core/pull/6168#issuecomment-4571606666
https://github.com/XTLS/Xray-core/pull/6168#issuecomment-4573294637

Example: https://github.com/XTLS/Xray-core/pull/6168#issue-4484980927

Closes https://github.com/XTLS/Xray-core/discussions/5879
2026-05-29 09:44:06 +00:00
LjhAUMEMandRPRX 66a8100737 Salamander finalmask: Support packetSize (Gecko in Hysteria v2.9.2) (#6198)
And some other refinements

https://github.com/XTLS/Xray-core/pull/6198#issuecomment-4567438813

Example: https://github.com/XTLS/Xray-core/pull/6198#issue-4522226670
2026-05-29 08:51:41 +00:00
LjhAUMEMandGitHub 36303694d1 Finalmask: Add Realm (UDP hole punching in Hysteria v2.9.1) (#6137)
https://github.com/XTLS/Xray-core/pull/5657#issuecomment-4446406536

https://github.com/XTLS/Xray-core/pull/6137#issuecomment-4469822775

Example: https://github.com/XTLS/Xray-core/pull/6137#issue-4454013510
2026-05-29 08:29:53 +00:00
Jason FoxandGitHub 4dcf802ae3 README.md: Add flutter_vless to Xray Wrapper in Others (#6197)
https://pub.dev/packages/flutter_vless
2026-05-28 20:15:38 +00:00
MeowandGitHub a2cec2e580 DNS: Avoid panic on domain too long (#6207)
Fixes https://github.com/XTLS/Xray-core/issues/6204
2026-05-28 20:06:32 +00:00
LjhAUMEMandGitHub 1cd7d25fec header-custom finalmask: Remove headerConnMode headerReadAddrAware interface (#6193)
https://github.com/XTLS/Xray-core/pull/5920#issuecomment-4253108964

https://github.com/XTLS/Xray-core/pull/6193#issuecomment-4531875088
2026-05-28 19:44:16 +00:00
LjhAUMEMandGitHub 09002ab763 noise finalmask: Better reset (#6188)
https://github.com/XTLS/Xray-core/pull/6170

Explanation: https://github.com/XTLS/Xray-core/pull/5657#issue-3901983829
2026-05-28 19:24:22 +00:00
cb206ddc74 DNS: Avoid passing domain to WriteTo func (#6163)
https://github.com/XTLS/Xray-core/pull/6163#issuecomment-4528771758

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-05-28 18:27:08 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fa466f8174 Bump golang.org/x/net from 0.54.0 to 0.55.0 (#6192)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.54.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.54.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.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-05-28 18:20:27 +00:00
e26f5e9548 Socks5 server: More standard UDP ASSOCIATE (RFC 1928) (#6149)
https://github.com/XTLS/Xray-core/pull/6149#issuecomment-4529069218

Fixes https://github.com/XTLS/Xray-core/issues/6145#issuecomment-4467482623

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-05-28 14:22:57 +00:00
214 changed files with 4689 additions and 3334 deletions
+2
View File
@@ -11,6 +11,7 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
@@ -75,6 +76,7 @@ jobs:
fail-fast: false
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
env:
GOOS: ${{ matrix.goos}}
GOARCH: ${{ matrix.goarch }}
+5
View File
@@ -11,6 +11,7 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
@@ -161,6 +162,7 @@ jobs:
fail-fast: false
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
@@ -208,6 +210,9 @@ jobs:
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# 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
elif [[ ${GOOS} == 'android' ]]; then
echo 'Building Xray for Android...'
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid= -checklinkname=0" -v ./main
else
echo 'Building Xray...'
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
+23 -1
View File
@@ -1,4 +1,4 @@
name: Test
name: Tests and Checkings
on:
push:
@@ -8,6 +8,7 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
@@ -36,6 +37,7 @@ jobs:
check-proto:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Checkout codebase
uses: actions/checkout@v6
@@ -50,8 +52,28 @@ jobs:
fi
done
check-format:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
permissions:
contents: read
steps:
- name: Checkout codebase
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
cache: false
- name: Check Format
run: |
go install -v mvdan.cc/gofumpt@latest
go run ./infra/vformat/main.go -mode check -pwd ./
test:
needs: check-assets
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
permissions:
contents: read
runs-on: ${{ matrix.os }}
+10
View File
@@ -145,6 +145,8 @@
- [v2rayN](https://github.com/2dust/v2rayN)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
- [OneXray](https://github.com/OneXray/OneXray)
- HarmonyOS
- [Hey](https://github.com/popsiclelmlm/Hey)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
@@ -162,6 +164,7 @@
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
- [xtlsapi](https://github.com/hiddify/xtlsapi)
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
- [flutter_vless](https://github.com/XIIIFOX/flutter_vless)
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
- [xray-api](https://github.com/XVGuardian/xray-api)
- [XrayR](https://github.com/XrayR-project/XrayR)
@@ -206,6 +209,13 @@ Make sure that you are using the same Go version, and remember to set the git co
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
```
For Android:
```bash
GOOS=android GOARCH=arm64 CGO_ENABLED=1 CC=/path/to/aarch64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
GOOS=android GOARCH=amd64 CGO_ENABLED=1 CC=/path/to/x86_64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
```
If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
```bash
+5 -6
View File
@@ -3,15 +3,15 @@ package commander
import (
"context"
"net"
"strings"
"sync"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/signal/done"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet"
"google.golang.org/grpc"
)
@@ -69,16 +69,15 @@ func (c *Commander) Start() error {
}
c.Unlock()
var listen = func(listener net.Listener) {
listen := func(listener net.Listener) {
if err := c.server.Serve(listener); err != nil {
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
}
}
if len(c.listen) > 0 {
var addr net.Addr
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
} else {
@@ -89,7 +88,7 @@ func (c *Commander) Start() error {
}
addr = tcpAddr
}
l, err := internet.ListenSystem(context.Background(), addr, nil)
l, err := internet.ListenSystem(context.Background(), addr, nil)
if err != nil {
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
return err
+4 -3
View File
@@ -139,7 +139,8 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
if lenAfter == 0 {
if c.highWatermark >= minSizeForEmptyRebuild {
errors.LogDebug(context.Background(), c.name,
errors.LogDebug(
context.Background(), c.name,
" rebuilding empty cache map to reclaim memory.",
" size_before_cleanup=", lenBefore,
" peak_size_before_rebuild=", c.highWatermark,
@@ -153,7 +154,8 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
errors.LogDebug(context.Background(), c.name,
errors.LogDebug(
context.Background(), c.name,
" shrinking cache map to reclaim memory.",
" new_size=", lenAfter,
" peak_size_before_shrink=", c.highWatermark,
@@ -165,7 +167,6 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
c.highWatermark = lenAfter
go c.migrate()
}
}
type migrationEntry struct {
+2 -2
View File
@@ -86,7 +86,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
return nil, errors.New("failed to create hosts").Base(err)
}
var defaultTag = config.Tag
defaultTag := config.Tag
if len(config.Tag) == 0 {
defaultTag = generateRandomTag()
}
@@ -139,7 +139,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
serveExpiredTTL = *ns.ServeExpiredTTL
}
var tag = defaultTag
tag := defaultTag
if len(ns.Tag) > 0 {
tag = ns.Tag
}
+9 -4
View File
@@ -127,15 +127,20 @@ func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
return opt
}
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) ([]*dnsRequest, error) {
name, err := dnsmessage.NewName(domain)
if err != nil {
return nil, err
}
qA := dnsmessage.Question{
Name: dnsmessage.MustNewName(domain),
Name: name,
Type: dnsmessage.TypeA,
Class: dnsmessage.ClassINET,
}
qAAAA := dnsmessage.Question{
Name: dnsmessage.MustNewName(domain),
Name: name,
Type: dnsmessage.TypeAAAA,
Class: dnsmessage.ClassINET,
}
@@ -175,7 +180,7 @@ func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() ui
})
}
return reqs
return reqs, nil
}
// parseResponse parses DNS answers from the returned payload
+11 -3
View File
@@ -2,6 +2,7 @@ package dns
import (
"math/rand"
"strings"
"testing"
"time"
@@ -24,7 +25,8 @@ func Test_parseResponse(t *testing.T) {
ans = new(dns.Msg)
ans.Id = 1
ans.Answer = append(ans.Answer,
ans.Answer = append(
ans.Answer,
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")),
@@ -34,7 +36,8 @@ func Test_parseResponse(t *testing.T) {
ans = new(dns.Msg)
ans.Id = 2
ans.Answer = append(ans.Answer,
ans.Answer = append(
ans.Answer,
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
@@ -131,10 +134,15 @@ func Test_buildReqMsgs(t *testing.T) {
IPv6Enable: false,
FakeEnable: false,
}, nil}, 0},
{"name too long", args{strings.Repeat("a", 256), dns_feature.IPOption{
IPv4Enable: true,
IPv6Enable: true,
FakeEnable: false,
}, nil}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
if got, _ := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want)
}
})
+10 -9
View File
@@ -129,15 +129,16 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
}
func TestFakeDNSMulti(t *testing.T) {
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
Pools: []*FakeDnsPool{{
IpPool: "240.0.0.0/12",
LruSize: 256,
}, {
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
LruSize: 256,
}},
},
fakeMulti, err := NewFakeDNSHolderMulti(
&FakeDnsPoolMulti{
Pools: []*FakeDnsPool{{
IpPool: "240.0.0.0/12",
LruSize: 256,
}, {
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
LruSize: 256,
}},
},
)
common.Must(err)
+1 -1
View File
@@ -135,7 +135,7 @@ func NewClient(
}
}
var timeoutMs = 4000 * time.Millisecond
timeoutMs := 4000 * time.Millisecond
if ns.TimeoutMs > 0 {
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
}
+20 -2
View File
@@ -137,14 +137,32 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
if s.Name()+"." == "DOH//"+fqdn {
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
if noResponseErrCh != nil {
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
err := errors.New("tries to resolve itself!", s.Name())
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
-1
View File
@@ -18,7 +18,6 @@ type LocalNameServer struct {
// QueryIP implements Server.
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
start := time.Now()
ips, ttl, err = s.client.LookupIP(domain, option)
+13 -1
View File
@@ -78,7 +78,19 @@ func (s *QUICNameServer) getCacheController() *CacheController { return s.cacheC
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
+13 -1
View File
@@ -113,7 +113,19 @@ func (s *TCPNameServer) getCacheController() *CacheController {
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
+13 -5
View File
@@ -131,8 +131,6 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
newReq.msg = &newMsg
s.addPendingRequest(&newReq)
b, _ := dns.PackMessage(newReq.msg)
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
return
}
@@ -163,7 +161,19 @@ func (s *ClassicNameServer) getCacheController() *CacheController {
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
for _, req := range reqs {
udpReq := &udpDnsRequest{
@@ -179,8 +189,6 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<
}
return
}
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
}
}
-1
View File
@@ -85,7 +85,6 @@ func (p *MetricsHandler) Type() interface{} {
}
func (p *MetricsHandler) Start() error {
// direct listen a port if listen is set
if p.listen != "" {
TCPlistener, err := net.Listen("tcp", p.listen)
-2
View File
@@ -2,7 +2,6 @@ package burst
import (
"context"
"sync"
"github.com/xtls/xray-core/app/observatory"
@@ -72,7 +71,6 @@ func (o *Observer) Start() error {
o.hp.StartScheduler(func() ([]string, error) {
hs, ok := o.ohm.(outbound.HandlerSelector)
if !ok {
return nil, errors.New("outbound.Manager is not a HandlerSelector")
}
+42 -24
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/dice"
@@ -24,11 +25,12 @@ type HealthPingSettings struct {
// HealthPing is the health checker for balancers
type HealthPing struct {
ctx context.Context
dispatcher routing.Dispatcher
access sync.Mutex
ticker *time.Ticker
tickerClose chan struct{}
ctx context.Context
cancelCtx context.CancelFunc
cancelPending atomic.Pointer[context.CancelFunc]
dispatcher routing.Dispatcher
access sync.Mutex
ticker *time.Ticker
Settings *HealthPingSettings
Results map[string]*HealthPingRTTS
@@ -62,10 +64,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
settings.Destination = "https://connectivitycheck.gstatic.com/generate_204"
}
if settings.Interval == 0 {
settings.Interval = time.Duration(1) * time.Minute
} else if settings.Interval < 10 {
settings.Interval = 1 * time.Minute
} else if settings.Interval < 10*time.Second {
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
settings.Interval = time.Duration(10) * time.Second
settings.Interval = 10 * time.Second
}
if settings.SamplingCount <= 0 {
settings.SamplingCount = 10
@@ -73,10 +75,12 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
if settings.Timeout <= 0 {
// results are saved after all health pings finish,
// a larger timeout could possibly makes checks run longer
settings.Timeout = time.Duration(5) * time.Second
settings.Timeout = 5 * time.Second
}
ctx, cancel := context.WithCancel(ctx)
return &HealthPing{
ctx: ctx,
cancelCtx: cancel,
dispatcher: dispatcher,
Settings: settings,
Results: nil,
@@ -90,9 +94,9 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
}
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
ticker := time.NewTicker(interval)
tickerClose := make(chan struct{})
h.ticker = ticker
h.tickerClose = tickerClose
// init run to get a fast check result
go func() {
tags, err := selector()
if err != nil {
@@ -110,13 +114,20 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
return
}
h.doCheck(tags, interval, h.Settings.SamplingCount)
subCtx, cancel := context.WithCancel(h.ctx)
old := h.cancelPending.Swap(&cancel)
if old != nil {
errors.LogDebug(h.ctx, "scheduled health check not finished before next round, canceling previous one")
(*old)()
}
h.doCheck(subCtx, tags, interval, h.Settings.SamplingCount)
h.cancelPending.CompareAndSwap(&cancel, nil)
h.Cleanup(tags)
}()
select {
case <-ticker.C:
continue
case <-tickerClose:
case <-h.ctx.Done():
return
}
}
@@ -130,8 +141,7 @@ func (h *HealthPing) StopScheduler() {
}
h.ticker.Stop()
h.ticker = nil
close(h.tickerClose)
h.tickerClose = nil
h.cancelCtx()
}
// Check implements the HealthChecker
@@ -140,7 +150,7 @@ func (h *HealthPing) Check(tags []string) error {
return nil
}
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
h.doCheck(tags, 0, 1)
h.doCheck(h.ctx, tags, 0, 1)
return nil
}
@@ -151,13 +161,14 @@ type rtt struct {
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
// sure all tags are valid for current balancer
func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {
// cancel ctx will stop all pending checks
func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.Duration, rounds int) {
count := len(tags) * rounds
if count == 0 {
return
}
ch := make(chan *rtt, count)
timers := make([]*time.Timer, 0, count)
for _, tag := range tags {
handler := tag
client := newPingClient(
@@ -172,7 +183,7 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
if duration > 0 {
delay = time.Duration(dice.RollInt63n(int64(duration)))
}
time.AfterFunc(delay, func() {
timers = append(timers, time.AfterFunc(delay, func() {
errors.LogDebug(h.ctx, "checking ", handler)
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
if err == nil {
@@ -200,14 +211,21 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
handler: handler,
value: rttFailed,
}
})
}))
}
}
for i := 0; i < count; i++ {
rtt := <-ch
if rtt.value > 0 {
// should not put results when network is down
h.PutResult(rtt.handler, rtt.value)
select {
case rtt := <-ch:
if rtt.value > 0 {
// should not put results when network is down
h.PutResult(rtt.handler, rtt.value)
}
case <-ctx.Done():
for _, timer := range timers {
timer.Stop()
}
return
}
}
}
+2 -2
View File
@@ -59,7 +59,7 @@ func (h *HealthPingRTTS) Put(d time.Duration) {
if h.rtts == nil {
h.rtts = make([]*pingRTT, h.cap)
for i := 0; i < h.cap; i++ {
h.rtts[i] = &pingRTT{}
h.rtts[i] = &pingRTT{value: rttUntested}
}
h.idx = -1
}
@@ -88,7 +88,7 @@ func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
validRTTs := make([]time.Duration, 0)
for _, rtt := range h.rtts {
switch {
case rtt.value == 0 || time.Since(rtt.time) > h.validity:
case rtt.value == rttUntested || time.Since(rtt.time) > h.validity:
continue
case rtt.value == rttFailed:
stats.Fail++
+1 -1
View File
@@ -186,7 +186,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
return nil
})
if err != nil {
var errorMessage = "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
errorMessage := "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
}
+1 -1
View File
@@ -138,7 +138,7 @@ func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInbound
if len(request.Email) > 0 {
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil
}
var result = make([]*protocol.User, 0, 100)
result := make([]*protocol.User, 0, 100)
users := um.GetUsers(ctx)
for _, u := range users {
result = append(result, protocol.ToProtoUser(u))
+3 -3
View File
@@ -16,10 +16,10 @@ import (
// Manager manages all inbound handlers.
type Manager struct {
access sync.RWMutex
access sync.RWMutex
untaggedHandlers []inbound.Handler
taggedHandlers map[string]inbound.Handler
running bool
taggedHandlers map[string]inbound.Handler
running bool
}
// New returns a new Manager for inbound handlers.
+1 -2
View File
@@ -348,7 +348,7 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
}
}
//case addr.Family().IsDomain():
// case addr.Family().IsDomain():
default:
ob.Gateway = addr
@@ -396,7 +396,6 @@ func (h *Handler) ProxySettings() *serial.TypedMessage {
}
func ParseRandomIP(addr net.Address, prefix string) net.Address {
_, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
ones, bits := ipnet.Mask.Size()
+4 -5
View File
@@ -22,8 +22,8 @@ import (
)
func TestInterfaces(t *testing.T) {
_ = (outbound.Handler)(new(Handler))
_ = (outbound.Manager)(new(Manager))
_ = outbound.Handler(new(Handler))
_ = outbound.Manager(new(Manager))
}
const xrayKey core.XrayKey = 1
@@ -43,7 +43,7 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
}
v, _ := core.New(config)
v.AddFeature((outbound.Manager)(new(Manager)))
v.AddFeature(outbound.Manager(new(Manager)))
ctx := context.WithValue(context.Background(), xrayKey, v)
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
@@ -73,7 +73,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
}
v, _ := core.New(config)
v.AddFeature((outbound.Manager)(new(Manager)))
v.AddFeature(outbound.Manager(new(Manager)))
ctx := context.WithValue(context.Background(), xrayKey, v)
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
@@ -88,7 +88,6 @@ func TestOutboundWithStatCounter(t *testing.T) {
}
func TestTagsCache(t *testing.T) {
test_duration := 10 * time.Second
threads_num := 50
delay := 10 * time.Millisecond
-1
View File
@@ -162,7 +162,6 @@ func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {
// Select implements outbound.HandlerSelector.
func (m *Manager) Select(selectors []string) []string {
key := strings.Join(selectors, ",")
if cache, ok := m.tagsCache.Load(key); ok {
return cache.([]string)
-1
View File
@@ -58,7 +58,6 @@ func (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*
return &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend)
}
return nil, errors.New("unsupported router implementation")
}
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
-2
View File
@@ -110,7 +110,6 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
// AddRule implements routing.Router.
func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {
inst, err := config.GetInstance()
if err != nil {
return err
@@ -227,7 +226,6 @@ func (r *Router) RemoveRule(tag string) error {
return nil
}
return errors.New("empty tag name!")
}
// ListRule implements routing.Router
+5
View File
@@ -85,6 +85,7 @@ func TestSelectLeastExpected(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpected2(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -102,6 +103,7 @@ func TestSelectLeastExpected2(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpectedAndBaselines(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -122,6 +124,7 @@ func TestSelectLeastExpectedAndBaselines(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -142,6 +145,7 @@ func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastLoadBaselines(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -160,6 +164,7 @@ func TestSelectLeastLoadBaselines(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
-2
View File
@@ -16,8 +16,6 @@ import (
routing_session "github.com/xtls/xray-core/features/routing/session"
)
func ptr[T any](v T) *T { return &v }
type event struct {
+1 -1
View File
@@ -11,7 +11,7 @@ import (
)
func TestInterface(t *testing.T) {
_ = (stats.Manager)(new(Manager))
_ = stats.Manager(new(Manager))
}
func TestStatsChannelRunnable(t *testing.T) {
+3 -2
View File
@@ -2,10 +2,11 @@ package version
import (
"context"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"strconv"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
)
type Version struct {
+2 -2
View File
@@ -38,8 +38,8 @@ func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
n := len(dest)
if n > 0 && !(dest)[n-1].IsFull() {
nBytes, _ := (dest)[n-1].Write(src)
if n > 0 && !dest[n-1].IsFull() {
nBytes, _ := dest[n-1].Write(src)
src = src[nBytes:]
}
+6 -6
View File
@@ -121,11 +121,11 @@ func TestPacketReader_ReadMultiBuffer(t *testing.T) {
}
func TestReaderInterface(t *testing.T) {
_ = (io.Reader)(new(ReadVReader))
_ = (Reader)(new(ReadVReader))
_ = io.Reader(new(ReadVReader))
_ = Reader(new(ReadVReader))
_ = (Reader)(new(BufferedReader))
_ = (io.Reader)(new(BufferedReader))
_ = (io.ByteReader)(new(BufferedReader))
_ = (io.WriterTo)(new(BufferedReader))
_ = Reader(new(BufferedReader))
_ = io.Reader(new(BufferedReader))
_ = io.ByteReader(new(BufferedReader))
_ = io.WriterTo(new(BufferedReader))
}
+1 -1
View File
@@ -19,7 +19,7 @@ func (r *posixReader) Init(bs []*Buffer) {
}
for idx, b := range bs {
iovecs = append(iovecs, syscall.Iovec{
Base: &(b.v[0]),
Base: &b.v[0],
})
iovecs[idx].SetLen(int(Size))
}
+3 -3
View File
@@ -315,7 +315,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
}
if !matcher.AnyMatch([]net.IP{
net.IP{},
{},
ip("1.1.1.1"),
ip("8.8.8.8"),
}) {
@@ -345,7 +345,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
if matcher.Matches([]net.IP{
ip("8.8.8.8"),
net.IP{},
{},
}) {
t.Fatal("expect Matches to be false when any IP is invalid")
}
@@ -362,7 +362,7 @@ func TestIPMatcherFilterIPs(t *testing.T) {
}
matched, unmatched := matcher.FilterIPs([]net.IP{
net.IP{},
{},
ip("8.8.8.8"),
ip("91.108.255.254"),
ip("1.1.1.1"),
-1
View File
@@ -98,7 +98,6 @@ func (l *generalLogger) run() {
}
func (l *generalLogger) Handle(msg Message) {
select {
case l.buffer <- msg:
default:
+3 -2
View File
@@ -36,9 +36,10 @@ func TestNormalizeEnvName(t *testing.T) {
}
func TestEnvFlag(t *testing.T) {
if v := (EnvFlag{
v := EnvFlag{
Name: "xxxxx.y",
}.GetValueAsInt(10)); v != 10 {
}.GetValueAsInt(10)
if v != 10 {
t.Error("env value: ", v)
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ type ID struct {
// Equals returns true if this ID equals to the other one.
func (id *ID) Equals(another *ID) bool {
return id.uuid.Equals(&(another.uuid))
return id.uuid.Equals(&another.uuid)
}
func (id *ID) Bytes() []byte {
+1 -1
View File
@@ -229,7 +229,7 @@ func TestSniffQUICComplex(t *testing.T) {
t.Errorf("SniffQUIC() error = %v, wantErr %v", err, tt.wantErr)
return
}
if (errors.Is(err, protocol.ErrProtoNeedMoreData)) != tt.needsMoreData {
if errors.Is(err, protocol.ErrProtoNeedMoreData) != tt.needsMoreData {
t.Errorf("SniffQUIC() error = %v, expectsNoClue %v", err, tt.needsMoreData)
return
}
+2 -2
View File
@@ -5,8 +5,8 @@ import (
)
type ServerSpec struct {
Destination net.Destination
User *MemoryUser
Destination net.Destination
User *MemoryUser
}
func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
-1
View File
@@ -228,7 +228,6 @@ func isValueKind(kind reflect.Kind) bool {
}
func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {
if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
return r
}
+1 -3
View File
@@ -27,7 +27,6 @@ func TestMashalAccount(t *testing.T) {
j, ok := MarshalToJson(user, false)
if !ok || strings.Contains(j, "_TypedMessage_") {
t.Error("marshal account failed")
}
@@ -79,13 +78,12 @@ func TestMashalStruct(t *testing.T) {
v := (*f2.Arr)[0]["foo"]["hello"]
if f1.N != f2.N || *(f1.Np) != *(f2.Np) || f1.S != f2.S || v != "world" {
if f1.N != f2.N || *f1.Np != *f2.Np || f1.S != f2.S || v != "world" {
t.Error("f1 not equal to f2")
}
}
func TestMarshalConfigJson(t *testing.T) {
buf := bytes.NewBufferString(getConfig())
config, err := iserial.DecodeJSONConfig(buf)
if err != nil {
-29
View File
@@ -4,7 +4,6 @@ import (
"testing"
. "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
)
func TestGetInstance(t *testing.T) {
@@ -23,31 +22,3 @@ func TestConvertingNilMessage(t *testing.T) {
t.Error("expect nil, but actually not")
}
}
func TestTypedMessageRoundTripPreservesFinalmaskCustomUDPMode(t *testing.T) {
msg := &custom.UDPConfig{
Mode: "standalone",
Client: []*custom.UDPItem{
{Rand: 12, Save: "txid"},
},
}
tm := ToTypedMessage(msg)
if tm == nil {
t.Fatal("expected typed message")
}
roundTrip, err := tm.GetInstance()
if err != nil {
t.Fatalf("GetInstance() failed: %v", err)
}
udp, ok := roundTrip.(*custom.UDPConfig)
if !ok {
t.Fatalf("unexpected round-trip type: %T", roundTrip)
}
if udp.GetMode() != "standalone" {
t.Fatalf("mode lost during typed message round-trip: got %q", udp.GetMode())
}
}
+14 -7
View File
@@ -10,37 +10,44 @@ func TestByteSizes(t *testing.T) {
size := units.ByteSize(0)
assertSizeString(t, size, "0")
size++
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00B"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00KB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00MB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00GB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00TB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00PB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00EB"),
size,
)
+77 -43
View File
@@ -4,10 +4,10 @@ import (
"hash/fnv"
"math"
"math/rand"
"strconv"
"time"
"net/http"
"strconv"
"strings"
"time"
"github.com/klauspost/cpuid/v2"
)
@@ -18,6 +18,7 @@ func GetRandomizer() *rand.Rand {
fnvHash.Write([]byte(strconv.Itoa(cpuid.CPU.Family) + strconv.Itoa(cpuid.CPU.Model) + strconv.Itoa(cpuid.CPU.PhysicalCores) + strconv.Itoa(cpuid.CPU.LogicalCores) + strconv.Itoa(cpuid.CPU.CacheLine) + strconv.Itoa(cpuid.CPU.ThreadsPerCore)))
return rand.New(rand.NewSource(int64(fnvHash.Sum64())))
}
var globalRng *rand.Rand = GetRandomizer()
// The Chrome version generator will suffer from deviation of a normal distribution.
@@ -26,83 +27,114 @@ func ChromeVersion() int {
var startVersion int = 144
var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeCurrent int64 = time.Now().Unix() / 86400
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 105))
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*105))
return startVersion + (timeDiff / 35) // It's 31.15 currently.
}
var safariMinorMap [25]int = [25]int{0, 0, 0, 1, 1,
var safariMinorMap [25]int = [25]int{
0, 0, 0, 1, 1,
1, 2, 2, 2, 2, 3, 3, 3, 4, 4,
4, 5, 5, 5, 5, 5, 6, 6, 6, 6}
4, 5, 5, 5, 5, 5, 6, 6, 6, 6,
}
// The following version generators use deterministic generators, but with the distribution scaled by a curve.
func CurlVersion() string {
// curl 8.0.0 was released on 20/03/2023.
var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2023, 3, 20, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 165))
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*165))
var minorValue int = int(timeDiff / 57) // The release cadence is actually 56.67 days.
return "8." + strconv.Itoa(minorValue) + ".0"
}
func FirefoxVersion() int {
// Firefox 128 ESR was released on 09/07/2023.
var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2024, 7, 29, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeDiff = timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2) * 50))
return int(timeDiff / 30) + 128
timeDiff := timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2)*50))
return int(timeDiff/30) + 128
}
func SafariVersion() string {
var anchoredTime time.Time = time.Now()
var releaseYear int = anchoredTime.Year()
var splitPoint time.Time = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
var delayedDays = int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
delayedDays := int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
if (anchoredTime.Compare(splitPoint) < 0) {
releaseYear --
if anchoredTime.Compare(splitPoint) < 0 {
releaseYear--
splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
}
var minorVersion = safariMinorMap[(anchoredTime.Unix() - splitPoint.Unix()) / 1296000]
return strconv.Itoa(releaseYear - 1999) + "." + strconv.Itoa(minorVersion)
minorVersion := safariMinorMap[(anchoredTime.Unix()-splitPoint.Unix())/1296000]
return strconv.Itoa(releaseYear-1999) + "." + strconv.Itoa(minorVersion)
}
// The full Chromium brand GREASE implementation
var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
var clientHintVersionNA = []string{"8", "99", "24"}
var clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
var clientHintShuffle4 = [][4]int{
{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1},
{1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0},
{2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0},
{3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}
var (
clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
clientHintVersionNA = []string{"8", "99", "24"}
clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
clientHintShuffle4 = [][4]int{
{0, 1, 2, 3},
{0, 1, 3, 2},
{0, 2, 1, 3},
{0, 2, 3, 1},
{0, 3, 1, 2},
{0, 3, 2, 1},
{1, 0, 2, 3},
{1, 0, 3, 2},
{1, 2, 0, 3},
{1, 2, 3, 0},
{1, 3, 0, 2},
{1, 3, 2, 0},
{2, 0, 1, 3},
{2, 0, 3, 1},
{2, 1, 0, 3},
{2, 1, 3, 0},
{2, 3, 0, 1},
{2, 3, 1, 0},
{3, 0, 1, 2},
{3, 0, 2, 1},
{3, 1, 0, 2},
{3, 1, 2, 0},
{3, 2, 0, 1},
{3, 2, 1, 0},
}
)
func getGreasedChInvalidBrand(seed int) string {
return "\"Not" + clientHintGreaseNA[seed % len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed + 1) % len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed % len(clientHintVersionNA)] + "\"";
return "\"Not" + clientHintGreaseNA[seed%len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed+1)%len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed%len(clientHintVersionNA)] + "\""
}
func getGreasedChOrder(brandLength int, seed int) []int {
switch brandLength {
case 1:
return []int{0}
case 2:
return []int{seed % brandLength, (seed + 1) % brandLength}
case 3:
return clientHintShuffle3[seed % len(clientHintShuffle3)][:]
default:
return clientHintShuffle4[seed % len(clientHintShuffle4)][:]
case 1:
return []int{0}
case 2:
return []int{seed % brandLength, (seed + 1) % brandLength}
case 3:
return clientHintShuffle3[seed%len(clientHintShuffle3)][:]
default:
return clientHintShuffle4[seed%len(clientHintShuffle4)][:]
}
//return []int{}
}
func getUngreasedChUa(majorVersion int, forkName string) []string {
// Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
baseChUa := make([]string, 0, 4)
baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
"\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"")
"\"Chromium\";v=\""+strconv.Itoa(majorVersion)+"\"")
switch forkName {
case "chrome":
baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"")
baseChUa = append(baseChUa, "\"Google Chrome\";v=\""+strconv.Itoa(majorVersion)+"\"")
case "edge":
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"")
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\""+strconv.Itoa(majorVersion)+"\"")
}
return baseChUa
}
func getGreasedChUa(majorVersion int, forkName string) string {
ungreasedCh := getUngreasedChUa(majorVersion, forkName)
shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion)
@@ -114,16 +146,18 @@ func getGreasedChUa(majorVersion int, forkName string) string {
}
// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
var CurlUA = "curl/" + CurlVersion()
var AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
var SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
// Chromium browsers.
var AnchoredChromeVersion = ChromeVersion()
var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
var (
CurlUA = "curl/" + CurlVersion()
AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
// Chromium browsers.
AnchoredChromeVersion = ChromeVersion()
ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
)
func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
// Browser-specific.
+1 -1
View File
@@ -109,4 +109,4 @@ func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
previous = anyPrevious.(V)
}
return previous, loaded
}
}
+19 -20
View File
@@ -19,18 +19,18 @@ import (
// Filesystem paths and abstract sockets on other platforms are returned
// unchanged.
func ResolveSocketPath(path string) string {
if len(path) == 0 || path[0] != '@' {
return path
}
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
return path
}
if len(path) > 1 && path[1] == '@' {
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
copy(fullAddr, path[1:])
return string(fullAddr)
}
return path
if len(path) == 0 || path[0] != '@' {
return path
}
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
return path
}
if len(path) > 1 && path[1] == '@' {
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
copy(fullAddr, path[1:])
return string(fullAddr)
}
return path
}
// SplitHTTPUnixURL splits a target into an HTTP URL and an optional Unix
@@ -44,12 +44,11 @@ func ResolveSocketPath(path string) string {
// The :/ separator delimits the socket path from the HTTP request path.
// If omitted, "/" is used.
func SplitHTTPUnixURL(raw string) (httpURL, socketPath string) {
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
return raw, ""
}
if idx := strings.Index(raw, ":/"); idx >= 0 {
return "http://localhost" + raw[idx+1:], raw[:idx]
}
return "http://localhost/", raw
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
return raw, ""
}
if idx := strings.Index(raw, ":/"); idx >= 0 {
return "http://localhost" + raw[idx+1:], raw[:idx]
}
return "http://localhost/", raw
}
+2 -2
View File
@@ -19,8 +19,8 @@ import (
var (
Version_x byte = 26
Version_y byte = 5
Version_z byte = 9
Version_y byte = 6
Version_z byte = 1
)
var (
-1
View File
@@ -34,7 +34,6 @@ func (ctx *Context) GetSourceIPs() []net.IP {
}
return nil
}
// GetSourcePort implements routing.Context.
+7 -2
View File
@@ -12,6 +12,7 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0
github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5
github.com/pion/stun/v3 v3.1.2
github.com/pires/go-proxyproto v0.12.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/robfig/cron/v3 v3.0.1
@@ -23,9 +24,9 @@ require (
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.51.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.54.0
golang.org/x/net v0.55.0
golang.org/x/sync v0.20.0
golang.org/x/sys v0.44.0
golang.org/x/sys v0.45.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.0.1
@@ -43,9 +44,13 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pion/dtls/v3 v3.1.2 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.12.0 // indirect
+14 -4
View File
@@ -45,6 +45,14 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY=
github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -67,6 +75,8 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -98,8 +108,8 @@ golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
@@ -111,8 +121,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+2 -1
View File
@@ -48,4 +48,5 @@ var configLoader = NewJSONConfigLoader(
"http": func() interface{} { return new(HTTPResponse) },
},
"type",
"")
"",
)
+2 -1
View File
@@ -3,6 +3,7 @@ package conf
import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
@@ -199,7 +200,7 @@ func (v *PortRange) UnmarshalJSON(data []byte) error {
if err == nil {
v.From = uint32(from)
v.To = uint32(to)
if v.From > v.To {
if v.From > v.To || v.To > math.MaxUint16 {
return errors.New("invalid port range ", v.From, " -> ", v.To)
}
return nil
+22 -20
View File
@@ -12,8 +12,9 @@ import (
type DNSOutboundRuleConfig struct {
Action string `json:"action"`
QType *PortList `json:"qtype"`
QType *PortList `json:"qType"`
Domain *StringList `json:"domain"`
RCode uint32 `json:"rCode"`
}
func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
@@ -24,8 +25,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
rule.Action = dns.RuleAction_Direct
case "drop":
rule.Action = dns.RuleAction_Drop
case "reject":
rule.Action = dns.RuleAction_Reject
case "return":
rule.Action = dns.RuleAction_Return
case "hijack":
rule.Action = dns.RuleAction_Hijack
default:
@@ -34,14 +35,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
if c.QType != nil {
for _, r := range c.QType.Range {
if r.From > r.To {
return nil, errors.New("invalid qtype range: ", r.String())
}
if r.To > 65535 {
return nil, errors.New("dns rule qtype out of range: ", r.String())
}
for qtype := r.From; qtype <= r.To; qtype++ {
rule.Qtype = append(rule.Qtype, int32(qtype))
for qType := r.From; qType <= r.To; qType++ {
rule.QType = append(rule.QType, int32(qType))
}
}
}
@@ -54,6 +49,11 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
rule.Domain = rules
}
if c.RCode > 65535 {
return nil, errors.New("rCode out of range: ", c.RCode)
}
rule.RCode = c.RCode
return rule, nil
}
@@ -133,28 +133,30 @@ func (c *DNSOutboundConfig) buildLegacyDNSPolicy() ([]*dns.DNSRuleConfig, error)
if c.BlockTypes != nil && len(*c.BlockTypes) > 0 {
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Drop}
if mode == "reject" {
rule.Action = dns.RuleAction_Reject
rule.Action = dns.RuleAction_Return
rule.RCode = 5
}
for _, qtype := range *c.BlockTypes {
if qtype < 0 || qtype > 65535 {
return nil, errors.New("legacy blockTypes qtype out of range: ", qtype)
for _, qType := range *c.BlockTypes {
if qType < 0 || qType > 65535 {
return nil, errors.New("legacy blockTypes qType out of range: ", qType)
}
rule.Qtype = append(rule.Qtype, qtype)
rule.QType = append(rule.QType, qType)
}
rules = append(rules, rule)
}
{
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Hijack}
rule.Qtype = append(rule.Qtype, 1)
rule.Qtype = append(rule.Qtype, 28)
rule.QType = append(rule.QType, 1)
rule.QType = append(rule.QType, 28)
rules = append(rules, rule)
}
{
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Reject}
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Return}
if mode == "reject" {
rule.Action = dns.RuleAction_Reject
rule.Action = dns.RuleAction_Return
rule.RCode = 5
} else if mode == "drop" {
rule.Action = dns.RuleAction_Drop
} else if mode == "skip" {
+24 -19
View File
@@ -35,10 +35,10 @@ func TestDnsProxyConfig(t *testing.T) {
Input: `{
"rules": [{
"action": "direct",
"qtype": "1,3,23-24"
"qType": "1,3,23-24"
}, {
"action": "drop",
"qtype": 28,
"qType": 28,
"domain": ["domain:example.com", "full:example.com"]
}]
}`,
@@ -48,11 +48,11 @@ func TestDnsProxyConfig(t *testing.T) {
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Direct,
Qtype: []int32{1, 3, 23, 24},
QType: []int32{1, 3, 23, 24},
},
{
Action: dns.RuleAction_Drop,
Qtype: []int32{28},
QType: []int32{28},
Domain: []*geodata.DomainRule{
{
Value: &geodata.DomainRule_Custom{
@@ -78,7 +78,8 @@ func TestDnsProxyConfig(t *testing.T) {
{
Input: `{
"rules": [{
"action": "reject",
"action": "return",
"rCode": 5,
"domain": "keyword:example"
}]
}`,
@@ -87,7 +88,8 @@ func TestDnsProxyConfig(t *testing.T) {
RewriteServer: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Reject,
Action: dns.RuleAction_Return,
RCode: 5,
Domain: []*geodata.DomainRule{
{
Value: &geodata.DomainRule_Custom{
@@ -106,7 +108,7 @@ func TestDnsProxyConfig(t *testing.T) {
Input: `{
"rules": [{
"action": "drop",
"qtype": 257
"qType": 257
}]
}`,
Parser: loadJSON(creator),
@@ -115,7 +117,7 @@ func TestDnsProxyConfig(t *testing.T) {
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Drop,
Qtype: []int32{257},
QType: []int32{257},
},
},
},
@@ -140,10 +142,11 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Hijack,
Qtype: []int32{1, 28},
QType: []int32{1, 28},
},
{
Action: dns.RuleAction_Reject,
Action: dns.RuleAction_Return,
RCode: 5,
},
},
},
@@ -157,15 +160,17 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
RewriteServer: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Reject,
Qtype: []int32{1, 65},
Action: dns.RuleAction_Return,
QType: []int32{1, 65},
RCode: 5,
},
{
Action: dns.RuleAction_Hijack,
Qtype: []int32{1, 28},
QType: []int32{1, 28},
},
{
Action: dns.RuleAction_Reject,
Action: dns.RuleAction_Return,
RCode: 5,
},
},
},
@@ -181,11 +186,11 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Drop,
Qtype: []int32{1},
QType: []int32{1},
},
{
Action: dns.RuleAction_Hijack,
Qtype: []int32{1, 28},
QType: []int32{1, 28},
},
{
Action: dns.RuleAction_Drop,
@@ -204,11 +209,11 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Drop,
Qtype: []int32{65, 28},
QType: []int32{65, 28},
},
{
Action: dns.RuleAction_Hijack,
Qtype: []int32{1, 28},
QType: []int32{1, 28},
},
{
Action: dns.RuleAction_Direct,
@@ -228,7 +233,7 @@ func TestDnsProxyConfigRejectsMixedLegacyAndNewFields(t *testing.T) {
_, err := loadJSON(creator)(`{
"rules": [{
"action": "direct",
"qtype": 65
"qType": 65
}],
"blockTypes": [65]
}`)
+9 -11
View File
@@ -1,9 +1,10 @@
package conf
import (
"google.golang.org/protobuf/proto"
"strings"
"google.golang.org/protobuf/proto"
"github.com/xtls/xray-core/app/observatory/burst"
"github.com/xtls/xray-core/app/router"
"github.com/xtls/xray-core/infra/conf/cfgcommon/duration"
@@ -16,17 +17,14 @@ const (
strategyLeastLoad string = "leastload"
)
var (
strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
strategyRandom: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) },
strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) },
}, "type", "settings")
)
var strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
strategyRandom: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) },
strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) },
}, "type", "settings")
type strategyEmptyConfig struct {
}
type strategyEmptyConfig struct{}
func (v *strategyEmptyConfig) Build() (proto.Message, error) {
return nil, nil
+9 -10
View File
@@ -78,18 +78,17 @@ func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
// byte-by-byte comment stripper and TeeReader, which are significant overhead on
// large configs.
func DecodeJSONConfigStrict(reader io.Reader) (*conf.Config, error) {
data, err := io.ReadAll(reader)
if err != nil {
return nil, errors.New("failed to read config file").Base(err)
}
jsonConfig := &conf.Config{}
if err := json.Unmarshal(data, jsonConfig); err != nil {
return nil, errors.New("failed to parse remote JSON config").Base(err)
}
return jsonConfig, nil
data, err := io.ReadAll(reader)
if err != nil {
return nil, errors.New("failed to read config file").Base(err)
}
jsonConfig := &conf.Config{}
if err := json.Unmarshal(data, jsonConfig); err != nil {
return nil, errors.New("failed to parse remote JSON config").Base(err)
}
return jsonConfig, nil
}
func LoadJSONConfig(reader io.Reader) (*core.Config, error) {
jsonConfig, err := DecodeJSONConfig(reader)
if err != nil {
+8 -8
View File
@@ -45,31 +45,31 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
Value: []string{utils.ChromeUA},
},
{
Name: "Sec-CH-UA",
Name: "Sec-CH-UA",
Value: []string{utils.ChromeUACH},
},
{
Name: "Sec-CH-UA-Mobile",
Name: "Sec-CH-UA-Mobile",
Value: []string{"?0"},
},
{
Name: "Sec-CH-UA-Platform",
Name: "Sec-CH-UA-Platform",
Value: []string{"Windows"},
},
{
Name: "Sec-Fetch-Mode",
Name: "Sec-Fetch-Mode",
Value: []string{"no-cors", "cors", "same-origin"},
},
{
Name: "Sec-Fetch-Dest",
Name: "Sec-Fetch-Dest",
Value: []string{"empty"},
},
{
Name: "Sec-Fetch-Site",
Name: "Sec-Fetch-Site",
Value: []string{"none"},
},
{
Name: "Sec-Fetch-User",
Name: "Sec-Fetch-User",
Value: []string{"?1"},
},
{
@@ -96,7 +96,7 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
}
if len(v.Path) > 0 {
config.Uri = append([]string(nil), (v.Path)...)
config.Uri = append([]string(nil), v.Path...)
}
if len(v.Headers) > 0 {
+212 -131
View File
@@ -6,6 +6,8 @@ import (
"encoding/hex"
"encoding/json"
"math"
"math/big"
"net/netip"
"net/url"
"os"
"regexp"
@@ -13,7 +15,6 @@ import (
"strconv"
"strings"
"syscall"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
@@ -23,15 +24,11 @@ import (
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dtls"
"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp"
"github.com/xtls/xray-core/transport/internet/finalmask/header/utp"
"github.com/xtls/xray-core/transport/internet/finalmask/header/wechat"
"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/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"
finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
@@ -48,12 +45,10 @@ import (
"google.golang.org/protobuf/proto"
)
var (
tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
"http": func() interface{} { return new(Authenticator) },
}, "type", "")
)
var tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
"http": func() interface{} { return new(Authenticator) },
}, "type", "")
type KCPConfig struct {
Mtu *uint32 `json:"mtu"`
@@ -225,8 +220,10 @@ type SplitHTTPConfig struct {
XPaddingPlacement string `json:"xPaddingPlacement"`
XPaddingMethod string `json:"xPaddingMethod"`
UplinkHTTPMethod string `json:"uplinkHTTPMethod"`
SessionPlacement string `json:"sessionPlacement"`
SessionKey string `json:"sessionKey"`
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"`
@@ -337,12 +334,12 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
return nil, errors.New("uplinkHTTPMethod can be GET only in packet-up mode")
}
switch c.SessionPlacement {
switch c.SessionIDPlacement {
case "":
c.SessionPlacement = "path"
c.SessionIDPlacement = "path"
case "path", "cookie", "header", "query":
default:
return nil, errors.New("unsupported session placement: " + c.SessionPlacement)
return nil, errors.New("unsupported session placement: " + c.SessionIDPlacement)
}
switch c.SeqPlacement {
@@ -353,12 +350,31 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
}
if c.SessionPlacement != "path" && c.SessionKey == "" {
switch c.SessionPlacement {
if c.SessionIDPlacement != "path" && c.SessionIDKey == "" {
switch c.SessionIDPlacement {
case "cookie", "query":
c.SessionKey = "x_session"
c.SessionIDKey = "x_session"
case "header":
c.SessionKey = "X-Session"
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")
}
}
}
@@ -408,9 +424,9 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
XPaddingPlacement: c.XPaddingPlacement,
XPaddingMethod: c.XPaddingMethod,
UplinkHTTPMethod: c.UplinkHTTPMethod,
SessionPlacement: c.SessionPlacement,
SessionIDPlacement: c.SessionIDPlacement,
SeqPlacement: c.SeqPlacement,
SessionKey: c.SessionKey,
SessionIDKey: c.SessionIDKey,
SeqKey: c.SeqKey,
UplinkDataPlacement: c.UplinkDataPlacement,
UplinkDataKey: c.UplinkDataKey,
@@ -422,6 +438,8 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
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),
@@ -445,6 +463,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
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
}
const (
Byte = 1
Kilobyte = 1024 * Byte
@@ -656,10 +685,8 @@ type TLSConfig struct {
MasterKeyLog string `json:"masterKeyLog"`
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
VerifyPeerCertByName string `json:"verifyPeerCertByName"`
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
ECHServerKeys string `json:"echServerKeys"`
ECHConfigList string `json:"echConfigList"`
ECHForceQuery string `json:"echForceQuery"`
ECHSocketSettings *SocketConfig `json:"echSockopt"`
}
@@ -704,12 +731,7 @@ func (c *TLSConfig) Build() (proto.Message, error) {
config.MasterKeyLog = c.MasterKeyLog
if c.AllowInsecure {
if time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) {
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
} else {
errors.LogWarning(context.Background(), `"allowInsecure" will be removed automatically after 2026-06-01, please use "pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT)`)
config.AllowInsecure = true
}
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn)`)
}
if c.PinnedPeerCertSha256 != "" {
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
@@ -728,10 +750,6 @@ func (c *TLSConfig) Build() (proto.Message, error) {
config.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)
}
}
if c.VerifyPeerCertInNames != nil {
return nil, errors.PrintRemovedFeatureError(`"verifyPeerCertInNames"`, `"verifyPeerCertByName"`)
}
if c.VerifyPeerCertByName != "" {
for v := range strings.SplitSeq(c.VerifyPeerCertByName, ",") {
v = strings.TrimSpace(v)
@@ -749,13 +767,6 @@ func (c *TLSConfig) Build() (proto.Message, error) {
}
config.EchServerKeys = EchPrivateKey
}
switch c.ECHForceQuery {
case "none", "half", "full", "":
config.EchForceQuery = c.ECHForceQuery
default:
return nil, errors.New(`invalid "echForceQuery": `, c.ECHForceQuery)
}
config.EchForceQuery = c.ECHForceQuery
config.EchConfigList = c.ECHConfigList
if c.ECHSocketSettings != nil {
ss, err := c.ECHSocketSettings.Build()
@@ -1037,7 +1048,7 @@ type HappyEyeballsConfig struct {
}
func (h *HappyEyeballsConfig) UnmarshalJSON(data []byte) error {
var innerHappyEyeballsConfig = struct {
innerHappyEyeballsConfig := struct {
PrioritizeIPv6 bool `json:"prioritizeIPv6"`
TryDelayMs uint64 `json:"tryDelayMs"`
Interleave uint32 `json:"interleave"`
@@ -1165,7 +1176,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
}
var happyEyeballs = &internet.HappyEyeballsConfig{Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 0, MaxConcurrentTry: 4}
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
@@ -1241,20 +1252,14 @@ var (
}, "type", "settings")
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
"header-dns": func() interface{} { return new(Dns) },
"header-dtls": func() interface{} { return new(Dtls) },
"header-srtp": func() interface{} { return new(Srtp) },
"header-utp": func() interface{} { return new(Utp) },
"header-wechat": func() interface{} { return new(Wechat) },
"header-wireguard": func() interface{} { return new(Wireguard) },
"mkcp-original": func() interface{} { return new(Original) },
"mkcp-aes128gcm": func() interface{} { return new(Aes128Gcm) },
"noise": func() interface{} { return new(NoiseMask) },
"salamander": func() interface{} { return new(Salamander) },
"sudoku": func() interface{} { return new(Sudoku) },
"xdns": func() interface{} { return new(Xdns) },
"xicmp": func() interface{} { return new(Xicmp) },
"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")
)
@@ -1734,82 +1739,70 @@ func (c *HeaderCustomUDP) Build() (proto.Message, error) {
})
}
return &custom.UDPConfig{
Client: client,
Server: server,
Mode: c.Mode,
}, nil
}
type Dns struct {
Domain string `json:"domain"`
}
func (c *Dns) Build() (proto.Message, error) {
config := &dns.Config{}
config.Domain = "www.baidu.com"
if len(c.Domain) > 0 {
config.Domain = c.Domain
if c.Mode == "standalone" {
return &custom.UDPStandaloneConfig{
Client: client,
Server: server,
}, nil
} else {
return &custom.UDPConfig{
Client: client,
Server: server,
}, nil
}
return config, nil
}
type Dtls struct{}
func (c *Dtls) Build() (proto.Message, error) {
return &dtls.Config{}, nil
type MkcpLegacy struct {
Header string `json:"header"`
Value string `json:"value"`
}
type Srtp struct{}
func (c *Srtp) Build() (proto.Message, error) {
return &srtp.Config{}, nil
}
type Utp struct{}
func (c *Utp) Build() (proto.Message, error) {
return &utp.Config{}, nil
}
type Wechat struct{}
func (c *Wechat) Build() (proto.Message, error) {
return &wechat.Config{}, nil
}
type Wireguard struct{}
func (c *Wireguard) Build() (proto.Message, error) {
return &wireguard.Config{}, nil
}
type Original struct{}
func (c *Original) Build() (proto.Message, error) {
return &original.Config{}, nil
}
type Aes128Gcm struct {
Password string `json:"password"`
}
func (c *Aes128Gcm) Build() (proto.Message, error) {
return &aes128gcm.Config{
Password: c.Password,
}, nil
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"`
Password string `json:"password"`
PacketSize *Int32Range `json:"packetSize"`
}
func (c *Salamander) Build() (proto.Message, error) {
config := &salamander.Config{}
config.Password = c.Password
return config, nil
if c.PacketSize != nil {
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 {
@@ -1885,23 +1878,111 @@ func (c *Xdns) Build() (proto.Message, error) {
}
type Xicmp struct {
ListenIp string `json:"listenIp"`
Id uint16 `json:"id"`
DGRAM bool `json:"dgram"`
IPs []string `json:"ips"`
}
func (c *Xicmp) Build() (proto.Message, error) {
config := &xicmp.Config{
Ip: c.ListenIp,
Id: int32(c.Id),
for _, ip := range c.IPs {
if _, err := netip.ParseAddr(ip); err != nil {
return nil, err
}
}
if config.Ip == "" {
config.Ip = "0.0.0.0"
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"`
+1 -2
View File
@@ -195,8 +195,7 @@ func TestHeaderCustomUDPBuild(t *testing.T) {
"mode": "standalone"
}`,
Parser: parser,
Output: &finalmaskcustom.UDPConfig{
Mode: "standalone",
Output: &finalmaskcustom.UDPStandaloneConfig{
Client: []*finalmaskcustom.UDPItem{
{
RandMax: 255,
+2 -1
View File
@@ -1,9 +1,10 @@
package conf
import (
"strconv"
"github.com/xtls/xray-core/app/version"
"github.com/xtls/xray-core/core"
"strconv"
)
type VersionConfig struct {
+3 -3
View File
@@ -177,7 +177,8 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
protocol := ss.GetEffectiveProtocol()
if (protocol == "websocket" || protocol == "httpupgrade" || protocol == "splithttp") &&
(c.StreamSetting.SocketSettings == nil || len(c.StreamSetting.SocketSettings.TrustedXForwardedFor) == 0) {
errors.LogWarning(context.Background(),
errors.LogWarning(
context.Background(),
`====== SECURITY WARNING ======`,
"\n",
`inbound "`, c.Tag, `" using `, protocol, ` has not configured "sockopt.trustedXForwardedFor".`,
@@ -285,7 +286,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if c.SendThrough != nil {
address := ParseSendThough(c.SendThrough)
//Check if CIDR exists
// Check if CIDR exists
if strings.Contains(*c.SendThrough, "/") {
senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1]
} else {
@@ -469,7 +470,6 @@ func (c *Config) Override(o *Config, fn string) {
c.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[i])
errors.LogInfo(context.Background(), "[", fn, "] appended inbound with tag: ", o.InboundConfigs[i].Tag)
}
}
}
+62 -19
View File
@@ -12,7 +12,16 @@ import (
"strings"
)
var directory = flag.String("pwd", "", "Working directory of Xray vformat.")
var (
directory = flag.String("pwd", "", "Working directory of Xray vformat.")
action = flag.String("mode", "format", "Execution mode. Default is 'format'.\n'format' formatting source files and save changes to files.\n'check' list all paths of improper formatted file.\n'dryrun' formatting source files and shows all diffs, but will not make any changes to files.")
)
var (
isCheck bool
isDryrun bool
isFormat bool
)
// envFile returns the name of the Go environment configuration file.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
@@ -90,9 +99,10 @@ func Run(binary string, args []string) ([]byte, error) {
return output, nil
}
func RunMany(binary string, args, files []string) {
fmt.Println("Processing...")
func RunMany(binary string, args, files []string) bool {
fmt.Println("Processing with", binary, args, "...")
formatRequired := false
maxTasks := make(chan struct{}, runtime.NumCPU())
for _, file := range files {
maxTasks <- struct{}{}
@@ -102,10 +112,12 @@ func RunMany(binary string, args, files []string) {
fmt.Println(err)
} else if len(output) > 0 {
fmt.Println(string(output))
formatRequired = true
}
<-maxTasks
}(file)
}
return formatRequired
}
func main() {
@@ -124,6 +136,19 @@ func main() {
*directory = filepath.Join(pwd, *directory)
}
switch *action {
case "format":
isFormat = true
case "check":
isCheck = true
case "dryrun":
isCheck = true
isDryrun = true
default:
fmt.Println("Unrecognized 'mode'. Will format all source files and save changes.")
isFormat = true
}
pwd := *directory
GOBIN := GetGOBIN()
binPath := os.Getenv("PATH")
@@ -136,7 +161,6 @@ func main() {
suffix = ".exe"
}
gofmt := "gofumpt" + suffix
goimports := "gci" + suffix
if gofmtPath, err := exec.LookPath(gofmt); err != nil {
fmt.Println("Can not find", gofmt, "in system path or current working directory.")
@@ -145,13 +169,6 @@ func main() {
gofmt = gofmtPath
}
if goimportsPath, err := exec.LookPath(goimports); err != nil {
fmt.Println("Can not find", goimports, "in system path or current working directory.")
os.Exit(1)
} else {
goimports = goimportsPath
}
rawFilesSlice := make([]string, 0, 1000)
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
if err != nil {
@@ -179,15 +196,41 @@ func main() {
os.Exit(1)
}
gofmtArgs := []string{
"-s", "-l", "-e", "-w",
if isFormat {
gofmtArgs := []string{
"-l", "-e", "-w",
}
fmt.Println("Formatting Go source files...")
RunMany(gofmt, gofmtArgs, rawFilesSlice)
fmt.Println("Do NOT forget to commit file changes.")
}
goimportsArgs := []string{
"write",
}
if isCheck {
gofmtListArgs := []string{
"-l", "-e",
}
RunMany(gofmt, gofmtArgs, rawFilesSlice)
RunMany(goimports, goimportsArgs, rawFilesSlice)
fmt.Println("Do NOT forget to commit file changes.")
fmt.Println("Checking files thar are not properly formatted...")
formatRequired := RunMany(gofmt, gofmtListArgs, rawFilesSlice)
if formatRequired {
fmt.Println("Format problem(s) found.")
}
if isDryrun {
if formatRequired {
gofmtShowArgs := []string{
"-d", "-e",
}
RunMany(gofmt, gofmtShowArgs, rawFilesSlice)
}
}
if formatRequired {
fmt.Println("Please run 'go install -v mvdan.cc/gofumpt@latest', then run 'go run ./infra/vformat/main.go' to format the Go source files.")
os.Exit(1)
} else {
fmt.Println("All Go source file format check has been passed.")
}
}
}
-1
View File
@@ -59,7 +59,6 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
}
showBalancerInfo(resp.Balancer)
}
func showBalancerInfo(b *routerService.BalancerMsg) {
+2 -1
View File
@@ -62,7 +62,8 @@ func addInboundUserAction(ctx context.Context, client handlerService.HandlerServ
Operation: cserial.ToTypedMessage(
&handlerService.AddUserOperation{
User: user,
}),
},
),
})
return err
}
+2 -1
View File
@@ -50,7 +50,8 @@ func executeRemoveUsers(cmd *base.Command, args []string) {
Operation: cserial.ToTypedMessage(
&handlerService.RemoveUserOperation{
Email: email,
}),
},
),
})
if err == nil {
success += 1
+1 -4
View File
@@ -38,9 +38,7 @@ Example:
}
func executeAddRules(cmd *base.Command, args []string) {
var (
shouldAppend bool
)
var shouldAppend bool
setSharedFlags(cmd)
cmd.Flag.BoolVar(&shouldAppend, "append", false, "")
cmd.Flag.Parse(args)
@@ -96,5 +94,4 @@ func executeAddRules(cmd *base.Command, args []string) {
}
showJSONResponse(resp)
}
}
-1
View File
@@ -56,5 +56,4 @@ func executeRemoveRules(cmd *base.Command, args []string) {
}
showJSONResponse(resp)
}
}
-1
View File
@@ -136,5 +136,4 @@ func executeSourceIpBlock(cmd *base.Command, args []string) {
base.Fatalf("failed to perform AddRule: %s", err)
}
showJSONResponse(resp)
}
-1
View File
@@ -38,7 +38,6 @@ Examples:
}
func executeTypedMessageToJson(cmd *base.Command, args []string) {
var injectTypeInfo bool
cmd.Flag.BoolVar(&injectTypeInfo, "t", false, "")
cmd.Flag.BoolVar(&injectTypeInfo, "type", false, "")
+1 -2
View File
@@ -41,7 +41,6 @@ Examples:
}
func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
var optFile string
var optDump bool
var optType bool
@@ -60,7 +59,7 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
}
if len(optFile) > 0 {
switch core.GetFormat(optFile){
switch core.GetFormat(optFile) {
case "protobuf", "":
fmt.Println("Output ProtoBuf file is ", optFile)
default:
+4 -2
View File
@@ -34,8 +34,10 @@ func init() {
var input_echServerKeys = cmdECH.Flag.String("i", "", "ECHServerKeys (base64.StdEncoding)")
// var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
var (
input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
)
func executeECH(cmd *base.Command, args []string) {
var kem uint16
+4 -2
View File
@@ -21,8 +21,10 @@ func init() {
cmdX25519.Run = executeX25519 // break init loop
}
var input_stdEncoding = cmdX25519.Flag.Bool("std-encoding", false, "")
var input_x25519 = cmdX25519.Flag.String("i", "", "")
var (
input_stdEncoding = cmdX25519.Flag.Bool("std-encoding", false, "")
input_x25519 = cmdX25519.Flag.String("i", "", "")
)
func executeX25519(cmd *base.Command, args []string) {
Curve25519Genkey(false, *input_x25519)
+1 -3
View File
@@ -12,9 +12,7 @@ type (
configFileLoader func(string) (io.Reader, error)
)
var (
EffectiveConfigFileLoader configFileLoader
)
var EffectiveConfigFileLoader configFileLoader
// LoadConfig reads from a path/url/stdin
// actual work is in external module
+10 -12
View File
@@ -52,7 +52,7 @@ func ConfigLoader(arg string) (out io.Reader, err error) {
// When the ":/" separator is omitted on a socket target, the request is
// made to "/".
func FetchHTTPContent(target string) ([]byte, error) {
httpURL, socketPath := utils.SplitHTTPUnixURL(target)
httpURL, socketPath := utils.SplitHTTPUnixURL(target)
parsedTarget, err := url.Parse(httpURL)
if err != nil {
@@ -63,15 +63,15 @@ func FetchHTTPContent(target string) ([]byte, error) {
Timeout: 30 * time.Second,
}
if socketPath != "" {
dialAddr := utils.ResolveSocketPath(socketPath)
client.Transport = &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", dialAddr)
},
}
}
if socketPath != "" {
dialAddr := utils.ResolveSocketPath(socketPath)
client.Transport = &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", dialAddr)
},
}
}
resp, err := client.Do(&http.Request{
Method: "GET",
@@ -95,7 +95,6 @@ func FetchHTTPContent(target string) ([]byte, error) {
return content, nil
}
// isRemoteSource reports whether arg should be fetched via HTTP (regular
// network or Unix socket) rather than read from the local filesystem.
// Recognized forms:
@@ -124,7 +123,6 @@ func isRemoteSource(arg string) bool {
return err == nil && info.Mode()&os.ModeSocket != 0
}
// httpUnixToCanonical converts the deprecated http+unix:///path/to/socket.sock/api
// URL into the canonical /path/to/socket.sock:/api form by inserting ":"
// between the ".sock" extension and the HTTP path. Inputs without a path
+20 -11
View File
@@ -28,7 +28,7 @@ type RuleAction int32
const (
RuleAction_Direct RuleAction = 0
RuleAction_Drop RuleAction = 1
RuleAction_Reject RuleAction = 2
RuleAction_Return RuleAction = 2
RuleAction_Hijack RuleAction = 3
)
@@ -37,13 +37,13 @@ var (
RuleAction_name = map[int32]string{
0: "Direct",
1: "Drop",
2: "Reject",
2: "Return",
3: "Hijack",
}
RuleAction_value = map[string]int32{
"Direct": 0,
"Drop": 1,
"Reject": 2,
"Return": 2,
"Hijack": 3,
}
)
@@ -78,8 +78,9 @@ func (RuleAction) EnumDescriptor() ([]byte, []int) {
type DNSRuleConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.dns.RuleAction" json:"action,omitempty"`
Qtype []int32 `protobuf:"varint,2,rep,packed,name=qtype,proto3" json:"qtype,omitempty"`
QType []int32 `protobuf:"varint,2,rep,packed,name=q_type,json=qType,proto3" json:"q_type,omitempty"`
Domain []*geodata.DomainRule `protobuf:"bytes,3,rep,name=domain,proto3" json:"domain,omitempty"`
RCode uint32 `protobuf:"varint,4,opt,name=r_code,json=rCode,proto3" json:"r_code,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -121,9 +122,9 @@ func (x *DNSRuleConfig) GetAction() RuleAction {
return RuleAction_Direct
}
func (x *DNSRuleConfig) GetQtype() []int32 {
func (x *DNSRuleConfig) GetQType() []int32 {
if x != nil {
return x.Qtype
return x.QType
}
return nil
}
@@ -135,6 +136,13 @@ func (x *DNSRuleConfig) GetDomain() []*geodata.DomainRule {
return nil
}
func (x *DNSRuleConfig) GetRCode() uint32 {
if x != nil {
return x.RCode
}
return 0
}
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
UserLevel uint32 `protobuf:"varint,1,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
@@ -199,11 +207,12 @@ var File_proxy_dns_config_proto protoreflect.FileDescriptor
const file_proxy_dns_config_proto_rawDesc = "" +
"\n" +
"\x16proxy/dns/config.proto\x12\x0exray.proxy.dns\x1a\x1ccommon/net/destination.proto\x1a\x1bcommon/geodata/geodat.proto\"\x92\x01\n" +
"\x16proxy/dns/config.proto\x12\x0exray.proxy.dns\x1a\x1ccommon/net/destination.proto\x1a\x1bcommon/geodata/geodat.proto\"\xaa\x01\n" +
"\rDNSRuleConfig\x122\n" +
"\x06action\x18\x01 \x01(\x0e2\x1a.xray.proxy.dns.RuleActionR\x06action\x12\x14\n" +
"\x05qtype\x18\x02 \x03(\x05R\x05qtype\x127\n" +
"\x06domain\x18\x03 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\"\x9c\x01\n" +
"\x06action\x18\x01 \x01(\x0e2\x1a.xray.proxy.dns.RuleActionR\x06action\x12\x15\n" +
"\x06q_type\x18\x02 \x03(\x05R\x05qType\x127\n" +
"\x06domain\x18\x03 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\x12\x15\n" +
"\x06r_code\x18\x04 \x01(\rR\x05rCode\"\x9c\x01\n" +
"\x06Config\x12\x1d\n" +
"\n" +
"user_level\x18\x01 \x01(\rR\tuserLevel\x121\n" +
@@ -215,7 +224,7 @@ const file_proxy_dns_config_proto_rawDesc = "" +
"\x06Direct\x10\x00\x12\b\n" +
"\x04Drop\x10\x01\x12\n" +
"\n" +
"\x06Reject\x10\x02\x12\n" +
"\x06Return\x10\x02\x12\n" +
"\n" +
"\x06Hijack\x10\x03BL\n" +
"\x12com.xray.proxy.dnsP\x01Z#github.com/xtls/xray-core/proxy/dns\xaa\x02\x0eXray.Proxy.Dnsb\x06proto3"
+3 -2
View File
@@ -12,14 +12,15 @@ import "common/geodata/geodat.proto";
enum RuleAction {
Direct = 0;
Drop = 1;
Reject = 2;
Return = 2;
Hijack = 3;
}
message DNSRuleConfig {
RuleAction action = 1;
repeated int32 qtype = 2;
repeated int32 q_type = 2;
repeated xray.common.geodata.DomainRule domain = 3;
uint32 r_code = 4;
}
message Config {
+41 -43
View File
@@ -45,6 +45,7 @@ type DNSRule struct {
action RuleAction
qTypes []uint16
domains geodata.DomainMatcher
rCode dnsmessage.RCode
}
func (r *DNSRule) matchQType(qType uint16) bool {
@@ -95,9 +96,10 @@ func (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager polic
for _, r := range config.Rule {
rule := &DNSRule{
action: r.Action,
qTypes: make([]uint16, 0, len(r.Qtype)),
qTypes: make([]uint16, 0, len(r.QType)),
rCode: dnsmessage.RCode(r.RCode),
}
for _, t := range r.Qtype {
for _, t := range r.QType {
rule.qTypes = append(rule.qTypes, uint16(t))
}
if len(r.Domain) > 0 {
@@ -136,17 +138,17 @@ func parseQuery(b []byte) (id uint16, qType dnsmessage.Type, domain string, ok b
return
}
func (h *Handler) applyRules(qType dnsmessage.Type, domain string) RuleAction {
func (h *Handler) applyRules(qType dnsmessage.Type, domain string) (RuleAction, dnsmessage.RCode) {
qCode := uint16(qType)
for _, r := range h.rules {
if r.Apply(qCode, domain) {
return r.action
return r.action, r.rCode
}
}
if qType == dnsmessage.TypeA || qType == dnsmessage.TypeAAAA {
return RuleAction_Hijack
return RuleAction_Hijack, dnsmessage.RCodeSuccess
}
return RuleAction_Reject
return RuleAction_Return, dnsmessage.RCodeSuccess
}
// Process implements proxy.Outbound.
@@ -213,7 +215,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
}
if session.TimeoutOnlyFromContext(ctx) {
ctx, _ = context.WithCancel(context.Background())
ctx = context.Background()
}
ctx, cancel := context.WithCancel(ctx)
@@ -250,21 +252,22 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
continue
}
switch h.applyRules(qType, domain) {
action, rCode := h.applyRules(qType, domain)
switch action {
case RuleAction_Drop:
b.Release()
errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain)
case RuleAction_Reject:
case RuleAction_Return:
b.Release()
errors.LogInfo(ctx, "rejected type ", qType, " query for domain ", domain)
if err := h.rejectNonIPQuery(id, qType, domain, writer); err != nil {
if err := h.rejectNonIPQuery(id, qType, domain, writer, rCode); err != nil {
return err
}
case RuleAction_Hijack:
b.Release()
if qType != dnsmessage.TypeA && qType != dnsmessage.TypeAAAA {
errors.LogError(ctx, "can only hijack A/AAAA records")
if err := h.rejectNonIPQuery(id, qType, domain, writer); err != nil {
if err := h.rejectNonIPQuery(id, qType, domain, writer, rCode); err != nil {
return err
}
} else {
@@ -309,48 +312,35 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter, timer *signal.ActivityTimer) {
var ips []net.IP
var ttl uint32
var err error
var ttl4 uint32
var ttl6 uint32
switch qType {
case dnsmessage.TypeA:
ips, ttl4, err = h.client.LookupIP(domain, dns.IPOption{
ips, ttl, err = h.client.LookupIP(domain, dns.IPOption{
IPv4Enable: true,
IPv6Enable: false,
FakeEnable: true,
})
case dnsmessage.TypeAAAA:
ips, ttl6, err = h.client.LookupIP(domain, dns.IPOption{
ips, ttl, err = h.client.LookupIP(domain, dns.IPOption{
IPv4Enable: false,
IPv6Enable: true,
FakeEnable: true,
})
}
rcode := dns.RCodeFromError(err)
if rcode == 0 && len(ips) == 0 && !go_errors.Is(err, dns.ErrEmptyResponse) {
rCode := dns.RCodeFromError(err)
if rCode == 0 && len(ips) == 0 && !go_errors.Is(err, dns.ErrEmptyResponse) {
errors.LogInfoInner(context.Background(), err, "ip query")
return
}
switch qType {
case dnsmessage.TypeA:
for i, ip := range ips {
ips[i] = ip.To4()
}
case dnsmessage.TypeAAAA:
for i, ip := range ips {
ips[i] = ip.To16()
}
}
b := buf.New()
rawBytes := b.Extend(buf.Size)
builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{
ID: id,
RCode: dnsmessage.RCode(rcode),
RCode: dnsmessage.RCode(rCode),
RecursionAvailable: true,
RecursionDesired: true,
Response: true,
@@ -365,17 +355,25 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
}))
common.Must(builder.StartAnswers())
rHeader4 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl4}
rHeader6 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl6}
for _, ip := range ips {
if len(ip) == net.IPv4len {
var r dnsmessage.AResource
copy(r.A[:], ip)
common.Must(builder.AResource(rHeader4, r))
} else {
var r dnsmessage.AAAAResource
copy(r.AAAA[:], ip)
common.Must(builder.AAAAResource(rHeader6, r))
rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl}
switch qType {
case dnsmessage.TypeA:
for _, ip := range ips {
ip = ip.To4()
if len(ip) == net.IPv4len {
var r dnsmessage.AResource
copy(r.A[:], ip)
common.Must(builder.AResource(rHeader, r))
}
}
case dnsmessage.TypeAAAA:
for _, ip := range ips {
ip = ip.To16()
if len(ip) == net.IPv6len {
var r dnsmessage.AAAAResource
copy(r.AAAA[:], ip)
common.Must(builder.AAAAResource(rHeader, r))
}
}
}
msgBytes, err := builder.Finish()
@@ -392,7 +390,7 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
}
}
func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) error {
func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter, rCode dnsmessage.RCode) error {
domainT := strings.TrimSuffix(domain, ".")
if domainT == "" {
return errors.New("empty domain name")
@@ -401,7 +399,7 @@ func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain stri
rawBytes := b.Extend(buf.Size)
builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{
ID: id,
RCode: dnsmessage.RCodeRefused,
RCode: rCode,
RecursionAvailable: true,
RecursionDesired: true,
Response: true,
+4 -3
View File
@@ -424,7 +424,7 @@ func TestDNSRules(t *testing.T) {
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{
Rule: []*dns_proxy.DNSRuleConfig{
{
Qtype: []int32{int32(dns.TypeA)},
QType: []int32{int32(dns.TypeA)},
Domain: []*geodata.DomainRule{
{
Value: &geodata.DomainRule_Custom{
@@ -438,7 +438,7 @@ func TestDNSRules(t *testing.T) {
Action: dns_proxy.RuleAction_Direct,
},
{
Qtype: []int32{int32(dns.TypeA)},
QType: []int32{int32(dns.TypeA)},
Domain: []*geodata.DomainRule{
{
Value: &geodata.DomainRule_Custom{
@@ -449,7 +449,8 @@ func TestDNSRules(t *testing.T) {
},
},
},
Action: dns_proxy.RuleAction_Reject,
Action: dns_proxy.RuleAction_Return,
RCode: 5,
},
},
}),
+5 -3
View File
@@ -190,9 +190,11 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
}
}
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: reader,
Writer: writer},
if err := dispatcher.DispatchLink(
ctx, dest, &transport.Link{
Reader: reader,
Writer: writer,
},
); err != nil {
return errors.New("failed to dispatch request").Base(err)
}
+9 -7
View File
@@ -30,10 +30,12 @@ import (
"github.com/xtls/xray-core/transport/internet/stat"
)
var useSplice bool
var allNetworks [8]bool
var defaultBlockPrivateRule *FinalRule
var defaultBlockAllRule *FinalRule
var (
useSplice bool
allNetworks [8]bool
defaultBlockPrivateRule *FinalRule
defaultBlockAllRule *FinalRule
)
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
@@ -677,7 +679,7 @@ type NoisePacketWriter struct {
func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if w.firstWrite {
w.firstWrite = false
//Do not send Noise for dns requests(just to be safe)
// Do not send Noise for dns requests(just to be safe)
if w.UDPOverride.Port == 53 {
return w.Writer.WriteMultiBuffer(mb)
}
@@ -700,11 +702,11 @@ func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
default:
panic("unreachable, applyTo is ip/ipv4/ipv6")
}
//User input string or base64 encoded string or hex string
// User input string or base64 encoded string or hex string
if n.Packet != nil {
noise = n.Packet
} else {
//Random noise
// Random noise
noise, err = GenerateRandomBytes(crypto.RandBetween(int64(n.LengthMin),
int64(n.LengthMax)))
}
+5 -3
View File
@@ -192,9 +192,11 @@ func (s *Server) handleConnect(ctx context.Context, _ *http.Request, buffer *buf
if inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: reader,
Writer: buf.NewWriter(conn)},
if err := dispatcher.DispatchLink(
ctx, dest, &transport.Link{
Reader: reader,
Writer: buf.NewWriter(conn),
},
); err != nil {
return errors.New("failed to dispatch request").Base(err)
}
+1 -1
View File
@@ -113,7 +113,7 @@ func (v *Validator) GetAll() []*protocol.MemoryUser {
v.mutex.Lock()
defer v.mutex.Unlock()
var users = make([]*protocol.MemoryUser, 0, len(v.users))
users := make([]*protocol.MemoryUser, 0, len(v.users))
for _, user := range v.users {
users = append(users, user)
}
+3
View File
@@ -253,6 +253,9 @@ func FragUDPMessage(m *UDPMessage, maxSize int) []UDPMessage {
}
fullPayload := m.Data
maxPayloadSize := maxSize - m.HeaderSize()
if maxPayloadSize <= 0 {
return nil
}
off := 0
fragID := uint8(0)
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
+1 -1
View File
@@ -743,7 +743,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
for {
inbound := session.InboundFromContext(ctx)
outbounds := session.OutboundsFromContext(ctx)
var splice = inbound.CanSpliceCopy == 1
splice := inbound.CanSpliceCopy == 1
for _, ob := range outbounds {
if ob.CanSpliceCopy != 1 {
splice = false
+32 -13
View File
@@ -1,14 +1,17 @@
package socks
import (
"context"
"encoding/binary"
"io"
gonet "net"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"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/transport/internet"
)
const (
@@ -137,13 +140,13 @@ func (s *ServerSession) auth5(nMethod byte, reader io.Reader, writer io.Writer)
return "", nil
}
func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer net.Conn) (*protocol.RequestHeader, *TempUDPConn, error) {
var (
username string
err error
)
if username, err = s.auth5(nMethod, reader, writer); err != nil {
return nil, err
return nil, nil, err
}
var cmd byte
@@ -151,7 +154,7 @@ func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer io.Wri
buffer := buf.StackNew()
if _, err := buffer.ReadFullFrom(reader, 3); err != nil {
buffer.Release()
return nil, errors.New("failed to read request").Base(err)
return nil, nil, errors.New("failed to read request").Base(err)
}
cmd = buffer.Byte(1)
buffer.Release()
@@ -168,28 +171,29 @@ func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer io.Wri
case cmdUDPAssociate:
if !s.config.UdpEnabled {
writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))
return nil, errors.New("UDP is not enabled.")
return nil, nil, errors.New("UDP is not enabled.")
}
request.Command = protocol.RequestCommandUDP
case cmdTCPBind:
writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))
return nil, errors.New("TCP bind is not supported.")
return nil, nil, errors.New("TCP bind is not supported.")
default:
writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))
return nil, errors.New("unknown command ", cmd)
return nil, nil, errors.New("unknown command ", cmd)
}
request.Version = socks5Version
addr, port, err := addrParser.ReadAddressPort(nil, reader)
if err != nil {
return nil, errors.New("failed to read address").Base(err)
return nil, nil, errors.New("failed to read address").Base(err)
}
request.Address = addr
request.Port = port
responseAddress := s.address
responsePort := s.port
var tempUDPConn *TempUDPConn
//nolint:gocritic // Use if else chain for clarity
if request.Command == protocol.RequestCommandUDP {
if s.config.Address != nil {
@@ -199,20 +203,34 @@ func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer io.Wri
// Use conn.LocalAddr() IP as remote address in the response by default
responseAddress = s.localAddress
}
udpHub, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: responseAddress.IP(), Port: 0}, nil)
if err != nil {
return nil, nil, errors.New("failed to create UDP listener").Base(err)
}
responsePort = net.Port(udpHub.LocalAddr().(*net.UDPAddr).Port)
expectedRemote := &gonet.UDPAddr{}
if request.Address.IP().IsUnspecified() {
expectedRemote.IP = writer.RemoteAddr().(*net.TCPAddr).IP // unix?
} else {
expectedRemote.IP = request.Address.IP() // panic?
expectedRemote.Port = int(request.Port) // 0 is allowed
}
tempUDPConn = NewTempUDPConn(udpHub, writer, expectedRemote)
}
if err := writeSocks5Response(writer, statusSuccess, responseAddress, responsePort); err != nil {
return nil, err
common.CloseIfExists(tempUDPConn)
return nil, nil, err
}
return request, nil
return request, tempUDPConn, nil
}
// Handshake performs a Socks4/4a/5 handshake.
func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
func (s *ServerSession) Handshake(reader io.Reader, writer net.Conn) (*protocol.RequestHeader, *TempUDPConn, error) {
buffer := buf.StackNew()
if _, err := buffer.ReadFullFrom(reader, 2); err != nil {
buffer.Release()
return nil, errors.New("insufficient header").Base(err)
return nil, nil, errors.New("insufficient header").Base(err)
}
version := buffer.Byte(0)
@@ -221,11 +239,12 @@ func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol
switch version {
case socks4Version:
return s.handshake4(cmd, reader, writer)
header, err := s.handshake4(cmd, reader, writer)
return header, nil, err
case socks5Version:
return s.handshake5(cmd, reader, writer)
default:
return nil, errors.New("unknown Socks version: ", version)
return nil, nil, errors.New("unknown Socks version: ", version)
}
}
+21 -27
View File
@@ -29,7 +29,6 @@ type Server struct {
config *ServerConfig
policyManager policy.Manager
cone bool
udpFilter *UDPFilter
httpServer *http.Server
}
@@ -46,7 +45,6 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
}
if config.AuthType == AuthType_PASSWORD {
httpConfig.Accounts = config.Accounts
s.udpFilter = new(UDPFilter) // We only use this when auth is enabled
}
s.httpServer, _ = http.NewServer(ctx, httpConfig)
return s, nil
@@ -60,11 +58,7 @@ func (s *Server) policy() policy.Session {
// Network implements proxy.Inbound.
func (s *Server) Network() []net.Network {
list := []net.Network{net.Network_TCP}
if s.config.UdpEnabled {
list = append(list, net.Network_UDP)
}
return list
return []net.Network{net.Network_TCP}
}
// Process implements proxy.Inbound.
@@ -94,8 +88,6 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
return s.httpServer.ProcessWithFirstbyte(ctx, network, conn, dispatcher, firstbyte...)
}
return s.processTCP(ctx, conn, dispatcher, firstbyte)
case net.Network_UDP:
return s.handleUDPPayload(ctx, conn, dispatcher)
default:
return errors.New("unknown network: ", network)
}
@@ -126,7 +118,8 @@ func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatche
Reader: buf.NewReader(conn),
Buffer: buf.MultiBuffer{buf.FromBytes(firstbyte)},
}
request, err := svrSession.Handshake(reader, conn)
request, tempUDPConn, err := svrSession.Handshake(reader, conn)
defer common.CloseIfExists(tempUDPConn)
if err != nil {
if inbound.Source.IsValid() {
log.Record(&log.AccessMessage{
@@ -160,9 +153,11 @@ func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatche
if inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: reader,
Writer: buf.NewWriter(conn)},
if err := dispatcher.DispatchLink(
ctx, dest, &transport.Link{
Reader: reader,
Writer: buf.NewWriter(conn),
},
); err != nil {
return errors.New("failed to dispatch request").Base(err)
}
@@ -170,26 +165,25 @@ func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatche
}
if request.Command == protocol.RequestCommandUDP {
if s.udpFilter != nil {
s.udpFilter.Add(conn.RemoteAddr())
if tempUDPConn == nil {
return errors.New("UDP associate with listen port failed")
}
return s.handleUDP(conn)
tempUDPConn.SetTimeout(plcy.Timeouts.ConnectionIdle)
errCh := make(chan error, 1)
go func() {
errCh <- s.handleUDPPayload(ctx, tempUDPConn, dispatcher)
}()
// Associated TCP keeps the UDP alive
// Close UDP if TCP connection is closed
// Or Close TCP if UDP is idle timeout
io.Copy(buf.DiscardBytes, conn)
tempUDPConn.Close()
return <-errCh
}
return nil
}
func (*Server) handleUDP(c io.Reader) error {
// The TCP connection closes after this method returns. We need to wait until
// the client closes it.
return common.Error2(io.Copy(buf.DiscardBytes, c))
}
func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {
if s.udpFilter != nil && !s.udpFilter.Check(conn.RemoteAddr()) {
errors.LogDebug(ctx, "Unauthorized UDP access from ", conn.RemoteAddr().String())
return nil
}
udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
payload := packet.Payload
errors.LogDebug(ctx, "writing back UDP response with ", payload.Len(), " bytes")
+72
View File
@@ -0,0 +1,72 @@
package socks
import (
"context"
"net"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/signal"
)
func NewTempUDPConn(udpConn net.PacketConn, tcpConn net.Conn, expectedRemote *net.UDPAddr) *TempUDPConn {
t := &TempUDPConn{
PacketConn: udpConn,
AssociatedTCPConn: tcpConn,
}
t.ExpectedRemote.Store(expectedRemote)
return t
}
// TempUDPConn wait for the first packet to determine the remote address
// SetTimeout MUST be called before any read/write operation
type TempUDPConn struct {
net.PacketConn
AssociatedTCPConn net.Conn
ExpectedRemote atomic.Pointer[net.UDPAddr]
Timer *signal.ActivityTimer
}
func (c *TempUDPConn) Read(b []byte) (n int, err error) {
var remote net.Addr
for {
n, remote, err = c.PacketConn.ReadFrom(b)
if err != nil {
return
}
remote := remote.(*net.UDPAddr)
expected := c.ExpectedRemote.Load()
if remote.IP.Equal(expected.IP) {
if remote.Port == expected.Port {
c.Timer.Update()
return
}
if expected.Port == 0 {
c.ExpectedRemote.Store(remote)
c.Timer.Update()
return
}
}
}
}
func (c *TempUDPConn) Write(b []byte) (n int, err error) {
c.Timer.Update()
return c.PacketConn.WriteTo(b, c.ExpectedRemote.Load())
}
func (c *TempUDPConn) RemoteAddr() net.Addr {
return c.ExpectedRemote.Load()
}
func (c *TempUDPConn) SetTimeout(d time.Duration) {
c.Timer = signal.CancelAfterInactivity(context.Background(), func() {
c.Close()
}, d)
}
func (c *TempUDPConn) Close() error {
c.Timer.SetTimeout(0)
c.AssociatedTCPConn.Close()
return c.PacketConn.Close()
}
-31
View File
@@ -1,31 +0,0 @@
package socks
import (
"net"
"sync"
)
/*
In the sock implementation of * ray, UDP authentication is flawed and can be bypassed.
Tracking a UDP connection may be a bit troublesome.
Here is a simple solution.
We create a filter, add remote IP to the pool when it try to establish a UDP connection with auth.
And drop UDP packets from unauthorized IP.
After discussion, we believe it is not necessary to add a timeout mechanism to this filter.
*/
type UDPFilter struct {
ips sync.Map
}
func (f *UDPFilter) Add(addr net.Addr) bool {
ip, _, _ := net.SplitHostPort(addr.String())
f.ips.Store(ip, true)
return true
}
func (f *UDPFilter) Check(addr net.Addr) bool {
ip, _, _ := net.SplitHostPort(addr.String())
_, ok := f.ips.Load(ip)
return ok
}
+1
View File
@@ -4,6 +4,7 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"google.golang.org/protobuf/proto"
"github.com/xtls/xray-core/common"
-1
View File
@@ -312,7 +312,6 @@ func (s *Server) handleUDPPayload(ctx context.Context, sessionPolicy policy.Sess
}
}
}
}
if err := task.Run(ctx, requestDone); err != nil {
+1 -1
View File
@@ -63,7 +63,7 @@ func (v *Validator) GetByEmail(email string) *protocol.MemoryUser {
// Get all users
func (v *Validator) GetAll() []*protocol.MemoryUser {
var u = make([]*protocol.MemoryUser, 0, 100)
u := make([]*protocol.MemoryUser, 0, 100)
v.email.Range(func(key, value interface{}) bool {
u = append(u, value.(*protocol.MemoryUser))
return true
+1 -1
View File
@@ -69,7 +69,7 @@ func (t *stackGVisor) Start() error {
tcpForwarder := tcp.NewForwarder(ipStack, 0, 65535, func(r *tcp.ForwarderRequest) {
go func(r *tcp.ForwarderRequest) {
var wq waiter.Queue
var id = r.ID()
id := r.ID()
// Perform a TCP three-way handshake.
ep, err := r.CreateEndpoint(&wq)
-2
View File
@@ -68,7 +68,6 @@ func (e *LinkEndpoint) IsAttached() bool {
}
func (e *LinkEndpoint) Wait() {
}
func (e *LinkEndpoint) ARPHardwareType() header.ARPHardwareType {
@@ -91,7 +90,6 @@ func (e *LinkEndpoint) Close() {
}
func (e *LinkEndpoint) SetOnCloseAction(_ func()) {
}
func (e *LinkEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
+4 -2
View File
@@ -44,8 +44,10 @@ type DarwinTun struct {
ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system)
}
var _ Tun = (*DarwinTun)(nil)
var _ GVisorDevice = (*DarwinTun)(nil)
var (
_ Tun = (*DarwinTun)(nil)
_ GVisorDevice = (*DarwinTun)(nil)
)
func NewTun(options *Config) (Tun, error) {
// Check if fd is provided via environment (iOS mode)
+1 -2
View File
@@ -9,8 +9,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
type DefaultTun struct {
}
type DefaultTun struct{}
// DefaultTun implements Tun
var _ Tun = (*DefaultTun)(nil)
+4 -2
View File
@@ -27,8 +27,10 @@ type FreeBSDTun struct {
mtu uint32
}
var _ Tun = (*FreeBSDTun)(nil)
var _ GVisorDevice = (*FreeBSDTun)(nil)
var (
_ Tun = (*FreeBSDTun)(nil)
_ GVisorDevice = (*FreeBSDTun)(nil)
)
// NewTun builds new tun interface handler
func NewTun(options *Config) (Tun, error) {
+2 -2
View File
@@ -53,7 +53,7 @@ func TestRequestSerialization(t *testing.T) {
}
addonsComparer := func(x, y *Addons) bool {
return (x.Flow == y.Flow) && (cmp.Equal(x.Seed, y.Seed))
return (x.Flow == y.Flow) && cmp.Equal(x.Seed, y.Seed)
}
if r := cmp.Diff(actualAddons, expectedAddons, cmp.Comparer(addonsComparer)); r != "" {
t.Error(r)
@@ -125,7 +125,7 @@ func TestMuxRequest(t *testing.T) {
}
addonsComparer := func(x, y *Addons) bool {
return (x.Flow == y.Flow) && (cmp.Equal(x.Seed, y.Seed))
return (x.Flow == y.Flow) && cmp.Equal(x.Seed, y.Seed)
}
if r := cmp.Diff(actualAddons, expectedAddons, cmp.Comparer(addonsComparer)); r != "" {
t.Error(r)
+1 -1
View File
@@ -79,7 +79,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
var nfsKey []byte
var lastCTR cipher.Stream
for j, k := range i.NfsPKeys {
var index = 32
index := 32
if k, ok := k.(*ecdh.PublicKey); ok {
privateKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
copy(relays, privateKey.PublicKey().Bytes())

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