mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 21:24:10 +00:00
Add Xray-core fuzz test harnesses
This commit is contained in:
parent
169b216d7e
commit
9b9f7fc19d
43 changed files with 1222 additions and 0 deletions
64
fuzz/xraycore/README.md
Normal file
64
fuzz/xraycore/README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Xray-core fuzzing, stages 1-2
|
||||
|
||||
This package contains Go fuzz targets for Xray-core only. It does not fuzz x-ui, the web panel, Telegram bot, installer scripts, Docker wiring, or any external control plane.
|
||||
|
||||
## Targets
|
||||
|
||||
| Target | Surface | Main code paths |
|
||||
| --- | --- | --- |
|
||||
| `FuzzXrayCoreFullConfigBuild` | Full JSON config | `infra/conf/serial.DecodeJSONConfig` -> `infra/conf.Config.Build` -> `core.New` |
|
||||
| `FuzzXrayCoreInboundVLESSConfigBuild` | Inbound and VLESS inbound fragments | `encoding/json` -> `infra/conf.InboundDetourConfig.Build` / `VLessInboundConfig.Build` -> `conf.Config.Build` -> `core.New` |
|
||||
| `FuzzXrayCoreOutboundConfigBuild` | Outbound and VLESS outbound fragments | `encoding/json` -> `infra/conf.OutboundDetourConfig.Build` / `VLessOutboundConfig.Build` -> `conf.Config.Build` -> `core.New` |
|
||||
| `FuzzXrayCoreStreamSettingsBuild` | Stream, transport, and security settings | `encoding/json` -> `infra/conf.StreamConfig.Build` |
|
||||
| `FuzzXrayCoreSniffingRoutingDNSConfigBuild` | Sniffing, routing, and DNS fragments | `encoding/json` -> `SniffingConfig.Build`, `RouterConfig.Build`, `DNSConfig.Build`, optional `conf.Config.Build` |
|
||||
| `FuzzXrayCoreVLESSFirstPacket` | VLESS inbound pre-auth first packet | byte input -> `proxy/vless/encoding.DecodeRequestHeader` with `MemoryValidator` |
|
||||
| `FuzzXrayCoreVLESSInboundProcessPreAuth` | VLESS inbound pre-auth handler path | byte input -> fake `net.Conn` -> `proxy/vless/inbound.Handler.Process` -> parser/auth/flow/dispatch decision |
|
||||
| `FuzzXrayCoreVLESSInboundFallbackPreAuth` | VLESS inbound fallback-enabled pre-auth path | byte input -> fake `net.Conn` -> `Handler.Process` with fallback map -> first-buffer parser or fallback reject decision |
|
||||
|
||||
## Run
|
||||
|
||||
Run seed and regression corpus:
|
||||
|
||||
```sh
|
||||
go test ./fuzz/xraycore -run=Fuzz -count=1
|
||||
```
|
||||
|
||||
Run individual fuzz targets:
|
||||
|
||||
```sh
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreFullConfigBuild -fuzztime=30s
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreInboundVLESSConfigBuild -fuzztime=30s
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreOutboundConfigBuild -fuzztime=30s
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreStreamSettingsBuild -fuzztime=30s
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreSniffingRoutingDNSConfigBuild -fuzztime=30s
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreVLESSFirstPacket -fuzztime=30s
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreVLESSInboundProcessPreAuth -fuzztime=30s
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreVLESSInboundFallbackPreAuth -fuzztime=30s
|
||||
```
|
||||
|
||||
For longer local runs, prefer one target per process and set the global timeout explicitly:
|
||||
|
||||
```sh
|
||||
go test ./fuzz/xraycore -run=^$ -fuzz=FuzzXrayCoreVLESSFirstPacket -fuzztime=10m -timeout=15m
|
||||
```
|
||||
|
||||
## Seed corpus
|
||||
|
||||
Initial seeds are present in two forms:
|
||||
|
||||
1. Programmatic `f.Add` seeds in the fuzz target source files.
|
||||
2. Persistent Go corpus files under `fuzz/xraycore/testdata/fuzz/<target>/`.
|
||||
|
||||
Config seeds include minimal full config, VLESS inbound, VLESS outbound, WebSocket/TLS, gRPC, stream fragments, DNS hosts, routing rules, and near-valid invalid JSON/config samples.
|
||||
|
||||
VLESS first-packet seeds include valid TCP/domain, TCP/IPv4, TCP/IPv6, UDP/IPv4, and Mux first packets for UUID `11111111-1111-1111-1111-111111111111`; truncated variants; wrong UUID; bad command; bad address type; malformed domain length; malformed IPv6 payload; oversized addons; XRV flow on raw transport; unknown protobuf flow; and valid prefix plus garbage suffix.
|
||||
|
||||
Stage 2 also adds persistent corpora for `FuzzXrayCoreVLESSInboundProcessPreAuth` and `FuzzXrayCoreVLESSInboundFallbackPreAuth`. The fallback harness uses a fallback map that is active for decision-making but intentionally has no matchable default target, so fuzzing reaches fallback selection/reject logic without opening real network connections.
|
||||
|
||||
The full-config corpus also contains the minimized known-crash input `{"inBounds":[{"listen":""}]}`. The active fuzz harness quarantines this exact Xray-core empty-domain-listen class so longer runs can continue searching for additional crashes while `TestXrayCoreKnownEmptyListenPanicReproducer` keeps the upstream panic visible.
|
||||
|
||||
## Guardrails
|
||||
|
||||
The targets cap input size, treat malformed parse/build errors as non-crashing outcomes, fail on unexpected nil successful builds, initialize built full configs with `core.New` where practical, and enforce coarse per-iteration elapsed-time checks. The direct VLESS parser target rejects any successful decode that does not authenticate to the configured seed user or that returns an invalid command/address shape.
|
||||
|
||||
The VLESS `Handler.Process` targets use a fake non-blocking `net.Conn` and a recording dispatcher. The oracle fails if malformed, unauthorized, bad-flow, reverse, or structurally incomplete input reaches `DispatchLink`; valid TCP/UDP/Mux seeds must reach `DispatchLink`; and trailing body bytes must not change the parsed first-packet header.
|
||||
70
fuzz/xraycore/TECHNICAL_NOTE.md
Normal file
70
fuzz/xraycore/TECHNICAL_NOTE.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Stage 1 technical note
|
||||
|
||||
## Covered
|
||||
|
||||
Config fuzzing covers the Xray-core JSON/config build surface without starting listeners:
|
||||
|
||||
- `infra/conf/serial.DecodeJSONConfig` for full JSON config loading.
|
||||
- `infra/conf.Config.Build` for global config normalization and app/inbound/outbound config construction.
|
||||
- `core.New` for non-running runtime object initialization after successful full-config builds.
|
||||
- `InboundDetourConfig.Build` and `VLessInboundConfig.Build` for inbound/VLESS settings, users, decryption, fallback, stream, and sniffing handling.
|
||||
- `OutboundDetourConfig.Build` and `VLessOutboundConfig.Build` for outbound/VLESS endpoint, user, stream, proxy, and mux handling.
|
||||
- `StreamConfig.Build` for transport/security fragments: TCP/raw, WS, gRPC, XHTTP, KCP, TLS, REALITY, sockopt, and finalmask paths where reachable from JSON.
|
||||
- `SniffingConfig.Build`, `RouterConfig.Build`, and `DNSConfig.Build` as focused fragment targets.
|
||||
- API/stats/metrics are included through full config seeds and `conf.Config.Build`.
|
||||
|
||||
VLESS first-packet fuzzing covers the early inbound pre-auth parser directly:
|
||||
|
||||
- `proxy/vless/encoding.DecodeRequestHeader`.
|
||||
- `proxy/vless.MemoryValidator` with one configured valid client UUID.
|
||||
- Version, raw UUID, addons length/value, command, and destination parser handling.
|
||||
- Both direct reader mode and the first-buffer mode used by VLESS inbound after the first socket read.
|
||||
|
||||
Stage 2 adds handler-level pre-auth coverage:
|
||||
|
||||
- `proxy/vless/inbound.(*Handler).Process` first-read path.
|
||||
- `connection.SetReadDeadline`, first `buf.Buffer.ReadFrom`, `buf.BufferedReader` setup, and parser call.
|
||||
- UUID lookup through `MemoryValidator`.
|
||||
- Flow admission for empty flow, unknown flow, and `xtls-rprx-vision` on a raw fake connection.
|
||||
- Command/destination admission for TCP, UDP, Mux, and Rvs.
|
||||
- Success handoff into `routing.Dispatcher.DispatchLink` using a recording dispatcher.
|
||||
- Fallback-enabled first-buffer parser/reject path without dialing a real fallback target.
|
||||
- Invariants for wrong UUID, malformed address metadata, malformed addon length/value, bad command, bad version, short reads, and valid prefix plus trailing body bytes.
|
||||
|
||||
## Bugs found
|
||||
|
||||
One Xray-core config-build crash was found by `FuzzXrayCoreFullConfigBuild`:
|
||||
|
||||
- Minimal reproducer: `{"inBounds":[{"listen":""}]}`
|
||||
- Crash: `panic: runtime error: index out of range [0] with length 0`
|
||||
- Upstream location: `github.com/xtls/xray-core/infra/conf.(*InboundDetourConfig).Build`, `infra/conf/xray.go:152`
|
||||
- Cause: empty string `listen` is parsed as a domain address with `Domain() == ""`; the build path indexes `Domain()[0]` while checking for Unix domain sockets.
|
||||
|
||||
The minimized reproducer is retained in `testdata/fuzz/FuzzXrayCoreFullConfigBuild/85cbe7a11661b2e3`. The active fuzz harness now quarantines this known empty-domain-listen class before calling `Config.Build` so subsequent fuzzing can continue. `TestXrayCoreKnownEmptyListenPanicReproducer` directly calls the upstream build path under `recover` and asserts the panic still reproduces; when Xray-core fixes the bug, that test should fail and the quarantine should be removed.
|
||||
|
||||
The deterministic regression test `TestXrayCoreVLESSWrongUUIDRejected` verifies that a packet with a changed non-normalized UUID byte does not authenticate.
|
||||
|
||||
No VLESS first-packet parser crash, hang, or false dispatch was found in the Stage 2 smoke-runs.
|
||||
|
||||
## Problems encountered
|
||||
|
||||
The config loader imports Xray-core serial config support, which requires additional indirect module metadata in the parent module:
|
||||
|
||||
- `github.com/ghodss/yaml`
|
||||
- `github.com/pelletier/go-toml`
|
||||
- `gopkg.in/yaml.v2`
|
||||
|
||||
These are Xray-core config-loader dependencies, not fuzzing infrastructure.
|
||||
|
||||
`core.New` is used only after successful build. It initializes Xray runtime objects but does not call `Start`, so it does not bind sockets or run transport lifecycles.
|
||||
|
||||
## Not covered in stage 1
|
||||
|
||||
- Stateful network harnesses for TCP, WS, gRPC, TLS, REALITY, QUIC, or full VLESS sessions.
|
||||
- Xray listener accept loops and actual socket deadlines.
|
||||
- Fallback connection I/O after malformed VLESS first packets.
|
||||
- Vision/REALITY encrypted first-packet lifecycle beyond the plain request-header parser.
|
||||
- Long-running resource leak measurement beyond input caps, build/init checks, and elapsed-time guardrails.
|
||||
- Corpus minimization for Stage 2, because no VLESS failing input was found in smoke-runs.
|
||||
|
||||
These are candidates for stage 2 rather than this stage.
|
||||
368
fuzz/xraycore/config_fuzz_test.go
Normal file
368
fuzz/xraycore/config_fuzz_test.go
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
package xraycorefuzz
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
core "github.com/xtls/xray-core/core"
|
||||
xconf "github.com/xtls/xray-core/infra/conf"
|
||||
xserial "github.com/xtls/xray-core/infra/conf/serial"
|
||||
|
||||
_ "github.com/xtls/xray-core/main/distro/all"
|
||||
)
|
||||
|
||||
const (
|
||||
maxConfigInputBytes = 64 << 10
|
||||
maxConfigIteration = 2 * time.Second
|
||||
)
|
||||
|
||||
func FuzzXrayCoreFullConfigBuild(f *testing.F) {
|
||||
addStringSeeds(f,
|
||||
minimalFullConfig,
|
||||
minimalVLESSInboundConfig,
|
||||
fullConfigWithAPIStatsDNSRouting,
|
||||
fullConfigWithVLESSWS,
|
||||
fullConfigWithVLESSGRPC,
|
||||
`{`,
|
||||
`null`,
|
||||
`{"inbounds":[{"protocol":"vless","port":"not-a-port","settings":{"clients":[]}}]}`,
|
||||
`{"routing":{"rules":[{"type":"field","domain":["regexp:("],"outboundTag":"direct"}]},"outbounds":[{"protocol":"freedom","tag":"direct"}]}`,
|
||||
)
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if tooLarge(data, maxConfigInputBytes) {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
cfg, err := xserial.DecodeJSONConfig(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buildAndInit(t, cfg)
|
||||
failIfSlow(t, start, maxConfigIteration)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzXrayCoreInboundVLESSConfigBuild(f *testing.F) {
|
||||
addStringSeeds(f,
|
||||
minimalVLESSInboundObject,
|
||||
minimalVLESSInboundSettings,
|
||||
vlessInboundWithFallback,
|
||||
vlessInboundWithVisionFlow,
|
||||
`{"clients":[{"id":"not-a-uuid"}],"decryption":"none"}`,
|
||||
`{"clients":[{"id":"11111111-1111-1111-1111-111111111111","encryption":"none"}],"decryption":"none"}`,
|
||||
`{"protocol":"vless","port":443,"settings":{"clients":[]}}`,
|
||||
)
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if tooLarge(data, maxConfigInputBytes/2) {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
var inbound xconf.InboundDetourConfig
|
||||
if err := json.Unmarshal(data, &inbound); err == nil {
|
||||
buildAndInit(t, &xconf.Config{InboundConfigs: []xconf.InboundDetourConfig{inbound}})
|
||||
}
|
||||
|
||||
if json.Valid(data) {
|
||||
wrapped := wrapVLESSInboundSettings(data)
|
||||
var vlessInbound xconf.InboundDetourConfig
|
||||
if err := json.Unmarshal(wrapped, &vlessInbound); err == nil {
|
||||
buildAndInit(t, &xconf.Config{InboundConfigs: []xconf.InboundDetourConfig{vlessInbound}})
|
||||
}
|
||||
}
|
||||
|
||||
failIfSlow(t, start, maxConfigIteration)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzXrayCoreOutboundConfigBuild(f *testing.F) {
|
||||
addStringSeeds(f,
|
||||
minimalFreedomOutboundObject,
|
||||
minimalVLESSOutboundObject,
|
||||
minimalVLESSOutboundSettings,
|
||||
`{"protocol":"vless","settings":{"vnext":[]}}`,
|
||||
`{"protocol":"vless","settings":{"vnext":[{"address":"example.com","port":443,"users":[]}]}}`,
|
||||
`{"protocol":"freedom","settings":{"domainStrategy":"forceIPv4"}}`,
|
||||
)
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if tooLarge(data, maxConfigInputBytes/2) {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
var outbound xconf.OutboundDetourConfig
|
||||
if err := json.Unmarshal(data, &outbound); err == nil {
|
||||
buildAndInit(t, &xconf.Config{OutboundConfigs: []xconf.OutboundDetourConfig{outbound}})
|
||||
}
|
||||
|
||||
if json.Valid(data) {
|
||||
wrapped := wrapVLESSOutboundSettings(data)
|
||||
var vlessOutbound xconf.OutboundDetourConfig
|
||||
if err := json.Unmarshal(wrapped, &vlessOutbound); err == nil {
|
||||
buildAndInit(t, &xconf.Config{OutboundConfigs: []xconf.OutboundDetourConfig{vlessOutbound}})
|
||||
}
|
||||
}
|
||||
|
||||
failIfSlow(t, start, maxConfigIteration)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzXrayCoreStreamSettingsBuild(f *testing.F) {
|
||||
addStringSeeds(f,
|
||||
`{}`,
|
||||
`{"network":"tcp","security":"none"}`,
|
||||
`{"network":"ws","wsSettings":{"path":"/vless","headers":{"Host":"example.com"}}}`,
|
||||
`{"network":"grpc","grpcSettings":{"serviceName":"svc","multiMode":true}}`,
|
||||
`{"network":"tcp","security":"tls","tlsSettings":{"serverName":"example.com","alpn":["h2","http/1.1"]}}`,
|
||||
`{"network":"tcp","security":"reality","realitySettings":{"show":false,"dest":"example.com:443","serverNames":["example.com"],"privateKey":"short","shortIds":["00"]}}`,
|
||||
`{"network":"kcp","kcpSettings":{"mtu":1350,"tti":50,"uplinkCapacity":5,"downlinkCapacity":20,"header":{"type":"wechat-video"}}}`,
|
||||
`{"network":"xhttp","xhttpSettings":{"path":"/x","mode":"auto"}}`,
|
||||
)
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if tooLarge(data, maxConfigInputBytes/2) {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
var stream xconf.StreamConfig
|
||||
if err := json.Unmarshal(data, &stream); err != nil {
|
||||
return
|
||||
}
|
||||
_, _ = stream.Build()
|
||||
failIfSlow(t, start, maxConfigIteration)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzXrayCoreSniffingRoutingDNSConfigBuild(f *testing.F) {
|
||||
addStringSeeds(f,
|
||||
`{"enabled":true,"destOverride":["http","tls","quic","fakedns"],"metadataOnly":false,"routeOnly":false}`,
|
||||
`{"domainStrategy":"IPIfNonMatch","rules":[{"type":"field","domain":["domain:example.com"],"outboundTag":"direct"}]}`,
|
||||
`{"servers":["1.1.1.1","https://dns.google/dns-query"],"hosts":{"example.com":"127.0.0.1"},"queryStrategy":"UseIPv4"}`,
|
||||
`{"sniffing":{"enabled":true,"destOverride":["bad-proto"]},"routing":{"rules":[{"type":"field","ip":["geoip:private"],"outboundTag":"direct"}]},"dns":{"servers":["localhost"]}}`,
|
||||
)
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if tooLarge(data, maxConfigInputBytes/2) {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
var sniffing xconf.SniffingConfig
|
||||
if err := json.Unmarshal(data, &sniffing); err == nil {
|
||||
_, _ = sniffing.Build()
|
||||
}
|
||||
|
||||
var routing xconf.RouterConfig
|
||||
if err := json.Unmarshal(data, &routing); err == nil {
|
||||
_, _ = routing.Build()
|
||||
}
|
||||
|
||||
var dns xconf.DNSConfig
|
||||
if err := json.Unmarshal(data, &dns); err == nil {
|
||||
_, _ = dns.Build()
|
||||
}
|
||||
|
||||
var cfg xconf.Config
|
||||
if err := json.Unmarshal(data, &cfg); err == nil {
|
||||
buildAndInit(t, &cfg)
|
||||
}
|
||||
|
||||
failIfSlow(t, start, maxConfigIteration)
|
||||
})
|
||||
}
|
||||
|
||||
func buildAndInit(t *testing.T, cfg *xconf.Config) {
|
||||
t.Helper()
|
||||
if hasKnownEmptyDomainListen(cfg) {
|
||||
return
|
||||
}
|
||||
pbConfig, err := cfg.Build()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if pbConfig == nil {
|
||||
t.Fatal("Build returned nil config without error")
|
||||
}
|
||||
instance, err := core.New(pbConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := instance.Close(); err != nil {
|
||||
t.Fatalf("closing initialized Xray instance failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXrayCoreKnownEmptyListenPanicReproducer(t *testing.T) {
|
||||
cfg, err := xserial.DecodeJSONConfig(bytes.NewReader([]byte(`{"inBounds":[{"listen":""}]}`)))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode minimized empty listen reproducer: %v", err)
|
||||
}
|
||||
if !hasKnownEmptyDomainListen(cfg) {
|
||||
t.Fatal("minimized empty listen reproducer was not classified as known Xray-core panic")
|
||||
}
|
||||
panicValue := catchPanic(func() {
|
||||
_, _ = cfg.Build()
|
||||
})
|
||||
if panicValue == nil {
|
||||
t.Fatal("known Xray-core empty listen panic no longer reproduces; remove the quarantine")
|
||||
}
|
||||
}
|
||||
|
||||
func hasKnownEmptyDomainListen(cfg *xconf.Config) bool {
|
||||
for _, inbound := range cfg.InboundConfigs {
|
||||
if inbound.ListenOn == nil || inbound.ListenOn.Address == nil {
|
||||
continue
|
||||
}
|
||||
if inbound.ListenOn.Family().IsDomain() && inbound.ListenOn.Domain() == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func catchPanic(fn func()) (panicValue any) {
|
||||
defer func() {
|
||||
panicValue = recover()
|
||||
}()
|
||||
fn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func addStringSeeds(f *testing.F, seeds ...string) {
|
||||
for _, seed := range seeds {
|
||||
f.Add([]byte(seed))
|
||||
}
|
||||
}
|
||||
|
||||
func tooLarge(data []byte, max int) bool {
|
||||
return len(data) > max
|
||||
}
|
||||
|
||||
func failIfSlow(t *testing.T, start time.Time, max time.Duration) {
|
||||
t.Helper()
|
||||
if elapsed := time.Since(start); elapsed > max {
|
||||
t.Fatalf("fuzz iteration took %s, max %s", elapsed, max)
|
||||
}
|
||||
}
|
||||
|
||||
func wrapVLESSInboundSettings(settings []byte) []byte {
|
||||
out := make([]byte, 0, len(settings)+96)
|
||||
out = append(out, `{"protocol":"vless","listen":"127.0.0.1","port":443,"settings":`...)
|
||||
out = append(out, settings...)
|
||||
out = append(out, `}`...)
|
||||
return out
|
||||
}
|
||||
|
||||
func wrapVLESSOutboundSettings(settings []byte) []byte {
|
||||
out := make([]byte, 0, len(settings)+64)
|
||||
out = append(out, `{"protocol":"vless","settings":`...)
|
||||
out = append(out, settings...)
|
||||
out = append(out, `}`...)
|
||||
return out
|
||||
}
|
||||
|
||||
const minimalFullConfig = `{
|
||||
"log": {"loglevel": "warning"},
|
||||
"inbounds": [],
|
||||
"outbounds": [{"protocol": "freedom", "tag": "direct"}]
|
||||
}`
|
||||
|
||||
const minimalVLESSInboundConfig = `{
|
||||
"inbounds": [{
|
||||
"tag": "vless-in",
|
||||
"listen": "127.0.0.1",
|
||||
"port": 443,
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [{"id": "11111111-1111-1111-1111-111111111111", "email": "seed@example"}],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {"network": "tcp", "security": "none"},
|
||||
"sniffing": {"enabled": true, "destOverride": ["http", "tls"]}
|
||||
}],
|
||||
"outbounds": [{"protocol": "freedom", "tag": "direct"}]
|
||||
}`
|
||||
|
||||
const fullConfigWithAPIStatsDNSRouting = `{
|
||||
"api": {"tag": "api", "services": ["HandlerService", "StatsService"]},
|
||||
"stats": {},
|
||||
"dns": {"servers": ["1.1.1.1"], "hosts": {"seed.example": "127.0.0.1"}},
|
||||
"routing": {"domainStrategy": "IPIfNonMatch", "rules": [{"type": "field", "domain": ["domain:seed.example"], "outboundTag": "direct"}]},
|
||||
"inbounds": [],
|
||||
"outbounds": [{"protocol": "freedom", "tag": "direct"}]
|
||||
}`
|
||||
|
||||
const fullConfigWithVLESSWS = `{
|
||||
"inbounds": [{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 8443,
|
||||
"protocol": "vless",
|
||||
"settings": {"clients": [{"id": "11111111-1111-1111-1111-111111111111"}], "decryption": "none"},
|
||||
"streamSettings": {"network": "ws", "security": "tls", "wsSettings": {"path": "/ws"}, "tlsSettings": {"serverName": "example.com"}}
|
||||
}],
|
||||
"outbounds": [{"protocol": "freedom", "tag": "direct"}]
|
||||
}`
|
||||
|
||||
const fullConfigWithVLESSGRPC = `{
|
||||
"inbounds": [{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 9443,
|
||||
"protocol": "vless",
|
||||
"settings": {"clients": [{"id": "11111111-1111-1111-1111-111111111111", "flow": "xtls-rprx-vision"}], "decryption": "none"},
|
||||
"streamSettings": {"network": "grpc", "grpcSettings": {"serviceName": "svc", "multiMode": true}}
|
||||
}],
|
||||
"outbounds": [{"protocol": "freedom", "tag": "direct"}]
|
||||
}`
|
||||
|
||||
const minimalVLESSInboundObject = `{
|
||||
"tag": "vless-in",
|
||||
"listen": "127.0.0.1",
|
||||
"port": 443,
|
||||
"protocol": "vless",
|
||||
"settings": {"clients": [{"id": "11111111-1111-1111-1111-111111111111"}], "decryption": "none"}
|
||||
}`
|
||||
|
||||
const minimalVLESSInboundSettings = `{
|
||||
"clients": [{"id": "11111111-1111-1111-1111-111111111111", "email": "seed@example"}],
|
||||
"decryption": "none"
|
||||
}`
|
||||
|
||||
const vlessInboundWithFallback = `{
|
||||
"clients": [{"id": "11111111-1111-1111-1111-111111111111"}],
|
||||
"decryption": "none",
|
||||
"fallbacks": [{"path": "/fallback", "dest": 8080, "xver": 1}]
|
||||
}`
|
||||
|
||||
const vlessInboundWithVisionFlow = `{
|
||||
"clients": [{"id": "11111111-1111-1111-1111-111111111111", "flow": "xtls-rprx-vision"}],
|
||||
"decryption": "none",
|
||||
"flow": "xtls-rprx-vision"
|
||||
}`
|
||||
|
||||
const minimalFreedomOutboundObject = `{"protocol":"freedom","tag":"direct","settings":{"domainStrategy":"AsIs"}}`
|
||||
|
||||
const minimalVLESSOutboundObject = `{
|
||||
"protocol": "vless",
|
||||
"tag": "vless-out",
|
||||
"settings": {
|
||||
"vnext": [{
|
||||
"address": "example.com",
|
||||
"port": 443,
|
||||
"users": [{"id": "11111111-1111-1111-1111-111111111111", "encryption": "none"}]
|
||||
}]
|
||||
},
|
||||
"streamSettings": {"network": "tcp", "security": "none"}
|
||||
}`
|
||||
|
||||
const minimalVLESSOutboundSettings = `{
|
||||
"vnext": [{
|
||||
"address": "example.com",
|
||||
"port": 443,
|
||||
"users": [{"id": "11111111-1111-1111-1111-111111111111", "encryption": "none"}]
|
||||
}]
|
||||
}`
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreFullConfigBuild/85cbe7a11661b2e3
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreFullConfigBuild/85cbe7a11661b2e3
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"inBounds\":[{\"listen\":\"\"}]}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreFullConfigBuild/invalid_near_json
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreFullConfigBuild/invalid_near_json
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"inbounds\":[{\"protocol\":\"vless\",\"port\":\"not-a-port\",\"settings\":{\"clients\":[]}}]}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreFullConfigBuild/minimal_full
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreFullConfigBuild/minimal_full
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"log\":{\"loglevel\":\"warning\"},\"inbounds\":[],\"outbounds\":[{\"protocol\":\"freedom\",\"tag\":\"direct\"}]}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreFullConfigBuild/vless_ws_tls
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreFullConfigBuild/vless_ws_tls
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"inbounds\":[{\"listen\":\"127.0.0.1\",\"port\":8443,\"protocol\":\"vless\",\"settings\":{\"clients\":[{\"id\":\"11111111-1111-1111-1111-111111111111\"}],\"decryption\":\"none\"},\"streamSettings\":{\"network\":\"ws\",\"security\":\"tls\",\"wsSettings\":{\"path\":\"/ws\"},\"tlsSettings\":{\"serverName\":\"example.com\"}}}],\"outbounds\":[{\"protocol\":\"freedom\",\"tag\":\"direct\"}]}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreInboundVLESSConfigBuild/minimal_vless_inbound
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreInboundVLESSConfigBuild/minimal_vless_inbound
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"tag\":\"vless-in\",\"listen\":\"127.0.0.1\",\"port\":443,\"protocol\":\"vless\",\"settings\":{\"clients\":[{\"id\":\"11111111-1111-1111-1111-111111111111\"}],\"decryption\":\"none\"}}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreInboundVLESSConfigBuild/vless_settings_bad_uuid
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreInboundVLESSConfigBuild/vless_settings_bad_uuid
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"clients\":[{\"id\":\"not-a-uuid\"}],\"decryption\":\"none\"}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreOutboundConfigBuild/minimal_freedom_outbound
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreOutboundConfigBuild/minimal_freedom_outbound
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"protocol\":\"freedom\",\"tag\":\"direct\",\"settings\":{\"domainStrategy\":\"AsIs\"}}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreOutboundConfigBuild/minimal_vless_outbound
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreOutboundConfigBuild/minimal_vless_outbound
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"protocol\":\"vless\",\"tag\":\"vless-out\",\"settings\":{\"vnext\":[{\"address\":\"example.com\",\"port\":443,\"users\":[{\"id\":\"11111111-1111-1111-1111-111111111111\",\"encryption\":\"none\"}]}]},\"streamSettings\":{\"network\":\"tcp\",\"security\":\"none\"}}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreSniffingRoutingDNSConfigBuild/dns_hosts
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreSniffingRoutingDNSConfigBuild/dns_hosts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"servers\":[\"1.1.1.1\",\"https://dns.google/dns-query\"],\"hosts\":{\"example.com\":\"127.0.0.1\"},\"queryStrategy\":\"UseIPv4\"}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreSniffingRoutingDNSConfigBuild/routing_rule
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreSniffingRoutingDNSConfigBuild/routing_rule
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"domainStrategy\":\"IPIfNonMatch\",\"rules\":[{\"type\":\"field\",\"domain\":[\"domain:example.com\"],\"outboundTag\":\"direct\"}]}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreStreamSettingsBuild/bad_reality_stream
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreStreamSettingsBuild/bad_reality_stream
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"network\":\"tcp\",\"security\":\"reality\",\"realitySettings\":{\"dest\":\"example.com:443\",\"serverNames\":[\"example.com\"],\"privateKey\":\"short\",\"shortIds\":[\"00\"]}}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreStreamSettingsBuild/ws_stream
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreStreamSettingsBuild/ws_stream
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("{\"network\":\"ws\",\"wsSettings\":{\"path\":\"/vless\",\"headers\":{\"Host\":\"example.com\"}}}")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/bad_command
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/bad_command
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\xff\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/bad_ipv6_payload
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/bad_ipv6_payload
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x03\x00\x01")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/truncated_uuid
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/truncated_uuid
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/unknown_flow
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/unknown_flow
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x05\x0a\x03bad\x01\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_mux
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_mux
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x03")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_prefix_garbage_suffix
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_prefix_garbage_suffix
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x02\x0bexample.comGET /garbage HTTP/1.1\r\nHost: fuzz\r\n\r\n")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_tcp_domain
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_tcp_domain
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_tcp_ipv4
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_tcp_ipv4
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x00\x50\x01\x7f\x00\x00\x01")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_tcp_ipv6
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_tcp_ipv6
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_udp_ipv4
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/valid_udp_ipv4
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x02\x00\x35\x01\x08\x08\x08\x08")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/wrong_uuid
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/wrong_uuid
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x91\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/xrv_flow
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSFirstPacket/xrv_flow
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12\x0a\x10xtls-rprx-vision\x01\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundFallbackPreAuth/bad_command
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundFallbackPreAuth/bad_command
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\xff\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundFallbackPreAuth/http_get_stage2
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundFallbackPreAuth/http_get_stage2
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("GET /stage2 HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundFallbackPreAuth/truncated
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundFallbackPreAuth/truncated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundFallbackPreAuth/valid_tcp_domain
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundFallbackPreAuth/valid_tcp_domain
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/bad_domain_length
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/bad_domain_length
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x02\xffx")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/unknown_flow
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/unknown_flow
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x05\x0a\x03bad\x01\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/valid_mux
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/valid_mux
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x03")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/valid_tcp_domain
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/valid_tcp_domain
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/valid_tcp_ipv4
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/valid_tcp_ipv4
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x00\x50\x01\x7f\x00\x00\x01")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/valid_udp_ipv4
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/valid_udp_ipv4
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x02\x00\x35\x01\x08\x08\x08\x08")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/wrong_uuid
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/wrong_uuid
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x91\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00\x01\x01\xbb\x02\x0bexample.com")
|
||||
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/xrv_flow_rejected_on_raw
vendored
Normal file
2
fuzz/xraycore/testdata/fuzz/FuzzXrayCoreVLESSInboundProcessPreAuth/xrv_flow_rejected_on_raw
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
go test fuzz v1
|
||||
[]byte("\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12\x0a\x10xtls-rprx-vision\x01\x01\xbb\x02\x0bexample.com")
|
||||
238
fuzz/xraycore/vless_first_packet_fuzz_test.go
Normal file
238
fuzz/xraycore/vless_first_packet_fuzz_test.go
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
package xraycorefuzz
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
xnet "github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
vlessencoding "github.com/xtls/xray-core/proxy/vless/encoding"
|
||||
)
|
||||
|
||||
const (
|
||||
seedVLESSUUID = "11111111-1111-1111-1111-111111111111"
|
||||
maxFirstPacketBytes = 16 << 10
|
||||
maxPacketIteration = time.Second
|
||||
)
|
||||
|
||||
func FuzzXrayCoreVLESSFirstPacket(f *testing.F) {
|
||||
validSeeds := validVLESSFirstPacketSeeds(f)
|
||||
for _, seed := range validSeeds {
|
||||
f.Add(seed)
|
||||
}
|
||||
for _, seed := range mutateVLESSPacketSeeds(validSeeds[0]) {
|
||||
f.Add(seed)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if tooLarge(data, maxFirstPacketBytes) {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
validator := mustVLESSValidator(t)
|
||||
|
||||
checkDecodedVLESSRequest(t, data, validator, false)
|
||||
if len(data) >= 18 {
|
||||
checkDecodedVLESSRequest(t, data, validator, true)
|
||||
}
|
||||
|
||||
failIfSlow(t, start, maxPacketIteration)
|
||||
})
|
||||
}
|
||||
|
||||
func TestXrayCoreVLESSWrongUUIDRejected(t *testing.T) {
|
||||
valid := mustValidVLESSFirstPacket(t)
|
||||
wrongUUID := append([]byte(nil), valid...)
|
||||
wrongUUID[1] ^= 0x80
|
||||
|
||||
validator := mustVLESSValidator(t)
|
||||
userSentID, request, _, _, err := vlessencoding.DecodeRequestHeader(false, nil, bytes.NewReader(wrongUUID), validator)
|
||||
if err == nil || request != nil || userSentID != nil {
|
||||
t.Fatalf("wrong UUID packet authenticated: userSentID=%x request=%#v err=%v", userSentID, request, err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkDecodedVLESSRequest(t *testing.T, data []byte, validator *vless.MemoryValidator, isFirstBuffer bool) {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
userSentID []byte
|
||||
request *protocol.RequestHeader
|
||||
err error
|
||||
)
|
||||
if isFirstBuffer {
|
||||
first := buf.FromBytes(append([]byte(nil), data...))
|
||||
reader := &buf.BufferedReader{
|
||||
Reader: buf.NewReader(bytes.NewReader(nil)),
|
||||
Buffer: buf.MultiBuffer{first},
|
||||
}
|
||||
userSentID, request, _, _, err = vlessencoding.DecodeRequestHeader(true, first, reader, validator)
|
||||
} else {
|
||||
userSentID, request, _, _, err = vlessencoding.DecodeRequestHeader(false, nil, bytes.NewReader(data), validator)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if request == nil {
|
||||
t.Fatal("DecodeRequestHeader returned nil request without error")
|
||||
}
|
||||
if request.User == nil || request.User.Account == nil {
|
||||
t.Fatal("DecodeRequestHeader accepted packet without authenticated user")
|
||||
}
|
||||
account, ok := request.User.Account.(*vless.MemoryAccount)
|
||||
if !ok {
|
||||
t.Fatalf("DecodeRequestHeader returned unexpected account type %T", request.User.Account)
|
||||
}
|
||||
validID := mustVLESSID(t)
|
||||
if !account.ID.Equals(validID) {
|
||||
t.Fatalf("DecodeRequestHeader authenticated unexpected account %s", account.ID.String())
|
||||
}
|
||||
if len(userSentID) != 16 {
|
||||
t.Fatalf("DecodeRequestHeader returned malformed user id length %d", len(userSentID))
|
||||
}
|
||||
switch request.Command {
|
||||
case protocol.RequestCommandTCP, protocol.RequestCommandUDP, protocol.RequestCommandMux, protocol.RequestCommandRvs:
|
||||
default:
|
||||
t.Fatalf("DecodeRequestHeader accepted invalid command 0x%x", byte(request.Command))
|
||||
}
|
||||
if request.Address == nil {
|
||||
t.Fatal("DecodeRequestHeader accepted request without destination address")
|
||||
}
|
||||
}
|
||||
|
||||
func mustVLESSValidator(t testing.TB) *vless.MemoryValidator {
|
||||
t.Helper()
|
||||
validator := new(vless.MemoryValidator)
|
||||
user := &protocol.MemoryUser{
|
||||
Account: &vless.MemoryAccount{
|
||||
ID: mustVLESSID(t),
|
||||
Encryption: "none",
|
||||
},
|
||||
Email: "seed@example",
|
||||
}
|
||||
if err := validator.Add(user); err != nil {
|
||||
t.Fatalf("failed to add VLESS seed user: %v", err)
|
||||
}
|
||||
return validator
|
||||
}
|
||||
|
||||
func mustVLESSID(t testing.TB) *protocol.ID {
|
||||
t.Helper()
|
||||
id, err := uuid.ParseString(seedVLESSUUID)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid seed UUID: %v", err)
|
||||
}
|
||||
return protocol.NewID(id)
|
||||
}
|
||||
|
||||
func mustValidVLESSFirstPacket(t testing.TB) []byte {
|
||||
t.Helper()
|
||||
return mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.DomainAddress("example.com"), xnet.Port(443), &vlessencoding.Addons{})
|
||||
}
|
||||
|
||||
func validVLESSFirstPacketSeeds(t testing.TB) [][]byte {
|
||||
t.Helper()
|
||||
return [][]byte{
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.DomainAddress("example.com"), xnet.Port(443), &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.ParseAddress("127.0.0.1"), xnet.Port(80), &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.ParseAddress("::1"), xnet.Port(443), &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandUDP, xnet.ParseAddress("8.8.8.8"), xnet.Port(53), &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandMux, nil, 0, &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandRvs, nil, 0, &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.DomainAddress("example.com"), xnet.Port(443), &vlessencoding.Addons{Flow: vless.XRV}),
|
||||
vlessPacketWithRawAddons(t, []byte{0x0a, 0x03, 'b', 'a', 'd'}, protocol.RequestCommandTCP, xnet.DomainAddress("example.com"), xnet.Port(443)),
|
||||
}
|
||||
}
|
||||
|
||||
func mustVLESSFirstPacket(t testing.TB, command protocol.RequestCommand, address xnet.Address, port xnet.Port, addons *vlessencoding.Addons) []byte {
|
||||
t.Helper()
|
||||
user := &protocol.MemoryUser{
|
||||
Account: &vless.MemoryAccount{
|
||||
ID: mustVLESSID(t),
|
||||
Encryption: "none",
|
||||
},
|
||||
Email: "seed@example",
|
||||
}
|
||||
request := &protocol.RequestHeader{
|
||||
Version: vlessencoding.Version,
|
||||
Command: command,
|
||||
Address: address,
|
||||
Port: port,
|
||||
User: user,
|
||||
}
|
||||
var out bytes.Buffer
|
||||
if err := vlessencoding.EncodeRequestHeader(&out, request, addons); err != nil {
|
||||
t.Fatalf("failed to build seed VLESS first packet: %v", err)
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
func vlessPacketWithRawAddons(t testing.TB, addons []byte, command protocol.RequestCommand, address xnet.Address, port xnet.Port) []byte {
|
||||
t.Helper()
|
||||
if len(addons) > 255 {
|
||||
t.Fatalf("raw addons too large: %d", len(addons))
|
||||
}
|
||||
base := mustVLESSFirstPacket(t, command, address, port, &vlessencoding.Addons{})
|
||||
out := make([]byte, 0, len(base)+len(addons))
|
||||
out = append(out, base[:17]...)
|
||||
out = append(out, byte(len(addons)))
|
||||
out = append(out, addons...)
|
||||
out = append(out, base[18:]...)
|
||||
return out
|
||||
}
|
||||
|
||||
func mutateVLESSPacketSeeds(valid []byte) [][]byte {
|
||||
seeds := [][]byte{
|
||||
{},
|
||||
{0x00},
|
||||
{0x00, 0x11},
|
||||
bytes.Repeat([]byte{0xff}, 32),
|
||||
append([]byte(nil), valid[:1]...),
|
||||
append([]byte(nil), valid[:17]...),
|
||||
append([]byte(nil), valid[:18]...),
|
||||
}
|
||||
|
||||
wrongUUID := append([]byte(nil), valid...)
|
||||
wrongUUID[1] ^= 0x80
|
||||
seeds = append(seeds, wrongUUID)
|
||||
|
||||
badVersion := append([]byte(nil), valid...)
|
||||
badVersion[0] = 0xff
|
||||
seeds = append(seeds, badVersion)
|
||||
|
||||
badCommand := append([]byte(nil), valid...)
|
||||
if len(badCommand) > 18 {
|
||||
badCommand[18] = 0xff
|
||||
}
|
||||
seeds = append(seeds, badCommand)
|
||||
|
||||
badAddressType := append([]byte(nil), valid...)
|
||||
if len(badAddressType) > 21 {
|
||||
badAddressType[21] = 0xff
|
||||
}
|
||||
seeds = append(seeds, badAddressType)
|
||||
|
||||
badDomainLength := append([]byte(nil), valid...)
|
||||
if len(badDomainLength) > 22 {
|
||||
badDomainLength[22] = 0xff
|
||||
}
|
||||
seeds = append(seeds, badDomainLength)
|
||||
|
||||
oversizedAddons := append([]byte(nil), valid[:17]...)
|
||||
oversizedAddons = append(oversizedAddons, 0xff, 0x01, 0x02, 0x03)
|
||||
seeds = append(seeds, oversizedAddons)
|
||||
|
||||
validWithGarbage := append([]byte(nil), valid...)
|
||||
validWithGarbage = append(validWithGarbage, []byte("GET /garbage HTTP/1.1\r\nHost: fuzz\r\n\r\n")...)
|
||||
seeds = append(seeds, validWithGarbage)
|
||||
|
||||
badIPv6 := append([]byte(nil), valid[:18]...)
|
||||
badIPv6 = append(badIPv6, byte(protocol.RequestCommandTCP), 0x01, 0xbb, byte(protocol.AddressTypeIPv6), 0x00, 0x01)
|
||||
seeds = append(seeds, badIPv6)
|
||||
|
||||
return seeds
|
||||
}
|
||||
406
fuzz/xraycore/vless_inbound_process_fuzz_test.go
Normal file
406
fuzz/xraycore/vless_inbound_process_fuzz_test.go
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
package xraycorefuzz
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
stdnet "net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
xnet "github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
xuuid "github.com/xtls/xray-core/common/uuid"
|
||||
core "github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
xconf "github.com/xtls/xray-core/infra/conf"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
vlessencoding "github.com/xtls/xray-core/proxy/vless/encoding"
|
||||
vinbound "github.com/xtls/xray-core/proxy/vless/inbound"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
)
|
||||
|
||||
const maxInboundProcessIteration = 1500 * time.Millisecond
|
||||
|
||||
func FuzzXrayCoreVLESSInboundProcessPreAuth(f *testing.F) {
|
||||
for _, seed := range stage2VLESSProcessSeeds(f) {
|
||||
f.Add(seed)
|
||||
}
|
||||
handler := mustStage2PlainVLESSHandler(f)
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if tooLarge(data, maxFirstPacketBytes) {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
dispatcher := newRecordingDispatcher()
|
||||
conn := newVLESSFuzzConn(data)
|
||||
err := handler.Process(vlessProcessContext(), xnet.Network_TCP, conn, dispatcher)
|
||||
|
||||
expectedDispatch := shouldVLESSProcessDispatch(t, data, false)
|
||||
assertVLESSProcessOutcome(t, expectedDispatch, dispatcher, err)
|
||||
failIfSlow(t, start, maxInboundProcessIteration)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzXrayCoreVLESSInboundFallbackPreAuth(f *testing.F) {
|
||||
for _, seed := range stage2VLESSFallbackSeeds(f) {
|
||||
f.Add(seed)
|
||||
}
|
||||
handler := mustStage2FallbackVLESSHandler(f)
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if tooLarge(data, maxFirstPacketBytes) {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
dispatcher := newRecordingDispatcher()
|
||||
conn := newVLESSFuzzConn(data)
|
||||
err := handler.Process(vlessProcessContext(), xnet.Network_TCP, conn, dispatcher)
|
||||
|
||||
expectedDispatch := shouldVLESSProcessDispatch(t, data, true)
|
||||
assertVLESSProcessOutcome(t, expectedDispatch, dispatcher, err)
|
||||
failIfSlow(t, start, maxInboundProcessIteration)
|
||||
})
|
||||
}
|
||||
|
||||
func TestXrayCoreVLESSInboundProcessValidSeedsDispatch(t *testing.T) {
|
||||
handler := mustStage2PlainVLESSHandler(t)
|
||||
for i, seed := range stage2ExpectedDispatchSeeds(t) {
|
||||
dispatcher := newRecordingDispatcher()
|
||||
err := handler.Process(vlessProcessContext(), xnet.Network_TCP, newVLESSFuzzConn(seed), dispatcher)
|
||||
if err != nil {
|
||||
t.Fatalf("valid seed %d was rejected: %v", i, err)
|
||||
}
|
||||
if !dispatcher.called {
|
||||
t.Fatalf("valid seed %d did not reach DispatchLink", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestXrayCoreVLESSInboundProcessRejectsMalformedSeeds(t *testing.T) {
|
||||
handler := mustStage2PlainVLESSHandler(t)
|
||||
for i, seed := range stage2ExpectedRejectSeeds(t) {
|
||||
dispatcher := newRecordingDispatcher()
|
||||
err := handler.Process(vlessProcessContext(), xnet.Network_TCP, newVLESSFuzzConn(seed), dispatcher)
|
||||
if err == nil {
|
||||
t.Fatalf("malformed seed %d returned nil error", i)
|
||||
}
|
||||
if dispatcher.called {
|
||||
t.Fatalf("malformed seed %d reached DispatchLink", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestXrayCoreVLESSTrailingGarbageDoesNotChangeHeader(t *testing.T) {
|
||||
valid := mustValidVLESSFirstPacket(t)
|
||||
withGarbage := append(append([]byte(nil), valid...), []byte("arbitrary trailing body bytes")...)
|
||||
|
||||
left := mustDecodeVLESSHeader(t, valid, false)
|
||||
right := mustDecodeVLESSHeader(t, withGarbage, false)
|
||||
if left.destination != right.destination || left.command != right.command || left.flow != right.flow || left.userID != right.userID {
|
||||
t.Fatalf("trailing bytes changed parsed header: %#v != %#v", left, right)
|
||||
}
|
||||
}
|
||||
|
||||
func shouldVLESSProcessDispatch(t testing.TB, data []byte, firstBufferMode bool) bool {
|
||||
t.Helper()
|
||||
decoded, ok := decodeVLESSHeaderForOracle(t, data, firstBufferMode)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
switch decoded.flow {
|
||||
case "":
|
||||
case vless.XRV:
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
switch decoded.command {
|
||||
case protocol.RequestCommandTCP, protocol.RequestCommandUDP, protocol.RequestCommandMux:
|
||||
return true
|
||||
case protocol.RequestCommandRvs:
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type decodedVLESSHeader struct {
|
||||
userID string
|
||||
command protocol.RequestCommand
|
||||
destination string
|
||||
flow string
|
||||
}
|
||||
|
||||
func mustDecodeVLESSHeader(t testing.TB, data []byte, firstBufferMode bool) decodedVLESSHeader {
|
||||
t.Helper()
|
||||
decoded, ok := decodeVLESSHeaderForOracle(t, data, firstBufferMode)
|
||||
if !ok {
|
||||
t.Fatalf("failed to decode VLESS header from valid seed")
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
func decodeVLESSHeaderForOracle(t testing.TB, data []byte, firstBufferMode bool) (decodedVLESSHeader, bool) {
|
||||
t.Helper()
|
||||
validator := mustVLESSValidator(t)
|
||||
var (
|
||||
request *protocol.RequestHeader
|
||||
addons *vlessencoding.Addons
|
||||
err error
|
||||
)
|
||||
if firstBufferMode {
|
||||
if len(data) < 18 {
|
||||
return decodedVLESSHeader{}, false
|
||||
}
|
||||
first := buf.FromBytes(append([]byte(nil), data...))
|
||||
reader := &buf.BufferedReader{
|
||||
Reader: buf.NewReader(bytes.NewReader(nil)),
|
||||
Buffer: buf.MultiBuffer{first},
|
||||
}
|
||||
_, request, addons, _, err = vlessencoding.DecodeRequestHeader(true, first, reader, validator)
|
||||
} else {
|
||||
_, request, addons, _, err = vlessencoding.DecodeRequestHeader(false, nil, bytes.NewReader(data), validator)
|
||||
}
|
||||
if err != nil || request == nil || request.User == nil || request.User.Account == nil || addons == nil {
|
||||
return decodedVLESSHeader{}, false
|
||||
}
|
||||
account, ok := request.User.Account.(*vless.MemoryAccount)
|
||||
if !ok || !account.ID.Equals(mustVLESSID(t)) || request.Address == nil {
|
||||
return decodedVLESSHeader{}, false
|
||||
}
|
||||
return decodedVLESSHeader{
|
||||
userID: account.ID.String(),
|
||||
command: request.Command,
|
||||
destination: request.Destination().String(),
|
||||
flow: addons.Flow,
|
||||
}, true
|
||||
}
|
||||
|
||||
func assertVLESSProcessOutcome(t *testing.T, expectedDispatch bool, dispatcher *recordingDispatcher, err error) {
|
||||
t.Helper()
|
||||
if expectedDispatch {
|
||||
if err != nil {
|
||||
t.Fatalf("valid first packet was rejected before dispatch: %v", err)
|
||||
}
|
||||
if !dispatcher.called {
|
||||
t.Fatal("valid first packet did not reach DispatchLink")
|
||||
}
|
||||
return
|
||||
}
|
||||
if dispatcher.called {
|
||||
t.Fatalf("malformed or unauthorized first packet reached DispatchLink for %s", dispatcher.dest.String())
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("malformed or unauthorized first packet returned nil error without dispatch")
|
||||
}
|
||||
}
|
||||
|
||||
func stage2VLESSProcessSeeds(t testing.TB) [][]byte {
|
||||
t.Helper()
|
||||
seeds := append([][]byte{}, validVLESSFirstPacketSeeds(t)...)
|
||||
seeds = append(seeds, mutateVLESSPacketSeeds(mustValidVLESSFirstPacket(t))...)
|
||||
seeds = append(seeds,
|
||||
vlessPacketWithRawAddons(t, bytes.Repeat([]byte{0x41}, 255), protocol.RequestCommandTCP, xnet.DomainAddress("example.com"), xnet.Port(443)),
|
||||
vlessPacketWithRawAddons(t, []byte{0x0a, 0x03, 'b', 'a', 'd'}, protocol.RequestCommandUDP, xnet.ParseAddress("8.8.8.8"), xnet.Port(53)),
|
||||
append(mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.ParseAddress("127.0.0.1"), xnet.Port(80), &vlessencoding.Addons{}), 0, 1, 2, 3, 4),
|
||||
)
|
||||
return seeds
|
||||
}
|
||||
|
||||
func stage2VLESSFallbackSeeds(t testing.TB) [][]byte {
|
||||
t.Helper()
|
||||
seeds := stage2VLESSProcessSeeds(t)
|
||||
seeds = append(seeds,
|
||||
[]byte("GET /stage2 HTTP/1.1\r\nHost: example.com\r\n\r\n"),
|
||||
[]byte("POST /missing HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nbody"),
|
||||
[]byte("* HTTP/2.0\r\n\r\n"),
|
||||
)
|
||||
return seeds
|
||||
}
|
||||
|
||||
func stage2ExpectedDispatchSeeds(t testing.TB) [][]byte {
|
||||
t.Helper()
|
||||
return [][]byte{
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.DomainAddress("example.com"), xnet.Port(443), &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.ParseAddress("127.0.0.1"), xnet.Port(80), &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.ParseAddress("::1"), xnet.Port(443), &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandUDP, xnet.ParseAddress("8.8.8.8"), xnet.Port(53), &vlessencoding.Addons{}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandMux, nil, 0, &vlessencoding.Addons{}),
|
||||
}
|
||||
}
|
||||
|
||||
func stage2ExpectedRejectSeeds(t testing.TB) [][]byte {
|
||||
t.Helper()
|
||||
valid := mustValidVLESSFirstPacket(t)
|
||||
return [][]byte{
|
||||
{},
|
||||
{0},
|
||||
append([]byte(nil), valid[:17]...),
|
||||
mutateVLESSPacketSeeds(valid)[7],
|
||||
mutateVLESSPacketSeeds(valid)[8],
|
||||
mutateVLESSPacketSeeds(valid)[9],
|
||||
vlessPacketWithRawAddons(t, []byte{0x0a, 0x03, 'b', 'a', 'd'}, protocol.RequestCommandTCP, xnet.DomainAddress("example.com"), xnet.Port(443)),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandTCP, xnet.DomainAddress("example.com"), xnet.Port(443), &vlessencoding.Addons{Flow: vless.XRV}),
|
||||
mustVLESSFirstPacket(t, protocol.RequestCommandRvs, nil, 0, &vlessencoding.Addons{}),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
stage2HandlersOnce sync.Once
|
||||
stage2PlainHandler *vinbound.Handler
|
||||
stage2FBHandler *vinbound.Handler
|
||||
stage2HandlersErr error
|
||||
)
|
||||
|
||||
func mustStage2PlainVLESSHandler(t testing.TB) *vinbound.Handler {
|
||||
t.Helper()
|
||||
mustInitStage2Handlers(t)
|
||||
return stage2PlainHandler
|
||||
}
|
||||
|
||||
func mustStage2FallbackVLESSHandler(t testing.TB) *vinbound.Handler {
|
||||
t.Helper()
|
||||
mustInitStage2Handlers(t)
|
||||
return stage2FBHandler
|
||||
}
|
||||
|
||||
func mustInitStage2Handlers(t testing.TB) {
|
||||
t.Helper()
|
||||
stage2HandlersOnce.Do(func() {
|
||||
stage2PlainHandler, stage2HandlersErr = newStage2VLESSHandler(nil)
|
||||
if stage2HandlersErr != nil {
|
||||
return
|
||||
}
|
||||
stage2FBHandler, stage2HandlersErr = newStage2VLESSHandler([]*vinbound.Fallback{
|
||||
{Path: "stage2-unreachable-fallback-target", Type: "stage2-invalid-network", Dest: "unused"},
|
||||
})
|
||||
})
|
||||
if stage2HandlersErr != nil {
|
||||
t.Fatalf("failed to initialize stage2 VLESS handler: %v", stage2HandlersErr)
|
||||
}
|
||||
}
|
||||
|
||||
func newStage2VLESSHandler(fallbacks []*vinbound.Fallback) (*vinbound.Handler, error) {
|
||||
pbConfig, err := (&xconf.Config{
|
||||
OutboundConfigs: []xconf.OutboundDetourConfig{{Protocol: "freedom", Tag: "direct"}},
|
||||
}).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance, err := core.New(pbConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := protocol.ToProtoUser(&protocol.MemoryUser{
|
||||
Account: &vless.MemoryAccount{
|
||||
ID: protocol.NewID(mustVLESSUUID()),
|
||||
Encryption: "none",
|
||||
},
|
||||
Email: "seed@example",
|
||||
})
|
||||
rawHandler, err := core.CreateObject(instance, &vinbound.Config{
|
||||
Clients: []*protocol.User{user},
|
||||
Fallbacks: fallbacks,
|
||||
Decryption: "none",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handler, ok := rawHandler.(*vinbound.Handler)
|
||||
if !ok {
|
||||
return nil, errors.New("VLESS inbound config did not create *inbound.Handler")
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func mustVLESSUUID() xuuid.UUID {
|
||||
id, err := xuuid.ParseString(seedVLESSUUID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func vlessProcessContext() context.Context {
|
||||
return session.ContextWithInbound(context.Background(), &session.Inbound{
|
||||
Source: xnet.TCPDestination(xnet.ParseAddress("203.0.113.10"), xnet.Port(50000)),
|
||||
Local: xnet.TCPDestination(xnet.ParseAddress("127.0.0.1"), xnet.Port(443)),
|
||||
Tag: "stage2-vless",
|
||||
})
|
||||
}
|
||||
|
||||
type recordingDispatcher struct {
|
||||
called bool
|
||||
dest xnet.Destination
|
||||
}
|
||||
|
||||
func newRecordingDispatcher() *recordingDispatcher {
|
||||
return &recordingDispatcher{}
|
||||
}
|
||||
|
||||
func (*recordingDispatcher) Type() interface{} { return routing.DispatcherType() }
|
||||
func (*recordingDispatcher) Start() error { return nil }
|
||||
func (*recordingDispatcher) Close() error { return nil }
|
||||
|
||||
func (d *recordingDispatcher) Dispatch(context.Context, xnet.Destination) (*transport.Link, error) {
|
||||
return nil, errors.New("Dispatch should not be used by VLESS Process harness")
|
||||
}
|
||||
|
||||
func (d *recordingDispatcher) DispatchLink(_ context.Context, dest xnet.Destination, _ *transport.Link) error {
|
||||
d.called = true
|
||||
d.dest = dest
|
||||
return nil
|
||||
}
|
||||
|
||||
type vlessFuzzConn struct {
|
||||
reader *bytes.Reader
|
||||
writes bytes.Buffer
|
||||
}
|
||||
|
||||
func newVLESSFuzzConn(data []byte) *vlessFuzzConn {
|
||||
return &vlessFuzzConn{reader: bytes.NewReader(append([]byte(nil), data...))}
|
||||
}
|
||||
|
||||
func (c *vlessFuzzConn) Read(p []byte) (int, error) {
|
||||
return c.reader.Read(p)
|
||||
}
|
||||
|
||||
func (c *vlessFuzzConn) Write(p []byte) (int, error) {
|
||||
return c.writes.Write(p)
|
||||
}
|
||||
|
||||
func (*vlessFuzzConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*vlessFuzzConn) LocalAddr() stdnet.Addr {
|
||||
return &stdnet.TCPAddr{IP: stdnet.ParseIP("127.0.0.1"), Port: 443}
|
||||
}
|
||||
|
||||
func (*vlessFuzzConn) RemoteAddr() stdnet.Addr {
|
||||
return &stdnet.TCPAddr{IP: stdnet.ParseIP("203.0.113.10"), Port: 50000}
|
||||
}
|
||||
|
||||
func (*vlessFuzzConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*vlessFuzzConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*vlessFuzzConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ routing.Dispatcher = (*recordingDispatcher)(nil)
|
||||
var _ interface {
|
||||
io.Reader
|
||||
io.Writer
|
||||
} = (*vlessFuzzConn)(nil)
|
||||
3
go.mod
3
go.mod
|
|
@ -41,6 +41,7 @@ require (
|
|||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
|
|
@ -66,6 +67,7 @@ require (
|
|||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pires/go-proxyproto v0.11.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
|
|
@ -97,6 +99,7 @@ require (
|
|||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
|
||||
lukechampine.com/blake3 v1.4.1 // indirect
|
||||
)
|
||||
|
|
|
|||
1
go.sum
1
go.sum
|
|
@ -266,6 +266,7 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
Loading…
Reference in a new issue