Compare commits

...
37 Commits
Author SHA1 Message Date
HuskyDG 3519dfecbd FakeDNS: Change default FakeIPv6Pool to 2001:2::/48 (#6815)
https://github.com/XTLS/Xray-core/discussions/5458#discussioncomment-18555021

https://github.com/XTLS/Xray-core/pull/6815#issuecomment-5842712128
2026-09-26 03:52:03 +00:00
Cluvex df261e4479 MASQUE client: Support HTTP/2 (Extended CONNECT, RFC 8441) (#6810)
https://github.com/XTLS/Xray-core/pull/6807#issuecomment-5808933074

https://github.com/XTLS/Xray-core/pull/6810#issuecomment-5842441136
2026-09-26 03:49:37 +00:00
风扇滑翔翼 61cad5ec8b Xray-core: Reduce error log usage for performance (#6796)
https://github.com/XTLS/Xray-core/pull/6796#issuecomment-5807809588
2026-09-25 17:59:49 +00:00
Artem Lytkin a642a190ed Geodata: Prefilter regexp rules by their required literals (#6818)
https://github.com/XTLS/Xray-core/pull/6818#issuecomment-5833512067
2026-09-25 17:25:52 +00:00
Hossin Asaadi 60e2a0c502 WireGuard proxy: Release packet views after use (#6801)
https://github.com/XTLS/Xray-core/pull/6801#issuecomment-5807228464
2026-09-24 05:46:26 +00:00
Esko Mobius 7d3e44fee2 Proxy: Add MASQUE outbound & transport (IETF CONNECT-IP, RFC 9484) (#6807)
Closes https://github.com/XTLS/Xray-core/issues/5495#issuecomment-3710683679
2026-09-24 02:13:37 +00:00
dependabot[bot] 9927942aaa Bump google.golang.org/grpc from 1.83.2 to 1.84.0 (#6793)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.83.2 to 1.84.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.83.2...v1.84.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.84.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-09-24 02:04:39 +00:00
LjhAUMEM a308ded2e6 WireGuard outbound: Fix endpoint IP address (#6804)
Fixes https://github.com/XTLS/Xray-core/issues/6803
2026-09-24 01:59:28 +00:00
dependabot[bot] 7741e9e77e Bump golang.zx2c4.com/wireguard/windows from 1.0.1 to 1.1.1 (#6809)
Bumps golang.zx2c4.com/wireguard/windows from 1.0.1 to 1.1.1.

---
updated-dependencies:
- dependency-name: golang.zx2c4.com/wireguard/windows
  dependency-version: 1.1.1
  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-09-24 01:57:16 +00:00
Жора ЗмейкинandLjhAUMEM d562d8947d Hysteria outbound: Fix UDP DATAGRAM truncation with ChromeParrot (#6788)
https://github.com/XTLS/Xray-core/pull/6788#issuecomment-5751428127

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-09-20 22:51:03 +00:00
Hossin Asaadi dbb1ea30ba WireGuard proxy: Prevent panic when closing netTun with a write in flight (#6778)
https://github.com/XTLS/Xray-core/pull/6778#pullrequestreview-5251957308
2026-09-20 20:07:54 +00:00
LjhAUMEM efc9e6da62 WireGuard outbound: Remove remoteDNS' "local" mode and domainStrategy (#6771)
https://github.com/XTLS/Xray-core/issues/6567#issuecomment-5660953757
https://github.com/XTLS/Xray-core/pull/6771#issuecomment-5751826982
https://github.com/XTLS/Xray-core/pull/6771#issuecomment-5751831637
2026-09-20 19:07:51 +00:00
LjhAUMEM 8267cf953a Transport: Refactor to be based on Finalmask's dialer & listener (#6754)
https://github.com/XTLS/Xray-core/pull/6327#issuecomment-5645958010
https://github.com/XTLS/Xray-core/pull/6754#issuecomment-5720818254
https://github.com/XTLS/Xray-core/pull/6754#issuecomment-5751585906
2026-09-20 18:12:50 +00:00
dependabot[bot] 24e6f6d551 Bump golang.org/x/net from 0.58.0 to 0.59.0 (#6756)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.58.0 to 0.59.0.
- [Commits](https://github.com/golang/net/compare/v0.58.0...v0.59.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.59.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-09-20 17:46:51 +00:00
RisaroandRPRX dcdfc57ccd XDRIVE transport: Add the Google Drive and "template" backend (#6748)
https://github.com/XTLS/Xray-core/pull/5645#issuecomment-3849778103
https://github.com/XTLS/Xray-core/pull/5645#issuecomment-3851839033
https://github.com/XTLS/Xray-core/pull/6745#issuecomment-5627294204
https://github.com/XTLS/Xray-core/pull/6748#issuecomment-5660444946
https://github.com/XTLS/Xray-core/pull/6748#issuecomment-5719209642

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-09-19 09:03:11 +00:00
RPRXandRisaro 3461c511aa XDRIVE transport: The universal remote-storage/any-service-based proxy, ignoring IP whitelists (#5645)
https://github.com/XTLS/Xray-core/pull/5414#issuecomment-3796734827
https://github.com/XTLS/Xray-core/pull/5581#issuecomment-3797134147
https://github.com/XTLS/Xray-core/pull/5645#issuecomment-3899873945
https://github.com/XTLS/Xray-core/pull/6745#issuecomment-5627420177
https://github.com/XTLS/Xray-core/pull/6748#issuecomment-5740443122

---------

Co-authored-by: Risaro <62798663+Risaro@users.noreply.github.com>
2026-09-19 08:23:38 +00:00
SVLAVRand风扇滑翔翼 c412e77a9b TUN inbound: Preserve UDP packet destinations with traffic stats (#6747)
https://github.com/XTLS/Xray-core/pull/6747#issuecomment-5647964551

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-09-12 20:09:35 +00:00
Kiangand风扇滑翔翼 ccb69ea5e2 common/buf/readv_windows.go: Fix raw WSARecv blocking thread and preventing connection close (#6743)
https://github.com/XTLS/Xray-core/pull/6743#issuecomment-5647971870

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-09-12 19:51:59 +00:00
RPRX 52a412d9e2 Xray-core v26.9.9
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-09-08 22:25:43 +00:00
LjhAUMEMandRPRX 18a1b5042a Finalmask: Make "udpHop" a new UDP mask (#6327)
https://github.com/XTLS/Xray-core/pull/6327#issuecomment-5592421364

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-09-08 22:15:02 +00:00
patterniha c26d2eda24 Direct/Freedom outbound: Skip domain resolution and finalRules when using sockopt.dialerProxy (#6742)
https://github.com/XTLS/Xray-core/pull/6058#issuecomment-5578800461
2026-09-08 18:45:38 +00:00
风扇滑翔翼 a1bf968be9 VLESS config: Fix validateOutboundTransportSecurity() (#6741)
Fixes https://github.com/XTLS/Xray-core/issues/6737#issuecomment-5587270250
2026-09-08 18:18:12 +00:00
风扇滑翔翼 c037ccd98d infra/vformat/main.go: Refactor (#6739)
https://github.com/XTLS/Xray-core/pull/6327#issuecomment-5581733848
2026-09-08 17:19:15 +00:00
RPRX 37ceb8b4b6 Xray-core v26.9.8
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-09-08 09:47:24 +00:00
RPRX fd2ca74822 go.mod: Specify Go 1.27.x
https://github.com/XTLS/Xray-core/pull/6327#issuecomment-5581777988
2026-09-08 08:44:33 +00:00
RPRX 47cfe9994a Update github.com/xtls/reality to 20260908062103
https://github.com/XTLS/REALITY/commit/8cdf7bf9c7f09cb9814bf08c3eb877f68b85fba8
https://github.com/XTLS/REALITY/commit/e1986a4d31ca33c087a72ab4644aef1295693b69
https://github.com/XTLS/REALITY/commit/393f8de3ee2d685271d79ee608334e441b2db324
2026-09-08 07:22:54 +00:00
Meow 3e2f040cd8 Direct/Freedom outbound: Better Compatibility (#6058)
Fixes https://github.com/XTLS/Xray-core/issues/6393
2026-09-08 03:31:25 +00:00
LjhAUMEM c7245c0336 TUN inbound: Refine Windows support (#6478)
https://github.com/XTLS/Xray-core/pull/6478#issuecomment-5489052152
2026-09-08 02:51:35 +00:00
Eninspaceand风扇滑翔翼 eef6e63bc1 XHTTP client: Fix a data race in WaitReadCloser (#6694)
https://github.com/XTLS/Xray-core/pull/6694#issuecomment-5575759500

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-09-07 22:38:16 +00:00
Hossin Asaadi 6ce8dc53e7 common/buf/writer.go: Fix buffered writes failing when payload exceeds remaining capacity (#6723)
Fixes https://github.com/XTLS/Xray-core/issues/5287
2026-09-07 21:14:41 +00:00
风扇滑翔翼andRaaad1on 01a034be53 Blackhole outbound: Add customizable response data support (#6713)
https://github.com/XTLS/Xray-core/pull/6711#issuecomment-5508683394

---------

Co-authored-by: Raaad1on <djilyawhite@gmail.com>
2026-09-07 21:09:58 +00:00
XXcipherX de2caf3cef Hysteria server: Fix Unix masquerade socket path (#6705)
https://github.com/XTLS/Xray-core/pull/6705#issuecomment-5489788730
2026-09-07 21:01:45 +00:00
dependabot[bot] cecc88f43c Bump google.golang.org/grpc from 1.83.1 to 1.83.2 (#6704)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.83.1 to 1.83.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.83.1...v1.83.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-07 20:42:22 +00:00
pexcn cd4ce973e9 WebSocket client: Avoid panic before real dialing in delayDialConn (#6544)
Fixes https://github.com/teddysun/xray-plugin-android/issues/11
2026-09-01 04:57:10 +00:00
patterniha fc7b980636 Sockopt: Fix indentation of the CustomSockopt block for Linux (#6568)
https://github.com/XTLS/Xray-core/pull/6568#issuecomment-5488145252

Fixes https://github.com/XTLS/Xray-core/issues/6564
Fixes https://github.com/XTLS/Xray-core/issues/6602
2026-09-01 04:47:55 +00:00
Younes 8ee131cbbb HTTP inbound: Fix a potential panic in readResponseAndHandle100Continue() (#6545) 2026-09-01 02:59:56 +03:00
Jorik_Smith 2776ea6d74 SS2022 outbound: Close connection to server on early returns (#6698)
https://github.com/XTLS/Xray-core/pull/6698#issuecomment-5468662565
2026-08-31 23:59:04 +00:00
206 changed files with 15595 additions and 2882 deletions
+1 -3
View File
@@ -67,9 +67,7 @@ jobs:
check-latest: true check-latest: true
cache: false cache: false
- name: Check Format - name: Check Format
run: | run: go run ./infra/vformat/main.go -mode check -pwd ./
go install -v mvdan.cc/gofumpt@latest
go run ./infra/vformat/main.go -mode check -pwd ./
test: test:
needs: check-assets needs: check-assets
+1 -1
View File
@@ -23,7 +23,7 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
} }
if fakeDNSEngine == nil { if fakeDNSEngine == nil {
errNotInit := errors.New("FakeDNSEngine is not initialized, but such a sniffer is used").AtError() errNotInit := errors.New("FakeDNSEngine is not initialized, but such a sniffer is used")
return protocolSnifferWithMetadata{}, errNotInit return protocolSnifferWithMetadata{}, errNotInit
} }
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) { return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
+1 -1
View File
@@ -28,7 +28,7 @@ func toNetIP(addrs []net.Address) ([]net.IP, error) {
if addr.Family().IsIP() { if addr.Family().IsIP() {
ips = append(ips, addr.IP()) ips = append(ips, addr.IP())
} else { } else {
return nil, errors.New("Failed to convert address", addr, "to Net IP.").AtWarning() return nil, errors.New("Failed to convert address", addr, "to Net IP.")
} }
} }
return ips, nil return ips, nil
+2 -2
View File
@@ -188,10 +188,10 @@ func parseResponse(payload []byte) (*IPRecord, error) {
var parser dnsmessage.Parser var parser dnsmessage.Parser
h, err := parser.Start(payload) h, err := parser.Start(payload)
if err != nil { if err != nil {
return nil, errors.New("failed to parse DNS response").Base(err).AtWarning() return nil, errors.New("failed to parse DNS response").Base(err)
} }
if err := parser.SkipAllQuestions(); err != nil { if err := parser.SkipAllQuestions(); err != nil {
return nil, errors.New("failed to skip questions in DNS response").Base(err).AtWarning() return nil, errors.New("failed to skip questions in DNS response").Base(err)
} }
now := time.Now() now := time.Now()
+3 -3
View File
@@ -58,7 +58,7 @@ func NewFakeDNSHolder() (*Holder, error) {
var err error var err error
if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil { if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {
return nil, errors.New("Unable to create Fake Dns Engine").Base(err).AtError() return nil, errors.New("Unable to create Fake Dns Engine").Base(err)
} }
err = fkdns.initialize(dns.FakeIPv4Pool, 65535) err = fkdns.initialize(dns.FakeIPv4Pool, 65535)
if err != nil { if err != nil {
@@ -80,13 +80,13 @@ func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
var err error var err error
if _, ipRange, err = net.ParseCIDR(ipPoolCidr); err != nil { if _, ipRange, err = net.ParseCIDR(ipPoolCidr); err != nil {
return errors.New("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError() return errors.New("Unable to parse CIDR for Fake DNS IP assignment").Base(err)
} }
ones, bits := ipRange.Mask.Size() ones, bits := ipRange.Mask.Size()
rooms := bits - ones rooms := bits - ones
if math.Log2(float64(lruSize)) >= float64(rooms) { if math.Log2(float64(lruSize)) >= float64(rooms) {
return errors.New("LRU size is bigger than subnet size").AtError() return errors.New("LRU size is bigger than subnet size")
} }
fkdns.domainToIP = cache.NewLru(lruSize) fkdns.domainToIP = cache.NewLru(lruSize)
fkdns.ipRange = ipRange fkdns.ipRange = ipRange
+4 -4
View File
@@ -84,7 +84,7 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
if dest.Network == net.Network_UDP { // UDP classic DNS mode if dest.Network == net.Network_UDP { // UDP classic DNS mode
return NewClassicNameServer(dest, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP), nil return NewClassicNameServer(dest, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP), nil
} }
return nil, errors.New("No available name server could be created from ", dest).AtWarning() return nil, errors.New("No available name server could be created from ", dest)
} }
// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs. // NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
@@ -102,7 +102,7 @@ func NewClient(
// Create a new server for each client for now // Create a new server for each client for now
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP) server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
if err != nil { if err != nil {
return errors.New("failed to create nameserver").Base(err).AtWarning() return errors.New("failed to create nameserver").Base(err)
} }
_, isLocalDNS := server.(*LocalNameServer) _, isLocalDNS := server.(*LocalNameServer)
@@ -113,7 +113,7 @@ func NewClient(
if len(ns.ExpectedIp) > 0 { if len(ns.ExpectedIp) > 0 {
expectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.ExpectedIp) expectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.ExpectedIp)
if err != nil { if err != nil {
return errors.New("failed to create expected ip matcher").Base(err).AtWarning() return errors.New("failed to create expected ip matcher").Base(err)
} }
} }
@@ -122,7 +122,7 @@ func NewClient(
if len(ns.UnexpectedIp) > 0 { if len(ns.UnexpectedIp) > 0 {
unexpectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.UnexpectedIp) unexpectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.UnexpectedIp)
if err != nil { if err != nil {
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning() return errors.New("failed to create unexpected ip matcher").Base(err)
} }
} }
+2 -2
View File
@@ -27,7 +27,7 @@ func (s *FakeDNSServer) IsDisableCache() bool {
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) { func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) {
if f.fakeDNSEngine == nil { if f.fakeDNSEngine == nil {
return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError() return nil, 0, errors.New("Unable to locate a fake DNS Engine")
} }
var ips []net.Address var ips []net.Address
@@ -39,7 +39,7 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOp
netIP, err := toNetIP(ips) netIP, err := toNetIP(ips)
if err != nil { if err != nil {
return nil, 0, errors.New("Unable to convert IP to net ip").Base(err).AtError() return nil, 0, errors.New("Unable to convert IP to net ip").Base(err)
} }
errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips) errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
+6 -2
View File
@@ -89,10 +89,10 @@ func (g *Instance) startInternal() error {
g.active = true g.active = true
if err := g.initAccessLogger(); err != nil { if err := g.initAccessLogger(); err != nil {
return errors.New("failed to initialize access logger").Base(err).AtWarning() return errors.New("failed to initialize access logger").Base(err)
} }
if err := g.initErrorLogger(); err != nil { if err := g.initErrorLogger(); err != nil {
return errors.New("failed to initialize error logger").Base(err).AtWarning() return errors.New("failed to initialize error logger").Base(err)
} }
return nil return nil
@@ -141,6 +141,10 @@ func (g *Instance) Handle(msg log.Message) {
} }
} }
func (g *Instance) Severity() log.Severity {
return g.config.ErrorLogLevel
}
// Close implements common.Closable.Close(). // Close implements common.Closable.Close().
func (g *Instance) Close() error { func (g *Instance) Close() error {
errors.LogDebug(context.Background(), "Logger closing") errors.LogDebug(context.Background(), "Logger closing")
+11 -22
View File
@@ -330,7 +330,6 @@ type SenderConfig struct {
// Send traffic through the given IP. Only IP is allowed. // Send traffic through the given IP. Only IP is allowed.
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"` Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"` StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"`
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"` MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"` ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"` TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
@@ -382,13 +381,6 @@ func (x *SenderConfig) GetStreamSettings() *internet.StreamConfig {
return nil return nil
} }
func (x *SenderConfig) GetProxySettings() *internet.ProxyConfig {
if x != nil {
return x.ProxySettings
}
return nil
}
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig { func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
if x != nil { if x != nil {
return x.MultiplexSettings return x.MultiplexSettings
@@ -506,14 +498,13 @@ const file_app_proxyman_config_proto_rawDesc = "" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" + "\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" + "\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" + "\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
"\x0eOutboundConfig\"\x9d\x03\n" + "\x0eOutboundConfig\"\xd6\x02\n" +
"\fSenderConfig\x12-\n" + "\fSenderConfig\x12-\n" +
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" + "\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12K\n" + "\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12T\n" +
"\x0eproxy_settings\x18\x03 \x01(\v2$.xray.transport.internet.ProxyConfigR\rproxySettings\x12T\n" +
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" + "\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" + "\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategy\"\xa4\x01\n" + "\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategyJ\x04\b\x03\x10\x04\"\xa4\x01\n" +
"\x12MultiplexingConfig\x12\x18\n" + "\x12MultiplexingConfig\x12\x18\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" + "\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
@@ -548,8 +539,7 @@ var file_app_proxyman_config_proto_goTypes = []any{
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain (*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig (*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage (*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
(*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig (internet.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy
(internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy
} }
var file_app_proxyman_config_proto_depIdxs = []int32{ var file_app_proxyman_config_proto_depIdxs = []int32{
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule 7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
@@ -562,14 +552,13 @@ var file_app_proxyman_config_proto_depIdxs = []int32{
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage 12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain 10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig 11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
13, // 10: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig 6, // 10: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
6, // 11: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig 13, // 11: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
14, // 12: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy 12, // [12:12] is the sub-list for method output_type
13, // [13:13] is the sub-list for method output_type 12, // [12:12] is the sub-list for method input_type
13, // [13:13] is the sub-list for method input_type 12, // [12:12] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension type_name 12, // [12:12] is the sub-list for extension extendee
13, // [13:13] is the sub-list for extension extendee 0, // [0:12] is the sub-list for field type_name
0, // [0:13] is the sub-list for field type_name
} }
func init() { file_app_proxyman_config_proto_init() } func init() { file_app_proxyman_config_proto_init() }
+1 -1
View File
@@ -57,7 +57,7 @@ message SenderConfig {
// Send traffic through the given IP. Only IP is allowed. // Send traffic through the given IP. Only IP is allowed.
xray.common.net.IPOrDomain via = 1; xray.common.net.IPOrDomain via = 1;
xray.transport.internet.StreamConfig stream_settings = 2; xray.transport.internet.StreamConfig stream_settings = 2;
xray.transport.internet.ProxyConfig proxy_settings = 3; reserved 3;
MultiplexingConfig multiplex_settings = 4; MultiplexingConfig multiplex_settings = 4;
string via_cidr = 5; string via_cidr = 5;
xray.transport.internet.DomainStrategy target_strategy = 6; xray.transport.internet.DomainStrategy target_strategy = 6;
+1 -1
View File
@@ -66,7 +66,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
} }
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings) mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
if err != nil { if err != nil {
return nil, errors.New("failed to parse stream config").Base(err).AtWarning() return nil, errors.New("failed to parse stream config").Base(err)
} }
newCtx := session.ContextWithInbound(ctx, &session.Inbound{Tag: tag, Source: src}) newCtx := session.ContextWithInbound(ctx, &session.Inbound{Tag: tag, Source: src})
+1 -1
View File
@@ -165,7 +165,7 @@ func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound
receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig) receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)
if !ok { if !ok {
return nil, errors.New("not a ReceiverConfig").AtError() return nil, errors.New("not a ReceiverConfig")
} }
streamSettings := receiverSettings.StreamSettings streamSettings := receiverSettings.StreamSettings
+2 -2
View File
@@ -142,7 +142,7 @@ func (w *tcpWorker) Start() error {
go w.callback(conn) go w.callback(conn)
}) })
if err != nil { if err != nil {
return errors.New("failed to listen TCP on ", w.port).AtWarning().Base(err) return errors.New("failed to listen TCP on ", w.port).Base(err)
} }
w.hub = hub w.hub = hub
return nil return nil
@@ -528,7 +528,7 @@ func (w *dsWorker) Start() error {
go w.callback(conn) go w.callback(conn)
}) })
if err != nil { if err != nil {
return errors.New("failed to listen Unix Domain Socket on ", w.address).AtWarning().Base(err) return errors.New("failed to listen Unix Domain Socket on ", w.address).Base(err)
} }
w.hub = hub w.hub = hub
return nil return nil
+13 -59
View File
@@ -15,7 +15,6 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/mux"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
@@ -26,8 +25,6 @@ import (
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
"github.com/xtls/xray-core/transport/pipe"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -63,7 +60,6 @@ type Handler struct {
streamSettings *internet.MemoryStreamConfig streamSettings *internet.MemoryStreamConfig
proxyConfig proto.Message proxyConfig proto.Message
proxy proxy.Outbound proxy proxy.Outbound
outboundManager outbound.Manager
mux *mux.ClientManager mux *mux.ClientManager
xudp *mux.ClientManager xudp *mux.ClientManager
udp443 string udp443 string
@@ -77,7 +73,6 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag) uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
h := &Handler{ h := &Handler{
tag: config.Tag, tag: config.Tag,
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
uplinkCounter: uplinkCounter, uplinkCounter: uplinkCounter,
downlinkCounter: downlinkCounter, downlinkCounter: downlinkCounter,
} }
@@ -92,7 +87,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
h.senderSettings = s h.senderSettings = s
mss, err := internet.ToMemoryStreamConfig(s.StreamSettings) mss, err := internet.ToMemoryStreamConfig(s.StreamSettings)
if err != nil { if err != nil {
return nil, errors.New("failed to parse stream settings").Base(err).AtWarning() return nil, errors.New("failed to parse stream settings").Base(err)
} }
h.streamSettings = mss h.streamSettings = mss
default: default:
@@ -108,9 +103,11 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
ctx = session.ContextWithFullHandler(ctx, h) ctx = session.ContextWithFullHandler(ctx, h)
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings) if h.streamSettings != nil {
ctx = session.ContextWithStreamSettings(ctx, h.streamSettings)
}
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig) rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -197,7 +194,6 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return
} }
} else { } else {
unchangedDomain := ob.Target.Address.Domain() unchangedDomain := ob.Target.Address.Domain()
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))]) ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
@@ -221,7 +217,7 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
if ob.Target.Network == net.Network_UDP && ob.Target.Port == 443 { if ob.Target.Network == net.Network_UDP && ob.Target.Port == 443 {
switch h.udp443 { switch h.udp443 {
case "reject": case "reject":
test(errors.New("XUDP rejected UDP/443 traffic").AtInfo()) test(errors.New("XUDP rejected UDP/443 traffic"))
return return
case "skip": case "skip":
goto out goto out
@@ -270,66 +266,26 @@ func (h *Handler) DestIpAddress() net.IP {
// Dial implements internet.Dialer. // Dial implements internet.Dialer.
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) { func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
if h.senderSettings != nil { if h.senderSettings != nil && h.senderSettings.Via != nil {
outbounds := session.OutboundsFromContext(ctx)
if h.senderSettings.ProxySettings.HasTag() { ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob)
tag := h.senderSettings.ProxySettings.Tag
handler := h.outboundManager.GetHandler(tag)
if handler != nil {
errors.LogDebug(ctx, "proxying to ", tag, " for dest ", dest)
outbounds := session.OutboundsFromContext(ctx)
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
Target: dest,
Tag: tag,
})) // add another outbound in session ctx
opts := pipe.OptionsFromContext(ctx)
uplinkReader, uplinkWriter := pipe.New(opts...)
downlinkReader, downlinkWriter := pipe.New(opts...)
go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
conn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))
if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
conn = tls.Client(conn, tlsConfig)
}
return h.getStatCouterConnection(conn), nil
}
errors.LogError(ctx, "failed to get outbound handler with tag: ", tag)
return nil, errors.New("failed to get outbound handler with tag: " + tag)
}
if h.senderSettings.Via != nil {
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob)
}
} }
conn, err := internet.Dial(ctx, dest, h.streamSettings) conn, err := internet.Dial(ctx, dest, h.streamSettings)
conn = h.getStatCouterConnection(conn) conn = h.getStatCouterConnection(conn)
outbounds := session.OutboundsFromContext(ctx)
if outbounds != nil {
ob := outbounds[len(outbounds)-1]
ob.Conn = conn
} else {
// for Vision's pre-connect
}
return conn, err return conn, err
} }
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) { func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil && !h.senderSettings.ProxySettings.HasTag() && (h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) { if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil &&
(h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
var domain string var domain string
addr := h.senderSettings.Via.AsAddress() addr := h.senderSettings.Via.AsAddress()
domain = h.senderSettings.Via.GetDomain() domain = h.senderSettings.Via.GetDomain()
switch { switch {
case h.senderSettings.ViaCidr != "": case h.senderSettings.ViaCidr != "":
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr) ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
case domain == "origin": case domain == "origin":
if inbound := session.InboundFromContext(ctx); inbound != nil { if inbound := session.InboundFromContext(ctx); inbound != nil {
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() { if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
@@ -344,11 +300,9 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String()) errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
} }
} }
// case addr.Family().IsDomain(): default: // case addr.Family().IsDomain():
default:
ob.Gateway = addr ob.Gateway = addr
} }
} }
} }
+2 -2
View File
@@ -68,13 +68,13 @@ func (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) err
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
if ob == nil { if ob == nil {
return errors.New("outbound metadata not found").AtError() return errors.New("outbound metadata not found")
} }
if isDomain(ob.Target, p.domain) { if isDomain(ob.Target, p.domain) {
muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{}) muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})
if err != nil { if err != nil {
return errors.New("failed to create mux client worker").Base(err).AtWarning() return errors.New("failed to create mux client worker").Base(err)
} }
worker, err := NewPortalWorker(muxClient) worker, err := NewPortalWorker(muxClient)
+2 -2
View File
@@ -115,7 +115,7 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
} }
if conds.Len() == 0 { if conds.Len() == 0 {
return nil, errors.New("this rule has no effective fields").AtWarning() return nil, errors.New("this rule has no effective fields")
} }
return conds, nil return conds, nil
@@ -145,7 +145,7 @@ func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatch
} }
s, ok := i.(*StrategyLeastLoadConfig) s, ok := i.(*StrategyLeastLoadConfig)
if !ok { if !ok {
return nil, errors.New("not a StrategyLeastLoadConfig").AtError() return nil, errors.New("not a StrategyLeastLoadConfig")
} }
leastLoadStrategy := NewLeastLoadStrategy(s) leastLoadStrategy := NewLeastLoadStrategy(s)
return &Balancer{ return &Balancer{
+11 -1
View File
@@ -5,7 +5,8 @@ import (
) )
type windowsReader struct { type windowsReader struct {
bufs []syscall.WSABuf bufs []syscall.WSABuf
ready bool
} }
func (r *windowsReader) Init(bs []*Buffer) { func (r *windowsReader) Init(bs []*Buffer) {
@@ -15,6 +16,7 @@ func (r *windowsReader) Init(bs []*Buffer) {
for _, b := range bs { for _, b := range bs {
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]}) r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
} }
r.ready = false
} }
func (r *windowsReader) Clear() { func (r *windowsReader) Clear() {
@@ -25,6 +27,14 @@ func (r *windowsReader) Clear() {
} }
func (r *windowsReader) Read(fd uintptr) int32 { func (r *windowsReader) Read(fd uintptr) int32 {
// On the first invocation, we return -1 to indicate "not ready"
// to make rawConn.Read wait for readability using the runtime's own mechanism
// because syscall.WSARecv() is a blocking call when used with nil OVERLAPPED
if !r.ready {
r.ready = true
return -1
}
var nBytes uint32 var nBytes uint32
var flags uint32 var flags uint32
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil) err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
+3 -1
View File
@@ -118,7 +118,9 @@ func (w *BufferedWriter) Write(b []byte) (int, error) {
nBytes, err := w.buffer.Write(b) nBytes, err := w.buffer.Write(b)
totalBytes += nBytes totalBytes += nBytes
if err != nil {
// ErrBufferFull means a partial write, so flush below and continue
if err != nil && err != ErrBufferFull {
return totalBytes, err return totalBytes, err
} }
if !w.buffered || w.buffer.IsFull() { if !w.buffered || w.buffer.IsFull() {
+3 -3
View File
@@ -10,12 +10,12 @@ import (
// [,) // [,)
func RandBetween(from int64, to int64) int64 { func RandBetween(from int64, to int64) int64 {
if from == to {
return from
}
if from > to { if from > to {
from, to = to, from from, to = to, from
} }
if d := to - from; d == 0 || d == 1 {
return from
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
return from + bigInt.Int64() return from + bigInt.Int64()
} }
+13 -65
View File
@@ -18,17 +18,12 @@ type hasInnerError interface {
Unwrap() error Unwrap() error
} }
type hasSeverity interface {
Severity() log.Severity
}
// Error is an error object with underlying error. // Error is an error object with underlying error.
type Error struct { type Error struct {
prefix []interface{} prefix []interface{}
message []interface{} message []interface{}
caller string caller string
inner error inner error
severity log.Severity
} }
// Error implements error.Error(). // Error implements error.Error().
@@ -69,46 +64,6 @@ func (err *Error) Base(e error) *Error {
return err return err
} }
func (err *Error) atSeverity(s log.Severity) *Error {
err.severity = s
return err
}
func (err *Error) Severity() log.Severity {
if err.inner == nil {
return err.severity
}
if s, ok := err.inner.(hasSeverity); ok {
as := s.Severity()
if as < err.severity {
return as
}
}
return err.severity
}
// AtDebug sets the severity to debug.
func (err *Error) AtDebug() *Error {
return err.atSeverity(log.Severity_Debug)
}
// AtInfo sets the severity to info.
func (err *Error) AtInfo() *Error {
return err.atSeverity(log.Severity_Info)
}
// AtWarning sets the severity to warning.
func (err *Error) AtWarning() *Error {
return err.atSeverity(log.Severity_Warning)
}
// AtError sets the severity to error.
func (err *Error) AtError() *Error {
return err.atSeverity(log.Severity_Error)
}
// String returns the string representation of this error. // String returns the string representation of this error.
func (err *Error) String() string { func (err *Error) String() string {
return err.Error() return err.Error()
@@ -132,9 +87,8 @@ func New(msg ...interface{}) *Error {
details = details[:i] details = details[:i]
} }
return &Error{ return &Error{
message: msg, message: msg,
severity: log.Severity_Info, caller: details,
caller: details,
} }
} }
@@ -171,6 +125,9 @@ func LogErrorInner(ctx context.Context, inner error, msg ...interface{}) {
} }
func doLog(ctx context.Context, inner error, severity log.Severity, msg ...interface{}) { func doLog(ctx context.Context, inner error, severity log.Severity, msg ...interface{}) {
if log.GetSeverity() < severity {
return
}
pc, _, _, _ := runtime.Caller(2) pc, _, _, _ := runtime.Caller(2)
details := runtime.FuncForPC(pc).Name() details := runtime.FuncForPC(pc).Name()
if len(details) >= trim { if len(details) >= trim {
@@ -181,10 +138,9 @@ func doLog(ctx context.Context, inner error, severity log.Severity, msg ...inter
details = details[:i] details = details[:i]
} }
err := &Error{ err := &Error{
message: msg, message: msg,
severity: severity, caller: details,
caller: details, inner: inner,
inner: inner,
} }
if ctx != nil && ctx != context.Background() { if ctx != nil && ctx != context.Background() {
id := uint32(c.IDFromContext(ctx)) id := uint32(c.IDFromContext(ctx))
@@ -193,7 +149,7 @@ func doLog(ctx context.Context, inner error, severity log.Severity, msg ...inter
} }
} }
log.Record(&log.GeneralMessage{ log.Record(&log.GeneralMessage{
Severity: GetSeverity(err), Severity: severity,
Content: err, Content: err,
}) })
} }
@@ -217,11 +173,3 @@ L:
} }
return err return err
} }
// GetSeverity returns the actual severity of the error, including inner errors.
func GetSeverity(err error) log.Severity {
if s, ok := err.(hasSeverity); ok {
return s.Severity()
}
return log.Severity_Info
}
+6 -15
View File
@@ -7,30 +7,21 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
. "github.com/xtls/xray-core/common/errors" . "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log"
) )
func TestError(t *testing.T) { func TestError(t *testing.T) {
err := New("TestError") err := New("TestError")
if v := GetSeverity(err); v != log.Severity_Info { if v := err.Error(); !strings.Contains(v, "TestError") {
t.Error("severity: ", v) t.Error("error: ", v)
} }
err = New("TestError2").Base(io.EOF) err = New("TestError2").Base(io.EOF)
if v := GetSeverity(err); v != log.Severity_Info { if v := err.Error(); !strings.Contains(v, "EOF") {
t.Error("severity: ", v) t.Error("error: ", v)
} }
err = New("TestError3").Base(io.EOF).AtWarning() err = New("TestError3").Base(io.EOF)
if v := GetSeverity(err); v != log.Severity_Warning { err = New("TestError4").Base(err)
t.Error("severity: ", v)
}
err = New("TestError4").Base(io.EOF).AtWarning()
err = New("TestError5").Base(err)
if v := GetSeverity(err); v != log.Severity_Warning {
t.Error("severity: ", v)
}
if v := err.Error(); !strings.Contains(v, "EOF") { if v := err.Error(); !strings.Contains(v, "EOF") {
t.Error("error: ", v) t.Error("error: ", v)
} }
@@ -1,6 +1,7 @@
package strmatcher_test package strmatcher_test
import ( import (
"regexp"
"strconv" "strconv"
"testing" "testing"
@@ -72,6 +73,64 @@ func BenchmarkSubstrMatcher(b *testing.B) {
}) })
} }
func BenchmarkRegexMatcher(b *testing.B) {
patterns := []string{ // taken from geosite
`(^|\.)91porn\.(best|com|cool|fun|group|party|plus|site|tw|work)$`,
`(^|\.)91porn[0-9]{3}\.me$`,
`(^|\.)apiproxy-device-prod-nlb-.+\.amazonaws\.com$`,
`(^|\.)dualstack\.apiproxy-.+\.amazonaws\.com$`,
`(^|\.)aqdk[0-9]{3}\.com$`,
`(^|\.)bilibili3(0[1-9]|1[0-2])\.xyz$`,
`(^|\.)byyum([3589]|2[235689]|3[34]|4[1-9]|5[1-79]|6[0134679])?\.com$`,
`(^|\.)fiftymvapi\..+$`,
`(^|\.)gossipfuli[0-9]{3,4}\.xyz$`,
`(^|\.)kpkuang\.(bond|fun|info|one|us)$`,
`(^|\.)rule34\.(asia|us|world|xxx|xyz)$`,
`(^|\.)[a-z][1-9][0-9][a-z]\.com$`,
`.+\.awsdns-[0-9][0-9]\.(co\.uk|com|net|org)$`,
`.+\.dkr\.ecr\.[^\.]+\.amazonaws\.com$`,
`^(.+\.)*zh\.okaapps\.com$`,
`^cdn\d-epicgames-\d+\.file\.myqcloud\.com$`,
`^chatgpt-async-webps-prod-\S+-\d+\.webpubsub\.azure\.com$`,
`^r+[0-9]+(---|\.)sn-(2x3|ni5|j5o)\w{5}\.googlevideo\.com$`,
`^speed\.(coe|open)\.ad\.[a-z]{2,6}\.prod\.hosts\.ooklaserver\.net$`,
`javdb\d+\.com$`,
}
domains := []string{
"www.google.com", "rr3---sn-4g5edndy.googlevideo.com", "r1---sn-2x3abcde.googlevideo.com", "i.ytimg.com",
"graph.facebook.com", "api.twitter.com", "www.baidu.com", "github.com", "objects.githubusercontent.com",
"login.microsoftonline.com", "e1234.dscb.akamaiedge.net", "d1a2b3c4d5e6f7.cloudfront.net",
"s3.us-east-1.amazonaws.com", "123456789012.dkr.ecr.us-east-1.amazonaws.com", "www.wikipedia.org",
"discord.com", "telegram.org", "store.steampowered.com", "www.91porn.com", "ns-1234.awsdns-12.org",
}
bench := func(b *testing.B, ctor func(pattern string) func(string) bool) {
var matchers []func(string) bool
for _, p := range patterns {
matchers = append(matchers, ctor(p))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, d := range domains {
for _, match := range matchers {
_ = match(d)
}
}
}
}
b.Run("regexp", func(b *testing.B) {
bench(b, func(pattern string) func(string) bool {
return regexp.MustCompile(pattern).MatchString
})
})
b.Run("prefilter", func(b *testing.B) {
bench(b, func(pattern string) func(string) bool {
m, err := Regex.New(pattern)
common.Must(err)
return m.Match
})
})
}
// Utility functions for benchmark // Utility functions for benchmark
func benchmarkMatcherType(b *testing.B, t Type, ctor func() MatcherGroup) { func benchmarkMatcherType(b *testing.B, t Type, ctor func() MatcherGroup) {
+45 -11
View File
@@ -3,6 +3,7 @@ package strmatcher
import ( import (
"errors" "errors"
"regexp" "regexp"
"regexp/syntax"
"slices" "slices"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
@@ -73,7 +74,43 @@ func (m SubstrMatcher) Match(s string) bool {
// RegexMatcher is an implementation of Matcher. // RegexMatcher is an implementation of Matcher.
type RegexMatcher struct { type RegexMatcher struct {
pattern *regexp.Regexp pattern *regexp.Regexp
literals []string // every match contains all of them, longest first
}
func newRegexMatcher(pattern string) (Matcher, error) {
regex, err := regexp.Compile(pattern)
if err != nil {
return nil, err
}
m := &RegexMatcher{pattern: regex}
if re, err := syntax.Parse(pattern, syntax.Perl); err == nil { // same flags as regexp.Compile
m.literals = requiredLiterals(re, nil)
slices.SortStableFunc(m.literals, func(a, b string) int { return len(b) - len(a) })
}
return m, nil
}
// requiredLiterals appends to dst the case-sensitive strings that every match of re contains.
func requiredLiterals(re *syntax.Regexp, dst []string) []string {
switch re.Op {
case syntax.OpLiteral:
// regexp matches U+FFFD against invalid UTF-8 bytes, strings.Contains does not
if re.Flags&syntax.FoldCase == 0 && !slices.Contains(re.Rune, utf8.RuneError) {
dst = append(dst, string(re.Rune))
}
case syntax.OpCapture, syntax.OpPlus:
dst = requiredLiterals(re.Sub[0], dst)
case syntax.OpRepeat:
if re.Min > 0 {
dst = requiredLiterals(re.Sub[0], dst)
}
case syntax.OpConcat:
for _, sub := range re.Sub {
dst = requiredLiterals(sub, dst)
}
}
return dst
} }
func (*RegexMatcher) Type() Type { func (*RegexMatcher) Type() Type {
@@ -89,6 +126,11 @@ func (m *RegexMatcher) String() string {
} }
func (m *RegexMatcher) Match(s string) bool { func (m *RegexMatcher) Match(s string) bool {
for _, l := range m.literals {
if !strings.Contains(s, l) {
return false
}
}
return m.pattern.MatchString(s) return m.pattern.MatchString(s)
} }
@@ -102,11 +144,7 @@ func (t Type) New(pattern string) (Matcher, error) {
case Domain: case Domain:
return DomainMatcher(pattern), nil return DomainMatcher(pattern), nil
case Regex: // 1. regex matching is case-sensitive case Regex: // 1. regex matching is case-sensitive
regex, err := regexp.Compile(pattern) return newRegexMatcher(pattern)
if err != nil {
return nil, err
}
return &RegexMatcher{pattern: regex}, nil
default: default:
return nil, errors.New("unknown matcher type") return nil, errors.New("unknown matcher type")
} }
@@ -135,11 +173,7 @@ func (t Type) NewDomainPattern(pattern string) (Matcher, error) {
} }
return DomainMatcher(pattern), nil return DomainMatcher(pattern), nil
case Regex: // Regex's charset not in LDH subset case Regex: // Regex's charset not in LDH subset
regex, err := regexp.Compile(pattern) return newRegexMatcher(pattern)
if err != nil {
return nil, err
}
return &RegexMatcher{pattern: regex}, nil
default: default:
return nil, errors.New("unknown matcher type") return nil, errors.New("unknown matcher type")
} }
@@ -0,0 +1,60 @@
package strmatcher
import (
"regexp"
"slices"
"testing"
)
var regexLiteralCases = []struct {
pattern string
literals []string
}{
{`(^|\.)91porn\.(best|com)$`, []string{"91porn."}},
{`.+\.awsdns-cn-[0-9][0-9]\.(biz|com|net|top)$`, []string{".awsdns-cn-", "."}},
{`^r+[0-9]+(---|\.)sn-(2x3|ni5|j5o)\w{5}\.googlevideo\.com$`, []string{".googlevideo.com", "sn-", "r"}},
{`(?i)abc`, nil},
{`ab(?i:CD)ef`, []string{"ab", "ef"}},
{`(abc)?x`, []string{"x"}},
{`(abc)*x`, []string{"x"}},
{`x{0,3}yy`, []string{"yy"}},
{`(ab)+c{2}`, []string{"ab", "c"}},
{`abc|abd`, []string{"ab"}},
{`\Qa.b\E`, []string{"a.b"}},
{`a\x{FFFD}b`, nil},
{`^[^.]+$`, nil},
}
func TestRegexRequiredLiterals(t *testing.T) {
for _, test := range regexLiteralCases {
m, err := newRegexMatcher(test.pattern)
if err != nil {
t.Fatal(err)
}
if got := m.(*RegexMatcher).literals; !slices.Equal(got, test.literals) {
t.Errorf("%s: got %q, want %q", test.pattern, got, test.literals)
}
}
}
func FuzzRegexMatcher(f *testing.F) {
inputs := []string{
"", "x", "yy", "abd", "ccc", "ABC", "abCDef", "abcdef", "abababcc", "a.b", "a\xffb", "a\uFFFDb",
"www.91porn.com", "ns1.awsdns-cn-01.top", "r1---sn-2x3abcde.googlevideo.com",
}
for _, test := range regexLiteralCases {
for _, s := range inputs {
f.Add(test.pattern, s)
}
}
f.Fuzz(func(t *testing.T, pattern, s string) {
re, err := regexp.Compile(pattern)
if err != nil {
return
}
m, _ := newRegexMatcher(pattern)
if got, want := m.Match(s), re.MatchString(s); got != want {
t.Errorf("pattern %q, input %q: got %v, want %v", pattern, s, got, want)
}
})
}
+21 -25
View File
@@ -1,7 +1,7 @@
package log // import "github.com/xtls/xray-core/common/log" package log // import "github.com/xtls/xray-core/common/log"
import ( import (
"sync" "sync/atomic"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
) )
@@ -29,36 +29,32 @@ func (m *GeneralMessage) String() string {
// Record writes a message into log stream. // Record writes a message into log stream.
func Record(msg Message) { func Record(msg Message) {
logHandler.Handle(msg) if h := logHandler.Load(); h != nil {
(*h).Handle(msg)
}
} }
var logHandler syncHandler type SeverityLogger interface {
Handler
Severity() Severity
}
func GetSeverity() Severity {
if h := logHandler.Load(); h != nil {
if sh, ok := (*h).(SeverityLogger); ok {
return sh.Severity()
}
}
// log everything by default
return Severity_Debug
}
var logHandler atomic.Pointer[Handler]
// RegisterHandler registers a new handler as current log handler. Previous registered handler will be discarded. // RegisterHandler registers a new handler as current log handler. Previous registered handler will be discarded.
func RegisterHandler(handler Handler) { func RegisterHandler(handler Handler) {
if handler == nil { if handler == nil {
panic("Log handler is nil") panic("Log handler is nil")
} }
logHandler.Set(handler) logHandler.Store(&handler)
}
type syncHandler struct {
sync.RWMutex
Handler
}
func (h *syncHandler) Handle(msg Message) {
h.RLock()
defer h.RUnlock()
if h.Handler != nil {
h.Handler.Handle(msg)
}
}
func (h *syncHandler) Set(handler Handler) {
h.Lock()
defer h.Unlock()
h.Handler = handler
} }
+4
View File
@@ -68,6 +68,10 @@ func (l *serverityLogger) Handle(msg Message) {
} }
} }
func (l *serverityLogger) Severity() Severity {
return l.logLevel
}
func (l *generalLogger) run() { func (l *generalLogger) run() {
defer l.access.Signal() defer l.access.Signal()
+1 -1
View File
@@ -38,7 +38,7 @@ func (m *ClientManager) Dispatch(ctx context.Context, link *transport.Link) erro
} }
} }
return errors.New("unable to find an available mux client").AtWarning() return errors.New("unable to find an available mux client")
} }
type WorkerPicker interface { type WorkerPicker interface {
+1 -1
View File
@@ -117,7 +117,7 @@ func (f *FrameMetadata) Unmarshal(reader io.Reader, readSourceAndLocal bool) err
return err return err
} }
if metaLen > 512 { if metaLen > 512 {
return errors.New("invalid metalen ", metaLen).AtError() return errors.New("invalid metalen ", metaLen)
} }
b := buf.New() b := buf.New()
+1 -1
View File
@@ -351,7 +351,7 @@ func (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedRead
err = w.handleStatusKeep(&meta, reader) err = w.handleStatusKeep(&meta, reader)
default: default:
status := meta.SessionStatus status := meta.SessionStatus
return errors.New("unknown status: ", status).AtError() return errors.New("unknown status: ", status)
} }
if err != nil { if err != nil {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
func (u *User) GetTypedAccount() (Account, error) { func (u *User) GetTypedAccount() (Account, error) {
if u.GetAccount() == nil { if u.GetAccount() == nil {
return nil, errors.New("Account is missing").AtWarning() return nil, errors.New("Account is missing")
} }
rawAccount, err := u.Account.GetInstance() rawAccount, err := u.Account.GetInstance()
-2
View File
@@ -70,8 +70,6 @@ type Outbound struct {
Tag string Tag string
// Name of the outbound proxy that handles the connection. // Name of the outbound proxy that handles the connection.
Name string Name string
// Unused. Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings
Conn net.Conn
// CanSpliceCopy is a property for this connection // CanSpliceCopy is a property for this connection
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot // 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
CanSpliceCopy int CanSpliceCopy int
+2 -2
View File
@@ -16,7 +16,7 @@ var typeCreatorRegistry = make(map[reflect.Type]ConfigCreator)
func RegisterConfig(config interface{}, configCreator ConfigCreator) error { func RegisterConfig(config interface{}, configCreator ConfigCreator) error {
configType := reflect.TypeOf(config) configType := reflect.TypeOf(config)
if _, found := typeCreatorRegistry[configType]; found { if _, found := typeCreatorRegistry[configType]; found {
return errors.New(configType.Name() + " is already registered").AtError() return errors.New(configType.Name() + " is already registered")
} }
typeCreatorRegistry[configType] = configCreator typeCreatorRegistry[configType] = configCreator
return nil return nil
@@ -27,7 +27,7 @@ func CreateObject(ctx context.Context, config interface{}) (interface{}, error)
configType := reflect.TypeOf(config) configType := reflect.TypeOf(config)
creator, found := typeCreatorRegistry[configType] creator, found := typeCreatorRegistry[configType]
if !found { if !found {
return nil, errors.New(configType.String() + " is not registered").AtError() return nil, errors.New(configType.String() + " is not registered")
} }
return creator(ctx, config) return creator(ctx, config)
} }
+4 -4
View File
@@ -125,7 +125,7 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) {
} }
if f == "" { if f == "" {
return nil, errors.New("Failed to get format of ", file).AtWarning() return nil, errors.New("Failed to get format of ", file)
} }
if f == "protobuf" { if f == "protobuf" {
@@ -142,7 +142,7 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) {
if len(v) == 1 { if len(v) == 1 {
return configLoaderByName["protobuf"].Loader(v) return configLoaderByName["protobuf"].Loader(v)
} else { } else {
return nil, errors.New("Only one protobuf config file is allowed").AtWarning() return nil, errors.New("Only one protobuf config file is allowed")
} }
} }
@@ -152,11 +152,11 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) {
if f, found := configLoaderByName[formatName]; found { if f, found := configLoaderByName[formatName]; found {
return f.Loader(v) return f.Loader(v)
} else { } else {
return nil, errors.New("Unable to load config in", formatName).AtWarning() return nil, errors.New("Unable to load config in", formatName)
} }
} }
return nil, errors.New("Unable to load config").AtWarning() return nil, errors.New("Unable to load config")
} }
func loadProtobufConfig(data []byte) (*Config, error) { func loadProtobufConfig(data []byte) (*Config, error) {
+2 -2
View File
@@ -19,8 +19,8 @@ import (
var ( var (
Version_x byte = 26 Version_x byte = 26
Version_y byte = 7 Version_y byte = 9
Version_z byte = 28 Version_z byte = 9
) )
var ( var (
+1 -1
View File
@@ -13,7 +13,7 @@ type FakeDNSEngine interface {
var ( var (
FakeIPv4Pool = "198.18.0.0/15" FakeIPv4Pool = "198.18.0.0/15"
FakeIPv6Pool = "fc00::/18" FakeIPv6Pool = "2001:2::/48"
) )
type FakeDNSEngineRev0 interface { type FakeDNSEngineRev0 interface {
+12 -11
View File
@@ -1,6 +1,6 @@
module github.com/xtls/xray-core module github.com/xtls/xray-core
go 1.26 go 1.27
require ( require (
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
@@ -22,21 +22,22 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/stretchr/testify v1.12.1 github.com/stretchr/testify v1.12.1
github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.55.0 golang.org/x/crypto v0.57.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.58.0 golang.org/x/net v0.59.0
golang.org/x/sync v0.22.0 golang.org/x/sync v0.23.0
golang.org/x/sys v0.47.0 golang.org/x/sys v0.48.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.0.1 golang.zx2c4.com/wireguard/windows v1.1.1
google.golang.org/grpc v1.83.1 google.golang.org/grpc v1.84.0
google.golang.org/protobuf v1.36.12 google.golang.org/protobuf v1.36.12
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3 h12.io/socks v1.0.3
lukechampine.com/blake3 v1.4.1 lukechampine.com/blake3 v1.4.1
mvdan.cc/gofumpt v0.12.0
) )
require ( require (
@@ -48,7 +49,6 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect github.com/koron/go-ssdp v0.0.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect
github.com/pion/dtls/v3 v3.1.5 // indirect github.com/pion/dtls/v3 v3.1.5 // indirect
github.com/pion/logging v0.2.4 // indirect github.com/pion/logging v0.2.4 // indirect
@@ -57,8 +57,9 @@ require (
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
github.com/wlynxg/anet v0.0.5 // indirect github.com/wlynxg/anet v0.0.5 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/text v0.41.0 // indirect golang.org/x/text v0.42.0 // indirect
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect golang.org/x/tools v0.49.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
+26 -39
View File
@@ -2,17 +2,12 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg= github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8= github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA= github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA=
github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c= github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -73,8 +68,8 @@ github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y= github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
@@ -87,21 +82,9 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 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 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= 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-20260908062103-8cdf7bf9c7f0 h1:rb+fKQFhz+5I2PPuQsNYxI5mUU840XWYtRF0ZBjvkws=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI= github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
@@ -110,8 +93,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@@ -120,12 +103,12 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-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.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -133,20 +116,22 @@ 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.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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -154,14 +139,14 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8= golang.zx2c4.com/wireguard/windows v1.1.1 h1:8/H97U1v1PNDNcBsMZgU3KFuND9MQdTsU2NOwmCXArE=
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs= golang.zx2c4.com/wireguard/windows v1.1.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -176,3 +161,5 @@ h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck= h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho=
mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA=
+20 -30
View File
@@ -1,52 +1,42 @@
package conf package conf
import ( import (
"encoding/json" "encoding/base64"
"strings"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/proxy/blackhole" "github.com/xtls/xray-core/proxy/blackhole"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type NoneResponse struct{} type ResponseConfig struct {
Type string `json:"type"`
func (*NoneResponse) Build() (proto.Message, error) { CustomResponseData string `json:"customResponseData"`
return new(blackhole.NoneResponse), nil
}
type HTTPResponse struct{}
func (*HTTPResponse) Build() (proto.Message, error) {
return new(blackhole.HTTPResponse), nil
} }
type BlackholeConfig struct { type BlackholeConfig struct {
Response json.RawMessage `json:"response"` Response *ResponseConfig `json:"response"`
} }
func (v *BlackholeConfig) Build() (proto.Message, error) { func (v *BlackholeConfig) Build() (proto.Message, error) {
config := new(blackhole.Config) config := new(blackhole.Config)
if v.Response != nil { if v.Response != nil {
response, _, err := configLoader.Load(v.Response) responseName := strings.ToLower(v.Response.Type)
if err != nil { switch responseName {
return nil, errors.New("Config: Failed to parse Blackhole response config.").Base(err) case "none", "":
config.Response = &blackhole.Response{Type: "none"}
case "http":
config.Response = &blackhole.Response{Type: "http"}
case "custom":
data, err := base64.StdEncoding.DecodeString(v.Response.CustomResponseData)
if err != nil {
return nil, errors.New("failed to decode custom response data: " + err.Error())
}
config.Response = &blackhole.Response{Type: "custom", CustomResponseData: data}
default:
return nil, errors.New("unknown blackhole response: " + responseName)
} }
responseSettings, err := response.(Buildable).Build()
if err != nil {
return nil, err
}
config.Response = serial.ToTypedMessage(responseSettings)
} }
return config, nil return config, nil
} }
var configLoader = NewJSONConfigLoader(
ConfigCreatorCache{
"none": func() interface{} { return new(NoneResponse) },
"http": func() interface{} { return new(HTTPResponse) },
},
"type",
"",
)
+25 -2
View File
@@ -3,7 +3,6 @@ package conf_test
import ( import (
"testing" "testing"
"github.com/xtls/xray-core/common/serial"
. "github.com/xtls/xray-core/infra/conf" . "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/proxy/blackhole" "github.com/xtls/xray-core/proxy/blackhole"
) )
@@ -22,7 +21,7 @@ func TestHTTPResponseJSON(t *testing.T) {
}`, }`,
Parser: loadJSON(creator), Parser: loadJSON(creator),
Output: &blackhole.Config{ Output: &blackhole.Config{
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}), Response: &blackhole.Response{Type: "http"},
}, },
}, },
{ {
@@ -32,3 +31,27 @@ func TestHTTPResponseJSON(t *testing.T) {
}, },
}) })
} }
func TestCustomResponseJSON(t *testing.T) {
creator := func() Buildable {
return new(BlackholeConfig)
}
runMultiTestCase(t, []TestCase{
{
Input: `{
"response": {
"type": "custom",
"customResponseData": "Y3VzdG9tIHJlc3BvbnNl"
}
}`,
Parser: loadJSON(creator),
Output: &blackhole.Config{
Response: &blackhole.Response{
Type: "custom",
CustomResponseData: []byte("custom response"),
},
},
},
})
}
+2 -2
View File
@@ -97,7 +97,7 @@ func (v *HTTPClientConfig) Build() (proto.Message, error) {
user.Email = v.Email user.Email = v.Email
} else { } else {
if err := json.Unmarshal(rawUser, user); err != nil { if err := json.Unmarshal(rawUser, user); err != nil {
return nil, errors.New("failed to parse HTTP user").Base(err).AtError() return nil, errors.New("failed to parse HTTP user").Base(err)
} }
} }
account := new(HTTPAccount) account := new(HTTPAccount)
@@ -106,7 +106,7 @@ func (v *HTTPClientConfig) Build() (proto.Message, error) {
account.Password = v.Password account.Password = v.Password
} else { } else {
if err := json.Unmarshal(rawUser, account); err != nil { if err := json.Unmarshal(rawUser, account); err != nil {
return nil, errors.New("failed to parse HTTP account").Base(err).AtError() return nil, errors.New("failed to parse HTTP account").Base(err)
} }
} }
user.Account = serial.ToTypedMessage(account.Build()) user.Account = serial.ToTypedMessage(account.Build())
+1 -1
View File
@@ -18,7 +18,7 @@ func RegisterConfigureFilePostProcessingStage(name string, stage ConfigureFilePo
func PostProcessConfigureFile(conf *Config) error { func PostProcessConfigureFile(conf *Config) error {
for k, v := range configureFilePostProcessingStages { for k, v := range configureFilePostProcessingStages {
if err := v.Process(conf); err != nil { if err := v.Process(conf); err != nil {
return errors.New("Rejected by Postprocessing Stage ", k).AtError().Base(err) return errors.New("Rejected by Postprocessing Stage ", k).Base(err)
} }
} }
return nil return nil
+2 -2
View File
@@ -13,7 +13,7 @@ type ConfigCreatorCache map[string]ConfigCreator
func (v ConfigCreatorCache) RegisterCreator(id string, creator ConfigCreator) error { func (v ConfigCreatorCache) RegisterCreator(id string, creator ConfigCreator) error {
if _, found := v[id]; found { if _, found := v[id]; found {
return errors.New(id, " already registered.").AtError() return errors.New(id, " already registered.")
} }
v[id] = creator v[id] = creator
@@ -61,7 +61,7 @@ func (v *JSONConfigLoader) Load(raw []byte) (interface{}, string, error) {
} }
rawID, found := obj[v.idKey] rawID, found := obj[v.idKey]
if !found { if !found {
return nil, "", errors.New(v.idKey, " not found in JSON context").AtError() return nil, "", errors.New(v.idKey, " not found in JSON context")
} }
var id string var id string
if err := json.Unmarshal(rawID, &id); err != nil { if err := json.Unmarshal(rawID, &id); err != nil {
+37
View File
@@ -0,0 +1,37 @@
package conf
import (
"net/netip"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/masque"
"google.golang.org/protobuf/proto"
)
type MasqueClientConfig struct {
Address *Address `json:"address"`
Port uint16 `json:"port"`
RemoteDNS []string `json:"remoteDNS"`
}
func (c *MasqueClientConfig) Build() (proto.Message, error) {
if c.Address == nil {
return nil, errors.New(`MASQUE: "address" is not set`)
}
if c.Port == 0 {
return nil, errors.New(`MASQUE: "port" is not set`)
}
for _, s := range c.RemoteDNS {
if _, err := netip.ParseAddr(s); err != nil {
return nil, errors.New(`MASQUE: invalid "remoteDNS" `, s).Base(err)
}
}
return &masque.ClientConfig{
Server: &protocol.ServerEndpoint{
Address: c.Address.Build(),
Port: uint32(c.Port),
},
RemoteDns: c.RemoteDNS,
}, nil
}
+85
View File
@@ -0,0 +1,85 @@
package conf_test
import (
"encoding/json"
"testing"
. "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/transport/internet/masque"
)
func TestMasqueConfig(t *testing.T) {
creator := func() Buildable {
return new(MasqueConfig)
}
runMultiTestCase(t, []TestCase{
{
Input: `{}`,
Parser: loadJSON(creator),
Output: &masque.Config{Path: "/.well-known/masque/ip/*/*/"},
},
{
Input: `{
"host": "example.com:8443",
"path": "/.well-known/masque/ip/{target}/{ipproto}/",
"headers": {"Authorization": "Basic dTpw"}
}`,
Parser: loadJSON(creator),
Output: &masque.Config{
Host: "example.com:8443",
Path: "/.well-known/masque/ip/*/*/",
Headers: map[string]string{"Authorization": "Basic dTpw"},
},
},
{
Input: `{"path": "/masque/ip{?target,ipproto}"}`,
Parser: loadJSON(creator),
Output: &masque.Config{Path: "/masque/ip?target=*&ipproto=*"},
},
})
for _, input := range []string{
`{"path": "/masque/{target}/{ipproto}/{dns}"}`,
`{"path": "masque"}`,
`{"host": "example.com/path"}`,
`{"headers": {"host": "example.com"}}`,
`{"headers": {"Capsule-Protocol": "?0"}}`,
`{"headers": {"X Token": "a"}}`,
`{"headers": {"X-Token": "a\r\nb"}}`,
} {
if _, err := loadJSON(creator)(input); err == nil {
t.Errorf("expected an error for %s", input)
}
}
}
func TestMasqueOutboundConfig(t *testing.T) {
build := func(s string) error {
c := new(OutboundDetourConfig)
if err := json.Unmarshal([]byte(s), c); err != nil {
return err
}
_, err := c.Build()
return err
}
if err := build(`{
"protocol": "masque",
"settings": {"address": "example.com", "port": 443},
"streamSettings": {"network": "masque", "security": "tls"},
"mux": {"enabled": false, "concurrency": -1}
}`); err != nil {
t.Error(err)
}
for _, input := range []string{
`{"protocol": "masque", "settings": {"address": "example.com"}, "streamSettings": {"network": "masque", "security": "tls"}}`,
`{"protocol": "masque", "settings": {"address": "example.com", "port": 443}, "streamSettings": {"network": "masque", "security": "tls"}, "mux": {"enabled": true}}`,
`{"protocol": "masque", "settings": {"address": "example.com", "port": 443}, "streamSettings": {"network": "masque", "security": "tls"}, "mux": {"enabled": true, "concurrency": -1}}`,
`{"protocol": "freedom", "streamSettings": {"network": "masque", "security": "tls"}}`,
} {
if err := build(input); err == nil {
t.Errorf("expected an error for %s", input)
}
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ func MergeConfigFromFiles(files []*core.ConfigSource) (string, error) {
if j, ok := creflect.MarshalToJson(c, true); ok { if j, ok := creflect.MarshalToJson(c, true); ok {
return j, nil return j, nil
} }
return "", errors.New("marshal to json failed.").AtError() return "", errors.New("marshal to json failed.")
} }
func mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) { func mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) {
+2 -3
View File
@@ -44,7 +44,6 @@ func (v *SocksServerConfig) Build() (proto.Message, error) {
case AuthMethodUserPass: case AuthMethodUserPass:
config.AuthType = socks.AuthType_PASSWORD config.AuthType = socks.AuthType_PASSWORD
default: default:
// errors.New("unknown socks auth method: ", v.AuthMethod, ". Default to noauth.").AtWarning().WriteToLog()
config.AuthType = socks.AuthType_NO_AUTH config.AuthType = socks.AuthType_NO_AUTH
} }
@@ -115,7 +114,7 @@ func (v *SocksClientConfig) Build() (proto.Message, error) {
user.Email = v.Email user.Email = v.Email
} else { } else {
if err := json.Unmarshal(rawUser, user); err != nil { if err := json.Unmarshal(rawUser, user); err != nil {
return nil, errors.New("failed to parse Socks user").Base(err).AtError() return nil, errors.New("failed to parse Socks user").Base(err)
} }
} }
account := new(SocksAccount) account := new(SocksAccount)
@@ -124,7 +123,7 @@ func (v *SocksClientConfig) Build() (proto.Message, error) {
account.Password = v.Password account.Password = v.Password
} else { } else {
if err := json.Unmarshal(rawUser, account); err != nil { if err := json.Unmarshal(rawUser, account); err != nil {
return nil, errors.New("failed to parse socks account").Base(err).AtError() return nil, errors.New("failed to parse socks account").Base(err)
} }
} }
user.Account = serial.ToTypedMessage(account.Build()) user.Account = serial.ToTypedMessage(account.Build())
+48 -1
View File
@@ -23,6 +23,7 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask/realm" "github.com/xtls/xray-core/transport/internet/finalmask/realm"
"github.com/xtls/xray-core/transport/internet/finalmask/salamander" "github.com/xtls/xray-core/transport/internet/finalmask/salamander"
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku" "github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
"github.com/xtls/xray-core/transport/internet/finalmask/udphop"
"github.com/xtls/xray-core/transport/internet/finalmask/xdns" "github.com/xtls/xray-core/transport/internet/finalmask/xdns"
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp" "github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
"github.com/xtls/xray-core/transport/internet/finalmask/xmc" "github.com/xtls/xray-core/transport/internet/finalmask/xmc"
@@ -83,6 +84,7 @@ var (
"xdns": func() interface{} { return new(Xdns) }, "xdns": func() interface{} { return new(Xdns) },
"xicmp": func() interface{} { return new(Xicmp) }, "xicmp": func() interface{} { return new(Xicmp) },
"realm": func() interface{} { return new(Realm) }, "realm": func() interface{} { return new(Realm) },
"udphop": func() interface{} { return new(UDPHop) },
}, "type", "settings") }, "type", "settings")
) )
@@ -905,6 +907,52 @@ func (c *Realm) Build() (proto.Message, error) {
}, nil }, nil
} }
type UDPHop struct {
Mode string `json:"mode"`
Interval Int32Range `json:"interval"`
RemoteIPs []string `json:"remoteIPs"`
RemotePorts PortList `json:"remotePorts"`
}
func (c *UDPHop) Build() (proto.Message, error) {
var local, remote, remoteOnce bool
for _, mode := range strings.Split(c.Mode, ",") {
switch strings.ToLower(mode) {
case "intervallocal":
local = true
case "intervalremote":
remote = true
case "perconnremote":
remoteOnce = true
default:
return nil, errors.New("invalid mode ", mode)
}
}
var remoteIPs []string
for _, ip := range c.RemoteIPs {
prefix, err := netip.ParsePrefix(ip)
if err == nil {
remoteIPs = append(remoteIPs, prefix.String())
continue
}
addr, err := netip.ParseAddr(ip)
if err == nil {
remoteIPs = append(remoteIPs, netip.PrefixFrom(addr, addr.BitLen()).String())
continue
}
return nil, errors.New("invalid ip ", ip)
}
return &udphop.Config{
Local: local,
Remote: remote,
RemoteOnce: remoteOnce,
IntervalMin: int64(c.Interval.From),
IntervalMax: int64(c.Interval.To),
RemoteIPs: remoteIPs,
RemotePorts: c.RemotePorts.Build().Ports(),
}, nil
}
type Mask struct { type Mask struct {
Type string `json:"type"` Type string `json:"type"`
Settings *json.RawMessage `json:"settings"` Settings *json.RawMessage `json:"settings"`
@@ -938,7 +986,6 @@ type QuicParamsConfig struct {
BrutalUp Bandwidth `json:"brutalUp"` BrutalUp Bandwidth `json:"brutalUp"`
BrutalDown Bandwidth `json:"brutalDown"` BrutalDown Bandwidth `json:"brutalDown"`
BrutalDisableLossCompensation bool `json:"brutalDisableLossCompensation"` BrutalDisableLossCompensation bool `json:"brutalDisableLossCompensation"`
UdpHop UdpHop `json:"udpHop"`
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"` InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"` MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"` InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
+37 -38
View File
@@ -36,6 +36,10 @@ func (p TransportProtocol) Build() (string, error) {
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3") return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
case "hysteria": case "hysteria":
return "hysteria", nil return "hysteria", nil
case "masque":
return "masque", nil
case "xdrive":
return "xdrive", nil
default: default:
return "", errors.New("Config: unknown transport protocol: ", p) return "", errors.New("Config: unknown transport protocol: ", p)
} }
@@ -59,6 +63,8 @@ type StreamConfig struct {
WSSettings *WebSocketConfig `json:"wsSettings"` WSSettings *WebSocketConfig `json:"wsSettings"`
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"` HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"` HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
MASQUESettings *MasqueConfig `json:"masqueSettings"`
XDRIVESettings *XDriveConfig `json:"xdriveSettings"`
SocketSettings *SocketConfig `json:"sockopt"` SocketSettings *SocketConfig `json:"sockopt"`
} }
@@ -192,6 +198,26 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
Settings: serial.ToTypedMessage(hs), Settings: serial.ToTypedMessage(hs),
}) })
} }
if c.MASQUESettings != nil {
ms, err := c.MASQUESettings.Build()
if err != nil {
return nil, errors.New("Failed to build MASQUE config.").Base(err)
}
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
ProtocolName: "masque",
Settings: serial.ToTypedMessage(ms),
})
}
if c.XDRIVESettings != nil {
xs, err := c.XDRIVESettings.Build()
if err != nil {
return nil, errors.New("Failed to build XDRIVE config.").Base(err)
}
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
ProtocolName: "xdrive",
Settings: serial.ToTypedMessage(xs),
})
}
if c.SocketSettings != nil { if c.SocketSettings != nil {
ss, err := c.SocketSettings.Build() ss, err := c.SocketSettings.Build()
if err != nil { if err != nil {
@@ -253,10 +279,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal") return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
} }
if (c.FinalMask.QuicParams.UdpHop.Interval.From != 0 && c.FinalMask.QuicParams.UdpHop.Interval.From < 5) || (c.FinalMask.QuicParams.UdpHop.Interval.To != 0 && c.FinalMask.QuicParams.UdpHop.Interval.To < 5) {
return nil, errors.New("Interval must be at least 5")
}
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 { if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
return nil, errors.New("InitStreamReceiveWindow must be at least 16384") return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
} }
@@ -290,43 +312,20 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
BrutalUp: up, BrutalUp: up,
BrutalDown: down, BrutalDown: down,
BrutalDisableLossCompensation: c.FinalMask.QuicParams.BrutalDisableLossCompensation, BrutalDisableLossCompensation: c.FinalMask.QuicParams.BrutalDisableLossCompensation,
UdpHop: &internet.UdpHop{ InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
Ports: c.FinalMask.QuicParams.UdpHop.PortList.Build().Ports(), MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
IntervalMin: int64(c.FinalMask.QuicParams.UdpHop.Interval.From), InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
IntervalMax: int64(c.FinalMask.QuicParams.UdpHop.Interval.To), MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
}, MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow, KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow, DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow, DisableChromeParrot: c.FinalMask.QuicParams.DisableChromeParrot,
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow, DisableGSO: c.FinalMask.QuicParams.DisableGSO,
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout, MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod, DisableStatelessReset: c.FinalMask.QuicParams.DisableStatelessReset,
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
DisableChromeParrot: c.FinalMask.QuicParams.DisableChromeParrot,
DisableGSO: c.FinalMask.QuicParams.DisableGSO,
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
DisableStatelessReset: c.FinalMask.QuicParams.DisableStatelessReset,
} }
} }
} }
return config, nil return config, nil
} }
type ProxyConfig struct {
Tag string `json:"tag"`
// TransportLayerProxy: For compatibility.
TransportLayerProxy bool `json:"transportLayer"`
}
// Build implements Buildable.
func (v *ProxyConfig) Build() (*internet.ProxyConfig, error) {
if v.Tag == "" {
return nil, errors.New("Proxy tag is not set.")
}
return &internet.ProxyConfig{
Tag: v.Tag,
TransportLayerProxy: v.TransportLayerProxy,
}, nil
}
+100 -30
View File
@@ -1,7 +1,6 @@
package conf package conf
import ( import (
"context"
"encoding/json" "encoding/json"
"math/big" "math/big"
"net/url" "net/url"
@@ -21,9 +20,12 @@ import (
"github.com/xtls/xray-core/transport/internet/httpupgrade" "github.com/xtls/xray-core/transport/internet/httpupgrade"
"github.com/xtls/xray-core/transport/internet/hysteria" "github.com/xtls/xray-core/transport/internet/hysteria"
"github.com/xtls/xray-core/transport/internet/kcp" "github.com/xtls/xray-core/transport/internet/kcp"
"github.com/xtls/xray-core/transport/internet/masque"
"github.com/xtls/xray-core/transport/internet/splithttp" "github.com/xtls/xray-core/transport/internet/splithttp"
"github.com/xtls/xray-core/transport/internet/tcp" "github.com/xtls/xray-core/transport/internet/tcp"
"github.com/xtls/xray-core/transport/internet/websocket" "github.com/xtls/xray-core/transport/internet/websocket"
"github.com/xtls/xray-core/transport/internet/xdrive"
"golang.org/x/net/http/httpguts"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -122,7 +124,7 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
for _, key := range headerNames { for _, key := range headerNames {
value := v.Headers[key] value := v.Headers[key]
if value == nil { if value == nil {
return nil, errors.New("empty HTTP header value: " + key).AtError() return nil, errors.New("empty HTTP header value: " + key)
} }
config.Header = append(config.Header, &http.Header{ config.Header = append(config.Header, &http.Header{
Name: key, Name: key,
@@ -190,7 +192,7 @@ func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
for _, key := range headerNames { for _, key := range headerNames {
value := v.Headers[key] value := v.Headers[key]
if value == nil { if value == nil {
return nil, errors.New("empty HTTP header value: " + key).AtError() return nil, errors.New("empty HTTP header value: " + key)
} }
config.Header = append(config.Header, &http.Header{ config.Header = append(config.Header, &http.Header{
Name: key, Name: key,
@@ -240,11 +242,11 @@ func (c *TCPConfig) Build() (proto.Message, error) {
if len(c.HeaderConfig) > 0 { if len(c.HeaderConfig) > 0 {
headerConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig) headerConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig)
if err != nil { if err != nil {
return nil, errors.New("invalid TCP header config").Base(err).AtError() return nil, errors.New("invalid TCP header config").Base(err)
} }
ts, err := headerConfig.(Buildable).Build() ts, err := headerConfig.(Buildable).Build()
if err != nil { if err != nil {
return nil, errors.New("invalid TCP header config").Base(err).AtError() return nil, errors.New("invalid TCP header config").Base(err)
} }
config.HeaderSettings = serial.ToTypedMessage(ts) config.HeaderSettings = serial.ToTypedMessage(ts)
} }
@@ -534,10 +536,6 @@ type KCPConfig struct {
// Build implements Buildable. // Build implements Buildable.
func (c *KCPConfig) Build() (proto.Message, error) { func (c *KCPConfig) Build() (proto.Message, error) {
if c.HeaderConfig != nil || c.Seed != nil {
return nil, errors.PrintRemovedFeatureError("mkcp header & seed", "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm")
}
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config) config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
if c.Mtu != nil { if c.Mtu != nil {
@@ -560,16 +558,16 @@ func (c *KCPConfig) Build() (proto.Message, error) {
} }
if config.Mtu < 21 { if config.Mtu < 21 {
return nil, errors.New("Mtu must be at least 21").AtError() return nil, errors.New("MTU must be at least 21")
} }
if config.Tti < 10 || config.Tti > 1000 { if config.Tti < 10 || config.Tti > 1000 {
return nil, errors.New("invalid mKCP TTI: ", c.Tti).AtError() return nil, errors.New("TTI must be between 10 and 1000")
} }
if config.CwndMultiplier < 1 { if config.CwndMultiplier < 1 {
return nil, errors.New("CwndMultiplier must be at least 1").AtError() return nil, errors.New("CwndMultiplier must be at least 1")
} }
if config.GetSendingBufferSize() == 0 { if config.GetSendingBufferSize() == 0 {
return nil, errors.New("MaxSendingWindow must be >= Mtu").AtError() return nil, errors.New("MaxSendingWindow must be at least ", config.Mtu)
} }
return config, nil return config, nil
@@ -739,11 +737,6 @@ func (b Bandwidth) Bps() (uint64, error) {
return uint64(val*float64(mul)) / 8, nil return uint64(val*float64(mul)) / 8, nil
} }
type UdpHop struct {
PortList PortList `json:"ports"`
Interval Int32Range `json:"interval"`
}
type Masquerade struct { type Masquerade struct {
Type string `json:"type"` Type string `json:"type"`
@@ -760,14 +753,8 @@ type Masquerade struct {
} }
type HysteriaConfig struct { type HysteriaConfig struct {
Version int32 `json:"version"` Version int32 `json:"version"`
Auth string `json:"auth"` Auth string `json:"auth"`
Congestion *string `json:"congestion"`
Up *Bandwidth `json:"up"`
Down *Bandwidth `json:"down"`
UdpHop *UdpHop `json:"udphop"`
UdpIdleTimeout int64 `json:"udpIdleTimeout"` UdpIdleTimeout int64 `json:"udpIdleTimeout"`
Masquerade Masquerade `json:"masquerade"` Masquerade Masquerade `json:"masquerade"`
} }
@@ -777,10 +764,6 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
return nil, errors.New("version != 2") return nil, errors.New("version != 2")
} }
if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {
errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams")
}
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) { if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
return nil, errors.New("UdpIdleTimeout must be between 2 and 600") return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
} }
@@ -805,6 +788,46 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
return config, nil return config, nil
} }
type MasqueConfig struct {
Host string `json:"host"`
Path string `json:"path"`
Headers map[string]string `json:"headers"`
}
func (c *MasqueConfig) Build() (proto.Message, error) {
path := c.Path
if path == "" {
path = masque.DefaultPath
}
path = strings.NewReplacer(
"{target}", "*", "{ipproto}", "*",
"{?target,ipproto}", "?target=*&ipproto=*", "{?ipproto,target}", "?ipproto=*&target=*",
"{&target,ipproto}", "&target=*&ipproto=*", "{&ipproto,target}", "&ipproto=*&target=*",
).Replace(path)
if !strings.HasPrefix(path, "/") || strings.ContainsAny(path, "{}") {
return nil, errors.New(`invalid "path": `, path, `, only the variables {target} and {ipproto} are supported`)
}
if c.Host != "" {
if u, err := url.Parse("https://" + c.Host); err != nil || u.Host != c.Host {
return nil, errors.New(`invalid "host": `, c.Host)
}
}
for k, v := range c.Headers {
if !httpguts.ValidHeaderFieldName(k) || !httpguts.ValidHeaderFieldValue(v) {
return nil, errors.New(`invalid header in "headers": `, strconv.Quote(k))
}
switch strings.ToLower(k) {
case "host", "capsule-protocol":
return nil, errors.New(`"headers" can't contain "`, k, `"`)
}
}
return &masque.Config{
Host: c.Host,
Path: path,
Headers: c.Headers,
}, nil
}
func readFileOrString(f string, s []string) ([]byte, error) { func readFileOrString(f string, s []string) ([]byte, error) {
if len(f) > 0 { if len(f) > 0 {
return filesystem.ReadCert(f) return filesystem.ReadCert(f)
@@ -814,3 +837,50 @@ func readFileOrString(f string, s []string) ([]byte, error) {
} }
return nil, errors.New("both file and bytes are empty.") return nil, errors.New("both file and bytes are empty.")
} }
type XDriveConfig struct {
RemoteFolder string `json:"remoteFolder"`
Service string `json:"service"`
Secrets []string `json:"secrets"`
SegmentBytes uint32 `json:"segmentBytes"`
FlushIntervalMs uint32 `json:"flushIntervalMs"`
PollIntervalMs uint32 `json:"pollIntervalMs"`
MaxPollIntervalMs uint32 `json:"maxPollIntervalMs"`
SessionTTLSeconds uint32 `json:"sessionTtlSeconds"`
Concurrency uint32 `json:"concurrency"`
EagerWindowMs uint32 `json:"eagerWindowMs"`
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
Template json.RawMessage `json:"template"`
}
// Build implements Buildable.
func (c *XDriveConfig) Build() (proto.Message, error) {
switch c.Service {
case "local":
case "Google Drive":
if len(c.Secrets) != 3 {
return nil, errors.New("Google Drive needs 3 secrets in order of ClientID, ClientSecret, RefreshToken")
}
case "template":
if len(c.Template) == 0 {
return nil, errors.New(`service "template" needs a "template" object`)
}
default:
return nil, errors.New("unsupported service")
}
config := &xdrive.Config{
RemoteFolder: c.RemoteFolder,
Service: c.Service,
Secrets: c.Secrets,
SegmentBytes: c.SegmentBytes,
FlushIntervalMs: c.FlushIntervalMs,
PollIntervalMs: c.PollIntervalMs,
MaxPollIntervalMs: c.MaxPollIntervalMs,
SessionTtlSeconds: c.SessionTTLSeconds,
Concurrency: c.Concurrency,
EagerWindowMs: c.EagerWindowMs,
HoleTimeoutMs: c.HoleTimeoutMs,
Template: string(c.Template),
}
return config, nil
}
+3 -3
View File
@@ -113,10 +113,10 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
config.MinClientVer[i] = byte(u) config.MinClientVer[i] = byte(u)
} }
} }
errors.LogWarning(context.Background(), `REALITY: Changing "minClientVer" will increase the likelihood of your server's IP being blocked by the GFW`) // errors.LogWarning(context.Background(), `REALITY: Changing "minClientVer" will increase the likelihood of your server's IP being blocked by the GFW`)
} else { } else {
config.MinClientVer = []byte{26, 3, 27} // change it at your own risk: https://github.com/XTLS/Xray-core/commit/af7eb68028732a8ee3c0e5d6ab2b8a657bb2e770 // config.MinClientVer = []byte{26, 3, 27} // change it at your own risk: https://github.com/XTLS/Xray-core/commit/af7eb68028732a8ee3c0e5d6ab2b8a657bb2e770
errors.LogWarning(context.Background(), `REALITY: The default minimal client version is Xray-core v26.3.27, other clients may be refused to connect`) // errors.LogWarning(context.Background(), `REALITY: The default minimal client version is Xray-core v26.3.27, other clients may be refused to connect`)
} }
if c.MaxClientVer != "" { if c.MaxClientVer != "" {
config.MaxClientVer = make([]byte, 3) config.MaxClientVer = make([]byte, 3)
+2 -2
View File
@@ -10,7 +10,7 @@ import (
) )
type CustomSockoptConfig struct { type CustomSockoptConfig struct {
Syetem string `json:"system"` System string `json:"system"`
Network string `json:"network"` Network string `json:"network"`
Level string `json:"level"` Level string `json:"level"`
Opt string `json:"opt"` Opt string `json:"opt"`
@@ -124,7 +124,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
for _, copt := range c.CustomSockopt { for _, copt := range c.CustomSockopt {
customSockopt := &internet.CustomSockopt{ customSockopt := &internet.CustomSockopt{
System: copt.Syetem, System: copt.System,
Network: copt.Network, Network: copt.Network,
Level: copt.Level, Level: copt.Level,
Opt: copt.Opt, Opt: copt.Opt,
+73
View File
@@ -291,3 +291,76 @@ func TestHeaderCustomUDPBuildRejectsExprWithoutArgs(t *testing.T) {
t.Fatalf("expected transform arg rejection, got %v", err) t.Fatalf("expected transform arg rejection, got %v", err)
} }
} }
func TestXDriveStreamConfig(t *testing.T) {
config := new(StreamConfig)
if err := json.Unmarshal([]byte(`{
"method": "xdrive",
"xdriveSettings": {
"remoteFolder": "/tmp/xdrive",
"service": "local"
}
}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
built, err := config.Build()
if err != nil {
t.Fatalf("Build: %v", err)
}
if built.ProtocolName != "xdrive" {
t.Fatalf("ProtocolName is %q, want %q", built.ProtocolName, "xdrive")
}
if len(built.TransportSettings) != 1 || built.TransportSettings[0].ProtocolName != "xdrive" {
t.Fatalf("TransportSettings is %v, want a single xdrive entry", built.TransportSettings)
}
}
func TestXDriveRejectsUnknownService(t *testing.T) {
config := new(XDriveConfig)
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, err := config.Build(); err == nil {
t.Fatal("Build accepted an unsupported service")
}
}
func TestXDriveTemplateStreamConfig(t *testing.T) {
config := new(StreamConfig)
if err := json.Unmarshal([]byte(`{
"method": "xdrive",
"xdriveSettings": {
"remoteFolder": "folder",
"service": "template",
"secrets": ["user", "pass"],
"template": {
"flatten": true,
"auth": {"type": "basic", "username": "{secret0}", "password": "{secret1}"},
"put": {"method": "PUT", "url": "https://dav.example/{folder}/{name}"},
"get": {"method": "GET", "url": "https://dav.example/{folder}/{name}"},
"delete": {"method": "DELETE", "url": "https://dav.example/{folder}/{name}"},
"list": {"method": "PROPFIND", "url": "https://dav.example/{folder}/", "namesRegex": "<d:href>/folder/([^<]+)</d:href>"}
}
}
}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
built, err := config.Build()
if err != nil {
t.Fatalf("Build: %v", err)
}
if built.ProtocolName != "xdrive" {
t.Fatalf("ProtocolName is %q, want xdrive", built.ProtocolName)
}
}
func TestXDriveTemplateNeedsTemplate(t *testing.T) {
config := new(XDriveConfig)
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, err := config.Build(); err == nil {
t.Fatal("Build accepted a template service without a template")
}
}
+3
View File
@@ -312,6 +312,9 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
if err := json.Unmarshal(rawUser, account); err != nil { if err := json.Unmarshal(rawUser, account); err != nil {
return nil, errors.New(`VLESS users: invalid user`).Base(err) return nil, errors.New(`VLESS users: invalid user`).Base(err)
} }
// validateOutboundTransportSecurity needs to see these
c.Encryption = account.Encryption
c.Address = rec.Address
if account.Reverse != nil { // may not be reached: error json unmarshal if account.Reverse != nil { // may not be reached: error json unmarshal
return nil, errors.New(`VLESS users: please use simplified outbound's config style to use "reverse"`) return nil, errors.New(`VLESS users: please use simplified outbound's config style to use "reverse"`)
} }
+7 -23
View File
@@ -59,14 +59,13 @@ func (c *WireGuardPeerConfig) Build() (*wireguard.PeerConfig, error) {
type WireGuardConfig struct { type WireGuardConfig struct {
IsClient bool `json:""` IsClient bool `json:""`
NoKernelTun bool `json:"noKernelTun"` NoKernelTun bool `json:"noKernelTun"`
SecretKey string `json:"secretKey"` SecretKey string `json:"secretKey"`
Address []string `json:"address"` Address []string `json:"address"`
Peers []*WireGuardPeerConfig `json:"peers"` Peers []*WireGuardPeerConfig `json:"peers"`
MTU int32 `json:"mtu"` MTU int32 `json:"mtu"`
Reserved []byte `json:"reserved"` Reserved []byte `json:"reserved"`
DomainStrategy string `json:"domainStrategy"` DNS []string `json:"remoteDNS"`
DNS []string `json:"remoteDNS"`
} }
func (c *WireGuardConfig) Build() (proto.Message, error) { func (c *WireGuardConfig) Build() (proto.Message, error) {
@@ -125,21 +124,6 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
} }
config.Reserved = c.Reserved config.Reserved = c.Reserved
switch strings.ToLower(c.DomainStrategy) {
case "forceip", "":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP
case "forceipv4":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP4
case "forceipv6":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP6
case "forceipv4v6":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP46
case "forceipv6v4":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP64
default:
return nil, errors.New("unsupported domain strategy: ", c.DomainStrategy)
}
config.IsClient = c.IsClient config.IsClient = c.IsClient
config.NoKernelTun = c.NoKernelTun config.NoKernelTun = c.NoKernelTun
config.DNS = c.DNS config.DNS = c.DNS
+42 -35
View File
@@ -15,6 +15,8 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
core "github.com/xtls/xray-core/core" core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/freedom"
"github.com/xtls/xray-core/proxy/masque"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
) )
@@ -47,6 +49,7 @@ var (
"vmess": func() interface{} { return new(VMessOutboundConfig) }, "vmess": func() interface{} { return new(VMessOutboundConfig) },
"trojan": func() interface{} { return new(TrojanClientConfig) }, "trojan": func() interface{} { return new(TrojanClientConfig) },
"hysteria": func() interface{} { return new(HysteriaClientConfig) }, "hysteria": func() interface{} { return new(HysteriaClientConfig) },
"masque": func() interface{} { return new(MasqueClientConfig) },
"dns": func() interface{} { return new(DNSOutboundConfig) }, "dns": func() interface{} { return new(DNSOutboundConfig) },
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} }, "wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} },
}, "protocol", "settings") }, "protocol", "settings")
@@ -216,21 +219,11 @@ type OutboundDetourConfig struct {
Tag string `json:"tag"` Tag string `json:"tag"`
Settings *json.RawMessage `json:"settings"` Settings *json.RawMessage `json:"settings"`
StreamSetting *StreamConfig `json:"streamSettings"` StreamSetting *StreamConfig `json:"streamSettings"`
ProxySettings *ProxyConfig `json:"proxySettings"` ProxySettings *json.RawMessage `json:"proxySettings"`
MuxSettings *MuxConfig `json:"mux"` MuxSettings *MuxConfig `json:"mux"`
TargetStrategy string `json:"targetStrategy"` TargetStrategy string `json:"targetStrategy"`
} }
func (c *OutboundDetourConfig) checkChainProxyConfig() error {
if c.StreamSetting == nil || c.ProxySettings == nil || c.StreamSetting.SocketSettings == nil {
return nil
}
if len(c.ProxySettings.Tag) > 0 && len(c.StreamSetting.SocketSettings.DialerProxy) > 0 {
return errors.New("proxySettings.tag is conflicted with sockopt.dialerProxy").AtWarning()
}
return nil
}
func requiresTransportSecurity(address *Address) bool { func requiresTransportSecurity(address *Address) bool {
if address == nil || address.Address == nil { if address == nil || address.Address == nil {
return false return false
@@ -251,7 +244,7 @@ func validateOutboundTransportSecurity(rawConfig interface{}, senderSettings *pr
if vlessCfg.Encryption != "" && vlessCfg.Encryption != "none" { if vlessCfg.Encryption != "" && vlessCfg.Encryption != "none" {
return nil return nil
} }
if requiresTransportSecurity(vlessCfg.Vnext[0].Address) { if requiresTransportSecurity(vlessCfg.Address) {
return errors.New("vless without TLS or other encryption is prohibited unless the server address is a private IP or domain") return errors.New("vless without TLS or other encryption is prohibited unless the server address is a private IP or domain")
} }
} }
@@ -267,6 +260,10 @@ func validateOutboundTransportSecurity(rawConfig interface{}, senderSettings *pr
// Build implements Buildable. // Build implements Buildable.
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if c.ProxySettings != nil {
return nil, errors.PrintRemovedFeatureError(`outbound "proxySettings"`, `"streamSettings.sockopt.dialerProxy"`)
}
senderSettings := &proxyman.SenderConfig{} senderSettings := &proxyman.SenderConfig{}
switch strings.ToLower(c.TargetStrategy) { switch strings.ToLower(c.TargetStrategy) {
case "asis", "": case "asis", "":
@@ -294,9 +291,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
default: default:
return nil, errors.New("unsupported target domain strategy: ", c.TargetStrategy) return nil, errors.New("unsupported target domain strategy: ", c.TargetStrategy)
} }
if err := c.checkChainProxyConfig(); err != nil {
return nil, err
}
if c.SendThrough != nil { if c.SendThrough != nil {
address := ParseSendThough(c.SendThrough) address := ParseSendThough(c.SendThrough)
@@ -322,26 +316,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
senderSettings.StreamSettings = ss senderSettings.StreamSettings = ss
} }
if c.ProxySettings != nil {
ps, err := c.ProxySettings.Build()
if err != nil {
return nil, errors.New("invalid outbound detour proxy settings").Base(err)
}
if ps.TransportLayerProxy {
if senderSettings.StreamSettings != nil {
if senderSettings.StreamSettings.SocketSettings != nil {
senderSettings.StreamSettings.SocketSettings.DialerProxy = ps.Tag
} else {
senderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{DialerProxy: ps.Tag}
}
} else {
senderSettings.StreamSettings = &internet.StreamConfig{SocketSettings: &internet.SocketConfig{DialerProxy: ps.Tag}}
}
ps = nil
}
senderSettings.ProxySettings = ps
}
if c.MuxSettings != nil { if c.MuxSettings != nil {
ms, err := c.MuxSettings.Build() ms, err := c.MuxSettings.Build()
if err != nil { if err != nil {
@@ -366,6 +340,39 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
return nil, err return nil, err
} }
if _, ok := ts.(*masque.ClientConfig); ok {
if ms := senderSettings.MultiplexSettings; ms != nil && ms.Enabled {
return nil, errors.New(`masque outbound does not support "mux"`)
}
} else if senderSettings.StreamSettings != nil && senderSettings.StreamSettings.ProtocolName == "masque" {
return nil, errors.New("the masque transport can only be used by the masque outbound")
}
if fc, ok := ts.(*freedom.Config); ok {
if senderSettings.StreamSettings != nil &&
senderSettings.StreamSettings.SocketSettings != nil &&
senderSettings.StreamSettings.SocketSettings.AddressPortStrategy != internet.AddressPortStrategy_None {
return nil, errors.New(`freedom outbound does not support "sockopt.addressPortStrategy"`)
}
var strategy internet.DomainStrategy
if strategy = senderSettings.TargetStrategy; strategy != internet.DomainStrategy_AS_IS {
errors.LogWarning(context.Background(), `The "outbound.targetStrategy" setting is not supported directly by freedom and has been automatically migrated to "sockopt.domainStrategy" with no behavior change.`)
senderSettings.TargetStrategy = internet.DomainStrategy_AS_IS
} else if strategy = fc.DomainStrategy; strategy != internet.DomainStrategy_AS_IS {
errors.LogWarning(context.Background(), `The "freedom.domainStrategy" setting is deprecated and will be removed. For compatibility, its value has been automatically migrated to "sockopt.domainStrategy". Please update your config before removal.`)
}
if strategy != internet.DomainStrategy_AS_IS {
if senderSettings.StreamSettings == nil {
senderSettings.StreamSettings = &internet.StreamConfig{}
}
if senderSettings.StreamSettings.SocketSettings == nil {
senderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{}
}
senderSettings.StreamSettings.SocketSettings.DomainStrategy = strategy
}
}
return &core.OutboundHandlerConfig{ return &core.OutboundHandlerConfig{
SenderSettings: serial.ToTypedMessage(senderSettings), SenderSettings: serial.ToTypedMessage(senderSettings),
Tag: c.Tag, Tag: c.Tag,
+273 -139
View File
@@ -1,15 +1,18 @@
package main package main
import ( import (
"errors" "bytes"
"flag" "flag"
"fmt" "fmt"
"go/build"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort"
"strings" "strings"
"sync"
"sync/atomic"
"mvdan.cc/gofumpt/format"
) )
var ( var (
@@ -23,101 +26,27 @@ var (
isFormat bool isFormat bool
) )
// envFile returns the name of the Go environment configuration file. func getModuleInfo(pwd string) (modPath, langVersion string, err error) {
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166 data, err := os.ReadFile(filepath.Join(pwd, "go.mod"))
func envFile() (string, error) {
if file := os.Getenv("GOENV"); file != "" {
if file == "off" {
return "", errors.New("GOENV=off")
}
return file, nil
}
dir, err := os.UserConfigDir()
if err != nil { if err != nil {
return "", err return "", "", err
} }
if dir == "" { for _, line := range strings.Split(string(data), "\n") {
return "", errors.New("missing user-config dir") fields := strings.Fields(line)
} if len(fields) >= 2 {
return filepath.Join(dir, "go", "env"), nil switch fields[0] {
} case "module":
modPath = fields[1]
// GetRuntimeEnv returns the value of runtime environment variable, case "go":
// that is set by running following command: `go env -w key=value`. langVersion = "go" + strings.TrimPrefix(fields[1], "go")
func GetRuntimeEnv(key string) (string, error) {
file, err := envFile()
if err != nil {
return "", err
}
if file == "" {
return "", errors.New("missing runtime env file")
}
var data []byte
var runtimeEnv string
data, readErr := os.ReadFile(file)
if readErr != nil {
return "", readErr
}
envStrings := strings.Split(string(data), "\n")
for _, envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=")
if len(envKeyValue) == 2 && strings.TrimSpace(envKeyValue[0]) == key {
runtimeEnv = strings.TrimSpace(envKeyValue[1])
}
}
return runtimeEnv, nil
}
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
func GetGOBIN() string {
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
GOBIN := os.Getenv("GOBIN")
if GOBIN == "" {
var err error
// The one set by user by running `go env -w GOBIN=/path`
GOBIN, err = GetRuntimeEnv("GOBIN")
if err != nil {
// The default one that Golang uses
return filepath.Join(build.Default.GOPATH, "bin")
}
if GOBIN == "" {
return filepath.Join(build.Default.GOPATH, "bin")
}
return GOBIN
}
return GOBIN
}
func Run(binary string, args []string) ([]byte, error) {
cmd := exec.Command(binary, args...)
cmd.Env = append(cmd.Env, os.Environ()...)
output, cmdErr := cmd.CombinedOutput()
if cmdErr != nil {
return nil, cmdErr
}
return output, nil
}
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{}{}
go func(file string) {
output, err := Run(binary, append(args, file))
if err != nil {
fmt.Println(err)
} else if len(output) > 0 {
fmt.Println(string(output))
formatRequired = true
} }
<-maxTasks }
}(file)
} }
return formatRequired return modPath, langVersion, nil
}
func formatGoSource(src []byte, opts format.Options) ([]byte, error) {
return format.Source(src, opts)
} }
func main() { func main() {
@@ -150,26 +79,76 @@ func main() {
} }
pwd := *directory pwd := *directory
GOBIN := GetGOBIN() modPath, langVersion, modErr := getModuleInfo(pwd)
binPath := os.Getenv("PATH") if modErr != nil {
pathSlice := []string{pwd, GOBIN, binPath} fmt.Println("Error reading go.mod:", modErr)
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
os.Setenv("PATH", binPath)
suffix := ""
if runtime.GOOS == "windows" {
suffix = ".exe"
}
gofmt := "gofumpt" + suffix
if gofmtPath, err := exec.LookPath(gofmt); err != nil {
fmt.Println("Can not find", gofmt, "in system path or current working directory.")
os.Exit(1) os.Exit(1)
} else { }
gofmt = gofmtPath opts := format.Options{
LangVersion: langVersion,
ModulePath: modPath,
}
if isFormat {
fmt.Println("Formatting Go source files...")
} else if isCheck {
fmt.Println("Checking files thar are not properly formatted...")
}
jobs := make(chan string, runtime.NumCPU())
var wg sync.WaitGroup
var formatRequired atomic.Bool
var hasErrors atomic.Bool
for i := 0; i < runtime.NumCPU(); i++ {
wg.Go(func() {
for path := range jobs {
src, err := os.ReadFile(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading %s: %v\n", path, err)
hasErrors.Store(true)
continue
}
formatted, err := formatGoSource(src, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Error formatting %s: %v\n", path, err)
hasErrors.Store(true)
continue
}
if !bytes.Equal(src, formatted) {
var diffText []byte
if isDryrun {
newName := filepath.ToSlash(path)
oldName := newName + ".orig"
diffText = diff(oldName, src, newName, formatted)
}
if isFormat {
info, statErr := os.Stat(path)
if statErr != nil {
fmt.Fprintf(os.Stderr, "Error stating %s: %v\n", path, statErr)
hasErrors.Store(true)
continue
}
if writeErr := os.WriteFile(path, formatted, info.Mode().Perm()); writeErr != nil {
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", path, writeErr)
hasErrors.Store(true)
continue
}
}
formatRequired.Store(true)
if isDryrun && len(diffText) > 0 {
fmt.Printf("%s\n%s", path, diffText)
} else {
fmt.Println(path)
}
}
}
})
} }
rawFilesSlice := make([]string, 0, 1000)
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error { walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@@ -186,51 +165,206 @@ func main() {
!strings.HasSuffix(filename, ".pb.go") && !strings.HasSuffix(filename, ".pb.go") &&
!strings.Contains(dir, filepath.Join("testing", "mocks")) && !strings.Contains(dir, filepath.Join("testing", "mocks")) &&
!strings.Contains(path, filepath.Join("main", "distro", "all", "all.go")) { !strings.Contains(path, filepath.Join("main", "distro", "all", "all.go")) {
rawFilesSlice = append(rawFilesSlice, path) jobs <- path
} }
return nil return nil
}) })
close(jobs)
wg.Wait()
if walkErr != nil { if walkErr != nil {
fmt.Println(walkErr) fmt.Println(walkErr)
os.Exit(1) os.Exit(1)
} }
if isFormat { if hasErrors.Load() {
gofmtArgs := []string{ os.Exit(1)
"-l", "-e", "-w", }
}
fmt.Println("Formatting Go source files...") if isFormat {
RunMany(gofmt, gofmtArgs, rawFilesSlice) if formatRequired.Load() {
fmt.Println("Do NOT forget to commit file changes.") fmt.Println("Do NOT forget to commit file changes.")
}
} }
if isCheck { if isCheck {
gofmtListArgs := []string{ if formatRequired.Load() {
"-l", "-e",
}
fmt.Println("Checking files thar are not properly formatted...")
formatRequired := RunMany(gofmt, gofmtListArgs, rawFilesSlice)
if formatRequired {
fmt.Println("Format problem(s) found.") fmt.Println("Format problem(s) found.")
} fmt.Println("Please run 'go run ./infra/vformat/main.go' to format the Go source files.")
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) os.Exit(1)
} else { } else {
fmt.Println("All Go source file format check has been passed.") fmt.Println("All Go source file format check has been passed.")
} }
} }
} }
// diff algorithm copied from mvdan.cc/gofumpt/internal/govendor/diff
type pair struct{ x, y int }
func diff(oldName string, old []byte, newName string, new []byte) []byte {
if bytes.Equal(old, new) {
return nil
}
x := diffLines(old)
y := diffLines(new)
var out bytes.Buffer
fmt.Fprintf(&out, "diff %s %s\n", oldName, newName)
fmt.Fprintf(&out, "--- %s\n", oldName)
fmt.Fprintf(&out, "+++ %s\n", newName)
var (
done pair
chunk pair
count pair
ctext []string
)
for _, m := range diffTgs(x, y) {
if m.x < done.x {
continue
}
start := m
for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] {
start.x--
start.y--
}
end := m
for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] {
end.x++
end.y++
}
for _, s := range x[done.x:start.x] {
ctext = append(ctext, "-"+s)
count.x++
}
for _, s := range y[done.y:start.y] {
ctext = append(ctext, "+"+s)
count.y++
}
const C = 3
if (end.x < len(x) || end.y < len(y)) &&
(end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) {
for _, s := range x[start.x:end.x] {
ctext = append(ctext, " "+s)
count.x++
count.y++
}
done = end
continue
}
if len(ctext) > 0 {
n := end.x - start.x
if n > C {
n = C
}
for _, s := range x[start.x : start.x+n] {
ctext = append(ctext, " "+s)
count.x++
count.y++
}
done = pair{start.x + n, start.y + n}
if count.x > 0 {
chunk.x++
}
if count.y > 0 {
chunk.y++
}
fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y)
for _, s := range ctext {
out.WriteString(s)
}
count.x = 0
count.y = 0
ctext = ctext[:0]
}
if end.x >= len(x) && end.y >= len(y) {
break
}
chunk = pair{end.x - C, end.y - C}
for _, s := range x[chunk.x:end.x] {
ctext = append(ctext, " "+s)
count.x++
count.y++
}
done = end
}
return out.Bytes()
}
func diffLines(x []byte) []string {
l := strings.SplitAfter(string(x), "\n")
if l[len(l)-1] == "" {
l = l[:len(l)-1]
} else {
l[len(l)-1] += "\n\\ No newline at end of file\n"
}
return l
}
func diffTgs(x, y []string) []pair {
m := make(map[string]int)
for _, s := range x {
if c := m[s]; c > -2 {
m[s] = c - 1
}
}
for _, s := range y {
if c := m[s]; c > -8 {
m[s] = c - 4
}
}
var xi, yi, inv []int
for i, s := range y {
if m[s] == -5 {
m[s] = len(yi)
yi = append(yi, i)
}
}
for i, s := range x {
if j, ok := m[s]; ok && j >= 0 {
xi = append(xi, i)
inv = append(inv, j)
}
}
J := inv
n := len(xi)
T := make([]int, n)
L := make([]int, n)
for i := range T {
T[i] = n + 1
}
for i := 0; i < n; i++ {
k := sort.Search(n, func(k int) bool {
return T[k] >= J[i]
})
T[k] = J[i]
L[i] = k + 1
}
k := 0
for _, v := range L {
if k < v {
k = v
}
}
seq := make([]pair, 2+k)
seq[1+k] = pair{len(x), len(y)}
lastj := n
for i := n - 1; i >= 0; i-- {
if L[i] == k && J[i] < lastj {
seq[k] = pair{xi[i], yi[J[i]]}
k--
}
}
seq[0] = pair{0, 0}
return seq
}
+3
View File
@@ -41,6 +41,7 @@ import (
_ "github.com/xtls/xray-core/proxy/freedom" _ "github.com/xtls/xray-core/proxy/freedom"
_ "github.com/xtls/xray-core/proxy/http" _ "github.com/xtls/xray-core/proxy/http"
_ "github.com/xtls/xray-core/proxy/loopback" _ "github.com/xtls/xray-core/proxy/loopback"
_ "github.com/xtls/xray-core/proxy/masque"
_ "github.com/xtls/xray-core/proxy/shadowsocks" _ "github.com/xtls/xray-core/proxy/shadowsocks"
_ "github.com/xtls/xray-core/proxy/socks" _ "github.com/xtls/xray-core/proxy/socks"
_ "github.com/xtls/xray-core/proxy/trojan" _ "github.com/xtls/xray-core/proxy/trojan"
@@ -54,12 +55,14 @@ import (
_ "github.com/xtls/xray-core/transport/internet/grpc" _ "github.com/xtls/xray-core/transport/internet/grpc"
_ "github.com/xtls/xray-core/transport/internet/httpupgrade" _ "github.com/xtls/xray-core/transport/internet/httpupgrade"
_ "github.com/xtls/xray-core/transport/internet/kcp" _ "github.com/xtls/xray-core/transport/internet/kcp"
_ "github.com/xtls/xray-core/transport/internet/masque"
_ "github.com/xtls/xray-core/transport/internet/reality" _ "github.com/xtls/xray-core/transport/internet/reality"
_ "github.com/xtls/xray-core/transport/internet/splithttp" _ "github.com/xtls/xray-core/transport/internet/splithttp"
_ "github.com/xtls/xray-core/transport/internet/tcp" _ "github.com/xtls/xray-core/transport/internet/tcp"
_ "github.com/xtls/xray-core/transport/internet/tls" _ "github.com/xtls/xray-core/transport/internet/tls"
_ "github.com/xtls/xray-core/transport/internet/udp" _ "github.com/xtls/xray-core/transport/internet/udp"
_ "github.com/xtls/xray-core/transport/internet/websocket" _ "github.com/xtls/xray-core/transport/internet/websocket"
_ "github.com/xtls/xray-core/transport/internet/xdrive"
// Transport headers // Transport headers
_ "github.com/xtls/xray-core/transport/internet/headers/http" _ "github.com/xtls/xray-core/transport/internet/headers/http"
+31 -6
View File
@@ -2,12 +2,15 @@
package blackhole package blackhole
import ( import (
"bytes"
"context" "context"
"net/http"
"time" "time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/signal"
@@ -17,14 +20,34 @@ import (
// Handler is an outbound connection that silently swallow the entire payload. // Handler is an outbound connection that silently swallow the entire payload.
type Handler struct { type Handler struct {
response ResponseConfig response []byte
}
var http403response = http.Response{
StatusCode: 403,
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header{
"Connection": {"close"},
"Cache-Control": {"max-age=3600, public"},
},
} }
// New creates a new blackhole handler. // New creates a new blackhole handler.
func New(ctx context.Context, config *Config) (*Handler, error) { func New(ctx context.Context, config *Config) (*Handler, error) {
response, err := config.GetInternalResponse() response := []byte{}
if err != nil { if config.Response != nil {
return nil, err switch config.Response.Type {
case "", "none":
case "http":
var data bytes.Buffer
common.Must(http403response.Write(&data))
response = data.Bytes()
case "custom":
response = config.Response.CustomResponseData
default:
return nil, errors.New("unknown blackhole response: " + config.Response.Type)
}
} }
return &Handler{ return &Handler{
response: response, response: response,
@@ -37,8 +60,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
ob.Name = "blackhole" ob.Name = "blackhole"
nBytes := h.response.WriteTo(link.Writer) if len(h.response) > 0 {
if nBytes > 0 { mbc := buf.MultiBufferContainer{}
common.Must2(mbc.Write(h.response))
link.Writer.WriteMultiBuffer(mbc.MultiBuffer)
// Sleep a little here to make sure the response is sent to client. // Sleep a little here to make sure the response is sent to client.
time.Sleep(time.Second) time.Sleep(time.Second)
} }
+44 -10
View File
@@ -1,12 +1,15 @@
package blackhole_test package blackhole_test
import ( import (
"bufio"
"bytes"
"context" "context"
"crypto/rand"
"net/http"
"testing" "testing"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/proxy/blackhole" "github.com/xtls/xray-core/proxy/blackhole"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
@@ -16,27 +19,58 @@ import (
func TestBlackholeHTTPResponse(t *testing.T) { func TestBlackholeHTTPResponse(t *testing.T) {
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}}) ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
handler, err := blackhole.New(ctx, &blackhole.Config{ handler, err := blackhole.New(ctx, &blackhole.Config{
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}), Response: &blackhole.Response{Type: "http"},
}) })
common.Must(err) common.Must(err)
reader, writer := pipe.New(pipe.WithoutSizeLimit()) reader, writer := pipe.New(pipe.WithoutSizeLimit())
var mb buf.MultiBuffer dataCh := make(chan buf.MultiBuffer, 1)
var rerr error
go func() { go func() {
b, e := reader.ReadMultiBuffer() mb := common.Must2(reader.ReadMultiBuffer())
mb = b dataCh <- mb
rerr = e
}() }()
link := transport.Link{ link := transport.Link{
Reader: reader, Reader: reader,
Writer: writer, Writer: writer,
} }
common.Must(handler.Process(ctx, &link, nil)) common.Must(handler.Process(ctx, &link, nil))
mb := <-dataCh
data := make([]byte, mb.Len())
mb.Copy(data)
resp := common.Must2(http.ReadResponse(bufio.NewReader(bytes.NewBuffer(data)), nil))
if resp.StatusCode != 403 {
t.Errorf("expected 403 response, got %d", resp.StatusCode)
}
}
func TestBlackholeCustomResponse(t *testing.T) {
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
// slightly bigger than a buffer
expected := make([]byte, buf.Size+1000)
if _, err := rand.Read(expected); err != nil {
t.Fatal(err)
}
handler, err := blackhole.New(ctx, &blackhole.Config{
Response: &blackhole.Response{
Type: "custom",
CustomResponseData: expected,
},
})
common.Must(err)
reader, writer := pipe.New(pipe.WithoutSizeLimit())
var actual buf.MultiBuffer
var rerr error
go func() {
actual, rerr = reader.ReadMultiBuffer()
}()
link := transport.Link{Reader: reader, Writer: writer}
common.Must(handler.Process(ctx, &link, nil))
common.Must(rerr) common.Must(rerr)
if mb.IsEmpty() {
t.Error("expect http response, but nothing") if actual.String() != string(expected) {
t.Errorf("custom response mismatch")
} }
} }
-47
View File
@@ -1,47 +0,0 @@
package blackhole
import (
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
)
const (
http403response = `HTTP/1.1 403 Forbidden
Connection: close
Cache-Control: max-age=3600, public
Content-Length: 0
`
)
// ResponseConfig is the configuration for blackhole responses.
type ResponseConfig interface {
// WriteTo writes a predefined response to the specified buffer.
WriteTo(buf.Writer) int32
}
// WriteTo implements ResponseConfig.WriteTo().
func (*NoneResponse) WriteTo(buf.Writer) int32 { return 0 }
// WriteTo implements ResponseConfig.WriteTo().
func (*HTTPResponse) WriteTo(writer buf.Writer) int32 {
b := buf.New()
common.Must2(b.WriteString(http403response))
n := b.Len()
writer.WriteMultiBuffer(buf.MultiBuffer{b})
return n
}
// GetInternalResponse converts response settings from proto to internal data structure.
func (c *Config) GetInternalResponse() (ResponseConfig, error) {
if c.GetResponse() == nil {
return new(NoneResponse), nil
}
config, err := c.GetResponse().GetInstance()
if err != nil {
return nil, err
}
return config.(ResponseConfig), nil
}
+37 -59
View File
@@ -7,7 +7,6 @@
package blackhole package blackhole
import ( import (
serial "github.com/xtls/xray-core/common/serial"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
@@ -22,26 +21,28 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
type NoneResponse struct { type Response struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
sizeCache protoimpl.SizeCache CustomResponseData []byte `protobuf:"bytes,2,opt,name=custom_response_data,json=customResponseData,proto3" json:"custom_response_data,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *NoneResponse) Reset() { func (x *Response) Reset() {
*x = NoneResponse{} *x = Response{}
mi := &file_proxy_blackhole_config_proto_msgTypes[0] mi := &file_proxy_blackhole_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
func (x *NoneResponse) String() string { func (x *Response) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
} }
func (*NoneResponse) ProtoMessage() {} func (*Response) ProtoMessage() {}
func (x *NoneResponse) ProtoReflect() protoreflect.Message { func (x *Response) ProtoReflect() protoreflect.Message {
mi := &file_proxy_blackhole_config_proto_msgTypes[0] mi := &file_proxy_blackhole_config_proto_msgTypes[0]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -53,57 +54,35 @@ func (x *NoneResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x) return mi.MessageOf(x)
} }
// Deprecated: Use NoneResponse.ProtoReflect.Descriptor instead. // Deprecated: Use Response.ProtoReflect.Descriptor instead.
func (*NoneResponse) Descriptor() ([]byte, []int) { func (*Response) Descriptor() ([]byte, []int) {
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0} return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0}
} }
type HTTPResponse struct { func (x *Response) GetType() string {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HTTPResponse) Reset() {
*x = HTTPResponse{}
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HTTPResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HTTPResponse) ProtoMessage() {}
func (x *HTTPResponse) ProtoReflect() protoreflect.Message {
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) return x.Type
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
} }
return mi.MessageOf(x) return ""
} }
// Deprecated: Use HTTPResponse.ProtoReflect.Descriptor instead. func (x *Response) GetCustomResponseData() []byte {
func (*HTTPResponse) Descriptor() ([]byte, []int) { if x != nil {
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1} return x.CustomResponseData
}
return nil
} }
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Response *serial.TypedMessage `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"` Response *Response `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
func (x *Config) Reset() { func (x *Config) Reset() {
*x = Config{} *x = Config{}
mi := &file_proxy_blackhole_config_proto_msgTypes[2] mi := &file_proxy_blackhole_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -115,7 +94,7 @@ func (x *Config) String() string {
func (*Config) ProtoMessage() {} func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message { func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_blackhole_config_proto_msgTypes[2] mi := &file_proxy_blackhole_config_proto_msgTypes[1]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -128,10 +107,10 @@ func (x *Config) ProtoReflect() protoreflect.Message {
// Deprecated: Use Config.ProtoReflect.Descriptor instead. // Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) { func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{2} return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}
} }
func (x *Config) GetResponse() *serial.TypedMessage { func (x *Config) GetResponse() *Response {
if x != nil { if x != nil {
return x.Response return x.Response
} }
@@ -142,11 +121,12 @@ var File_proxy_blackhole_config_proto protoreflect.FileDescriptor
const file_proxy_blackhole_config_proto_rawDesc = "" + const file_proxy_blackhole_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x1cproxy/blackhole/config.proto\x12\x14xray.proxy.blackhole\x1a!common/serial/typed_message.proto\"\x0e\n" + "\x1cproxy/blackhole/config.proto\x12\x14xray.proxy.blackhole\"P\n" +
"\fNoneResponse\"\x0e\n" + "\bResponse\x12\x12\n" +
"\fHTTPResponse\"F\n" + "\x04type\x18\x01 \x01(\tR\x04type\x120\n" +
"\x06Config\x12<\n" + "\x14custom_response_data\x18\x02 \x01(\fR\x12customResponseData\"D\n" +
"\bresponse\x18\x01 \x01(\v2 .xray.common.serial.TypedMessageR\bresponseB^\n" + "\x06Config\x12:\n" +
"\bresponse\x18\x01 \x01(\v2\x1e.xray.proxy.blackhole.ResponseR\bresponseB^\n" +
"\x18com.xray.proxy.blackholeP\x01Z)github.com/xtls/xray-core/proxy/blackhole\xaa\x02\x14Xray.Proxy.Blackholeb\x06proto3" "\x18com.xray.proxy.blackholeP\x01Z)github.com/xtls/xray-core/proxy/blackhole\xaa\x02\x14Xray.Proxy.Blackholeb\x06proto3"
var ( var (
@@ -161,15 +141,13 @@ func file_proxy_blackhole_config_proto_rawDescGZIP() []byte {
return file_proxy_blackhole_config_proto_rawDescData return file_proxy_blackhole_config_proto_rawDescData
} }
var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_blackhole_config_proto_goTypes = []any{ var file_proxy_blackhole_config_proto_goTypes = []any{
(*NoneResponse)(nil), // 0: xray.proxy.blackhole.NoneResponse (*Response)(nil), // 0: xray.proxy.blackhole.Response
(*HTTPResponse)(nil), // 1: xray.proxy.blackhole.HTTPResponse (*Config)(nil), // 1: xray.proxy.blackhole.Config
(*Config)(nil), // 2: xray.proxy.blackhole.Config
(*serial.TypedMessage)(nil), // 3: xray.common.serial.TypedMessage
} }
var file_proxy_blackhole_config_proto_depIdxs = []int32{ var file_proxy_blackhole_config_proto_depIdxs = []int32{
3, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.common.serial.TypedMessage 0, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.proxy.blackhole.Response
1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension type_name
@@ -188,7 +166,7 @@ func file_proxy_blackhole_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 3, NumMessages: 2,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
+5 -6
View File
@@ -6,12 +6,11 @@ option go_package = "github.com/xtls/xray-core/proxy/blackhole";
option java_package = "com.xray.proxy.blackhole"; option java_package = "com.xray.proxy.blackhole";
option java_multiple_files = true; option java_multiple_files = true;
import "common/serial/typed_message.proto"; message Response {
string type = 1;
message NoneResponse {} bytes custom_response_data = 2;
}
message HTTPResponse {}
message Config { message Config {
xray.common.serial.TypedMessage response = 1; Response response = 1;
} }
+7 -14
View File
@@ -1,26 +1,19 @@
package blackhole_test package blackhole_test
import ( import (
"bufio" "context"
"net/http"
"testing" "testing"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/proxy/blackhole"
. "github.com/xtls/xray-core/proxy/blackhole"
) )
func TestHTTPResponse(t *testing.T) { func TestHTTPResponse(t *testing.T) {
buffer := buf.New() handler, err := blackhole.New(context.Background(), &blackhole.Config{
Response: &blackhole.Response{Type: "http"},
httpResponse := new(HTTPResponse) })
httpResponse.WriteTo(buf.NewWriter(buffer))
reader := bufio.NewReader(buffer)
response, err := http.ReadResponse(reader, nil)
common.Must(err) common.Must(err)
if handler == nil {
if response.StatusCode != 403 { t.Error("expected HTTP response handler")
t.Error("expected status code 403, but got ", response.StatusCode)
} }
} }
+96 -102
View File
@@ -53,6 +53,10 @@ func reloadEnvSettings() error {
func init() { func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
h := new(Handler) h := new(Handler)
if streamSettings, ok := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig); ok && streamSettings.SocketSettings != nil {
h.resolveStrategy = streamSettings.SocketSettings.DomainStrategy
h.usesDialerProxy = len(streamSettings.SocketSettings.DialerProxy) > 0
}
if err := core.RequireFeatures(ctx, func(pm policy.Manager) error { if err := core.RequireFeatures(ctx, func(pm policy.Manager) error {
return h.Init(config.(*Config), pm) return h.Init(config.(*Config), pm)
}); err != nil { }); err != nil {
@@ -89,9 +93,11 @@ type FinalRule struct {
// Handler handles Freedom connections. // Handler handles Freedom connections.
type Handler struct { type Handler struct {
policyManager policy.Manager policyManager policy.Manager
config *Config config *Config
finalRules []*FinalRule finalRules []*FinalRule
resolveStrategy internet.DomainStrategy
usesDialerProxy bool
} }
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) { func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
@@ -168,22 +174,6 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
return nil return nil
} }
func (h *Handler) shouldResolveDomainBeforeFinalRules(dialDest net.Destination, defaultRule *FinalRule) bool {
if !dialDest.Address.Family().IsDomain() {
return false
}
if len(h.finalRules) > 0 {
rule := h.finalRules[0]
if rule.action == RuleAction_Allow && rule.network[dialDest.Network] && len(rule.port) == 0 && rule.ip == nil {
return false
}
}
if defaultRule != nil || len(h.finalRules) > 0 {
return true
}
return false
}
func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule { func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule {
for _, rule := range h.finalRules { for _, rule := range h.finalRules {
if rule.Apply(network, address, port) { if rule.Apply(network, address, port) {
@@ -196,17 +186,16 @@ func (h *Handler) matchFinalRule(network net.Network, address net.Address, port
return nil return nil
} }
func (h *Handler) applyFinalRules(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) RuleAction {
if rule := h.matchFinalRule(network, address, port, defaultRule); rule != nil {
return rule.action
}
return RuleAction_Allow
}
// Init initializes the Handler with necessary parameters. // Init initializes the Handler with necessary parameters.
func (h *Handler) Init(config *Config, pm policy.Manager) error { func (h *Handler) Init(config *Config, pm policy.Manager) error {
h.config = config h.config = config
h.policyManager = pm h.policyManager = pm
if h.usesDialerProxy { // freedom is not the final outbound, final rules do not apply
if len(config.FinalRules) > 0 {
errors.LogWarning(context.Background(), `The "finalRules" setting is ignored when "sockopt.dialerProxy" is set, since freedom is not the final outbound.`)
}
return nil
}
h.finalRules = make([]*FinalRule, 0, len(config.FinalRules)) h.finalRules = make([]*FinalRule, 0, len(config.FinalRules))
for _, rc := range config.FinalRules { for _, rc := range config.FinalRules {
rule, err := buildFinalRule(rc) rule, err := buildFinalRule(rc)
@@ -237,6 +226,20 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
} }
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
delay := h.blockDelay(rule)
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
timer := time.AfterFunc(delay, func() {
common.Interrupt(input)
common.Interrupt(output)
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
})
defer timer.Stop()
defer common.Close(output)
_ = buf.Copy(input, buf.Discard)
return nil
}
func isValidAddress(addr *net.IPOrDomain) bool { func isValidAddress(addr *net.IPOrDomain) bool {
if addr == nil { if addr == nil {
return false return false
@@ -256,7 +259,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ob.Name = "freedom" ob.Name = "freedom"
ob.CanSpliceCopy = 1 ob.CanSpliceCopy = 1
inbound := session.InboundFromContext(ctx) inbound := session.InboundFromContext(ctx)
defaultRule := getDefaultFinalRule(inbound) var defaultRule *FinalRule
if !h.usesDialerProxy { // freedom is not the final outbound, final rules do not apply (and the domain is not resolved)
defaultRule = getDefaultFinalRule(inbound)
}
destination := ob.Target destination := ob.Target
origTargetAddr := ob.OriginalTarget.Address origTargetAddr := ob.OriginalTarget.Address
@@ -284,61 +290,53 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
var conn stat.Connection var conn stat.Connection
var blockedDest *net.Destination var blockedDest *net.Destination
var blockedRule *FinalRule var blockedRule *FinalRule
firstResolve := true
err := retry.ExponentialBackoff(5, 100).On(func() error { err := retry.ExponentialBackoff(5, 100).On(func() error {
dialDest := destination if destination.Address.Family().IsDomain() {
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() { if defaultRule != nil || len(h.finalRules) > 0 {
strategy := h.config.DomainStrategy if strategy := h.resolveStrategy; strategy.HasStrategy() {
if destination.Network == net.Network_UDP && origTargetAddr != nil && outGateway == nil { ips, err := internet.LookupForIP(destination.Address.Domain(), strategy, outGateway)
strategy = strategy.GetDynamicStrategy(origTargetAddr.Family()) if err != nil { // non-force may still dial with system DNS
} errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", destination.Address.Domain())
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway) if strategy.ForceIP() {
if err != nil { return err // retry
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain()) }
if h.config.DomainStrategy.ForceIP() || h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { }
return err for _, ip := range ips {
if addr := net.IPAddress(ip); addr != nil {
if rule := h.matchFinalRule(destination.Network, addr, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &destination
blockedDest.Address = addr
blockedRule = rule
return nil
}
}
}
} else {
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, destination.Address.Domain())
if err != nil { // dialer may retry DNS
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", destination.Address.Domain())
}
for _, addr := range addrs {
if ipAddr := net.IPAddress(addr.IP); ipAddr != nil {
if rule := h.matchFinalRule(destination.Network, ipAddr, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &destination
blockedDest.Address = ipAddr
blockedRule = rule
return nil
}
}
}
} }
} else {
dialDest = net.Destination{
Network: dialDest.Network,
Address: net.IPAddress(ips[dice.Roll(len(ips))]),
Port: dialDest.Port,
}
errors.LogInfo(ctx, "dialing to ", dialDest)
} }
} else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules } else {
domain := dialDest.Address.Domain() if rule := h.matchFinalRule(destination.Network, destination.Address, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
var ips []net.IP blockedDest = &destination
if firstResolve { blockedRule = rule
firstResolve = false return nil
supportIPv4, supportIPv6 := utils.CheckRoutes()
if supportIPv4 {
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip4", domain)
}
if len(ips) == 0 && supportIPv6 {
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip6", domain)
}
if len(ips) == 0 {
return errors.New("failed to get IP address for domain ", domain)
}
} else {
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip", domain)
} }
if len(ips) == 0 { // SRV/TXT, lookup failed
return errors.New("failed to get IP address for domain ", domain)
}
if addr := net.IPAddress(ips[dice.Roll(len(ips))]); addr != nil {
dialDest.Address = addr
errors.LogInfo(ctx, "dialing to ", dialDest)
}
}
if rule := h.matchFinalRule(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &dialDest
blockedRule = rule
return nil
} }
rawConn, err := dialer.Dial(ctx, dialDest) rawConn, err := dialer.Dial(ctx, destination)
if err != nil { if err != nil {
return err return err
} }
@@ -350,20 +348,17 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return errors.New("failed to open connection to ", destination).Base(err) return errors.New("failed to open connection to ", destination).Base(err)
} }
if blockedDest != nil { if blockedDest != nil {
delay := h.blockDelay(blockedRule) return h.blackhole(ctx, input, output, blockedRule, blockedDest)
errors.LogInfo(ctx, "blocked target: ", *blockedDest, ", blackholing connection for ", delay)
timer := time.AfterFunc(delay, func() {
common.Interrupt(input)
common.Interrupt(output)
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *blockedDest)
})
defer timer.Stop()
defer common.Close(output)
if err := buf.Copy(input, buf.Discard); err != nil {
return nil
}
return nil
} }
if destination.Address.Family().IsDomain() && (defaultRule != nil || len(h.finalRules) > 0) {
// pre-check may fail or dialer may select another IP
remoteDest := net.DestinationFromAddr(conn.RemoteAddr())
if rule := h.matchFinalRule(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
conn.Close()
return h.blackhole(ctx, input, output, rule, &remoteDest)
}
}
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 { if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
version := byte(h.config.ProxyProtocol) version := byte(h.config.ProxyProtocol)
srcAddr := inbound.Source.RawNetAddr() srcAddr := inbound.Source.RawNetAddr()
@@ -408,7 +403,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
writer = buf.NewWriter(conn) writer = buf.NewWriter(conn)
} }
} else { } else {
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination) writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination, outGateway)
if h.config.Noises != nil { if h.config.Noises != nil {
errors.LogDebug(ctx, "NOISE", h.config.Noises) errors.LogDebug(ctx, "NOISE", h.config.Noises)
writer = &NoisePacketWriter{ writer = &NoisePacketWriter{
@@ -512,7 +507,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
} }
udpAddr := d.(*net.UDPAddr) udpAddr := d.(*net.UDPAddr)
sourceAddr := net.IPAddress(udpAddr.IP) sourceAddr := net.IPAddress(udpAddr.IP)
if r.Handler.applyFinalRules(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule) == RuleAction_Block { if rule := r.Handler.matchFinalRule(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule); rule != nil && rule.action == RuleAction_Block {
continue continue
} }
b.Resize(0, int32(n)) b.Resize(0, int32(n))
@@ -537,7 +532,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
} }
// DialDest means the dial target used in the dialer when creating conn // DialDest means the dial target used in the dialer when creating conn
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Writer { func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination, outGateway net.Address) buf.Writer {
iConn := conn iConn := conn
statConn, ok := iConn.(*stat.CounterConnection) statConn, ok := iConn.(*stat.CounterConnection)
if ok { if ok {
@@ -561,9 +556,8 @@ func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverr
DefaultRule: defaultRule, DefaultRule: defaultRule,
UDPOverride: UDPOverride, UDPOverride: UDPOverride,
ResolvedUDPAddr: resolvedUDPAddr, ResolvedUDPAddr: resolvedUDPAddr,
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address, OutGateway: outGateway,
} }
} }
return &buf.SequentialWriter{Writer: conn} return &buf.SequentialWriter{Writer: conn}
} }
@@ -580,7 +574,7 @@ type PacketWriter struct {
// Resulting in these packets being sent to many different IPs randomly // Resulting in these packets being sent to many different IPs randomly
// So, cache and keep the resolve result // So, cache and keep the resolve result
ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address] ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address]
LocalAddr net.Address OutGateway net.Address
} }
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
@@ -603,21 +597,21 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok { if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
b.UDP.Address = ip b.UDP.Address = ip
} else { } else {
ShouldUseSystemResolver := true shouldUseSystemResolver := true
if w.Handler.config.DomainStrategy.HasStrategy() { if strategy := w.Handler.resolveStrategy; strategy.HasStrategy() {
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), w.Handler.config.DomainStrategy, w.LocalAddr) ips, err := internet.LookupForIP(b.UDP.Address.Domain(), strategy, w.OutGateway)
if err != nil { if err != nil {
// drop packet if resolve failed when forceIP // drop packet if resolve failed when forceIP
if w.Handler.config.DomainStrategy.ForceIP() { if strategy.ForceIP() {
b.Release() b.Release()
continue continue
} }
} else { } else {
ip = net.IPAddress(ips[dice.Roll(len(ips))]) ip = net.IPAddress(ips[dice.Roll(len(ips))])
ShouldUseSystemResolver = false shouldUseSystemResolver = false
} }
} }
if ShouldUseSystemResolver { if shouldUseSystemResolver {
udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr()) udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
if err != nil { if err != nil {
b.Release() b.Release()
@@ -631,7 +625,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
} }
} }
} }
if w.applyFinalRules(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule) == RuleAction_Block { if rule := w.matchFinalRule(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule); rule != nil && rule.action == RuleAction_Block {
b.Release() b.Release()
continue continue
} }
+5 -9
View File
@@ -115,11 +115,7 @@ Start:
request, err := http.ReadRequest(reader) request, err := http.ReadRequest(reader)
if err != nil { if err != nil {
trace := errors.New("failed to read http request").Base(err) return errors.New("failed to read http request").Base(err)
if errors.Cause(err) != io.EOF && !isTimeout(errors.Cause(err)) {
trace.AtWarning()
}
return trace
} }
if len(s.config.Accounts) > 0 { if len(s.config.Accounts) > 0 {
@@ -147,7 +143,7 @@ Start:
} }
dest, err := http_proto.ParseHost(host, defaultPort) dest, err := http_proto.ParseHost(host, defaultPort)
if err != nil { if err != nil {
return errors.New("malformed proxy host: ", host).AtWarning().Base(err) return errors.New("malformed proxy host: ", host).Base(err)
} }
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: conn.RemoteAddr(), From: conn.RemoteAddr(),
@@ -262,7 +258,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
requestWriter := buf.NewBufferedWriter(link.Writer) requestWriter := buf.NewBufferedWriter(link.Writer)
common.Must(requestWriter.SetBuffered(false)) common.Must(requestWriter.SetBuffered(false))
if err := request.Write(requestWriter); err != nil { if err := request.Write(requestWriter); err != nil {
return errors.New("failed to write whole request").Base(err).AtWarning() return errors.New("failed to write whole request").Base(err)
} }
return nil return nil
} }
@@ -299,7 +295,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
response.Header.Set("Proxy-Connection", "close") response.Header.Set("Proxy-Connection", "close")
} }
if err := response.Write(writer); err != nil { if err := response.Write(writer); err != nil {
return errors.New("failed to write response").Base(err).AtWarning() return errors.New("failed to write response").Base(err)
} }
return nil return nil
} }
@@ -332,7 +328,7 @@ func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer
return nil, errors.New("failed to read http 1xx response").Base(err) return nil, errors.New("failed to read http 1xx response").Base(err)
} }
ResponseHeader1xx = append(ResponseHeader1xx, data...) ResponseHeader1xx = append(ResponseHeader1xx, data...)
if bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) { if len(ResponseHeader1xx) >= 4 && bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
break break
} }
if len(ResponseHeader1xx) > 1024 { if len(ResponseHeader1xx) > 1024 {
+49
View File
@@ -0,0 +1,49 @@
package http
import (
"bufio"
"bytes"
"io"
"net/http"
"strings"
"testing"
)
// A malformed upstream response containing a bare '\n' before the real
// status line used to crash readResponseAndHandle100Continue: the first
// ReadSlice('\n') returns fewer than 4 bytes, and slicing
// ResponseHeader1xx[len(ResponseHeader1xx)-4:] panicked with a negative
// index instead of returning an error.
func TestReadResponseAndHandle100ContinueDoesNotPanicOnEarlyNewline(t *testing.T) {
payload := "X\nHTTP/1.1 100 Continue\r\n\r\n" + strings.Repeat("A", 40)
r := bufio.NewReader(bytes.NewReader([]byte(payload)))
req, err := http.NewRequest("GET", "http://example.com/", nil)
if err != nil {
t.Fatal(err)
}
// Must not panic; a parse error for the garbage trailing bytes is fine.
_, _ = readResponseAndHandle100Continue(r, req, io.Discard)
}
func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testing.T) {
payload := "HTTP/1.1 100 Continue\r\n\r\n" +
"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello"
r := bufio.NewReader(bytes.NewReader([]byte(payload)))
req, err := http.NewRequest("GET", "http://example.com/", nil)
if err != nil {
t.Fatal(err)
}
var forwarded bytes.Buffer
resp, err := readResponseAndHandle100Continue(r, req, &forwarded)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != 200 {
t.Fatalf("expected status 200, got %d", resp.StatusCode)
}
if !strings.Contains(forwarded.String(), "100 Continue") {
t.Fatalf("expected 1xx response to be forwarded, got %q", forwarded.String())
}
}
+4 -4
View File
@@ -62,7 +62,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
conn, err := dialer.Dial(hysteria.ContextWithDatagram(ctx, target.Network == net.Network_UDP), c.server.Destination) conn, err := dialer.Dial(hysteria.ContextWithDatagram(ctx, target.Network == net.Network_UDP), c.server.Destination)
if err != nil { if err != nil {
return errors.New("failed to find an available destination").AtWarning().Base(err) return errors.New("failed to find an available destination").Base(err)
} }
defer conn.Close() defer conn.Close()
errors.LogInfo(ctx, "tunneling request to ", target, " via ", target.Network, ":", c.server.Destination.NetAddr()) errors.LogInfo(ctx, "tunneling request to ", target, " via ", target.Network, ":", c.server.Destination.NetAddr())
@@ -236,14 +236,14 @@ type UDPReader struct {
func (r *UDPReader) ReadFrom(p []byte) (n int, addr *net.Destination, err error) { func (r *UDPReader) ReadFrom(p []byte) (n int, addr *net.Destination, err error) {
for { for {
var buf [hysteria.MaxDatagramFrameSize]byte var packet [1500]byte
n, err := r.reader.Read(buf[:]) n, err := r.reader.Read(packet[:])
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
msg, err := ParseUDPMessage(buf[:n]) msg, err := ParseUDPMessage(packet[:n])
if err != nil { if err != nil {
continue continue
} }
+2 -2
View File
@@ -40,11 +40,11 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
for _, user := range config.Users { for _, user := range config.Users {
u, err := user.ToMemoryUser() u, err := user.ToMemoryUser()
if err != nil { if err != nil {
return nil, errors.New("failed to get hysteria user").Base(err).AtError() return nil, errors.New("failed to get hysteria user").Base(err)
} }
if err := validator.Add(u); err != nil { if err := validator.Add(u); err != nil {
return nil, errors.New("failed to add user").Base(err).AtError() return nil, errors.New("failed to add user").Base(err)
} }
} }
+1 -1
View File
@@ -56,7 +56,7 @@ func (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) e
if config.Sniffing.GetEnabled() { if config.Sniffing.GetEnabled() {
request, err := proxyman.BuildSniffingRequest(config.Sniffing) request, err := proxyman.BuildSniffingRequest(config.Sniffing)
if err != nil { if err != nil {
return errors.New("failed to build loopback sniffing request").Base(err).AtError() return errors.New("failed to build loopback sniffing request").Base(err)
} }
l.sniffingRequest = request l.sniffingRequest = request
} }
+328
View File
@@ -0,0 +1,328 @@
package masque
import (
"context"
go_errors "errors"
"io"
"net/netip"
"slices"
"sync"
"sync/atomic"
"time"
"golang.zx2c4.com/wireguard/tun"
"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/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/proxy/wireguard"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/masque"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
)
const (
establishTimeout = 10 * time.Second
retryInterval = time.Second
)
type Client struct {
server *protocol.ServerSpec
policyManager policy.Manager
remoteDNS []netip.Addr
ctx context.Context
cancel context.CancelFunc
tunnel atomic.Pointer[tunnel]
mu sync.Mutex
lastErr error
lastErrAt time.Time
}
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
v := core.MustFromContext(ctx)
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
if _, ok := streamSettings.ProtocolSettings.(*masque.Config); !ok {
return nil, errors.New("not masque transport")
}
if tls.ConfigFromStreamSettings(streamSettings) == nil {
return nil, errors.New(`MASQUE requires "security": "tls"`)
}
if config.Server == nil {
return nil, errors.New(`no target server found`)
}
server, err := protocol.NewServerSpecFromPB(config.Server)
if err != nil {
return nil, errors.New("failed to get server spec").Base(err)
}
dns := config.RemoteDns
if len(dns) == 0 {
dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"}
}
remoteDNS := make([]netip.Addr, 0, len(dns))
for _, s := range dns {
addr, err := netip.ParseAddr(s)
if err != nil {
return nil, errors.New("invalid remote DNS server ", s).Base(err)
}
remoteDNS = append(remoteDNS, addr)
}
c := &Client{
server: server,
policyManager: p,
remoteDNS: remoteDNS,
}
c.ctx, c.cancel = context.WithCancel(context.Background())
return c, nil
}
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
if !ob.Target.IsValid() {
return errors.New("target not specified")
}
ob.Name = "masque"
ob.CanSpliceCopy = 3
t, err := c.getTunnel(ctx, dialer)
if err != nil {
return errors.New("failed to establish CONNECT-IP tunnel").Base(err)
}
var newCtx context.Context
var newCancel context.CancelFunc
if session.TimeoutOnlyFromContext(ctx) {
newCtx, newCancel = context.WithCancel(context.Background())
}
sessionPolicy := c.policyManager.ForLevel(0)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, func() {
cancel()
if newCancel != nil {
newCancel()
}
}, sessionPolicy.Timeouts.ConnectionIdle)
if newCtx != nil {
ctx = newCtx
}
var reader buf.Reader
var writer buf.Writer
switch ob.Target.Network {
case net.Network_TCP:
var conn net.Conn
var err error
if sessionPolicy.Timeouts.Handshake != 0 {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
conn, err = t.tnet.DialContext(timeoutCtx, "tcp", ob.Target.NetAddr())
timeoutCancel()
} else {
conn, err = t.tnet.Dial("tcp", ob.Target.NetAddr())
}
if err != nil {
return errors.New("failed to create TCP connection").Base(err)
}
defer conn.Close()
reader = buf.NewReader(conn)
writer = buf.NewWriter(conn)
case net.Network_UDP:
conn, err := t.tnet.Dial("udp", ob.Target.NetAddr())
if err != nil {
return errors.New("failed to create UDP connection").Base(err)
}
defer conn.Close()
uc := &wireguard.UDPConnClient{
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
Dest: conn.RemoteAddr().(*net.UDPAddr),
}
reader = uc
writer = uc
default:
panic(ob.Target.Network)
}
requestFunc := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))
}
responseFunc := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))
}
responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))
if err := task.Run(ctx, requestFunc, responseDonePost); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
return errors.New("connection ends").Base(err)
}
return nil
}
func (c *Client) getTunnel(ctx context.Context, dialer internet.Dialer) (*tunnel, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.ctx.Err() != nil {
return nil, errors.New("closed")
}
if t := c.tunnel.Load(); t != nil {
select {
case <-t.done:
default:
return t, nil
}
}
if err := ctx.Err(); err != nil {
return nil, err
}
if c.lastErr != nil && time.Since(c.lastErrAt) < retryInterval {
return nil, c.lastErr
}
t, err := c.establish(ctx, dialer)
if err != nil {
c.lastErr, c.lastErrAt = err, time.Now()
return nil, err
}
c.lastErr = nil
c.tunnel.Store(t)
if c.ctx.Err() != nil {
if c.tunnel.CompareAndSwap(t, nil) {
t.close()
}
return nil, errors.New("closed")
}
return t, nil
}
func (c *Client) establish(ctx context.Context, dialer internet.Dialer) (*tunnel, error) {
ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), establishTimeout)
defer cancel()
defer context.AfterFunc(c.ctx, cancel)()
conn, err := dialer.Dial(ctx, c.server.Destination)
if err != nil {
return nil, err
}
mconn, ok := stat.TryUnwrapStatsConn(conn).(*masque.Conn)
if !ok {
conn.Close()
return nil, errors.New("not a CONNECT-IP connection")
}
t, err := newTunnel(conn, mconn.LocalAddrs(), c.remoteDNS)
if err != nil {
conn.Close()
return nil, err
}
errors.LogInfo(ctx, "MASQUE: tunnel established from ", mconn.LocalAddrs())
return t, nil
}
func (c *Client) Close() error {
c.cancel()
if t := c.tunnel.Swap(nil); t != nil {
t.close()
}
return nil
}
type tunnel struct {
conn stat.Connection
dev tun.Device
tnet *wireguard.Net
done chan struct{}
closeOnce sync.Once
}
func newTunnel(conn stat.Connection, local []netip.Addr, remoteDNS []netip.Addr) (*tunnel, error) {
var dns []netip.Addr
for _, addr := range remoteDNS {
if slices.ContainsFunc(local, func(l netip.Addr) bool { return l.Is4() == addr.Is4() }) {
dns = append(dns, addr)
}
}
if len(dns) == 0 {
errors.LogWarning(context.Background(), "MASQUE: no remote DNS server is reachable from the assigned addresses ", local, ", domain names will fail to resolve")
dns = remoteDNS
}
dev, tnet, _, err := wireguard.CreateNetTUN(local, dns, masque.MinPacketSize, true)
if err != nil {
return nil, err
}
t := &tunnel{
conn: conn,
dev: dev,
tnet: tnet,
done: make(chan struct{}),
}
go t.readFromTunnel()
go t.writeToTunnel()
return t, nil
}
func (t *tunnel) readFromTunnel() {
defer t.close()
b := make([]byte, buf.Size)
for {
n, err := t.conn.Read(b)
if err != nil {
if go_errors.Is(err, io.ErrShortBuffer) {
continue
}
errors.LogInfoInner(context.Background(), err, "MASQUE: tunnel closed")
return
}
t.dev.Write([][]byte{b[:n]}, 0)
}
}
func (t *tunnel) writeToTunnel() {
bufs := [][]byte{make([]byte, masque.MinPacketSize)}
sizes := []int{0}
for {
if _, err := t.dev.Read(bufs, sizes, 0); err != nil {
return
}
if _, err := t.conn.Write(bufs[0][:sizes[0]]); err != nil {
var ptb *masque.PacketTooBigError
if go_errors.As(err, &ptb) {
go t.dev.Write([][]byte{ptb.ICMP}, 0)
}
}
}
}
func (t *tunnel) close() {
t.closeOnce.Do(func() {
close(t.done)
t.conn.Close()
t.dev.Close()
})
}
func init() {
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewClient(ctx, config.(*ClientConfig))
}))
}
+136
View File
@@ -0,0 +1,136 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: proxy/masque/config.proto
package masque
import (
protocol "github.com/xtls/xray-core/common/protocol"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ClientConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
RemoteDns []string `protobuf:"bytes,2,rep,name=remote_dns,json=remoteDns,proto3" json:"remote_dns,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ClientConfig) Reset() {
*x = ClientConfig{}
mi := &file_proxy_masque_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ClientConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClientConfig) ProtoMessage() {}
func (x *ClientConfig) ProtoReflect() protoreflect.Message {
mi := &file_proxy_masque_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.
func (*ClientConfig) Descriptor() ([]byte, []int) {
return file_proxy_masque_config_proto_rawDescGZIP(), []int{0}
}
func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
if x != nil {
return x.Server
}
return nil
}
func (x *ClientConfig) GetRemoteDns() []string {
if x != nil {
return x.RemoteDns
}
return nil
}
var File_proxy_masque_config_proto protoreflect.FileDescriptor
const file_proxy_masque_config_proto_rawDesc = "" +
"\n" +
"\x19proxy/masque/config.proto\x12\x11xray.proxy.masque\x1a!common/protocol/server_spec.proto\"k\n" +
"\fClientConfig\x12<\n" +
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\x12\x1d\n" +
"\n" +
"remote_dns\x18\x02 \x03(\tR\tremoteDnsBU\n" +
"\x15com.xray.proxy.masqueP\x01Z&github.com/xtls/xray-core/proxy/masque\xaa\x02\x11Xray.Proxy.Masqueb\x06proto3"
var (
file_proxy_masque_config_proto_rawDescOnce sync.Once
file_proxy_masque_config_proto_rawDescData []byte
)
func file_proxy_masque_config_proto_rawDescGZIP() []byte {
file_proxy_masque_config_proto_rawDescOnce.Do(func() {
file_proxy_masque_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_masque_config_proto_rawDesc), len(file_proxy_masque_config_proto_rawDesc)))
})
return file_proxy_masque_config_proto_rawDescData
}
var file_proxy_masque_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_masque_config_proto_goTypes = []any{
(*ClientConfig)(nil), // 0: xray.proxy.masque.ClientConfig
(*protocol.ServerEndpoint)(nil), // 1: xray.common.protocol.ServerEndpoint
}
var file_proxy_masque_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.masque.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proxy_masque_config_proto_init() }
func file_proxy_masque_config_proto_init() {
if File_proxy_masque_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_masque_config_proto_rawDesc), len(file_proxy_masque_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_masque_config_proto_goTypes,
DependencyIndexes: file_proxy_masque_config_proto_depIdxs,
MessageInfos: file_proxy_masque_config_proto_msgTypes,
}.Build()
File_proxy_masque_config_proto = out.File
file_proxy_masque_config_proto_goTypes = nil
file_proxy_masque_config_proto_depIdxs = nil
}
+14
View File
@@ -0,0 +1,14 @@
syntax = "proto3";
package xray.proxy.masque;
option csharp_namespace = "Xray.Proxy.Masque";
option go_package = "github.com/xtls/xray-core/proxy/masque";
option java_package = "com.xray.proxy.masque";
option java_multiple_files = true;
import "common/protocol/server_spec.proto";
message ClientConfig {
xray.common.protocol.ServerEndpoint server = 1;
repeated string remote_dns = 2;
}
+2 -2
View File
@@ -71,7 +71,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
return nil return nil
}) })
if err != nil { if err != nil {
return errors.New("failed to find an available destination").AtWarning().Base(err) return errors.New("failed to find an available destination").Base(err)
} }
errors.LogInfo(ctx, "tunneling request to ", destination, " via ", network, ":", server.Destination.NetAddr()) errors.LogInfo(ctx, "tunneling request to ", destination, " via ", network, ":", server.Destination.NetAddr())
@@ -124,7 +124,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
} }
if err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { if err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
return errors.New("failed to write A request payload").Base(err).AtWarning() return errors.New("failed to write A request payload").Base(err)
} }
if err := bufferedWriter.SetBuffered(false); err != nil { if err := bufferedWriter.SetBuffered(false); err != nil {
+2 -2
View File
@@ -98,7 +98,7 @@ func ReadTCPSession(validator *Validator, reader io.Reader) (*protocol.RequestHe
iv := append([]byte(nil), buffer.BytesTo(ivLen)...) iv := append([]byte(nil), buffer.BytesTo(ivLen)...)
r, err = account.Cipher.NewDecryptionReader(account.Key, iv, reader) r, err = account.Cipher.NewDecryptionReader(account.Key, iv, reader)
if err != nil { if err != nil {
return nil, nil, drain.WithError(drainer, reader, errors.New("failed to initialize decoding stream").Base(err).AtError()) return nil, nil, drain.WithError(drainer, reader, errors.New("failed to initialize decoding stream").Base(err))
} }
} }
} }
@@ -146,7 +146,7 @@ func WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Wri
w, err := account.Cipher.NewEncryptionWriter(account.Key, iv, writer) w, err := account.Cipher.NewEncryptionWriter(account.Key, iv, writer)
if err != nil { if err != nil {
return nil, errors.New("failed to create encoding stream").Base(err).AtError() return nil, errors.New("failed to create encoding stream").Base(err)
} }
header := buf.New() header := buf.New()
+3 -3
View File
@@ -34,11 +34,11 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
for _, user := range config.Users { for _, user := range config.Users {
u, err := user.ToMemoryUser() u, err := user.ToMemoryUser()
if err != nil { if err != nil {
return nil, errors.New("failed to get shadowsocks user").Base(err).AtError() return nil, errors.New("failed to get shadowsocks user").Base(err)
} }
if err := validator.Add(u); err != nil { if err := validator.Add(u); err != nil {
return nil, errors.New("failed to add user").Base(err).AtError() return nil, errors.New("failed to add user").Base(err)
} }
} }
@@ -200,7 +200,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
func (s *Server) handleConnection(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error { func (s *Server) handleConnection(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {
sessionPolicy := s.policyManager.ForLevel(0) sessionPolicy := s.policyManager.ForLevel(0)
if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return errors.New("unable to set read deadline").Base(err).AtWarning() return errors.New("unable to set read deadline").Base(err)
} }
bufferedReader := buf.BufferedReader{Reader: buf.NewReader(conn)} bufferedReader := buf.BufferedReader{Reader: buf.NewReader(conn)}
+1 -1
View File
@@ -59,7 +59,7 @@ func NewMultiServer(ctx context.Context, config *MultiUserServerConfig) (*MultiU
} }
u, err := user.ToMemoryUser() u, err := user.ToMemoryUser()
if err != nil { if err != nil {
return nil, errors.New("failed to get shadowsocks user").Base(err).AtError() return nil, errors.New("failed to get shadowsocks user").Base(err)
} }
memUsers = append(memUsers, u) memUsers = append(memUsers, u)
} }
+1
View File
@@ -82,6 +82,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
if err != nil { if err != nil {
return errors.New("failed to connect to server").Base(err) return errors.New("failed to connect to server").Base(err)
} }
defer connection.Close()
if session.TimeoutOnlyFromContext(ctx) { if session.TimeoutOnlyFromContext(ctx) {
ctx, _ = context.WithCancel(context.Background()) ctx, _ = context.WithCancel(context.Background())
+1 -1
View File
@@ -105,7 +105,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
} }
udpRequest, err := ClientHandshake(request, conn, conn) udpRequest, err := ClientHandshake(request, conn, conn)
if err != nil { if err != nil {
return errors.New("failed to establish connection to server").AtWarning().Base(err) return errors.New("failed to establish connection to server").Base(err)
} }
if udpRequest != nil { if udpRequest != nil {
if udpRequest.Address == net.AnyIP || udpRequest.Address == net.AnyIPv6 { if udpRequest.Address == net.AnyIP || udpRequest.Address == net.AnyIPv6 {
+2 -2
View File
@@ -458,10 +458,10 @@ func ClientHandshake(request *protocol.RequestHeader, reader io.Reader, writer i
} }
if b.Byte(0) != socks5Version { if b.Byte(0) != socks5Version {
return nil, errors.New("unexpected server version: ", b.Byte(0)).AtWarning() return nil, errors.New("unexpected server version: ", b.Byte(0))
} }
if b.Byte(1) != authByte { if b.Byte(1) != authByte {
return nil, errors.New("auth method not supported.").AtWarning() return nil, errors.New("auth method not supported.")
} }
if authByte == authPassword { if authByte == authPassword {
+5 -5
View File
@@ -69,7 +69,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
return nil return nil
}) })
if err != nil { if err != nil {
return errors.New("failed to find an available destination").AtWarning().Base(err) return errors.New("failed to find an available destination").Base(err)
} }
errors.LogInfo(ctx, "tunneling request to ", destination, " via ", server.Destination.NetAddr()) errors.LogInfo(ctx, "tunneling request to ", destination, " via ", server.Destination.NetAddr())
@@ -116,21 +116,21 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
// write some request payload to buffer // write some request payload to buffer
if err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { if err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
return errors.New("failed to write A request payload").Base(err).AtWarning() return errors.New("failed to write A request payload").Base(err)
} }
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer // Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
if err = bufferWriter.SetBuffered(false); err != nil { if err = bufferWriter.SetBuffered(false); err != nil {
return errors.New("failed to flush payload").Base(err).AtWarning() return errors.New("failed to flush payload").Base(err)
} }
// Send header if not sent yet // Send header if not sent yet
if _, err = connWriter.Write([]byte{}); err != nil { if _, err = connWriter.Write([]byte{}); err != nil {
return err.(*errors.Error).AtWarning() return err
} }
if err = buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil { if err = buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transfer request payload").Base(err).AtInfo() return errors.New("failed to transfer request payload").Base(err)
} }
return nil return nil
+12 -12
View File
@@ -47,11 +47,11 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
for _, user := range config.Users { for _, user := range config.Users {
u, err := user.ToMemoryUser() u, err := user.ToMemoryUser()
if err != nil { if err != nil {
return nil, errors.New("failed to get trojan user").Base(err).AtError() return nil, errors.New("failed to get trojan user").Base(err)
} }
if err := validator.Add(u); err != nil { if err := validator.Add(u); err != nil {
return nil, errors.New("failed to add user").Base(err).AtError() return nil, errors.New("failed to add user").Base(err)
} }
} }
@@ -151,7 +151,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
sessionPolicy := s.policyManager.ForLevel(0) sessionPolicy := s.policyManager.ForLevel(0)
if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return errors.New("unable to set read deadline").Base(err).AtWarning() return errors.New("unable to set read deadline").Base(err)
} }
first := buf.FromBytes(make([]byte, buf.Size)) first := buf.FromBytes(make([]byte, buf.Size))
@@ -219,7 +219,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
destination := clientReader.Target destination := clientReader.Target
if err := conn.SetReadDeadline(time.Time{}); err != nil { if err := conn.SetReadDeadline(time.Time{}); err != nil {
return errors.New("unable to set read deadline").Base(err).AtWarning() return errors.New("unable to set read deadline").Base(err)
} }
inbound := session.InboundFromContext(ctx) inbound := session.InboundFromContext(ctx)
@@ -402,7 +402,7 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S
} }
apfb := napfb[name] apfb := napfb[name]
if apfb == nil { if apfb == nil {
return errors.New(`failed to find the default "name" config`).AtWarning() return errors.New(`failed to find the default "name" config`)
} }
if apfb[alpn] == nil { if apfb[alpn] == nil {
@@ -410,7 +410,7 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S
} }
pfb := apfb[alpn] pfb := apfb[alpn]
if pfb == nil { if pfb == nil {
return errors.New(`failed to find the default "alpn" config`).AtWarning() return errors.New(`failed to find the default "alpn" config`)
} }
path := "" path := ""
@@ -444,7 +444,7 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S
} }
fb := pfb[path] fb := pfb[path]
if fb == nil { if fb == nil {
return errors.New(`failed to find the default "path" config`).AtWarning() return errors.New(`failed to find the default "path" config`)
} }
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
@@ -460,7 +460,7 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S
} }
return nil return nil
}); err != nil { }); err != nil {
return errors.New("failed to dial to " + fb.Dest).Base(err).AtWarning() return errors.New("failed to dial to " + fb.Dest).Base(err)
} }
defer conn.Close() defer conn.Close()
@@ -520,11 +520,11 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S
common.Must2(pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)})) common.Must2(pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)}))
} }
if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil { if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil {
return errors.New("failed to set PROXY protocol v", fb.Xver).Base(err).AtWarning() return errors.New("failed to set PROXY protocol v", fb.Xver).Base(err)
} }
} }
if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil { if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to fallback request payload").Base(err).AtInfo() return errors.New("failed to fallback request payload").Base(err)
} }
return nil return nil
} }
@@ -534,7 +534,7 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S
getResponse := func() error { getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil { if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to deliver response payload").Base(err).AtInfo() return errors.New("failed to deliver response payload").Base(err)
} }
return nil return nil
} }
@@ -542,7 +542,7 @@ func (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.S
if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil { if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil {
common.Must(common.Interrupt(serverReader)) common.Must(common.Interrupt(serverReader))
common.Must(common.Interrupt(serverWriter)) common.Must(common.Interrupt(serverWriter))
return errors.New("fallback ends").Base(err).AtInfo() return errors.New("fallback ends").Base(err)
} }
return nil return nil
+33 -4
View File
@@ -37,6 +37,25 @@ type Handler struct {
downlinkCounter stats.Counter downlinkCounter stats.Counter
} }
type tunUDPStatsWriter struct {
writer buf.Writer
counter stats.Counter
}
func (w *tunUDPStatsWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
for len(mb) > 0 {
remaining, packet := buf.SplitFirst(mb)
packetSize := packet.Len()
if err := w.writer.WriteMultiBuffer(buf.MultiBuffer{packet}); err != nil {
buf.ReleaseMulti(remaining)
return err
}
w.counter.Add(int64(packetSize))
mb = remaining
}
return nil
}
// ConnectionHandler interface with the only method that stack is going to push new connections to // ConnectionHandler interface with the only method that stack is going to push new connections to
type ConnectionHandler interface { type ConnectionHandler interface {
HandleConnection(conn net.Conn, destination net.Destination) HandleConnection(conn net.Conn, destination net.Destination)
@@ -104,7 +123,7 @@ func (t *Handler) Start() error {
iface := updater.Get() iface := updater.Get()
if iface == nil { if iface == nil {
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil") errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
return nil return errors.New("iface not found")
} }
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
addrPort, _ := netip.ParseAddrPort(address) addrPort, _ := netip.ParseAddrPort(address)
@@ -171,7 +190,8 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
return return
} }
source := net.DestinationFromAddr(remote) source := net.DestinationFromAddr(remote)
if t.uplinkCounter != nil || t.downlinkCounter != nil { isUDP := destination.Network == net.Network_UDP
if !isUDP && (t.uplinkCounter != nil || t.downlinkCounter != nil) {
conn = &stat.CounterConnection{ conn = &stat.CounterConnection{
Connection: conn, Connection: conn,
ReadCounter: t.uplinkCounter, ReadCounter: t.uplinkCounter,
@@ -203,9 +223,18 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
}) })
errors.LogInfo(ctx, "processing from ", source, " to ", destination) errors.LogInfo(ctx, "processing from ", source, " to ", destination)
reader := &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)}
writer := buf.NewWriter(conn)
if isUDP {
reader.Counter = t.uplinkCounter
if t.downlinkCounter != nil {
writer = &tunUDPStatsWriter{writer: writer, counter: t.downlinkCounter}
}
}
link := &transport.Link{ link := &transport.Link{
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)}, Reader: reader,
Writer: buf.NewWriter(conn), Writer: writer,
} }
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil { if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
errors.LogError(ctx, errors.New("connection closed").Base(err)) errors.LogError(ctx, errors.New("connection closed").Base(err))
+145 -137
View File
@@ -3,14 +3,14 @@
package tun package tun
import ( import (
"context"
"crypto/md5" "crypto/md5"
"encoding/binary" "encoding/binary"
go_errors "errors" go_errors "errors"
"net" "net"
"net/netip" "net/netip"
"sort"
"strings"
"sync" "sync"
"time"
"unsafe" "unsafe"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -31,13 +31,14 @@ func procyield(cycles uint32)
type WindowsTun struct { type WindowsTun struct {
sync.RWMutex sync.RWMutex
options *Config options *Config
adapter *wintun.Adapter adapter *wintun.Adapter
session wintun.Session session wintun.Session
readWait windows.Handle readWait windows.Handle
luid winipcfg.LUID luid winipcfg.LUID
changeCallback winipcfg.ChangeCallback cbr winipcfg.ChangeCallback
closed bool cbi winipcfg.ChangeCallback
closed bool
} }
// WindowsTun implements Tun // WindowsTun implements Tun
@@ -85,23 +86,37 @@ func open(name, desc string) (*wintun.Adapter, error) {
return nil, err return nil, err
} }
func (t *WindowsTun) Start() error { func (t *WindowsTun) Start() (err error) {
var has4, has6 bool var address4, address6 bool
allowedIPs := make([]netip.Prefix, 0, len(t.options.AutoSystemRoutingTable)) addresses := make([]netip.Prefix, 0, len(t.options.Gateway))
for _, route := range t.options.AutoSystemRoutingTable { for _, cidr := range t.options.Gateway {
allowedIPs = append(allowedIPs, netip.MustParsePrefix(route)) prefix := netip.MustParsePrefix(cidr)
if prefix.Addr().Is4() {
address4 = true
} else {
address6 = true
}
addresses = append(addresses, prefix)
} }
dns := make([]netip.Addr, 0, len(t.options.DNS))
for _, ip := range t.options.DNS {
dns = append(dns, netip.MustParseAddr(ip))
}
var route4, route6 bool
routesMap := make(map[winipcfg.RouteData]struct{}) routesMap := make(map[winipcfg.RouteData]struct{})
for _, ip := range allowedIPs { for _, cidr := range t.options.AutoSystemRoutingTable {
prefix := netip.MustParsePrefix(cidr)
route := winipcfg.RouteData{ route := winipcfg.RouteData{
Destination: ip.Masked(), Destination: prefix.Masked(),
Metric: 0, Metric: 0,
} }
if ip.Addr().Is4() { if prefix.Addr().Is4() {
has4 = true route4 = true
route.NextHop = netip.IPv4Unspecified() route.NextHop = netip.IPv4Unspecified()
} else { } else {
has6 = true route6 = true
route.NextHop = netip.IPv6Unspecified() route.NextHop = netip.IPv6Unspecified()
} }
routesMap[route] = struct{}{} routesMap[route] = struct{}{}
@@ -111,24 +126,40 @@ func (t *WindowsTun) Start() error {
r := route r := route
routesData = append(routesData, &r) routesData = append(routesData, &r)
} }
err := t.luid.SetRoutes(routesData)
if err != nil {
return errors.New("unable to set routes").Base(err)
}
if len(t.options.Gateway) > 0 { var retryTimes int
addresses := make([]netip.Prefix, 0, len(t.options.Gateway)) var firstErr error
for _, address := range t.options.Gateway { startOver:
addresses = append(addresses, netip.MustParsePrefix(address)) if retryTimes > 0 {
} if retryTimes > 15 {
err := t.luid.SetIPAddresses(addresses) return windows.ERROR_NOT_FOUND
if err != nil {
return errors.New("unable to set ips").Base(err)
} }
errors.LogErrorInner(context.Background(), firstErr, "Interface configuration failed, retrying attempt ", retryTimes, "/15")
time.Sleep(time.Second)
} }
retryTimes++
if has4 { for _, family := range []winipcfg.AddressFamily{windows.AF_INET, windows.AF_INET6} {
ipif, err := t.luid.IPInterface(windows.AF_INET) if family == windows.AF_INET && route4 || family == windows.AF_INET6 && route6 {
err = t.luid.SetRoutesForFamily(family, routesData)
if err != nil {
firstErr = errors.New("unable to set routes").Base(err)
if err == windows.ERROR_NOT_FOUND {
goto startOver
}
return firstErr
}
}
if family == windows.AF_INET && address4 || family == windows.AF_INET6 && address6 {
err = t.luid.SetIPAddressesForFamily(family, addresses)
if err != nil {
firstErr = errors.New("unable to set ips").Base(err)
if err == windows.ERROR_NOT_FOUND {
goto startOver
}
return firstErr
}
}
ipif, err := t.luid.IPInterface(family)
if err != nil { if err != nil {
return err return err
} }
@@ -136,56 +167,45 @@ func (t *WindowsTun) Start() error {
ipif.DadTransmits = 0 ipif.DadTransmits = 0
ipif.ManagedAddressConfigurationSupported = false ipif.ManagedAddressConfigurationSupported = false
ipif.OtherStatefulConfigurationSupported = false ipif.OtherStatefulConfigurationSupported = false
ipif.NLMTU = t.options.MTU if family == windows.AF_INET && (address4 || route4) || family == windows.AF_INET6 && (address6 || route6) {
ipif.UseAutomaticMetric = false ipif.NLMTU = t.options.MTU
ipif.Metric = 0 }
if family == windows.AF_INET && route4 || family == windows.AF_INET6 && route6 {
ipif.UseAutomaticMetric = false
ipif.Metric = 0
}
err = ipif.Set() err = ipif.Set()
if err != nil { if err != nil {
return err firstErr = errors.New("unable to set metric and MTU").Base(err)
if err == windows.ERROR_NOT_FOUND {
goto startOver
}
return firstErr
} }
} err = t.luid.SetDNS(family, dns, nil)
if has6 {
ipif, err := t.luid.IPInterface(windows.AF_INET6)
if err != nil { if err != nil {
return err firstErr = errors.New("unable to set DNS").Base(err)
} if err == windows.ERROR_NOT_FOUND {
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled goto startOver
ipif.DadTransmits = 0 }
ipif.ManagedAddressConfigurationSupported = false return firstErr
ipif.OtherStatefulConfigurationSupported = false
ipif.NLMTU = t.options.MTU
ipif.UseAutomaticMetric = false
ipif.Metric = 0
err = ipif.Set()
if err != nil {
return err
}
}
if len(t.options.DNS) > 0 {
dns := make([]netip.Addr, 0, len(t.options.DNS))
for _, ip := range t.options.DNS {
dns = append(dns, netip.MustParseAddr(ip))
}
err := t.luid.SetDNS(windows.AF_INET, dns, nil)
if err != nil {
return err
}
err = t.luid.SetDNS(windows.AF_INET6, dns, nil)
if err != nil {
return err
} }
} }
if updater != nil { if updater != nil {
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { t.cbr, err = winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) {
updater.Update()
})
if err != nil {
return err
}
t.cbi, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
updater.Update() updater.Update()
}) })
if err != nil { if err != nil {
return err return err
} }
} }
return nil return nil
} }
@@ -197,12 +217,26 @@ func (t *WindowsTun) Close() error {
} }
t.closed = true t.closed = true
if t.changeCallback != nil { if t.cbr != nil {
t.changeCallback.Unregister() t.cbr.Unregister()
}
if t.cbi != nil {
t.cbi.Unregister()
}
if t.luid != 0 {
t.luid.FlushRoutes(windows.AF_INET)
t.luid.FlushIPAddresses(windows.AF_INET)
t.luid.FlushDNS(windows.AF_INET)
t.luid.FlushRoutes(windows.AF_INET6)
t.luid.FlushIPAddresses(windows.AF_INET6)
t.luid.FlushDNS(windows.AF_INET6)
}
if t.session != (wintun.Session{}) {
t.session.End()
}
if t.adapter != nil {
t.adapter.Close()
} }
t.session.End()
_ = t.adapter.Close()
return nil return nil
} }
@@ -311,75 +345,49 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
} }
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) { func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
interfaces, err := net.Interfaces() if fixedName != "" {
return net.InterfaceByName(fixedName)
}
r, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
if err != nil { if err != nil {
return nil, err return nil, err
} }
lowestMetric := ^uint32(0)
index := uint32(0)
lowestMetricWifi := ^uint32(0)
indexWifi := uint32(0)
for i := range r {
if r[i].DestinationPrefix.PrefixLength != 0 || r[i].InterfaceIndex == uint32(tunIndex) {
continue
}
ifrow, err := r[i].InterfaceLUID.Interface()
if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp {
continue
}
if fixedName != "" { iface, err := r[i].InterfaceLUID.IPInterface(windows.AF_INET)
for _, iface := range interfaces { if err != nil {
if iface.Index != tunIndex && iface.Name == fixedName { iface, err = r[i].InterfaceLUID.IPInterface(windows.AF_INET6)
return &iface, nil if err != nil {
continue
} }
} }
return nil, nil
}
var candidates []struct { if ifrow.Type == windows.IF_TYPE_IEEE80211 {
index int if r[i].Metric+iface.Metric < lowestMetricWifi {
score int lowestMetricWifi = r[i].Metric + iface.Metric
indexWifi = r[i].InterfaceIndex
}
continue
}
if r[i].Metric+iface.Metric < lowestMetric {
lowestMetric = r[i].Metric + iface.Metric
index = r[i].InterfaceIndex
}
} }
for i, iface := range interfaces { if indexWifi != 0 {
if iface.Index == tunIndex { index = indexWifi
continue
}
if strings.Contains(iface.Name, "vEthernet") {
continue
}
if iface.Flags&net.FlagUp == 0 {
continue
}
if iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil || len(addrs) == 0 {
continue
}
candidates = append(candidates, struct {
index int
score int
}{i, scoreWindowsInterface(&iface, addrs)})
} }
return net.InterfaceByIndex(int(index))
sort.Slice(candidates, func(i, j int) bool {
if candidates[i].score != candidates[j].score {
return candidates[i].score > candidates[j].score
}
return interfaces[candidates[i].index].Name < interfaces[candidates[j].index].Name
})
if len(candidates) == 0 {
return nil, nil
}
iface := interfaces[candidates[0].index]
return &iface, nil
}
func scoreWindowsInterface(iface *net.Interface, addrs []net.Addr) int {
score := 0
name := strings.ToLower(iface.Name)
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
score += 2
}
for _, addr := range addrs {
if strings.HasPrefix(addr.String(), "192.168.") {
score++
break
}
}
return score
} }
+1 -1
View File
@@ -12,7 +12,7 @@ import (
func (a *Account) AsAccount() (protocol.Account, error) { func (a *Account) AsAccount() (protocol.Account, error) {
id, err := uuid.ParseString(a.Id) id, err := uuid.ParseString(a.Id)
if err != nil { if err != nil {
return nil, errors.New("failed to parse ID").Base(err).AtError() return nil, errors.New("failed to parse ID").Base(err)
} }
return &MemoryAccount{ return &MemoryAccount{
ID: protocol.NewID(id), ID: protocol.NewID(id),
+25 -25
View File
@@ -61,10 +61,10 @@ func init() {
for _, user := range c.Users { for _, user := range c.Users {
u, err := user.ToMemoryUser() u, err := user.ToMemoryUser()
if err != nil { if err != nil {
return nil, errors.New("failed to get VLESS user").Base(err).AtError() return nil, errors.New("failed to get VLESS user").Base(err)
} }
if err := validator.Add(u); err != nil { if err := validator.Add(u); err != nil {
return nil, errors.New("failed to initiate user").Base(err).AtError() return nil, errors.New("failed to initiate user").Base(err)
} }
} }
@@ -110,7 +110,7 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
} }
handler.decryption = &encryption.ServerInstance{} handler.decryption = &encryption.ServerInstance{}
if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.SecondsFrom, config.SecondsTo, config.Padding); err != nil { if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.SecondsFrom, config.SecondsTo, config.Padding); err != nil {
return nil, errors.New("failed to use decryption").Base(err).AtError() return nil, errors.New("failed to use decryption").Base(err)
} }
} }
@@ -128,7 +128,7 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
/* /*
if fb.Path != "" { if fb.Path != "" {
if r, err := regexp.Compile(fb.Path); err != nil { if r, err := regexp.Compile(fb.Path); err != nil {
return nil, errors.New("invalid path regexp").Base(err).AtError() return nil, errors.New("invalid path regexp").Base(err)
} else { } else {
handler.regexps[fb.Path] = r handler.regexps[fb.Path] = r
} }
@@ -274,13 +274,13 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
if h.decryption != nil { if h.decryption != nil {
var err error var err error
if connection, err = h.decryption.Handshake(connection, nil); err != nil { if connection, err = h.decryption.Handshake(connection, nil); err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() return errors.New("ML-KEM-768 handshake failed").Base(err)
} }
} }
sessionPolicy := h.policyManager.ForLevel(0) sessionPolicy := h.policyManager.ForLevel(0)
if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return errors.New("unable to set read deadline").Base(err).AtWarning() return errors.New("unable to set read deadline").Base(err)
} }
first := buf.FromBytes(make([]byte, buf.Size)) first := buf.FromBytes(make([]byte, buf.Size))
@@ -352,7 +352,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
} }
apfb := napfb[name] apfb := napfb[name]
if apfb == nil { if apfb == nil {
return errors.New(`failed to find the default "name" config`).AtWarning() return errors.New(`failed to find the default "name" config`)
} }
if apfb[alpn] == nil { if apfb[alpn] == nil {
@@ -360,7 +360,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
} }
pfb := apfb[alpn] pfb := apfb[alpn]
if pfb == nil { if pfb == nil {
return errors.New(`failed to find the default "alpn" config`).AtWarning() return errors.New(`failed to find the default "alpn" config`)
} }
path := "" path := ""
@@ -369,7 +369,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
if lines := bytes.Split(firstBytes, []byte{'\r', '\n'}); len(lines) > 1 { if lines := bytes.Split(firstBytes, []byte{'\r', '\n'}); len(lines) > 1 {
if s := bytes.Split(lines[0], []byte{' '}); len(s) == 3 { if s := bytes.Split(lines[0], []byte{' '}); len(s) == 3 {
if len(s[0]) < 8 && len(s[1]) > 0 && len(s[2]) == 8 { if len(s[0]) < 8 && len(s[1]) > 0 && len(s[2]) == 8 {
errors.New("realPath = " + string(s[1])).AtInfo().WriteToLog(sid) errors.New("realPath = " + string(s[1])).WriteToLog(sid)
for _, fb := range pfb { for _, fb := range pfb {
if fb.Path != "" && h.regexps[fb.Path].Match(s[1]) { if fb.Path != "" && h.regexps[fb.Path].Match(s[1]) {
path = fb.Path path = fb.Path
@@ -409,7 +409,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
} }
fb := pfb[path] fb := pfb[path]
if fb == nil { if fb == nil {
return errors.New(`failed to find the default "path" config`).AtWarning() return errors.New(`failed to find the default "path" config`)
} }
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
@@ -425,7 +425,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
} }
return nil return nil
}); err != nil { }); err != nil {
return errors.New("failed to dial to " + fb.Dest).Base(err).AtWarning() return errors.New("failed to dial to " + fb.Dest).Base(err)
} }
defer conn.Close() defer conn.Close()
@@ -485,11 +485,11 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)}) pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)})
} }
if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil { if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil {
return errors.New("failed to set PROXY protocol v", fb.Xver).Base(err).AtWarning() return errors.New("failed to set PROXY protocol v", fb.Xver).Base(err)
} }
} }
if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil { if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to fallback request payload").Base(err).AtInfo() return errors.New("failed to fallback request payload").Base(err)
} }
return nil return nil
} }
@@ -499,7 +499,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
getResponse := func() error { getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil { if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to deliver response payload").Base(err).AtInfo() return errors.New("failed to deliver response payload").Base(err)
} }
return nil return nil
} }
@@ -507,7 +507,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil { if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil {
common.Interrupt(serverReader) common.Interrupt(serverReader)
common.Interrupt(serverWriter) common.Interrupt(serverWriter)
return errors.New("fallback ends").Base(err).AtInfo() return errors.New("fallback ends").Base(err)
} }
return nil return nil
} }
@@ -519,7 +519,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
Status: log.AccessRejected, Status: log.AccessRejected,
Reason: err, Reason: err,
}) })
err = errors.New("invalid request from ", connection.RemoteAddr()).Base(err).AtInfo() err = errors.New("invalid request from ", connection.RemoteAddr()).Base(err)
} }
return err return err
} }
@@ -555,7 +555,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
inbound.CanSpliceCopy = 2 inbound.CanSpliceCopy = 2
switch request.Command { switch request.Command {
case protocol.RequestCommandUDP: case protocol.RequestCommandUDP:
return errors.New(requestAddons.Flow + " doesn't support UDP").AtWarning() return errors.New(requestAddons.Flow + " doesn't support UDP")
case protocol.RequestCommandMux, protocol.RequestCommandRvs: case protocol.RequestCommandMux, protocol.RequestCommandRvs:
inbound.CanSpliceCopy = 3 inbound.CanSpliceCopy = 3
fallthrough // we will break Mux connections that contain TCP requests fallthrough // we will break Mux connections that contain TCP requests
@@ -570,7 +570,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
p = uintptr(unsafe.Pointer(commonConn)) p = uintptr(unsafe.Pointer(commonConn))
} else if tlsConn, ok := iConn.(*tls.Conn); ok { } else if tlsConn, ok := iConn.(*tls.Conn); ok {
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { if tlsConn.ConnectionState().Version != gotls.VersionTLS13 {
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning() return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version)
} }
t = reflect.TypeOf(tlsConn.Conn).Elem() t = reflect.TypeOf(tlsConn.Conn).Elem()
p = uintptr(unsafe.Pointer(tlsConn.Conn)) p = uintptr(unsafe.Pointer(tlsConn.Conn))
@@ -578,7 +578,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
t = reflect.TypeOf(realityConn.Conn).Elem() t = reflect.TypeOf(realityConn.Conn).Elem()
p = uintptr(unsafe.Pointer(realityConn.Conn)) p = uintptr(unsafe.Pointer(realityConn.Conn))
} else { } else {
return errors.New("XTLS only supports TLS and REALITY directly for now.").AtWarning() return errors.New("XTLS only supports TLS and REALITY directly for now.")
} }
i, _ := t.FieldByName("input") i, _ := t.FieldByName("input")
r, _ := t.FieldByName("rawInput") r, _ := t.FieldByName("rawInput")
@@ -586,15 +586,15 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset)) rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))
} }
} else { } else {
return errors.New("account " + account.ID.String() + " is not able to use the flow " + requestAddons.Flow).AtWarning() return errors.New("account " + account.ID.String() + " is not able to use the flow " + requestAddons.Flow)
} }
case "": case "":
inbound.CanSpliceCopy = 3 inbound.CanSpliceCopy = 3
if account.Flow == vless.XRV && (request.Command == protocol.RequestCommandTCP || isMuxAndNotXUDP(request, first)) { if account.Flow == vless.XRV && (request.Command == protocol.RequestCommandTCP || isMuxAndNotXUDP(request, first)) {
return errors.New("account " + account.ID.String() + " is rejected since the client flow is empty. Note that the pure TLS proxy has certain TLS in TLS characters.").AtWarning() return errors.New("account " + account.ID.String() + " is rejected since the client flow is empty. Note that the pure TLS proxy has certain TLS in TLS characters.")
} }
default: default:
return errors.New("unknown request flow " + requestAddons.Flow).AtWarning() return errors.New("unknown request flow " + requestAddons.Flow)
} }
if request.Command != protocol.RequestCommandMux { if request.Command != protocol.RequestCommandMux {
@@ -617,7 +617,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection)) bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection))
if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil { if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil {
return errors.New("failed to encode response header").Base(err).AtWarning() return errors.New("failed to encode response header").Base(err)
} }
clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, false, ctx, connection, nil) clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, false, ctx, connection, nil)
bufferWriter.SetFlushNext() bufferWriter.SetFlushNext()
@@ -654,11 +654,11 @@ func (r *Reverse) Tag() string {
func (r *Reverse) NewMux(ctx context.Context, link *transport.Link, observer features.Feature) error { func (r *Reverse) NewMux(ctx context.Context, link *transport.Link, observer features.Feature) error {
muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{}) muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})
if err != nil { if err != nil {
return errors.New("failed to create mux client worker").Base(err).AtWarning() return errors.New("failed to create mux client worker").Base(err)
} }
worker, err := reverse.NewPortalWorker(muxClient) worker, err := reverse.NewPortalWorker(muxClient)
if err != nil { if err != nil {
return errors.New("failed to create portal worker").Base(err).AtWarning() return errors.New("failed to create portal worker").Base(err)
} }
r.picker.AddWorker(worker) r.picker.AddWorker(worker)
if burstObs, ok := observer.(extension.BurstObservatory); ok { if burstObs, ok := observer.(extension.BurstObservatory); ok {
+18 -20
View File
@@ -73,7 +73,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
} }
server, err := protocol.NewServerSpecFromPB(config.Vnext) server, err := protocol.NewServerSpecFromPB(config.Vnext)
if err != nil { if err != nil {
return nil, errors.New("failed to get server spec").Base(err).AtError() return nil, errors.New("failed to get server spec").Base(err)
} }
v := core.MustFromContext(ctx) v := core.MustFromContext(ctx)
@@ -93,7 +93,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
} }
handler.encryption = &encryption.ClientInstance{} handler.encryption = &encryption.ClientInstance{}
if err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds, a.Padding); err != nil { if err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds, a.Padding); err != nil {
return nil, errors.New("failed to use encryption").Base(err).AtError() return nil, errors.New("failed to use encryption").Base(err)
} }
} }
@@ -106,7 +106,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
if sc := a.Reverse.Sniffing; sc != nil && sc.Enabled { if sc := a.Reverse.Sniffing; sc != nil && sc.Enabled {
request, err := proxymanConfig.BuildSniffingRequest(sc) request, err := proxymanConfig.BuildSniffingRequest(sc)
if err != nil { if err != nil {
return nil, errors.New("failed to build reverse sniffing request").Base(err).AtError() return nil, errors.New("failed to build reverse sniffing request").Base(err)
} }
rvsCtx = session.ContextWithContent(rvsCtx, &session.Content{ rvsCtx = session.ContextWithContent(rvsCtx, &session.Content{
SniffingRequest: request, SniffingRequest: request,
@@ -149,7 +149,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
if !ob.Target.IsValid() && ob.Target.Address.String() != "v1.rvs.cool" { if !ob.Target.IsValid() && ob.Target.Address.String() != "v1.rvs.cool" {
return errors.New("target not specified").AtError() return errors.New("target not specified")
} }
ob.Name = "vless" ob.Name = "vless"
@@ -178,7 +178,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
for { for {
connTime := <-h.preConns connTime := <-h.preConns
if connTime == nil { if connTime == nil {
return errors.New("closed handler").AtWarning() return errors.New("closed handler")
} }
if time.Now().Before(connTime.Expire) { if time.Now().Before(connTime.Expire) {
conn = connTime.Conn conn = connTime.Conn
@@ -197,13 +197,11 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
return nil return nil
}); err != nil { }); err != nil {
return errors.New("failed to find an available destination").Base(err).AtWarning() return errors.New("failed to find an available destination").Base(err)
} }
} }
defer conn.Close() defer conn.Close()
ob.Conn = conn // for Vision's pre-connect
iConn := stat.TryUnwrapStatsConn(conn) iConn := stat.TryUnwrapStatsConn(conn)
target := ob.Target target := ob.Target
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination.NetAddr()) errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination.NetAddr())
@@ -211,7 +209,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
if h.encryption != nil { if h.encryption != nil {
var err error var err error
if conn, err = h.encryption.Handshake(conn); err != nil { if conn, err = h.encryption.Handshake(conn); err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() return errors.New("ML-KEM-768 handshake failed").Base(err)
} }
} }
@@ -225,7 +223,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
command = protocol.RequestCommandMux command = protocol.RequestCommandMux
case "v1.rvs.cool": case "v1.rvs.cool":
if target.Network != net.Network_Unknown { if target.Network != net.Network_Unknown {
return errors.New("nice try baby").AtError() return errors.New("nice try baby")
} }
command = protocol.RequestCommandRvs command = protocol.RequestCommandRvs
} }
@@ -258,7 +256,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
switch request.Command { switch request.Command {
case protocol.RequestCommandUDP: case protocol.RequestCommandUDP:
if !allowUDP443 && request.Port == 443 { if !allowUDP443 && request.Port == 443 {
return errors.New("XTLS rejected UDP/443 traffic").AtInfo() return errors.New("XTLS rejected UDP/443 traffic")
} }
case protocol.RequestCommandMux: case protocol.RequestCommandMux:
fallthrough // let server break Mux connections that contain TCP requests fallthrough // let server break Mux connections that contain TCP requests
@@ -281,7 +279,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
t = reflect.TypeOf(realityConn.Conn).Elem() t = reflect.TypeOf(realityConn.Conn).Elem()
p = uintptr(unsafe.Pointer(realityConn.Conn)) p = uintptr(unsafe.Pointer(realityConn.Conn))
} else { } else {
return errors.New("XTLS only supports TLS and REALITY directly for now.").AtWarning() return errors.New("XTLS only supports TLS and REALITY directly for now.")
} }
i, _ := t.FieldByName("input") i, _ := t.FieldByName("input")
r, _ := t.FieldByName("rawInput") r, _ := t.FieldByName("rawInput")
@@ -323,7 +321,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
if err := encoding.EncodeRequestHeader(bufferWriter, request, requestAddons); err != nil { if err := encoding.EncodeRequestHeader(bufferWriter, request, requestAddons); err != nil {
return errors.New("failed to encode request header").Base(err).AtWarning() return errors.New("failed to encode request header").Base(err)
} }
// default: serverWriter := bufferWriter // default: serverWriter := bufferWriter
@@ -352,23 +350,23 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer // Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
if err := bufferWriter.SetBuffered(false); err != nil { if err := bufferWriter.SetBuffered(false); err != nil {
return errors.New("failed to write A request payload").Base(err).AtWarning() return errors.New("failed to write A request payload").Base(err)
} }
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
if tlsConn, ok := iConn.(*tls.Conn); ok { if tlsConn, ok := iConn.(*tls.Conn); ok {
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { if tlsConn.ConnectionState().Version != gotls.VersionTLS13 {
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning() return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version)
} }
} else if utlsConn, ok := iConn.(*tls.UConn); ok { } else if utlsConn, ok := iConn.(*tls.UConn); ok {
if utlsConn.ConnectionState().Version != utls.VersionTLS13 { if utlsConn.ConnectionState().Version != utls.VersionTLS13 {
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, utlsConn.ConnectionState().Version).AtWarning() return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, utlsConn.ConnectionState().Version)
} }
} }
} }
err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)) err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
if err != nil { if err != nil {
return errors.New("failed to transfer request payload").Base(err).AtInfo() return errors.New("failed to transfer request payload").Base(err)
} }
// Indicates the end of request payload. // Indicates the end of request payload.
@@ -383,7 +381,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
responseAddons, err := encoding.DecodeResponseHeader(conn, request) responseAddons, err := encoding.DecodeResponseHeader(conn, request)
if err != nil { if err != nil {
return errors.New("failed to decode response header").Base(err).AtInfo() return errors.New("failed to decode response header").Base(err)
} }
// default: serverReader := buf.NewReader(conn) // default: serverReader := buf.NewReader(conn)
@@ -407,7 +405,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
if err != nil { if err != nil {
return errors.New("failed to transfer response payload").Base(err).AtInfo() return errors.New("failed to transfer response payload").Base(err)
} }
return nil return nil
@@ -418,7 +416,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
if err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil { if err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil {
return errors.New("connection ends").Base(err).AtInfo() return errors.New("connection ends").Base(err)
} }
return nil return nil
+1 -1
View File
@@ -49,7 +49,7 @@ func (a *MemoryAccount) ToProto() proto.Message {
func (a *Account) AsAccount() (protocol.Account, error) { func (a *Account) AsAccount() (protocol.Account, error) {
id, err := uuid.ParseString(a.Id) id, err := uuid.ParseString(a.Id)
if err != nil { if err != nil {
return nil, errors.New("failed to parse ID").Base(err).AtError() return nil, errors.New("failed to parse ID").Base(err)
} }
protoID := protocol.NewID(id) protoID := protocol.NewID(id)
var AuthenticatedLength, NoTerminationSignal bool var AuthenticatedLength, NoTerminationSignal bool
+1 -1
View File
@@ -209,7 +209,7 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon
defer buffer.Release() defer buffer.Release()
if _, err := buffer.ReadFullFrom(c.responseReader, 4); err != nil { if _, err := buffer.ReadFullFrom(c.responseReader, 4); err != nil {
return nil, errors.New("failed to read response header").Base(err).AtWarning() return nil, errors.New("failed to read response header").Base(err)
} }
if buffer.Byte(0) != c.responseHeader { if buffer.Byte(0) != c.responseHeader {
+2 -2
View File
@@ -227,7 +227,7 @@ func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSess
func (h *Handler) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error { func (h *Handler) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error {
sessionPolicy := h.policyManager.ForLevel(0) sessionPolicy := h.policyManager.ForLevel(0)
if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return errors.New("unable to set read deadline").Base(err).AtWarning() return errors.New("unable to set read deadline").Base(err)
} }
iConn := stat.TryUnwrapStatsConn(connection) iConn := stat.TryUnwrapStatsConn(connection)
@@ -247,7 +247,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
Status: log.AccessRejected, Status: log.AccessRejected,
Reason: err, Reason: err,
}) })
err = errors.New("invalid request from ", connection.RemoteAddr()).Base(err).AtInfo() err = errors.New("invalid request from ", connection.RemoteAddr()).Base(err)
} }
return err return err
} }
+3 -3
View File
@@ -60,7 +60,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
if !ob.Target.IsValid() { if !ob.Target.IsValid() {
return errors.New("target not specified").AtError() return errors.New("target not specified")
} }
ob.Name = "vmess" ob.Name = "vmess"
ob.CanSpliceCopy = 3 ob.CanSpliceCopy = 3
@@ -78,7 +78,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return nil return nil
}) })
if err != nil { if err != nil {
return errors.New("failed to find an available destination").Base(err).AtWarning() return errors.New("failed to find an available destination").Base(err)
} }
defer conn.Close() defer conn.Close()
@@ -154,7 +154,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
writer := buf.NewBufferedWriter(buf.NewWriter(conn)) writer := buf.NewBufferedWriter(buf.NewWriter(conn))
if err := session.EncodeRequestHeader(request, writer); err != nil { if err := session.EncodeRequestHeader(request, writer); err != nil {
return errors.New("failed to encode request").Base(err).AtWarning() return errors.New("failed to encode request").Base(err)
} }
bodyWriter, err := session.EncodeRequestBody(request, writer) bodyWriter, err := session.EncodeRequestBody(request, writer)
+123 -138
View File
@@ -3,7 +3,6 @@ package wireguard
import ( import (
"context" "context"
"fmt" "fmt"
gonet "net"
"net/netip" "net/netip"
"reflect" "reflect"
"strings" "strings"
@@ -28,14 +27,10 @@ import (
"github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/device"
) )
type entry struct {
got []net.IP
time time.Time
}
type Handler struct { type Handler struct {
conf *DeviceConfig conf *DeviceConfig
policyManager policy.Manager policyManager policy.Manager
@@ -49,11 +44,6 @@ type Handler struct {
tnet *Net tnet *Net
dev *device.Device dev *device.Device
mu sync.Mutex mu sync.Mutex
// TODO: cache cleanup loop
local bool
cache map[string]entry
cacheMu sync.Mutex
} }
func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) { func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
@@ -109,15 +99,10 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
return nil, err return nil, err
} }
local := false
dns := conf.DNS dns := conf.DNS
if len(dns) == 0 { if len(dns) == 0 {
dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"} dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"}
} }
if len(dns) == 1 && dns[0] == "local" {
local = true
dns = nil
}
dnses := make([]netip.Addr, 0, len(dns)) dnses := make([]netip.Addr, 0, len(dns))
for _, dns := range dns { for _, dns := range dns {
dnses = append(dnses, netip.MustParseAddr(dns)) dnses = append(dnses, netip.MustParseAddr(dns))
@@ -151,9 +136,6 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
tun: tun, tun: tun,
tnet: tnet, tnet: tnet,
local: local,
cache: make(map[string]entry),
}, nil }, nil
} }
@@ -172,22 +154,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return err return err
} }
var addr netip.Addr
if ob.Target.Address.Family().IsDomain() {
ip, err := h.resolveRemote(ob.Target.Address.String())
if err != nil {
return errors.New("failed to resolve domain").Base(err)
}
addr, _ = netip.AddrFromSlice(ip)
} else {
addr, _ = netip.AddrFromSlice(ob.Target.Address.IP())
}
addrPort := netip.AddrPortFrom(addr, ob.Target.Port.Value())
if !addrPort.IsValid() {
return errors.New("invalid target ", ob.Target)
}
var newCtx context.Context var newCtx context.Context
var newCancel context.CancelFunc var newCancel context.CancelFunc
if session.TimeoutOnlyFromContext(ctx) { if session.TimeoutOnlyFromContext(ctx) {
@@ -216,10 +182,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
var err error var err error
if sessionPolicy.Timeouts.Handshake != 0 { if sessionPolicy.Timeouts.Handshake != 0 {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake) timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
conn, err = h.tnet.DialContextTCPAddrPort(timeoutCtx, addrPort) conn, err = h.tnet.DialContext(timeoutCtx, "tcp", ob.Target.NetAddr())
timeoutCancel() timeoutCancel()
} else { } else {
conn, err = h.tnet.DialContextTCPAddrPort(ctx, addrPort) conn, err = h.tnet.Dial("tcp", ob.Target.NetAddr())
} }
if err != nil { if err != nil {
return errors.New("failed to create TCP connection").Base(err) return errors.New("failed to create TCP connection").Base(err)
@@ -228,15 +194,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
reader = buf.NewReader(conn) reader = buf.NewReader(conn)
writer = buf.NewWriter(conn) writer = buf.NewWriter(conn)
case net.Network_UDP: case net.Network_UDP:
conn, err := h.tnet.DialUDPAddrPort(netip.AddrPort{}, addrPort) conn, err := h.tnet.Dial("udp", ob.Target.NetAddr())
if err != nil { if err != nil {
return errors.New("failed to create UDP connection").Base(err) return errors.New("failed to create UDP connection").Base(err)
} }
defer conn.Close() defer conn.Close()
c := &udpConnClient{ c := &UDPConnClient{
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn, PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
resolveFunc: h.resolveRemote, Dest: conn.RemoteAddr().(*net.UDPAddr),
dest: gonet.UDPAddrFromAddrPort(addrPort),
} }
reader = c reader = c
writer = c writer = c
@@ -293,26 +258,26 @@ func (h *Handler) init(ctx context.Context) error {
if err != nil { if err != nil {
return nil, err return nil, err
} }
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
if err != nil {
return nil, err
}
var pktConn net.PacketConn var pktConn net.PacketConn
switch c := conn.(type) { if h.streamSettings.FinalMask != nil {
case *internet.PacketConnWrapper: conn, err := h.streamSettings.FinalMask.DialUDP(ctx, dest)
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
if h.streamSettings.UdpmaskManager != nil {
newConn, err := h.streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil { if err != nil {
pktConn.Close() return nil, errors.New("failed to dial to dest").Base(err)
return nil, errors.New("mask err").Base(err) }
pktConn = conn.(*finalmask.PacketConnWrapper).PacketConn
} else {
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
} }
pktConn = newConn
} }
if h.uplinkCounter != nil || h.downlinkCounter != nil { if h.uplinkCounter != nil || h.downlinkCounter != nil {
pktConn = &PacketCounterConnection{ pktConn = &PacketCounterConnection{
@@ -371,90 +336,54 @@ func (h *Handler) init(ctx context.Context) error {
} }
func (h *Handler) resolveLocal(host string) (net.IP, error) { func (h *Handler) resolveLocal(host string) (net.IP, error) {
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
})
}
func (h *Handler) resolveRemote(host string) (net.IP, error) {
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
if h.local {
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
}
return h.tnet.LookupHost(host)
})
}
func (h *Handler) resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, uint32, error)) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil { if ip := net.ParseIP(host); ip != nil {
return ip, nil return ip, nil
} }
h.cacheMu.Lock() ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
if entry, ok := h.cache[host]; ok {
if time.Now().Before(entry.time) {
h.cacheMu.Unlock()
return entry.got[dice.Roll(len(entry.got))], nil
}
delete(h.cache, host)
}
h.cacheMu.Unlock()
ips, ttl, err := lookupIP(host)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(ips) == 0 { got := ips
return nil, dns.ErrEmptyResponse if h.streamSettings.SocketSettings != nil {
} var got4, got6 []net.IP
var got4, got6 []net.IP for _, ip := range ips {
for _, ip := range ips { if ip.To4() != nil {
if ip.To4() != nil { got4 = append(got4, ip)
got4 = append(got4, ip) } else {
} else { got6 = append(got6, ip)
got6 = append(got6, ip) }
} }
} switch h.streamSettings.SocketSettings.DomainStrategy {
var got []net.IP case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
switch strategy { got = ips
case DeviceConfig_FORCE_IP: case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
got = ips
return ips[dice.Roll(len(ips))], nil
case DeviceConfig_FORCE_IP4:
got = got4
case DeviceConfig_FORCE_IP6:
got = got6
case DeviceConfig_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case DeviceConfig_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4 got = got4
case internet.DomainStrategy_USE_IP6, internet.DomainStrategy_FORCE_IP6:
got = got6
case internet.DomainStrategy_USE_IP46, internet.DomainStrategy_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case internet.DomainStrategy_USE_IP64, internet.DomainStrategy_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4
}
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
} }
default:
panic(strategy)
} }
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
}
entry := entry{
got: got,
time: time.Now().Add(time.Duration(ttl) * time.Second),
}
h.cacheMu.Lock()
h.cache[host] = entry
h.cacheMu.Unlock()
return got[dice.Roll(len(got))], nil return got[dice.Roll(len(got))], nil
} }
type udpConnClient struct { type UDPConnClient struct {
net.PacketConn net.PacketConn
resolveFunc func(host string) (net.IP, error) Dest *net.UDPAddr
dest *net.UDPAddr
} }
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) { func (c *UDPConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New() b := buf.New()
b.Resize(0, buf.Size) b.Resize(0, buf.Size)
n, addr, err := c.PacketConn.ReadFrom(b.Bytes()) n, addr, err := c.PacketConn.ReadFrom(b.Bytes())
@@ -473,20 +402,13 @@ func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
return buf.MultiBuffer{b}, nil return buf.MultiBuffer{b}, nil
} }
func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error { func (c *UDPConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb { for i, b := range mb {
dst := c.dest dst := c.Dest
if b.UDP != nil { if b.UDP != nil {
if b.UDP.Address.Family().IsDomain() { if b.UDP.Address.Family().IsDomain() {
ip, err := c.resolveFunc(b.UDP.Address.String()) if b.UDP.Port != net.Port(dst.Port) {
if err != nil { dst = &net.UDPAddr{IP: dst.IP, Port: int(b.UDP.Port)}
errors.LogErrorInner(context.Background(), err, "drop packet to ", b.UDP, " with size ", len(b.Bytes()))
b.Release()
continue
}
dst = &net.UDPAddr{
IP: ip,
Port: int(b.UDP.Port),
} }
} else { } else {
dst = b.UDP.RawNetAddr().(*net.UDPAddr) dst = b.UDP.RawNetAddr().(*net.UDPAddr)
@@ -523,3 +445,66 @@ func (c *PacketCounterConnection) WriteTo(p []byte, addr net.Addr) (n int, err e
} }
return return
} }
type entry struct {
saddr []string
deadline time.Time
}
type cache struct {
running bool
m map[string]entry
mu sync.Mutex
}
func (c *cache) run() {
if c.running {
return
}
c.running = true
if c.m == nil {
c.m = make(map[string]entry)
}
go c.gc()
}
func (c *cache) gc() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for now := range ticker.C {
c.mu.Lock()
for key, entry := range c.m {
if now.After(entry.deadline) {
delete(c.m, key)
}
}
if len(c.m) == 0 {
c.running = false
c.mu.Unlock()
return
}
c.mu.Unlock()
}
}
func (c *cache) LookupHost(host string) []string {
c.mu.Lock()
defer c.mu.Unlock()
c.run()
if entry, ok := c.m[host]; ok {
if time.Now().Before(entry.deadline) {
return entry.saddr
}
delete(c.m, host)
}
return nil
}
func (c *cache) Cache(host string, saddr []string, ttl uint32) {
c.mu.Lock()
defer c.mu.Unlock()
c.m[host] = entry{
saddr: saddr,
deadline: time.Now().Add(time.Second * time.Duration(ttl)),
}
}
+26 -102
View File
@@ -22,61 +22,6 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
type DeviceConfig_DomainStrategy int32
const (
DeviceConfig_FORCE_IP DeviceConfig_DomainStrategy = 0
DeviceConfig_FORCE_IP4 DeviceConfig_DomainStrategy = 1
DeviceConfig_FORCE_IP6 DeviceConfig_DomainStrategy = 2
DeviceConfig_FORCE_IP46 DeviceConfig_DomainStrategy = 3
DeviceConfig_FORCE_IP64 DeviceConfig_DomainStrategy = 4
)
// Enum value maps for DeviceConfig_DomainStrategy.
var (
DeviceConfig_DomainStrategy_name = map[int32]string{
0: "FORCE_IP",
1: "FORCE_IP4",
2: "FORCE_IP6",
3: "FORCE_IP46",
4: "FORCE_IP64",
}
DeviceConfig_DomainStrategy_value = map[string]int32{
"FORCE_IP": 0,
"FORCE_IP4": 1,
"FORCE_IP6": 2,
"FORCE_IP46": 3,
"FORCE_IP64": 4,
}
)
func (x DeviceConfig_DomainStrategy) Enum() *DeviceConfig_DomainStrategy {
p := new(DeviceConfig_DomainStrategy)
*p = x
return p
}
func (x DeviceConfig_DomainStrategy) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (DeviceConfig_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
return file_proxy_wireguard_config_proto_enumTypes[0].Descriptor()
}
func (DeviceConfig_DomainStrategy) Type() protoreflect.EnumType {
return &file_proxy_wireguard_config_proto_enumTypes[0]
}
func (x DeviceConfig_DomainStrategy) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use DeviceConfig_DomainStrategy.Descriptor instead.
func (DeviceConfig_DomainStrategy) EnumDescriptor() ([]byte, []int) {
return file_proxy_wireguard_config_proto_rawDescGZIP(), []int{1, 0}
}
type PeerConfig struct { type PeerConfig struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
@@ -154,19 +99,18 @@ func (x *PeerConfig) GetAllowedIps() []string {
} }
type DeviceConfig struct { type DeviceConfig struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"` SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"` Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"` Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"` Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"` Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"` Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"` IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"` NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"`
NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"` DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"` unknownFields protoimpl.UnknownFields
unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache
sizeCache protoimpl.SizeCache
} }
func (x *DeviceConfig) Reset() { func (x *DeviceConfig) Reset() {
@@ -241,13 +185,6 @@ func (x *DeviceConfig) GetReserved() []byte {
return nil return nil
} }
func (x *DeviceConfig) GetDomainStrategy() DeviceConfig_DomainStrategy {
if x != nil {
return x.DomainStrategy
}
return DeviceConfig_FORCE_IP
}
func (x *DeviceConfig) GetIsClient() bool { func (x *DeviceConfig) GetIsClient() bool {
if x != nil { if x != nil {
return x.IsClient return x.IsClient
@@ -283,7 +220,7 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\n" + "\n" +
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" + "keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
"\vallowed_ips\x18\x05 \x03(\tR\n" + "\vallowed_ips\x18\x05 \x03(\tR\n" +
"allowedIps\"\xee\x03\n" + "allowedIps\"\xb4\x02\n" +
"\fDeviceConfig\x12\x1d\n" + "\fDeviceConfig\x12\x1d\n" +
"\n" + "\n" +
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" + "secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
@@ -291,20 +228,11 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" + "\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" +
"\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" + "\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" +
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" + "\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" + "\breserved\x18\x06 \x01(\fR\breserved\x12\x1b\n" +
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
"\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" + "\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" +
"\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\x12\x10\n" + "\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\x12\x10\n" +
"\x03DNS\x18\n" + "\x03DNS\x18\n" +
" \x03(\tR\x03DNS\"\\\n" + " \x03(\tR\x03DNSB^\n" +
"\x0eDomainStrategy\x12\f\n" +
"\bFORCE_IP\x10\x00\x12\r\n" +
"\tFORCE_IP4\x10\x01\x12\r\n" +
"\tFORCE_IP6\x10\x02\x12\x0e\n" +
"\n" +
"FORCE_IP46\x10\x03\x12\x0e\n" +
"\n" +
"FORCE_IP64\x10\x04B^\n" +
"\x18com.xray.proxy.wireguardP\x01Z)github.com/xtls/xray-core/proxy/wireguard\xaa\x02\x14Xray.Proxy.WireGuardb\x06proto3" "\x18com.xray.proxy.wireguardP\x01Z)github.com/xtls/xray-core/proxy/wireguard\xaa\x02\x14Xray.Proxy.WireGuardb\x06proto3"
var ( var (
@@ -319,23 +247,20 @@ func file_proxy_wireguard_config_proto_rawDescGZIP() []byte {
return file_proxy_wireguard_config_proto_rawDescData return file_proxy_wireguard_config_proto_rawDescData
} }
var file_proxy_wireguard_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_proxy_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_proxy_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_wireguard_config_proto_goTypes = []any{ var file_proxy_wireguard_config_proto_goTypes = []any{
(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy (*PeerConfig)(nil), // 0: xray.proxy.wireguard.PeerConfig
(*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig (*DeviceConfig)(nil), // 1: xray.proxy.wireguard.DeviceConfig
(*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig (*protocol.User)(nil), // 2: xray.common.protocol.User
(*protocol.User)(nil), // 3: xray.common.protocol.User
} }
var file_proxy_wireguard_config_proto_depIdxs = []int32{ var file_proxy_wireguard_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig 0, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
3, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User 2, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
0, // 2: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy 2, // [2:2] is the sub-list for method output_type
3, // [3:3] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type
3, // [3:3] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee
3, // [3:3] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name
0, // [0:3] is the sub-list for field type_name
} }
func init() { file_proxy_wireguard_config_proto_init() } func init() { file_proxy_wireguard_config_proto_init() }
@@ -348,14 +273,13 @@ func file_proxy_wireguard_config_proto_init() {
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)),
NumEnums: 1, NumEnums: 0,
NumMessages: 2, NumMessages: 2,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
GoTypes: file_proxy_wireguard_config_proto_goTypes, GoTypes: file_proxy_wireguard_config_proto_goTypes,
DependencyIndexes: file_proxy_wireguard_config_proto_depIdxs, DependencyIndexes: file_proxy_wireguard_config_proto_depIdxs,
EnumInfos: file_proxy_wireguard_config_proto_enumTypes,
MessageInfos: file_proxy_wireguard_config_proto_msgTypes, MessageInfos: file_proxy_wireguard_config_proto_msgTypes,
}.Build() }.Build()
File_proxy_wireguard_config_proto = out.File File_proxy_wireguard_config_proto = out.File
-8
View File
@@ -17,13 +17,6 @@ message PeerConfig {
} }
message DeviceConfig { message DeviceConfig {
enum DomainStrategy {
FORCE_IP = 0;
FORCE_IP4 = 1;
FORCE_IP6 = 2;
FORCE_IP46 = 3;
FORCE_IP64 = 4;
}
string secret_key = 1; string secret_key = 1;
repeated string endpoint = 2; repeated string endpoint = 2;
repeated PeerConfig peers = 3; repeated PeerConfig peers = 3;
@@ -31,7 +24,6 @@ message DeviceConfig {
int32 mtu = 4; int32 mtu = 4;
bytes reserved = 6; bytes reserved = 6;
DomainStrategy domain_strategy = 7;
bool is_client = 8; bool is_client = 8;
bool no_kernel_tun = 9; bool no_kernel_tun = 9;
repeated string DNS = 10; repeated string DNS = 10;
+159 -14
View File
@@ -15,6 +15,8 @@ import (
"net" "net"
"net/netip" "net/netip"
"os" "os"
"regexp"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@@ -42,6 +44,7 @@ type netTun struct {
events chan tun.Event events chan tun.Event
notifyHandle *channel.NotificationHandle notifyHandle *channel.NotificationHandle
incomingPacket chan *buffer.View incomingPacket chan *buffer.View
closed chan struct{}
mtu int mtu int
dnsServers []netip.Addr dnsServers []netip.Addr
hasV4, hasV6 bool hasV4, hasV6 bool
@@ -58,6 +61,7 @@ func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handleLocal
stack: stack.New(opts), stack: stack.New(opts),
events: make(chan tun.Event, 10), events: make(chan tun.Event, 10),
incomingPacket: make(chan *buffer.View), incomingPacket: make(chan *buffer.View),
closed: make(chan struct{}),
dnsServers: dnsServers, dnsServers: dnsServers,
mtu: mtu, mtu: mtu,
} }
@@ -124,12 +128,15 @@ func (tun *netTun) Events() <-chan tun.Event {
} }
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) { func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
view, ok := <-tun.incomingPacket var view *buffer.View
if !ok { select {
case view = <-tun.incomingPacket:
case <-tun.closed:
return 0, os.ErrClosed return 0, os.ErrClosed
} }
n, err := view.Read(buf[0][offset:]) n, err := view.Read(buf[0][offset:])
view.Release()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -166,7 +173,11 @@ func (tun *netTun) WriteNotify() {
view := pkt.ToView() view := pkt.ToView()
pkt.DecRef() pkt.DecRef()
tun.incomingPacket <- view select {
case tun.incomingPacket <- view:
case <-tun.closed:
view.Release()
}
} }
func (tun *netTun) Close() error { func (tun *netTun) Close() error {
@@ -179,8 +190,9 @@ func (tun *netTun) Close() error {
close(tun.events) close(tun.events)
} }
if tun.incomingPacket != nil { // we don't close incomingPacket, because WriteNotify may be mid-send on it (DNS lookup) and would panic.
close(tun.incomingPacket) if tun.closed != nil {
close(tun.closed)
} }
return nil return nil
@@ -219,6 +231,7 @@ type Net struct {
DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error) DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error)
dnsServers []netip.Addr dnsServers []netip.Addr
hasV4, hasV6 bool hasV4, hasV6 bool
cache cache
} }
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) { func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
@@ -246,9 +259,12 @@ var (
errServerTemporarilyMisbehaving = errors.New("server misbehaving") errServerTemporarilyMisbehaving = errors.New("server misbehaving")
errCanceled = errors.New("operation was canceled") errCanceled = errors.New("operation was canceled")
errTimeout = errors.New("i/o timeout") errTimeout = errors.New("i/o timeout")
errNumericPort = errors.New("port must be numeric")
errNoSuitableAddress = errors.New("no suitable address found")
errMissingAddress = errors.New("missing address")
) )
func (net *Net) LookupHost(host string) (addrs []net.IP, ttl uint32, err error) { func (net *Net) LookupHost(host string) (addrs []string, err error) {
return net.LookupContextHost(context.Background(), host) return net.LookupContextHost(context.Background(), host)
} }
@@ -567,9 +583,12 @@ func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.T
return dnsmessage.Parser{}, "", lastErr return dnsmessage.Parser{}, "", lastErr
} }
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP, uint32, error) { func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) {
if saddr := tnet.cache.LookupHost(host); saddr != nil {
return saddr, nil
}
if host == "" || (!tnet.hasV6 && !tnet.hasV4) { if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
} }
zlen := len(host) zlen := len(host)
if strings.IndexByte(host, ':') != -1 { if strings.IndexByte(host, ':') != -1 {
@@ -578,11 +597,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP,
} }
} }
if ip, err := netip.ParseAddr(host[:zlen]); err == nil { if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
return []net.IP{ip.AsSlice()}, 0, nil return []string{ip.String()}, nil
} }
if !isDomainName(host) { if !isDomainName(host) {
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
} }
type result struct { type result struct {
p dnsmessage.Parser p dnsmessage.Parser
@@ -683,11 +702,137 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP,
} }
if len(addrs) == 0 && lastErr != nil { if len(addrs) == 0 && lastErr != nil {
return nil, 0, lastErr return nil, lastErr
} }
ips := make([]net.IP, 0, len(addrs)) saddrs := make([]string, 0, len(addrs))
for _, ip := range addrs { for _, ip := range addrs {
ips = append(ips, ip.AsSlice()) saddrs = append(saddrs, ip.String())
} }
return ips, ttl, nil tnet.cache.Cache(host, saddrs, ttl)
return saddrs, nil
}
func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) {
if deadline.IsZero() {
return deadline, nil
}
timeRemaining := deadline.Sub(now)
if timeRemaining <= 0 {
return time.Time{}, errTimeout
}
timeout := timeRemaining / time.Duration(addrsRemaining)
const saneMinimum = 2 * time.Second
if timeout < saneMinimum {
if timeRemaining < saneMinimum {
timeout = timeRemaining
} else {
timeout = saneMinimum
}
}
return now.Add(timeout), nil
}
var protoSplitter = regexp.MustCompile(`^(tcp|udp|ping)(4|6)?$`)
func (tnet *Net) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
if ctx == nil {
panic("nil context")
}
var acceptV4, acceptV6 bool
matches := protoSplitter.FindStringSubmatch(network)
if matches == nil {
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
} else if len(matches[2]) == 0 {
acceptV4 = true
acceptV6 = true
} else {
acceptV4 = matches[2][0] == '4'
acceptV6 = !acceptV4
}
var host string
var port int
if matches[1] == "ping" {
host = address
} else {
var sport string
var err error
host, sport, err = net.SplitHostPort(address)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
port, err = strconv.Atoi(sport)
if err != nil || port < 0 || port > 65535 {
return nil, &net.OpError{Op: "dial", Err: errNumericPort}
}
}
allAddr, err := tnet.LookupContextHost(ctx, host)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
var addrs []netip.AddrPort
for _, addr := range allAddr {
ip, err := netip.ParseAddr(addr)
if err == nil && ((ip.Is4() && acceptV4) || (ip.Is6() && acceptV6)) {
addrs = append(addrs, netip.AddrPortFrom(ip, uint16(port)))
}
}
if len(addrs) == 0 && len(allAddr) != 0 {
return nil, &net.OpError{Op: "dial", Err: errNoSuitableAddress}
}
var firstErr error
for i, addr := range addrs {
select {
case <-ctx.Done():
err := ctx.Err()
if err == context.Canceled {
err = errCanceled
} else if err == context.DeadlineExceeded {
err = errTimeout
}
return nil, &net.OpError{Op: "dial", Err: err}
default:
}
dialCtx := ctx
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
partialDeadline, err := partialDeadline(time.Now(), deadline, len(addrs)-i)
if err != nil {
if firstErr == nil {
firstErr = &net.OpError{Op: "dial", Err: err}
}
break
}
if partialDeadline.Before(deadline) {
var cancel context.CancelFunc
dialCtx, cancel = context.WithDeadline(ctx, partialDeadline)
defer cancel()
}
}
var c net.Conn
switch matches[1] {
case "tcp":
c, err = tnet.DialContextTCPAddrPort(dialCtx, addr)
case "udp":
c, err = tnet.DialUDPAddrPort(netip.AddrPort{}, addr)
case "ping":
err = errors.New("not support")
// c, err = tnet.DialPingAddr(netip.Addr{}, addr.Addr())
}
if err == nil {
return c, nil
}
if firstErr == nil {
firstErr = err
}
}
if firstErr == nil {
firstErr = &net.OpError{Op: "dial", Err: errMissingAddress}
}
return nil, firstErr
}
func (tnet *Net) Dial(network, address string) (net.Conn, error) {
return tnet.DialContext(context.Background(), network, address)
} }
+7 -9
View File
@@ -258,18 +258,16 @@ func (s *Server) Start() error {
return errors.New("address is domain") return errors.New("address is domain")
} }
listenFunc := func() (net.PacketConn, error) { listenFunc := func() (net.PacketConn, error) {
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings) var pktConn net.PacketConn
var err error
if s.streamSettings.FinalMask != nil {
pktConn, err = s.streamSettings.FinalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)})
} else {
pktConn, err = internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
if s.streamSettings.UdpmaskManager != nil {
newConn, err := s.streamSettings.UdpmaskManager.WrapPacketConnServer(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
if s.uplinkCounter != nil || s.downlinkCounter != nil { if s.uplinkCounter != nil || s.downlinkCounter != nil {
pktConn = &PacketCounterConnection{ pktConn = &PacketCounterConnection{
PacketConn: pktConn, PacketConn: pktConn,
+7 -2
View File
@@ -82,9 +82,14 @@ func TestResolveIP(t *testing.T) {
}, },
{ {
Tag: "direct", Tag: "direct",
ProxySettings: serial.ToTypedMessage(&freedom.Config{ SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
DomainStrategy: internet.DomainStrategy_USE_IP, StreamSettings: &internet.StreamConfig{
SocketSettings: &internet.SocketConfig{
DomainStrategy: internet.DomainStrategy_USE_IP,
},
},
}), }),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
}, },
}, },
} }
+9 -7
View File
@@ -92,7 +92,7 @@ func TestPassiveConnection(t *testing.T) {
} }
} }
func TestProxy(t *testing.T) { func TestDialerProxy(t *testing.T) {
tcpServer := tcp.Server{ tcpServer := tcp.Server{
MsgProcessor: xor, MsgProcessor: xor,
} }
@@ -187,8 +187,10 @@ func TestProxy(t *testing.T) {
}, },
}), }),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
ProxySettings: &internet.ProxyConfig{ StreamSettings: &internet.StreamConfig{
Tag: "proxy", SocketSettings: &internet.SocketConfig{
DialerProxy: "proxy",
},
}, },
}), }),
}, },
@@ -218,7 +220,7 @@ func TestProxy(t *testing.T) {
} }
} }
func TestProxyOverKCP(t *testing.T) { func TestDialerProxyOverKCP(t *testing.T) {
tcpServer := tcp.Server{ tcpServer := tcp.Server{
MsgProcessor: xor, MsgProcessor: xor,
} }
@@ -321,11 +323,11 @@ func TestProxyOverKCP(t *testing.T) {
}, },
}), }),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
ProxySettings: &internet.ProxyConfig{
Tag: "proxy",
},
StreamSettings: &internet.StreamConfig{ StreamSettings: &internet.StreamConfig{
ProtocolName: "mkcp", ProtocolName: "mkcp",
SocketSettings: &internet.SocketConfig{
DialerProxy: "proxy",
},
}, },
}), }),
}, },
+468
View File
@@ -0,0 +1,468 @@
package scenarios
import (
"bufio"
"bytes"
"context"
gotls "crypto/tls"
"crypto/x509"
go_errors "errors"
"io"
"net/http"
"net/netip"
"net/url"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"golang.org/x/sync/errgroup"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"github.com/xtls/xray-core/app/log"
"github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/common"
clog "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/protocol/tls/cert"
"github.com/xtls/xray-core/common/serial"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/dokodemo"
"github.com/xtls/xray-core/proxy/masque"
"github.com/xtls/xray-core/proxy/wireguard"
"github.com/xtls/xray-core/testing/servers/tcp"
"github.com/xtls/xray-core/testing/servers/udp"
"github.com/xtls/xray-core/transport/internet"
transmasque "github.com/xtls/xray-core/transport/internet/masque"
"github.com/xtls/xray-core/transport/internet/masque/connectip"
"github.com/xtls/xray-core/transport/internet/tls"
)
var (
masqueServerV4 = netip.MustParseAddr("10.13.0.1")
masqueServerV6 = netip.MustParseAddr("fd13::1")
masqueClientV4 = netip.MustParsePrefix("10.13.0.2/32")
masqueClientV6 = netip.MustParsePrefix("fd13::2/128")
)
const (
masqueEchoPort = 7
masqueAuthorization = "Basic dTpw"
)
func startMasqueServer(t *testing.T, h2 bool) (net.Port, [32]byte) {
dev, _, gstack, err := wireguard.CreateNetTUN([]netip.Addr{masqueServerV4, masqueServerV6}, nil, transmasque.MinPacketSize, false)
common.Must(err)
t.Cleanup(func() { dev.Close() })
for _, addr := range []netip.Addr{masqueServerV4, masqueServerV6} {
proto := ipv4.ProtocolNumber
if addr.Is6() {
proto = ipv6.ProtocolNumber
}
local := tcpip.FullAddress{NIC: 1, Addr: tcpip.AddrFromSlice(addr.AsSlice()), Port: masqueEchoPort}
l, err := gonet.ListenTCP(gstack, local, proto)
common.Must(err)
go func() {
for {
c, err := l.Accept()
if err != nil {
return
}
go func() {
defer c.Close()
b := make([]byte, 2048)
for {
n, err := c.Read(b)
if err != nil {
return
}
if _, err := c.Write(xor(b[:n])); err != nil {
return
}
}
}()
}
}()
u, err := gonet.DialUDP(gstack, &local, nil, proto)
common.Must(err)
go func() {
b := make([]byte, 2048)
for {
n, addr, err := u.ReadFrom(b)
if err != nil {
return
}
u.WriteTo(xor(b[:n]), addr)
}
}()
}
var current atomic.Pointer[connectip.Conn]
go func() {
bufs := [][]byte{make([]byte, transmasque.MinPacketSize)}
sizes := []int{0}
for {
if _, err := dev.Read(bufs, sizes, 0); err != nil {
return
}
if conn := current.Load(); conn != nil {
if icmp, _ := conn.WritePacket(bufs[0][:sizes[0]]); len(icmp) > 0 {
go dev.Write([][]byte{icmp}, 0)
}
}
}
}()
handler := func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != transmasque.DefaultPath {
w.WriteHeader(http.StatusNotFound)
return
}
if r.Header.Get("Authorization") != masqueAuthorization {
w.WriteHeader(http.StatusUnauthorized)
return
}
req, err := connectip.ParseProxyRequest(r)
if err != nil {
var perr *connectip.ProxyRequestParseError
if go_errors.As(err, &perr) {
w.WriteHeader(perr.HTTPStatus)
}
return
}
conn, err := (&connectip.Proxy{}).Proxy(w, req)
if err != nil {
return
}
defer conn.Close()
common.Must(conn.AssignAddresses([]netip.Prefix{masqueClientV4, masqueClientV6}))
common.Must(conn.AdvertiseRoute([]connectip.IPRoute{
{StartIP: netip.IPv4Unspecified(), EndIP: netip.AddrFrom4([4]byte{255, 255, 255, 255})},
{StartIP: netip.IPv6Unspecified(), EndIP: netip.AddrFrom16([16]byte{0: 0xff, 1: 0xff, 2: 0xff, 3: 0xff, 4: 0xff, 5: 0xff, 6: 0xff, 7: 0xff, 8: 0xff, 9: 0xff, 10: 0xff, 11: 0xff, 12: 0xff, 13: 0xff, 14: 0xff, 15: 0xff})},
}))
go func() {
for {
ar, err := conn.ReceiveAddressRequest(context.Background())
if err != nil {
return
}
assigned := make([]netip.Prefix, len(ar.Prefixes))
for i, p := range ar.Prefixes {
if p.Addr().Is4() {
assigned[i] = masqueClientV4
} else {
assigned[i] = masqueClientV6
}
}
ar.Respond(assigned, nil)
}
}()
current.Store(conn)
b := make([]byte, 2048)
for {
n, err := conn.ReadPacket(b)
if err != nil {
if go_errors.Is(err, io.ErrShortBuffer) {
continue
}
return
}
dev.Write([][]byte{b[:n]}, 0)
}
}
certificate, certHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
key := common.Must2(x509.ParsePKCS8PrivateKey(certificate.PrivateKey))
tlsConfig := &gotls.Config{
Certificates: []gotls.Certificate{{Certificate: [][]byte{certificate.Certificate}, PrivateKey: key}},
NextProtos: []string{http3.NextProtoH3},
}
if h2 {
tlsConfig.NextProtos = []string{http2.NextProtoTLS}
ln := common.Must2(gotls.Listen("tcp", "127.0.0.1:0", tlsConfig))
t.Cleanup(func() { ln.Close() })
go serveHTTP2(ln, http.HandlerFunc(handler))
return net.Port(ln.Addr().(*net.TCPAddr).Port), certHash
}
pktConn := common.Must2(net.ListenUDP("udp", &net.UDPAddr{IP: net.LocalHostIP.IP()}))
tr := &quic.Transport{Conn: pktConn}
ln := common.Must2(tr.ListenEarly(tlsConfig, &quic.Config{EnableDatagrams: true, InitialPacketSize: 1350}))
server := &http3.Server{Handler: http.HandlerFunc(handler), EnableDatagrams: true}
go server.ServeListener(ln)
t.Cleanup(func() {
server.Close()
ln.Close()
tr.Close()
pktConn.Close()
})
return net.Port(pktConn.LocalAddr().(*net.UDPAddr).Port), certHash
}
func serveHTTP2(ln net.Listener, handler http.Handler) {
for {
conn, err := ln.Accept()
if err != nil {
return
}
go serveHTTP2Conn(conn, handler)
}
}
type http2ServerConn struct {
mu sync.Mutex
fr *http2.Framer
hbuf bytes.Buffer
henc *hpack.Encoder
}
func (c *http2ServerConn) write(f func(*http2.Framer) error) error {
c.mu.Lock()
defer c.mu.Unlock()
return f(c.fr)
}
func (c *http2ServerConn) writeHeaders(streamID uint32, status int, header http.Header) error {
c.mu.Lock()
defer c.mu.Unlock()
c.hbuf.Reset()
c.henc.WriteField(hpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)})
for k, vv := range header {
for _, v := range vv {
c.henc.WriteField(hpack.HeaderField{Name: strings.ToLower(k), Value: v})
}
}
return c.fr.WriteHeaders(http2.HeadersFrameParam{StreamID: streamID, BlockFragment: c.hbuf.Bytes(), EndHeaders: true})
}
func (c *http2ServerConn) writeData(streamID uint32, endStream bool, data []byte) error {
c.mu.Lock()
defer c.mu.Unlock()
for {
n := min(len(data), 16384)
if err := c.fr.WriteData(streamID, endStream && n == len(data), data[:n]); err != nil {
return err
}
if data = data[n:]; len(data) == 0 {
return nil
}
}
}
func serveHTTP2Conn(conn net.Conn, handler http.Handler) {
defer conn.Close()
br := bufio.NewReader(conn)
preface := make([]byte, len(http2.ClientPreface))
if _, err := io.ReadFull(br, preface); err != nil || string(preface) != http2.ClientPreface {
return
}
sc := &http2ServerConn{fr: http2.NewFramer(conn, br)}
sc.henc = hpack.NewEncoder(&sc.hbuf)
sc.fr.ReadMetaHeaders = hpack.NewDecoder(4096, nil)
if err := sc.write(func(fr *http2.Framer) error {
if err := fr.WriteSettings(
http2.Setting{ID: http2.SettingEnableConnectProtocol, Val: 1},
http2.Setting{ID: http2.SettingInitialWindowSize, Val: 1 << 30},
); err != nil {
return err
}
return fr.WriteWindowUpdate(0, 1<<30)
}); err != nil {
return
}
bodies := make(map[uint32]*io.PipeWriter)
defer func() {
for _, body := range bodies {
body.Close()
}
}()
for {
f, err := sc.fr.ReadFrame()
if err != nil {
return
}
switch f := f.(type) {
case *http2.SettingsFrame:
if !f.IsAck() {
err = sc.write((*http2.Framer).WriteSettingsAck)
}
case *http2.PingFrame:
if !f.IsAck() {
err = sc.write(func(fr *http2.Framer) error { return fr.WritePing(true, f.Data) })
}
case *http2.MetaHeadersFrame:
u, err := url.ParseRequestURI(f.PseudoValue("path"))
if err != nil {
return
}
pr, pw := io.Pipe()
bodies[f.StreamID] = pw
req := &http.Request{
Method: f.PseudoValue("method"),
URL: u,
Proto: "HTTP/2.0",
ProtoMajor: 2,
Header: http.Header{},
Host: f.PseudoValue("authority"),
Body: pr,
}
for _, hf := range f.RegularFields() {
req.Header.Add(hf.Name, hf.Value)
}
if protocol := f.PseudoValue("protocol"); protocol != "" {
req.Header.Set(":protocol", protocol)
}
streamID := f.StreamID
w := &http2ResponseWriter{conn: sc, streamID: streamID, header: http.Header{}}
go func() {
handler.ServeHTTP(w, req)
w.WriteHeader(http.StatusOK)
sc.writeData(streamID, true, nil)
}()
case *http2.DataFrame:
if body := bodies[f.StreamID]; body != nil {
if _, err := body.Write(f.Data()); err != nil || f.StreamEnded() {
body.Close()
delete(bodies, f.StreamID)
}
}
case *http2.RSTStreamFrame:
if body := bodies[f.StreamID]; body != nil {
body.CloseWithError(http2.StreamError{StreamID: f.StreamID, Code: f.ErrCode})
delete(bodies, f.StreamID)
}
}
if err != nil {
return
}
}
}
type http2ResponseWriter struct {
conn *http2ServerConn
streamID uint32
header http.Header
wroteHeader bool
}
func (w *http2ResponseWriter) Header() http.Header { return w.header }
func (w *http2ResponseWriter) WriteHeader(code int) {
if !w.wroteHeader {
w.wroteHeader = true
w.conn.writeHeaders(w.streamID, code, w.header)
}
}
func (w *http2ResponseWriter) Write(b []byte) (int, error) {
w.WriteHeader(http.StatusOK)
if err := w.conn.writeData(w.streamID, false, b); err != nil {
return 0, err
}
return len(b), nil
}
func (w *http2ResponseWriter) Flush() {}
func TestMasque(t *testing.T) {
testMasque(t, false)
}
func TestMasqueHTTP2(t *testing.T) {
testMasque(t, true)
}
func testMasque(t *testing.T, h2 bool) {
serverPort, certHash := startMasqueServer(t, h2)
tcpPort := tcp.PickPort()
tcp6Port := tcp.PickPort()
udpPort := udp.PickPort()
dokodemoTo := func(port net.Port, addr netip.Addr, network net.Network) *core.InboundHandlerConfig {
return &core.InboundHandlerConfig{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(port)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
RewriteAddress: net.NewIPOrDomain(net.IPAddress(addr.AsSlice())),
RewritePort: masqueEchoPort,
AllowedNetworks: []net.Network{network},
}),
}
}
tlsConfig := &tls.Config{
ServerName: "localhost",
PinnedPeerCertSha256: [][]byte{certHash[:]},
}
if h2 {
tlsConfig.NextProtocol = []string{http2.NextProtoTLS}
}
clientConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogLevel: clog.Severity_Debug,
ErrorLogType: log.LogType_Console,
}),
},
Inbound: []*core.InboundHandlerConfig{
dokodemoTo(tcpPort, masqueServerV4, net.Network_TCP),
dokodemoTo(tcp6Port, masqueServerV6, net.Network_TCP),
dokodemoTo(udpPort, masqueServerV4, net.Network_UDP),
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&masque.ClientConfig{
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
ProtocolName: "masque",
TransportSettings: []*internet.TransportConfig{
{
ProtocolName: "masque",
Settings: serial.ToTypedMessage(&transmasque.Config{
Path: transmasque.DefaultPath,
Headers: map[string]string{"Authorization": masqueAuthorization},
}),
},
},
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(tlsConfig),
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(clientConfig)
common.Must(err)
defer CloseAllServers(servers)
var errg errgroup.Group
for range 3 {
errg.Go(testTCPConn(tcpPort, 1024*1024, time.Second*20))
}
errg.Go(testTCPConn(tcp6Port, 1024*1024, time.Second*20))
errg.Go(testUDPConn(udpPort, 1024, time.Second*5))
if err := errg.Wait(); err != nil {
t.Error(err)
}
}
+2 -2
View File
@@ -509,7 +509,7 @@ func TestVlessXtlsVisionReality(t *testing.T) {
// This testing test all known utls fingerprint in tls.PresetFingerprints that support reality (expect unsafe and random*) // This testing test all known utls fingerprint in tls.PresetFingerprints that support reality (expect unsafe and random*)
// Beacuse figerprint support may be broken after utls/reality update // Beacuse figerprint support may be broken after utls/reality update
// Known broken fingerprint: android, 360 // Known working fingerprint: chrome, firefox, safari
func TestVlessRealityFingerprints(t *testing.T) { func TestVlessRealityFingerprints(t *testing.T) {
TestFingerprint := func(fingerprint string) error { TestFingerprint := func(fingerprint string) error {
tcpServer := tcp.Server{ tcpServer := tcp.Server{
@@ -641,7 +641,7 @@ func TestVlessRealityFingerprints(t *testing.T) {
} }
return nil return nil
} }
fingerPrints := []string{"chrome", "firefox", "safari", "ios", "edge", "qq"} fingerPrints := []string{"chrome", "firefox", "safari"}
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(fingerPrints)) wg.Add(len(fingerPrints))
for _, fp := range fingerPrints { for _, fp := range fingerPrints {
+2
View File
@@ -65,6 +65,7 @@ func TestWireguard(t *testing.T) {
ProxySettings: serial.ToTypedMessage(&freedom.Config{ ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}, FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
}), }),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
}, },
}, },
} }
@@ -104,6 +105,7 @@ func TestWireguard(t *testing.T) {
AllowedIps: []string{"0.0.0.0/0", "::0/0"}, AllowedIps: []string{"0.0.0.0/0", "::0/0"},
}}, }},
}), }),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
}, },
}, },
} }

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