Compare commits

..
Author SHA1 Message Date
21c4fb34cf Add tests for TUN system stack (TCP/UDP)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-09-21 02:42:18 +00:00
05921d1755 Implement lean "system" TCP/IP stack backend for TUN inbound
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-09-21 02:37:52 +00:00
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 AsaadiandGitHub 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
LjhAUMEMandRPRX 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
LjhAUMEMandGitHub 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]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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
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
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
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
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
RPRXandGitHub 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
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
patternihaandGitHub 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
风扇滑翔翼andGitHub 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
风扇滑翔翼andGitHub 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
RPRXandGitHub 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
RPRXandGitHub 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
MeowandGitHub 3e2f040cd8 Direct/Freedom outbound: Better Compatibility (#6058)
Fixes https://github.com/XTLS/Xray-core/issues/6393
2026-09-08 03:31:25 +00:00
LjhAUMEMandGitHub 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
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 AsaadiandGitHub 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
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
XXcipherXandGitHub 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]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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
pexcnandGitHub 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
patternihaandGitHub 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
YounesandRPRX 8ee131cbbb HTTP inbound: Fix a potential panic in readResponseAndHandle100Continue() (#6545) 2026-09-01 02:59:56 +03:00
Jorik_SmithandGitHub 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
128 changed files with 9818 additions and 2689 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
+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;
+8 -54
View File
@@ -15,7 +15,6 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/mux"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
@@ -26,8 +25,6 @@ import (
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
"github.com/xtls/xray-core/transport/pipe"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -63,7 +60,6 @@ type Handler struct {
streamSettings *internet.MemoryStreamConfig streamSettings *internet.MemoryStreamConfig
proxyConfig proto.Message proxyConfig proto.Message
proxy proxy.Outbound proxy proxy.Outbound
outboundManager outbound.Manager
mux *mux.ClientManager mux *mux.ClientManager
xudp *mux.ClientManager xudp *mux.ClientManager
udp443 string udp443 string
@@ -77,7 +73,6 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag) uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
h := &Handler{ h := &Handler{
tag: config.Tag, tag: config.Tag,
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
uplinkCounter: uplinkCounter, uplinkCounter: uplinkCounter,
downlinkCounter: downlinkCounter, downlinkCounter: downlinkCounter,
} }
@@ -108,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))])
@@ -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 {
if h.senderSettings.ProxySettings.HasTag() {
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) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob) 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
} }
} }
} }
+10
View File
@@ -6,6 +6,7 @@ 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()
} }
+60 -51
View File
@@ -1,16 +1,13 @@
package http package http
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"errors" "errors"
"io"
"net/http"
"strings" "strings"
"unsafe"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
) )
@@ -42,67 +39,79 @@ func (h *SniffHeader) Domain() string {
} }
var ( var (
validMethods = map[string]bool{} methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect"}
errNotHTTP = errors.New("not an HTTP request")
errNotHTTPMethod = errors.New("not an HTTP method")
) )
func init() { func beginWithHTTPMethod(b []byte) error {
// https://www.iana.org/assignments/http-methods for _, m := range &methods {
methods := []string{ if len(b) >= len(m) && strings.EqualFold(string(b[:len(m)]), m) {
"ACL", "BASELINE-CONTROL", "BIND", "CHECKIN", "CHECKOUT", return nil
"CONNECT", "COPY", "DELETE", "GET", "HEAD",
"LABEL", "LINK", "LOCK", "MERGE", "MKACTIVITY",
"MKCALENDAR", "MKCOL", "MKREDIRECTREF", "MKWORKSPACE", "MOVE",
"OPTIONS", "ORDERPATCH", "PATCH", "POST", "PRI",
"PROPFIND", "PROPPATCH", "PUT", "QUERY", "REBIND",
"REPORT", "SEARCH", "TRACE", "UNBIND", "UNCHECKOUT",
"UNLINK", "UNLOCK", "UPDATE", "UPDATEREDIRECTREF", "VERSION-CONTROL",
} }
for _, m := range methods {
validMethods[m] = true if len(b) < len(m) {
return common.ErrNoClue
} }
} }
func isValidHTTPMethod(b []byte) bool { return errNotHTTPMethod
if len(b) == 0 {
return false
}
idx := bytes.IndexByte(b, ' ')
if idx == -1 {
return false
}
method := unsafe.String(unsafe.SliceData(b), idx)
return validMethods[method]
} }
func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) { func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {
if !isValidHTTPMethod(b) {
return nil, errNotHTTP
}
content := session.ContentFromContext(c) content := session.ContentFromContext(c)
r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) ShouldSniffAttr := true
if err != nil {
if err == io.ErrUnexpectedEOF {
return nil, common.ErrNoClue
}
return nil, errNotHTTP
}
if r.Host == "" {
return nil, common.ErrNoClue
}
sh := &SniffHeader{
version: HTTP1,
host: r.Host,
}
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode. // If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
// It will set attributes, so skip it. // It will set attributes, so skip it.
if content != nil && len(content.Attributes) == 0 { if content == nil || len(content.Attributes) != 0 {
for key, h := range r.Header { ShouldSniffAttr = false
content.Attributes[key] = strings.Join(h, ",")
} }
content.Attributes[":method"] = r.Method if err := beginWithHTTPMethod(b); err != nil {
content.Attributes[":path"] = r.URL.Path return nil, err
} }
sh := &SniffHeader{
version: HTTP1,
}
headers := bytes.Split(b, []byte{'\n'})
for i := 1; i < len(headers); i++ {
header := headers[i]
if len(header) == 0 {
break
}
parts := bytes.SplitN(header, []byte{':'}, 2)
if len(parts) != 2 {
continue
}
key := strings.ToLower(string(parts[0]))
value := string(bytes.TrimSpace(parts[1]))
if ShouldSniffAttr {
content.SetAttribute(key, value) // Put header in attribute
}
if key == "host" {
rawHost := strings.ToLower(value)
dest, err := ParseHost(rawHost, net.Port(80))
if err != nil {
return nil, err
}
sh.host = dest.Address.String()
}
}
// Parse request line
// Request line is like this
// "GET /homo/114514 HTTP/1.1"
if len(headers) > 0 && ShouldSniffAttr {
RequestLineParts := bytes.Split(headers[0], []byte{' '})
if len(RequestLineParts) == 3 {
content.SetAttribute(":method", string(RequestLineParts[0]))
content.SetAttribute(":path", string(RequestLineParts[1]))
}
}
if len(sh.host) > 0 {
return sh, nil return sh, nil
} }
return nil, common.ErrNoClue
}
+55 -57
View File
@@ -14,76 +14,75 @@ func TestHTTPHeaders(t *testing.T) {
err bool err bool
}{ }{
{ {
input: "GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1\r\n" + input: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1
"Host: net.tutsplus.com\r\n" + Host: net.tutsplus.com
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" + User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
"Accept-Language: en-us,en;q=0.5\r\n" + Accept-Language: en-us,en;q=0.5
"Accept-Encoding: gzip,deflate\r\n" + Accept-Encoding: gzip,deflate
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
"Keep-Alive: 300\r\n" + Keep-Alive: 300
"Connection: keep-alive\r\n" + Connection: keep-alive
"Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120\r\n" + Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120
"Pragma: no-cache\r\n" + Pragma: no-cache
"Cache-Control: no-cache\r\n" + Cache-Control: no-cache`,
"\r\n",
domain: "net.tutsplus.com", domain: "net.tutsplus.com",
}, },
{ {
input: "POST /foo.php HTTP/1.1\r\n" + input: `POST /foo.php HTTP/1.1
"Host: localhost\r\n" + Host: localhost
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" + User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
"Accept-Language: en-us,en;q=0.5\r\n" + Accept-Language: en-us,en;q=0.5
"Accept-Encoding: gzip,deflate\r\n" + Accept-Encoding: gzip,deflate
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
"Keep-Alive: 300\r\n" + Keep-Alive: 300
"Connection: keep-alive\r\n" + Connection: keep-alive
"Referer: http://localhost/test.php\r\n" + Referer: http://localhost/test.php
"Content-Type: application/x-www-form-urlencoded\r\n" + Content-Type: application/x-www-form-urlencoded
"Content-Length: 43\r\n" + Content-Length: 43
"\r\n" +
"first_name=John&last_name=Doe&action=Submit", first_name=John&last_name=Doe&action=Submit`,
domain: "localhost", domain: "localhost",
}, },
{ {
input: "X /foo.php HTTP/1.1\r\n" + input: `X /foo.php HTTP/1.1
"Host: localhost\r\n" + Host: localhost
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" + User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
"Accept-Language: en-us,en;q=0.5\r\n" + Accept-Language: en-us,en;q=0.5
"Accept-Encoding: gzip,deflate\r\n" + Accept-Encoding: gzip,deflate
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
"Keep-Alive: 300\r\n" + Keep-Alive: 300
"Connection: keep-alive\r\n" + Connection: keep-alive
"Referer: http://localhost/test.php\r\n" + Referer: http://localhost/test.php
"Content-Type: application/x-www-form-urlencoded\r\n" + Content-Type: application/x-www-form-urlencoded
"Content-Length: 43\r\n" + Content-Length: 43
"\r\n" +
"first_name=John&last_name=Doe&action=Submit", first_name=John&last_name=Doe&action=Submit`,
domain: "", domain: "",
err: true, err: true,
}, },
{ {
input: "GET /foo.php HTTP/1.1\r\n" + input: `GET /foo.php HTTP/1.1
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" + User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
"Accept-Language: en-us,en;q=0.5\r\n" + Accept-Language: en-us,en;q=0.5
"Accept-Encoding: gzip,deflate\r\n" + Accept-Encoding: gzip,deflate
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
"Keep-Alive: 300\r\n" + Keep-Alive: 300
"Connection: keep-alive\r\n" + Connection: keep-alive
"Referer: http://localhost/test.php\r\n" + Referer: http://localhost/test.php
"Content-Type: application/x-www-form-urlencoded\r\n" + Content-Type: application/x-www-form-urlencoded
"Content-Length: 43\r\n" + Content-Length: 43
"\r\n" +
"Host: localhost\r\n" + Host: localhost
"first_name=John&last_name=Doe&action=Submit", first_name=John&last_name=Doe&action=Submit`,
domain: "", domain: "",
err: true, err: true,
}, },
{ {
input: "GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1\r\n", input: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1`,
domain: "", domain: "",
err: true, err: true,
}, },
@@ -98,7 +97,6 @@ func TestHTTPHeaders(t *testing.T) {
} else { } else {
if err != nil { if err != nil {
t.Errorf("Expect no error but actually %s in test %v", err.Error(), test) t.Errorf("Expect no error but actually %s in test %v", err.Error(), test)
continue
} }
if header.Domain() != test.domain { if header.Domain() != test.domain {
t.Error("expected domain ", test.domain, " but got ", header.Domain()) t.Error("expected domain ", test.domain, " but got ", header.Domain())
-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
@@ -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 (
+10 -9
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.0.1
google.golang.org/grpc v1.83.1 google.golang.org/grpc v1.83.2
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
golang.org/x/tools v0.49.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
+22 -17
View File
@@ -6,13 +6,14 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.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-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 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/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
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 +74,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,8 +88,8 @@ 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 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
@@ -110,8 +111,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 +121,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 +134,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=
@@ -160,8 +163,8 @@ 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-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
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-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
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 +179,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=
+18 -28
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)
switch responseName {
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 { if err != nil {
return nil, errors.New("Config: Failed to parse Blackhole response config.").Base(err) return nil, errors.New("failed to decode custom response data: " + err.Error())
} }
responseSettings, err := response.(Buildable).Build() config.Response = &blackhole.Response{Type: "custom", CustomResponseData: data}
if err != nil { default:
return nil, err return nil, errors.New("unknown blackhole response: " + responseName)
} }
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"),
},
},
},
})
}
+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"`
+13 -27
View File
@@ -36,6 +36,8 @@ 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 "xdrive":
return "xdrive", nil
default: default:
return "", errors.New("Config: unknown transport protocol: ", p) return "", errors.New("Config: unknown transport protocol: ", p)
} }
@@ -59,6 +61,7 @@ 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"`
XDRIVESettings *XDriveConfig `json:"xdriveSettings"`
SocketSettings *SocketConfig `json:"sockopt"` SocketSettings *SocketConfig `json:"sockopt"`
} }
@@ -192,6 +195,16 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
Settings: serial.ToTypedMessage(hs), Settings: serial.ToTypedMessage(hs),
}) })
} }
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 +266,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,11 +299,6 @@ 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{
Ports: c.FinalMask.QuicParams.UdpHop.PortList.Build().Ports(),
IntervalMin: int64(c.FinalMask.QuicParams.UdpHop.Interval.From),
IntervalMax: int64(c.FinalMask.QuicParams.UdpHop.Interval.To),
},
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow, InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow, MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow, InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
@@ -312,21 +316,3 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
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
}
+52 -24
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"
@@ -24,6 +23,7 @@ import (
"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"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -534,10 +534,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 +556,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 +735,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"`
@@ -762,12 +753,6 @@ 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 +762,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")
} }
@@ -814,3 +795,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")
}
}
+7
View File
@@ -20,6 +20,7 @@ type TunConfig struct {
UserLevel uint32 `json:"userLevel"` UserLevel uint32 `json:"userLevel"`
AutoSystemRoutingTable []string `json:"autoSystemRoutingTable"` AutoSystemRoutingTable []string `json:"autoSystemRoutingTable"`
AutoOutboundsInterface *string `json:"autoOutboundsInterface"` AutoOutboundsInterface *string `json:"autoOutboundsInterface"`
Stack string `json:"stack"`
} }
func (v *TunConfig) Build() (proto.Message, error) { func (v *TunConfig) Build() (proto.Message, error) {
@@ -31,6 +32,7 @@ func (v *TunConfig) Build() (proto.Message, error) {
DNS: v.DNS, DNS: v.DNS,
UserLevel: v.UserLevel, UserLevel: v.UserLevel,
AutoSystemRoutingTable: v.AutoSystemRoutingTable, AutoSystemRoutingTable: v.AutoSystemRoutingTable,
Stack: v.Stack,
} }
if v.AutoOutboundsInterface != nil { if v.AutoOutboundsInterface != nil {
config.AutoOutboundsInterface = *v.AutoOutboundsInterface config.AutoOutboundsInterface = *v.AutoOutboundsInterface
@@ -52,6 +54,11 @@ func (v *TunConfig) Build() (proto.Message, error) {
if config.MTU == 0 { if config.MTU == 0 {
config.MTU = 1500 config.MTU = 1500
} }
switch config.Stack {
case "", "gvisor", "system":
default:
return nil, fmt.Errorf("unknown tun stack: %s (must be \"gvisor\" or \"system\")", config.Stack)
}
return config, nil return config, nil
} }
+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"`)
} }
-16
View File
@@ -65,7 +65,6 @@ type WireGuardConfig struct {
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"`
} }
@@ -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
+32 -35
View File
@@ -15,6 +15,7 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
core "github.com/xtls/xray-core/core" core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/freedom"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
) )
@@ -216,21 +217,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 +242,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 +258,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 +289,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 +314,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 +338,31 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
return nil, err return nil, err
} }
if fc, ok := ts.(*freedom.Config); ok {
if senderSettings.StreamSettings != nil &&
senderSettings.StreamSettings.SocketSettings != nil &&
senderSettings.StreamSettings.SocketSettings.AddressPortStrategy != internet.AddressPortStrategy_None {
return nil, errors.New(`freedom outbound does not support "sockopt.addressPortStrategy"`)
}
var strategy internet.DomainStrategy
if strategy = senderSettings.TargetStrategy; strategy != internet.DomainStrategy_AS_IS {
errors.LogWarning(context.Background(), `The "outbound.targetStrategy" setting is not supported directly by freedom and has been automatically migrated to "sockopt.domainStrategy" with no behavior change.`)
senderSettings.TargetStrategy = internet.DomainStrategy_AS_IS
} else if strategy = fc.DomainStrategy; strategy != internet.DomainStrategy_AS_IS {
errors.LogWarning(context.Background(), `The "freedom.domainStrategy" setting is deprecated and will be removed. For compatibility, its value has been automatically migrated to "sockopt.domainStrategy". Please update your config before removal.`)
}
if strategy != internet.DomainStrategy_AS_IS {
if senderSettings.StreamSettings == nil {
senderSettings.StreamSettings = &internet.StreamConfig{}
}
if senderSettings.StreamSettings.SocketSettings == nil {
senderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{}
}
senderSettings.StreamSettings.SocketSettings.DomainStrategy = strategy
}
}
return &core.OutboundHandlerConfig{ return &core.OutboundHandlerConfig{
SenderSettings: serial.ToTypedMessage(senderSettings), SenderSettings: serial.ToTypedMessage(senderSettings),
Tag: c.Tag, Tag: c.Tag,
+270 -136
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 {
switch fields[0] {
case "module":
modPath = fields[1]
case "go":
langVersion = "go" + strings.TrimPrefix(fields[1], "go")
} }
return filepath.Join(dir, "go", "env"), nil }
}
return modPath, langVersion, nil
} }
// GetRuntimeEnv returns the value of runtime environment variable, func formatGoSource(src []byte, opts format.Options) ([]byte, error) {
// that is set by running following command: `go env -w key=value`. return format.Source(src, opts)
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
} }
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
}
+1
View File
@@ -60,6 +60,7 @@ import (
_ "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
}
+34 -56
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"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
CustomResponseData []byte `protobuf:"bytes,2,opt,name=custom_response_data,json=customResponseData,proto3" json:"custom_response_data,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache 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 ""
}
return mi.MessageOf(x)
} }
// 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)
} }
} }
+91 -97
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 {
@@ -92,6 +96,8 @@ 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
}
} 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
domain := dialDest.Address.Domain()
var ips []net.IP
if firstResolve {
firstResolve = false
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 { for _, ip := range ips {
blockedDest = &dialDest 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 blockedRule = rule
return nil 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 {
if rule := h.matchFinalRule(destination.Network, destination.Address, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &destination
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
} }
+1 -1
View File
@@ -332,7 +332,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())
}
}
+3 -3
View File
@@ -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
} }
+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())
+11 -2
View File
@@ -32,6 +32,7 @@ type Config struct {
AutoSystemRoutingTable []string `protobuf:"bytes,6,rep,name=auto_system_routing_table,json=autoSystemRoutingTable,proto3" json:"auto_system_routing_table,omitempty"` AutoSystemRoutingTable []string `protobuf:"bytes,6,rep,name=auto_system_routing_table,json=autoSystemRoutingTable,proto3" json:"auto_system_routing_table,omitempty"`
AutoOutboundsInterface string `protobuf:"bytes,7,opt,name=auto_outbounds_interface,json=autoOutboundsInterface,proto3" json:"auto_outbounds_interface,omitempty"` AutoOutboundsInterface string `protobuf:"bytes,7,opt,name=auto_outbounds_interface,json=autoOutboundsInterface,proto3" json:"auto_outbounds_interface,omitempty"`
Desc string `protobuf:"bytes,8,opt,name=desc,proto3" json:"desc,omitempty"` Desc string `protobuf:"bytes,8,opt,name=desc,proto3" json:"desc,omitempty"`
Stack string `protobuf:"bytes,9,opt,name=stack,proto3" json:"stack,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -122,11 +123,18 @@ func (x *Config) GetDesc() string {
return "" return ""
} }
func (x *Config) GetStack() string {
if x != nil {
return x.Stack
}
return ""
}
var File_proxy_tun_config_proto protoreflect.FileDescriptor var File_proxy_tun_config_proto protoreflect.FileDescriptor
const file_proxy_tun_config_proto_rawDesc = "" + const file_proxy_tun_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\x82\x02\n" + "\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\x98\x02\n" +
"\x06Config\x12\x12\n" + "\x06Config\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" +
"\x03MTU\x18\x02 \x01(\rR\x03MTU\x12\x18\n" + "\x03MTU\x18\x02 \x01(\rR\x03MTU\x12\x18\n" +
@@ -136,7 +144,8 @@ const file_proxy_tun_config_proto_rawDesc = "" +
"user_level\x18\x05 \x01(\rR\tuserLevel\x129\n" + "user_level\x18\x05 \x01(\rR\tuserLevel\x129\n" +
"\x19auto_system_routing_table\x18\x06 \x03(\tR\x16autoSystemRoutingTable\x128\n" + "\x19auto_system_routing_table\x18\x06 \x03(\tR\x16autoSystemRoutingTable\x128\n" +
"\x18auto_outbounds_interface\x18\a \x01(\tR\x16autoOutboundsInterface\x12\x12\n" + "\x18auto_outbounds_interface\x18\a \x01(\tR\x16autoOutboundsInterface\x12\x12\n" +
"\x04desc\x18\b \x01(\tR\x04descBL\n" + "\x04desc\x18\b \x01(\tR\x04desc\x12\x14\n" +
"\x05stack\x18\t \x01(\tR\x05stackBL\n" +
"\x12com.xray.proxy.tunP\x01Z#github.com/xtls/xray-core/proxy/tun\xaa\x02\x0eXray.Proxy.Tunb\x06proto3" "\x12com.xray.proxy.tunP\x01Z#github.com/xtls/xray-core/proxy/tun\xaa\x02\x0eXray.Proxy.Tunb\x06proto3"
var ( var (
+1
View File
@@ -15,4 +15,5 @@ message Config {
repeated string auto_system_routing_table = 6; repeated string auto_system_routing_table = 6;
string auto_outbounds_interface = 7; string auto_outbounds_interface = 7;
string desc = 8; string desc = 8;
string stack = 9;
} }
+35 -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)
@@ -124,7 +143,9 @@ func (t *Handler) Start() error {
tunStackOptions := StackOptions{ tunStackOptions := StackOptions{
Tun: tunInterface, Tun: tunInterface,
MTU: t.config.MTU,
IdleTimeout: t.policyManager.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle, IdleTimeout: t.policyManager.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,
Backend: t.config.Stack,
} }
tunStack, err := NewStack(t.ctx, tunStackOptions, t) tunStack, err := NewStack(t.ctx, tunStackOptions, t)
if err != nil { if err != nil {
@@ -171,7 +192,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 +225,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))
+37
View File
@@ -1,7 +1,10 @@
package tun package tun
import ( import (
"context"
"time" "time"
"github.com/xtls/xray-core/common/errors"
) )
// Stack interface implement ip protocol stack, bridging raw network packets and data streams // Stack interface implement ip protocol stack, bridging raw network packets and data streams
@@ -13,5 +16,39 @@ type Stack interface {
// StackOptions for the stack implementation // StackOptions for the stack implementation
type StackOptions struct { type StackOptions struct {
Tun Tun Tun Tun
MTU uint32
IdleTimeout time.Duration IdleTimeout time.Duration
// Backend selects the concrete Stack implementation, see NewStack.
Backend string
}
const (
// StackGVisor selects the full-featured gVisor based stack (default).
StackGVisor = "gvisor"
// StackSystem selects the lightweight, Xray-native stack, see newSystemStack.
StackSystem = "system"
)
// NewStack builds the ip stack selected by options.Backend.
//
// gVisor (the default/"gvisor" backend) is a general purpose stack, built
// with the semantics needed for a real, lossy public network in mind:
// congestion control, SACK/RACK loss recovery, retransmission timers, etc.
// TUN traffic instead travels over a local, kernel-to-userspace channel that
// neither reorders nor drops packets in normal operation, so none of that
// complexity is actually required to shuffle bytes between it and the
// dispatcher. The "system" backend trades gVisor's generality for a much
// smaller, more direct code path tailored to that trusted, in-order channel:
// no congestion control, no SACK/RACK, minimal buffering, and a plain RTO
// timer as a safety net for the rare real loss, rather than a full
// re-implementation of one. See stack_system.go for details.
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
switch options.Backend {
case "", StackGVisor:
return newGVisorStack(ctx, options, handler)
case StackSystem:
return newSystemStack(ctx, options, handler)
default:
return nil, errors.New("unknown tun stack: ", options.Backend)
}
} }
+2 -2
View File
@@ -42,8 +42,8 @@ type stackGVisor struct {
endpoint stack.LinkEndpoint endpoint stack.LinkEndpoint
} }
// NewStack builds new ip stack (using gVisor) // newGVisorStack builds new ip stack (using gVisor)
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) { func newGVisorStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
gStack := &stackGVisor{ gStack := &stackGVisor{
ctx: ctx, ctx: ctx,
tun: options.Tun, tun: options.Tun,
+370
View File
@@ -0,0 +1,370 @@
package tun
import (
"context"
"crypto/rand"
"encoding/binary"
"errors"
"sync"
"time"
xerrors "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
tunicmp "github.com/xtls/xray-core/proxy/tun/icmp"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// stackSystem is the lightweight, Xray-native ip stack, see NewStack.
//
// It reads and parses IPv4/IPv6 packets directly off the tun device (through
// the GVisorDevice interface, already implemented for every supported
// platform), without involving gVisor's stack.Stack, NIC or routing
// machinery. UDP and ICMP echo reuse the exact same handlers as the gVisor
// backend (udpConnectionHandler, tun/icmp) since those were already
// implemented in terms of raw bytes. TCP is handled by a small dedicated
// state machine, see stack_system_tcp.go.
type stackSystem struct {
ctx context.Context
device GVisorDevice
mtu uint32
idleTimeout time.Duration
// handler is stored as the narrower ConnectionHandler interface (which
// *Handler satisfies) rather than *Handler itself, so the stack can be
// exercised in tests with a lightweight fake, the same way stack_system_test.go does.
handler ConnectionHandler
udp *udpConnectionHandler
tcpMu sync.Mutex
tcp map[tcpKey]*tcpConn
cancel context.CancelFunc
}
const systemStackDefaultMTU = 1500
// newSystemStack builds the lightweight "system" ip stack, see NewStack.
func newSystemStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
device, ok := options.Tun.(GVisorDevice)
if !ok {
return nil, xerrors.New("tun stack \"system\" is not supported by this tun device")
}
mtu := options.MTU
if mtu == 0 {
mtu = systemStackDefaultMTU
}
return &stackSystem{
ctx: ctx,
device: device,
mtu: mtu,
idleTimeout: options.IdleTimeout,
handler: handler,
tcp: make(map[tcpKey]*tcpConn),
}, nil
}
// Start is called by Handler to bring the stack to life
func (s *stackSystem) Start() error {
ctx, cancel := context.WithCancel(s.ctx)
s.cancel = cancel
s.udp = newUdpConnectionHandler(s.handler.HandleConnection, s.writeRawUDPPacket)
go s.dispatchLoop(ctx)
go s.idleReapLoop(ctx)
return nil
}
// Close is called by Handler to shut down the stack
func (s *stackSystem) Close() error {
if s.cancel != nil {
s.cancel()
}
s.tcpMu.Lock()
conns := make([]*tcpConn, 0, len(s.tcp))
for _, c := range s.tcp {
conns = append(conns, c)
}
s.tcp = make(map[tcpKey]*tcpConn)
s.tcpMu.Unlock()
for _, c := range conns {
c.abort(errStackClosed)
}
return nil
}
// dispatchLoop reads and demultiplexes packets off the tun device, until ctx
// is cancelled or the device fails permanently. It mirrors LinkEndpoint's own
// dispatchLoop (stack_gvisor_endpoint.go), reusing the exact same GVisorDevice
// contract, but hands packets to this file's own IPv4/IPv6 parsing instead of
// gVisor's NIC/stack.Stack.
func (s *stackSystem) dispatchLoop(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
}
version, packet, err := s.device.ReadPacket()
if err != nil {
if errors.Is(err, ErrQueueEmpty) {
s.device.Wait()
continue
}
return
}
s.handlePacket(version, packet)
packet.DecRef()
}
}
func (s *stackSystem) handlePacket(version byte, packet *stack.PacketBuffer) {
data := concatSlices(packet.AsSlices())
if len(data) == 0 {
return
}
switch version {
case 4:
s.handleIPv4(data)
case 6:
s.handleIPv6(data)
}
}
func concatSlices(slices [][]byte) []byte {
if len(slices) == 1 {
return slices[0]
}
total := 0
for _, sl := range slices {
total += len(sl)
}
if total == 0 {
return nil
}
data := make([]byte, 0, total)
for _, sl := range slices {
data = append(data, sl...)
}
return data
}
func (s *stackSystem) handleIPv4(data []byte) {
hdr := header.IPv4(data)
if !hdr.IsValid(len(data)) {
return
}
// fragmentation is not supported: the tun MTU is expected to keep locally
// generated packets from ever needing it, same as the gVisor backend's
// default configuration
if hdr.More() || hdr.FragmentOffset() != 0 {
return
}
s.handleTransport(header.IPv4ProtocolNumber, hdr.TransportProtocol(), hdr.SourceAddress(), hdr.DestinationAddress(), hdr.Payload())
}
func (s *stackSystem) handleIPv6(data []byte) {
hdr := header.IPv6(data)
if !hdr.IsValid(len(data)) {
return
}
// only directly-encapsulated transport headers are handled, IPv6
// extension headers (rare for ordinary locally generated traffic) are not
// walked, same limitation as the fragmentation one above
s.handleTransport(header.IPv6ProtocolNumber, hdr.TransportProtocol(), hdr.SourceAddress(), hdr.DestinationAddress(), hdr.Payload())
}
func (s *stackSystem) handleTransport(netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, srcIP, dstIP tcpip.Address, payload []byte) {
switch transProto {
case header.TCPProtocolNumber:
s.handleTCP(netProto, srcIP, dstIP, payload)
case header.UDPProtocolNumber:
s.handleUDP(netProto, srcIP, dstIP, payload)
case header.ICMPv4ProtocolNumber:
if netProto == header.IPv4ProtocolNumber {
s.handleICMP(netProto, srcIP, dstIP, payload)
}
case header.ICMPv6ProtocolNumber:
if netProto == header.IPv6ProtocolNumber {
s.handleICMP(netProto, srcIP, dstIP, payload)
}
}
}
func (s *stackSystem) handleUDP(netProto tcpip.NetworkProtocolNumber, srcIP, dstIP tcpip.Address, payload []byte) {
if len(payload) < header.UDPMinimumSize {
return
}
udpHdr := header.UDP(payload)
length := udpHdr.Length()
if int(length) < header.UDPMinimumSize || int(length) > len(payload) {
return
}
// source/destination of the packet we process as incoming are, in other terms,
// src is the side behind tun, dst is the side behind the dispatcher
src := net.UDPDestination(net.IPAddress(srcIP.AsSlice()), net.Port(udpHdr.SourcePort()))
dst := net.UDPDestination(net.IPAddress(dstIP.AsSlice()), net.Port(udpHdr.DestinationPort()))
s.udp.HandlePacket(src, dst, payload[header.UDPMinimumSize:length])
}
func (s *stackSystem) handleICMP(netProto tcpip.NetworkProtocolNumber, srcIP, dstIP tcpip.Address, message []byte) {
ident, sequence, ok := tunicmp.ParseEchoRequest(netProto, message)
if !ok {
return
}
reply, err := tunicmp.BuildLocalEchoReply(netProto, message, dstIP, srcIP)
if err != nil {
xerrors.LogInfoInner(s.ctx, err, "[tun] failed to build local icmp echo reply")
return
}
xerrors.LogDebug(s.ctx, "[tun][icmp] ", tunicmp.ProtocolLabel(netProto), " local echo reply ", dstIP, " -> ", srcIP, " id=", ident, " seq=", sequence)
transProto := header.ICMPv4ProtocolNumber
if netProto == header.IPv6ProtocolNumber {
transProto = header.ICMPv6ProtocolNumber
}
if err := s.writeTransportSegment(netProto, tcpip.TransportProtocolNumber(transProto), dstIP, srcIP, reply); err != nil {
xerrors.LogInfoInner(s.ctx, err, "[tun] failed to write local icmp echo reply")
}
}
func (s *stackSystem) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error {
udpLen := header.UDPMinimumSize + len(payload)
srcIP := tcpip.AddrFromSlice(src.Address.IP())
dstIP := tcpip.AddrFromSlice(dst.Address.IP())
netProto := header.IPv4ProtocolNumber
if !dst.Address.Family().IsIPv4() {
netProto = header.IPv6ProtocolNumber
}
segment := make([]byte, udpLen)
udpHdr := header.UDP(segment)
udpHdr.Encode(&header.UDPFields{
SrcPort: uint16(src.Port),
DstPort: uint16(dst.Port),
Length: uint16(udpLen),
})
copy(segment[header.UDPMinimumSize:], payload)
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
return s.writeTransportSegment(netProto, header.UDPProtocolNumber, srcIP, dstIP, segment)
}
// writeTransportSegment wraps a fully built, already checksummed transport
// layer segment (UDP, ICMP or TCP) with an IP header and writes it to the tun
// device.
func (s *stackSystem) writeTransportSegment(netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, srcIP, dstIP tcpip.Address, segment []byte) error {
ipHdrSize := header.IPv4MinimumSize
if netProto == header.IPv6ProtocolNumber {
ipHdrSize = header.IPv6MinimumSize
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: ipHdrSize,
Payload: buffer.MakeWithData(segment),
})
defer pkt.DecRef()
if netProto == header.IPv4ProtocolNumber {
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
ipHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(header.IPv4MinimumSize + len(segment)),
TTL: 64,
Protocol: uint8(transProto),
SrcAddr: srcIP,
DstAddr: dstIP,
})
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
} else {
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
ipHdr.Encode(&header.IPv6Fields{
PayloadLength: uint16(len(segment)),
TransportProtocol: transProto,
HopLimit: 64,
SrcAddr: srcIP,
DstAddr: dstIP,
})
}
if err := s.device.WritePacket(pkt); err != nil {
return xerrors.New("failed to write raw packet: ", err.String())
}
return nil
}
// idleReapLoop periodically aborts tcp connections that have seen no traffic
// for longer than idleTimeout, finally putting that option to use (it was
// tracked but never read anywhere before the "system" backend existed).
func (s *stackSystem) idleReapLoop(ctx context.Context) {
if s.idleTimeout <= 0 {
return
}
interval := s.idleTimeout / 4
if interval < time.Second {
interval = time.Second
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.reapIdleConnections()
}
}
}
func (s *stackSystem) reapIdleConnections() {
deadline := time.Now().Add(-s.idleTimeout)
s.tcpMu.Lock()
var idle []*tcpConn
for _, c := range s.tcp {
if c.lastActiveTime().Before(deadline) {
idle = append(idle, c)
}
}
s.tcpMu.Unlock()
for _, c := range idle {
c.abort(errConnIdleTimeout)
}
}
func (s *stackSystem) removeTCPConn(key tcpKey, c *tcpConn) {
s.tcpMu.Lock()
if existing, ok := s.tcp[key]; ok && existing == c {
delete(s.tcp, key)
}
s.tcpMu.Unlock()
}
// randomSequenceNumber returns a random initial sequence number for a new
// connection. It doesn't need to be cryptographically unpredictable (the tun
// channel is local and trusted), just varied enough to avoid confusion with
// prior incarnations of the same 4-tuple.
func randomSequenceNumber() seqnum.Value {
var b [4]byte
_, _ = rand.Read(b[:])
return seqnum.Value(binary.BigEndian.Uint32(b[:]))
}
+725
View File
@@ -0,0 +1,725 @@
package tun
import (
"io"
"sync"
"time"
xerrors "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
)
// This file implements a small, dedicated TCP state machine for the "system"
// tun stack, see stack_system.go. It intentionally does not implement window
// scaling, SACK, timestamps, congestion control, fast retransmit or
// out-of-order reassembly: the tun channel only ever carries packets produced
// by the local OS network stack and handed to us directly, so it neither
// reorders nor drops them the way the public internet does; a single RTO
// timer (also used for zero-window probing) is enough to make the connection
// robust against the rare occasions a segment does not make it through.
const (
minRTO = 300 * time.Millisecond
maxRTO = 30 * time.Second
maxRTORetries = 12
lingerDuration = 5 * time.Second
// maxSendBuffer/maxRecvBuffer match the gVisor backend's own default
// buffer sizes (tcp.DefaultSendBufferSize/DefaultReceiveBufferSize), so
// switching between backends does not change buffering expectations.
maxSendBuffer = 1 << 20
maxRecvBuffer = 1 << 20
)
var (
errStackClosed = xerrors.New("tun stack closed")
errConnReset = xerrors.New("connection reset by peer")
errConnClosed = xerrors.New("use of closed network connection")
errConnIdleTimeout = xerrors.New("connection idle timeout")
errConnTimedOut = xerrors.New("connection timed out")
)
type tcpState uint8
const (
stateSynRcvd tcpState = iota
stateEstablished
stateCloseWait // peer's FIN was received; we may still send until we close too
stateClosing // our FIN was sent (from Established or CloseWait)
stateTimeWait // both FINs exchanged and acked; short linger before removal
stateClosed // terminal, removed from the connection table
)
// tcpKey identifies a tcp connection the same way it appears on the wire
// flowing from the app behind the tun device towards its destination.
type tcpKey struct {
netProto tcpip.NetworkProtocolNumber
srcAddr tcpip.Address
srcPort uint16
dstAddr tcpip.Address
dstPort uint16
}
// tcpConn is a minimal TCP endpoint implementing net.Conn. It deliberately
// exposes only plain Read/Write (never ReadMultiBuffer/WriteMultiBuffer) so
// that stat.CounterConnection in handler.go keeps accounting traffic
// correctly, matching the udpConn precedent in udp_fullcone.go.
type tcpConn struct {
stack *stackSystem
key tcpKey
src net.Destination
dst net.Destination
ourMSS int
mu sync.Mutex
cond *sync.Cond
state tcpState
// send side. sendQueue[0] always holds the byte at sequence sndUna: acked
// bytes are trimmed off the front, so no separate "acked" bookkeeping is
// needed. sendQueue[:unsentOffset] has been transmitted at least once;
// sendQueue[unsentOffset:] never has.
iss seqnum.Value
sndUna seqnum.Value
sndNxt seqnum.Value
sndMSS int
peerWindow uint32
sendQueue []byte
unsentOffset int
closeCalled bool
finSent bool
finAcked bool
finSeq seqnum.Value
// receive side.
irs seqnum.Value
rcvNxt seqnum.Value
recvQueue [][]byte
recvOffset int
recvBuffered int
recvClosed bool
err error
lastActive time.Time
rtoTimer *time.Timer
rtoBackoff int
lingerTimer *time.Timer
}
var _ net.Conn = (*tcpConn)(nil)
// outgoingMSS returns the MSS we can use without ever needing IP
// fragmentation (unsupported), given the tun device's MTU.
func outgoingMSS(mtu uint32, netProto tcpip.NetworkProtocolNumber) int {
ipHdrSize := header.IPv4MinimumSize
if netProto == header.IPv6ProtocolNumber {
ipHdrSize = header.IPv6MinimumSize
}
mss := int(mtu) - ipHdrSize - header.TCPMinimumSize
const minMSS = 88
if mss < minMSS {
mss = minMSS
}
return mss
}
// handleTCP is the tcp entry point from stackSystem.handleTransport.
func (s *stackSystem) handleTCP(netProto tcpip.NetworkProtocolNumber, srcIP, dstIP tcpip.Address, payload []byte) {
if len(payload) < header.TCPMinimumSize {
return
}
tcpHdr := header.TCP(payload)
if _, _, ok := header.TCPValid(tcpHdr, nil, 0, tcpip.Address{}, tcpip.Address{}, true); !ok {
return
}
key := tcpKey{
netProto: netProto,
srcAddr: srcIP,
srcPort: tcpHdr.SourcePort(),
dstAddr: dstIP,
dstPort: tcpHdr.DestinationPort(),
}
s.tcpMu.Lock()
conn, ok := s.tcp[key]
s.tcpMu.Unlock()
if ok {
conn.handleSegment(tcpHdr)
return
}
flags := tcpHdr.Flags()
if flags&header.TCPFlagRst != 0 {
return // never generate a reset in response to a reset
}
if flags&header.TCPFlagSyn != 0 && flags&header.TCPFlagAck == 0 {
s.newTCPConn(key, tcpHdr)
return
}
// any other segment referencing an unknown connection: let the peer know
// promptly it no longer/never existed, same as a real kernel would
s.sendRawTCPReset(key, tcpHdr)
}
func (s *stackSystem) newTCPConn(key tcpKey, tcpHdr header.TCP) {
synOpts := header.ParseSynOptions(tcpHdr.Options(), false)
c := &tcpConn{
stack: s,
key: key,
src: net.TCPDestination(net.IPAddress(key.srcAddr.AsSlice()), net.Port(key.srcPort)),
dst: net.TCPDestination(net.IPAddress(key.dstAddr.AsSlice()), net.Port(key.dstPort)),
state: stateSynRcvd,
}
c.cond = sync.NewCond(&c.mu)
c.iss = randomSequenceNumber()
c.sndUna = c.iss
c.sndNxt = c.iss.Add(1)
c.irs = seqnum.Value(tcpHdr.SequenceNumber())
c.rcvNxt = c.irs.Add(1)
c.ourMSS = outgoingMSS(s.mtu, key.netProto)
c.sndMSS = int(synOpts.MSS)
if c.sndMSS <= 0 || c.sndMSS > c.ourMSS {
c.sndMSS = c.ourMSS
}
c.lastActive = time.Now()
s.tcpMu.Lock()
s.tcp[key] = c
s.tcpMu.Unlock()
c.mu.Lock()
c.sendSynAckLocked()
c.mu.Unlock()
}
// sendRawTCPReset replies to a segment that does not match any known
// connection, following the rules of RFC 9293 §3.10.7.1.
func (s *stackSystem) sendRawTCPReset(key tcpKey, tcpHdr header.TCP) {
flags := tcpHdr.Flags()
segLen := seqnum.Size(len(tcpHdr.Payload()))
if flags&header.TCPFlagSyn != 0 {
segLen++
}
if flags&header.TCPFlagFin != 0 {
segLen++
}
var seq, ack seqnum.Value
var ackFlag header.TCPFlags
if flags&header.TCPFlagAck != 0 {
seq = seqnum.Value(tcpHdr.AckNumber())
} else {
ack = seqnum.Value(tcpHdr.SequenceNumber()).Add(segLen)
ackFlag = header.TCPFlagAck
}
segment := make([]byte, header.TCPMinimumSize)
rst := header.TCP(segment)
rst.Encode(&header.TCPFields{
SrcPort: key.dstPort,
DstPort: key.srcPort,
SeqNum: uint32(seq),
AckNum: uint32(ack),
DataOffset: header.TCPMinimumSize,
Flags: header.TCPFlagRst | ackFlag,
WindowSize: 0,
})
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, key.dstAddr, key.srcAddr, uint16(len(segment)))
rst.SetChecksum(^rst.CalculateChecksum(xsum))
if err := s.writeTransportSegment(key.netProto, header.TCPProtocolNumber, key.dstAddr, key.srcAddr, segment); err != nil {
xerrors.LogInfoInner(s.ctx, err, "[tun] failed to write tcp reset")
}
}
func (c *tcpConn) lastActiveTime() time.Time {
c.mu.Lock()
defer c.mu.Unlock()
return c.lastActive
}
// abort is the externally callable (unlocked) equivalent of abortLocked,
// used by the idle reaper and by Close's callers indirectly through it.
func (c *tcpConn) abort(err error) {
c.mu.Lock()
defer c.mu.Unlock()
c.abortLocked(err)
}
func (c *tcpConn) abortLocked(err error) {
if c.state == stateClosed {
return
}
c.state = stateClosed
c.stopRTOLocked()
c.stopLingerLocked()
c.err = err
c.cond.Broadcast()
// deliberately does not send an RST: if the peer sends anything else for
// this connection later, it will miss the (now removed) table entry and
// get a fresh, correctly-addressed reset from sendRawTCPReset above.
c.stack.removeTCPConn(c.key, c)
}
// handleSegment processes one already-demultiplexed incoming segment.
func (c *tcpConn) handleSegment(tcpHdr header.TCP) {
c.mu.Lock()
defer c.mu.Unlock()
if c.state == stateClosed {
return
}
c.lastActive = time.Now()
flags := tcpHdr.Flags()
if flags&header.TCPFlagRst != 0 {
c.abortLocked(errConnReset)
return
}
if c.state == stateSynRcvd {
c.handleSynRcvdSegmentLocked(tcpHdr)
return
}
if flags&header.TCPFlagSyn != 0 {
// unexpected SYN on an already-established connection is not
// modeled; treat it like the peer abandoned and reset it
c.abortLocked(errConnReset)
return
}
if flags&header.TCPFlagAck != 0 {
c.handleAckLocked(seqnum.Value(tcpHdr.AckNumber()), tcpHdr.WindowSize())
}
c.acceptInOrderLocked(seqnum.Value(tcpHdr.SequenceNumber()), tcpHdr.Payload(), flags&header.TCPFlagFin != 0)
}
func (c *tcpConn) handleSynRcvdSegmentLocked(tcpHdr header.TCP) {
flags := tcpHdr.Flags()
if flags&header.TCPFlagSyn != 0 {
// peer's retransmission of the original SYN, our SYN-ACK likely
// hasn't reached them yet: resend it and rely entirely on their own
// retransmission timer rather than running one on our side too
c.sendSynAckLocked()
return
}
if flags&header.TCPFlagAck == 0 {
return
}
if seqnum.Value(tcpHdr.AckNumber()) != c.sndNxt {
// does not acknowledge our SYN correctly; a well-behaved peer will
// simply retry, so it is safe to just ignore this segment
return
}
c.state = stateEstablished
go c.stack.handler.HandleConnection(c, c.dst)
c.handleAckLocked(seqnum.Value(tcpHdr.AckNumber()), tcpHdr.WindowSize())
c.acceptInOrderLocked(seqnum.Value(tcpHdr.SequenceNumber()), tcpHdr.Payload(), flags&header.TCPFlagFin != 0)
}
// acceptInOrderLocked handles the data/FIN portion of a segment once it is
// known to be neither a SYN nor a RST. Only strictly in-order segments are
// accepted; anything else is dropped (relying on the peer's retransmission)
// since the tun channel is expected to already deliver packets in order.
func (c *tcpConn) acceptInOrderLocked(seq seqnum.Value, payload []byte, fin bool) {
if seq != c.rcvNxt {
c.sendAckLocked()
return
}
accept := payload
if room := c.recvWindowLocked(); uint32(len(accept)) > room {
accept = accept[:room]
}
if len(accept) > 0 {
c.enqueueRecvLocked(accept)
c.rcvNxt = c.rcvNxt.Add(seqnum.Size(len(accept)))
}
finAccepted := false
if fin && len(accept) == len(payload) {
c.onFinLocked()
c.rcvNxt = c.rcvNxt.Add(1)
finAccepted = true
}
if len(accept) > 0 || finAccepted || len(accept) < len(payload) {
c.sendAckLocked()
}
}
func (c *tcpConn) onFinLocked() {
if c.recvClosed {
return
}
c.recvClosed = true
c.cond.Broadcast()
if c.state == stateEstablished {
c.state = stateCloseWait
}
c.maybeFinishCloseLocked()
}
func (c *tcpConn) handleAckLocked(ackNum seqnum.Value, windowSize uint16) {
if ackNum.LessThan(c.sndUna) {
// old/duplicate ack: no fast-retransmit heuristics implemented
c.peerWindow = uint32(windowSize)
c.trySendLocked()
return
}
if c.sndNxt.LessThan(ackNum) {
// acknowledges more than we ever sent: lenient clamp instead of
// rejecting the segment outright
ackNum = c.sndNxt
}
if advanced := c.sndUna.Size(ackNum); advanced > 0 {
c.sndUna = ackNum
n := int(advanced)
if n > len(c.sendQueue) {
n = len(c.sendQueue)
}
c.sendQueue = c.sendQueue[n:]
c.unsentOffset -= n
if c.unsentOffset < 0 {
c.unsentOffset = 0
}
c.rtoBackoff = 0
if c.finSent && c.sndUna == c.sndNxt {
c.finAcked = true
}
c.cond.Broadcast()
}
c.peerWindow = uint32(windowSize)
c.trySendLocked()
c.maybeFinishCloseLocked()
}
func (c *tcpConn) maybeFinishCloseLocked() {
if c.state == stateClosing && c.finAcked && c.recvClosed {
c.state = stateTimeWait
c.startLingerLocked()
}
}
func (c *tcpConn) recvWindowLocked() uint32 {
room := maxRecvBuffer - c.recvBuffered
if room < 0 {
room = 0
}
if room > 0xffff {
room = 0xffff
}
return uint32(room)
}
func (c *tcpConn) enqueueRecvLocked(payload []byte) {
data := make([]byte, len(payload))
copy(data, payload)
c.recvQueue = append(c.recvQueue, data)
c.recvBuffered += len(data)
c.cond.Broadcast()
}
// sendOneChunkLocked transmits up to maxLen bytes of never-yet-sent data (if
// any remains), advancing sndNxt/unsentOffset. It returns the number of
// bytes sent, 0 if none remained.
func (c *tcpConn) sendOneChunkLocked(maxLen int) int {
remaining := len(c.sendQueue) - c.unsentOffset
if remaining <= 0 {
return 0
}
if maxLen > remaining {
maxLen = remaining
}
if maxLen > c.sndMSS {
maxLen = c.sndMSS
}
if maxLen <= 0 {
return 0
}
data := c.sendQueue[c.unsentOffset : c.unsentOffset+maxLen]
c.sendDataSegmentLocked(c.sndNxt, data, false)
c.sndNxt = c.sndNxt.Add(seqnum.Size(maxLen))
c.unsentOffset += maxLen
return maxLen
}
func (c *tcpConn) trySendLocked() {
switch c.state {
case stateSynRcvd, stateTimeWait, stateClosed:
return
}
for {
inFlight := int(c.sndUna.Size(c.sndNxt))
windowLeft := int(c.peerWindow) - inFlight
if windowLeft <= 0 {
break
}
if c.sendOneChunkLocked(windowLeft) == 0 {
break
}
}
if c.closeCalled && !c.finSent && c.unsentOffset == len(c.sendQueue) {
c.finSeq = c.sndNxt
c.sendDataSegmentLocked(c.finSeq, nil, true)
c.sndNxt = c.sndNxt.Add(1)
c.finSent = true
}
c.refreshRTOLocked()
}
func (c *tcpConn) outstandingLocked() bool {
if c.unsentOffset > 0 {
return true // already-transmitted data pending ack
}
if len(c.sendQueue) > c.unsentOffset && c.peerWindow == 0 {
return true // blocked purely by a zero window; need to probe
}
if c.finSent && !c.finAcked {
return true // FIN transmitted but not yet acked
}
return false
}
func (c *tcpConn) refreshRTOLocked() {
if c.outstandingLocked() {
c.scheduleRTOLocked()
} else {
c.stopRTOLocked()
}
}
func (c *tcpConn) rtoDurationLocked() time.Duration {
d := minRTO * time.Duration(uint64(1)<<uint(c.rtoBackoff))
if d > maxRTO || d <= 0 {
d = maxRTO
}
return d
}
func (c *tcpConn) scheduleRTOLocked() {
d := c.rtoDurationLocked()
if c.rtoTimer == nil {
c.rtoTimer = time.AfterFunc(d, c.onRTOTimerFired)
} else {
c.rtoTimer.Reset(d)
}
}
func (c *tcpConn) stopRTOLocked() {
if c.rtoTimer != nil {
c.rtoTimer.Stop()
}
}
func (c *tcpConn) onRTOTimerFired() {
c.mu.Lock()
defer c.mu.Unlock()
c.onRTOFireLocked()
}
func (c *tcpConn) onRTOFireLocked() {
if c.state == stateClosed || !c.outstandingLocked() {
return
}
if c.rtoBackoff >= maxRTORetries {
c.abortLocked(errConnTimedOut)
return
}
c.rtoBackoff++
switch {
case c.unsentOffset > 0:
c.sendDataSegmentLocked(c.sndUna, c.sendQueue[:c.unsentOffset], false)
case len(c.sendQueue) > c.unsentOffset:
// nothing in flight, but blocked by a zero peer window: probe with
// exactly one new byte, per RFC 9293 §3.8.6.1
c.sendOneChunkLocked(1)
case c.finSent && !c.finAcked:
c.sendDataSegmentLocked(c.finSeq, nil, true)
}
c.refreshRTOLocked()
}
func (c *tcpConn) startLingerLocked() {
c.stopRTOLocked()
c.lingerTimer = time.AfterFunc(lingerDuration, func() {
c.mu.Lock()
defer c.mu.Unlock()
c.abortLocked(errConnClosed)
})
}
func (c *tcpConn) stopLingerLocked() {
if c.lingerTimer != nil {
c.lingerTimer.Stop()
}
}
// transmitLocked builds, checksums and writes a single tcp segment.
func (c *tcpConn) transmitLocked(seq, ack seqnum.Value, flags header.TCPFlags, payload []byte, options []byte) {
headerLen := header.TCPMinimumSize + len(options)
segment := make([]byte, headerLen+len(payload))
tcpHdr := header.TCP(segment)
tcpHdr.Encode(&header.TCPFields{
SrcPort: c.key.dstPort,
DstPort: c.key.srcPort,
SeqNum: uint32(seq),
AckNum: uint32(ack),
DataOffset: uint8(headerLen),
Flags: flags,
WindowSize: uint16(c.recvWindowLocked()),
})
copy(tcpHdr.Options(), options)
copy(segment[headerLen:], payload)
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, c.key.dstAddr, c.key.srcAddr, uint16(len(segment)))
xsum = checksum.Checksum(payload, xsum)
tcpHdr.SetChecksum(^tcpHdr.CalculateChecksum(xsum))
if err := c.stack.writeTransportSegment(c.key.netProto, header.TCPProtocolNumber, c.key.dstAddr, c.key.srcAddr, segment); err != nil {
xerrors.LogInfoInner(c.stack.ctx, err, "[tun] failed to write tcp segment")
}
}
func (c *tcpConn) sendSynAckLocked() {
var optBuf [header.TCPOptionMSSLength]byte
n := header.EncodeMSSOption(uint32(c.ourMSS), optBuf[:])
c.transmitLocked(c.iss, c.rcvNxt, header.TCPFlagSyn|header.TCPFlagAck, nil, optBuf[:n])
}
func (c *tcpConn) sendAckLocked() {
c.transmitLocked(c.sndNxt, c.rcvNxt, header.TCPFlagAck, nil, nil)
}
func (c *tcpConn) sendDataSegmentLocked(seq seqnum.Value, payload []byte, fin bool) {
flags := header.TCPFlagAck
if fin {
flags |= header.TCPFlagFin
}
c.transmitLocked(seq, c.rcvNxt, flags, payload, nil)
}
// Read implements net.Conn.
func (c *tcpConn) Read(p []byte) (int, error) {
c.mu.Lock()
defer c.mu.Unlock()
for len(c.recvQueue) == 0 && c.err == nil && !c.recvClosed {
c.cond.Wait()
}
if c.err != nil {
return 0, c.err
}
if len(c.recvQueue) == 0 {
return 0, io.EOF
}
before := c.recvWindowLocked()
chunk := c.recvQueue[0]
n := copy(p, chunk[c.recvOffset:])
c.recvOffset += n
c.recvBuffered -= n
if c.recvOffset == len(chunk) {
c.recvQueue = c.recvQueue[1:]
c.recvOffset = 0
}
// let the peer know promptly if reading just freed up a previously
// exhausted window, instead of waiting for it to probe us for an update
if after := c.recvWindowLocked(); before == 0 && after > 0 {
c.sendAckLocked()
}
return n, nil
}
// Write implements net.Conn.
func (c *tcpConn) Write(p []byte) (int, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.closeCalled {
return 0, errConnClosed
}
total := 0
for total < len(p) {
if c.err != nil {
return total, c.err
}
if c.closeCalled {
return total, errConnClosed
}
room := maxSendBuffer - len(c.sendQueue)
if room <= 0 {
c.cond.Wait()
continue
}
n := len(p) - total
if n > room {
n = room
}
c.sendQueue = append(c.sendQueue, p[total:total+n]...)
total += n
}
c.trySendLocked()
return total, nil
}
// Close implements net.Conn.
func (c *tcpConn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.closeCalled {
return nil
}
c.closeCalled = true
c.cond.Broadcast()
switch c.state {
case stateEstablished, stateCloseWait:
c.state = stateClosing
default:
return nil
}
c.trySendLocked()
return nil
}
func (c *tcpConn) LocalAddr() net.Addr { return c.dst.RawNetAddr() }
func (c *tcpConn) RemoteAddr() net.Addr { return c.src.RawNetAddr() }
func (c *tcpConn) SetDeadline(t time.Time) error { return nil }
func (c *tcpConn) SetReadDeadline(t time.Time) error { return nil }
func (c *tcpConn) SetWriteDeadline(t time.Time) error { return nil }
+448
View File
@@ -0,0 +1,448 @@
package tun
import (
"context"
"io"
"sync"
"testing"
"time"
"github.com/xtls/xray-core/common/net"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// fakeGVisorDevice is an in-memory GVisorDevice used to script conversations
// with the "system" stack without any real tun device or privileges.
type fakeGVisorDevice struct {
inbound chan []byte
outbound chan []byte
notify chan struct{}
}
func newFakeGVisorDevice() *fakeGVisorDevice {
return &fakeGVisorDevice{
inbound: make(chan []byte, 256),
outbound: make(chan []byte, 256),
notify: make(chan struct{}, 1),
}
}
func (d *fakeGVisorDevice) push(data []byte) {
d.inbound <- data
select {
case d.notify <- struct{}{}:
default:
}
}
func (d *fakeGVisorDevice) ReadPacket() (byte, *stack.PacketBuffer, error) {
select {
case data := <-d.inbound:
version := data[0] >> 4
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(data)})
return version, pkt, nil
default:
return 0, nil, ErrQueueEmpty
}
}
func (d *fakeGVisorDevice) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
var data []byte
for _, s := range packet.AsSlices() {
data = append(data, s...)
}
d.outbound <- data
return nil
}
func (d *fakeGVisorDevice) Wait() {
select {
case <-d.notify:
case <-time.After(20 * time.Millisecond):
}
}
func (d *fakeGVisorDevice) recv(t *testing.T, timeout time.Duration) []byte {
t.Helper()
select {
case data := <-d.outbound:
return data
case <-time.After(timeout):
t.Fatal("timed out waiting for outbound packet")
return nil
}
}
var _ GVisorDevice = (*fakeGVisorDevice)(nil)
// echoHandler is a ConnectionHandler that echoes back everything it reads on
// each connection, and records connections/destinations it has seen.
type echoHandler struct {
mu sync.Mutex
conns []net.Conn
dests []net.Destination
done chan struct{}
}
func newEchoHandler() *echoHandler {
return &echoHandler{done: make(chan struct{}, 8)}
}
func (h *echoHandler) HandleConnection(conn net.Conn, dest net.Destination) {
h.mu.Lock()
h.conns = append(h.conns, conn)
h.dests = append(h.dests, dest)
h.mu.Unlock()
_, _ = io.Copy(conn, conn)
_ = conn.Close()
h.done <- struct{}{}
}
func newTestStackSystem(device GVisorDevice, handler ConnectionHandler, idleTimeout time.Duration) (*stackSystem, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
s := &stackSystem{
ctx: ctx,
device: device,
mtu: 1500,
idleTimeout: idleTimeout,
handler: handler,
tcp: make(map[tcpKey]*tcpConn),
}
return s, cancel
}
func testIP(s string) tcpip.Address {
return tcpip.AddrFrom4Slice(net.ParseIP(s).To4())
}
const (
testPeerIP = "10.0.0.2"
testTargetIP = "10.0.0.1"
testPeerPort = uint16(51234)
testDstPort = uint16(8080)
)
// buildIPv4TCP builds a raw IPv4+TCP segment, computing valid checksums.
func buildIPv4TCP(src, dst tcpip.Address, srcPort, dstPort uint16, seq, ack uint32, flags header.TCPFlags, window uint16, payload []byte, options []byte) []byte {
headerLen := header.TCPMinimumSize + len(options)
totalLen := header.IPv4MinimumSize + headerLen + len(payload)
data := make([]byte, totalLen)
ipHdr := header.IPv4(data)
ipHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(totalLen),
TTL: 64,
Protocol: uint8(header.TCPProtocolNumber),
SrcAddr: src,
DstAddr: dst,
})
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
tcpHdr := header.TCP(data[header.IPv4MinimumSize:])
tcpHdr.Encode(&header.TCPFields{
SrcPort: srcPort,
DstPort: dstPort,
SeqNum: seq,
AckNum: ack,
DataOffset: uint8(headerLen),
Flags: flags,
WindowSize: window,
})
copy(tcpHdr.Options(), options)
copy(data[header.IPv4MinimumSize+headerLen:], payload)
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, src, dst, uint16(headerLen+len(payload)))
xsum = checksum.Checksum(payload, xsum)
tcpHdr.SetChecksum(^tcpHdr.CalculateChecksum(xsum))
return data
}
func parseIPv4TCP(t *testing.T, data []byte) header.TCP {
t.Helper()
ipHdr := header.IPv4(data)
if !ipHdr.IsValid(len(data)) {
t.Fatalf("invalid ipv4 packet")
}
return header.TCP(ipHdr.Payload())
}
func TestSystemStackTCPHandshakeEchoClose(t *testing.T) {
device := newFakeGVisorDevice()
handler := newEchoHandler()
s, cancel := newTestStackSystem(device, handler, time.Minute)
defer cancel()
if err := s.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer s.Close()
src := testIP(testPeerIP)
dst := testIP(testTargetIP)
iss := uint32(1000)
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss, 0, header.TCPFlagSyn, 65535, nil, nil))
synAck := parseIPv4TCP(t, device.recv(t, time.Second))
if synAck.Flags() != header.TCPFlagSyn|header.TCPFlagAck {
t.Fatalf("expected SYN-ACK, got flags %v", synAck.Flags())
}
if synAck.AckNumber() != iss+1 {
t.Fatalf("unexpected ack number %d, want %d", synAck.AckNumber(), iss+1)
}
serverISS := synAck.SequenceNumber()
// final handshake ACK
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss+1, serverISS+1, header.TCPFlagAck, 65535, nil, nil))
// send data
payload := []byte("hello world")
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss+1, serverISS+1, header.TCPFlagAck|header.TCPFlagPsh, 65535, payload, nil))
// drain outbound packets until the full echo has been observed, acking
// any data segments as they arrive so the connection can make progress
var echoed []byte
deadline := time.After(2 * time.Second)
for len(echoed) < len(payload) {
select {
case raw := <-device.outbound:
tcpHdr := parseIPv4TCP(t, raw)
if len(tcpHdr.Payload()) > 0 {
echoed = append(echoed, tcpHdr.Payload()...)
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort,
iss+1+uint32(len(payload)), tcpHdr.SequenceNumber()+uint32(len(tcpHdr.Payload())),
header.TCPFlagAck, 65535, nil, nil))
}
case <-deadline:
t.Fatalf("timed out waiting for echo, got %q so far", echoed)
}
}
if string(echoed) != string(payload) {
t.Fatalf("echo mismatch: got %q want %q", echoed, payload)
}
h := handler
h.mu.Lock()
if len(h.dests) != 1 || h.dests[0].NetAddr() != "10.0.0.1:8080" {
t.Fatalf("unexpected destination recorded: %+v", h.dests)
}
h.mu.Unlock()
// peer sends FIN
finSeq := iss + 1 + uint32(len(payload))
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, finSeq, serverISS+1+uint32(len(payload)), header.TCPFlagFin|header.TCPFlagAck, 65535, nil, nil))
select {
case <-handler.done:
case <-time.After(2 * time.Second):
t.Fatal("echo handler never finished after peer FIN")
}
var sawAckOfFin, sawOurFin bool
var ourFinSeq uint32
deadline = time.After(2 * time.Second)
for !sawAckOfFin || !sawOurFin {
select {
case raw := <-device.outbound:
tcpHdr := parseIPv4TCP(t, raw)
if tcpHdr.Flags()&header.TCPFlagFin != 0 {
sawOurFin = true
ourFinSeq = tcpHdr.SequenceNumber()
}
if tcpHdr.AckNumber() == finSeq+1 {
sawAckOfFin = true
}
case <-deadline:
t.Fatalf("timed out waiting for our fin/ack (sawAckOfFin=%v sawOurFin=%v)", sawAckOfFin, sawOurFin)
}
}
// ack our FIN, completing a graceful close
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, finSeq+1, ourFinSeq+1, header.TCPFlagAck, 65535, nil, nil))
deadline = time.After(2 * time.Second)
for {
s.tcpMu.Lock()
var conn *tcpConn
for _, c := range s.tcp {
conn = c
}
s.tcpMu.Unlock()
if conn == nil {
t.Fatal("connection unexpectedly removed before linger")
}
conn.mu.Lock()
state := conn.state
conn.mu.Unlock()
if state == stateTimeWait {
break
}
select {
case <-deadline:
t.Fatalf("connection did not reach TimeWait, state=%d", state)
case <-time.After(10 * time.Millisecond):
}
}
}
func TestSystemStackTCPUnknownConnectionReset(t *testing.T) {
device := newFakeGVisorDevice()
handler := newEchoHandler()
s, cancel := newTestStackSystem(device, handler, time.Minute)
defer cancel()
if err := s.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer s.Close()
src := testIP(testPeerIP)
dst := testIP(testTargetIP)
// an ACK referencing a connection the stack has never seen
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, 5000, 0, header.TCPFlagAck, 65535, nil, nil))
rst := parseIPv4TCP(t, device.recv(t, time.Second))
if rst.Flags()&header.TCPFlagRst == 0 {
t.Fatalf("expected RST, got flags %v", rst.Flags())
}
if rst.SequenceNumber() != 5000 {
t.Fatalf("expected reset seq to echo the ack number 5000, got %d", rst.SequenceNumber())
}
}
func TestSystemStackTCPRetransmit(t *testing.T) {
device := newFakeGVisorDevice()
handler := newEchoHandler()
s, cancel := newTestStackSystem(device, handler, time.Minute)
defer cancel()
if err := s.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer s.Close()
src := testIP(testPeerIP)
dst := testIP(testTargetIP)
iss := uint32(2000)
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss, 0, header.TCPFlagSyn, 65535, nil, nil))
synAck := parseIPv4TCP(t, device.recv(t, time.Second))
serverISS := synAck.SequenceNumber()
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss+1, serverISS+1, header.TCPFlagAck, 65535, nil, nil))
payload := []byte("hi")
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss+1, serverISS+1, header.TCPFlagAck|header.TCPFlagPsh, 65535, payload, nil))
// consume the data-ack and the first echoed data segment, but do NOT ack
// the echoed data, forcing a retransmit
var first []byte
deadline := time.After(2 * time.Second)
for len(first) == 0 {
select {
case raw := <-device.outbound:
tcpHdr := parseIPv4TCP(t, raw)
if len(tcpHdr.Payload()) > 0 {
first = append([]byte(nil), tcpHdr.Payload()...)
}
case <-deadline:
t.Fatal("timed out waiting for first echoed segment")
}
}
// now wait for a retransmission of the same bytes, without acking
deadline = time.After(2 * time.Second)
for {
select {
case raw := <-device.outbound:
tcpHdr := parseIPv4TCP(t, raw)
if string(tcpHdr.Payload()) == string(first) {
return // retransmit observed, test passes
}
case <-deadline:
t.Fatal("timed out waiting for retransmission of unacked data")
}
}
}
func TestSystemStackIdleReap(t *testing.T) {
device := newFakeGVisorDevice()
handler := newEchoHandler()
s, cancel := newTestStackSystem(device, handler, time.Millisecond)
defer cancel()
if err := s.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer s.Close()
src := testIP(testPeerIP)
dst := testIP(testTargetIP)
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, 1, 0, header.TCPFlagSyn, 65535, nil, nil))
device.recv(t, time.Second) // SYN-ACK
deadline := time.After(2 * time.Second)
for {
s.tcpMu.Lock()
n := len(s.tcp)
s.tcpMu.Unlock()
if n == 0 {
return
}
select {
case <-deadline:
t.Fatal("idle connection was not reaped")
case <-time.After(10 * time.Millisecond):
}
}
}
func TestSystemStackUDPEcho(t *testing.T) {
device := newFakeGVisorDevice()
handler := newEchoHandler()
s, cancel := newTestStackSystem(device, handler, time.Minute)
defer cancel()
if err := s.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer s.Close()
src := testIP(testPeerIP)
dst := testIP(testTargetIP)
payload := []byte("ping")
udpLen := header.UDPMinimumSize + len(payload)
totalLen := header.IPv4MinimumSize + udpLen
data := make([]byte, totalLen)
ipHdr := header.IPv4(data)
ipHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(totalLen),
TTL: 64,
Protocol: uint8(header.UDPProtocolNumber),
SrcAddr: src,
DstAddr: dst,
})
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
udpHdr := header.UDP(data[header.IPv4MinimumSize:])
udpHdr.Encode(&header.UDPFields{SrcPort: testPeerPort, DstPort: testDstPort, Length: uint16(udpLen)})
copy(data[header.IPv4MinimumSize+header.UDPMinimumSize:], payload)
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, src, dst, uint16(udpLen))
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
device.push(data)
select {
case <-handler.done:
case <-time.After(time.Second):
t.Fatal("udp handler never invoked/finished")
}
handler.mu.Lock()
defer handler.mu.Unlock()
if len(handler.dests) != 1 || handler.dests[0].Network != net.Network_UDP {
t.Fatalf("unexpected udp destination recorded: %+v", handler.dests)
}
}
+85
View File
@@ -7,9 +7,12 @@ import (
"net" "net"
"strconv" "strconv"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased" "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
) )
@@ -22,6 +25,22 @@ type AndroidTun struct {
// DefaultTun implements Tun // DefaultTun implements Tun
var _ Tun = (*AndroidTun)(nil) var _ Tun = (*AndroidTun)(nil)
// AndroidTun implements GVisorDevice, used by the "system" (lite) ip stack
var _ GVisorDevice = (*AndroidTun)(nil)
// fdReadWriter adapts a raw, already non-blocking file descriptor to io.Reader/io.Writer,
// so it can be used with buf.Buffer.ReadFrom, without the ownership/finalizer overhead of
// wrapping it in an *os.File (the fd is owned and closed elsewhere).
type fdReadWriter int
func (f fdReadWriter) Read(p []byte) (int, error) {
return unix.Read(int(f), p)
}
func (f fdReadWriter) Write(p []byte) (int, error) {
return unix.Write(int(f), p)
}
// NewTun builds new tun interface handler // NewTun builds new tun interface handler
func NewTun(options *Config) (Tun, error) { func NewTun(options *Config) (Tun, error) {
fd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "0" })) fd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "0" }))
@@ -78,6 +97,72 @@ func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
}) })
} }
// ReadPacket implements GVisorDevice method to read one packet from the tun device, used by
// the "system" (lite) ip stack. The gVisor backed stack instead talks to the fd directly through
// fdbased.New above, for lower overhead batched IO, bypassing GVisorDevice entirely.
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
// which will make the stack call Wait which should implement desired push-back
func (t *AndroidTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.options.MTU))
// read the bytes from the interface file descriptor, which is already non-blocking
n, err := b.ReadFrom(fdReadWriter(t.tunFd))
if err == unix.EAGAIN || err == unix.EWOULDBLOCK || err == unix.EINTR {
b.Release()
return 0, nil, ErrQueueEmpty
}
if err != nil {
b.Release()
return 0, nil, err
}
// discard empty packets
if n == 0 {
b.Release()
return 0, nil, ErrQueueEmpty
}
// network protocol version from the first nibble of the raw packet
version := b.Byte(0) >> 4
packetBuffer := buffer.MakeWithData(b.Bytes())
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: packetBuffer,
IsForwardedPacket: true,
OnRelease: func() {
b.Release()
},
}), nil
}
// WritePacket implements GVisorDevice method to write one packet to the tun device
func (t *AndroidTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.options.MTU))
defer b.Release()
// copy the bytes of slices that compose the packet into the allocated buffer, no
// extra header is needed here, unlike Darwin/FreeBSD's utun devices
for _, packetElement := range packet.AsSlices() {
_, _ = b.Write(packetElement)
}
if _, err := fdReadWriter(t.tunFd).Write(b.Bytes()); err != nil {
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
return &tcpip.ErrWouldBlock{}
}
return &tcpip.ErrAborted{}
}
return nil
}
// Wait blocks until the tun fd is likely readable again, rather than spinning the CPU.
// A bounded timeout keeps this responsive to a Close() racing a call already parked here.
func (t *AndroidTun) Wait() {
fds := []unix.PollFd{{Fd: int32(t.tunFd), Events: unix.POLLIN}}
_, _ = unix.Poll(fds, 1000)
}
func setinterface(network, address string, fd uintptr, iface *net.Interface) error { func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
return unix.BindToDevice(int(fd), iface.Name) return unix.BindToDevice(int(fd), iface.Name)
} }
+85
View File
@@ -10,9 +10,12 @@ import (
"sync" "sync"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased" "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
) )
@@ -35,6 +38,22 @@ type LinuxTun struct {
// LinuxTun implements Tun // LinuxTun implements Tun
var _ Tun = (*LinuxTun)(nil) var _ Tun = (*LinuxTun)(nil)
// LinuxTun implements GVisorDevice, used by the "system" (lite) ip stack
var _ GVisorDevice = (*LinuxTun)(nil)
// fdReadWriter adapts a raw, already non-blocking file descriptor to io.Reader/io.Writer,
// so it can be used with buf.Buffer.ReadFrom, without the ownership/finalizer overhead of
// wrapping it in an *os.File (the fd is owned and closed elsewhere, see LinuxTun.Close).
type fdReadWriter int
func (f fdReadWriter) Read(p []byte) (int, error) {
return unix.Read(int(f), p)
}
func (f fdReadWriter) Write(p []byte) (int, error) {
return unix.Write(int(f), p)
}
// NewTun builds new tun interface handler (linux specific) // NewTun builds new tun interface handler (linux specific)
func NewTun(options *Config) (Tun, error) { func NewTun(options *Config) (Tun, error) {
tunFd, tunLink, fdProvided, err := openFromEnv(options.Name) tunFd, tunLink, fdProvided, err := openFromEnv(options.Name)
@@ -228,6 +247,72 @@ func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
}) })
} }
// ReadPacket implements GVisorDevice method to read one packet from the tun device, used by
// the "system" (lite) ip stack. The gVisor backed stack instead talks to the fd directly through
// fdbased.New above, for lower overhead batched IO, bypassing GVisorDevice entirely.
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
// which will make the stack call Wait which should implement desired push-back
func (t *LinuxTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.options.MTU))
// read the bytes from the interface file descriptor, which is already non-blocking
n, err := b.ReadFrom(fdReadWriter(t.tunFd))
if err == unix.EAGAIN || err == unix.EWOULDBLOCK || err == unix.EINTR {
b.Release()
return 0, nil, ErrQueueEmpty
}
if err != nil {
b.Release()
return 0, nil, err
}
// discard empty packets
if n == 0 {
b.Release()
return 0, nil, ErrQueueEmpty
}
// network protocol version from the first nibble of the raw packet
version := b.Byte(0) >> 4
packetBuffer := buffer.MakeWithData(b.Bytes())
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: packetBuffer,
IsForwardedPacket: true,
OnRelease: func() {
b.Release()
},
}), nil
}
// WritePacket implements GVisorDevice method to write one packet to the tun device
func (t *LinuxTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.options.MTU))
defer b.Release()
// copy the bytes of slices that compose the packet into the allocated buffer, no
// Linux specific header is needed here, unlike Darwin/FreeBSD's utun devices
for _, packetElement := range packet.AsSlices() {
_, _ = b.Write(packetElement)
}
if _, err := fdReadWriter(t.tunFd).Write(b.Bytes()); err != nil {
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
return &tcpip.ErrWouldBlock{}
}
return &tcpip.ErrAborted{}
}
return nil
}
// Wait blocks until the tun fd is likely readable again, rather than spinning the CPU.
// A bounded timeout keeps this responsive to a Close() racing a call already parked here.
func (t *LinuxTun) Wait() {
fds := []unix.PollFd{{Fd: int32(t.tunFd), Events: unix.POLLIN}}
_, _ = unix.Poll(fds, 1000)
}
func setinterface(network, address string, fd uintptr, iface *net.Interface) error { func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
return unix.BindToDevice(int(fd), iface.Name) return unix.BindToDevice(int(fd), iface.Name)
} }
+127 -119
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"
@@ -36,7 +36,8 @@ type WindowsTun struct {
session wintun.Session session wintun.Session
readWait windows.Handle readWait windows.Handle
luid winipcfg.LUID luid winipcfg.LUID
changeCallback winipcfg.ChangeCallback cbr winipcfg.ChangeCallback
cbi winipcfg.ChangeCallback
closed bool closed bool
} }
@@ -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 {
return windows.ERROR_NOT_FOUND
} }
err := t.luid.SetIPAddresses(addresses) errors.LogErrorInner(context.Background(), firstErr, "Interface configuration failed, retrying attempt ", retryTimes, "/15")
time.Sleep(time.Second)
}
retryTimes++
for _, family := range []winipcfg.AddressFamily{windows.AF_INET, windows.AF_INET6} {
if family == windows.AF_INET && route4 || family == windows.AF_INET6 && route6 {
err = t.luid.SetRoutesForFamily(family, routesData)
if err != nil { if err != nil {
return errors.New("unable to set ips").Base(err) 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 {
if has4 { err = t.luid.SetIPAddressesForFamily(family, addresses)
ipif, err := t.luid.IPInterface(windows.AF_INET) 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
if family == windows.AF_INET && (address4 || route4) || family == windows.AF_INET6 && (address6 || route6) {
ipif.NLMTU = t.options.MTU ipif.NLMTU = t.options.MTU
}
if family == windows.AF_INET && route4 || family == windows.AF_INET6 && route6 {
ipif.UseAutomaticMetric = false ipif.UseAutomaticMetric = false
ipif.Metric = 0 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
} }
if has6 { err = t.luid.SetDNS(family, dns, nil)
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 {
goto startOver
} }
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled return firstErr
ipif.DadTransmits = 0
ipif.ManagedAddressConfigurationSupported = false
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() t.session.End()
_ = t.adapter.Close() }
if t.adapter != nil {
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)
if fixedName != "" { index := uint32(0)
for _, iface := range interfaces { lowestMetricWifi := ^uint32(0)
if iface.Index != tunIndex && iface.Name == fixedName { indexWifi := uint32(0)
return &iface, nil for i := range r {
} if r[i].DestinationPrefix.PrefixLength != 0 || r[i].InterfaceIndex == uint32(tunIndex) {
}
return nil, nil
}
var candidates []struct {
index int
score int
}
for i, iface := range interfaces {
if iface.Index == tunIndex {
continue continue
} }
if strings.Contains(iface.Name, "vEthernet") { ifrow, err := r[i].InterfaceLUID.Interface()
if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp {
continue continue
} }
if iface.Flags&net.FlagUp == 0 {
iface, err := r[i].InterfaceLUID.IPInterface(windows.AF_INET)
if err != nil {
iface, err = r[i].InterfaceLUID.IPInterface(windows.AF_INET6)
if err != nil {
continue continue
} }
if iface.Flags&net.FlagLoopback != 0 { }
if ifrow.Type == windows.IF_TYPE_IEEE80211 {
if r[i].Metric+iface.Metric < lowestMetricWifi {
lowestMetricWifi = r[i].Metric + iface.Metric
indexWifi = r[i].InterfaceIndex
}
continue continue
} }
addrs, err := iface.Addrs() if r[i].Metric+iface.Metric < lowestMetric {
if err != nil || len(addrs) == 0 { lowestMetric = r[i].Metric + iface.Metric
continue index = r[i].InterfaceIndex
}
candidates = append(candidates, struct {
index int
score int
}{i, scoreWindowsInterface(&iface, addrs)})
}
sort.Slice(candidates, func(i, j int) bool {
if candidates[i].score != candidates[j].score {
return candidates[i].score > candidates[j].score
}
return interfaces[candidates[i].index].Name < interfaces[candidates[j].index].Name
})
if len(candidates) == 0 {
return nil, nil
}
iface := interfaces[candidates[0].index]
return &iface, nil
}
func scoreWindowsInterface(iface *net.Interface, addrs []net.Addr) int {
score := 0
name := strings.ToLower(iface.Name)
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
score += 2
}
for _, addr := range addrs {
if strings.HasPrefix(addr.String(), "192.168.") {
score++
break
} }
} }
if indexWifi != 0 {
return score index = indexWifi
}
return net.InterfaceByIndex(int(index))
} }
-2
View File
@@ -202,8 +202,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
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())
+81 -106
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,11 +258,18 @@ func (h *Handler) init(ctx context.Context) error {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var pktConn net.PacketConn
if h.streamSettings.FinalMask != nil {
conn, err := h.streamSettings.FinalMask.DialUDP(ctx, dest)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
pktConn = conn.(*finalmask.PacketConnWrapper).PacketConn
} else {
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings) conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to dial to dest").Base(err)
} }
var pktConn net.PacketConn
switch c := conn.(type) { switch c := conn.(type) {
case *internet.PacketConnWrapper: case *internet.PacketConnWrapper:
pktConn = c.PacketConn pktConn = c.PacketConn
@@ -306,13 +278,6 @@ func (h *Handler) init(ctx context.Context) error {
default: default:
panic(reflect.TypeOf(c)) panic(reflect.TypeOf(c))
} }
if h.streamSettings.UdpmaskManager != nil {
newConn, err := h.streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
} }
if h.uplinkCounter != nil || h.downlinkCounter != nil { if h.uplinkCounter != nil || h.downlinkCounter != nil {
pktConn = &PacketCounterConnection{ pktConn = &PacketCounterConnection{
@@ -371,40 +336,12 @@ 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) { ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
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 {
return ip, nil
}
h.cacheMu.Lock()
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 {
@@ -413,44 +350,33 @@ func (h *Handler) resolveDomain(host string, strategy DeviceConfig_DomainStrateg
got6 = append(got6, ip) got6 = append(got6, ip)
} }
} }
var got []net.IP switch h.streamSettings.SocketSettings.DomainStrategy {
switch strategy { case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
case DeviceConfig_FORCE_IP:
got = ips got = ips
return ips[dice.Roll(len(ips))], nil case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
case DeviceConfig_FORCE_IP4:
got = got4 got = got4
case DeviceConfig_FORCE_IP6: case internet.DomainStrategy_USE_IP6, internet.DomainStrategy_FORCE_IP6:
got = got6 got = got6
case DeviceConfig_FORCE_IP46: case internet.DomainStrategy_USE_IP46, internet.DomainStrategy_FORCE_IP46:
got = got4 got = got4
if len(got) == 0 { if len(got) == 0 {
got = got6 got = got6
} }
case DeviceConfig_FORCE_IP64: case internet.DomainStrategy_USE_IP64, internet.DomainStrategy_FORCE_IP64:
got = got6 got = got6
if len(got) == 0 { if len(got) == 0 {
got = got4 got = got4
} }
default:
panic(strategy)
} }
if len(got) == 0 { if len(got) == 0 {
return nil, dns.ErrEmptyResponse 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
} }
@@ -478,15 +404,8 @@ func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
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 +442,59 @@ 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
c.m = make(map[string]entry)
go c.gc()
}
func (c *cache) gc() {
ticker := time.NewTicker(time.Minute)
for {
now := <-ticker.C
c.mu.Lock()
for key, entry := range c.m {
if now.After(entry.deadline) {
delete(c.m, key)
}
}
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)),
}
}
+14 -90
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"`
@@ -161,7 +106,6 @@ type DeviceConfig struct {
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"`
@@ -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;
+157 -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,8 +128,10 @@ 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
} }
@@ -166,7 +172,10 @@ 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:
}
} }
func (tun *netTun) Close() error { func (tun *netTun) Close() error {
@@ -179,8 +188,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 +229,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 +257,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 +581,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 +595,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 +700,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,
+6 -1
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{
StreamSettings: &internet.StreamConfig{
SocketSettings: &internet.SocketConfig{
DomainStrategy: internet.DomainStrategy_USE_IP, 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",
},
}, },
}), }),
}, },
+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{}),
}, },
}, },
} }
+1 -7
View File
@@ -10,7 +10,7 @@ type ConfigCreator func() interface{}
var globalTransportConfigCreatorCache = make(map[string]ConfigCreator) var globalTransportConfigCreatorCache = make(map[string]ConfigCreator)
var strategy = [][]byte{ var strategy = [11][3]byte{
// name strategy, prefer, fallback // name strategy, prefer, fallback
{0, 0, 0}, // AsIs none, /, / {0, 0, 0}, // AsIs none, /, /
{1, 0, 0}, // UseIP use, both, none {1, 0, 0}, // UseIP use, both, none
@@ -25,8 +25,6 @@ var strategy = [][]byte{
{2, 6, 4}, // ForceIPv6v4 force, 6, 4 {2, 6, 4}, // ForceIPv6v4 force, 6, 4
} }
const unknownProtocol = "unknown"
func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error { func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error {
if _, found := globalTransportConfigCreatorCache[name]; found { if _, found := globalTransportConfigCreatorCache[name]; found {
return errors.New("protocol ", name, " is already registered").AtError() return errors.New("protocol ", name, " is already registered").AtError()
@@ -91,10 +89,6 @@ func (c *StreamConfig) HasSecuritySettings() bool {
return len(c.SecuritySettings) > 0 return len(c.SecuritySettings) > 0
} }
func (c *ProxyConfig) HasTag() bool {
return c != nil && len(c.Tag) > 0
}
func (m SocketConfig_TProxyMode) IsEnabled() bool { func (m SocketConfig_TProxyMode) IsEnabled() bool {
return m != SocketConfig_Off return m != SocketConfig_Off
} }
+63 -194
View File
@@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead. // Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) { func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{6, 0} return file_transport_internet_config_proto_rawDescGZIP(), []int{4, 0}
} }
type TransportConfig struct { type TransportConfig struct {
@@ -382,66 +382,6 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
return nil return nil
} }
type UdpHop struct {
state protoimpl.MessageState `protogen:"open.v1"`
Ports []uint32 `protobuf:"varint,1,rep,packed,name=ports,proto3" json:"ports,omitempty"`
IntervalMin int64 `protobuf:"varint,2,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
IntervalMax int64 `protobuf:"varint,3,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UdpHop) Reset() {
*x = UdpHop{}
mi := &file_transport_internet_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UdpHop) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UdpHop) ProtoMessage() {}
func (x *UdpHop) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UdpHop.ProtoReflect.Descriptor instead.
func (*UdpHop) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
}
func (x *UdpHop) GetPorts() []uint32 {
if x != nil {
return x.Ports
}
return nil
}
func (x *UdpHop) GetIntervalMin() int64 {
if x != nil {
return x.IntervalMin
}
return 0
}
func (x *UdpHop) GetIntervalMax() int64 {
if x != nil {
return x.IntervalMax
}
return 0
}
type QuicParams struct { type QuicParams struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"` Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
@@ -449,25 +389,24 @@ type QuicParams struct {
BrutalUp uint64 `protobuf:"varint,3,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"` BrutalUp uint64 `protobuf:"varint,3,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"`
BrutalDown uint64 `protobuf:"varint,4,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"` BrutalDown uint64 `protobuf:"varint,4,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"`
BrutalDisableLossCompensation bool `protobuf:"varint,5,opt,name=brutal_disable_loss_compensation,json=brutalDisableLossCompensation,proto3" json:"brutal_disable_loss_compensation,omitempty"` BrutalDisableLossCompensation bool `protobuf:"varint,5,opt,name=brutal_disable_loss_compensation,json=brutalDisableLossCompensation,proto3" json:"brutal_disable_loss_compensation,omitempty"`
UdpHop *UdpHop `protobuf:"bytes,6,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,omitempty"` InitStreamReceiveWindow uint64 `protobuf:"varint,6,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
InitStreamReceiveWindow uint64 `protobuf:"varint,7,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"` MaxStreamReceiveWindow uint64 `protobuf:"varint,7,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
MaxStreamReceiveWindow uint64 `protobuf:"varint,8,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"` InitConnReceiveWindow uint64 `protobuf:"varint,8,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
InitConnReceiveWindow uint64 `protobuf:"varint,9,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"` MaxConnReceiveWindow uint64 `protobuf:"varint,9,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
MaxConnReceiveWindow uint64 `protobuf:"varint,10,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"` MaxIdleTimeout int64 `protobuf:"varint,10,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
MaxIdleTimeout int64 `protobuf:"varint,11,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"` KeepAlivePeriod int64 `protobuf:"varint,11,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
KeepAlivePeriod int64 `protobuf:"varint,12,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"` DisablePathMtuDiscovery bool `protobuf:"varint,12,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
DisablePathMtuDiscovery bool `protobuf:"varint,13,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"` DisableChromeParrot bool `protobuf:"varint,13,opt,name=disable_chrome_parrot,json=disableChromeParrot,proto3" json:"disable_chrome_parrot,omitempty"`
DisableChromeParrot bool `protobuf:"varint,14,opt,name=disable_chrome_parrot,json=disableChromeParrot,proto3" json:"disable_chrome_parrot,omitempty"` DisableGSO bool `protobuf:"varint,14,opt,name=disableGSO,proto3" json:"disableGSO,omitempty"`
DisableGSO bool `protobuf:"varint,15,opt,name=disableGSO,proto3" json:"disableGSO,omitempty"` MaxIncomingStreams int64 `protobuf:"varint,15,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
MaxIncomingStreams int64 `protobuf:"varint,16,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"` DisableStatelessReset bool `protobuf:"varint,16,opt,name=disable_stateless_reset,json=disableStatelessReset,proto3" json:"disable_stateless_reset,omitempty"`
DisableStatelessReset bool `protobuf:"varint,17,opt,name=disable_stateless_reset,json=disableStatelessReset,proto3" json:"disable_stateless_reset,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
func (x *QuicParams) Reset() { func (x *QuicParams) Reset() {
*x = QuicParams{} *x = QuicParams{}
mi := &file_transport_internet_config_proto_msgTypes[3] mi := &file_transport_internet_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -479,7 +418,7 @@ func (x *QuicParams) String() string {
func (*QuicParams) ProtoMessage() {} func (*QuicParams) ProtoMessage() {}
func (x *QuicParams) ProtoReflect() protoreflect.Message { func (x *QuicParams) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[3] mi := &file_transport_internet_config_proto_msgTypes[2]
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 {
@@ -492,7 +431,7 @@ func (x *QuicParams) ProtoReflect() protoreflect.Message {
// Deprecated: Use QuicParams.ProtoReflect.Descriptor instead. // Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.
func (*QuicParams) Descriptor() ([]byte, []int) { func (*QuicParams) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{3} return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
} }
func (x *QuicParams) GetCongestion() string { func (x *QuicParams) GetCongestion() string {
@@ -530,13 +469,6 @@ func (x *QuicParams) GetBrutalDisableLossCompensation() bool {
return false return false
} }
func (x *QuicParams) GetUdpHop() *UdpHop {
if x != nil {
return x.UdpHop
}
return nil
}
func (x *QuicParams) GetInitStreamReceiveWindow() uint64 { func (x *QuicParams) GetInitStreamReceiveWindow() uint64 {
if x != nil { if x != nil {
return x.InitStreamReceiveWindow return x.InitStreamReceiveWindow
@@ -614,58 +546,6 @@ func (x *QuicParams) GetDisableStatelessReset() bool {
return false return false
} }
type ProxyConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
TransportLayerProxy bool `protobuf:"varint,2,opt,name=transportLayerProxy,proto3" json:"transportLayerProxy,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProxyConfig) Reset() {
*x = ProxyConfig{}
mi := &file_transport_internet_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProxyConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProxyConfig) ProtoMessage() {}
func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead.
func (*ProxyConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
}
func (x *ProxyConfig) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
func (x *ProxyConfig) GetTransportLayerProxy() bool {
if x != nil {
return x.TransportLayerProxy
}
return false
}
type CustomSockopt struct { type CustomSockopt struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"` System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"`
@@ -680,7 +560,7 @@ type CustomSockopt struct {
func (x *CustomSockopt) Reset() { func (x *CustomSockopt) Reset() {
*x = CustomSockopt{} *x = CustomSockopt{}
mi := &file_transport_internet_config_proto_msgTypes[5] mi := &file_transport_internet_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -692,7 +572,7 @@ func (x *CustomSockopt) String() string {
func (*CustomSockopt) ProtoMessage() {} func (*CustomSockopt) ProtoMessage() {}
func (x *CustomSockopt) ProtoReflect() protoreflect.Message { func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[5] mi := &file_transport_internet_config_proto_msgTypes[3]
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 {
@@ -705,7 +585,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead. // Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
func (*CustomSockopt) Descriptor() ([]byte, []int) { func (*CustomSockopt) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{5} return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
} }
func (x *CustomSockopt) GetSystem() string { func (x *CustomSockopt) GetSystem() string {
@@ -785,7 +665,7 @@ type SocketConfig struct {
func (x *SocketConfig) Reset() { func (x *SocketConfig) Reset() {
*x = SocketConfig{} *x = SocketConfig{}
mi := &file_transport_internet_config_proto_msgTypes[6] mi := &file_transport_internet_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -797,7 +677,7 @@ func (x *SocketConfig) String() string {
func (*SocketConfig) ProtoMessage() {} func (*SocketConfig) ProtoMessage() {}
func (x *SocketConfig) ProtoReflect() protoreflect.Message { func (x *SocketConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[6] mi := &file_transport_internet_config_proto_msgTypes[4]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -810,7 +690,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead. // Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
func (*SocketConfig) Descriptor() ([]byte, []int) { func (*SocketConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{6} return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
} }
func (x *SocketConfig) GetMark() int32 { func (x *SocketConfig) GetMark() int32 {
@@ -972,7 +852,7 @@ type HappyEyeballsConfig struct {
func (x *HappyEyeballsConfig) Reset() { func (x *HappyEyeballsConfig) Reset() {
*x = HappyEyeballsConfig{} *x = HappyEyeballsConfig{}
mi := &file_transport_internet_config_proto_msgTypes[7] mi := &file_transport_internet_config_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -984,7 +864,7 @@ func (x *HappyEyeballsConfig) String() string {
func (*HappyEyeballsConfig) ProtoMessage() {} func (*HappyEyeballsConfig) ProtoMessage() {}
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message { func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[7] mi := &file_transport_internet_config_proto_msgTypes[5]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -997,7 +877,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead. // Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) { func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{7} return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
} }
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool { func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
@@ -1048,11 +928,7 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" + "\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" +
"\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" + "\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
"quicParams\x12N\n" + "quicParams\x12N\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"d\n" + "\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"\x8d\x06\n" +
"\x06UdpHop\x12\x14\n" +
"\x05ports\x18\x01 \x03(\rR\x05ports\x12!\n" +
"\finterval_min\x18\x02 \x01(\x03R\vintervalMin\x12!\n" +
"\finterval_max\x18\x03 \x01(\x03R\vintervalMax\"\xc7\x06\n" +
"\n" + "\n" +
"QuicParams\x12\x1e\n" + "QuicParams\x12\x1e\n" +
"\n" + "\n" +
@@ -1063,25 +939,21 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\tbrutal_up\x18\x03 \x01(\x04R\bbrutalUp\x12\x1f\n" + "\tbrutal_up\x18\x03 \x01(\x04R\bbrutalUp\x12\x1f\n" +
"\vbrutal_down\x18\x04 \x01(\x04R\n" + "\vbrutal_down\x18\x04 \x01(\x04R\n" +
"brutalDown\x12G\n" + "brutalDown\x12G\n" +
" brutal_disable_loss_compensation\x18\x05 \x01(\bR\x1dbrutalDisableLossCompensation\x128\n" + " brutal_disable_loss_compensation\x18\x05 \x01(\bR\x1dbrutalDisableLossCompensation\x12;\n" +
"\audp_hop\x18\x06 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" + "\x1ainit_stream_receive_window\x18\x06 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
"\x1ainit_stream_receive_window\x18\a \x01(\x04R\x17initStreamReceiveWindow\x129\n" + "\x19max_stream_receive_window\x18\a \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
"\x19max_stream_receive_window\x18\b \x01(\x04R\x16maxStreamReceiveWindow\x127\n" + "\x18init_conn_receive_window\x18\b \x01(\x04R\x15initConnReceiveWindow\x125\n" +
"\x18init_conn_receive_window\x18\t \x01(\x04R\x15initConnReceiveWindow\x125\n" + "\x17max_conn_receive_window\x18\t \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
"\x17max_conn_receive_window\x18\n" + "\x10max_idle_timeout\x18\n" +
" \x01(\x04R\x14maxConnReceiveWindow\x12(\n" + " \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x10max_idle_timeout\x18\v \x01(\x03R\x0emaxIdleTimeout\x12*\n" + "\x11keep_alive_period\x18\v \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x11keep_alive_period\x18\f \x01(\x03R\x0fkeepAlivePeriod\x12;\n" + "\x1adisable_path_mtu_discovery\x18\f \x01(\bR\x17disablePathMtuDiscovery\x122\n" +
"\x1adisable_path_mtu_discovery\x18\r \x01(\bR\x17disablePathMtuDiscovery\x122\n" + "\x15disable_chrome_parrot\x18\r \x01(\bR\x13disableChromeParrot\x12\x1e\n" +
"\x15disable_chrome_parrot\x18\x0e \x01(\bR\x13disableChromeParrot\x12\x1e\n" +
"\n" + "\n" +
"disableGSO\x18\x0f \x01(\bR\n" + "disableGSO\x18\x0e \x01(\bR\n" +
"disableGSO\x120\n" + "disableGSO\x120\n" +
"\x14max_incoming_streams\x18\x10 \x01(\x03R\x12maxIncomingStreams\x126\n" + "\x14max_incoming_streams\x18\x0f \x01(\x03R\x12maxIncomingStreams\x126\n" +
"\x17disable_stateless_reset\x18\x11 \x01(\bR\x15disableStatelessReset\"Q\n" + "\x17disable_stateless_reset\x18\x10 \x01(\bR\x15disableStatelessReset\"\x93\x01\n" +
"\vProxyConfig\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
"\rCustomSockopt\x12\x16\n" + "\rCustomSockopt\x12\x16\n" +
"\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" + "\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" +
"\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" + "\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" +
@@ -1165,42 +1037,39 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
} }
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3) var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_transport_internet_config_proto_goTypes = []any{ var file_transport_internet_config_proto_goTypes = []any{
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy (DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy (AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode (SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig (*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig (*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
(*UdpHop)(nil), // 5: xray.transport.internet.UdpHop (*QuicParams)(nil), // 5: xray.transport.internet.QuicParams
(*QuicParams)(nil), // 6: xray.transport.internet.QuicParams (*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt
(*ProxyConfig)(nil), // 7: xray.transport.internet.ProxyConfig (*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig
(*CustomSockopt)(nil), // 8: xray.transport.internet.CustomSockopt (*HappyEyeballsConfig)(nil), // 8: xray.transport.internet.HappyEyeballsConfig
(*SocketConfig)(nil), // 9: xray.transport.internet.SocketConfig (*serial.TypedMessage)(nil), // 9: xray.common.serial.TypedMessage
(*HappyEyeballsConfig)(nil), // 10: xray.transport.internet.HappyEyeballsConfig (*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
(*serial.TypedMessage)(nil), // 11: xray.common.serial.TypedMessage
(*net.IPOrDomain)(nil), // 12: xray.common.net.IPOrDomain
} }
var file_transport_internet_config_proto_depIdxs = []int32{ var file_transport_internet_config_proto_depIdxs = []int32{
11, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage 9, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
12, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain 10, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig 3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
11, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage 9, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
11, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage 9, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
11, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage 9, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
6, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams 5, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
9, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig 7, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
5, // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop 2, // 8: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
2, // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode 0, // 9: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
0, // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy 6, // 10: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
8, // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt 1, // 11: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
1, // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy 8, // 12: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
10, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig 13, // [13:13] is the sub-list for method output_type
14, // [14:14] is the sub-list for method output_type 13, // [13:13] is the sub-list for method input_type
14, // [14:14] is the sub-list for method input_type 13, // [13:13] is the sub-list for extension type_name
14, // [14:14] is the sub-list for extension type_name 13, // [13:13] is the sub-list for extension extendee
14, // [14:14] is the sub-list for extension extendee 0, // [0:13] is the sub-list for field type_name
0, // [0:14] is the sub-list for field type_name
} }
func init() { file_transport_internet_config_proto_init() } func init() { file_transport_internet_config_proto_init() }
@@ -1214,7 +1083,7 @@ func file_transport_internet_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
NumEnums: 3, NumEnums: 3,
NumMessages: 8, NumMessages: 6,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
+11 -23
View File
@@ -64,35 +64,23 @@ message StreamConfig {
SocketConfig socket_settings = 6; SocketConfig socket_settings = 6;
} }
message UdpHop {
repeated uint32 ports = 1;
int64 interval_min = 2;
int64 interval_max = 3;
}
message QuicParams { message QuicParams {
string congestion = 1; string congestion = 1;
string bbr_profile = 2; string bbr_profile = 2;
uint64 brutal_up = 3; uint64 brutal_up = 3;
uint64 brutal_down = 4; uint64 brutal_down = 4;
bool brutal_disable_loss_compensation = 5; bool brutal_disable_loss_compensation = 5;
UdpHop udp_hop = 6; uint64 init_stream_receive_window = 6;
uint64 init_stream_receive_window = 7; uint64 max_stream_receive_window = 7;
uint64 max_stream_receive_window = 8; uint64 init_conn_receive_window = 8;
uint64 init_conn_receive_window = 9; uint64 max_conn_receive_window = 9;
uint64 max_conn_receive_window = 10; int64 max_idle_timeout = 10;
int64 max_idle_timeout = 11; int64 keep_alive_period = 11;
int64 keep_alive_period = 12; bool disable_path_mtu_discovery = 12;
bool disable_path_mtu_discovery = 13; bool disable_chrome_parrot = 13;
bool disable_chrome_parrot = 14; bool disableGSO = 14;
bool disableGSO = 15; int64 max_incoming_streams = 15;
int64 max_incoming_streams = 16; bool disable_stateless_reset = 16;
bool disable_stateless_reset = 17;
}
message ProxyConfig {
string tag = 1;
bool transportLayerProxy = 2;
} }
message CustomSockopt { message CustomSockopt {
+238 -101
View File
@@ -2,106 +2,291 @@ package finalmask
import ( import (
"context" "context"
"net" "fmt"
"slices" "slices"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
) )
type Udpmask interface { type Dialer struct {
UDP() DialTCP func(net.Destination) (net.Conn, error)
DialUDP func(net.Destination) (net.Conn, error)
WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
} }
type UdpmaskManager struct { type ListenConfig struct {
udpmasks []Udpmask Listen func(net.Addr) (net.Listener, error)
ListenPacket func(net.Addr) (net.PacketConn, error)
} }
func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager { type TCPMask interface {
return &UdpmaskManager{ WrapConnClient(net.Conn, *net.Destination, *Dialer) (net.Conn, error)
udpmasks: udpmasks, WrapConnServer(net.Conn) (net.Conn, error)
// Listen(net.Listener) (net.Listener, error)
}
type UDPMask interface {
WrapPacketConnClient(net.PacketConn, *net.Destination, *Dialer) (net.PacketConn, error)
WrapPacketConnServer(net.PacketConn, net.Addr, *ListenConfig) (net.PacketConn, error)
}
type FinalMask struct {
tcpMasks []TCPMask
udpMasks []UDPMask
dialTCP func(context.Context, net.Destination) (net.Conn, error)
listen func(context.Context, net.Addr) (net.Listener, error)
dialUDP func(context.Context, net.Destination) (net.PacketConn, net.Addr, error)
listenPacket func(context.Context, net.Addr) (net.PacketConn, error)
}
func NewFinalMask(tcpMasks []TCPMask, udpMasks []UDPMask, dialTCP func(context.Context, net.Destination) (net.Conn, error), listen func(context.Context, net.Addr) (net.Listener, error), dialUDP func(context.Context, net.Destination) (net.PacketConn, net.Addr, error), listenPacket func(context.Context, net.Addr) (net.PacketConn, error)) *FinalMask {
slices.Reverse(tcpMasks)
slices.Reverse(udpMasks)
return &FinalMask{
tcpMasks: tcpMasks,
udpMasks: udpMasks,
dialTCP: dialTCP,
dialUDP: dialUDP,
listen: listen,
listenPacket: listenPacket,
} }
} }
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) { func (fm *FinalMask) DialTCP(ctx context.Context, dest net.Destination) (net.Conn, error) {
if len(fm.tcpMasks) == 0 {
return fm.dialTCP(ctx, dest)
}
for i := range fm.tcpMasks {
if i > 0 {
if _, ok := fm.tcpMasks[i].(interface{ HandleDial() }); ok {
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.tcpMasks[i])
}
}
}
var conn net.Conn
var err error
if _, ok := fm.tcpMasks[0].(interface{ HandleDial() }); !ok {
conn, err = fm.dialTCP(ctx, dest)
if err != nil {
return nil, err
}
}
dialer := &Dialer{
DialTCP: func(dest net.Destination) (net.Conn, error) {
return fm.dialTCP(ctx, dest)
},
DialUDP: func(dest net.Destination) (net.Conn, error) {
conn, addr, err := fm.dialUDP(ctx, dest)
if err != nil {
return nil, err
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, err
},
}
for i := range fm.tcpMasks {
var newConn net.Conn
newConn, err = fm.tcpMasks[i].WrapConnClient(conn, &dest, dialer)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
}
return conn, nil
}
func (fm *FinalMask) Listen(ctx context.Context, addr net.Addr) (net.Listener, error) {
if len(fm.tcpMasks) == 0 {
return fm.listen(ctx, addr)
}
off := 0
listener, err := fm.listen(ctx, addr)
if err != nil {
return nil, err
}
for i := range fm.tcpMasks {
if _, ok := fm.tcpMasks[i].(interface {
Listen(net.Listener) (net.Listener, error)
}); ok {
if i-off == 0 {
l, err := fm.tcpMasks[i].(interface {
Listen(net.Listener) (net.Listener, error)
}).Listen(listener)
if err != nil {
listener.Close()
return nil, err
}
listener = l
} else {
l, err := fm.tcpMasks[i].(interface {
Listen(net.Listener) (net.Listener, error)
}).Listen(&TCPListener{Listener: listener, tcpMasks: fm.tcpMasks[off:i]})
if err != nil {
listener.Close()
return nil, err
}
listener = l
}
off = i + 1
}
}
if off < len(fm.tcpMasks) {
return &TCPListener{Listener: listener, tcpMasks: fm.tcpMasks[off:]}, nil
}
return listener, nil
}
func (fm *FinalMask) DialUDP(ctx context.Context, dest net.Destination) (net.Conn, error) {
if len(fm.udpMasks) == 0 {
conn, addr, err := fm.dialUDP(ctx, dest)
if err != nil {
return nil, err
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, nil
}
for i := range fm.udpMasks {
if i > 0 {
if _, ok := fm.udpMasks[i].(interface{ HandleDial() }); ok {
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.udpMasks[i])
}
}
}
var conn net.PacketConn
var addr net.Addr
var err error
if _, ok := fm.udpMasks[0].(interface{ HandleDial() }); !ok {
conn, addr, err = fm.dialUDP(ctx, dest)
if err != nil {
return nil, err
}
}
dialer := &Dialer{
DialTCP: func(dest net.Destination) (net.Conn, error) {
return fm.dialTCP(ctx, dest)
},
DialUDP: func(dest net.Destination) (net.Conn, error) {
conn, addr, err := fm.dialUDP(ctx, dest)
if err != nil {
return nil, err
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, err
},
}
var sizes []int var sizes []int
var conns []net.PacketConn var conns []net.PacketConn
for i, mask := range slices.Backward(m.udpmasks) { for i := range fm.udpMasks {
if _, ok := mask.(headerConn); ok { var newConn net.PacketConn
conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1) if _, ok := fm.udpMasks[i].(interface{ HeaderConn() }); ok {
newConn, err = fm.udpMasks[i].WrapPacketConnClient(nil, nil, nil)
if err != nil { if err != nil {
_ = conn.Close()
return nil, err return nil, err
} }
sizes = append(sizes, conn.(headerSize).Size()) sizes = append(sizes, newConn.(interface{ Size() int }).Size())
conns = append(conns, conn) conns = append(conns, newConn)
} else { } else {
if len(conns) > 0 { if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw} conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
sizes = nil sizes = nil
conns = nil conns = nil
} }
newConn, err = fm.udpMasks[i].WrapPacketConnClient(conn, &dest, dialer)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
}
}
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
sizes = nil
conns = nil
}
if addr == nil {
addr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, nil
}
func (fm *FinalMask) ListenPacket(ctx context.Context, addr net.Addr) (net.PacketConn, error) {
if len(fm.udpMasks) == 0 {
return fm.listenPacket(ctx, addr)
}
for i := range fm.udpMasks {
if i > 0 {
if _, ok := fm.udpMasks[i].(interface{ HandleListen() }); ok {
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.udpMasks[i])
}
}
}
var conn net.PacketConn
var err error var err error
raw, err = mask.WrapPacketConnClient(raw, i, len(m.udpmasks)-1) if _, ok := fm.udpMasks[0].(interface{ HandleListen() }); !ok {
conn, err = fm.listenPacket(ctx, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
lc := &ListenConfig{
Listen: func(addr net.Addr) (net.Listener, error) { return fm.listen(ctx, addr) },
ListenPacket: func(addr net.Addr) (net.PacketConn, error) { return fm.listenPacket(ctx, addr) },
} }
if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
return raw, nil
}
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int var sizes []int
var conns []net.PacketConn var conns []net.PacketConn
for i, mask := range slices.Backward(m.udpmasks) { for i := range fm.udpMasks {
if _, ok := mask.(headerConn); ok { var newConn net.PacketConn
conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1) if _, ok := fm.udpMasks[i].(interface{ HeaderConn() }); ok {
newConn, err = fm.udpMasks[i].WrapPacketConnServer(nil, nil, nil)
if err != nil { if err != nil {
_ = conn.Close()
return nil, err return nil, err
} }
sizes = append(sizes, conn.(headerSize).Size()) sizes = append(sizes, newConn.(interface{ Size() int }).Size())
conns = append(conns, conn) conns = append(conns, newConn)
} else { } else {
if len(conns) > 0 { if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw} conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
sizes = nil sizes = nil
conns = nil conns = nil
} }
var err error newConn, err = fm.udpMasks[i].WrapPacketConnServer(conn, addr, lc)
raw, err = mask.WrapPacketConnServer(raw, i, len(m.udpmasks)-1)
if err != nil { if err != nil {
_ = conn.Close()
return nil, err return nil, err
} }
conn = newConn
} }
} }
if len(conns) > 0 { if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw} conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
sizes = nil sizes = nil
conns = nil conns = nil
} }
return raw, nil return conn, nil
} }
const ( const (
UDPSize = 4096 UDPSize = 4096
) )
type headerConn interface { type PacketConnWrapper struct {
HeaderConn() net.PacketConn
udpAddr net.Addr
} }
type headerSize interface { func (c *PacketConnWrapper) RemoteAddr() net.Addr {
Size() int return c.udpAddr
}
func (c *PacketConnWrapper) Read(b []byte) (n int, err error) {
n, _, err = c.PacketConn.ReadFrom(b)
return
}
func (c *PacketConnWrapper) Write(b []byte) (n int, err error) {
return c.PacketConn.WriteTo(b, c.udpAddr)
} }
type headerManagerConn struct { type headerManagerConn struct {
@@ -194,75 +379,27 @@ func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error)
return len(p), nil return len(p), nil
} }
type Tcpmask interface { type TCPListener struct {
TCP()
WrapConnClient(net.Conn) (net.Conn, error)
WrapConnServer(net.Conn) (net.Conn, error)
}
type TcpmaskManager struct {
tcpmasks []Tcpmask
}
func NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager {
return &TcpmaskManager{
tcpmasks: tcpmasks,
}
}
func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
var err error
for _, mask := range slices.Backward(m.tcpmasks) {
raw, err = mask.WrapConnClient(raw)
if err != nil {
return nil, err
}
}
return raw, nil
}
func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
var err error
for _, mask := range slices.Backward(m.tcpmasks) {
raw, err = mask.WrapConnServer(raw)
if err != nil {
return nil, err
}
}
return raw, nil
}
func (m *TcpmaskManager) WrapListener(l net.Listener) (net.Listener, error) {
return NewTcpListener(m, l)
}
type tcpListener struct {
m *TcpmaskManager
net.Listener net.Listener
tcpMasks []TCPMask
} }
func NewTcpListener(m *TcpmaskManager, l net.Listener) (net.Listener, error) { func (l *TCPListener) Accept() (net.Conn, error) {
return &tcpListener{
m: m,
Listener: l,
}, nil
}
func (l *tcpListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept() conn, err := l.Listener.Accept()
if err != nil { if err != nil {
return conn, err return conn, err
} }
newConn, err := l.m.WrapConnServer(conn) for i := range l.tcpMasks {
var newConn net.Conn
newConn, err = l.tcpMasks[i].WrapConnServer(conn)
if err != nil { if err != nil {
errors.LogDebugInner(context.Background(), err, "mask err")
_ = conn.Close() _ = conn.Close()
return nil, err return nil, err
} }
conn = newConn
return newConn, nil }
return conn, nil
} }
type TcpMaskConn interface { type TcpMaskConn interface {
@@ -1,14 +1,14 @@
package fragment package fragment
import "net" import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func (c *Config) TCP() { func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return NewConnClient(c, conn, false)
} }
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
return NewConnClient(c, raw, false) return NewConnServer(c, conn, true)
}
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServer(c, raw, true)
} }
@@ -1,35 +1,30 @@
package custom package custom
import ( import (
"net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func (c *TCPConfig) TCP() {} func (c *TCPConfig) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return NewConnClientTCP(c, conn)
func (c *TCPConfig) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClientTCP(c, raw)
} }
func (c *TCPConfig) WrapConnServer(raw net.Conn) (net.Conn, error) { func (c *TCPConfig) WrapConnServer(conn net.Conn) (net.Conn, error) {
return NewConnServerTCP(c, raw) return NewConnServerTCP(c, conn)
} }
func (c *UDPConfig) UDP() {} func (c *UDPConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClientUDP(c, conn)
func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClientUDP(c, raw)
} }
func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *UDPConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServerUDP(c, raw) return NewConnServerUDP(c, conn)
} }
func (c *UDPStandaloneConfig) UDP() {} func (c *UDPStandaloneConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClientUDPStandalone(c, conn)
func (c *UDPStandaloneConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClientUDPStandalone(c, raw)
} }
func (c *UDPStandaloneConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *UDPStandaloneConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServerUDPStandalone(c, raw) return NewConnServerUDPStandalone(c, conn)
} }
@@ -9,8 +9,6 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) { func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
@@ -156,7 +154,7 @@ func TestMetadataUDPStandaloneWriteUsesRemotePort(t *testing.T) {
} }
defer serverRaw.Close() defer serverRaw.Close()
client, err := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg}).WrapPacketConnClient(clientRaw) client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -301,7 +299,7 @@ func TestMetadataTCPHandshakeUsesEndpointPorts(t *testing.T) {
} }
defer serverRaw.Close() defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw) client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -5,8 +5,6 @@ import (
"net" "net"
"testing" "testing"
"time" "time"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) { func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) {
@@ -48,7 +46,6 @@ func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
}, },
}, },
} }
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0") clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil { if err != nil {
@@ -62,11 +59,11 @@ func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
} }
defer serverRaw.Close() defer serverRaw.Close()
client, err := maskManager.WrapPacketConnClient(clientRaw) client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
server, err := maskManager.WrapPacketConnServer(serverRaw) server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -37,7 +37,7 @@ func TestDSLTCPHandshakeReusesCapturedValue(t *testing.T) {
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
client, err := cfg.WrapConnClient(clientRaw) client, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -117,7 +117,7 @@ func TestDSLTCPClientRejectsMismatchedResponseSequence(t *testing.T) {
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw) client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1,17 +1,16 @@
package aes128gcm package aes128gcm
import ( import (
"net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {} func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, raw) return NewConnClient(c, conn)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, raw) return NewConnServer(c, conn)
} }
@@ -1,17 +1,16 @@
package header package header
import ( import (
"net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {} func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, raw) return NewConnClient(c, conn)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, raw) return NewConnServer(c, conn)
} }
@@ -1,17 +1,16 @@
package original package original
import ( import (
"net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {} func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, raw) return NewConnClient(c, conn)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, raw) return NewConnServer(c, conn)
} }
+8 -8
View File
@@ -1,14 +1,14 @@
package noise package noise
import "net" import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func (c *Config) UDP() { func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnClient(c, raw) return NewConnServer(c, conn)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
} }
+6 -19
View File
@@ -1,27 +1,14 @@
package realm package realm
import ( import (
"net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
) )
func (c *Config) UDP() {} func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
_, ok1 := raw.(*internet.FakePacketConn)
_, ok2 := raw.(*udphop.UdpHopPacketConn)
if level != 0 || ok1 || ok2 {
return nil, errors.New("realm requires being at the outermost level")
}
return NewConnClient(c, raw)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
if level != 0 { return NewConnServer(c, conn)
return nil, errors.New("realm requires being at the outermost level")
}
return NewConnServer(c, raw)
} }
@@ -1,27 +1,24 @@
package salamander package salamander
import ( import (
"net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {} func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewSalamanderConnClient(c, raw) return NewSalamanderConnClient(c, conn)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewSalamanderConnServer(c, raw) return NewSalamanderConnServer(c, conn)
} }
func (c *GeckoConfig) UDP() {} func (c *GeckoConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewGeckoConnClient(c, conn)
func (c *GeckoConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewGeckoConnClient(c, raw)
} }
func (c *GeckoConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *GeckoConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewGeckoConnServer(c, raw) return NewGeckoConnServer(c, conn)
} }
+10 -23
View File
@@ -1,25 +1,18 @@
package sudoku package sudoku
import ( import (
"net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/common/errors"
) )
func (c *Config) TCP() {
}
func (c *Config) UDP() {
}
// Sudoku in finalmask mode is a pure appearance transform with no standalone handshake. // Sudoku in finalmask mode is a pure appearance transform with no standalone handshake.
// TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes. // TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes.
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return newPackedDirectionalConn(raw, c, true) return newPackedDirectionalConn(conn, c, true)
} }
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) { func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(raw, c, false) return newPackedDirectionalConn(conn, c, false)
} }
func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (net.Conn, error) { func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (net.Conn, error) {
@@ -42,16 +35,10 @@ func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (ne
return newWrappedConn(raw, reader, writer), nil return newWrappedConn(raw, reader, writer), nil
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
if level != levelCount { return NewUDPConn(conn, c)
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
}
return NewUDPConn(raw, c)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
if level != levelCount { return NewUDPConn(conn, c)
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
}
return NewUDPConn(raw, c)
} }
+54 -52
View File
@@ -2,12 +2,14 @@ package finalmask_test
import ( import (
"bytes" "bytes"
"context"
"io" "io"
"net" gonet "net"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
) )
@@ -20,11 +22,14 @@ func mustSendRecvTcp(
) { ) {
t.Helper() t.Helper()
waitCh := make(chan error)
go func() { go func() {
_, err := from.Write(msg) _, err := from.Write(msg)
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
} }
close(waitCh)
}() }()
buf := make([]byte, 1024) buf := make([]byte, 1024)
@@ -40,18 +45,23 @@ func mustSendRecvTcp(
if !bytes.Equal(buf[:n], msg) { if !bytes.Equal(buf[:n], msg) {
t.Fatalf("unexpected data %q", buf[:n]) t.Fatalf("unexpected data %q", buf[:n])
} }
<-waitCh
} }
type layerMaskTcp struct { type layerMaskTcp struct {
name string name string
mask finalmask.Tcpmask mask finalmask.TCPMask
} }
type failingWrapMask struct{} type failingWrapMask struct{}
func (failingWrapMask) TCP() {} func (failingWrapMask) TCP() {}
func (f failingWrapMask) WrapConnClient(raw net.Conn) (net.Conn, error) { return raw, nil } func (f failingWrapMask) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
func (f failingWrapMask) WrapConnServer(raw net.Conn) (net.Conn, error) { return conn, nil
}
func (f failingWrapMask) WrapConnServer(conn net.Conn) (net.Conn, error) {
return nil, io.ErrClosedPipe return nil, io.ErrClosedPipe
} }
@@ -92,32 +102,31 @@ func TestConnReadWrite(t *testing.T) {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
mask := c.mask mask := c.mask
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{mask}) dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
return net.Dial("tcp", dest.NetAddr())
}
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
return net.Listen("tcp", addr.String())
}
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{mask}, nil, dialTCP, listen, nil, nil)
ln, err := net.Listen("tcp", "127.0.0.1:0") listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { listener.Close() })
client, err := net.Dial("tcp", ln.Addr().String()) client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { client.Close() })
client, err = maskManager.WrapConnClient(client) server, err := listener.Accept()
if err != nil {
t.Fatal(err)
}
server, err := ln.Accept()
if err != nil {
t.Fatal(err)
}
server, err = maskManager.WrapConnServer(server)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { server.Close() })
_ = client.SetDeadline(time.Now().Add(time.Second)) _ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second)) _ = server.SetDeadline(time.Now().Add(time.Second))
@@ -150,34 +159,32 @@ func TestTCPcustomStaticHandshakeRoundTrip(t *testing.T) {
}, },
}, },
} }
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{cfg})
ln, err := net.Listen("tcp", "127.0.0.1:0") dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
if err != nil { return net.Dial("tcp", dest.NetAddr())
t.Fatal(err)
} }
defer ln.Close() listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
return net.Listen("tcp", addr.String())
}
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{cfg}, nil, dialTCP, listen, nil, nil)
clientRaw, err := net.Dial("tcp", ln.Addr().String()) listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer clientRaw.Close() defer listener.Close()
serverRaw, err := ln.Accept() client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer serverRaw.Close() defer client.Close()
client, err := maskManager.WrapConnClient(clientRaw) server, err := listener.Accept()
if err != nil {
t.Fatal(err)
}
server, err := maskManager.WrapConnServer(serverRaw)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer server.Close()
_ = client.SetDeadline(time.Now().Add(time.Second)) _ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second)) _ = server.SetDeadline(time.Now().Add(time.Second))
@@ -220,11 +227,11 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
}, },
} }
clientRaw, serverRaw := net.Pipe() clientRaw, serverRaw := gonet.Pipe()
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw) client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -257,42 +264,37 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
} }
func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) { func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) {
clientManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}}) dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
serverManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}}) return net.Dial("tcp", dest.NetAddr())
}
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
return net.Listen("tcp", addr.String())
}
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{failingWrapMask{}}, nil, dialTCP, listen, nil, nil)
rawLn, err := net.Listen("tcp", "127.0.0.1:0") listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
if err != nil {
t.Fatal(err)
}
defer rawLn.Close()
ln, err := serverManager.WrapListener(rawLn)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer listener.Close()
accepted := make(chan struct { accepted := make(chan struct {
conn net.Conn conn net.Conn
err error err error
}, 1) }, 1)
go func() { go func() {
conn, err := ln.Accept() conn, err := listener.Accept()
accepted <- struct { accepted <- struct {
conn net.Conn conn net.Conn
err error err error
}{conn: conn, err: err} }{conn: conn, err: err}
}() }()
clientRaw, err := net.Dial("tcp", rawLn.Addr().String()) client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
client, err := clientManager.WrapConnClient(clientRaw)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer client.Close()
_ = client.SetDeadline(time.Now().Add(time.Second)) _ = client.SetDeadline(time.Now().Add(time.Second))
+52 -59
View File
@@ -2,13 +2,15 @@ package finalmask_test
import ( import (
"bytes" "bytes"
"context"
"encoding/binary" "encoding/binary"
"io" "io"
"net" gonet "net"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
@@ -51,7 +53,7 @@ func mustSendRecv(
type layerMask struct { type layerMask struct {
name string name string
mask finalmask.Udpmask mask finalmask.UDPMask
layers int layers int
} }
@@ -213,25 +215,23 @@ func newStandaloneStunLikeUDPServerConfig() *custom.UDPStandaloneConfig {
func newUDPClientServerPair(t *testing.T, cfg *custom.UDPStandaloneConfig) (net.PacketConn, net.PacketConn, net.PacketConn, net.PacketConn) { func newUDPClientServerPair(t *testing.T, cfg *custom.UDPStandaloneConfig) (net.PacketConn, net.PacketConn, net.PacketConn, net.PacketConn) {
t.Helper() t.Helper()
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0") clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { _ = clientRaw.Close() }) t.Cleanup(func() { _ = clientRaw.Close() })
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0") serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { _ = serverRaw.Close() }) t.Cleanup(func() { _ = serverRaw.Close() })
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg}) client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
server, err := maskManager.WrapPacketConnServer(serverRaw) server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -348,31 +348,39 @@ func TestPacketConnReadWrite(t *testing.T) {
if layers <= 0 { if layers <= 0 {
layers = 1 layers = 1
} }
masks := make([]finalmask.Udpmask, 0, layers) masks := make([]finalmask.UDPMask, 0, layers)
for i := 0; i < layers; i++ { for i := 0; i < layers; i++ {
masks = append(masks, mask) masks = append(masks, mask)
} }
maskManager := finalmask.NewUdpmaskManager(masks)
client, err := net.ListenPacket("udp", "127.0.0.1:0") dialUDP := func(ctx context.Context, dest net.Destination) (net.PacketConn, net.Addr, error) {
udpAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
if err != nil {
return nil, nil, err
}
conn, err := gonet.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
return nil, nil, err
}
return conn, udpAddr, nil
}
listenPacket := func(ctx context.Context, addr net.Addr) (net.PacketConn, error) {
return gonet.ListenPacket(addr.Network(), addr.String())
}
finalMask := finalmask.NewFinalMask(nil, masks, nil, nil, dialUDP, listenPacket)
server, err := finalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: net.LocalHostIP.IP()})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { server.Close() })
client, err = maskManager.WrapPacketConnClient(client) clientConn, err := finalMask.DialUDP(context.Background(), net.UDPDestination(net.IPAddress(server.LocalAddr().(*net.UDPAddr).IP), net.Port(server.LocalAddr().(*net.UDPAddr).Port)))
if err != nil {
t.Fatal(err)
}
server, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
server, err = maskManager.WrapPacketConnServer(server)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { clientConn.Close() })
client := clientConn.(*finalmask.PacketConnWrapper).PacketConn
_ = client.SetDeadline(time.Now().Add(time.Second)) _ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second)) _ = server.SetDeadline(time.Now().Add(time.Second))
@@ -397,21 +405,20 @@ func TestUDPcustomStaticHeaderWireShape(t *testing.T) {
{Rand: 1, RandMin: 0x30, RandMax: 0x40}, {Rand: 1, RandMin: 0x30, RandMax: 0x40},
}, },
} }
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0") clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer clientRaw.Close() defer clientRaw.Close()
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0") serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer serverRaw.Close() defer serverRaw.Close()
client, err := maskManager.WrapPacketConnClient(clientRaw) client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -642,11 +649,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_ascii", Ascii: "prefer_ascii",
} }
clientRaw, serverRaw := net.Pipe() clientRaw, serverRaw := gonet.Pipe()
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw) clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -683,11 +690,11 @@ func TestSudokuBDD(t *testing.T) {
PaddingMax: 0, PaddingMax: 0,
} }
clientRaw, serverRaw := net.Pipe() clientRaw, serverRaw := gonet.Pipe()
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw) clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -738,10 +745,10 @@ func TestSudokuBDD(t *testing.T) {
countWireBytes := func(wrapServer func(net.Conn, *sudoku.Config) (net.Conn, error), cfg *sudoku.Config) int64 { countWireBytes := func(wrapServer func(net.Conn, *sudoku.Config) (net.Conn, error), cfg *sudoku.Config) int64 {
t.Helper() t.Helper()
clientRaw, serverRaw := net.Pipe() clientRaw, serverRaw := gonet.Pipe()
watchedServerRaw := &countingConn{Conn: serverRaw} watchedServerRaw := &countingConn{Conn: serverRaw}
clientConn, err := cfg.WrapConnClient(clientRaw) clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -793,11 +800,11 @@ func TestSudokuBDD(t *testing.T) {
CustomTables: []string{"xpxvvpvv", "vxpvxvvp"}, CustomTables: []string{"xpxvvpvv", "vxpvxvvp"},
} }
clientRaw, serverRaw := net.Pipe() clientRaw, serverRaw := gonet.Pipe()
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw) clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -835,11 +842,11 @@ func TestSudokuBDD(t *testing.T) {
PaddingMax: 0, PaddingMax: 0,
} }
clientRaw, serverRaw := net.Pipe() clientRaw, serverRaw := gonet.Pipe()
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw) clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -868,19 +875,6 @@ func TestSudokuBDD(t *testing.T) {
} }
}) })
t.Run("GivenSudokuUDPMask_WhenNotInnermost_ThenWrapFails", func(t *testing.T) {
cfg := &sudoku.Config{Password: "sudoku-udp"}
raw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer raw.Close()
if _, err := cfg.WrapPacketConnClient(raw, 0, 1); err == nil {
t.Fatal("expected innermost check failure")
}
})
t.Run("GivenSudokuMultiTableUDPMask_WhenClientSendsMultipleDatagrams_ThenPayloadMatches", func(t *testing.T) { t.Run("GivenSudokuMultiTableUDPMask_WhenClientSendsMultipleDatagrams_ThenPayloadMatches", func(t *testing.T) {
cfg := &sudoku.Config{ cfg := &sudoku.Config{
Password: "sudoku-udp-multi", Password: "sudoku-udp-multi",
@@ -889,25 +883,24 @@ func TestSudokuBDD(t *testing.T) {
PaddingMin: 0, PaddingMin: 0,
PaddingMax: 0, PaddingMax: 0,
} }
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0") clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer clientRaw.Close() defer clientRaw.Close()
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0") serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer serverRaw.Close() defer serverRaw.Close()
client, err := maskManager.WrapPacketConnClient(clientRaw) client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
server, err := maskManager.WrapPacketConnServer(serverRaw) server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -961,7 +954,7 @@ func TestSudokuBDD(t *testing.T) {
} }
defer serverRaw.Close() defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw) clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1008,11 +1001,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_entropy", Ascii: "prefer_entropy",
} }
clientRaw, serverRaw := net.Pipe() clientRaw, serverRaw := gonet.Pipe()
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw) clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1032,11 +1025,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_entropy", Ascii: "prefer_entropy",
} }
clientRaw, serverRaw := net.Pipe() clientRaw, serverRaw := gonet.Pipe()
defer clientRaw.Close() defer clientRaw.Close()
defer serverRaw.Close() defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw) clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -0,0 +1,17 @@
package udphop
import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func (c *Config) HandleDial() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewUDPHopConn(c, dest, dialer)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return nil, errors.New("udphop: client only")
}
@@ -0,0 +1,178 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: transport/internet/finalmask/udphop/config.proto
package udphop
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
Local bool `protobuf:"varint,2,opt,name=local,proto3" json:"local,omitempty"`
Remote bool `protobuf:"varint,3,opt,name=remote,proto3" json:"remote,omitempty"`
RemoteOnce bool `protobuf:"varint,4,opt,name=remote_once,json=remoteOnce,proto3" json:"remote_once,omitempty"`
IntervalMin int64 `protobuf:"varint,5,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
IntervalMax int64 `protobuf:"varint,6,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
RemoteIPs []string `protobuf:"bytes,7,rep,name=remoteIPs,proto3" json:"remoteIPs,omitempty"`
RemotePorts []uint32 `protobuf:"varint,8,rep,packed,name=remote_ports,json=remotePorts,proto3" json:"remote_ports,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_finalmask_udphop_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_udphop_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_udphop_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetLocal() bool {
if x != nil {
return x.Local
}
return false
}
func (x *Config) GetRemote() bool {
if x != nil {
return x.Remote
}
return false
}
func (x *Config) GetRemoteOnce() bool {
if x != nil {
return x.RemoteOnce
}
return false
}
func (x *Config) GetIntervalMin() int64 {
if x != nil {
return x.IntervalMin
}
return 0
}
func (x *Config) GetIntervalMax() int64 {
if x != nil {
return x.IntervalMax
}
return 0
}
func (x *Config) GetRemoteIPs() []string {
if x != nil {
return x.RemoteIPs
}
return nil
}
func (x *Config) GetRemotePorts() []uint32 {
if x != nil {
return x.RemotePorts
}
return nil
}
var File_transport_internet_finalmask_udphop_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_udphop_config_proto_rawDesc = "" +
"\n" +
"0transport/internet/finalmask/udphop/config.proto\x12(xray.transport.internet.finalmask.udphop\"\xe4\x01\n" +
"\x06Config\x12\x14\n" +
"\x05local\x18\x02 \x01(\bR\x05local\x12\x16\n" +
"\x06remote\x18\x03 \x01(\bR\x06remote\x12\x1f\n" +
"\vremote_once\x18\x04 \x01(\bR\n" +
"remoteOnce\x12!\n" +
"\finterval_min\x18\x05 \x01(\x03R\vintervalMin\x12!\n" +
"\finterval_max\x18\x06 \x01(\x03R\vintervalMax\x12\x1c\n" +
"\tremoteIPs\x18\a \x03(\tR\tremoteIPs\x12!\n" +
"\fremote_ports\x18\b \x03(\rR\vremotePortsJ\x04\b\x01\x10\x02B\x9a\x01\n" +
",com.xray.transport.internet.finalmask.udphopP\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/udphop\xaa\x02(Xray.Transport.Internet.Finalmask.Udphopb\x06proto3"
var (
file_transport_internet_finalmask_udphop_config_proto_rawDescOnce sync.Once
file_transport_internet_finalmask_udphop_config_proto_rawDescData []byte
)
func file_transport_internet_finalmask_udphop_config_proto_rawDescGZIP() []byte {
file_transport_internet_finalmask_udphop_config_proto_rawDescOnce.Do(func() {
file_transport_internet_finalmask_udphop_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_udphop_config_proto_rawDesc), len(file_transport_internet_finalmask_udphop_config_proto_rawDesc)))
})
return file_transport_internet_finalmask_udphop_config_proto_rawDescData
}
var file_transport_internet_finalmask_udphop_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_transport_internet_finalmask_udphop_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.transport.internet.finalmask.udphop.Config
}
var file_transport_internet_finalmask_udphop_config_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_udphop_config_proto_init() }
func file_transport_internet_finalmask_udphop_config_proto_init() {
if File_transport_internet_finalmask_udphop_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_udphop_config_proto_rawDesc), len(file_transport_internet_finalmask_udphop_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_finalmask_udphop_config_proto_goTypes,
DependencyIndexes: file_transport_internet_finalmask_udphop_config_proto_depIdxs,
MessageInfos: file_transport_internet_finalmask_udphop_config_proto_msgTypes,
}.Build()
File_transport_internet_finalmask_udphop_config_proto = out.File
file_transport_internet_finalmask_udphop_config_proto_goTypes = nil
file_transport_internet_finalmask_udphop_config_proto_depIdxs = nil
}
@@ -0,0 +1,19 @@
syntax = "proto3";
package xray.transport.internet.finalmask.udphop;
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Udphop";
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/udphop";
option java_package = "com.xray.transport.internet.finalmask.udphop";
option java_multiple_files = true;
message Config {
reserved 1;
bool local = 2;
bool remote = 3;
bool remote_once = 4;
int64 interval_min = 5;
int64 interval_max = 6;
repeated string remoteIPs = 7;
repeated uint32 remote_ports = 8;
}
+289
View File
@@ -0,0 +1,289 @@
package udphop
import (
"context"
"crypto/rand"
goerrors "errors"
"io"
mrand "math/rand"
"net/netip"
"sync"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
var pool = sync.Pool{
New: func() any {
return make([]byte, finalmask.UDPSize)
},
}
type packet struct {
p []byte
addr net.Addr
err error
}
type udpHopConn struct {
dialer *finalmask.Dialer
local bool
remote bool
intervalMin int64
intervalMax int64
remoteIPs []netip.Prefix
remotePorts []uint32
deadline time.Time
readDeadline time.Time
writeDeadline time.Time
pre net.PacketConn
cur net.PacketConn
addr *net.UDPAddr
readCh chan packet
closeCh chan struct{}
wg sync.WaitGroup
mu sync.RWMutex
}
func NewUDPHopConn(c *Config, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
if c.IntervalMin < 5 || c.IntervalMax < 5 {
return nil, errors.New("invalid interval")
}
remoteIPs := make([]netip.Prefix, 0, len(c.RemoteIPs))
for _, ip := range c.RemoteIPs {
remoteIPs = append(remoteIPs, netip.MustParsePrefix(ip))
}
remotePorts := c.RemotePorts
if c.Remote || c.RemoteOnce {
if len(remoteIPs) > 0 {
dest.Address = net.IPAddress(randPrefix(remoteIPs[mrand.Intn(len(remoteIPs))]))
}
if len(remotePorts) > 0 {
dest.Port = net.Port(remotePorts[mrand.Intn(len(remotePorts))])
}
}
conn, err := dialer.DialUDP(*dest)
if err != nil {
return nil, err
}
cur := conn.(*finalmask.PacketConnWrapper).PacketConn
addr := conn.RemoteAddr().(*net.UDPAddr)
client := &udpHopConn{
dialer: dialer,
local: c.Local,
remote: c.Remote,
intervalMin: c.IntervalMin,
intervalMax: c.IntervalMax,
remoteIPs: remoteIPs,
remotePorts: remotePorts,
cur: cur,
addr: addr,
readCh: make(chan packet),
closeCh: make(chan struct{}),
}
go client.run()
client.wg.Add(1)
go client.recv(client.cur)
return client, nil
}
func (c *udpHopConn) closed() bool {
select {
case <-c.closeCh:
return true
default:
return false
}
}
func (c *udpHopConn) run() {
ticker := time.NewTicker(time.Second * time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1)))
defer ticker.Stop()
for {
select {
case <-c.closeCh:
return
case <-ticker.C:
ticker.Reset(time.Second * time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1)))
c.hop()
}
}
}
func (c *udpHopConn) hop() {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed() {
return
}
oldIP := c.addr.IP
oldPort := c.addr.Port
if c.remote {
if len(c.remoteIPs) > 0 {
c.addr.IP = randPrefix(c.remoteIPs[mrand.Intn(len(c.remoteIPs))])
}
if len(c.remotePorts) > 0 {
c.addr.Port = int(c.remotePorts[mrand.Intn(len(c.remotePorts))])
}
}
if c.local {
conn, err := c.dialer.DialUDP(net.UDPDestination(net.IPAddress(c.addr.IP), net.Port(c.addr.Port)))
if err != nil {
c.addr.IP = oldIP
c.addr.Port = oldPort
errors.LogErrorInner(context.Background(), err, "hop err")
return
}
conn.SetDeadline(c.deadline)
conn.SetReadDeadline(c.readDeadline)
conn.SetWriteDeadline(c.writeDeadline)
if c.pre != nil {
_ = c.pre.Close()
}
c.pre = c.cur
c.cur = conn.(*finalmask.PacketConnWrapper).PacketConn
c.wg.Add(1)
go c.recv(c.cur)
}
}
func (c *udpHopConn) recv(conn net.PacketConn) {
defer c.wg.Done()
for {
p := pool.Get().([]byte)
n, addr, err := conn.ReadFrom(p)
if err != nil {
pool.Put(p[:cap(p)])
if c.closed() {
return
}
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{err: err}:
case <-c.closeCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err")
return
}
select {
case c.readCh <- packet{p: p[:n], addr: addr}:
case <-c.closeCh:
pool.Put(p[:cap(p)])
return
}
}
}
func (c *udpHopConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet, ok := <-c.readCh
if ok {
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p[:cap(packet.p)])
}
return n, packet.addr, packet.err
}
return 0, nil, io.ErrClosedPipe
}
func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.mu.RLock()
defer c.mu.RUnlock()
_, err = c.cur.WriteTo(p, c.addr)
if err != nil {
errors.LogErrorInner(context.Background(), err, "send err")
return 0, err
}
return len(p), nil
}
func (c *udpHopConn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed() {
return nil
}
close(c.closeCh)
if c.pre != nil {
_ = c.pre.Close()
}
_ = c.cur.Close()
c.wg.Wait()
select {
case packet := <-c.readCh:
if packet.p != nil {
pool.Put(packet.p[:cap(packet.p)])
}
default:
}
close(c.readCh)
return nil
}
func (c *udpHopConn) LocalAddr() net.Addr {
c.mu.RLock()
defer c.mu.RUnlock()
return c.cur.LocalAddr()
}
func (c *udpHopConn) SetDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.deadline = t
if c.pre != nil {
_ = c.pre.SetDeadline(t)
}
return c.cur.SetDeadline(t)
}
func (c *udpHopConn) SetReadDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.readDeadline = t
if c.pre != nil {
_ = c.pre.SetReadDeadline(t)
}
return c.cur.SetReadDeadline(t)
}
func (c *udpHopConn) SetWriteDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.writeDeadline = t
if c.pre != nil {
_ = c.pre.SetWriteDeadline(t)
}
return c.cur.SetWriteDeadline(t)
}
func randPrefix(p netip.Prefix) []byte {
if p.IsSingleIP() {
return p.Addr().AsSlice()
}
b := p.Addr().AsSlice()
prefix := p.Bits()
var new [16]byte
common.Must2(rand.Read(new[:len(b)]))
i := prefix / 8
j := prefix % 8
if i+1 < len(b) {
copy(b[i+1:], new[i+1:])
}
mask := byte(0xff << (8 - j))
b[i] = (b[i] & mask) | (new[i] &^ mask)
return b
}
+6 -16
View File
@@ -1,24 +1,14 @@
package xdns package xdns
import ( import (
"net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func (c *Config) UDP() { func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
// _, ok1 := raw.(*internet.FakePacketConn) return NewConnServer(c, conn)
// _, ok2 := raw.(*udphop.UdpHopPacketConn)
// if level != 0 || ok1 || ok2 {
// return nil, errors.New("xdns requires being at the outermost level")
// }
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
// if level != 0 {
// return nil, errors.New("xdns requires being at the outermost level")
// }
return NewConnServer(c, raw)
} }
+54 -47
View File
@@ -8,8 +8,7 @@ import (
goerrors "errors" goerrors "errors"
"fmt" "fmt"
"io" "io"
mathrand "math/rand" mrand "math/rand"
"net"
"net/netip" "net/netip"
"sync" "sync"
"time" "time"
@@ -17,6 +16,7 @@ import (
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/finalmask"
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
@@ -36,20 +36,21 @@ type packet struct {
} }
type xicmpConnClient struct { type xicmpConnClient struct {
conn net.PacketConn
icmp4 *icmp.PacketConn icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn icmp6 *icmp.PacketConn
udp bool udp bool
ips []netip.Addr ips []netip.Addr
ip net.IP
clientID [8]byte clientID [8]byte
id int id int
seq int seq int
readCh chan packet readCh chan packet
closedCh chan struct{} closeCh chan struct{}
wg sync.WaitGroup
mu sync.Mutex mu sync.Mutex
} }
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) { func NewConnClient(c *Config, dest *net.Destination) (net.PacketConn, error) {
var icmp4, icmp6 *icmp.PacketConn var icmp4, icmp6 *icmp.PacketConn
var err4, err6 error var err4, err6 error
if c.DGRAM { if c.DGRAM {
@@ -68,35 +69,39 @@ func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
ips = append(ips, netip.MustParseAddr(ip)) ips = append(ips, netip.MustParseAddr(ip))
} }
var ip net.IP
if len(ips) > 0 {
ip = ips[mrand.Intn(len(ips))].AsSlice()
} else {
ip = dest.Address.IP()
}
var clientID [8]byte var clientID [8]byte
common.Must2(rand.Read(clientID[:])) common.Must2(rand.Read(clientID[:]))
conn := &xicmpConnClient{ conn := &xicmpConnClient{
conn: raw,
icmp4: icmp4, icmp4: icmp4,
icmp6: icmp6, icmp6: icmp6,
udp: c.DGRAM, udp: c.DGRAM,
ips: ips, ips: ips,
ip: ip,
clientID: clientID, clientID: clientID,
id: mathrand.Intn(65536), id: mrand.Intn(65536),
seq: 1, seq: 1,
readCh: make(chan packet), readCh: make(chan packet),
closedCh: make(chan struct{}), closeCh: make(chan struct{}),
} }
conn.wg.Add(2)
go conn.recv4() go conn.recv4()
go conn.recv6() go conn.recv6()
return conn, nil return conn, nil
} }
func (c *xicmpConnClient) ring(a, b uint16) uint16 {
return min(a-b, b-a)
}
func (c *xicmpConnClient) closed() bool { func (c *xicmpConnClient) closed() bool {
select { select {
case <-c.closedCh: case <-c.closeCh:
return true return true
default: default:
return false return false
@@ -104,27 +109,29 @@ func (c *xicmpConnClient) closed() bool {
} }
func (c *xicmpConnClient) recv4() { func (c *xicmpConnClient) recv4() {
var b [finalmask.UDPSize]byte defer c.wg.Done()
var b [finalmask.UDPSize]byte
for { for {
n, addr, err := c.icmp4.ReadFrom(b[:])
if err != nil {
if c.closed() { if c.closed() {
return return
} }
n, addr, err := c.icmp4.ReadFrom(b[:])
if err != nil {
var netErr net.Error var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() { if goerrors.As(err, &netErr) && netErr.Timeout() {
select { select {
case c.readCh <- packet{ case c.readCh <- packet{
err: err, err: err,
}: }:
case <-c.closedCh: case <-c.closeCh:
return return
} }
}
continue continue
} }
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
}
msg, err := icmp.ParseMessage(1, b[:n]) msg, err := icmp.ParseMessage(1, b[:n])
if err != nil { if err != nil {
@@ -146,10 +153,6 @@ func (c *xicmpConnClient) recv4() {
continue continue
} }
if c.ring(uint16(echo.Seq), uint16(c.seq)) > 1000 {
continue
}
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) { if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
continue continue
} }
@@ -166,7 +169,7 @@ func (c *xicmpConnClient) recv4() {
p: p, p: p,
addr: addr, addr: addr,
}: }:
case <-c.closedCh: case <-c.closeCh:
pool.Put(p) pool.Put(p)
return return
} }
@@ -174,27 +177,29 @@ func (c *xicmpConnClient) recv4() {
} }
func (c *xicmpConnClient) recv6() { func (c *xicmpConnClient) recv6() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte var b [finalmask.UDPSize]byte
for { for {
if c.closed() {
break
}
n, addr, err := c.icmp6.ReadFrom(b[:]) n, addr, err := c.icmp6.ReadFrom(b[:])
if err != nil { if err != nil {
if c.closed() {
return
}
var netErr net.Error var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() { if goerrors.As(err, &netErr) && netErr.Timeout() {
select { select {
case c.readCh <- packet{ case c.readCh <- packet{
err: err, err: err,
}: }:
case <-c.closedCh: case <-c.closeCh:
return return
} }
}
continue continue
} }
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
}
msg, err := icmp.ParseMessage(58, b[:n]) msg, err := icmp.ParseMessage(58, b[:n])
if err != nil { if err != nil {
@@ -216,10 +221,6 @@ func (c *xicmpConnClient) recv6() {
continue continue
} }
if c.ring(uint16(echo.Seq), uint16(c.seq)) > 1000 {
continue
}
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) { if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
continue continue
} }
@@ -236,7 +237,7 @@ func (c *xicmpConnClient) recv6() {
p: p, p: p,
addr: addr, addr: addr,
}: }:
case <-c.closedCh: case <-c.closeCh:
pool.Put(p) pool.Put(p)
return return
} }
@@ -244,16 +245,15 @@ func (c *xicmpConnClient) recv6() {
} }
func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
select { packet, ok := <-c.readCh
case packet := <-c.readCh: if ok {
if packet.p != nil { if packet.p != nil {
n = copy(p, packet.p) n = copy(p, packet.p)
pool.Put(packet.p) pool.Put(packet.p)
} }
return n, packet.addr, packet.err return n, packet.addr, packet.err
case <-c.closedCh:
return 0, nil, io.EOF
} }
return 0, nil, io.EOF
} }
func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
@@ -268,9 +268,9 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.seq %= 65536 c.seq %= 65536
c.mu.Unlock() c.mu.Unlock()
ip := addr.(*net.UDPAddr).IP ip := c.ip
if len(c.ips) > 0 { if len(c.ips) > 0 {
ip = c.ips[mathrand.Intn(len(c.ips))].AsSlice() ip = c.ips[mrand.Intn(len(c.ips))].AsSlice()
} }
if c.udp { if c.udp {
@@ -294,10 +294,9 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
} }
if err != nil { if err != nil {
errors.LogErrorInner(context.Background(), err, "xicmp write") errors.LogErrorInner(context.Background(), err, "send err")
return 0, err return 0, err
} }
return len(p), nil return len(p), nil
} }
@@ -307,15 +306,23 @@ func (c *xicmpConnClient) Close() error {
if c.closed() { if c.closed() {
return nil return nil
} }
close(c.closedCh) close(c.closeCh)
_ = c.icmp4.Close() _ = c.icmp4.Close()
_ = c.icmp6.Close() _ = c.icmp6.Close()
_ = c.conn.Close() c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
return nil return nil
} }
func (c *xicmpConnClient) LocalAddr() net.Addr { func (c *xicmpConnClient) LocalAddr() net.Addr {
return c.conn.LocalAddr() return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
} }
func (c *xicmpConnClient) SetDeadline(t time.Time) error { func (c *xicmpConnClient) SetDeadline(t time.Time) error {
+14 -19
View File
@@ -1,28 +1,23 @@
package xicmp package xicmp
import ( import (
"net" "errors"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
) )
func (c *Config) UDP() { func (c *Config) HandleDial() {}
func (c *Config) HandleListen() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
if dest.Address.Family().IsDomain() && len(c.IPs) == 0 {
return nil, errors.New("empty ip addresses")
}
return NewConnClient(c, dest)
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
_, ok1 := raw.(*internet.FakePacketConn) return NewConnServer(c)
_, ok2 := raw.(*udphop.UdpHopPacketConn)
if level != 0 || ok1 || ok2 {
return nil, errors.New("xicmp requires being at the outermost level")
}
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != 0 {
return nil, errors.New("xicmp requires being at the outermost level")
}
return NewConnServer(c, raw)
} }
+40 -30
View File
@@ -37,17 +37,17 @@ type record struct {
} }
type xicmpConnServer struct { type xicmpConnServer struct {
conn net.PacketConn
icmp4 *icmp.PacketConn icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn icmp6 *icmp.PacketConn
ips map[netip.Addr]struct{} ips map[netip.Addr]struct{}
rec map[string]record rec map[string]record
readCh chan packet readCh chan packet
closedCh chan struct{} closeCh chan struct{}
wg sync.WaitGroup
mu sync.Mutex mu sync.Mutex
} }
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { func NewConnServer(c *Config) (net.PacketConn, error) {
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil { if err != nil {
return nil, err return nil, err
@@ -63,16 +63,16 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
} }
conn := &xicmpConnServer{ conn := &xicmpConnServer{
conn: raw,
icmp4: icmp4, icmp4: icmp4,
icmp6: icmp6, icmp6: icmp6,
ips: ips, ips: ips,
rec: make(map[string]record), rec: make(map[string]record),
readCh: make(chan packet), readCh: make(chan packet),
closedCh: make(chan struct{}), closeCh: make(chan struct{}),
} }
go conn.clean() go conn.clean()
conn.wg.Add(2)
go conn.recv4() go conn.recv4()
go conn.recv6() go conn.recv6()
@@ -81,7 +81,7 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
func (c *xicmpConnServer) closed() bool { func (c *xicmpConnServer) closed() bool {
select { select {
case <-c.closedCh: case <-c.closeCh:
return true return true
default: default:
return false return false
@@ -102,34 +102,36 @@ func (c *xicmpConnServer) clean() {
} }
} }
c.mu.Unlock() c.mu.Unlock()
case <-c.closedCh: case <-c.closeCh:
return return
} }
} }
} }
func (c *xicmpConnServer) recv4() { func (c *xicmpConnServer) recv4() {
var b [finalmask.UDPSize]byte defer c.wg.Done()
var b [finalmask.UDPSize]byte
for { for {
n, addr, err := c.icmp4.ReadFrom(b[:])
if err != nil {
if c.closed() { if c.closed() {
return return
} }
n, addr, err := c.icmp4.ReadFrom(b[:])
if err != nil {
var netErr net.Error var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() { if goerrors.As(err, &netErr) && netErr.Timeout() {
select { select {
case c.readCh <- packet{ case c.readCh <- packet{
err: err, err: err,
}: }:
case <-c.closedCh: case <-c.closeCh:
return return
} }
}
continue continue
} }
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
}
msg, err := icmp.ParseMessage(1, b[:n]) msg, err := icmp.ParseMessage(1, b[:n])
if err != nil { if err != nil {
@@ -179,7 +181,7 @@ func (c *xicmpConnServer) recv4() {
p: p, p: p,
addr: cAddr, addr: cAddr,
}: }:
case <-c.closedCh: case <-c.closeCh:
pool.Put(p) pool.Put(p)
return return
} }
@@ -187,27 +189,29 @@ func (c *xicmpConnServer) recv4() {
} }
func (c *xicmpConnServer) recv6() { func (c *xicmpConnServer) recv6() {
var b [finalmask.UDPSize]byte defer c.wg.Done()
var b [finalmask.UDPSize]byte
for { for {
n, addr, err := c.icmp6.ReadFrom(b[:])
if err != nil {
if c.closed() { if c.closed() {
return return
} }
n, addr, err := c.icmp6.ReadFrom(b[:])
if err != nil {
var netErr net.Error var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() { if goerrors.As(err, &netErr) && netErr.Timeout() {
select { select {
case c.readCh <- packet{ case c.readCh <- packet{
err: err, err: err,
}: }:
case <-c.closedCh: case <-c.closeCh:
return return
} }
}
continue continue
} }
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
}
msg, err := icmp.ParseMessage(58, b[:n]) msg, err := icmp.ParseMessage(58, b[:n])
if err != nil { if err != nil {
@@ -257,7 +261,7 @@ func (c *xicmpConnServer) recv6() {
p: p, p: p,
addr: cAddr, addr: cAddr,
}: }:
case <-c.closedCh: case <-c.closeCh:
pool.Put(p) pool.Put(p)
return return
} }
@@ -265,16 +269,15 @@ func (c *xicmpConnServer) recv6() {
} }
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
select { packet, ok := <-c.readCh
case packet := <-c.readCh: if ok {
if packet.p != nil { if packet.p != nil {
n = copy(p, packet.p) n = copy(p, packet.p)
pool.Put(packet.p) pool.Put(packet.p)
} }
return n, packet.addr, packet.err return n, packet.addr, packet.err
case <-c.closedCh:
return 0, nil, io.EOF
} }
return 0, nil, io.EOF
} }
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
@@ -310,10 +313,9 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
} }
if err != nil { if err != nil {
errors.LogErrorInner(context.Background(), err, "xicmp write") errors.LogErrorInner(context.Background(), err, "send err")
return 0, err return 0, err
} }
return len(p), nil return len(p), nil
} }
@@ -323,15 +325,23 @@ func (c *xicmpConnServer) Close() error {
if c.closed() { if c.closed() {
return nil return nil
} }
close(c.closedCh) close(c.closeCh)
_ = c.icmp4.Close() _ = c.icmp4.Close()
_ = c.icmp6.Close() _ = c.icmp6.Close()
_ = c.conn.Close() c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
return nil return nil
} }
func (c *xicmpConnServer) LocalAddr() net.Addr { func (c *xicmpConnServer) LocalAddr() net.Addr {
return c.conn.LocalAddr() return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
} }
func (c *xicmpConnServer) SetDeadline(t time.Time) error { func (c *xicmpConnServer) SetDeadline(t time.Time) error {
@@ -39,7 +39,6 @@ type record struct {
} }
type xicmpConnServer struct { type xicmpConnServer struct {
conn net.PacketConn
icmp4 *icmp.PacketConn icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn icmp6 *icmp.PacketConn
ipv4PC *ipv4.PacketConn ipv4PC *ipv4.PacketConn
@@ -47,11 +46,12 @@ type xicmpConnServer struct {
ips map[netip.Addr]struct{} ips map[netip.Addr]struct{}
rec map[string]record rec map[string]record
readCh chan packet readCh chan packet
closedCh chan struct{} closeCh chan struct{}
wg sync.WaitGroup
mu sync.Mutex mu sync.Mutex
} }
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { func NewConnServer(c *Config) (net.PacketConn, error) {
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil { if err != nil {
return nil, err return nil, err
@@ -67,7 +67,6 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
} }
conn := &xicmpConnServer{ conn := &xicmpConnServer{
conn: raw,
icmp4: icmp4, icmp4: icmp4,
icmp6: icmp6, icmp6: icmp6,
ipv4PC: icmp4.IPv4PacketConn(), ipv4PC: icmp4.IPv4PacketConn(),
@@ -75,13 +74,14 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
ips: ips, ips: ips,
rec: make(map[string]record), rec: make(map[string]record),
readCh: make(chan packet), readCh: make(chan packet),
closedCh: make(chan struct{}), closeCh: make(chan struct{}),
} }
common.Must(conn.ipv4PC.SetControlMessage(ipv4.FlagDst, true)) common.Must(conn.ipv4PC.SetControlMessage(ipv4.FlagDst, true))
common.Must(conn.ipv6PC.SetControlMessage(ipv6.FlagDst, true)) common.Must(conn.ipv6PC.SetControlMessage(ipv6.FlagDst, true))
go conn.clean() go conn.clean()
conn.wg.Add(2)
go conn.recv4() go conn.recv4()
go conn.recv6() go conn.recv6()
@@ -90,7 +90,7 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
func (c *xicmpConnServer) closed() bool { func (c *xicmpConnServer) closed() bool {
select { select {
case <-c.closedCh: case <-c.closeCh:
return true return true
default: default:
return false return false
@@ -111,34 +111,36 @@ func (c *xicmpConnServer) clean() {
} }
} }
c.mu.Unlock() c.mu.Unlock()
case <-c.closedCh: case <-c.closeCh:
return return
} }
} }
} }
func (c *xicmpConnServer) recv4() { func (c *xicmpConnServer) recv4() {
var b [finalmask.UDPSize]byte defer c.wg.Done()
var b [finalmask.UDPSize]byte
for { for {
n, cm, addr, err := c.ipv4PC.ReadFrom(b[:])
if err != nil {
if c.closed() { if c.closed() {
return return
} }
n, cm, addr, err := c.ipv4PC.ReadFrom(b[:])
if err != nil {
var netErr net.Error var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() { if goerrors.As(err, &netErr) && netErr.Timeout() {
select { select {
case c.readCh <- packet{ case c.readCh <- packet{
err: err, err: err,
}: }:
case <-c.closedCh: case <-c.closeCh:
return return
} }
}
continue continue
} }
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
}
msg, err := icmp.ParseMessage(1, b[:n]) msg, err := icmp.ParseMessage(1, b[:n])
if err != nil { if err != nil {
@@ -189,7 +191,7 @@ func (c *xicmpConnServer) recv4() {
p: p, p: p,
addr: cAddr, addr: cAddr,
}: }:
case <-c.closedCh: case <-c.closeCh:
pool.Put(p) pool.Put(p)
return return
} }
@@ -197,27 +199,29 @@ func (c *xicmpConnServer) recv4() {
} }
func (c *xicmpConnServer) recv6() { func (c *xicmpConnServer) recv6() {
var b [finalmask.UDPSize]byte defer c.wg.Done()
var b [finalmask.UDPSize]byte
for { for {
n, cm, addr, err := c.ipv6PC.ReadFrom(b[:])
if err != nil {
if c.closed() { if c.closed() {
return return
} }
n, cm, addr, err := c.ipv6PC.ReadFrom(b[:])
if err != nil {
var netErr net.Error var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() { if goerrors.As(err, &netErr) && netErr.Timeout() {
select { select {
case c.readCh <- packet{ case c.readCh <- packet{
err: err, err: err,
}: }:
case <-c.closedCh: case <-c.closeCh:
return return
} }
}
continue continue
} }
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
}
msg, err := icmp.ParseMessage(58, b[:n]) msg, err := icmp.ParseMessage(58, b[:n])
if err != nil { if err != nil {
@@ -268,7 +272,7 @@ func (c *xicmpConnServer) recv6() {
p: p, p: p,
addr: cAddr, addr: cAddr,
}: }:
case <-c.closedCh: case <-c.closeCh:
pool.Put(p) pool.Put(p)
return return
} }
@@ -276,16 +280,15 @@ func (c *xicmpConnServer) recv6() {
} }
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
select { packet, ok := <-c.readCh
case packet := <-c.readCh: if ok {
if packet.p != nil { if packet.p != nil {
n = copy(p, packet.p) n = copy(p, packet.p)
pool.Put(packet.p) pool.Put(packet.p)
} }
return n, packet.addr, packet.err return n, packet.addr, packet.err
case <-c.closedCh:
return 0, nil, io.EOF
} }
return 0, nil, io.EOF
} }
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
@@ -321,10 +324,9 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
} }
if err != nil { if err != nil {
errors.LogErrorInner(context.Background(), err, "xicmp write") errors.LogErrorInner(context.Background(), err, "send err")
return 0, err return 0, err
} }
return len(p), nil return len(p), nil
} }
@@ -334,15 +336,23 @@ func (c *xicmpConnServer) Close() error {
if c.closed() { if c.closed() {
return nil return nil
} }
close(c.closedCh) close(c.closeCh)
_ = c.icmp4.Close() _ = c.icmp4.Close()
_ = c.icmp6.Close() _ = c.icmp6.Close()
_ = c.conn.Close() c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
return nil return nil
} }
func (c *xicmpConnServer) LocalAddr() net.Addr { func (c *xicmpConnServer) LocalAddr() net.Addr {
return c.conn.LocalAddr() return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
} }
func (c *xicmpConnServer) SetDeadline(t time.Time) error { func (c *xicmpConnServer) SetDeadline(t time.Time) error {
+4 -5
View File
@@ -2,13 +2,12 @@ package xmc
import ( import (
"fmt" "fmt"
"net"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
) )
func (c *Config) TCP() { func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
}
func (c *Config) WrapConnClient(conn net.Conn) (net.Conn, error) {
profiles, err := profilesFromConfig(c.Profiles) profiles, err := profilesFromConfig(c.Profiles)
if err != nil { if err != nil {
return nil, fmt.Errorf("minecraft finalmask: %w", err) return nil, fmt.Errorf("minecraft finalmask: %w", err)
+6 -11
View File
@@ -83,7 +83,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
} }
tlsConfig := tls.ConfigFromStreamSettings(streamSettings) tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
realityConfig := reality.ConfigFromStreamSettings(streamSettings) realityConfig := reality.ConfigFromStreamSettings(streamSettings)
sockopt := streamSettings.SocketSettings
grpcSettings := streamSettings.ProtocolSettings.(*Config) grpcSettings := streamSettings.ProtocolSettings.(*Config)
if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found && client.GetState() != connectivity.Shutdown { if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found && client.GetState() != connectivity.Shutdown {
@@ -124,17 +123,13 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
gctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx)) gctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx))
gctx = session.ContextWithTimeoutOnly(gctx, true) gctx = session.ContextWithTimeoutOnly(gctx, true)
c, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt) var c net.Conn
if streamSettings.FinalMask != nil {
c, err = streamSettings.FinalMask.DialTCP(gctx, net.TCPDestination(address, port))
} else {
c, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
if err == nil { if err == nil {
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(c)
if err != nil {
c.Close()
return nil, errors.New("mask err").Base(err)
}
c = newConn
}
if tlsConfig != nil { if tlsConfig != nil {
config := tlsConfig.GetTLSConfig(tls.WithDestination(dest)) config := tlsConfig.GetTLSConfig(tls.WithDestination(dest))
if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {
+9 -17
View File
@@ -104,29 +104,21 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i
go func() { go func() {
var streamListener net.Listener var streamListener net.Listener
var err error var err error
var addr net.Addr
if port == net.Port(0) { // unix if port == net.Port(0) { // unix
streamListener, err = internet.ListenSystem(ctx, &net.UnixAddr{ addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
Name: address.Domain(),
Net: "unix",
}, settings.SocketSettings)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to listen on ", address)
return
}
} else { // tcp } else { // tcp
streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{ addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
IP: address.IP(), }
Port: int(port), if settings.FinalMask != nil {
}, settings.SocketSettings) streamListener, err = settings.FinalMask.Listen(ctx, addr)
} else {
streamListener, err = internet.ListenSystem(ctx, addr, settings.SocketSettings)
}
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to listen on ", address, ":", port) errors.LogErrorInner(ctx, err, "failed to listen on ", address, ":", port)
return return
} }
}
if settings.TcpmaskManager != nil {
streamListener, _ = settings.TcpmaskManager.WrapListener(streamListener)
}
errors.LogDebug(ctx, "gRPC listen for service name `"+grpcSettings.getServiceName()+"` tun `"+grpcSettings.getTunStreamName()+"` multi tun `"+grpcSettings.getTunMultiStreamName()+"`") errors.LogDebug(ctx, "gRPC listen for service name `"+grpcSettings.getServiceName()+"` tun `"+grpcSettings.getTunStreamName()+"` multi tun `"+grpcSettings.getTunMultiStreamName()+"`")
encoding.RegisterGRPCServiceServerX(s, listener, grpcSettings.getServiceName(), grpcSettings.getTunStreamName(), grpcSettings.getTunMultiStreamName()) encoding.RegisterGRPCServiceServerX(s, listener, grpcSettings.getServiceName(), grpcSettings.getTunStreamName(), grpcSettings.getTunMultiStreamName())
+7 -10
View File
@@ -46,21 +46,18 @@ func (c *ConnRF) Read(b []byte) (int, error) {
func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) { func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {
transportConfiguration := streamSettings.ProtocolSettings.(*Config) transportConfiguration := streamSettings.ProtocolSettings.(*Config)
pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) var pconn net.Conn
var err error
if streamSettings.FinalMask != nil {
pconn, err = streamSettings.FinalMask.DialTCP(ctx, dest)
} else {
pconn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to dial to ", dest) errors.LogErrorInner(ctx, err, "failed to dial to ", dest)
return nil, err return nil, err
} }
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(pconn)
if err != nil {
pconn.Close()
return nil, errors.New("mask err").Base(err)
}
pconn = newConn
}
var conn net.Conn var conn net.Conn
var requestURL url.URL var requestURL url.URL
tConfig := tls.ConfigFromStreamSettings(streamSettings) tConfig := tls.ConfigFromStreamSettings(streamSettings)
+11 -19
View File
@@ -124,29 +124,21 @@ func ListenHTTPUpgrade(ctx context.Context, address net.Address, port net.Port,
} }
var listener net.Listener var listener net.Listener
var err error var err error
var addr net.Addr
if port == net.Port(0) { // unix if port == net.Port(0) { // unix
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{ addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
Name: address.Domain(),
Net: "unix",
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen unix domain socket(for HttpUpgrade) on ", address).Base(err)
}
errors.LogInfo(ctx, "listening unix domain socket(for HttpUpgrade) on ", address)
} else { // tcp } else { // tcp
listener, err = internet.ListenSystem(ctx, &net.TCPAddr{ addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
IP: address.IP(), }
Port: int(port), if streamSettings.FinalMask != nil {
}, streamSettings.SocketSettings) listener, err = streamSettings.FinalMask.Listen(ctx, addr)
} else {
listener, err = internet.ListenSystem(ctx, addr, streamSettings.SocketSettings)
}
if err != nil { if err != nil {
return nil, errors.New("failed to listen TCP(for HttpUpgrade) on ", address, ":", port).Base(err) return nil, errors.New("failed to listen ", addr.Network(), "(for HttpUpgrade) on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening TCP(for HttpUpgrade) on ", address, ":", port)
}
if streamSettings.TcpmaskManager != nil {
listener, _ = streamSettings.TcpmaskManager.WrapListener(listener)
} }
errors.LogInfo(ctx, "listening ", addr.Network(), "(for HttpUpgrade) on ", address, ":", port)
if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol { if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {
errors.LogWarning(ctx, "accepting PROXY protocol") errors.LogWarning(ctx, "accepting PROXY protocol")
+3 -6
View File
@@ -103,15 +103,12 @@ func (c *InterConn) Update() {
func (c *InterConn) Read(p []byte) (int, error) { func (c *InterConn) Read(p []byte) (int, error) {
b, ok := <-c.ch b, ok := <-c.ch
if !ok { if ok {
return 0, io.EOF
}
if len(p) < len(b) {
return 0, io.ErrShortBuffer
}
c.Update() c.Update()
return copy(p, b), nil return copy(p, b), nil
} }
return 0, io.EOF
}
func (c *InterConn) Write(p []byte) (int, error) { func (c *InterConn) Write(p []byte) (int, error) {
if c.closed { if c.closed {
+18 -53
View File
@@ -2,8 +2,7 @@ package hysteria
import ( import (
"context" "context"
go_tls "crypto/tls" gotls "crypto/tls"
"math/rand"
"net/http" "net/http"
"net/url" "net/url"
"reflect" "reflect"
@@ -22,7 +21,6 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion" "github.com/xtls/xray-core/transport/internet/hysteria/congestion"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr" "github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
"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/internet/tls"
) )
@@ -32,9 +30,9 @@ type client struct {
dest net.Destination dest net.Destination
config *Config config *Config
tlsConfig *go_tls.Config tlsConfig *gotls.Config
socketConfig *internet.SocketConfig socketConfig *internet.SocketConfig
udpmaskManager *finalmask.UdpmaskManager finalMask *finalmask.FinalMask
quicParams *internet.QuicParams quicParams *internet.QuicParams
conn *quic.Conn conn *quic.Conn
@@ -78,7 +76,6 @@ func (c *client) dial(ctx context.Context) error {
if quicParams == nil { if quicParams == nil {
quicParams = &internet.QuicParams{ quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard), BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
} }
} }
@@ -115,62 +112,30 @@ func (c *client) dial(ctx context.Context) error {
// quicConfig.KeepAlivePeriod = 10 * time.Second // quicConfig.KeepAlivePeriod = 10 * time.Second
// } // }
udpHopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig)
if err != nil {
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("")
}
var pktConn net.PacketConn var pktConn net.PacketConn
var udpAddr net.Addr
switch c := conn.(type) { if c.finalMask != nil {
case *internet.PacketConnWrapper: conn, err := c.finalMask.DialUDP(ctx, c.dest)
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
return pktConn, nil
}
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
var index int
if len(quicParams.UdpHop.Ports) > 0 {
index = rand.Intn(len(quicParams.UdpHop.Ports))
c.dest.Port = net.Port(quicParams.UdpHop.Ports[index])
}
raw, err := internet.DialSystem(ctx, c.dest, c.socketConfig)
if err != nil { if err != nil {
return errors.New("failed to dial to dest").Base(err) return errors.New("failed to dial to dest").Base(err)
} }
switch c := raw.(type) { pktConn = conn.(*finalmask.PacketConnWrapper).PacketConn
udpAddr = conn.RemoteAddr()
} else {
conn, err := internet.DialSystem(ctx, c.dest, c.socketConfig)
if err != nil {
return errors.New("failed to dial to dest").Base(err)
}
switch c := conn.(type) {
case *internet.PacketConnWrapper: case *internet.PacketConnWrapper:
pktConn = c.PacketConn pktConn = c.PacketConn
udpAddr = raw.RemoteAddr().(*net.UDPAddr) udpAddr = c.RemoteAddr()
case *cnc.Connection: case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c} pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port} udpAddr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
default: default:
panic(reflect.TypeOf(c)) panic(reflect.TypeOf(c))
} }
if len(quicParams.UdpHop.Ports) > 0 {
pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index)
}
if c.udpmaskManager != nil {
newConn, err := c.udpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return errors.New("mask err").Base(err)
}
pktConn = newConn
} }
tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO} tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO}
@@ -184,7 +149,7 @@ func (c *client) dial(ctx context.Context) error {
rt := &http3.Transport{ rt := &http3.Transport{
TLSClientConfig: c.tlsConfig, TLSClientConfig: c.tlsConfig,
QUICConfig: quicConfig, QUICConfig: quicConfig,
Dial: func(ctx context.Context, _ string, tlsCfg *go_tls.Config, cfg *quic.Config) (*quic.Conn, error) { Dial: func(ctx context.Context, _ string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
qc, err := tr.DialEarly(ctx, udpAddr, tlsCfg, cfg) qc, err := tr.DialEarly(ctx, udpAddr, tlsCfg, cfg)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -354,7 +319,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
config: streamSettings.ProtocolSettings.(*Config), config: streamSettings.ProtocolSettings.(*Config),
tlsConfig: tlsConfig.GetTLSConfig(tls.WithDestination(dest)), tlsConfig: tlsConfig.GetTLSConfig(tls.WithDestination(dest)),
socketConfig: streamSettings.SocketSettings, socketConfig: streamSettings.SocketSettings,
udpmaskManager: streamSettings.UdpmaskManager, finalMask: streamSettings.FinalMask,
quicParams: streamSettings.QuicParams, quicParams: streamSettings.QuicParams,
} }
manager.m[dialerConf{dest, streamSettings}] = c manager.m[dialerConf{dest, streamSettings}] = c
+8 -12
View File
@@ -231,8 +231,8 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
transport.TLSClientConfig.InsecureSkipVerify = true transport.TLSClientConfig.InsecureSkipVerify = true
} }
case "", "unix": case "", "unix":
u = &url.URL{Scheme: "http", Host: "localhost"}
path := u.Path path := u.Path
u = &url.URL{Scheme: "http", Host: "localhost"}
dialer := &net.Dialer{Timeout: 30 * time.Second} dialer := &net.Dialer{Timeout: 30 * time.Second}
transport = transport.Clone() transport = transport.Clone()
transport.Proxy = nil transport.Proxy = nil
@@ -281,7 +281,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
if quicParams == nil { if quicParams == nil {
quicParams = &internet.QuicParams{ quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard), BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
} }
} }
@@ -317,20 +316,17 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
quicConfig.MaxIncomingStreams = 1024 quicConfig.MaxIncomingStreams = 1024
} }
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings) var pktConn net.PacketConn
var err error
if streamSettings.FinalMask != nil {
pktConn, err = streamSettings.FinalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)})
} else {
pktConn, err = internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
var k *quic.StatelessResetKey var k *quic.StatelessResetKey
if !quicParams.DisableStatelessReset { if !quicParams.DisableStatelessReset {
k = &quic.StatelessResetKey{} k = &quic.StatelessResetKey{}
@@ -0,0 +1,165 @@
package hysteria
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"net"
"runtime"
"testing"
"time"
"github.com/apernet/quic-go"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/protocol/tls/cert"
)
func TestDatagram(t *testing.T) {
run := func() (addr net.Addr, recv chan int64, cancel func()) {
cert, _ := cert.MustGenerate(nil)
Certificate := [][]byte{cert.Certificate}
PrivateKey := common.Must2(x509.ParsePKCS8PrivateKey(cert.PrivateKey))
tlsConf := &tls.Config{
Certificates: []tls.Certificate{
{
Certificate: Certificate,
PrivateKey: PrivateKey,
},
},
NextProtos: []string{"h3"},
}
quicConf := &quic.Config{
InitialStreamReceiveWindow: 8388608,
MaxStreamReceiveWindow: 8388608,
InitialConnectionReceiveWindow: 8388608 * 5 / 2,
MaxConnectionReceiveWindow: 8388608 * 5 / 2,
MaxIdleTimeout: 30 * time.Second,
MaxIncomingStreams: 1024,
DisablePathMTUDiscovery: runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin",
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
AssumePeerMaxDatagramFrameSize: MaxDatagramFrameSize,
DisablePathManager: true,
}
pktConn := common.Must2(net.ListenPacket("udp", "127.0.0.1:0"))
tr := &quic.Transport{Conn: pktConn}
l := common.Must2(tr.Listen(tlsConf, quicConf))
recv = make(chan int64)
ctx, cancel := context.WithCancel(context.Background())
go func() {
defer pktConn.Close()
defer tr.Close()
defer l.Close()
defer close(recv)
var buf [1500]byte
for {
conn, err := l.Accept(ctx)
if err != nil {
if !errors.Is(err, context.Canceled) {
t.Error(err)
}
break
}
err = conn.SendDatagram(buf[:])
var qErr *quic.DatagramTooLargeError
if !errors.As(err, &qErr) {
t.Error(err)
}
recv <- qErr.MaxDatagramPayloadSize
defer conn.CloseWithError(0, "")
}
}()
return l.Addr(), recv, cancel
}
addr, recv, cancel := run()
t.Run("With ChromeParrot", func(t *testing.T) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
}
quicConf := &quic.Config{
InitialStreamReceiveWindow: 8388608,
MaxStreamReceiveWindow: 8388608,
InitialConnectionReceiveWindow: 8388608 * 5 / 2,
MaxConnectionReceiveWindow: 8388608 * 5 / 2,
MaxIdleTimeout: 30 * time.Second,
KeepAlivePeriod: 10 * time.Second,
DisablePathMTUDiscovery: runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin",
ChromeParrot: true,
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
OmitMaxDatagramFrameSize: true,
DisablePathManager: true,
}
pktConn := common.Must2(net.ListenPacket("udp", "127.0.0.1:0"))
tr := &quic.Transport{Conn: pktConn, ConnectionIDGenerator: quic.ZeroLengthConnectionIDGenerator{}}
conn := common.Must2(tr.DialEarly(context.Background(), addr, tlsConf, quicConf))
defer pktConn.Close()
defer tr.Close()
defer conn.CloseWithError(0, "")
var buf [1500]byte
err := conn.SendDatagram(buf[:])
var qErr *quic.DatagramTooLargeError
if !errors.As(err, &qErr) || qErr.MaxDatagramPayloadSize != 1197 {
t.Error(err)
}
if server := <-recv; server != 1243 {
t.Error(server)
}
})
t.Run("Without ChromeParrot", func(t *testing.T) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h3"},
}
quicConf := &quic.Config{
InitialStreamReceiveWindow: 8388608,
MaxStreamReceiveWindow: 8388608,
InitialConnectionReceiveWindow: 8388608 * 5 / 2,
MaxConnectionReceiveWindow: 8388608 * 5 / 2,
MaxIdleTimeout: 30 * time.Second,
KeepAlivePeriod: 10 * time.Second,
DisablePathMTUDiscovery: runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin",
ChromeParrot: false,
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
OmitMaxDatagramFrameSize: true,
DisablePathManager: true,
}
pktConn := common.Must2(net.ListenPacket("udp", "127.0.0.1:0"))
tr := &quic.Transport{Conn: pktConn}
conn := common.Must2(tr.DialEarly(context.Background(), addr, tlsConf, quicConf))
defer pktConn.Close()
defer tr.Close()
defer conn.CloseWithError(0, "")
var buf [1500]byte
err := conn.SendDatagram(buf[:])
var qErr *quic.DatagramTooLargeError
if !errors.As(err, &qErr) || qErr.MaxDatagramPayloadSize != 1197 {
t.Error(err)
}
if server := <-recv; server != 1197 {
t.Error(server)
}
})
cancel()
}
-254
View File
@@ -1,254 +0,0 @@
package udphop
import (
"errors"
"math/rand"
"net"
"sync"
"time"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
const (
packetQueueSize = 1024
udpBufferSize = finalmask.UDPSize
defaultHopInterval = 30 * time.Second
)
type UdpHopPacketConn struct {
Addrs []net.Addr
HopIntervalMin time.Duration
HopIntervalMax time.Duration
ListenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error)
connMutex sync.RWMutex
prevConn net.PacketConn
currentConn net.PacketConn
addrIndex int
deadline time.Time
readDeadline time.Time
writeDeadline time.Time
recvQueue chan *udpPacket
closeChan chan struct{}
closed bool
bufPool sync.Pool
}
type udpPacket struct {
Buf []byte
N int
Addr net.Addr
Err error
}
func NewUDPHopPacketConn(addrs []net.Addr, hopIntervalMin time.Duration, hopIntervalMax time.Duration, listenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error), currentConn net.PacketConn, addrIndex int) net.PacketConn {
if len(addrs) == 0 {
panic("len(addrs) == 0")
}
if hopIntervalMin == 0 {
hopIntervalMin = defaultHopInterval
}
if hopIntervalMax == 0 {
hopIntervalMax = defaultHopInterval
}
if hopIntervalMin < 5*time.Second {
panic("hopIntervalMin < 5*time.Second")
}
if hopIntervalMax < 5*time.Second {
panic("hopIntervalMax < 5*time.Second")
}
if hopIntervalMax < hopIntervalMin {
panic("hopIntervalMax < hopIntervalMin")
}
if listenUDPFunc == nil {
panic("listenUDPFunc is nil")
}
hConn := &UdpHopPacketConn{
Addrs: addrs,
HopIntervalMin: hopIntervalMin,
HopIntervalMax: hopIntervalMax,
ListenUDPFunc: listenUDPFunc,
prevConn: nil,
currentConn: currentConn,
addrIndex: addrIndex,
recvQueue: make(chan *udpPacket, packetQueueSize),
closeChan: make(chan struct{}),
bufPool: sync.Pool{
New: func() interface{} {
return make([]byte, udpBufferSize)
},
},
}
go hConn.recvLoop(hConn.currentConn)
go hConn.hopLoop()
return hConn
}
func (u *UdpHopPacketConn) recvLoop(conn net.PacketConn) {
for {
buf := u.bufPool.Get().([]byte)
n, addr, err := conn.ReadFrom(buf)
if err != nil {
u.bufPool.Put(buf)
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
u.recvQueue <- &udpPacket{nil, 0, nil, netErr}
continue
}
return
}
select {
case u.recvQueue <- &udpPacket{buf, n, addr, nil}:
default:
u.bufPool.Put(buf)
}
}
}
func (u *UdpHopPacketConn) hopLoop() {
timer := time.NewTimer(u.nextHopInterval())
defer timer.Stop()
for {
select {
case <-timer.C:
u.hop()
timer.Reset(u.nextHopInterval())
case <-u.closeChan:
return
}
}
}
func (u *UdpHopPacketConn) nextHopInterval() time.Duration {
if u.HopIntervalMin == u.HopIntervalMax {
return u.HopIntervalMin
}
return u.HopIntervalMin + time.Duration(rand.Int63n(int64(u.HopIntervalMax-u.HopIntervalMin)+1))
}
func (u *UdpHopPacketConn) hop() {
u.connMutex.Lock()
defer u.connMutex.Unlock()
if u.closed {
return
}
addrIndex := rand.Intn(len(u.Addrs))
newConn, err := u.ListenUDPFunc(u.Addrs[addrIndex].(*net.UDPAddr))
if err != nil {
return
}
if u.prevConn != nil {
_ = u.prevConn.Close()
}
u.prevConn = u.currentConn
u.addrIndex = addrIndex
u.currentConn = newConn
if !u.deadline.IsZero() {
_ = u.currentConn.SetDeadline(u.deadline)
}
if !u.readDeadline.IsZero() {
_ = u.currentConn.SetReadDeadline(u.readDeadline)
}
if !u.writeDeadline.IsZero() {
_ = u.currentConn.SetWriteDeadline(u.writeDeadline)
}
go u.recvLoop(newConn)
}
func (u *UdpHopPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
for {
select {
case p := <-u.recvQueue:
if p.Err != nil {
return 0, nil, p.Err
}
n := copy(b, p.Buf[:p.N])
u.bufPool.Put(p.Buf)
return n, p.Addr, nil
case <-u.closeChan:
return 0, nil, net.ErrClosed
}
}
}
func (u *UdpHopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
if u.closed {
return 0, net.ErrClosed
}
return u.currentConn.WriteTo(b, u.Addrs[u.addrIndex])
}
func (u *UdpHopPacketConn) Close() error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
if u.closed {
return nil
}
if u.prevConn != nil {
_ = u.prevConn.Close()
}
err := u.currentConn.Close()
close(u.closeChan)
u.closed = true
u.Addrs = nil
return err
}
func (u *UdpHopPacketConn) LocalAddr() net.Addr {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
return u.currentConn.LocalAddr()
}
func (u *UdpHopPacketConn) SetDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = t
u.readDeadline = t
u.writeDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetDeadline(t)
}
return u.currentConn.SetDeadline(t)
}
func (u *UdpHopPacketConn) SetReadDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = time.Time{}
u.readDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetReadDeadline(t)
}
return u.currentConn.SetReadDeadline(t)
}
func (u *UdpHopPacketConn) SetWriteDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = time.Time{}
u.writeDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetWriteDeadline(t)
}
return u.currentConn.SetWriteDeadline(t)
}
func ToAddrs(ip net.IP, ports []uint32) []net.Addr {
var addrs []net.Addr
for _, port := range ports {
addr := &net.UDPAddr{
IP: ip,
Port: int(port),
}
addrs = append(addrs, addr)
}
return addrs
}
+7 -28
View File
@@ -3,7 +3,6 @@ package kcp
import ( import (
"context" "context"
"io" "io"
reflect "reflect"
"sync/atomic" "sync/atomic"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@@ -11,7 +10,6 @@ import (
"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/errors"
"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/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/internet/tls"
@@ -51,36 +49,17 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet
dest.Network = net.Network_UDP dest.Network = net.Network_UDP
errors.LogInfo(ctx, "dialing mKCP to ", dest) errors.LogInfo(ctx, "dialing mKCP to ", dest)
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) var conn net.Conn
var err error
if streamSettings.FinalMask != nil {
conn, err = streamSettings.FinalMask.DialUDP(ctx, dest)
} else {
conn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
if err != nil { if err != nil {
return nil, errors.New("failed to dial to dest: ", err).AtWarning().Base(err) return nil, errors.New("failed to dial to dest: ", err).AtWarning().Base(err)
} }
if streamSettings.UdpmaskManager != nil {
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = c.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: udpAddr,
}
}
kcpSettings := streamSettings.ProtocolSettings.(*Config) kcpSettings := streamSettings.ProtocolSettings.(*Config)
reader := &KCPPacketReader{} reader := &KCPPacketReader{}
+45 -21
View File
@@ -1,7 +1,12 @@
package internet package internet
import ( import (
"context"
"reflect"
"github.com/xtls/xray-core/common"
"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/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/finalmask"
) )
@@ -12,8 +17,7 @@ type MemoryStreamConfig struct {
ProtocolSettings interface{} ProtocolSettings interface{}
SecurityType string SecurityType string
SecuritySettings interface{} SecuritySettings interface{}
TcpmaskManager *finalmask.TcpmaskManager FinalMask *finalmask.FinalMask
UdpmaskManager *finalmask.UdpmaskManager
QuicParams *QuicParams QuicParams *QuicParams
SocketSettings *SocketConfig SocketSettings *SocketConfig
DownloadSettings *MemoryStreamConfig DownloadSettings *MemoryStreamConfig
@@ -51,33 +55,53 @@ func ToMemoryStreamConfig(s *StreamConfig) (*MemoryStreamConfig, error) {
mss.SecuritySettings = ess mss.SecuritySettings = ess
} }
if s != nil && len(s.Tcpmasks) > 0 { var tcpMasks []finalmask.TCPMask
var masks []finalmask.Tcpmask var udpMasks []finalmask.UDPMask
for _, msg := range s.Tcpmasks {
instance, err := msg.GetInstance() if s != nil {
for i := range s.Tcpmasks {
instance := common.Must2(s.Tcpmasks[i].GetInstance())
tcpMasks = append(tcpMasks, instance.(finalmask.TCPMask))
}
for i := range s.Udpmasks {
instance := common.Must2(s.Udpmasks[i].GetInstance())
udpMasks = append(udpMasks, instance.(finalmask.UDPMask))
}
}
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
return DialSystem(ctx, dest, mss.SocketSettings)
}
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
return ListenSystem(ctx, addr, mss.SocketSettings)
}
dialUDP := func(ctx context.Context, dest net.Destination) (net.PacketConn, net.Addr, error) {
conn, err := DialSystem(ctx, dest, mss.SocketSettings)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
masks = append(masks, instance.(finalmask.Tcpmask)) var newConn net.PacketConn
var udpAddr net.Addr
switch c := conn.(type) {
case *PacketConnWrapper:
newConn = c.PacketConn
udpAddr = conn.RemoteAddr()
case *cnc.Connection:
newConn = &FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}, Port: 0}
default:
panic(reflect.TypeOf(c))
} }
mss.TcpmaskManager = finalmask.NewTcpmaskManager(masks) return newConn, udpAddr, nil
} }
listenPacket := func(ctx context.Context, addr net.Addr) (net.PacketConn, error) {
return ListenSystemPacket(ctx, addr, mss.SocketSettings)
}
mss.FinalMask = finalmask.NewFinalMask(tcpMasks, udpMasks, dialTCP, listen, dialUDP, listenPacket)
if s != nil && s.QuicParams != nil { if s != nil && s.QuicParams != nil {
mss.QuicParams = s.QuicParams mss.QuicParams = s.QuicParams
} }
if s != nil && len(s.Udpmasks) > 0 {
var masks []finalmask.Udpmask
for _, msg := range s.Udpmasks {
instance, err := msg.GetInstance()
if err != nil {
return nil, err
}
masks = append(masks, instance.(finalmask.Udpmask))
}
mss.UdpmaskManager = finalmask.NewUdpmaskManager(masks)
}
return mss, nil return mss, nil
} }
+2 -1
View File
@@ -169,6 +169,8 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
return errors.New("failed to set TCP_MAXSEG", err) return errors.New("failed to set TCP_MAXSEG", err)
} }
} }
}
if len(config.CustomSockopt) > 0 { if len(config.CustomSockopt) > 0 {
for _, custom := range config.CustomSockopt { for _, custom := range config.CustomSockopt {
if custom.System != "" && custom.System != runtime.GOOS { if custom.System != "" && custom.System != runtime.GOOS {
@@ -206,7 +208,6 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
} }
} }
} }
}
if config.Tproxy.IsEnabled() { if config.Tproxy.IsEnabled() {
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
+17 -20
View File
@@ -68,7 +68,7 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
} }
c.transportConfig.FillStreamRequest(req, sessionId, "") c.transportConfig.FillStreamRequest(req, sessionId, "")
wrc = &WaitReadCloser{Wait: make(chan struct{})} wrc = &WaitReadCloser{wait: done.New()}
go func() { go func() {
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
@@ -188,38 +188,35 @@ func (c *DefaultDialerClient) Close() error {
} }
type WaitReadCloser struct { type WaitReadCloser struct {
Wait chan struct{} wait *done.Instance
io.ReadCloser reader atomic.Pointer[io.ReadCloser]
} }
func (w *WaitReadCloser) Set(rc io.ReadCloser) { func (w *WaitReadCloser) Set(rc io.ReadCloser) {
w.ReadCloser = rc w.reader.Store(&rc)
defer func() { if w.wait.Done() {
if recover() != nil { if p := w.reader.Swap(nil); p != nil {
rc.Close() (*p).Close()
} }
}() }
close(w.Wait) w.wait.Close()
} }
func (w *WaitReadCloser) Read(b []byte) (int, error) { func (w *WaitReadCloser) Read(b []byte) (int, error) {
if w.ReadCloser == nil { rc := w.reader.Load()
if <-w.Wait; w.ReadCloser == nil { if rc == nil {
<-w.wait.Wait()
if rc = w.reader.Load(); rc == nil {
return 0, io.ErrClosedPipe return 0, io.ErrClosedPipe
} }
} }
return w.ReadCloser.Read(b) return (*rc).Read(b)
} }
func (w *WaitReadCloser) Close() error { func (w *WaitReadCloser) Close() error {
if w.ReadCloser != nil { w.wait.Close()
return w.ReadCloser.Close() if p := w.reader.Swap(nil); p != nil {
return (*p).Close()
} }
defer func() {
if recover() != nil && w.ReadCloser != nil {
w.ReadCloser.Close()
}
}()
close(w.Wait)
return nil return nil
} }

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