Compare commits

..
2 Commits
Author SHA1 Message Date
Fangliding bd60b01576 Refine 2026-09-03 02:21:01 +08:00
Fangliding f49d8b89c8 Clear code & add custom 2026-09-02 20:17:46 +08:00
108 changed files with 2371 additions and 7729 deletions
+3 -1
View File
@@ -67,7 +67,9 @@ jobs:
check-latest: true
cache: false
- name: Check Format
run: go run ./infra/vformat/main.go -mode check -pwd ./
run: |
go install -v mvdan.cc/gofumpt@latest
go run ./infra/vformat/main.go -mode check -pwd ./
test:
needs: check-assets
+22 -11
View File
@@ -330,6 +330,7 @@ type SenderConfig struct {
// Send traffic through the given IP. Only IP is allowed.
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"`
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"`
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"`
@@ -381,6 +382,13 @@ func (x *SenderConfig) GetStreamSettings() *internet.StreamConfig {
return nil
}
func (x *SenderConfig) GetProxySettings() *internet.ProxyConfig {
if x != nil {
return x.ProxySettings
}
return nil
}
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
if x != nil {
return x.MultiplexSettings
@@ -498,13 +506,14 @@ const file_app_proxyman_config_proto_rawDesc = "" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\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" +
"\x0eOutboundConfig\"\xd6\x02\n" +
"\x0eOutboundConfig\"\x9d\x03\n" +
"\fSenderConfig\x12-\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\x12T\n" +
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12K\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" +
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategyJ\x04\b\x03\x10\x04\"\xa4\x01\n" +
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategy\"\xa4\x01\n" +
"\x12MultiplexingConfig\x12\x18\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
@@ -539,7 +548,8 @@ var file_app_proxyman_config_proto_goTypes = []any{
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
(internet.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy
(*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig
(internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy
}
var file_app_proxyman_config_proto_depIdxs = []int32{
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
@@ -552,13 +562,14 @@ var file_app_proxyman_config_proto_depIdxs = []int32{
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
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
6, // 10: 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
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
13, // 10: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
6, // 11: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
14, // 12: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
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.
xray.common.net.IPOrDomain via = 1;
xray.transport.internet.StreamConfig stream_settings = 2;
reserved 3;
xray.transport.internet.ProxyConfig proxy_settings = 3;
MultiplexingConfig multiplex_settings = 4;
string via_cidr = 5;
xray.transport.internet.DomainStrategy target_strategy = 6;
+57 -11
View File
@@ -15,6 +15,7 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/mux"
"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/session"
"github.com/xtls/xray-core/core"
@@ -25,6 +26,8 @@ import (
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"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"
)
@@ -60,6 +63,7 @@ type Handler struct {
streamSettings *internet.MemoryStreamConfig
proxyConfig proto.Message
proxy proxy.Outbound
outboundManager outbound.Manager
mux *mux.ClientManager
xudp *mux.ClientManager
udp443 string
@@ -73,6 +77,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
h := &Handler{
tag: config.Tag,
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
uplinkCounter: uplinkCounter,
downlinkCounter: downlinkCounter,
}
@@ -103,11 +108,9 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
ctx = session.ContextWithFullHandler(ctx, h)
if h.streamSettings != nil {
ctx = session.ContextWithStreamSettings(ctx, h.streamSettings)
}
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
if err != nil {
return nil, err
}
@@ -194,6 +197,7 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
common.Interrupt(link.Reader)
return
}
} else {
unchangedDomain := ob.Target.Address.Domain()
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
@@ -266,26 +270,66 @@ func (h *Handler) DestIpAddress() net.IP {
// Dial implements internet.Dialer.
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
if h.senderSettings != nil && h.senderSettings.Via != nil {
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob)
if h.senderSettings != 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)
ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob)
}
}
conn, err := internet.Dial(ctx, dest, h.streamSettings)
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
}
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil &&
(h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
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) {
var domain string
addr := h.senderSettings.Via.AsAddress()
domain = h.senderSettings.Via.GetDomain()
switch {
case h.senderSettings.ViaCidr != "":
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
case domain == "origin":
if inbound := session.InboundFromContext(ctx); inbound != nil {
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
@@ -300,9 +344,11 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
}
}
default: // case addr.Family().IsDomain():
// case addr.Family().IsDomain():
default:
ob.Gateway = addr
}
}
}
+1 -11
View File
@@ -5,8 +5,7 @@ import (
)
type windowsReader struct {
bufs []syscall.WSABuf
ready bool
bufs []syscall.WSABuf
}
func (r *windowsReader) Init(bs []*Buffer) {
@@ -16,7 +15,6 @@ func (r *windowsReader) Init(bs []*Buffer) {
for _, b := range bs {
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
}
r.ready = false
}
func (r *windowsReader) Clear() {
@@ -27,14 +25,6 @@ func (r *windowsReader) Clear() {
}
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 flags uint32
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
+1 -3
View File
@@ -118,9 +118,7 @@ func (w *BufferedWriter) Write(b []byte) (int, error) {
nBytes, err := w.buffer.Write(b)
totalBytes += nBytes
// ErrBufferFull means a partial write, so flush below and continue
if err != nil && err != ErrBufferFull {
if err != nil {
return totalBytes, err
}
if !w.buffered || w.buffer.IsFull() {
+3 -3
View File
@@ -10,12 +10,12 @@ import (
// [,)
func RandBetween(from int64, to int64) int64 {
if from == to {
return from
}
if from > to {
from, to = to, from
}
if d := to - from; d == 0 || d == 1 {
return from
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
return from + bigInt.Int64()
}
+2
View File
@@ -70,6 +70,8 @@ type Outbound struct {
Tag string
// Name of the outbound proxy that handles the connection.
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
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
CanSpliceCopy int
+2 -2
View File
@@ -19,8 +19,8 @@ import (
var (
Version_x byte = 26
Version_y byte = 9
Version_z byte = 9
Version_y byte = 7
Version_z byte = 28
)
var (
+9 -10
View File
@@ -1,6 +1,6 @@
module github.com/xtls/xray-core
go 1.27
go 1.26
require (
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
@@ -22,22 +22,21 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/stretchr/testify v1.12.1
github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.57.0
golang.org/x/crypto v0.55.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.59.0
golang.org/x/sync v0.23.0
golang.org/x/sys v0.48.0
golang.org/x/net v0.58.0
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.0.1
google.golang.org/grpc v1.83.2
google.golang.org/grpc v1.83.1
google.golang.org/protobuf v1.36.12
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3
lukechampine.com/blake3 v1.4.1
mvdan.cc/gofumpt v0.12.0
)
require (
@@ -49,6 +48,7 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.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/pion/dtls/v3 v3.1.5 // indirect
github.com/pion/logging v0.2.4 // indirect
@@ -57,9 +57,8 @@ require (
github.com/vishvananda/netns v0.0.5 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/text v0.41.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
gopkg.in/yaml.v2 v2.4.0 // indirect
)
+17 -22
View File
@@ -6,14 +6,13 @@ 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/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA=
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/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
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/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -74,8 +73,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/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/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
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-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
@@ -88,8 +87,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/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0 h1:rb+fKQFhz+5I2PPuQsNYxI5mUU840XWYtRF0ZBjvkws=
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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=
@@ -111,8 +110,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=
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.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
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/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@@ -121,12 +120,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-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.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
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.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -134,22 +133,20 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
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/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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -163,8 +160,8 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
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/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
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=
@@ -179,5 +176,3 @@ h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
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/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho=
mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA=
+1 -48
View File
@@ -23,7 +23,6 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
"github.com/xtls/xray-core/transport/internet/finalmask/udphop"
"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/xmc"
@@ -84,7 +83,6 @@ var (
"xdns": func() interface{} { return new(Xdns) },
"xicmp": func() interface{} { return new(Xicmp) },
"realm": func() interface{} { return new(Realm) },
"udphop": func() interface{} { return new(UDPHop) },
}, "type", "settings")
)
@@ -907,52 +905,6 @@ func (c *Realm) Build() (proto.Message, error) {
}, 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 string `json:"type"`
Settings *json.RawMessage `json:"settings"`
@@ -986,6 +938,7 @@ type QuicParamsConfig struct {
BrutalUp Bandwidth `json:"brutalUp"`
BrutalDown Bandwidth `json:"brutalDown"`
BrutalDisableLossCompensation bool `json:"brutalDisableLossCompensation"`
UdpHop UdpHop `json:"udpHop"`
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
+38 -24
View File
@@ -36,8 +36,6 @@ func (p TransportProtocol) Build() (string, error) {
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
case "hysteria":
return "hysteria", nil
case "xdrive":
return "xdrive", nil
default:
return "", errors.New("Config: unknown transport protocol: ", p)
}
@@ -61,7 +59,6 @@ type StreamConfig struct {
WSSettings *WebSocketConfig `json:"wsSettings"`
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
XDRIVESettings *XDriveConfig `json:"xdriveSettings"`
SocketSettings *SocketConfig `json:"sockopt"`
}
@@ -195,16 +192,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
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 {
ss, err := c.SocketSettings.Build()
if err != nil {
@@ -266,6 +253,10 @@ 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")
}
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 {
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
}
@@ -299,20 +290,43 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
BrutalUp: up,
BrutalDown: down,
BrutalDisableLossCompensation: c.FinalMask.QuicParams.BrutalDisableLossCompensation,
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
DisableChromeParrot: c.FinalMask.QuicParams.DisableChromeParrot,
DisableGSO: c.FinalMask.QuicParams.DisableGSO,
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
DisableStatelessReset: c.FinalMask.QuicParams.DisableStatelessReset,
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,
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
DisableChromeParrot: c.FinalMask.QuicParams.DisableChromeParrot,
DisableGSO: c.FinalMask.QuicParams.DisableGSO,
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
DisableStatelessReset: c.FinalMask.QuicParams.DisableStatelessReset,
}
}
}
return config, nil
}
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
}
+26 -54
View File
@@ -1,6 +1,7 @@
package conf
import (
"context"
"encoding/json"
"math/big"
"net/url"
@@ -23,7 +24,6 @@ import (
"github.com/xtls/xray-core/transport/internet/splithttp"
"github.com/xtls/xray-core/transport/internet/tcp"
"github.com/xtls/xray-core/transport/internet/websocket"
"github.com/xtls/xray-core/transport/internet/xdrive"
"google.golang.org/protobuf/proto"
)
@@ -534,6 +534,10 @@ type KCPConfig struct {
// Build implements Buildable.
func (c *KCPConfig) Build() (proto.Message, error) {
if c.HeaderConfig != nil || c.Seed != nil {
return nil, errors.PrintRemovedFeatureError("mkcp header & seed", "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm")
}
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
if c.Mtu != nil {
@@ -556,16 +560,16 @@ func (c *KCPConfig) Build() (proto.Message, error) {
}
if config.Mtu < 21 {
return nil, errors.New("MTU must be at least 21")
return nil, errors.New("Mtu must be at least 21").AtError()
}
if config.Tti < 10 || config.Tti > 1000 {
return nil, errors.New("TTI must be between 10 and 1000")
return nil, errors.New("invalid mKCP TTI: ", c.Tti).AtError()
}
if config.CwndMultiplier < 1 {
return nil, errors.New("CwndMultiplier must be at least 1")
return nil, errors.New("CwndMultiplier must be at least 1").AtError()
}
if config.GetSendingBufferSize() == 0 {
return nil, errors.New("MaxSendingWindow must be at least ", config.Mtu)
return nil, errors.New("MaxSendingWindow must be >= Mtu").AtError()
}
return config, nil
@@ -735,6 +739,11 @@ func (b Bandwidth) Bps() (uint64, error) {
return uint64(val*float64(mul)) / 8, nil
}
type UdpHop struct {
PortList PortList `json:"ports"`
Interval Int32Range `json:"interval"`
}
type Masquerade struct {
Type string `json:"type"`
@@ -751,8 +760,14 @@ type Masquerade struct {
}
type HysteriaConfig struct {
Version int32 `json:"version"`
Auth string `json:"auth"`
Version int32 `json:"version"`
Auth string `json:"auth"`
Congestion *string `json:"congestion"`
Up *Bandwidth `json:"up"`
Down *Bandwidth `json:"down"`
UdpHop *UdpHop `json:"udphop"`
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
Masquerade Masquerade `json:"masquerade"`
}
@@ -762,6 +777,10 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
return nil, errors.New("version != 2")
}
if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {
errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams")
}
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
}
@@ -795,50 +814,3 @@ func readFileOrString(f string, s []string) ([]byte, error) {
}
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)
}
}
// 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 {
// 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`)
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`)
}
if c.MaxClientVer != "" {
config.MaxClientVer = make([]byte, 3)
+2 -2
View File
@@ -10,7 +10,7 @@ import (
)
type CustomSockoptConfig struct {
System string `json:"system"`
Syetem string `json:"system"`
Network string `json:"network"`
Level string `json:"level"`
Opt string `json:"opt"`
@@ -124,7 +124,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
for _, copt := range c.CustomSockopt {
customSockopt := &internet.CustomSockopt{
System: copt.System,
System: copt.Syetem,
Network: copt.Network,
Level: copt.Level,
Opt: copt.Opt,
-73
View File
@@ -291,76 +291,3 @@ func TestHeaderCustomUDPBuildRejectsExprWithoutArgs(t *testing.T) {
t.Fatalf("expected transform arg rejection, got %v", err)
}
}
func TestXDriveStreamConfig(t *testing.T) {
config := new(StreamConfig)
if err := json.Unmarshal([]byte(`{
"method": "xdrive",
"xdriveSettings": {
"remoteFolder": "/tmp/xdrive",
"service": "local"
}
}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
built, err := config.Build()
if err != nil {
t.Fatalf("Build: %v", err)
}
if built.ProtocolName != "xdrive" {
t.Fatalf("ProtocolName is %q, want %q", built.ProtocolName, "xdrive")
}
if len(built.TransportSettings) != 1 || built.TransportSettings[0].ProtocolName != "xdrive" {
t.Fatalf("TransportSettings is %v, want a single xdrive entry", built.TransportSettings)
}
}
func TestXDriveRejectsUnknownService(t *testing.T) {
config := new(XDriveConfig)
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, err := config.Build(); err == nil {
t.Fatal("Build accepted an unsupported service")
}
}
func TestXDriveTemplateStreamConfig(t *testing.T) {
config := new(StreamConfig)
if err := json.Unmarshal([]byte(`{
"method": "xdrive",
"xdriveSettings": {
"remoteFolder": "folder",
"service": "template",
"secrets": ["user", "pass"],
"template": {
"flatten": true,
"auth": {"type": "basic", "username": "{secret0}", "password": "{secret1}"},
"put": {"method": "PUT", "url": "https://dav.example/{folder}/{name}"},
"get": {"method": "GET", "url": "https://dav.example/{folder}/{name}"},
"delete": {"method": "DELETE", "url": "https://dav.example/{folder}/{name}"},
"list": {"method": "PROPFIND", "url": "https://dav.example/{folder}/", "namesRegex": "<d:href>/folder/([^<]+)</d:href>"}
}
}
}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
built, err := config.Build()
if err != nil {
t.Fatalf("Build: %v", err)
}
if built.ProtocolName != "xdrive" {
t.Fatalf("ProtocolName is %q, want xdrive", built.ProtocolName)
}
}
func TestXDriveTemplateNeedsTemplate(t *testing.T) {
config := new(XDriveConfig)
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, err := config.Build(); err == nil {
t.Fatal("Build accepted a template service without a template")
}
}
-3
View File
@@ -312,9 +312,6 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
if err := json.Unmarshal(rawUser, account); err != nil {
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
return nil, errors.New(`VLESS users: please use simplified outbound's config style to use "reverse"`)
}
+23 -7
View File
@@ -59,13 +59,14 @@ func (c *WireGuardPeerConfig) Build() (*wireguard.PeerConfig, error) {
type WireGuardConfig struct {
IsClient bool `json:""`
NoKernelTun bool `json:"noKernelTun"`
SecretKey string `json:"secretKey"`
Address []string `json:"address"`
Peers []*WireGuardPeerConfig `json:"peers"`
MTU int32 `json:"mtu"`
Reserved []byte `json:"reserved"`
DNS []string `json:"remoteDNS"`
NoKernelTun bool `json:"noKernelTun"`
SecretKey string `json:"secretKey"`
Address []string `json:"address"`
Peers []*WireGuardPeerConfig `json:"peers"`
MTU int32 `json:"mtu"`
Reserved []byte `json:"reserved"`
DomainStrategy string `json:"domainStrategy"`
DNS []string `json:"remoteDNS"`
}
func (c *WireGuardConfig) Build() (proto.Message, error) {
@@ -124,6 +125,21 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
}
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.NoKernelTun = c.NoKernelTun
config.DNS = c.DNS
+35 -32
View File
@@ -15,7 +15,6 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/serial"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/freedom"
"github.com/xtls/xray-core/transport/internet"
)
@@ -217,11 +216,21 @@ type OutboundDetourConfig struct {
Tag string `json:"tag"`
Settings *json.RawMessage `json:"settings"`
StreamSetting *StreamConfig `json:"streamSettings"`
ProxySettings *json.RawMessage `json:"proxySettings"`
ProxySettings *ProxyConfig `json:"proxySettings"`
MuxSettings *MuxConfig `json:"mux"`
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 {
if address == nil || address.Address == nil {
return false
@@ -242,7 +251,7 @@ func validateOutboundTransportSecurity(rawConfig interface{}, senderSettings *pr
if vlessCfg.Encryption != "" && vlessCfg.Encryption != "none" {
return nil
}
if requiresTransportSecurity(vlessCfg.Address) {
if requiresTransportSecurity(vlessCfg.Vnext[0].Address) {
return errors.New("vless without TLS or other encryption is prohibited unless the server address is a private IP or domain")
}
}
@@ -258,10 +267,6 @@ func validateOutboundTransportSecurity(rawConfig interface{}, senderSettings *pr
// Build implements Buildable.
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if c.ProxySettings != nil {
return nil, errors.PrintRemovedFeatureError(`outbound "proxySettings"`, `"streamSettings.sockopt.dialerProxy"`)
}
senderSettings := &proxyman.SenderConfig{}
switch strings.ToLower(c.TargetStrategy) {
case "asis", "":
@@ -289,6 +294,9 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
default:
return nil, errors.New("unsupported target domain strategy: ", c.TargetStrategy)
}
if err := c.checkChainProxyConfig(); err != nil {
return nil, err
}
if c.SendThrough != nil {
address := ParseSendThough(c.SendThrough)
@@ -314,6 +322,26 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
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 {
ms, err := c.MuxSettings.Build()
if err != nil {
@@ -338,31 +366,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
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{
SenderSettings: serial.ToTypedMessage(senderSettings),
Tag: c.Tag,
+138 -272
View File
@@ -1,18 +1,15 @@
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"go/build"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"sync/atomic"
"mvdan.cc/gofumpt/format"
)
var (
@@ -26,27 +23,101 @@ var (
isFormat bool
)
func getModuleInfo(pwd string) (modPath, langVersion string, err error) {
data, err := os.ReadFile(filepath.Join(pwd, "go.mod"))
if err != nil {
return "", "", err
}
for _, line := range strings.Split(string(data), "\n") {
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")
}
// envFile returns the name of the Go environment configuration file.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
func envFile() (string, error) {
if file := os.Getenv("GOENV"); file != "" {
if file == "off" {
return "", errors.New("GOENV=off")
}
return file, nil
}
return modPath, langVersion, nil
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
if dir == "" {
return "", errors.New("missing user-config dir")
}
return filepath.Join(dir, "go", "env"), nil
}
func formatGoSource(src []byte, opts format.Options) ([]byte, error) {
return format.Source(src, opts)
// GetRuntimeEnv returns the value of runtime environment variable,
// that is set by running following command: `go env -w key=value`.
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() {
@@ -79,76 +150,26 @@ func main() {
}
pwd := *directory
modPath, langVersion, modErr := getModuleInfo(pwd)
if modErr != nil {
fmt.Println("Error reading go.mod:", modErr)
GOBIN := GetGOBIN()
binPath := os.Getenv("PATH")
pathSlice := []string{pwd, GOBIN, binPath}
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)
}
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)
}
}
}
})
} else {
gofmt = gofmtPath
}
rawFilesSlice := make([]string, 0, 1000)
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
@@ -165,206 +186,51 @@ func main() {
!strings.HasSuffix(filename, ".pb.go") &&
!strings.Contains(dir, filepath.Join("testing", "mocks")) &&
!strings.Contains(path, filepath.Join("main", "distro", "all", "all.go")) {
jobs <- path
rawFilesSlice = append(rawFilesSlice, path)
}
return nil
})
close(jobs)
wg.Wait()
if walkErr != nil {
fmt.Println(walkErr)
os.Exit(1)
}
if hasErrors.Load() {
os.Exit(1)
}
if isFormat {
if formatRequired.Load() {
fmt.Println("Do NOT forget to commit file changes.")
gofmtArgs := []string{
"-l", "-e", "-w",
}
fmt.Println("Formatting Go source files...")
RunMany(gofmt, gofmtArgs, rawFilesSlice)
fmt.Println("Do NOT forget to commit file changes.")
}
if isCheck {
if formatRequired.Load() {
gofmtListArgs := []string{
"-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("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)
} else {
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,7 +60,6 @@ import (
_ "github.com/xtls/xray-core/transport/internet/tls"
_ "github.com/xtls/xray-core/transport/internet/udp"
_ "github.com/xtls/xray-core/transport/internet/websocket"
_ "github.com/xtls/xray-core/transport/internet/xdrive"
// Transport headers
_ "github.com/xtls/xray-core/transport/internet/headers/http"
+102 -96
View File
@@ -53,10 +53,6 @@ func reloadEnvSettings() error {
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
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 {
return h.Init(config.(*Config), pm)
}); err != nil {
@@ -93,11 +89,9 @@ type FinalRule struct {
// Handler handles Freedom connections.
type Handler struct {
policyManager policy.Manager
config *Config
finalRules []*FinalRule
resolveStrategy internet.DomainStrategy
usesDialerProxy bool
policyManager policy.Manager
config *Config
finalRules []*FinalRule
}
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
@@ -174,6 +168,22 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
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 {
for _, rule := range h.finalRules {
if rule.Apply(network, address, port) {
@@ -186,16 +196,17 @@ func (h *Handler) matchFinalRule(network net.Network, address net.Address, port
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.
func (h *Handler) Init(config *Config, pm policy.Manager) error {
h.config = config
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))
for _, rc := range config.FinalRules {
rule, err := buildFinalRule(rc)
@@ -226,20 +237,6 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
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 {
if addr == nil {
return false
@@ -259,10 +256,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ob.Name = "freedom"
ob.CanSpliceCopy = 1
inbound := session.InboundFromContext(ctx)
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)
}
defaultRule := getDefaultFinalRule(inbound)
destination := ob.Target
origTargetAddr := ob.OriginalTarget.Address
@@ -290,53 +284,61 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
var conn stat.Connection
var blockedDest *net.Destination
var blockedRule *FinalRule
firstResolve := true
err := retry.ExponentialBackoff(5, 100).On(func() error {
if destination.Address.Family().IsDomain() {
if defaultRule != nil || len(h.finalRules) > 0 {
if strategy := h.resolveStrategy; strategy.HasStrategy() {
ips, err := internet.LookupForIP(destination.Address.Domain(), strategy, outGateway)
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())
if strategy.ForceIP() {
return err // retry
}
}
for _, ip := range ips {
if addr := net.IPAddress(ip); addr != nil {
if rule := h.matchFinalRule(destination.Network, addr, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &destination
blockedDest.Address = addr
blockedRule = rule
return nil
}
}
}
} else {
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, destination.Address.Domain())
if err != nil { // dialer may retry DNS
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", destination.Address.Domain())
}
for _, addr := range addrs {
if ipAddr := net.IPAddress(addr.IP); ipAddr != nil {
if rule := h.matchFinalRule(destination.Network, ipAddr, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &destination
blockedDest.Address = ipAddr
blockedRule = rule
return nil
}
}
}
}
dialDest := destination
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() {
strategy := h.config.DomainStrategy
if destination.Network == net.Network_UDP && origTargetAddr != nil && outGateway == nil {
strategy = strategy.GetDynamicStrategy(origTargetAddr.Family())
}
} else {
if rule := h.matchFinalRule(destination.Network, destination.Address, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &destination
blockedRule = rule
return nil
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
if err != nil {
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 {
blockedDest = &dialDest
blockedRule = rule
return nil
}
rawConn, err := dialer.Dial(ctx, destination)
rawConn, err := dialer.Dial(ctx, dialDest)
if err != nil {
return err
}
@@ -348,17 +350,20 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return errors.New("failed to open connection to ", destination).Base(err)
}
if blockedDest != nil {
return h.blackhole(ctx, input, output, blockedRule, blockedDest)
}
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)
delay := h.blockDelay(blockedRule)
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 h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
version := byte(h.config.ProxyProtocol)
srcAddr := inbound.Source.RawNetAddr()
@@ -403,7 +408,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
writer = buf.NewWriter(conn)
}
} else {
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination, outGateway)
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination)
if h.config.Noises != nil {
errors.LogDebug(ctx, "NOISE", h.config.Noises)
writer = &NoisePacketWriter{
@@ -507,7 +512,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
}
udpAddr := d.(*net.UDPAddr)
sourceAddr := net.IPAddress(udpAddr.IP)
if rule := r.Handler.matchFinalRule(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule); rule != nil && rule.action == RuleAction_Block {
if r.Handler.applyFinalRules(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule) == RuleAction_Block {
continue
}
b.Resize(0, int32(n))
@@ -532,7 +537,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
}
// 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, outGateway net.Address) buf.Writer {
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Writer {
iConn := conn
statConn, ok := iConn.(*stat.CounterConnection)
if ok {
@@ -556,8 +561,9 @@ func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverr
DefaultRule: defaultRule,
UDPOverride: UDPOverride,
ResolvedUDPAddr: resolvedUDPAddr,
OutGateway: outGateway,
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
}
}
return &buf.SequentialWriter{Writer: conn}
}
@@ -574,7 +580,7 @@ type PacketWriter struct {
// Resulting in these packets being sent to many different IPs randomly
// So, cache and keep the resolve result
ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address]
OutGateway net.Address
LocalAddr net.Address
}
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
@@ -597,21 +603,21 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
b.UDP.Address = ip
} else {
shouldUseSystemResolver := true
if strategy := w.Handler.resolveStrategy; strategy.HasStrategy() {
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), strategy, w.OutGateway)
ShouldUseSystemResolver := true
if w.Handler.config.DomainStrategy.HasStrategy() {
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), w.Handler.config.DomainStrategy, w.LocalAddr)
if err != nil {
// drop packet if resolve failed when forceIP
if strategy.ForceIP() {
if w.Handler.config.DomainStrategy.ForceIP() {
b.Release()
continue
}
} else {
ip = net.IPAddress(ips[dice.Roll(len(ips))])
shouldUseSystemResolver = false
ShouldUseSystemResolver = false
}
}
if shouldUseSystemResolver {
if ShouldUseSystemResolver {
udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
if err != nil {
b.Release()
@@ -625,7 +631,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
}
}
}
if rule := w.matchFinalRule(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule); rule != nil && rule.action == RuleAction_Block {
if w.applyFinalRules(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule) == RuleAction_Block {
b.Release()
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)
}
ResponseHeader1xx = append(ResponseHeader1xx, data...)
if len(ResponseHeader1xx) >= 4 && bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
if bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
break
}
if len(ResponseHeader1xx) > 1024 {
-49
View File
@@ -1,49 +0,0 @@
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) {
for {
var packet [1500]byte
var buf [hysteria.MaxDatagramFrameSize]byte
n, err := r.reader.Read(packet[:])
n, err := r.reader.Read(buf[:])
if err != nil {
return 0, nil, err
}
msg, err := ParseUDPMessage(packet[:n])
msg, err := ParseUDPMessage(buf[:n])
if err != nil {
continue
}
-1
View File
@@ -82,7 +82,6 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
if err != nil {
return errors.New("failed to connect to server").Base(err)
}
defer connection.Close()
if session.TimeoutOnlyFromContext(ctx) {
ctx, _ = context.WithCancel(context.Background())
+4 -33
View File
@@ -37,25 +37,6 @@ type Handler struct {
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
type ConnectionHandler interface {
HandleConnection(conn net.Conn, destination net.Destination)
@@ -123,7 +104,7 @@ func (t *Handler) Start() error {
iface := updater.Get()
if iface == nil {
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
return errors.New("iface not found")
return nil
}
return c.Control(func(fd uintptr) {
addrPort, _ := netip.ParseAddrPort(address)
@@ -190,8 +171,7 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
return
}
source := net.DestinationFromAddr(remote)
isUDP := destination.Network == net.Network_UDP
if !isUDP && (t.uplinkCounter != nil || t.downlinkCounter != nil) {
if t.uplinkCounter != nil || t.downlinkCounter != nil {
conn = &stat.CounterConnection{
Connection: conn,
ReadCounter: t.uplinkCounter,
@@ -223,18 +203,9 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.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{
Reader: reader,
Writer: writer,
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
Writer: buf.NewWriter(conn),
}
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
errors.LogError(ctx, errors.New("connection closed").Base(err))
+134 -142
View File
@@ -3,14 +3,14 @@
package tun
import (
"context"
"crypto/md5"
"encoding/binary"
go_errors "errors"
"net"
"net/netip"
"sort"
"strings"
"sync"
"time"
"unsafe"
"github.com/xtls/xray-core/common/errors"
@@ -31,14 +31,13 @@ func procyield(cycles uint32)
type WindowsTun struct {
sync.RWMutex
options *Config
adapter *wintun.Adapter
session wintun.Session
readWait windows.Handle
luid winipcfg.LUID
cbr winipcfg.ChangeCallback
cbi winipcfg.ChangeCallback
closed bool
options *Config
adapter *wintun.Adapter
session wintun.Session
readWait windows.Handle
luid winipcfg.LUID
changeCallback winipcfg.ChangeCallback
closed bool
}
// WindowsTun implements Tun
@@ -86,37 +85,23 @@ func open(name, desc string) (*wintun.Adapter, error) {
return nil, err
}
func (t *WindowsTun) Start() (err error) {
var address4, address6 bool
addresses := make([]netip.Prefix, 0, len(t.options.Gateway))
for _, cidr := range t.options.Gateway {
prefix := netip.MustParsePrefix(cidr)
if prefix.Addr().Is4() {
address4 = true
} else {
address6 = true
}
addresses = append(addresses, prefix)
func (t *WindowsTun) Start() error {
var has4, has6 bool
allowedIPs := make([]netip.Prefix, 0, len(t.options.AutoSystemRoutingTable))
for _, route := range t.options.AutoSystemRoutingTable {
allowedIPs = append(allowedIPs, netip.MustParsePrefix(route))
}
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{})
for _, cidr := range t.options.AutoSystemRoutingTable {
prefix := netip.MustParsePrefix(cidr)
for _, ip := range allowedIPs {
route := winipcfg.RouteData{
Destination: prefix.Masked(),
Destination: ip.Masked(),
Metric: 0,
}
if prefix.Addr().Is4() {
route4 = true
if ip.Addr().Is4() {
has4 = true
route.NextHop = netip.IPv4Unspecified()
} else {
route6 = true
has6 = true
route.NextHop = netip.IPv6Unspecified()
}
routesMap[route] = struct{}{}
@@ -126,40 +111,24 @@ func (t *WindowsTun) Start() (err error) {
r := route
routesData = append(routesData, &r)
}
var retryTimes int
var firstErr error
startOver:
if retryTimes > 0 {
if retryTimes > 15 {
return windows.ERROR_NOT_FOUND
}
errors.LogErrorInner(context.Background(), firstErr, "Interface configuration failed, retrying attempt ", retryTimes, "/15")
time.Sleep(time.Second)
err := t.luid.SetRoutes(routesData)
if err != nil {
return errors.New("unable to set routes").Base(err)
}
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 {
firstErr = errors.New("unable to set routes").Base(err)
if err == windows.ERROR_NOT_FOUND {
goto startOver
}
return firstErr
}
if len(t.options.Gateway) > 0 {
addresses := make([]netip.Prefix, 0, len(t.options.Gateway))
for _, address := range t.options.Gateway {
addresses = append(addresses, netip.MustParsePrefix(address))
}
if family == windows.AF_INET && address4 || family == windows.AF_INET6 && address6 {
err = t.luid.SetIPAddressesForFamily(family, addresses)
if err != nil {
firstErr = errors.New("unable to set ips").Base(err)
if err == windows.ERROR_NOT_FOUND {
goto startOver
}
return firstErr
}
err := t.luid.SetIPAddresses(addresses)
if err != nil {
return errors.New("unable to set ips").Base(err)
}
ipif, err := t.luid.IPInterface(family)
}
if has4 {
ipif, err := t.luid.IPInterface(windows.AF_INET)
if err != nil {
return err
}
@@ -167,45 +136,56 @@ startOver:
ipif.DadTransmits = 0
ipif.ManagedAddressConfigurationSupported = false
ipif.OtherStatefulConfigurationSupported = false
if family == windows.AF_INET && (address4 || route4) || family == windows.AF_INET6 && (address6 || route6) {
ipif.NLMTU = t.options.MTU
}
if family == windows.AF_INET && route4 || family == windows.AF_INET6 && route6 {
ipif.UseAutomaticMetric = false
ipif.Metric = 0
}
ipif.NLMTU = t.options.MTU
ipif.UseAutomaticMetric = false
ipif.Metric = 0
err = ipif.Set()
if err != nil {
firstErr = errors.New("unable to set metric and MTU").Base(err)
if err == windows.ERROR_NOT_FOUND {
goto startOver
}
return firstErr
return err
}
err = t.luid.SetDNS(family, dns, nil)
}
if has6 {
ipif, err := t.luid.IPInterface(windows.AF_INET6)
if err != nil {
firstErr = errors.New("unable to set DNS").Base(err)
if err == windows.ERROR_NOT_FOUND {
goto startOver
}
return firstErr
return err
}
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
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 {
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) {
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
updater.Update()
})
if err != nil {
return err
}
}
return nil
}
@@ -217,26 +197,12 @@ func (t *WindowsTun) Close() error {
}
t.closed = true
if t.cbr != nil {
t.cbr.Unregister()
}
if t.cbi != nil {
t.cbi.Unregister()
}
if t.luid != 0 {
t.luid.FlushRoutes(windows.AF_INET)
t.luid.FlushIPAddresses(windows.AF_INET)
t.luid.FlushDNS(windows.AF_INET)
t.luid.FlushRoutes(windows.AF_INET6)
t.luid.FlushIPAddresses(windows.AF_INET6)
t.luid.FlushDNS(windows.AF_INET6)
}
if t.session != (wintun.Session{}) {
t.session.End()
}
if t.adapter != nil {
t.adapter.Close()
if t.changeCallback != nil {
t.changeCallback.Unregister()
}
t.session.End()
_ = t.adapter.Close()
return nil
}
@@ -345,49 +311,75 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
}
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
if fixedName != "" {
return net.InterfaceByName(fixedName)
}
r, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
lowestMetric := ^uint32(0)
index := uint32(0)
lowestMetricWifi := ^uint32(0)
indexWifi := uint32(0)
for i := range r {
if r[i].DestinationPrefix.PrefixLength != 0 || r[i].InterfaceIndex == uint32(tunIndex) {
continue
}
ifrow, err := r[i].InterfaceLUID.Interface()
if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp {
continue
}
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
if fixedName != "" {
for _, iface := range interfaces {
if iface.Index != tunIndex && iface.Name == fixedName {
return &iface, nil
}
}
return nil, nil
}
if ifrow.Type == windows.IF_TYPE_IEEE80211 {
if r[i].Metric+iface.Metric < lowestMetricWifi {
lowestMetricWifi = r[i].Metric + iface.Metric
indexWifi = r[i].InterfaceIndex
}
var candidates []struct {
index int
score int
}
for i, iface := range interfaces {
if iface.Index == tunIndex {
continue
}
if r[i].Metric+iface.Metric < lowestMetric {
lowestMetric = r[i].Metric + iface.Metric
index = r[i].InterfaceIndex
if strings.Contains(iface.Name, "vEthernet") {
continue
}
if iface.Flags&net.FlagUp == 0 {
continue
}
if iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil || len(addrs) == 0 {
continue
}
candidates = append(candidates, struct {
index int
score int
}{i, scoreWindowsInterface(&iface, addrs)})
}
if indexWifi != 0 {
index = indexWifi
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
}
return net.InterfaceByIndex(int(index))
iface := interfaces[candidates[0].index]
return &iface, nil
}
func scoreWindowsInterface(iface *net.Interface, addrs []net.Addr) int {
score := 0
name := strings.ToLower(iface.Name)
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
score += 2
}
for _, addr := range addrs {
if strings.HasPrefix(addr.String(), "192.168.") {
score++
break
}
}
return score
}
+2
View File
@@ -202,6 +202,8 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
}
defer conn.Close()
ob.Conn = conn // for Vision's pre-connect
iConn := stat.TryUnwrapStatsConn(conn)
target := ob.Target
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination.NetAddr())
+138 -113
View File
@@ -3,6 +3,7 @@ package wireguard
import (
"context"
"fmt"
gonet "net"
"net/netip"
"reflect"
"strings"
@@ -27,10 +28,14 @@ import (
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"golang.zx2c4.com/wireguard/device"
)
type entry struct {
got []net.IP
time time.Time
}
type Handler struct {
conf *DeviceConfig
policyManager policy.Manager
@@ -44,6 +49,11 @@ type Handler struct {
tnet *Net
dev *device.Device
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) {
@@ -99,10 +109,15 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
return nil, err
}
local := false
dns := conf.DNS
if len(dns) == 0 {
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))
for _, dns := range dns {
dnses = append(dnses, netip.MustParseAddr(dns))
@@ -136,6 +151,9 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
tun: tun,
tnet: tnet,
local: local,
cache: make(map[string]entry),
}, nil
}
@@ -154,6 +172,22 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
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 newCancel context.CancelFunc
if session.TimeoutOnlyFromContext(ctx) {
@@ -182,10 +216,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
var err error
if sessionPolicy.Timeouts.Handshake != 0 {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
conn, err = h.tnet.DialContext(timeoutCtx, "tcp", ob.Target.NetAddr())
conn, err = h.tnet.DialContextTCPAddrPort(timeoutCtx, addrPort)
timeoutCancel()
} else {
conn, err = h.tnet.Dial("tcp", ob.Target.NetAddr())
conn, err = h.tnet.DialContextTCPAddrPort(ctx, addrPort)
}
if err != nil {
return errors.New("failed to create TCP connection").Base(err)
@@ -194,14 +228,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
reader = buf.NewReader(conn)
writer = buf.NewWriter(conn)
case net.Network_UDP:
conn, err := h.tnet.Dial("udp", ob.Target.NetAddr())
conn, err := h.tnet.DialUDPAddrPort(netip.AddrPort{}, addrPort)
if err != nil {
return errors.New("failed to create UDP connection").Base(err)
}
defer conn.Close()
c := &udpConnClient{
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
dest: conn.RemoteAddr().(*net.UDPAddr),
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
resolveFunc: h.resolveRemote,
dest: gonet.UDPAddrFromAddrPort(addrPort),
}
reader = c
writer = c
@@ -258,26 +293,26 @@ func (h *Handler) init(ctx context.Context) error {
if err != nil {
return nil, err
}
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
if err != nil {
return nil, err
}
var pktConn net.PacketConn
if h.streamSettings.FinalMask != nil {
conn, err := h.streamSettings.FinalMask.DialUDP(ctx, dest)
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
if h.streamSettings.UdpmaskManager != nil {
newConn, err := h.streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
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)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
if h.uplinkCounter != nil || h.downlinkCounter != nil {
pktConn = &PacketCounterConnection{
@@ -336,48 +371,87 @@ func (h *Handler) init(ctx context.Context) error {
}
func (h *Handler) resolveLocal(host string) (net.IP, error) {
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
})
}
func (h *Handler) resolveRemote(host string) (net.IP, error) {
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
if h.local {
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
}
return h.tnet.LookupHost(host)
})
}
func (h *Handler) resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, uint32, error)) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil {
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 {
return nil, err
}
got := ips
if h.streamSettings.SocketSettings != nil {
var got4, got6 []net.IP
for _, ip := range ips {
if ip.To4() != nil {
got4 = append(got4, ip)
} else {
got6 = append(got6, ip)
}
}
switch h.streamSettings.SocketSettings.DomainStrategy {
case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
got = ips
case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
got = got4
case internet.DomainStrategy_USE_IP6, internet.DomainStrategy_FORCE_IP6:
got = got6
case internet.DomainStrategy_USE_IP46, internet.DomainStrategy_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case internet.DomainStrategy_USE_IP64, internet.DomainStrategy_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4
}
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
if len(ips) == 0 {
return nil, dns.ErrEmptyResponse
}
var got4, got6 []net.IP
for _, ip := range ips {
if ip.To4() != nil {
got4 = append(got4, ip)
} else {
got6 = append(got6, ip)
}
}
var got []net.IP
switch strategy {
case DeviceConfig_FORCE_IP:
got = ips
return ips[dice.Roll(len(ips))], nil
case DeviceConfig_FORCE_IP4:
got = got4
case DeviceConfig_FORCE_IP6:
got = got6
case DeviceConfig_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case DeviceConfig_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4
}
default:
panic(strategy)
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
}
entry := entry{
got: got,
time: time.Now().Add(time.Duration(ttl) * time.Second),
}
h.cacheMu.Lock()
h.cache[host] = entry
h.cacheMu.Unlock()
return got[dice.Roll(len(got))], nil
}
type udpConnClient struct {
net.PacketConn
dest *net.UDPAddr
resolveFunc func(host string) (net.IP, error)
dest *net.UDPAddr
}
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
@@ -404,8 +478,15 @@ func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
dst := c.dest
if b.UDP != nil {
if b.UDP.Address.Family().IsDomain() {
if b.UDP.Port != net.Port(dst.Port) {
dst = &net.UDPAddr{IP: dst.IP, Port: int(b.UDP.Port)}
ip, err := c.resolveFunc(b.UDP.Address.String())
if err != nil {
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 {
dst = b.UDP.RawNetAddr().(*net.UDPAddr)
@@ -442,59 +523,3 @@ func (c *PacketCounterConnection) WriteTo(p []byte, addr net.Addr) (n int, err e
}
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)),
}
}
+102 -26
View File
@@ -22,6 +22,61 @@ const (
_ = 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 {
state protoimpl.MessageState `protogen:"open.v1"`
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
@@ -99,18 +154,19 @@ func (x *PeerConfig) GetAllowedIps() []string {
}
type DeviceConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,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"`
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
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"`
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"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceConfig) Reset() {
@@ -185,6 +241,13 @@ func (x *DeviceConfig) GetReserved() []byte {
return nil
}
func (x *DeviceConfig) GetDomainStrategy() DeviceConfig_DomainStrategy {
if x != nil {
return x.DomainStrategy
}
return DeviceConfig_FORCE_IP
}
func (x *DeviceConfig) GetIsClient() bool {
if x != nil {
return x.IsClient
@@ -220,7 +283,7 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\n" +
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
"\vallowed_ips\x18\x05 \x03(\tR\n" +
"allowedIps\"\xb4\x02\n" +
"allowedIps\"\xee\x03\n" +
"\fDeviceConfig\x12\x1d\n" +
"\n" +
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
@@ -228,11 +291,20 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" +
"\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" +
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12\x1b\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\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" +
"\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\x12\x10\n" +
"\x03DNS\x18\n" +
" \x03(\tR\x03DNSB^\n" +
" \x03(\tR\x03DNS\"\\\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"
var (
@@ -247,20 +319,23 @@ func file_proxy_wireguard_config_proto_rawDescGZIP() []byte {
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_goTypes = []any{
(*PeerConfig)(nil), // 0: xray.proxy.wireguard.PeerConfig
(*DeviceConfig)(nil), // 1: xray.proxy.wireguard.DeviceConfig
(*protocol.User)(nil), // 2: xray.common.protocol.User
(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy
(*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig
(*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig
(*protocol.User)(nil), // 3: xray.common.protocol.User
}
var file_proxy_wireguard_config_proto_depIdxs = []int32{
0, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
2, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
1, // 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
0, // 2: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_proxy_wireguard_config_proto_init() }
@@ -273,13 +348,14 @@ func file_proxy_wireguard_config_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)),
NumEnums: 0,
NumEnums: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_wireguard_config_proto_goTypes,
DependencyIndexes: file_proxy_wireguard_config_proto_depIdxs,
EnumInfos: file_proxy_wireguard_config_proto_enumTypes,
MessageInfos: file_proxy_wireguard_config_proto_msgTypes,
}.Build()
File_proxy_wireguard_config_proto = out.File
+8
View File
@@ -17,6 +17,13 @@ message PeerConfig {
}
message DeviceConfig {
enum DomainStrategy {
FORCE_IP = 0;
FORCE_IP4 = 1;
FORCE_IP6 = 2;
FORCE_IP46 = 3;
FORCE_IP64 = 4;
}
string secret_key = 1;
repeated string endpoint = 2;
repeated PeerConfig peers = 3;
@@ -24,6 +31,7 @@ message DeviceConfig {
int32 mtu = 4;
bytes reserved = 6;
DomainStrategy domain_strategy = 7;
bool is_client = 8;
bool no_kernel_tun = 9;
repeated string DNS = 10;
+14 -157
View File
@@ -15,8 +15,6 @@ import (
"net"
"net/netip"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"time"
@@ -44,7 +42,6 @@ type netTun struct {
events chan tun.Event
notifyHandle *channel.NotificationHandle
incomingPacket chan *buffer.View
closed chan struct{}
mtu int
dnsServers []netip.Addr
hasV4, hasV6 bool
@@ -61,7 +58,6 @@ func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handleLocal
stack: stack.New(opts),
events: make(chan tun.Event, 10),
incomingPacket: make(chan *buffer.View),
closed: make(chan struct{}),
dnsServers: dnsServers,
mtu: mtu,
}
@@ -128,10 +124,8 @@ func (tun *netTun) Events() <-chan tun.Event {
}
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
var view *buffer.View
select {
case view = <-tun.incomingPacket:
case <-tun.closed:
view, ok := <-tun.incomingPacket
if !ok {
return 0, os.ErrClosed
}
@@ -172,10 +166,7 @@ func (tun *netTun) WriteNotify() {
view := pkt.ToView()
pkt.DecRef()
select {
case tun.incomingPacket <- view:
case <-tun.closed:
}
tun.incomingPacket <- view
}
func (tun *netTun) Close() error {
@@ -188,9 +179,8 @@ func (tun *netTun) Close() error {
close(tun.events)
}
// we don't close incomingPacket, because WriteNotify may be mid-send on it (DNS lookup) and would panic.
if tun.closed != nil {
close(tun.closed)
if tun.incomingPacket != nil {
close(tun.incomingPacket)
}
return nil
@@ -229,7 +219,6 @@ type Net struct {
DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error)
dnsServers []netip.Addr
hasV4, hasV6 bool
cache cache
}
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
@@ -257,12 +246,9 @@ var (
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
errCanceled = errors.New("operation was canceled")
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 []string, err error) {
func (net *Net) LookupHost(host string) (addrs []net.IP, ttl uint32, err error) {
return net.LookupContextHost(context.Background(), host)
}
@@ -581,12 +567,9 @@ func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.T
return dnsmessage.Parser{}, "", lastErr
}
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) {
if saddr := tnet.cache.LookupHost(host); saddr != nil {
return saddr, nil
}
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP, uint32, error) {
if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
zlen := len(host)
if strings.IndexByte(host, ':') != -1 {
@@ -595,11 +578,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
}
}
if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
return []string{ip.String()}, nil
return []net.IP{ip.AsSlice()}, 0, nil
}
if !isDomainName(host) {
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
type result struct {
p dnsmessage.Parser
@@ -700,137 +683,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
}
if len(addrs) == 0 && lastErr != nil {
return nil, lastErr
return nil, 0, lastErr
}
saddrs := make([]string, 0, len(addrs))
ips := make([]net.IP, 0, len(addrs))
for _, ip := range addrs {
saddrs = append(saddrs, ip.String())
ips = append(ips, ip.AsSlice())
}
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)
return ips, ttl, nil
}
+9 -7
View File
@@ -258,16 +258,18 @@ func (s *Server) Start() error {
return errors.New("address is domain")
}
listenFunc := func() (net.PacketConn, error) {
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)
}
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings)
if err != nil {
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 {
pktConn = &PacketCounterConnection{
PacketConn: pktConn,
+2 -7
View File
@@ -82,14 +82,9 @@ func TestResolveIP(t *testing.T) {
},
{
Tag: "direct",
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
SocketSettings: &internet.SocketConfig{
DomainStrategy: internet.DomainStrategy_USE_IP,
},
},
ProxySettings: serial.ToTypedMessage(&freedom.Config{
DomainStrategy: internet.DomainStrategy_USE_IP,
}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
+7 -9
View File
@@ -92,7 +92,7 @@ func TestPassiveConnection(t *testing.T) {
}
}
func TestDialerProxy(t *testing.T) {
func TestProxy(t *testing.T) {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
@@ -187,10 +187,8 @@ func TestDialerProxy(t *testing.T) {
},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
SocketSettings: &internet.SocketConfig{
DialerProxy: "proxy",
},
ProxySettings: &internet.ProxyConfig{
Tag: "proxy",
},
}),
},
@@ -220,7 +218,7 @@ func TestDialerProxy(t *testing.T) {
}
}
func TestDialerProxyOverKCP(t *testing.T) {
func TestProxyOverKCP(t *testing.T) {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
@@ -323,11 +321,11 @@ func TestDialerProxyOverKCP(t *testing.T) {
},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
ProxySettings: &internet.ProxyConfig{
Tag: "proxy",
},
StreamSettings: &internet.StreamConfig{
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*)
// Beacuse figerprint support may be broken after utls/reality update
// Known working fingerprint: chrome, firefox, safari
// Known broken fingerprint: android, 360
func TestVlessRealityFingerprints(t *testing.T) {
TestFingerprint := func(fingerprint string) error {
tcpServer := tcp.Server{
@@ -641,7 +641,7 @@ func TestVlessRealityFingerprints(t *testing.T) {
}
return nil
}
fingerPrints := []string{"chrome", "firefox", "safari"}
fingerPrints := []string{"chrome", "firefox", "safari", "ios", "edge", "qq"}
wg := sync.WaitGroup{}
wg.Add(len(fingerPrints))
for _, fp := range fingerPrints {
-2
View File
@@ -65,7 +65,6 @@ func TestWireguard(t *testing.T) {
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
},
},
}
@@ -105,7 +104,6 @@ func TestWireguard(t *testing.T) {
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
}},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
},
},
}
+7 -1
View File
@@ -10,7 +10,7 @@ type ConfigCreator func() interface{}
var globalTransportConfigCreatorCache = make(map[string]ConfigCreator)
var strategy = [11][3]byte{
var strategy = [][]byte{
// name strategy, prefer, fallback
{0, 0, 0}, // AsIs none, /, /
{1, 0, 0}, // UseIP use, both, none
@@ -25,6 +25,8 @@ var strategy = [11][3]byte{
{2, 6, 4}, // ForceIPv6v4 force, 6, 4
}
const unknownProtocol = "unknown"
func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error {
if _, found := globalTransportConfigCreatorCache[name]; found {
return errors.New("protocol ", name, " is already registered").AtError()
@@ -89,6 +91,10 @@ func (c *StreamConfig) HasSecuritySettings() bool {
return len(c.SecuritySettings) > 0
}
func (c *ProxyConfig) HasTag() bool {
return c != nil && len(c.Tag) > 0
}
func (m SocketConfig_TProxyMode) IsEnabled() bool {
return m != SocketConfig_Off
}
+194 -63
View File
@@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4, 0}
return file_transport_internet_config_proto_rawDescGZIP(), []int{6, 0}
}
type TransportConfig struct {
@@ -382,6 +382,66 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
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 {
state protoimpl.MessageState `protogen:"open.v1"`
Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
@@ -389,24 +449,25 @@ type QuicParams struct {
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"`
BrutalDisableLossCompensation bool `protobuf:"varint,5,opt,name=brutal_disable_loss_compensation,json=brutalDisableLossCompensation,proto3" json:"brutal_disable_loss_compensation,omitempty"`
InitStreamReceiveWindow uint64 `protobuf:"varint,6,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"`
InitConnReceiveWindow uint64 `protobuf:"varint,8,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"`
MaxIdleTimeout int64 `protobuf:"varint,10,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"`
DisablePathMtuDiscovery bool `protobuf:"varint,12,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"`
DisableGSO bool `protobuf:"varint,14,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"`
DisableStatelessReset bool `protobuf:"varint,16,opt,name=disable_stateless_reset,json=disableStatelessReset,proto3" json:"disable_stateless_reset,omitempty"`
UdpHop *UdpHop `protobuf:"bytes,6,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,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,8,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_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,10,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
MaxIdleTimeout int64 `protobuf:"varint,11,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
KeepAlivePeriod int64 `protobuf:"varint,12,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,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,14,opt,name=disable_chrome_parrot,json=disableChromeParrot,proto3" json:"disable_chrome_parrot,omitempty"`
DisableGSO bool `protobuf:"varint,15,opt,name=disableGSO,proto3" json:"disableGSO,omitempty"`
MaxIncomingStreams int64 `protobuf:"varint,16,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
DisableStatelessReset bool `protobuf:"varint,17,opt,name=disable_stateless_reset,json=disableStatelessReset,proto3" json:"disable_stateless_reset,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *QuicParams) Reset() {
*x = QuicParams{}
mi := &file_transport_internet_config_proto_msgTypes[2]
mi := &file_transport_internet_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -418,7 +479,7 @@ func (x *QuicParams) String() string {
func (*QuicParams) ProtoMessage() {}
func (x *QuicParams) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[2]
mi := &file_transport_internet_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -431,7 +492,7 @@ func (x *QuicParams) ProtoReflect() protoreflect.Message {
// Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.
func (*QuicParams) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
}
func (x *QuicParams) GetCongestion() string {
@@ -469,6 +530,13 @@ func (x *QuicParams) GetBrutalDisableLossCompensation() bool {
return false
}
func (x *QuicParams) GetUdpHop() *UdpHop {
if x != nil {
return x.UdpHop
}
return nil
}
func (x *QuicParams) GetInitStreamReceiveWindow() uint64 {
if x != nil {
return x.InitStreamReceiveWindow
@@ -546,6 +614,58 @@ func (x *QuicParams) GetDisableStatelessReset() bool {
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 {
state protoimpl.MessageState `protogen:"open.v1"`
System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"`
@@ -560,7 +680,7 @@ type CustomSockopt struct {
func (x *CustomSockopt) Reset() {
*x = CustomSockopt{}
mi := &file_transport_internet_config_proto_msgTypes[3]
mi := &file_transport_internet_config_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -572,7 +692,7 @@ func (x *CustomSockopt) String() string {
func (*CustomSockopt) ProtoMessage() {}
func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[3]
mi := &file_transport_internet_config_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -585,7 +705,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
func (*CustomSockopt) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
}
func (x *CustomSockopt) GetSystem() string {
@@ -665,7 +785,7 @@ type SocketConfig struct {
func (x *SocketConfig) Reset() {
*x = SocketConfig{}
mi := &file_transport_internet_config_proto_msgTypes[4]
mi := &file_transport_internet_config_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -677,7 +797,7 @@ func (x *SocketConfig) String() string {
func (*SocketConfig) ProtoMessage() {}
func (x *SocketConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[4]
mi := &file_transport_internet_config_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -690,7 +810,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
func (*SocketConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
}
func (x *SocketConfig) GetMark() int32 {
@@ -852,7 +972,7 @@ type HappyEyeballsConfig struct {
func (x *HappyEyeballsConfig) Reset() {
*x = HappyEyeballsConfig{}
mi := &file_transport_internet_config_proto_msgTypes[5]
mi := &file_transport_internet_config_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -864,7 +984,7 @@ func (x *HappyEyeballsConfig) String() string {
func (*HappyEyeballsConfig) ProtoMessage() {}
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[5]
mi := &file_transport_internet_config_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -877,7 +997,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
return file_transport_internet_config_proto_rawDescGZIP(), []int{7}
}
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
@@ -928,7 +1048,11 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" +
"\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
"quicParams\x12N\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"\x8d\x06\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"d\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" +
"QuicParams\x12\x1e\n" +
"\n" +
@@ -939,21 +1063,25 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\tbrutal_up\x18\x03 \x01(\x04R\bbrutalUp\x12\x1f\n" +
"\vbrutal_down\x18\x04 \x01(\x04R\n" +
"brutalDown\x12G\n" +
" brutal_disable_loss_compensation\x18\x05 \x01(\bR\x1dbrutalDisableLossCompensation\x12;\n" +
"\x1ainit_stream_receive_window\x18\x06 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
"\x19max_stream_receive_window\x18\a \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
"\x18init_conn_receive_window\x18\b \x01(\x04R\x15initConnReceiveWindow\x125\n" +
"\x17max_conn_receive_window\x18\t \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
"\x10max_idle_timeout\x18\n" +
" \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x11keep_alive_period\x18\v \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x1adisable_path_mtu_discovery\x18\f \x01(\bR\x17disablePathMtuDiscovery\x122\n" +
"\x15disable_chrome_parrot\x18\r \x01(\bR\x13disableChromeParrot\x12\x1e\n" +
" brutal_disable_loss_compensation\x18\x05 \x01(\bR\x1dbrutalDisableLossCompensation\x128\n" +
"\audp_hop\x18\x06 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" +
"\x1ainit_stream_receive_window\x18\a \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
"\x19max_stream_receive_window\x18\b \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
"\x18init_conn_receive_window\x18\t \x01(\x04R\x15initConnReceiveWindow\x125\n" +
"\x17max_conn_receive_window\x18\n" +
" \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
"\x10max_idle_timeout\x18\v \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x11keep_alive_period\x18\f \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x1adisable_path_mtu_discovery\x18\r \x01(\bR\x17disablePathMtuDiscovery\x122\n" +
"\x15disable_chrome_parrot\x18\x0e \x01(\bR\x13disableChromeParrot\x12\x1e\n" +
"\n" +
"disableGSO\x18\x0e \x01(\bR\n" +
"disableGSO\x18\x0f \x01(\bR\n" +
"disableGSO\x120\n" +
"\x14max_incoming_streams\x18\x0f \x01(\x03R\x12maxIncomingStreams\x126\n" +
"\x17disable_stateless_reset\x18\x10 \x01(\bR\x15disableStatelessReset\"\x93\x01\n" +
"\x14max_incoming_streams\x18\x10 \x01(\x03R\x12maxIncomingStreams\x126\n" +
"\x17disable_stateless_reset\x18\x11 \x01(\bR\x15disableStatelessReset\"Q\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" +
"\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" +
"\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" +
@@ -1037,39 +1165,42 @@ 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_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_transport_internet_config_proto_goTypes = []any{
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
(*QuicParams)(nil), // 5: xray.transport.internet.QuicParams
(*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt
(*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig
(*HappyEyeballsConfig)(nil), // 8: xray.transport.internet.HappyEyeballsConfig
(*serial.TypedMessage)(nil), // 9: xray.common.serial.TypedMessage
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
(*UdpHop)(nil), // 5: xray.transport.internet.UdpHop
(*QuicParams)(nil), // 6: xray.transport.internet.QuicParams
(*ProxyConfig)(nil), // 7: xray.transport.internet.ProxyConfig
(*CustomSockopt)(nil), // 8: xray.transport.internet.CustomSockopt
(*SocketConfig)(nil), // 9: xray.transport.internet.SocketConfig
(*HappyEyeballsConfig)(nil), // 10: xray.transport.internet.HappyEyeballsConfig
(*serial.TypedMessage)(nil), // 11: xray.common.serial.TypedMessage
(*net.IPOrDomain)(nil), // 12: xray.common.net.IPOrDomain
}
var file_transport_internet_config_proto_depIdxs = []int32{
9, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
10, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
11, // 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
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
9, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
9, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
9, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
5, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
7, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
2, // 8: 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
6, // 10: 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
8, // 12: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
11, // 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
11, // 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
9, // 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, // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
0, // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
8, // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
1, // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
10, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
14, // [14:14] is the sub-list for method output_type
14, // [14:14] is the sub-list for method input_type
14, // [14:14] is the sub-list for extension type_name
14, // [14:14] is the sub-list for extension extendee
0, // [0:14] is the sub-list for field type_name
}
func init() { file_transport_internet_config_proto_init() }
@@ -1083,7 +1214,7 @@ func file_transport_internet_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
NumEnums: 3,
NumMessages: 6,
NumMessages: 8,
NumExtensions: 0,
NumServices: 0,
},
+23 -11
View File
@@ -64,23 +64,35 @@ message StreamConfig {
SocketConfig socket_settings = 6;
}
message UdpHop {
repeated uint32 ports = 1;
int64 interval_min = 2;
int64 interval_max = 3;
}
message QuicParams {
string congestion = 1;
string bbr_profile = 2;
uint64 brutal_up = 3;
uint64 brutal_down = 4;
bool brutal_disable_loss_compensation = 5;
uint64 init_stream_receive_window = 6;
uint64 max_stream_receive_window = 7;
uint64 init_conn_receive_window = 8;
uint64 max_conn_receive_window = 9;
int64 max_idle_timeout = 10;
int64 keep_alive_period = 11;
bool disable_path_mtu_discovery = 12;
bool disable_chrome_parrot = 13;
bool disableGSO = 14;
int64 max_incoming_streams = 15;
bool disable_stateless_reset = 16;
UdpHop udp_hop = 6;
uint64 init_stream_receive_window = 7;
uint64 max_stream_receive_window = 8;
uint64 init_conn_receive_window = 9;
uint64 max_conn_receive_window = 10;
int64 max_idle_timeout = 11;
int64 keep_alive_period = 12;
bool disable_path_mtu_discovery = 13;
bool disable_chrome_parrot = 14;
bool disableGSO = 15;
int64 max_incoming_streams = 16;
bool disable_stateless_reset = 17;
}
message ProxyConfig {
string tag = 1;
bool transportLayerProxy = 2;
}
message CustomSockopt {
+100 -237
View File
@@ -2,291 +2,106 @@ package finalmask
import (
"context"
"fmt"
"net"
"slices"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
)
type Dialer struct {
DialTCP func(net.Destination) (net.Conn, error)
DialUDP func(net.Destination) (net.Conn, error)
type Udpmask interface {
UDP()
WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
}
type ListenConfig struct {
Listen func(net.Addr) (net.Listener, error)
ListenPacket func(net.Addr) (net.PacketConn, error)
type UdpmaskManager struct {
udpmasks []Udpmask
}
type TCPMask interface {
WrapConnClient(net.Conn, *net.Destination, *Dialer) (net.Conn, error)
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 NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
return &UdpmaskManager{
udpmasks: udpmasks,
}
}
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
},
}
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int
var conns []net.PacketConn
for i := range fm.udpMasks {
var newConn net.PacketConn
if _, ok := fm.udpMasks[i].(interface{ HeaderConn() }); ok {
newConn, err = fm.udpMasks[i].WrapPacketConnClient(nil, nil, nil)
for i, mask := range slices.Backward(m.udpmasks) {
if _, ok := mask.(headerConn); ok {
conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1)
if err != nil {
_ = conn.Close()
return nil, err
}
sizes = append(sizes, newConn.(interface{ Size() int }).Size())
conns = append(conns, newConn)
sizes = append(sizes, conn.(headerSize).Size())
conns = append(conns, conn)
} else {
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
newConn, err = fm.udpMasks[i].WrapPacketConnClient(conn, &dest, dialer)
var err error
raw, err = mask.WrapPacketConnClient(raw, i, len(m.udpmasks)-1)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
}
}
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
if addr == nil {
addr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, nil
return raw, 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
if _, ok := fm.udpMasks[0].(interface{ HandleListen() }); !ok {
conn, err = fm.listenPacket(ctx, addr)
if err != nil {
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) },
}
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int
var conns []net.PacketConn
for i := range fm.udpMasks {
var newConn net.PacketConn
if _, ok := fm.udpMasks[i].(interface{ HeaderConn() }); ok {
newConn, err = fm.udpMasks[i].WrapPacketConnServer(nil, nil, nil)
for i, mask := range slices.Backward(m.udpmasks) {
if _, ok := mask.(headerConn); ok {
conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1)
if err != nil {
_ = conn.Close()
return nil, err
}
sizes = append(sizes, newConn.(interface{ Size() int }).Size())
conns = append(conns, newConn)
sizes = append(sizes, conn.(headerSize).Size())
conns = append(conns, conn)
} else {
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
newConn, err = fm.udpMasks[i].WrapPacketConnServer(conn, addr, lc)
var err error
raw, err = mask.WrapPacketConnServer(raw, i, len(m.udpmasks)-1)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
}
}
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
return conn, nil
return raw, nil
}
const (
UDPSize = 4096
)
type PacketConnWrapper struct {
net.PacketConn
udpAddr net.Addr
type headerConn interface {
HeaderConn()
}
func (c *PacketConnWrapper) RemoteAddr() net.Addr {
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 headerSize interface {
Size() int
}
type headerManagerConn struct {
@@ -379,27 +194,75 @@ func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error)
return len(p), nil
}
type TCPListener struct {
net.Listener
tcpMasks []TCPMask
type Tcpmask interface {
TCP()
WrapConnClient(net.Conn) (net.Conn, error)
WrapConnServer(net.Conn) (net.Conn, error)
}
func (l *TCPListener) Accept() (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
}
func NewTcpListener(m *TcpmaskManager, l net.Listener) (net.Listener, error) {
return &tcpListener{
m: m,
Listener: l,
}, nil
}
func (l *tcpListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return conn, err
}
for i := range l.tcpMasks {
var newConn net.Conn
newConn, err = l.tcpMasks[i].WrapConnServer(conn)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
newConn, err := l.m.WrapConnServer(conn)
if err != nil {
errors.LogDebugInner(context.Background(), err, "mask err")
_ = conn.Close()
return nil, err
}
return conn, nil
return newConn, nil
}
type TcpMaskConn interface {
@@ -1,14 +1,14 @@
package fragment
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
import "net"
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return NewConnClient(c, conn, false)
func (c *Config) TCP() {
}
func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
return NewConnServer(c, conn, true)
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClient(c, raw, false)
}
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServer(c, raw, true)
}
@@ -1,30 +1,35 @@
package custom
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *TCPConfig) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return NewConnClientTCP(c, conn)
func (c *TCPConfig) TCP() {}
func (c *TCPConfig) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClientTCP(c, raw)
}
func (c *TCPConfig) WrapConnServer(conn net.Conn) (net.Conn, error) {
return NewConnServerTCP(c, conn)
func (c *TCPConfig) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServerTCP(c, raw)
}
func (c *UDPConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClientUDP(c, conn)
func (c *UDPConfig) UDP() {}
func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClientUDP(c, raw)
}
func (c *UDPConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServerUDP(c, conn)
func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServerUDP(c, raw)
}
func (c *UDPStandaloneConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClientUDPStandalone(c, conn)
func (c *UDPStandaloneConfig) UDP() {}
func (c *UDPStandaloneConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClientUDPStandalone(c, raw)
}
func (c *UDPStandaloneConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServerUDPStandalone(c, conn)
func (c *UDPStandaloneConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServerUDPStandalone(c, raw)
}
@@ -9,6 +9,8 @@ import (
"strings"
"testing"
"time"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
@@ -154,7 +156,7 @@ func TestMetadataUDPStandaloneWriteUsesRemotePort(t *testing.T) {
}
defer serverRaw.Close()
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg}).WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -299,7 +301,7 @@ func TestMetadataTCPHandshakeUsesEndpointPorts(t *testing.T) {
}
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -5,6 +5,8 @@ import (
"net"
"testing"
"time"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) {
@@ -46,6 +48,7 @@ func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
},
},
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
@@ -59,11 +62,11 @@ func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
}
defer serverRaw.Close()
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
@@ -37,7 +37,7 @@ func TestDSLTCPHandshakeReusesCapturedValue(t *testing.T) {
defer clientRaw.Close()
defer serverRaw.Close()
client, err := cfg.WrapConnClient(clientRaw, nil, nil)
client, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -117,7 +117,7 @@ func TestDSLTCPClientRejectsMismatchedResponseSequence(t *testing.T) {
defer clientRaw.Close()
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -1,16 +1,17 @@
package aes128gcm
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {}
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) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,16 +1,17 @@
package header
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {}
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) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,16 +1,17 @@
package original
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {}
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) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
+8 -8
View File
@@ -1,14 +1,14 @@
package noise
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
import "net"
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
+19 -6
View File
@@ -1,14 +1,27 @@
package realm
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
"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) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) UDP() {}
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(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != 0 {
return nil, errors.New("realm requires being at the outermost level")
}
return NewConnServer(c, raw)
}
@@ -1,24 +1,27 @@
package salamander
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewSalamanderConnClient(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewSalamanderConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewSalamanderConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewSalamanderConnServer(c, raw)
}
func (c *GeckoConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewGeckoConnClient(c, conn)
func (c *GeckoConfig) UDP() {}
func (c *GeckoConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewGeckoConnClient(c, raw)
}
func (c *GeckoConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewGeckoConnServer(c, conn)
func (c *GeckoConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewGeckoConnServer(c, raw)
}
+23 -10
View File
@@ -1,18 +1,25 @@
package sudoku
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
"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.
// TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes.
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return newPackedDirectionalConn(conn, c, true)
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(raw, c, true)
}
func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(conn, c, false)
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(raw, c, false)
}
func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (net.Conn, error) {
@@ -35,10 +42,16 @@ func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (ne
return newWrappedConn(raw, reader, writer), nil
}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewUDPConn(conn, c)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != levelCount {
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
}
return NewUDPConn(raw, c)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewUDPConn(conn, c)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != levelCount {
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
}
return NewUDPConn(raw, c)
}
+55 -57
View File
@@ -2,14 +2,12 @@ package finalmask_test
import (
"bytes"
"context"
"io"
gonet "net"
"net"
"strings"
"testing"
"time"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
)
@@ -22,14 +20,11 @@ func mustSendRecvTcp(
) {
t.Helper()
waitCh := make(chan error)
go func() {
_, err := from.Write(msg)
if err != nil {
t.Fatal(err)
t.Error(err)
}
close(waitCh)
}()
buf := make([]byte, 1024)
@@ -45,23 +40,18 @@ func mustSendRecvTcp(
if !bytes.Equal(buf[:n], msg) {
t.Fatalf("unexpected data %q", buf[:n])
}
<-waitCh
}
type layerMaskTcp struct {
name string
mask finalmask.TCPMask
mask finalmask.Tcpmask
}
type failingWrapMask struct{}
func (failingWrapMask) TCP() {}
func (f failingWrapMask) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return conn, nil
}
func (f failingWrapMask) WrapConnServer(conn net.Conn) (net.Conn, error) {
func (failingWrapMask) TCP() {}
func (f failingWrapMask) WrapConnClient(raw net.Conn) (net.Conn, error) { return raw, nil }
func (f failingWrapMask) WrapConnServer(raw net.Conn) (net.Conn, error) {
return nil, io.ErrClosedPipe
}
@@ -102,31 +92,32 @@ func TestConnReadWrite(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
mask := c.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)
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{mask})
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
client, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { client.Close() })
server, err := listener.Accept()
client, err = maskManager.WrapConnClient(client)
if err != nil {
t.Fatal(err)
}
server, err := ln.Accept()
if err != nil {
t.Fatal(err)
}
server, err = maskManager.WrapConnServer(server)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { server.Close() })
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
@@ -159,32 +150,34 @@ func TestTCPcustomStaticHandshakeRoundTrip(t *testing.T) {
},
},
}
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{cfg})
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{cfg}, nil, dialTCP, listen, nil, nil)
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
defer ln.Close()
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
clientRaw, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
defer client.Close()
defer clientRaw.Close()
server, err := listener.Accept()
serverRaw, err := ln.Accept()
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := maskManager.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := maskManager.WrapConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
defer server.Close()
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
@@ -227,11 +220,11 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
},
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -264,37 +257,42 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
}
func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) {
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{failingWrapMask{}}, nil, dialTCP, listen, nil, nil)
clientManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}})
serverManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}})
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
rawLn, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer rawLn.Close()
ln, err := serverManager.WrapListener(rawLn)
if err != nil {
t.Fatal(err)
}
defer listener.Close()
accepted := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, err := listener.Accept()
conn, err := ln.Accept()
accepted <- struct {
conn net.Conn
err error
}{conn: conn, err: err}
}()
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
clientRaw, err := net.Dial("tcp", rawLn.Addr().String())
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
client, err := clientManager.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
defer client.Close()
_ = client.SetDeadline(time.Now().Add(time.Second))
+59 -52
View File
@@ -2,15 +2,13 @@ package finalmask_test
import (
"bytes"
"context"
"encoding/binary"
"io"
gonet "net"
"net"
"sync/atomic"
"testing"
"time"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
@@ -53,7 +51,7 @@ func mustSendRecv(
type layerMask struct {
name string
mask finalmask.UDPMask
mask finalmask.Udpmask
layers int
}
@@ -215,23 +213,25 @@ func newStandaloneStunLikeUDPServerConfig() *custom.UDPStandaloneConfig {
func newUDPClientServerPair(t *testing.T, cfg *custom.UDPStandaloneConfig) (net.PacketConn, net.PacketConn, net.PacketConn, net.PacketConn) {
t.Helper()
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = clientRaw.Close() })
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = serverRaw.Close() })
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
@@ -348,39 +348,31 @@ func TestPacketConnReadWrite(t *testing.T) {
if layers <= 0 {
layers = 1
}
masks := make([]finalmask.UDPMask, 0, layers)
masks := make([]finalmask.Udpmask, 0, layers)
for i := 0; i < layers; i++ {
masks = append(masks, mask)
}
maskManager := finalmask.NewUdpmaskManager(masks)
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()})
client, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { server.Close() })
clientConn, err := finalMask.DialUDP(context.Background(), net.UDPDestination(net.IPAddress(server.LocalAddr().(*net.UDPAddr).IP), net.Port(server.LocalAddr().(*net.UDPAddr).Port)))
client, err = maskManager.WrapPacketConnClient(client)
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 {
t.Fatal(err)
}
t.Cleanup(func() { clientConn.Close() })
client := clientConn.(*finalmask.PacketConnWrapper).PacketConn
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
@@ -405,20 +397,21 @@ func TestUDPcustomStaticHeaderWireShape(t *testing.T) {
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
},
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -649,11 +642,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_ascii",
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -690,11 +683,11 @@ func TestSudokuBDD(t *testing.T) {
PaddingMax: 0,
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -745,10 +738,10 @@ func TestSudokuBDD(t *testing.T) {
countWireBytes := func(wrapServer func(net.Conn, *sudoku.Config) (net.Conn, error), cfg *sudoku.Config) int64 {
t.Helper()
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
watchedServerRaw := &countingConn{Conn: serverRaw}
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -800,11 +793,11 @@ func TestSudokuBDD(t *testing.T) {
CustomTables: []string{"xpxvvpvv", "vxpvxvvp"},
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -842,11 +835,11 @@ func TestSudokuBDD(t *testing.T) {
PaddingMax: 0,
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -875,6 +868,19 @@ 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) {
cfg := &sudoku.Config{
Password: "sudoku-udp-multi",
@@ -883,24 +889,25 @@ func TestSudokuBDD(t *testing.T) {
PaddingMin: 0,
PaddingMax: 0,
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
@@ -954,7 +961,7 @@ func TestSudokuBDD(t *testing.T) {
}
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -1001,11 +1008,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_entropy",
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -1025,11 +1032,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_entropy",
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -1,17 +0,0 @@
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")
}
@@ -1,178 +0,0 @@
// 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
}
@@ -1,19 +0,0 @@
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
@@ -1,289 +0,0 @@
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
}
+16 -6
View File
@@ -1,14 +1,24 @@
package xdns
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(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("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)
}
+48 -55
View File
@@ -8,7 +8,8 @@ import (
goerrors "errors"
"fmt"
"io"
mrand "math/rand"
mathrand "math/rand"
"net"
"net/netip"
"sync"
"time"
@@ -16,7 +17,6 @@ import (
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
@@ -36,21 +36,20 @@ type packet struct {
}
type xicmpConnClient struct {
conn net.PacketConn
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
udp bool
ips []netip.Addr
ip net.IP
clientID [8]byte
id int
seq int
readCh chan packet
closeCh chan struct{}
wg sync.WaitGroup
closedCh chan struct{}
mu sync.Mutex
}
func NewConnClient(c *Config, dest *net.Destination) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
var icmp4, icmp6 *icmp.PacketConn
var err4, err6 error
if c.DGRAM {
@@ -69,39 +68,35 @@ func NewConnClient(c *Config, dest *net.Destination) (net.PacketConn, error) {
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
common.Must2(rand.Read(clientID[:]))
conn := &xicmpConnClient{
conn: raw,
icmp4: icmp4,
icmp6: icmp6,
udp: c.DGRAM,
ips: ips,
ip: ip,
clientID: clientID,
id: mrand.Intn(65536),
id: mathrand.Intn(65536),
seq: 1,
readCh: make(chan packet),
closeCh: make(chan struct{}),
closedCh: make(chan struct{}),
}
conn.wg.Add(2)
go conn.recv4()
go conn.recv6()
return conn, nil
}
func (c *xicmpConnClient) ring(a, b uint16) uint16 {
return min(a-b, b-a)
}
func (c *xicmpConnClient) closed() bool {
select {
case <-c.closeCh:
case <-c.closedCh:
return true
default:
return false
@@ -109,28 +104,26 @@ func (c *xicmpConnClient) closed() bool {
}
func (c *xicmpConnClient) recv4() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, addr, err := c.icmp4.ReadFrom(b[:])
if err != nil {
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:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
continue
}
msg, err := icmp.ParseMessage(1, b[:n])
@@ -153,6 +146,10 @@ func (c *xicmpConnClient) recv4() {
continue
}
if c.ring(uint16(echo.Seq), uint16(c.seq)) > 1000 {
continue
}
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
continue
}
@@ -169,7 +166,7 @@ func (c *xicmpConnClient) recv4() {
p: p,
addr: addr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -177,28 +174,26 @@ func (c *xicmpConnClient) recv4() {
}
func (c *xicmpConnClient) recv6() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
break
}
n, addr, err := c.icmp6.ReadFrom(b[:])
if err != nil {
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:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
continue
}
msg, err := icmp.ParseMessage(58, b[:n])
@@ -221,6 +216,10 @@ func (c *xicmpConnClient) recv6() {
continue
}
if c.ring(uint16(echo.Seq), uint16(c.seq)) > 1000 {
continue
}
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
continue
}
@@ -237,7 +236,7 @@ func (c *xicmpConnClient) recv6() {
p: p,
addr: addr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -245,15 +244,16 @@ func (c *xicmpConnClient) recv6() {
}
func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet, ok := <-c.readCh
if ok {
select {
case packet := <-c.readCh:
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p)
}
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) {
@@ -268,9 +268,9 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.seq %= 65536
c.mu.Unlock()
ip := c.ip
ip := addr.(*net.UDPAddr).IP
if len(c.ips) > 0 {
ip = c.ips[mrand.Intn(len(c.ips))].AsSlice()
ip = c.ips[mathrand.Intn(len(c.ips))].AsSlice()
}
if c.udp {
@@ -294,9 +294,10 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
}
if err != nil {
errors.LogErrorInner(context.Background(), err, "send err")
errors.LogErrorInner(context.Background(), err, "xicmp write")
return 0, err
}
return len(p), nil
}
@@ -306,23 +307,15 @@ func (c *xicmpConnClient) Close() error {
if c.closed() {
return nil
}
close(c.closeCh)
close(c.closedCh)
_ = c.icmp4.Close()
_ = c.icmp6.Close()
c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
_ = c.conn.Close()
return nil
}
func (c *xicmpConnClient) LocalAddr() net.Addr {
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
return c.conn.LocalAddr()
}
func (c *xicmpConnClient) SetDeadline(t time.Time) error {
+17 -12
View File
@@ -1,23 +1,28 @@
package xicmp
import (
"errors"
"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) HandleDial() {}
func (c *Config) UDP() {
}
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")
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("xicmp requires being at the outermost level")
}
return NewConnClient(c, dest)
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c)
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)
}
+43 -53
View File
@@ -37,17 +37,17 @@ type record struct {
}
type xicmpConnServer struct {
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closeCh chan struct{}
wg sync.WaitGroup
mu sync.Mutex
conn net.PacketConn
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closedCh chan struct{}
mu sync.Mutex
}
func NewConnServer(c *Config) (net.PacketConn, error) {
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return nil, err
@@ -63,16 +63,16 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
}
conn := &xicmpConnServer{
icmp4: icmp4,
icmp6: icmp6,
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closeCh: make(chan struct{}),
conn: raw,
icmp4: icmp4,
icmp6: icmp6,
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closedCh: make(chan struct{}),
}
go conn.clean()
conn.wg.Add(2)
go conn.recv4()
go conn.recv6()
@@ -81,7 +81,7 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
func (c *xicmpConnServer) closed() bool {
select {
case <-c.closeCh:
case <-c.closedCh:
return true
default:
return false
@@ -102,35 +102,33 @@ func (c *xicmpConnServer) clean() {
}
}
c.mu.Unlock()
case <-c.closeCh:
case <-c.closedCh:
return
}
}
}
func (c *xicmpConnServer) recv4() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, addr, err := c.icmp4.ReadFrom(b[:])
if err != nil {
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:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
continue
}
msg, err := icmp.ParseMessage(1, b[:n])
@@ -181,7 +179,7 @@ func (c *xicmpConnServer) recv4() {
p: p,
addr: cAddr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -189,28 +187,26 @@ func (c *xicmpConnServer) recv4() {
}
func (c *xicmpConnServer) recv6() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, addr, err := c.icmp6.ReadFrom(b[:])
if err != nil {
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:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
continue
}
msg, err := icmp.ParseMessage(58, b[:n])
@@ -261,7 +257,7 @@ func (c *xicmpConnServer) recv6() {
p: p,
addr: cAddr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -269,15 +265,16 @@ func (c *xicmpConnServer) recv6() {
}
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet, ok := <-c.readCh
if ok {
select {
case packet := <-c.readCh:
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p)
}
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) {
@@ -313,9 +310,10 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
}
if err != nil {
errors.LogErrorInner(context.Background(), err, "send err")
errors.LogErrorInner(context.Background(), err, "xicmp write")
return 0, err
}
return len(p), nil
}
@@ -325,23 +323,15 @@ func (c *xicmpConnServer) Close() error {
if c.closed() {
return nil
}
close(c.closeCh)
close(c.closedCh)
_ = c.icmp4.Close()
_ = c.icmp6.Close()
c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
_ = c.conn.Close()
return nil
}
func (c *xicmpConnServer) LocalAddr() net.Addr {
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
return c.conn.LocalAddr()
}
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
@@ -39,19 +39,19 @@ type record struct {
}
type xicmpConnServer struct {
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ipv4PC *ipv4.PacketConn
ipv6PC *ipv6.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closeCh chan struct{}
wg sync.WaitGroup
mu sync.Mutex
conn net.PacketConn
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ipv4PC *ipv4.PacketConn
ipv6PC *ipv6.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closedCh chan struct{}
mu sync.Mutex
}
func NewConnServer(c *Config) (net.PacketConn, error) {
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return nil, err
@@ -67,21 +67,21 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
}
conn := &xicmpConnServer{
icmp4: icmp4,
icmp6: icmp6,
ipv4PC: icmp4.IPv4PacketConn(),
ipv6PC: icmp6.IPv6PacketConn(),
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closeCh: make(chan struct{}),
conn: raw,
icmp4: icmp4,
icmp6: icmp6,
ipv4PC: icmp4.IPv4PacketConn(),
ipv6PC: icmp6.IPv6PacketConn(),
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closedCh: make(chan struct{}),
}
common.Must(conn.ipv4PC.SetControlMessage(ipv4.FlagDst, true))
common.Must(conn.ipv6PC.SetControlMessage(ipv6.FlagDst, true))
go conn.clean()
conn.wg.Add(2)
go conn.recv4()
go conn.recv6()
@@ -90,7 +90,7 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
func (c *xicmpConnServer) closed() bool {
select {
case <-c.closeCh:
case <-c.closedCh:
return true
default:
return false
@@ -111,35 +111,33 @@ func (c *xicmpConnServer) clean() {
}
}
c.mu.Unlock()
case <-c.closeCh:
case <-c.closedCh:
return
}
}
}
func (c *xicmpConnServer) recv4() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, cm, addr, err := c.ipv4PC.ReadFrom(b[:])
if err != nil {
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:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
continue
}
msg, err := icmp.ParseMessage(1, b[:n])
@@ -191,7 +189,7 @@ func (c *xicmpConnServer) recv4() {
p: p,
addr: cAddr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -199,28 +197,26 @@ func (c *xicmpConnServer) recv4() {
}
func (c *xicmpConnServer) recv6() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, cm, addr, err := c.ipv6PC.ReadFrom(b[:])
if err != nil {
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:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
continue
}
msg, err := icmp.ParseMessage(58, b[:n])
@@ -272,7 +268,7 @@ func (c *xicmpConnServer) recv6() {
p: p,
addr: cAddr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -280,15 +276,16 @@ func (c *xicmpConnServer) recv6() {
}
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet, ok := <-c.readCh
if ok {
select {
case packet := <-c.readCh:
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p)
}
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) {
@@ -324,9 +321,10 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
}
if err != nil {
errors.LogErrorInner(context.Background(), err, "send err")
errors.LogErrorInner(context.Background(), err, "xicmp write")
return 0, err
}
return len(p), nil
}
@@ -336,23 +334,15 @@ func (c *xicmpConnServer) Close() error {
if c.closed() {
return nil
}
close(c.closeCh)
close(c.closedCh)
_ = c.icmp4.Close()
_ = c.icmp6.Close()
c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
_ = c.conn.Close()
return nil
}
func (c *xicmpConnServer) LocalAddr() net.Addr {
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
return c.conn.LocalAddr()
}
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
+5 -4
View File
@@ -2,12 +2,13 @@ package xmc
import (
"fmt"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
func (c *Config) TCP() {
}
func (c *Config) WrapConnClient(conn net.Conn) (net.Conn, error) {
profiles, err := profilesFromConfig(c.Profiles)
if err != nil {
return nil, fmt.Errorf("minecraft finalmask: %w", err)
+11 -6
View File
@@ -83,6 +83,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
}
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
sockopt := streamSettings.SocketSettings
grpcSettings := streamSettings.ProtocolSettings.(*Config)
if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found && client.GetState() != connectivity.Shutdown {
@@ -123,13 +124,17 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
gctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx))
gctx = session.ContextWithTimeoutOnly(gctx, true)
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)
}
c, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt)
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 {
config := tlsConfig.GetTLSConfig(tls.WithDestination(dest))
if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {
+19 -11
View File
@@ -104,20 +104,28 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i
go func() {
var streamListener net.Listener
var err error
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
streamListener, err = internet.ListenSystem(ctx, &net.UnixAddr{
Name: address.Domain(),
Net: "unix",
}, settings.SocketSettings)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to listen on ", address)
return
}
} else { // tcp
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, settings.SocketSettings)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to listen on ", address, ":", port)
return
}
}
if settings.FinalMask != nil {
streamListener, err = settings.FinalMask.Listen(ctx, addr)
} else {
streamListener, err = internet.ListenSystem(ctx, addr, settings.SocketSettings)
}
if err != nil {
errors.LogErrorInner(ctx, err, "failed to listen on ", address, ":", port)
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()+"`")
+10 -7
View File
@@ -46,18 +46,21 @@ func (c *ConnRF) Read(b []byte) (int, error) {
func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
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)
}
pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to dial to ", dest)
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 requestURL url.URL
tConfig := tls.ConfigFromStreamSettings(streamSettings)
+19 -11
View File
@@ -124,21 +124,29 @@ func ListenHTTPUpgrade(ctx context.Context, address net.Address, port net.Port,
}
var listener net.Listener
var err error
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
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
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen TCP(for HttpUpgrade) on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening TCP(for HttpUpgrade) on ", address, ":", port)
}
if streamSettings.FinalMask != nil {
listener, err = streamSettings.FinalMask.Listen(ctx, addr)
} else {
listener, err = internet.ListenSystem(ctx, addr, streamSettings.SocketSettings)
if streamSettings.TcpmaskManager != nil {
listener, _ = streamSettings.TcpmaskManager.WrapListener(listener)
}
if err != nil {
return nil, errors.New("failed to listen ", addr.Network(), "(for HttpUpgrade) on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening ", addr.Network(), "(for HttpUpgrade) on ", address, ":", port)
if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {
errors.LogWarning(ctx, "accepting PROXY protocol")
+7 -4
View File
@@ -103,11 +103,14 @@ func (c *InterConn) Update() {
func (c *InterConn) Read(p []byte) (int, error) {
b, ok := <-c.ch
if ok {
c.Update()
return copy(p, b), nil
if !ok {
return 0, io.EOF
}
return 0, io.EOF
if len(p) < len(b) {
return 0, io.ErrShortBuffer
}
c.Update()
return copy(p, b), nil
}
func (c *InterConn) Write(p []byte) (int, error) {
+63 -28
View File
@@ -2,7 +2,8 @@ package hysteria
import (
"context"
gotls "crypto/tls"
go_tls "crypto/tls"
"math/rand"
"net/http"
"net/url"
"reflect"
@@ -21,6 +22,7 @@ import (
"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/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/tls"
)
@@ -28,12 +30,12 @@ import (
type client struct {
sync.Mutex
dest net.Destination
config *Config
tlsConfig *gotls.Config
socketConfig *internet.SocketConfig
finalMask *finalmask.FinalMask
quicParams *internet.QuicParams
dest net.Destination
config *Config
tlsConfig *go_tls.Config
socketConfig *internet.SocketConfig
udpmaskManager *finalmask.UdpmaskManager
quicParams *internet.QuicParams
conn *quic.Conn
tr *quic.Transport
@@ -76,6 +78,7 @@ func (c *client) dial(ctx context.Context) error {
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
@@ -112,30 +115,62 @@ func (c *client) dial(ctx context.Context) error {
// quicConfig.KeepAlivePeriod = 10 * time.Second
// }
var pktConn net.PacketConn
var udpAddr net.Addr
if c.finalMask != nil {
conn, err := c.finalMask.DialUDP(ctx, c.dest)
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 {
return errors.New("failed to dial to dest").Base(err)
}
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)
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("")
}
var pktConn net.PacketConn
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = c.RemoteAddr()
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
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 {
return errors.New("failed to dial to dest").Base(err)
}
switch c := raw.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = raw.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))
}
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}
@@ -149,7 +184,7 @@ func (c *client) dial(ctx context.Context) error {
rt := &http3.Transport{
TLSClientConfig: c.tlsConfig,
QUICConfig: quicConfig,
Dial: func(ctx context.Context, _ string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
Dial: func(ctx context.Context, _ string, tlsCfg *go_tls.Config, cfg *quic.Config) (*quic.Conn, error) {
qc, err := tr.DialEarly(ctx, udpAddr, tlsCfg, cfg)
if err != nil {
return nil, err
@@ -315,12 +350,12 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
c = manager.m[dialerConf{dest, streamSettings}]
if c == nil {
c = &client{
dest: dest,
config: streamSettings.ProtocolSettings.(*Config),
tlsConfig: tlsConfig.GetTLSConfig(tls.WithDestination(dest)),
socketConfig: streamSettings.SocketSettings,
finalMask: streamSettings.FinalMask,
quicParams: streamSettings.QuicParams,
dest: dest,
config: streamSettings.ProtocolSettings.(*Config),
tlsConfig: tlsConfig.GetTLSConfig(tls.WithDestination(dest)),
socketConfig: streamSettings.SocketSettings,
udpmaskManager: streamSettings.UdpmaskManager,
quicParams: streamSettings.QuicParams,
}
manager.m[dialerConf{dest, streamSettings}] = c
}
+12 -8
View File
@@ -231,8 +231,8 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
transport.TLSClientConfig.InsecureSkipVerify = true
}
case "", "unix":
path := u.Path
u = &url.URL{Scheme: "http", Host: "localhost"}
path := u.Path
dialer := &net.Dialer{Timeout: 30 * time.Second}
transport = transport.Clone()
transport.Proxy = nil
@@ -281,6 +281,7 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
@@ -316,17 +317,20 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
quicConfig.MaxIncomingStreams = 1024
}
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)
}
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
if err != nil {
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
if !quicParams.DisableStatelessReset {
k = &quic.StatelessResetKey{}
@@ -1,165 +0,0 @@
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
@@ -0,0 +1,254 @@
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
}
+28 -7
View File
@@ -3,6 +3,7 @@ package kcp
import (
"context"
"io"
reflect "reflect"
"sync/atomic"
"github.com/xtls/xray-core/common"
@@ -10,6 +11,7 @@ import (
"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/cnc"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
@@ -49,17 +51,36 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet
dest.Network = net.Network_UDP
errors.LogInfo(ctx, "dialing mKCP to ", dest)
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)
}
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
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)
reader := &KCPPacketReader{}
+23 -47
View File
@@ -1,12 +1,7 @@
package internet
import (
"context"
"reflect"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
@@ -17,7 +12,8 @@ type MemoryStreamConfig struct {
ProtocolSettings interface{}
SecurityType string
SecuritySettings interface{}
FinalMask *finalmask.FinalMask
TcpmaskManager *finalmask.TcpmaskManager
UdpmaskManager *finalmask.UdpmaskManager
QuicParams *QuicParams
SocketSettings *SocketConfig
DownloadSettings *MemoryStreamConfig
@@ -55,53 +51,33 @@ func ToMemoryStreamConfig(s *StreamConfig) (*MemoryStreamConfig, error) {
mss.SecuritySettings = ess
}
var tcpMasks []finalmask.TCPMask
var udpMasks []finalmask.UDPMask
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))
if s != nil && len(s.Tcpmasks) > 0 {
var masks []finalmask.Tcpmask
for _, msg := range s.Tcpmasks {
instance, err := msg.GetInstance()
if err != nil {
return nil, err
}
masks = append(masks, instance.(finalmask.Tcpmask))
}
mss.TcpmaskManager = finalmask.NewTcpmaskManager(masks)
}
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 {
return nil, nil, err
}
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))
}
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 {
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
}
+33 -34
View File
@@ -169,42 +169,41 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
return errors.New("failed to set TCP_MAXSEG", err)
}
}
}
if len(config.CustomSockopt) > 0 {
for _, custom := range config.CustomSockopt {
if custom.System != "" && custom.System != runtime.GOOS {
errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
continue
}
// Skip unwanted network type
// network might be tcp4 or tcp6
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
if !strings.HasPrefix(network, custom.Network) {
continue
}
level := 0x6 // default TCP
var opt int
if len(custom.Opt) == 0 {
return errors.New("No opt!")
} else {
opt, _ = strconv.Atoi(custom.Opt)
}
if custom.Level != "" {
level, _ = strconv.Atoi(custom.Level)
}
if custom.Type == "int" {
value, _ := strconv.Atoi(custom.Value)
if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {
return errors.New("failed to set CustomSockoptInt", opt, value, err)
if len(config.CustomSockopt) > 0 {
for _, custom := range config.CustomSockopt {
if custom.System != "" && custom.System != runtime.GOOS {
errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
continue
}
} else if custom.Type == "str" {
if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {
return errors.New("failed to set CustomSockoptString", opt, custom.Value, err)
// Skip unwanted network type
// network might be tcp4 or tcp6
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
if !strings.HasPrefix(network, custom.Network) {
continue
}
level := 0x6 // default TCP
var opt int
if len(custom.Opt) == 0 {
return errors.New("No opt!")
} else {
opt, _ = strconv.Atoi(custom.Opt)
}
if custom.Level != "" {
level, _ = strconv.Atoi(custom.Level)
}
if custom.Type == "int" {
value, _ := strconv.Atoi(custom.Value)
if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {
return errors.New("failed to set CustomSockoptInt", opt, value, err)
}
} else if custom.Type == "str" {
if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {
return errors.New("failed to set CustomSockoptString", opt, custom.Value, err)
}
} else {
return errors.New("unknown CustomSockopt type:", custom.Type)
}
} else {
return errors.New("unknown CustomSockopt type:", custom.Type)
}
}
}
+20 -17
View File
@@ -68,7 +68,7 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
}
c.transportConfig.FillStreamRequest(req, sessionId, "")
wrc = &WaitReadCloser{wait: done.New()}
wrc = &WaitReadCloser{Wait: make(chan struct{})}
go func() {
resp, err := c.client.Do(req)
if err != nil {
@@ -188,35 +188,38 @@ func (c *DefaultDialerClient) Close() error {
}
type WaitReadCloser struct {
wait *done.Instance
reader atomic.Pointer[io.ReadCloser]
Wait chan struct{}
io.ReadCloser
}
func (w *WaitReadCloser) Set(rc io.ReadCloser) {
w.reader.Store(&rc)
if w.wait.Done() {
if p := w.reader.Swap(nil); p != nil {
(*p).Close()
w.ReadCloser = rc
defer func() {
if recover() != nil {
rc.Close()
}
}
w.wait.Close()
}()
close(w.Wait)
}
func (w *WaitReadCloser) Read(b []byte) (int, error) {
rc := w.reader.Load()
if rc == nil {
<-w.wait.Wait()
if rc = w.reader.Load(); rc == nil {
if w.ReadCloser == nil {
if <-w.Wait; w.ReadCloser == nil {
return 0, io.ErrClosedPipe
}
}
return (*rc).Read(b)
return w.ReadCloser.Read(b)
}
func (w *WaitReadCloser) Close() error {
w.wait.Close()
if p := w.reader.Swap(nil); p != nil {
return (*p).Close()
if w.ReadCloser != nil {
return w.ReadCloser.Close()
}
defer func() {
if recover() != nil && w.ReadCloser != nil {
w.ReadCloser.Close()
}
}()
close(w.Wait)
return nil
}
+60 -23
View File
@@ -5,10 +5,11 @@ import (
gotls "crypto/tls"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptrace"
"net/url"
"reflect"
reflect "reflect"
"runtime"
"strconv"
"sync"
@@ -25,9 +26,9 @@ import (
"github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/browser_dialer"
"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/bbr"
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
@@ -117,17 +118,20 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
transportConfig := streamSettings.ProtocolSettings.(*Config)
dialContext := func(ctxInner context.Context) (net.Conn, error) {
var conn net.Conn
var err error
if streamSettings.FinalMask != nil {
conn, err = streamSettings.FinalMask.DialTCP(ctxInner, dest)
} else {
conn, err = internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)
}
conn, err := internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(conn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = newConn
}
if realityConfig != nil {
return reality.UClient(conn, realityConfig, ctxInner, dest)
}
@@ -158,6 +162,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
@@ -193,30 +198,62 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
QUICConfig: quicConfig,
TLSClientConfig: gotlsConfig,
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
var pktConn net.PacketConn
var udpAddr net.Addr
if streamSettings.FinalMask != nil {
conn, err := streamSettings.FinalMask.DialUDP(ctx, dest)
udpHopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
pktConn = conn.(*finalmask.PacketConnWrapper).PacketConn
udpAddr = conn.RemoteAddr()
} else {
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("")
}
var pktConn net.PacketConn
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = c.RemoteAddr()
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
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))
dest.Port = net.Port(quicParams.UdpHop.Ports[index])
}
raw, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
switch c := raw.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = raw.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))
}
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 streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO}
+35 -23
View File
@@ -463,22 +463,37 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
l.isH3 = len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "h3"
var err error
if l.isH3 {
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 port == net.Port(0) { // unix
l.listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
Name: address.Domain(),
Net: "unix",
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen UNIX domain socket for XHTTP on ", address).Base(err)
}
errors.LogInfo(ctx, "listening UNIX domain socket for XHTTP on ", address)
} else if l.isH3 { // quic
Conn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen UDP for XHTTP/3 on ", address, ":", port).Base(err)
}
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(Conn)
if err != nil {
Conn.Close()
return nil, errors.New("mask err").Base(err)
}
Conn = newConn
}
quicParams := streamSettings.QuicParams
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
@@ -498,7 +513,7 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
common.Must2(rand.Read((*k)[:]))
}
tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO, StatelessResetKey: k}
tr := &quic.Transport{Conn: Conn, DisableGSO: quicParams.DisableGSO, StatelessResetKey: k}
l.h3listener, err = tr.ListenEarly(tlsConfig, quicConfig)
if err != nil {
@@ -520,24 +535,21 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
errors.LogErrorInner(ctx, err, "failed to serve HTTP/3 for XHTTP/3")
}
_ = tr.Close()
_ = pktConn.Close()
_ = Conn.Close()
}()
} else {
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
} else { // tcp
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
}
if streamSettings.FinalMask != nil {
l.listener, err = streamSettings.FinalMask.Listen(ctx, addr)
} else {
l.listener, err = internet.ListenSystem(ctx, addr, streamSettings.SocketSettings)
}
} else { // tcp
l.listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen ", addr.Network(), " for XHTTP on ", address, ":", port).Base(err)
return nil, errors.New("failed to listen TCP for XHTTP on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening ", addr.Network(), " for XHTTP on ", address, ":", port)
errors.LogInfo(ctx, "listening TCP for XHTTP on ", address, ":", port)
}
if !l.isH3 && streamSettings.TcpmaskManager != nil {
l.listener, _ = streamSettings.TcpmaskManager.WrapListener(l.listener)
}
// tcp/unix (h1/h2)
+1 -1
View File
@@ -235,5 +235,5 @@ func (c *FakePacketConn) WriteTo(p []byte, _ net.Addr) (n int, err error) {
}
func (c *FakePacketConn) LocalAddr() net.Addr {
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
return &net.UDPAddr{IP: c.Conn.LocalAddr().(*net.TCPAddr).IP, Port: c.Conn.LocalAddr().(*net.TCPAddr).Port}
}
+11 -8
View File
@@ -19,15 +19,18 @@ import (
// Dial dials a new TCP connection to the given destination.
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
errors.LogInfo(ctx, "dialing TCP to ", dest)
var conn net.Conn
var err error
if streamSettings.FinalMask != nil {
conn, err = streamSettings.FinalMask.DialTCP(ctx, dest)
} else {
conn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
return nil, err
}
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(conn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = newConn
}
if config := tls.ConfigFromStreamSettings(streamSettings); config != nil {
+19 -11
View File
@@ -41,21 +41,29 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSe
}
var listener net.Listener
var err error
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
} else { // tcp
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
}
if streamSettings.FinalMask != nil {
listener, err = streamSettings.FinalMask.Listen(ctx, addr)
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
Name: address.Domain(),
Net: "unix",
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen Unix Domain Socket on ", address).Base(err)
}
errors.LogInfo(ctx, "listening Unix Domain Socket on ", address)
} else {
listener, err = internet.ListenSystem(ctx, addr, streamSettings.SocketSettings)
listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen TCP on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening TCP on ", address, ":", port)
}
if err != nil {
return nil, errors.New("failed to listen ", addr.Network(), " on ", address, ":", port).Base(err)
if streamSettings.TcpmaskManager != nil {
listener, _ = streamSettings.TcpmaskManager.WrapListener(listener)
}
errors.LogInfo(ctx, "listening ", addr.Network(), " on ", address, ":", port)
if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {
errors.LogWarning(ctx, "accepting PROXY protocol")
+37 -8
View File
@@ -2,9 +2,12 @@ package udp
import (
"context"
"reflect"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"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/stat"
)
@@ -12,14 +15,40 @@ import (
func init() {
common.Must(internet.RegisterTransportDialer(protocolName,
func(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
if streamSettings != nil && streamSettings.FinalMask != nil {
return streamSettings.FinalMask.DialUDP(ctx, dest)
} else {
var sockopt *internet.SocketConfig
if streamSettings != nil && streamSettings.SocketSettings != nil {
sockopt = streamSettings.SocketSettings
}
return internet.DialSystem(ctx, dest, sockopt)
var sockopt *internet.SocketConfig
if streamSettings != nil {
sockopt = streamSettings.SocketSettings
}
conn, err := internet.DialSystem(ctx, dest, sockopt)
if err != nil {
return nil, err
}
if streamSettings != nil && 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,
}
}
return conn, nil
}))
}
+14 -5
View File
@@ -58,15 +58,24 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe
}
var err error
if streamSettings.FinalMask != nil {
hub.conn, err = streamSettings.FinalMask.ListenPacket(ctx, &net.UDPAddr{IP: address.IP(), Port: int(port)})
} else {
hub.conn, err = internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
}
hub.conn, err = internet.ListenSystemPacket(ctx, &net.UDPAddr{
IP: address.IP(),
Port: int(port),
}, sockopt)
if err != nil {
return nil, err
}
raw := hub.conn
if streamSettings.UdpmaskManager != nil {
hub.conn, err = streamSettings.UdpmaskManager.WrapPacketConnServer(raw)
if err != nil {
raw.Close()
return nil, errors.New("mask err").Base(err)
}
}
errors.LogInfo(ctx, "listening UDP on ", address, ":", port)
hub.udpConn, _ = hub.conn.(*net.UDPConn)
hub.cache = make(chan *udp.Packet, hub.capacity)
+24 -35
View File
@@ -48,16 +48,20 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in
dialer := &websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
var conn net.Conn
var err error
if streamSettings.FinalMask != nil {
conn, err = streamSettings.FinalMask.DialTCP(ctx, dest)
} else {
conn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
return nil, err
}
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(conn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = newConn
}
return conn, err
},
ReadBufferSize: 4 * 1024,
@@ -75,15 +79,19 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in
if fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil {
dialer.NetDialTLSContext = func(_ context.Context, _, addr string) (net.Conn, error) {
// Like the NetDial in the dialer
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)
}
pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
errors.LogErrorInner(ctx, err, "failed to dial to "+addr)
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
}
// TLS and apply the handshake
@@ -167,25 +175,6 @@ type delayDialConn struct {
streamSettings *internet.MemoryStreamConfig
}
// LocalAddr returns nil until the deferred WebSocket dial has completed.
// Without this method, Go promotes LocalAddr from the embedded net.Conn; the
// embedded interface is nil before the first Write, so the promoted call panics.
func (d *delayDialConn) LocalAddr() net.Addr {
if d.Conn == nil {
return nil
}
return d.Conn.LocalAddr()
}
// RemoteAddr returns nil until the deferred WebSocket dial has completed.
// See LocalAddr for why an explicit method is required here.
func (d *delayDialConn) RemoteAddr() net.Addr {
if d.Conn == nil {
return nil
}
return d.Conn.RemoteAddr()
}
func (d *delayDialConn) Write(b []byte) (int, error) {
if d.closed {
return 0, io.ErrClosedPipe
+19 -11
View File
@@ -97,21 +97,29 @@ func ListenWS(ctx context.Context, address net.Address, port net.Port, streamSet
}
var listener net.Listener
var err error
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
Name: address.Domain(),
Net: "unix",
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen unix domain socket(for WS) on ", address).Base(err)
}
errors.LogInfo(ctx, "listening unix domain socket(for WS) on ", address)
} else { // tcp
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen TCP(for WS) on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening TCP(for WS) on ", address, ":", port)
}
if streamSettings.FinalMask != nil {
listener, err = streamSettings.FinalMask.Listen(ctx, addr)
} else {
listener, err = internet.ListenSystem(ctx, addr, streamSettings.SocketSettings)
if streamSettings.TcpmaskManager != nil {
listener, _ = streamSettings.TcpmaskManager.WrapListener(listener)
}
if err != nil {
return nil, errors.New("failed to listen ", addr.Network(), "(for WS) on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening ", addr.Network(), "(for WS) on ", address, ":", port)
if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {
errors.LogWarning(ctx, "accepting PROXY protocol")
-153
View File
@@ -1,153 +0,0 @@
package xdrive
import (
"context"
gotls "crypto/tls"
"net/http"
"time"
"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/reality"
"github.com/xtls/xray-core/transport/internet/tls"
"golang.org/x/net/http2"
)
type serviceTransport struct {
plain http.RoundTripper
secure http.RoundTripper
}
func (t *serviceTransport) RoundTrip(r *http.Request) (*http.Response, error) {
if r.URL.Scheme == "https" {
return t.secure.RoundTrip(r)
}
return t.plain.RoundTrip(r)
}
func newServiceClient(streamSettings *internet.MemoryStreamConfig, timeout time.Duration, maxConns int) *http.Client {
var (
tlsConfig *tls.Config
realityConfig *reality.Config
sockopt *internet.SocketConfig
fronting *net.Destination
)
if streamSettings != nil {
tlsConfig = tls.ConfigFromStreamSettings(streamSettings)
realityConfig = reality.ConfigFromStreamSettings(streamSettings)
sockopt = streamSettings.SocketSettings
fronting = streamSettings.Destination
}
overHTTP2 := allowsHTTP2(tlsConfig, realityConfig)
dial := func(ctx context.Context, addr string) (net.Conn, net.Destination, error) {
host, err := net.ParseDestination("tcp:" + addr)
if err != nil {
return nil, host, errors.New("bad address: ", addr).Base(err)
}
target := host
if fronting != nil {
target.Address = fronting.Address
if fronting.Port != 0 {
target.Port = fronting.Port
}
}
var conn net.Conn
if streamSettings.FinalMask != nil {
conn, err = streamSettings.FinalMask.DialTCP(ctx, target)
} else {
conn, err = internet.DialSystem(ctx, target, sockopt)
}
if err != nil {
return nil, host, errors.New("failed to dial to dest").Base(err)
}
return conn, host, nil
}
dialPlain := func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, _, err := dial(ctx, addr)
return conn, err
}
dialTLS := func(ctx context.Context, addr string) (net.Conn, error) {
conn, host, err := dial(ctx, addr)
if err != nil {
return nil, err
}
if realityConfig != nil {
return reality.UClient(conn, realityConfig, ctx, host)
}
gotlsConfig := &gotls.Config{ServerName: host.Address.String()}
if tlsConfig != nil {
gotlsConfig = tlsConfig.GetTLSConfig(tls.WithDestination(host))
}
if len(gotlsConfig.NextProtos) != 1 {
if overHTTP2 {
gotlsConfig.NextProtos = []string{"h2"}
} else {
gotlsConfig.NextProtos = []string{"http/1.1"}
}
}
if tlsConfig != nil {
if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {
uconn := tls.UClient(conn, gotlsConfig, fingerprint)
if err := uconn.(*tls.UConn).HandshakeContext(ctx); err != nil {
conn.Close()
return nil, err
}
return uconn, nil
}
}
return tls.Client(conn, gotlsConfig), nil
}
var secure http.RoundTripper
if overHTTP2 {
secure = &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) {
return dialTLS(ctx, addr)
},
IdleConnTimeout: net.ConnIdleTimeout,
}
} else {
secure = &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialTLS(ctx, addr)
},
IdleConnTimeout: net.ConnIdleTimeout,
MaxIdleConns: maxConns,
MaxIdleConnsPerHost: maxConns,
MaxConnsPerHost: maxConns,
}
}
return &http.Client{
Transport: &serviceTransport{
plain: &http.Transport{
DialContext: dialPlain,
IdleConnTimeout: net.ConnIdleTimeout,
MaxIdleConns: maxConns,
MaxIdleConnsPerHost: maxConns,
MaxConnsPerHost: maxConns,
},
secure: secure,
},
Timeout: timeout,
}
}
func allowsHTTP2(tlsConfig *tls.Config, realityConfig *reality.Config) bool {
if realityConfig != nil {
return true
}
if tlsConfig == nil {
return false
}
return len(tlsConfig.NextProtocol) == 1 && tlsConfig.NextProtocol[0] == "h2"
}
-101
View File
@@ -1,101 +0,0 @@
package xdrive
import (
"context"
gotls "crypto/tls"
"net"
"net/http"
"sync"
"testing"
"time"
xnet "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/tls"
)
func recordingTLSListener(t *testing.T, sni *string, mu *sync.Mutex) net.Listener {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
go func() {
for {
conn, err := ln.Accept()
if err != nil {
return
}
cfg := &gotls.Config{
GetConfigForClient: func(hello *gotls.ClientHelloInfo) (*gotls.Config, error) {
mu.Lock()
*sni = hello.ServerName
mu.Unlock()
return nil, nil
},
}
tconn := gotls.Server(conn, cfg)
tconn.HandshakeContext(context.Background())
tconn.Close()
}
}()
t.Cleanup(func() { ln.Close() })
return ln
}
func sniForSettings(t *testing.T, serverName string) string {
t.Helper()
var (
sni string
mu sync.Mutex
)
ln := recordingTLSListener(t, &sni, &mu)
addr := ln.Addr().(*net.TCPAddr)
settings := &internet.MemoryStreamConfig{
ProtocolName: protocolName,
Destination: &xnet.Destination{
Address: xnet.ParseAddress(addr.IP.String()),
Port: xnet.Port(addr.Port),
Network: xnet.Network_TCP,
},
SecuritySettings: &tls.Config{ServerName: serverName},
}
prev := driveFilesURL
driveFilesURL = "https://www.googleapis.com/drive/v3/files"
defer func() { driveFilesURL = prev }()
client := newServiceClient(settings, 5*time.Second, 8)
req, err := http.NewRequest(http.MethodGet, driveFilesURL, nil)
if err != nil {
t.Fatalf("request: %v", err)
}
client.Do(req)
deadline := time.Now().Add(3 * time.Second)
for time.Now().Before(deadline) {
mu.Lock()
got := sni
mu.Unlock()
if got != "" {
return got
}
time.Sleep(5 * time.Millisecond)
}
return ""
}
func TestServiceSNIDefaultsToHost(t *testing.T) {
if got := sniForSettings(t, ""); got != "www.googleapis.com" {
t.Fatalf("SNI defaulted to %q, want the host www.googleapis.com, not address", got)
}
}
func TestServiceSNIOverride(t *testing.T) {
if got := sniForSettings(t, "www.google.com"); got != "www.google.com" {
t.Fatalf("explicit serverName gave SNI %q, want www.google.com", got)
}
}
-222
View File
@@ -1,222 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: transport/internet/xdrive/config.proto
package xdrive
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"`
RemoteFolder string `protobuf:"bytes,1,opt,name=remote_folder,json=remoteFolder,proto3" json:"remote_folder,omitempty"`
Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"`
Secrets []string `protobuf:"bytes,3,rep,name=secrets,proto3" json:"secrets,omitempty"`
SegmentBytes uint32 `protobuf:"varint,4,opt,name=segment_bytes,json=segmentBytes,proto3" json:"segment_bytes,omitempty"`
FlushIntervalMs uint32 `protobuf:"varint,5,opt,name=flush_interval_ms,json=flushIntervalMs,proto3" json:"flush_interval_ms,omitempty"`
PollIntervalMs uint32 `protobuf:"varint,6,opt,name=poll_interval_ms,json=pollIntervalMs,proto3" json:"poll_interval_ms,omitempty"`
MaxPollIntervalMs uint32 `protobuf:"varint,7,opt,name=max_poll_interval_ms,json=maxPollIntervalMs,proto3" json:"max_poll_interval_ms,omitempty"`
SessionTtlSeconds uint32 `protobuf:"varint,8,opt,name=session_ttl_seconds,json=sessionTtlSeconds,proto3" json:"session_ttl_seconds,omitempty"`
Concurrency uint32 `protobuf:"varint,9,opt,name=concurrency,proto3" json:"concurrency,omitempty"`
EagerWindowMs uint32 `protobuf:"varint,10,opt,name=eager_window_ms,json=eagerWindowMs,proto3" json:"eager_window_ms,omitempty"`
HoleTimeoutMs uint32 `protobuf:"varint,11,opt,name=hole_timeout_ms,json=holeTimeoutMs,proto3" json:"hole_timeout_ms,omitempty"`
Template string `protobuf:"bytes,12,opt,name=template,proto3" json:"template,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_xdrive_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_xdrive_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_xdrive_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetRemoteFolder() string {
if x != nil {
return x.RemoteFolder
}
return ""
}
func (x *Config) GetService() string {
if x != nil {
return x.Service
}
return ""
}
func (x *Config) GetSecrets() []string {
if x != nil {
return x.Secrets
}
return nil
}
func (x *Config) GetSegmentBytes() uint32 {
if x != nil {
return x.SegmentBytes
}
return 0
}
func (x *Config) GetFlushIntervalMs() uint32 {
if x != nil {
return x.FlushIntervalMs
}
return 0
}
func (x *Config) GetPollIntervalMs() uint32 {
if x != nil {
return x.PollIntervalMs
}
return 0
}
func (x *Config) GetMaxPollIntervalMs() uint32 {
if x != nil {
return x.MaxPollIntervalMs
}
return 0
}
func (x *Config) GetSessionTtlSeconds() uint32 {
if x != nil {
return x.SessionTtlSeconds
}
return 0
}
func (x *Config) GetConcurrency() uint32 {
if x != nil {
return x.Concurrency
}
return 0
}
func (x *Config) GetEagerWindowMs() uint32 {
if x != nil {
return x.EagerWindowMs
}
return 0
}
func (x *Config) GetHoleTimeoutMs() uint32 {
if x != nil {
return x.HoleTimeoutMs
}
return 0
}
func (x *Config) GetTemplate() string {
if x != nil {
return x.Template
}
return ""
}
var File_transport_internet_xdrive_config_proto protoreflect.FileDescriptor
const file_transport_internet_xdrive_config_proto_rawDesc = "" +
"\n" +
"&transport/internet/xdrive/config.proto\x12\x1exray.transport.internet.xdrive\"\xcb\x03\n" +
"\x06Config\x12#\n" +
"\rremote_folder\x18\x01 \x01(\tR\fremoteFolder\x12\x18\n" +
"\aservice\x18\x02 \x01(\tR\aservice\x12\x18\n" +
"\asecrets\x18\x03 \x03(\tR\asecrets\x12#\n" +
"\rsegment_bytes\x18\x04 \x01(\rR\fsegmentBytes\x12*\n" +
"\x11flush_interval_ms\x18\x05 \x01(\rR\x0fflushIntervalMs\x12(\n" +
"\x10poll_interval_ms\x18\x06 \x01(\rR\x0epollIntervalMs\x12/\n" +
"\x14max_poll_interval_ms\x18\a \x01(\rR\x11maxPollIntervalMs\x12.\n" +
"\x13session_ttl_seconds\x18\b \x01(\rR\x11sessionTtlSeconds\x12 \n" +
"\vconcurrency\x18\t \x01(\rR\vconcurrency\x12&\n" +
"\x0feager_window_ms\x18\n" +
" \x01(\rR\reagerWindowMs\x12&\n" +
"\x0fhole_timeout_ms\x18\v \x01(\rR\rholeTimeoutMs\x12\x1a\n" +
"\btemplate\x18\f \x01(\tR\btemplateB5Z3github.com/xtls/xray-core/transport/internet/xdriveb\x06proto3"
var (
file_transport_internet_xdrive_config_proto_rawDescOnce sync.Once
file_transport_internet_xdrive_config_proto_rawDescData []byte
)
func file_transport_internet_xdrive_config_proto_rawDescGZIP() []byte {
file_transport_internet_xdrive_config_proto_rawDescOnce.Do(func() {
file_transport_internet_xdrive_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_xdrive_config_proto_rawDesc), len(file_transport_internet_xdrive_config_proto_rawDesc)))
})
return file_transport_internet_xdrive_config_proto_rawDescData
}
var file_transport_internet_xdrive_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_transport_internet_xdrive_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.transport.internet.xdrive.Config
}
var file_transport_internet_xdrive_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_xdrive_config_proto_init() }
func file_transport_internet_xdrive_config_proto_init() {
if File_transport_internet_xdrive_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_xdrive_config_proto_rawDesc), len(file_transport_internet_xdrive_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_xdrive_config_proto_goTypes,
DependencyIndexes: file_transport_internet_xdrive_config_proto_depIdxs,
MessageInfos: file_transport_internet_xdrive_config_proto_msgTypes,
}.Build()
File_transport_internet_xdrive_config_proto = out.File
file_transport_internet_xdrive_config_proto_goTypes = nil
file_transport_internet_xdrive_config_proto_depIdxs = nil
}
-19
View File
@@ -1,19 +0,0 @@
syntax = "proto3";
package xray.transport.internet.xdrive;
option go_package = "github.com/xtls/xray-core/transport/internet/xdrive";
message Config {
string remote_folder = 1;
string service = 2;
repeated string secrets = 3;
uint32 segment_bytes = 4;
uint32 flush_interval_ms = 5;
uint32 poll_interval_ms = 6;
uint32 max_poll_interval_ms = 7;
uint32 session_ttl_seconds = 8;
uint32 concurrency = 9;
uint32 eager_window_ms = 10;
uint32 hole_timeout_ms = 11;
string template = 12;
}
-139
View File
@@ -1,139 +0,0 @@
package xdrive
import (
"context"
"os"
"sync"
"time"
"github.com/xtls/xray-core/common/net"
)
var placeholderAddr = &net.TCPAddr{IP: net.IP{127, 0, 0, 1}, Port: 0}
type Conn struct {
cancel context.CancelFunc
writer *walWriter
reader *walReader
onClose func()
readBuf []byte
deadlineMu sync.Mutex
readDeadline time.Time
writeDeadline time.Time
closeOnce sync.Once
closeErr error
}
func newConn(ctx context.Context, storage Storage, writePrefix, readPrefix string, p params, onClose func()) *Conn {
ctx, cancel := context.WithCancel(ctx)
return &Conn{
cancel: cancel,
writer: newWALWriter(ctx, storage, writePrefix, p),
reader: newWALReader(ctx, storage, readPrefix, p),
onClose: onClose,
}
}
func (c *Conn) Read(b []byte) (int, error) {
if len(c.readBuf) == 0 {
data, err := c.receive()
if err != nil {
return 0, err
}
c.readBuf = data
}
n := copy(b, c.readBuf)
c.readBuf = c.readBuf[n:]
return n, nil
}
func (c *Conn) receive() ([]byte, error) {
deadline := c.getDeadline(true)
if deadline.IsZero() {
data, ok := <-c.reader.ch
if !ok {
return nil, c.reader.Err()
}
return data, nil
}
if !time.Now().Before(deadline) {
return nil, os.ErrDeadlineExceeded
}
timer := time.NewTimer(time.Until(deadline))
defer timer.Stop()
select {
case data, ok := <-c.reader.ch:
if !ok {
return nil, c.reader.Err()
}
return data, nil
case <-timer.C:
return nil, os.ErrDeadlineExceeded
}
}
func (c *Conn) Write(b []byte) (int, error) {
if deadline := c.getDeadline(false); !deadline.IsZero() && !time.Now().Before(deadline) {
return 0, os.ErrDeadlineExceeded
}
n, err := c.writer.Write(b)
if err == nil {
c.reader.Wake()
}
return n, err
}
func (c *Conn) Close() error {
c.closeOnce.Do(func() {
c.closeErr = c.writer.Close()
c.cancel()
if c.onClose != nil {
c.onClose()
}
})
return c.closeErr
}
func (c *Conn) LocalAddr() net.Addr {
return placeholderAddr
}
func (c *Conn) RemoteAddr() net.Addr {
return placeholderAddr
}
func (c *Conn) getDeadline(read bool) time.Time {
c.deadlineMu.Lock()
defer c.deadlineMu.Unlock()
if read {
return c.readDeadline
}
return c.writeDeadline
}
func (c *Conn) SetDeadline(t time.Time) error {
c.deadlineMu.Lock()
defer c.deadlineMu.Unlock()
c.readDeadline = t
c.writeDeadline = t
return nil
}
func (c *Conn) SetReadDeadline(t time.Time) error {
c.deadlineMu.Lock()
defer c.deadlineMu.Unlock()
c.readDeadline = t
return nil
}
func (c *Conn) SetWriteDeadline(t time.Time) error {
c.deadlineMu.Lock()
defer c.deadlineMu.Unlock()
c.writeDeadline = t
return nil
}
-575
View File
@@ -1,575 +0,0 @@
package xdrive
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet"
)
const (
flatSeparator = "~"
driveBoundary = "xdrive-boundary"
drivePageSize = 1000
driveMaxAttempts = 8
driveMaxInflight = 32
driveInlineLimit = 12000
driveTimeout = 60 * time.Second
)
var (
driveMaxBackoff = 8 * time.Second
driveTokenURL = "https://oauth2.googleapis.com/token"
driveFilesURL = "https://www.googleapis.com/drive/v3/files"
driveUploadURL = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name"
driveInitialBackoff = 200 * time.Millisecond
)
type driveStorage struct {
folder string
clientID string
clientSecret string
refreshToken string
client *http.Client
tokenURL string
filesURL string
uploadURL string
backoff time.Duration
tokenMu sync.Mutex
token string
tokenExpiry time.Time
inflight chan struct{}
idMu sync.Mutex
ids map[string]string
}
func newDriveStorage(streamSettings *internet.MemoryStreamConfig, config *Config) (*driveStorage, error) {
if config.RemoteFolder == "" {
return nil, errors.New(`empty "remoteFolder", it must be a Google Drive folder id`)
}
if len(config.Secrets) != 3 {
return nil, errors.New("Google Drive needs 3 secrets in order of ClientID, ClientSecret, RefreshToken")
}
for i, secret := range config.Secrets {
if secret == "" {
return nil, errors.New("Google Drive secret ", i, " is empty")
}
}
return &driveStorage{
folder: config.RemoteFolder,
clientID: config.Secrets[0],
clientSecret: config.Secrets[1],
refreshToken: config.Secrets[2],
client: newServiceClient(streamSettings, driveTimeout, driveMaxInflight),
tokenURL: driveTokenURL,
filesURL: driveFilesURL,
uploadURL: driveUploadURL,
backoff: driveInitialBackoff,
inflight: make(chan struct{}, driveMaxInflight),
ids: make(map[string]string),
}, nil
}
func flatten(name string) string {
return strings.ReplaceAll(name, "/", flatSeparator)
}
func quoteDriveValue(value string) string {
return strings.NewReplacer(`\`, `\\`, `'`, `\'`).Replace(value)
}
func (s *driveStorage) accessToken(ctx context.Context) (string, error) {
s.tokenMu.Lock()
defer s.tokenMu.Unlock()
if s.token != "" && time.Now().Before(s.tokenExpiry) {
return s.token, nil
}
form := url.Values{
"client_id": {s.clientID},
"client_secret": {s.clientSecret},
"refresh_token": {s.refreshToken},
"grant_type": {"refresh_token"},
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.tokenURL, strings.NewReader(form.Encode()))
if err != nil {
return "", errors.New("failed to build the token request").Base(err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := s.client.Do(req)
if err != nil {
return "", errors.New("failed to refresh the access token").Base(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return "", errors.New("failed to read the token response").Base(err)
}
if resp.StatusCode != http.StatusOK {
return "", errors.New("the token endpoint answered ", resp.StatusCode, ": ", string(body))
}
var parsed struct {
AccessToken string `json:"access_token"`
ExpiresIn int64 `json:"expires_in"`
}
if err := json.Unmarshal(body, &parsed); err != nil {
return "", errors.New("failed to parse the token response").Base(err)
}
if parsed.AccessToken == "" {
return "", errors.New("the token endpoint returned no access token")
}
lifetime := parsed.ExpiresIn
if lifetime > 60 {
lifetime -= 60
}
s.token = parsed.AccessToken
s.tokenExpiry = time.Now().Add(time.Duration(lifetime) * time.Second)
return s.token, nil
}
func jitter(backoff time.Duration) time.Duration {
half := backoff / 2
if half <= 0 {
return backoff
}
return half + time.Duration(dice.Roll(int(half)))
}
func rateLimited(payload []byte) bool {
var parsed struct {
Error struct {
Status string `json:"status"`
Errors []struct {
Reason string `json:"reason"`
} `json:"errors"`
} `json:"error"`
}
if json.Unmarshal(payload, &parsed) != nil {
return false
}
for _, item := range parsed.Error.Errors {
switch item.Reason {
case "rateLimitExceeded", "userRateLimitExceeded", "sharingRateLimitExceeded":
return true
}
}
return parsed.Error.Status == "RESOURCE_EXHAUSTED"
}
func retryableStatus(status int) bool {
switch status {
case http.StatusTooManyRequests, http.StatusInternalServerError,
http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
return true
}
return false
}
func (s *driveStorage) do(ctx context.Context, method, target, contentType string, body []byte) (int, []byte, error) {
backoff := s.backoff
var lastErr error
for attempt := 0; attempt < driveMaxAttempts; attempt++ {
if attempt > 0 {
select {
case <-ctx.Done():
return 0, nil, ctx.Err()
case <-time.After(jitter(backoff)):
}
backoff *= 2
if backoff > driveMaxBackoff {
backoff = driveMaxBackoff
}
}
token, err := s.accessToken(ctx)
if err != nil {
lastErr = err
continue
}
select {
case s.inflight <- struct{}{}:
case <-ctx.Done():
return 0, nil, ctx.Err()
}
var reader io.Reader
if body != nil {
reader = bytes.NewReader(body)
}
req, err := http.NewRequestWithContext(ctx, method, target, reader)
if err != nil {
return 0, nil, errors.New("failed to build a Drive request").Base(err)
}
req.Header.Set("Authorization", "Bearer "+token)
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
resp, err := s.client.Do(req)
<-s.inflight
if err != nil {
if ctx.Err() != nil {
return 0, nil, ctx.Err()
}
lastErr = errors.New("Drive request failed").Base(err)
errors.LogWarningInner(ctx, err, "retrying a failed Drive request, attempt ",
attempt+1, " of ", driveMaxAttempts)
continue
}
payload, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
if ctx.Err() != nil {
return 0, nil, ctx.Err()
}
lastErr = errors.New("failed to read the Drive response").Base(err)
continue
}
if resp.StatusCode == http.StatusUnauthorized {
s.invalidateToken()
lastErr = errors.New("Drive rejected the access token")
continue
}
if resp.StatusCode == http.StatusForbidden && rateLimited(payload) {
lastErr = errors.New("Drive is rate limiting: ", string(payload))
errors.LogWarning(ctx, "rate limited by Drive, attempt ",
attempt+1, " of ", driveMaxAttempts)
continue
}
if retryableStatus(resp.StatusCode) {
lastErr = errors.New("Drive answered ", resp.StatusCode, ": ", string(payload))
errors.LogWarning(ctx, "retrying after Drive answered ", resp.StatusCode,
", attempt ", attempt+1, " of ", driveMaxAttempts)
continue
}
return resp.StatusCode, payload, nil
}
return 0, nil, lastErr
}
func (s *driveStorage) invalidateToken() {
s.tokenMu.Lock()
s.token = ""
s.tokenMu.Unlock()
}
func (s *driveStorage) rememberID(name, id string) {
s.idMu.Lock()
s.ids[name] = id
s.idMu.Unlock()
}
func (s *driveStorage) forgetID(name string) {
s.idMu.Lock()
delete(s.ids, name)
s.idMu.Unlock()
}
func (s *driveStorage) cachedID(name string) (string, bool) {
s.idMu.Lock()
defer s.idMu.Unlock()
id, ok := s.ids[name]
return id, ok
}
type driveFile struct {
id string
description string
}
func (s *driveStorage) query(ctx context.Context, condition string) (map[string]driveFile, error) {
found := make(map[string]driveFile)
pageToken := ""
for {
params := url.Values{
"q": {"'" + quoteDriveValue(s.folder) + "' in parents and trashed = false and " + condition},
"fields": {"nextPageToken,files(id,name,description)"},
"pageSize": {fmt.Sprint(drivePageSize)},
"supportsAllDrives": {"true"},
"includeItemsFromAllDrives": {"true"},
}
if pageToken != "" {
params.Set("pageToken", pageToken)
}
status, payload, err := s.do(ctx, http.MethodGet, s.filesURL+"?"+params.Encode(), "", nil)
if err != nil {
return nil, err
}
if status != http.StatusOK {
return nil, errors.New("Drive listing answered ", status, ": ", string(payload))
}
var parsed struct {
NextPageToken string `json:"nextPageToken"`
Files []struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
} `json:"files"`
}
if err := json.Unmarshal(payload, &parsed); err != nil {
return nil, errors.New("failed to parse the Drive listing").Base(err)
}
for _, file := range parsed.Files {
found[file.Name] = driveFile{id: file.ID, description: file.Description}
s.rememberID(file.Name, file.ID)
}
pageToken = parsed.NextPageToken
if pageToken == "" {
return found, nil
}
}
}
func (s *driveStorage) resolveID(ctx context.Context, flat string) (string, error) {
if id, ok := s.cachedID(flat); ok {
return id, nil
}
found, err := s.query(ctx, "name = '"+quoteDriveValue(flat)+"'")
if err != nil {
return "", err
}
if file, ok := found[flat]; ok {
return file.id, nil
}
return "", errNotFound
}
func (s *driveStorage) Put(ctx context.Context, name string, data []byte) error {
if len(data) <= driveInlineLimit {
return s.putInline(ctx, name, data)
}
return s.putMedia(ctx, name, data)
}
func (s *driveStorage) putInline(ctx context.Context, name string, data []byte) error {
flat := flatten(name)
body, err := json.Marshal(map[string]interface{}{
"name": flat,
"parents": []string{s.folder},
"description": base64.StdEncoding.EncodeToString(data),
})
if err != nil {
return errors.New("failed to build the inline metadata").Base(err)
}
status, payload, err := s.do(ctx, http.MethodPost, s.filesURL+"?fields=id",
"application/json; charset=UTF-8", body)
if err != nil {
return err
}
if status != http.StatusOK {
return errors.New("Drive rejected the inline upload of ", name,
" with ", status, ": ", string(payload))
}
var parsed struct {
ID string `json:"id"`
}
if err := json.Unmarshal(payload, &parsed); err != nil {
return errors.New("failed to parse the Drive upload response").Base(err)
}
if parsed.ID != "" {
s.rememberID(flat, parsed.ID)
}
return nil
}
func (s *driveStorage) putMedia(ctx context.Context, name string, data []byte) error {
flat := flatten(name)
metadata, err := json.Marshal(map[string]interface{}{
"name": flat,
"parents": []string{s.folder},
})
if err != nil {
return errors.New("failed to build the upload metadata").Base(err)
}
var body bytes.Buffer
fmt.Fprintf(&body, "--%s\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n", driveBoundary)
body.Write(metadata)
fmt.Fprintf(&body, "\r\n--%s\r\nContent-Type: application/octet-stream\r\n\r\n", driveBoundary)
body.Write(data)
fmt.Fprintf(&body, "\r\n--%s--\r\n", driveBoundary)
status, payload, err := s.do(ctx, http.MethodPost, s.uploadURL,
"multipart/related; boundary="+driveBoundary, body.Bytes())
if err != nil {
return err
}
if status != http.StatusOK {
return errors.New("Drive upload of ", name, " answered ", status, ": ", string(payload))
}
var parsed struct {
ID string `json:"id"`
}
if err := json.Unmarshal(payload, &parsed); err != nil {
return errors.New("failed to parse the Drive upload response").Base(err)
}
if parsed.ID != "" {
s.rememberID(flat, parsed.ID)
}
return nil
}
func (s *driveStorage) Get(ctx context.Context, name string) ([]byte, error) {
flat := flatten(name)
id, err := s.resolveID(ctx, flat)
if err != nil {
return nil, err
}
status, payload, err := s.do(ctx, http.MethodGet,
s.filesURL+"/"+url.PathEscape(id)+"?alt=media&supportsAllDrives=true", "", nil)
if err != nil {
return nil, err
}
switch status {
case http.StatusOK:
if len(payload) > 0 {
return payload, nil
}
return s.getInline(ctx, id)
case http.StatusNotFound:
s.forgetID(flat)
return nil, errNotFound
default:
return nil, errors.New("Drive download of ", name, " answered ", status, ": ", string(payload))
}
}
func (s *driveStorage) getInline(ctx context.Context, id string) ([]byte, error) {
status, payload, err := s.do(ctx, http.MethodGet,
s.filesURL+"/"+url.PathEscape(id)+"?fields=description&supportsAllDrives=true", "", nil)
if err != nil {
return nil, err
}
if status != http.StatusOK {
return nil, errors.New("Drive answered ", status, " for inline data: ", string(payload))
}
var parsed struct {
Description string `json:"description"`
}
if err := json.Unmarshal(payload, &parsed); err != nil {
return nil, errors.New("failed to parse the inline data").Base(err)
}
if parsed.Description == "" {
return nil, nil
}
data, err := base64.StdEncoding.DecodeString(parsed.Description)
if err != nil {
return nil, errors.New("the inline data is not valid base64").Base(err)
}
return data, nil
}
func (s *driveStorage) deleteID(ctx context.Context, flat, id string) error {
status, payload, err := s.do(ctx, http.MethodDelete,
s.filesURL+"/"+url.PathEscape(id)+"?supportsAllDrives=true", "", nil)
if err != nil {
return err
}
s.forgetID(flat)
switch status {
case http.StatusOK, http.StatusNoContent, http.StatusNotFound:
return nil
default:
return errors.New("Drive deletion of ", flat, " answered ", status, ": ", string(payload))
}
}
func (s *driveStorage) Delete(ctx context.Context, name string) error {
flat := flatten(name)
if id, err := s.resolveID(ctx, flat); err == nil {
if err := s.deleteID(ctx, flat, id); err != nil {
return err
}
} else if err != errNotFound {
return err
}
children, err := s.query(ctx, "name contains '"+quoteDriveValue(flat+flatSeparator)+"'")
if err != nil {
return err
}
for childName, file := range children {
if err := s.deleteID(ctx, childName, file.id); err != nil {
return err
}
}
return nil
}
func (s *driveStorage) List(ctx context.Context, prefix string) ([]Entry, error) {
flat := flatten(prefix) + flatSeparator
found, err := s.query(ctx, "name contains '"+quoteDriveValue(flat)+"'")
if err != nil {
return nil, err
}
seen := make(map[string]bool, len(found))
entries := make([]Entry, 0, len(found))
for name, file := range found {
rest := strings.TrimPrefix(name, flat)
if rest == "" {
continue
}
direct := true
if cut := strings.Index(rest, flatSeparator); cut >= 0 {
rest = rest[:cut]
direct = false
}
if seen[rest] {
continue
}
seen[rest] = true
entry := Entry{Name: rest}
if direct && file.description != "" {
if data, err := base64.StdEncoding.DecodeString(file.description); err == nil {
entry.Inline = data
}
}
entries = append(entries, entry)
}
return entries, nil
}
func (s *driveStorage) Close() error {
return nil
}
@@ -1,345 +0,0 @@
package xdrive
import (
"bytes"
"context"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"sync"
"testing"
"time"
"github.com/xtls/xray-core/transport/internet"
)
const liveSecretsEnv = "XRAY_XDRIVE_DRIVE_SECRETS"
func liveDriveConfig(t *testing.T) *Config {
t.Helper()
path := os.Getenv(liveSecretsEnv)
if path == "" {
t.Skipf("set %s to run this test", liveSecretsEnv)
}
payload, err := os.ReadFile(path)
if err != nil {
t.Fatalf("reading %s: %v", path, err)
}
var secrets struct {
Folder string `json:"folder"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RefreshToken string `json:"refresh_token"`
}
if err := json.Unmarshal(payload, &secrets); err != nil {
t.Fatalf("parsing %s: %v", path, err)
}
config := &Config{
RemoteFolder: secrets.Folder,
Service: "Google Drive",
Secrets: []string{secrets.ClientID, secrets.ClientSecret, secrets.RefreshToken},
SegmentBytes: 256 * 1024,
FlushIntervalMs: 100,
PollIntervalMs: 500,
MaxPollIntervalMs: 2000,
SessionTtlSeconds: 120,
}
if raw := os.Getenv("XRAY_XDRIVE_LIVE_SEGMENT"); raw != "" {
config.SegmentBytes = uint32(envInt(t, "XRAY_XDRIVE_LIVE_SEGMENT"))
}
if raw := os.Getenv("XRAY_XDRIVE_LIVE_CONCURRENCY"); raw != "" {
config.Concurrency = uint32(envInt(t, "XRAY_XDRIVE_LIVE_CONCURRENCY"))
}
storage, err := newDriveStorage(nil, config)
if err != nil {
t.Fatalf("newDriveStorage: %v", err)
}
defer storage.Close()
for _, dir := range []string{sessionsDir, streamsDir} {
if err := storage.Delete(context.Background(), dir); err != nil {
t.Fatalf("clearing %s: %v", dir, err)
}
}
return config
}
func envInt(t *testing.T, name string) int {
t.Helper()
parsed, err := strconv.Atoi(os.Getenv(name))
if err != nil {
t.Fatalf("%s: %v", name, err)
}
return parsed
}
func TestLiveDriveStorage(t *testing.T) {
config := liveDriveConfig(t)
storage, err := newDriveStorage(nil, config)
if err != nil {
t.Fatalf("newDriveStorage: %v", err)
}
defer storage.Close()
ctx := context.Background()
name := "streams/livetest/c2s/000000000.seg"
payload := []byte("xdrive over a real remote storage service")
defer storage.Delete(ctx, "streams/livetest")
start := time.Now()
if err := storage.Put(ctx, name, payload); err != nil {
t.Fatalf("Put: %v", err)
}
t.Logf("Put took %v", time.Since(start))
start = time.Now()
names, err := storage.List(ctx, "streams/livetest/c2s")
if err != nil {
t.Fatalf("List: %v", err)
}
t.Logf("List took %v", time.Since(start))
if len(names) != 1 || names[0].Name != "000000000.seg" {
t.Fatalf("List returned %v, want one segment", names)
}
start = time.Now()
got, err := storage.Get(ctx, name)
if err != nil {
t.Fatalf("Get: %v", err)
}
t.Logf("Get took %v", time.Since(start))
if !bytes.Equal(got, payload) {
t.Fatalf("Get returned %q, want %q", got, payload)
}
if _, err := storage.Get(ctx, "streams/livetest/c2s/000000009.seg"); err != errNotFound {
t.Fatalf("Get returned %v, want errNotFound", err)
}
if err := storage.Delete(ctx, "streams/livetest"); err != nil {
t.Fatalf("Delete: %v", err)
}
names, err = storage.List(ctx, "streams/livetest/c2s")
if err != nil {
t.Fatalf("List after delete: %v", err)
}
if len(names) != 0 {
t.Fatalf("List after delete returned %v", names)
}
}
func TestLiveDriveTransport(t *testing.T) {
config := liveDriveConfig(t)
streamSettings := &internet.MemoryStreamConfig{
ProtocolName: protocolName,
ProtocolSettings: config,
}
client, server, cleanup := pairWith(t, streamSettings)
defer cleanup()
start := time.Now()
if _, err := client.Write([]byte("ping")); err != nil {
t.Fatalf("client write: %v", err)
}
expectRead(t, server, "ping")
t.Logf("client to server round took %v", time.Since(start))
start = time.Now()
if _, err := server.Write([]byte("pong")); err != nil {
t.Fatalf("server write: %v", err)
}
expectRead(t, client, "pong")
t.Logf("server to client round took %v", time.Since(start))
size := 400000
if raw := os.Getenv("XRAY_XDRIVE_LIVE_BYTES"); raw != "" {
parsed, err := strconv.Atoi(raw)
if err != nil {
t.Fatalf("XRAY_XDRIVE_LIVE_BYTES: %v", err)
}
size = parsed
}
payload := make([]byte, size)
if _, err := rand.Read(payload); err != nil {
t.Fatalf("rand: %v", err)
}
start = time.Now()
go func() {
client.Write(payload)
}()
if err := server.SetReadDeadline(time.Now().Add(5 * time.Minute)); err != nil {
t.Fatalf("SetReadDeadline: %v", err)
}
got := make([]byte, len(payload))
if _, err := io.ReadFull(server, got); err != nil {
t.Fatalf("ReadFull: %v", err)
}
elapsed := time.Since(start)
if !bytes.Equal(got, payload) {
t.Fatal("payload mismatch")
}
t.Logf("%d bytes took %v (%.1f KiB/s)", len(payload), elapsed,
float64(len(payload))/1024/elapsed.Seconds())
}
func TestLiveDriveParallelPut(t *testing.T) {
config := liveDriveConfig(t)
storage, err := newDriveStorage(nil, config)
if err != nil {
t.Fatalf("newDriveStorage: %v", err)
}
defer storage.Close()
ctx := context.Background()
defer storage.Delete(ctx, "streams/benchtest")
chunk := make([]byte, 256*1024)
if _, err := rand.Read(chunk); err != nil {
t.Fatalf("rand: %v", err)
}
if err := storage.Put(ctx, "streams/benchtest/warmup", chunk); err != nil {
t.Fatalf("warmup: %v", err)
}
start := time.Now()
for i := 0; i < 4; i++ {
if err := storage.Put(ctx, fmt.Sprintf("streams/benchtest/seq%d", i), chunk); err != nil {
t.Fatalf("sequential put: %v", err)
}
}
sequential := time.Since(start)
t.Logf("4 sequential puts of 256 KiB: %v (%.1f KiB/s)",
sequential, float64(4*len(chunk))/1024/sequential.Seconds())
start = time.Now()
var wg sync.WaitGroup
failures := make([]error, 8)
for i := 0; i < 8; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
failures[i] = storage.Put(ctx, fmt.Sprintf("streams/benchtest/par%d", i), chunk)
}(i)
}
wg.Wait()
parallel := time.Since(start)
for _, err := range failures {
if err != nil {
t.Fatalf("parallel put: %v", err)
}
}
t.Logf("8 parallel puts of 256 KiB: %v (%.1f KiB/s)",
parallel, float64(8*len(chunk))/1024/parallel.Seconds())
start = time.Now()
names, err := storage.List(ctx, "streams/benchtest")
if err != nil {
t.Fatalf("List: %v", err)
}
t.Logf("List of %d objects took %v", len(names), time.Since(start))
start = time.Now()
wg = sync.WaitGroup{}
for i := 0; i < 8; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
storage.Get(ctx, fmt.Sprintf("streams/benchtest/par%d", i))
}(i)
}
wg.Wait()
download := time.Since(start)
t.Logf("8 parallel gets of 256 KiB: %v (%.1f KiB/s)",
download, float64(8*len(chunk))/1024/download.Seconds())
}
func TestLiveDriveSegmentSweep(t *testing.T) {
config := liveDriveConfig(t)
storage, err := newDriveStorage(nil, config)
if err != nil {
t.Fatalf("newDriveStorage: %v", err)
}
defer storage.Close()
ctx := context.Background()
defer storage.Delete(ctx, "streams/sweeptest")
const total = 1024 * 1024
for _, size := range []int{64 * 1024, 128 * 1024, 256 * 1024, 512 * 1024} {
chunk := make([]byte, size)
if _, err := rand.Read(chunk); err != nil {
t.Fatalf("rand: %v", err)
}
count := total / size
start := time.Now()
var wg sync.WaitGroup
for i := 0; i < count; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
storage.Put(ctx, fmt.Sprintf("streams/sweeptest/s%d-%d", size, i), chunk)
}(i)
}
wg.Wait()
elapsed := time.Since(start)
t.Logf("%4d KiB x %2d = 1 MiB in %8v -> %6.1f KiB/s",
size/1024, count, elapsed.Round(time.Millisecond),
float64(total)/1024/elapsed.Seconds())
}
}
func TestLiveDriveListLag(t *testing.T) {
config := liveDriveConfig(t)
storage, err := newDriveStorage(nil, config)
if err != nil {
t.Fatalf("newDriveStorage: %v", err)
}
defer storage.Close()
ctx := context.Background()
defer storage.Delete(ctx, "streams/lagtest")
const rounds = 6
var worst time.Duration
for i := 0; i < rounds; i++ {
name := fmt.Sprintf("streams/lagtest/round%d/000000000.seg", i)
if err := storage.Put(ctx, name, []byte("probe")); err != nil {
t.Fatalf("Put: %v", err)
}
start := time.Now()
var lag time.Duration
for {
names, err := storage.List(ctx, fmt.Sprintf("streams/lagtest/round%d", i))
if err != nil {
t.Fatalf("List: %v", err)
}
if len(names) == 1 {
lag = time.Since(start)
break
}
if time.Since(start) > 30*time.Second {
t.Fatalf("round %d: the object never showed up in a listing", i)
}
}
if lag > worst {
worst = lag
}
t.Logf("round %d: the object became listable after %v", i, lag.Round(time.Millisecond))
}
t.Logf("worst listing lag: %v", worst.Round(time.Millisecond))
}
-690
View File
@@ -1,690 +0,0 @@
package xdrive
import (
"context"
"encoding/json"
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"sync"
"testing"
"time"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
)
type fakeFile struct {
id string
name string
data []byte
description string
}
type fakeDrive struct {
server *httptest.Server
mu sync.Mutex
files map[string]*fakeFile
nextID int
failOnce map[string]bool
failStatus map[string]int
failBody map[string]string
tokens int
hosts map[string]bool
}
func newFakeDrive(t *testing.T) *fakeDrive {
t.Helper()
drive := &fakeDrive{
files: make(map[string]*fakeFile),
failOnce: make(map[string]bool),
failStatus: make(map[string]int),
failBody: make(map[string]string),
hosts: make(map[string]bool),
}
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}))
record := func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
drive.mu.Lock()
drive.hosts[r.Host] = true
drive.mu.Unlock()
next(w, r)
}
}
mux.HandleFunc("/token", record(drive.handleToken))
mux.HandleFunc("/upload", record(drive.handleUpload))
mux.HandleFunc("/files", record(drive.handleFiles))
mux.HandleFunc("/files/", record(drive.handleFile))
drive.server = httptest.NewServer(mux)
resetSharedStorage()
previous := []string{driveTokenURL, driveFilesURL, driveUploadURL}
previousBackoff := driveInitialBackoff
driveTokenURL = drive.server.URL + "/token"
driveFilesURL = drive.server.URL + "/files"
driveUploadURL = drive.server.URL + "/upload"
driveInitialBackoff = 5 * time.Millisecond
t.Cleanup(func() {
driveTokenURL, driveFilesURL, driveUploadURL = previous[0], previous[1], previous[2]
driveInitialBackoff = previousBackoff
drive.server.Close()
resetSharedStorage()
})
return drive
}
func (d *fakeDrive) handleToken(w http.ResponseWriter, r *http.Request) {
d.mu.Lock()
d.tokens++
d.mu.Unlock()
json.NewEncoder(w).Encode(map[string]interface{}{
"access_token": "fake-token",
"expires_in": 3600,
})
}
func (d *fakeDrive) handleUpload(w http.ResponseWriter, r *http.Request) {
if d.shouldFail("upload") {
if status, body := d.failure("upload"); status != 0 {
w.WriteHeader(status)
w.Write([]byte(body))
return
}
w.WriteHeader(http.StatusTooManyRequests)
return
}
_, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
reader := multipart.NewReader(r.Body, params["boundary"])
metaPart, err := reader.NextPart()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var metadata struct {
Name string `json:"name"`
}
if err := json.NewDecoder(metaPart).Decode(&metadata); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var data []byte
if dataPart, err := reader.NextPart(); err == nil {
data, _ = io.ReadAll(dataPart)
}
d.mu.Lock()
d.nextID++
id := fmt.Sprintf("id-%d", d.nextID)
d.files[id] = &fakeFile{id: id, name: metadata.Name, data: data}
d.mu.Unlock()
json.NewEncoder(w).Encode(map[string]string{"id": id, "name": metadata.Name})
}
func (d *fakeDrive) handleFiles(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
d.handleCreate(w, r)
return
}
d.handleList(w, r)
}
func (d *fakeDrive) handleCreate(w http.ResponseWriter, r *http.Request) {
if d.shouldFail("upload") {
if status, body := d.failure("upload"); status != 0 {
w.WriteHeader(status)
w.Write([]byte(body))
return
}
w.WriteHeader(http.StatusTooManyRequests)
return
}
var meta struct {
Name string `json:"name"`
Description string `json:"description"`
}
if err := json.NewDecoder(r.Body).Decode(&meta); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
d.mu.Lock()
d.nextID++
id := fmt.Sprintf("id-%d", d.nextID)
d.files[id] = &fakeFile{id: id, name: meta.Name, description: meta.Description}
d.mu.Unlock()
json.NewEncoder(w).Encode(map[string]string{"id": id, "name": meta.Name})
}
func (d *fakeDrive) handleList(w http.ResponseWriter, r *http.Request) {
if d.shouldFail("list") {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
query := r.URL.Query().Get("q")
exact, prefix := parseFakeQuery(query)
type entry struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
}
result := struct {
Files []entry `json:"files"`
}{}
d.mu.Lock()
for _, file := range d.files {
match := false
switch {
case exact != "":
match = file.name == exact
case prefix != "":
match = strings.HasPrefix(file.name, prefix)
}
if match {
result.Files = append(result.Files, entry{
ID: file.id, Name: file.name, Description: file.description,
})
}
}
d.mu.Unlock()
json.NewEncoder(w).Encode(result)
}
func (d *fakeDrive) handleFile(w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/files/")
d.mu.Lock()
file, ok := d.files[id]
if ok && r.Method == http.MethodDelete {
delete(d.files, id)
}
d.mu.Unlock()
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
if r.Method == http.MethodDelete {
w.WriteHeader(http.StatusNoContent)
return
}
if strings.Contains(r.URL.RawQuery, "fields=description") {
json.NewEncoder(w).Encode(map[string]string{"description": file.description})
return
}
w.Write(file.data)
}
func (d *fakeDrive) shouldFail(kind string) bool {
d.mu.Lock()
defer d.mu.Unlock()
if d.failOnce[kind] {
d.failOnce[kind] = false
return true
}
return false
}
func (d *fakeDrive) failOnceWith(kind string, status int, body string) {
d.mu.Lock()
d.failOnce[kind] = true
d.failStatus[kind] = status
d.failBody[kind] = body
d.mu.Unlock()
}
func (d *fakeDrive) failure(kind string) (int, string) {
d.mu.Lock()
defer d.mu.Unlock()
if status, ok := d.failStatus[kind]; ok {
return status, d.failBody[kind]
}
return 0, ""
}
func (d *fakeDrive) failNext(kind string) {
d.mu.Lock()
d.failOnce[kind] = true
d.mu.Unlock()
}
func (d *fakeDrive) seenHosts() []string {
d.mu.Lock()
defer d.mu.Unlock()
out := make([]string, 0, len(d.hosts))
for h := range d.hosts {
out = append(out, h)
}
return out
}
func (d *fakeDrive) count() int {
d.mu.Lock()
defer d.mu.Unlock()
return len(d.files)
}
func parseFakeQuery(query string) (exact, prefix string) {
if value, ok := cutQuoted(query, "name = '"); ok {
return value, ""
}
if value, ok := cutQuoted(query, "name contains '"); ok {
return "", value
}
return "", ""
}
func cutQuoted(query, marker string) (string, bool) {
start := strings.Index(query, marker)
if start < 0 {
return "", false
}
rest := query[start+len(marker):]
end := strings.Index(rest, "'")
if end < 0 {
return "", false
}
return rest[:end], true
}
func driveSettings() *internet.MemoryStreamConfig {
return &internet.MemoryStreamConfig{
ProtocolName: protocolName,
ProtocolSettings: &Config{
RemoteFolder: "folder-id",
Service: "Google Drive",
Secrets: []string{"client", "secret", "refresh"},
FlushIntervalMs: 5,
PollIntervalMs: 5,
MaxPollIntervalMs: 20,
SessionTtlSeconds: 1,
},
}
}
func newDriveBackend(t *testing.T) *driveStorage {
t.Helper()
storage, err := newDriveStorage(driveSettings(), driveSettings().ProtocolSettings.(*Config))
if err != nil {
t.Fatalf("newDriveStorage: %v", err)
}
return storage
}
func TestDriveSecrets(t *testing.T) {
if _, err := newDriveStorage(nil, &Config{RemoteFolder: "f", Secrets: []string{"a", "b"}}); err == nil {
t.Fatal("accepted two secrets")
}
if _, err := newDriveStorage(nil, &Config{Secrets: []string{"a", "b", "c"}}); err == nil {
t.Fatal("accepted an empty remoteFolder")
}
if _, err := newDriveStorage(nil, &Config{RemoteFolder: "f", Secrets: []string{"a", "", "c"}}); err == nil {
t.Fatal("accepted an empty secret")
}
}
func TestDriveRoundTrip(t *testing.T) {
drive := newFakeDrive(t)
storage := newDriveBackend(t)
ctx := context.Background()
if err := storage.Put(ctx, "streams/abc/c2s/000000000.seg", []byte("hello")); err != nil {
t.Fatalf("Put: %v", err)
}
data, err := storage.Get(ctx, "streams/abc/c2s/000000000.seg")
if err != nil {
t.Fatalf("Get: %v", err)
}
if string(data) != "hello" {
t.Fatalf("Get returned %q, want %q", data, "hello")
}
names, err := storage.List(ctx, "streams/abc/c2s")
if err != nil {
t.Fatalf("List: %v", err)
}
if len(names) != 1 || names[0].Name != "000000000.seg" {
t.Fatalf("List returned %v, want 1 segment", names)
}
if _, err := storage.Get(ctx, "streams/abc/c2s/000000009.seg"); err != errNotFound {
t.Fatalf("Get returned %v, want errNotFound", err)
}
if err := storage.Delete(ctx, "streams/abc/c2s/000000000.seg"); err != nil {
t.Fatalf("Delete: %v", err)
}
if drive.count() != 0 {
t.Fatalf("fake drive still holds %d files", drive.count())
}
}
func TestDriveListChildren(t *testing.T) {
newFakeDrive(t)
storage := newDriveBackend(t)
ctx := context.Background()
for _, name := range []string{
"streams/one/c2s/000000000.seg",
"streams/one/s2c/000000000.seg",
"streams/two/c2s/000000000.seg",
} {
if err := storage.Put(ctx, name, []byte("x")); err != nil {
t.Fatalf("Put %s: %v", name, err)
}
}
names, err := storage.List(ctx, "streams")
if err != nil {
t.Fatalf("List: %v", err)
}
if len(names) != 2 {
t.Fatalf("List returned %v, want 2 sessions", names)
}
}
func TestDriveDeleteSession(t *testing.T) {
drive := newFakeDrive(t)
storage := newDriveBackend(t)
ctx := context.Background()
for _, name := range []string{
"streams/one/c2s/000000000.seg",
"streams/one/c2s/000000001.end",
"streams/one/s2c/000000000.seg",
"streams/two/c2s/000000000.seg",
} {
if err := storage.Put(ctx, name, []byte("x")); err != nil {
t.Fatalf("Put %s: %v", name, err)
}
}
if err := storage.Delete(ctx, "streams/one"); err != nil {
t.Fatalf("Delete: %v", err)
}
if drive.count() != 1 {
t.Fatalf("fake drive holds %d files, want 1", drive.count())
}
}
func TestDriveRetry(t *testing.T) {
drive := newFakeDrive(t)
storage := newDriveBackend(t)
ctx := context.Background()
drive.failNext("upload")
if err := storage.Put(ctx, "sessions/abc", nil); err != nil {
t.Fatalf("Put did not survive a 429: %v", err)
}
drive.failNext("list")
if _, err := storage.List(ctx, "sessions"); err != nil {
t.Fatalf("List did not survive a 503: %v", err)
}
}
func TestDriveTokenCache(t *testing.T) {
drive := newFakeDrive(t)
storage := newDriveBackend(t)
ctx := context.Background()
for i := 0; i < 5; i++ {
if err := storage.Put(ctx, fmt.Sprintf("sessions/s%d", i), nil); err != nil {
t.Fatalf("Put: %v", err)
}
}
if drive.tokens != 1 {
t.Fatalf("token endpoint hit %d times, want 1", drive.tokens)
}
}
func TestDriveTransport(t *testing.T) {
newFakeDrive(t)
client, server, cleanup := pairWith(t, driveSettings())
defer cleanup()
if _, err := client.Write([]byte("ping")); err != nil {
t.Fatalf("client write: %v", err)
}
expectRead(t, server, "ping")
if _, err := server.Write([]byte("pong")); err != nil {
t.Fatalf("server write: %v", err)
}
expectRead(t, client, "pong")
}
func TestDriveLargeTransfer(t *testing.T) {
newFakeDrive(t)
client, server, cleanup := pairWith(t, driveSettings())
defer cleanup()
payload := make([]byte, 300000)
for i := range payload {
payload[i] = byte(i % 251)
}
go func() {
client.Write(payload)
}()
if err := server.SetReadDeadline(time.Now().Add(60 * time.Second)); err != nil {
t.Fatalf("SetReadDeadline: %v", err)
}
got := make([]byte, len(payload))
if _, err := io.ReadFull(server, got); err != nil {
t.Fatalf("ReadFull: %v", err)
}
for i := range got {
if got[i] != payload[i] {
t.Fatalf("payload mismatch at byte %d", i)
}
}
}
func TestRateLimited(t *testing.T) {
limited := []string{
`{"error":{"code":403,"errors":[{"reason":"userRateLimitExceeded"}]}}`,
`{"error":{"code":403,"errors":[{"reason":"rateLimitExceeded"}]}}`,
`{"error":{"code":403,"errors":[{"reason":"sharingRateLimitExceeded"}]}}`,
`{"error":{"status":"RESOURCE_EXHAUSTED"}}`,
}
for _, payload := range limited {
if !rateLimited([]byte(payload)) {
t.Fatalf("rateLimited missed %s", payload)
}
}
permanent := []string{
`{"error":{"code":403,"errors":[{"reason":"insufficientFilePermissions"}]}}`,
`{"error":{"code":403,"errors":[{"reason":"storageQuotaExceeded"}]}}`,
`not json at all`,
}
for _, payload := range permanent {
if rateLimited([]byte(payload)) {
t.Fatalf("rateLimited treated %s as temporary", payload)
}
}
}
func TestDriveRetryRateLimit(t *testing.T) {
drive := newFakeDrive(t)
storage := newDriveBackend(t)
drive.failOnceWith("upload", http.StatusForbidden,
`{"error":{"code":403,"errors":[{"reason":"userRateLimitExceeded"}]}}`)
if err := storage.Put(context.Background(), "sessions/abc", nil); err != nil {
t.Fatalf("Put did not survive a 403: %v", err)
}
}
func TestDriveSharedClient(t *testing.T) {
newFakeDrive(t)
first, err := newStorage(driveSettings())
if err != nil {
t.Fatalf("newStorage: %v", err)
}
second, err := newStorage(driveSettings())
if err != nil {
t.Fatalf("newStorage: %v", err)
}
if first != second {
t.Fatal("same settings did not share one storage")
}
}
func TestDriveInlineListing(t *testing.T) {
drive := newFakeDrive(t)
storage := newDriveBackend(t)
ctx := context.Background()
payload := []byte("small enough to ride along with the listing")
if err := storage.Put(ctx, "streams/abc/c2s/000000000.seg", payload); err != nil {
t.Fatalf("Put: %v", err)
}
entries, err := storage.List(ctx, "streams/abc/c2s")
if err != nil {
t.Fatalf("List: %v", err)
}
if len(entries) != 1 {
t.Fatalf("List returned %v, want 1 entry", entries)
}
if string(entries[0].Inline) != string(payload) {
t.Fatalf("listing carried %q, want %q", entries[0].Inline, payload)
}
d := drive
d.mu.Lock()
var stored *fakeFile
for _, f := range d.files {
stored = f
}
d.mu.Unlock()
if len(stored.data) != 0 {
t.Fatal("small payload was uploaded as content")
}
}
func TestDriveLargeContent(t *testing.T) {
drive := newFakeDrive(t)
storage := newDriveBackend(t)
ctx := context.Background()
payload := make([]byte, driveInlineLimit+1)
for i := range payload {
payload[i] = byte(i)
}
if err := storage.Put(ctx, "streams/abc/c2s/000000000.seg", payload); err != nil {
t.Fatalf("Put: %v", err)
}
entries, err := storage.List(ctx, "streams/abc/c2s")
if err != nil {
t.Fatalf("List: %v", err)
}
if len(entries) != 1 || entries[0].Inline != nil {
t.Fatalf("large payload was inlined, got %v", entries)
}
got, err := storage.Get(ctx, "streams/abc/c2s/000000000.seg")
if err != nil {
t.Fatalf("Get: %v", err)
}
if len(got) != len(payload) {
t.Fatalf("Get returned %d bytes, want %d", len(got), len(payload))
}
_ = drive
}
func TestDriveInlineGet(t *testing.T) {
newFakeDrive(t)
storage := newDriveBackend(t)
ctx := context.Background()
payload := []byte("only in the description")
if err := storage.Put(ctx, "sessions/abc", payload); err != nil {
t.Fatalf("Put: %v", err)
}
got, err := storage.Get(ctx, "sessions/abc")
if err != nil {
t.Fatalf("Get: %v", err)
}
if string(got) != string(payload) {
t.Fatalf("Get returned %q, want %q", got, payload)
}
}
func TestDriveFronting(t *testing.T) {
drive := newFakeDrive(t)
fake, err := url.Parse(drive.server.URL)
if err != nil {
t.Fatalf("parse: %v", err)
}
port, err := net.PortFromString(fake.Port())
if err != nil {
t.Fatalf("port: %v", err)
}
driveTokenURL = "http://www.googleapis.com/token"
driveFilesURL = "http://www.googleapis.com/files"
driveUploadURL = "http://www.googleapis.com/upload"
settings := driveSettings()
settings.Destination = &net.Destination{
Address: net.ParseAddress(fake.Hostname()),
Port: port,
Network: net.Network_TCP,
}
storage, err := newDriveStorage(settings, settings.ProtocolSettings.(*Config))
if err != nil {
t.Fatalf("newDriveStorage: %v", err)
}
if err := storage.Put(context.Background(), "sessions/fronted", []byte("x")); err != nil {
t.Fatalf("Put: %v", err)
}
if drive.count() != 1 {
t.Fatalf("fake drive holds %d files, want 1", drive.count())
}
for _, host := range drive.seenHosts() {
if host != "www.googleapis.com" {
t.Fatalf("inner host was %q, want www.googleapis.com regardless of address", host)
}
}
}
-39
View File
@@ -1,39 +0,0 @@
package xdrive
import (
"encoding/json"
"strings"
)
func jsonWalk(payload []byte, path string) interface{} {
var root interface{}
if json.Unmarshal(payload, &root) != nil {
return nil
}
node := root
for _, key := range strings.Split(path, ".") {
obj, ok := node.(map[string]interface{})
if !ok {
return nil
}
node, ok = obj[key]
if !ok {
return nil
}
}
return node
}
func jsonString(payload []byte, path string) string {
if s, ok := jsonWalk(payload, path).(string); ok {
return s
}
return ""
}
func jsonNumber(payload []byte, path string) int64 {
if f, ok := jsonWalk(payload, path).(float64); ok {
return int64(f)
}
return 0
}
-120
View File
@@ -1,120 +0,0 @@
package xdrive
import (
"context"
"os"
"path"
"path/filepath"
"strings"
"github.com/xtls/xray-core/common/errors"
)
const tempPrefix = ".xdrive-tmp-"
type localStorage struct {
root string
}
func newLocalStorage(root string) (*localStorage, error) {
if root == "" {
return nil, errors.New(`empty "remoteFolder"`)
}
if err := os.MkdirAll(root, 0o700); err != nil {
return nil, errors.New("failed to create remote folder").Base(err)
}
return &localStorage{root: root}, nil
}
func (s *localStorage) resolve(name string) (string, error) {
clean := path.Clean("/" + name)
if clean == "/" {
return "", errors.New("invalid object name: ", name)
}
if strings.HasPrefix(path.Base(clean), tempPrefix) {
return "", errors.New("reserved object name: ", name)
}
return filepath.Join(s.root, filepath.FromSlash(clean[1:])), nil
}
func (s *localStorage) Put(ctx context.Context, name string, data []byte) error {
full, err := s.resolve(name)
if err != nil {
return err
}
dir := filepath.Dir(full)
if err := os.MkdirAll(dir, 0o700); err != nil {
return errors.New("failed to create folder ", dir).Base(err)
}
tmp, err := os.CreateTemp(dir, tempPrefix+"*")
if err != nil {
return errors.New("failed to create temp file in ", dir).Base(err)
}
tmpName := tmp.Name()
defer os.Remove(tmpName)
if _, err := tmp.Write(data); err != nil {
tmp.Close()
return errors.New("failed to write ", name).Base(err)
}
if err := tmp.Close(); err != nil {
return errors.New("failed to close ", name).Base(err)
}
if err := os.Rename(tmpName, full); err != nil {
return errors.New("failed to commit ", name).Base(err)
}
return nil
}
func (s *localStorage) Get(ctx context.Context, name string) ([]byte, error) {
full, err := s.resolve(name)
if err != nil {
return nil, err
}
data, err := os.ReadFile(full)
if err != nil {
if os.IsNotExist(err) {
return nil, errNotFound
}
return nil, errors.New("failed to read ", name).Base(err)
}
return data, nil
}
func (s *localStorage) Delete(ctx context.Context, name string) error {
full, err := s.resolve(name)
if err != nil {
return err
}
if err := os.RemoveAll(full); err != nil {
return errors.New("failed to delete ", name).Base(err)
}
return nil
}
func (s *localStorage) List(ctx context.Context, prefix string) ([]Entry, error) {
full, err := s.resolve(prefix)
if err != nil {
return nil, err
}
entries, err := os.ReadDir(full)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, errors.New("failed to list ", prefix).Base(err)
}
found := make([]Entry, 0, len(entries))
for _, entry := range entries {
if strings.HasPrefix(entry.Name(), tempPrefix) {
continue
}
found = append(found, Entry{Name: entry.Name()})
}
return found, nil
}
func (s *localStorage) Close() error {
return nil
}
-69
View File
@@ -1,69 +0,0 @@
package xdrive
import "time"
const (
defaultSegmentBytes = 512 * 1024
defaultFlushInterval = 20 * time.Millisecond
defaultMinPollInterval = 50 * time.Millisecond
defaultMaxPollInterval = 500 * time.Millisecond
defaultEagerWindow = 2 * time.Second
defaultHoleTimeout = 30 * time.Second
defaultSessionTTL = 5 * time.Minute
defaultConcurrency = 8
maxSegmentBytes = 16 * 1024 * 1024
maxConcurrency = 64
)
type params struct {
segmentBytes int
flushInterval time.Duration
minPollInterval time.Duration
maxPollInterval time.Duration
eagerWindow time.Duration
holeTimeout time.Duration
sessionTTL time.Duration
concurrency int
}
func millis(value uint32, fallback time.Duration) time.Duration {
if value == 0 {
return fallback
}
return time.Duration(value) * time.Millisecond
}
func seconds(value uint32, fallback time.Duration) time.Duration {
if value == 0 {
return fallback
}
return time.Duration(value) * time.Second
}
func capped(value uint32, fallback, limit int) int {
if value == 0 {
return fallback
}
if int(value) > limit {
return limit
}
return int(value)
}
func paramsFromConfig(c *Config) params {
p := params{
segmentBytes: capped(c.SegmentBytes, defaultSegmentBytes, maxSegmentBytes),
flushInterval: millis(c.FlushIntervalMs, defaultFlushInterval),
minPollInterval: millis(c.PollIntervalMs, defaultMinPollInterval),
maxPollInterval: millis(c.MaxPollIntervalMs, defaultMaxPollInterval),
eagerWindow: millis(c.EagerWindowMs, defaultEagerWindow),
holeTimeout: millis(c.HoleTimeoutMs, defaultHoleTimeout),
sessionTTL: seconds(c.SessionTtlSeconds, defaultSessionTTL),
concurrency: capped(c.Concurrency, defaultConcurrency, maxConcurrency),
}
if p.maxPollInterval < p.minPollInterval {
p.maxPollInterval = p.minPollInterval
}
return p
}

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