mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-16 22:40:27 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c9282cc9c | ||
|
|
5ed59b3911 | ||
|
|
37ceb8b4b6 | ||
|
|
fd2ca74822 | ||
|
|
47cfe9994a | ||
|
|
3e2f040cd8 | ||
|
|
c7245c0336 | ||
|
|
eef6e63bc1 | ||
|
|
6ce8dc53e7 | ||
|
|
01a034be53 | ||
|
|
de2caf3cef | ||
|
|
cecc88f43c | ||
|
|
cd4ce973e9 | ||
|
|
fc7b980636 | ||
|
|
8ee131cbbb | ||
|
|
2776ea6d74 |
@@ -67,9 +67,7 @@ jobs:
|
||||
check-latest: true
|
||||
cache: false
|
||||
- name: Check Format
|
||||
run: |
|
||||
go install -v mvdan.cc/gofumpt@latest
|
||||
go run ./infra/vformat/main.go -mode check -pwd ./
|
||||
run: go run ./infra/vformat/main.go -mode check -pwd ./
|
||||
|
||||
test:
|
||||
needs: check-assets
|
||||
|
||||
+11
-22
@@ -330,7 +330,6 @@ 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"`
|
||||
@@ -382,13 +381,6 @@ 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
|
||||
@@ -506,14 +498,13 @@ 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\"\x9d\x03\n" +
|
||||
"\x0eOutboundConfig\"\xd6\x02\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\x12K\n" +
|
||||
"\x0eproxy_settings\x18\x03 \x01(\v2$.xray.transport.internet.ProxyConfigR\rproxySettings\x12T\n" +
|
||||
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\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\x0etargetStrategy\"\xa4\x01\n" +
|
||||
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategyJ\x04\b\x03\x10\x04\"\xa4\x01\n" +
|
||||
"\x12MultiplexingConfig\x12\x18\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
|
||||
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
|
||||
@@ -548,8 +539,7 @@ var file_app_proxyman_config_proto_goTypes = []any{
|
||||
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
||||
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
|
||||
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
|
||||
(*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig
|
||||
(internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy
|
||||
(internet.DomainStrategy)(0), // 13: 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
|
||||
@@ -562,14 +552,13 @@ var file_app_proxyman_config_proto_depIdxs = []int32{
|
||||
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func init() { file_app_proxyman_config_proto_init() }
|
||||
|
||||
@@ -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;
|
||||
xray.transport.internet.ProxyConfig proxy_settings = 3;
|
||||
reserved 3;
|
||||
MultiplexingConfig multiplex_settings = 4;
|
||||
string via_cidr = 5;
|
||||
xray.transport.internet.DomainStrategy target_strategy = 6;
|
||||
|
||||
@@ -15,7 +15,6 @@ 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"
|
||||
@@ -26,8 +25,6 @@ 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"
|
||||
)
|
||||
|
||||
@@ -63,7 +60,6 @@ type Handler struct {
|
||||
streamSettings *internet.MemoryStreamConfig
|
||||
proxyConfig proto.Message
|
||||
proxy proxy.Outbound
|
||||
outboundManager outbound.Manager
|
||||
mux *mux.ClientManager
|
||||
xudp *mux.ClientManager
|
||||
udp443 string
|
||||
@@ -77,7 +73,6 @@ 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,
|
||||
}
|
||||
@@ -108,9 +103,11 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
||||
|
||||
ctx = session.ContextWithFullHandler(ctx, h)
|
||||
|
||||
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
|
||||
if h.streamSettings != nil {
|
||||
ctx = session.ContextWithStreamSettings(ctx, h.streamSettings)
|
||||
}
|
||||
|
||||
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
|
||||
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -197,7 +194,6 @@ 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))])
|
||||
@@ -270,66 +266,26 @@ 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 {
|
||||
|
||||
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)
|
||||
}
|
||||
if h.senderSettings != nil && 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.senderSettings.ProxySettings.HasTag() && (h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
||||
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil &&
|
||||
(h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
||||
var domain string
|
||||
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() {
|
||||
@@ -344,11 +300,9 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
|
||||
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
||||
}
|
||||
}
|
||||
// case addr.Family().IsDomain():
|
||||
default:
|
||||
default: // case addr.Family().IsDomain():
|
||||
ob.Gateway = addr
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,9 @@ func (w *BufferedWriter) Write(b []byte) (int, error) {
|
||||
|
||||
nBytes, err := w.buffer.Write(b)
|
||||
totalBytes += nBytes
|
||||
if err != nil {
|
||||
|
||||
// ErrBufferFull means a partial write, so flush below and continue
|
||||
if err != nil && err != ErrBufferFull {
|
||||
return totalBytes, err
|
||||
}
|
||||
if !w.buffered || w.buffer.IsFull() {
|
||||
|
||||
@@ -70,8 +70,6 @@ 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
@@ -19,8 +19,8 @@ import (
|
||||
|
||||
var (
|
||||
Version_x byte = 26
|
||||
Version_y byte = 7
|
||||
Version_z byte = 28
|
||||
Version_y byte = 9
|
||||
Version_z byte = 8
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/xtls/xray-core
|
||||
|
||||
go 1.26
|
||||
go 1.27
|
||||
|
||||
require (
|
||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
|
||||
@@ -22,7 +22,7 @@ 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-20260322125925-9234c772ba8f
|
||||
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.55.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
@@ -32,11 +32,12 @@ require (
|
||||
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.1
|
||||
google.golang.org/grpc v1.83.2
|
||||
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 (
|
||||
@@ -48,7 +49,6 @@ 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
|
||||
@@ -59,6 +59,7 @@ require (
|
||||
go.yaml.in/yaml/v3 v3.0.5 // 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
|
||||
)
|
||||
|
||||
@@ -6,13 +6,14 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/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=
|
||||
@@ -73,8 +74,8 @@ github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
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/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=
|
||||
@@ -87,8 +88,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
|
||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
|
||||
github.com/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/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=
|
||||
@@ -147,6 +148,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
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=
|
||||
@@ -160,8 +163,8 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
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.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
|
||||
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||
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/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=
|
||||
@@ -176,3 +179,5 @@ h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
||||
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||
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=
|
||||
|
||||
+20
-30
@@ -1,52 +1,42 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/proxy/blackhole"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type NoneResponse struct{}
|
||||
|
||||
func (*NoneResponse) Build() (proto.Message, error) {
|
||||
return new(blackhole.NoneResponse), nil
|
||||
}
|
||||
|
||||
type HTTPResponse struct{}
|
||||
|
||||
func (*HTTPResponse) Build() (proto.Message, error) {
|
||||
return new(blackhole.HTTPResponse), nil
|
||||
type ResponseConfig struct {
|
||||
Type string `json:"type"`
|
||||
CustomResponseData string `json:"customResponseData"`
|
||||
}
|
||||
|
||||
type BlackholeConfig struct {
|
||||
Response json.RawMessage `json:"response"`
|
||||
Response *ResponseConfig `json:"response"`
|
||||
}
|
||||
|
||||
func (v *BlackholeConfig) Build() (proto.Message, error) {
|
||||
config := new(blackhole.Config)
|
||||
if v.Response != nil {
|
||||
response, _, err := configLoader.Load(v.Response)
|
||||
if err != nil {
|
||||
return nil, errors.New("Config: Failed to parse Blackhole response config.").Base(err)
|
||||
responseName := strings.ToLower(v.Response.Type)
|
||||
switch responseName {
|
||||
case "none", "":
|
||||
config.Response = &blackhole.Response{Type: "none"}
|
||||
case "http":
|
||||
config.Response = &blackhole.Response{Type: "http"}
|
||||
case "custom":
|
||||
data, err := base64.StdEncoding.DecodeString(v.Response.CustomResponseData)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to decode custom response data: " + err.Error())
|
||||
}
|
||||
config.Response = &blackhole.Response{Type: "custom", CustomResponseData: data}
|
||||
default:
|
||||
return nil, errors.New("unknown blackhole response: " + responseName)
|
||||
}
|
||||
responseSettings, err := response.(Buildable).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Response = serial.ToTypedMessage(responseSettings)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
var configLoader = NewJSONConfigLoader(
|
||||
ConfigCreatorCache{
|
||||
"none": func() interface{} { return new(NoneResponse) },
|
||||
"http": func() interface{} { return new(HTTPResponse) },
|
||||
},
|
||||
"type",
|
||||
"",
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ package conf_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
. "github.com/xtls/xray-core/infra/conf"
|
||||
"github.com/xtls/xray-core/proxy/blackhole"
|
||||
)
|
||||
@@ -22,7 +21,7 @@ func TestHTTPResponseJSON(t *testing.T) {
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &blackhole.Config{
|
||||
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
|
||||
Response: &blackhole.Response{Type: "http"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -32,3 +31,27 @@ func TestHTTPResponseJSON(t *testing.T) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestCustomResponseJSON(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(BlackholeConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"response": {
|
||||
"type": "custom",
|
||||
"customResponseData": "Y3VzdG9tIHJlc3BvbnNl"
|
||||
}
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &blackhole.Config{
|
||||
Response: &blackhole.Response{
|
||||
Type: "custom",
|
||||
CustomResponseData: []byte("custom response"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -312,21 +312,3 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type CustomSockoptConfig struct {
|
||||
Syetem string `json:"system"`
|
||||
System 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.Syetem,
|
||||
System: copt.System,
|
||||
Network: copt.Network,
|
||||
Level: copt.Level,
|
||||
Opt: copt.Opt,
|
||||
|
||||
+31
-34
@@ -15,6 +15,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -216,21 +217,11 @@ type OutboundDetourConfig struct {
|
||||
Tag string `json:"tag"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||
ProxySettings *ProxyConfig `json:"proxySettings"`
|
||||
ProxySettings *json.RawMessage `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
|
||||
@@ -267,6 +258,10 @@ 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", "":
|
||||
@@ -294,9 +289,6 @@ 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)
|
||||
@@ -322,26 +314,6 @@ 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 {
|
||||
@@ -366,6 +338,31 @@ 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,
|
||||
|
||||
+273
-139
@@ -1,15 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"mvdan.cc/gofumpt/format"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -23,101 +26,27 @@ var (
|
||||
isFormat bool
|
||||
)
|
||||
|
||||
// envFile returns the name of the Go environment configuration file.
|
||||
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
|
||||
func envFile() (string, error) {
|
||||
if file := os.Getenv("GOENV"); file != "" {
|
||||
if file == "off" {
|
||||
return "", errors.New("GOENV=off")
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
dir, err := os.UserConfigDir()
|
||||
func getModuleInfo(pwd string) (modPath, langVersion string, err error) {
|
||||
data, err := os.ReadFile(filepath.Join(pwd, "go.mod"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
if dir == "" {
|
||||
return "", errors.New("missing user-config dir")
|
||||
}
|
||||
return filepath.Join(dir, "go", "env"), nil
|
||||
}
|
||||
|
||||
// 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
|
||||
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")
|
||||
}
|
||||
<-maxTasks
|
||||
}(file)
|
||||
}
|
||||
}
|
||||
return formatRequired
|
||||
return modPath, langVersion, nil
|
||||
}
|
||||
|
||||
func formatGoSource(src []byte, opts format.Options) ([]byte, error) {
|
||||
return format.Source(src, opts)
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -150,26 +79,76 @@ func main() {
|
||||
}
|
||||
|
||||
pwd := *directory
|
||||
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.")
|
||||
modPath, langVersion, modErr := getModuleInfo(pwd)
|
||||
if modErr != nil {
|
||||
fmt.Println("Error reading go.mod:", modErr)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
gofmt = gofmtPath
|
||||
}
|
||||
opts := format.Options{
|
||||
LangVersion: langVersion,
|
||||
ModulePath: modPath,
|
||||
}
|
||||
|
||||
if isFormat {
|
||||
fmt.Println("Formatting Go source files...")
|
||||
} else if isCheck {
|
||||
fmt.Println("Checking files thar are not properly formatted...")
|
||||
}
|
||||
|
||||
jobs := make(chan string, runtime.NumCPU())
|
||||
var wg sync.WaitGroup
|
||||
var formatRequired atomic.Bool
|
||||
var hasErrors atomic.Bool
|
||||
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
wg.Go(func() {
|
||||
for path := range jobs {
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading %s: %v\n", path, err)
|
||||
hasErrors.Store(true)
|
||||
continue
|
||||
}
|
||||
|
||||
formatted, err := formatGoSource(src, opts)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error formatting %s: %v\n", path, err)
|
||||
hasErrors.Store(true)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, formatted) {
|
||||
var diffText []byte
|
||||
if isDryrun {
|
||||
newName := filepath.ToSlash(path)
|
||||
oldName := newName + ".orig"
|
||||
diffText = diff(oldName, src, newName, formatted)
|
||||
}
|
||||
if isFormat {
|
||||
info, statErr := os.Stat(path)
|
||||
if statErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error stating %s: %v\n", path, statErr)
|
||||
hasErrors.Store(true)
|
||||
continue
|
||||
}
|
||||
if writeErr := os.WriteFile(path, formatted, info.Mode().Perm()); writeErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", path, writeErr)
|
||||
hasErrors.Store(true)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
formatRequired.Store(true)
|
||||
if isDryrun && len(diffText) > 0 {
|
||||
fmt.Printf("%s\n%s", path, diffText)
|
||||
} else {
|
||||
fmt.Println(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
rawFilesSlice := make([]string, 0, 1000)
|
||||
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -186,51 +165,206 @@ func main() {
|
||||
!strings.HasSuffix(filename, ".pb.go") &&
|
||||
!strings.Contains(dir, filepath.Join("testing", "mocks")) &&
|
||||
!strings.Contains(path, filepath.Join("main", "distro", "all", "all.go")) {
|
||||
rawFilesSlice = append(rawFilesSlice, path)
|
||||
jobs <- path
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
close(jobs)
|
||||
wg.Wait()
|
||||
|
||||
if walkErr != nil {
|
||||
fmt.Println(walkErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if isFormat {
|
||||
gofmtArgs := []string{
|
||||
"-l", "-e", "-w",
|
||||
}
|
||||
if hasErrors.Load() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Formatting Go source files...")
|
||||
RunMany(gofmt, gofmtArgs, rawFilesSlice)
|
||||
fmt.Println("Do NOT forget to commit file changes.")
|
||||
if isFormat {
|
||||
if formatRequired.Load() {
|
||||
fmt.Println("Do NOT forget to commit file changes.")
|
||||
}
|
||||
}
|
||||
|
||||
if isCheck {
|
||||
gofmtListArgs := []string{
|
||||
"-l", "-e",
|
||||
}
|
||||
|
||||
fmt.Println("Checking files thar are not properly formatted...")
|
||||
formatRequired := RunMany(gofmt, gofmtListArgs, rawFilesSlice)
|
||||
if formatRequired {
|
||||
if formatRequired.Load() {
|
||||
fmt.Println("Format problem(s) found.")
|
||||
}
|
||||
|
||||
if isDryrun {
|
||||
if formatRequired {
|
||||
gofmtShowArgs := []string{
|
||||
"-d", "-e",
|
||||
}
|
||||
RunMany(gofmt, gofmtShowArgs, rawFilesSlice)
|
||||
}
|
||||
}
|
||||
|
||||
if formatRequired {
|
||||
fmt.Println("Please run 'go install -v mvdan.cc/gofumpt@latest', then run 'go run ./infra/vformat/main.go' to format the Go source files.")
|
||||
fmt.Println("Please 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
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
package blackhole
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"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/session"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
@@ -17,14 +20,34 @@ import (
|
||||
|
||||
// Handler is an outbound connection that silently swallow the entire payload.
|
||||
type Handler struct {
|
||||
response ResponseConfig
|
||||
response []byte
|
||||
}
|
||||
|
||||
var http403response = http.Response{
|
||||
StatusCode: 403,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: http.Header{
|
||||
"Connection": {"close"},
|
||||
"Cache-Control": {"max-age=3600, public"},
|
||||
},
|
||||
}
|
||||
|
||||
// New creates a new blackhole handler.
|
||||
func New(ctx context.Context, config *Config) (*Handler, error) {
|
||||
response, err := config.GetInternalResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
response := []byte{}
|
||||
if config.Response != nil {
|
||||
switch config.Response.Type {
|
||||
case "", "none":
|
||||
case "http":
|
||||
var data bytes.Buffer
|
||||
common.Must(http403response.Write(&data))
|
||||
response = data.Bytes()
|
||||
case "custom":
|
||||
response = config.Response.CustomResponseData
|
||||
default:
|
||||
return nil, errors.New("unknown blackhole response: " + config.Response.Type)
|
||||
}
|
||||
}
|
||||
return &Handler{
|
||||
response: response,
|
||||
@@ -37,8 +60,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
ob.Name = "blackhole"
|
||||
|
||||
nBytes := h.response.WriteTo(link.Writer)
|
||||
if nBytes > 0 {
|
||||
if len(h.response) > 0 {
|
||||
mbc := buf.MultiBufferContainer{}
|
||||
common.Must2(mbc.Write(h.response))
|
||||
link.Writer.WriteMultiBuffer(mbc.MultiBuffer)
|
||||
// Sleep a little here to make sure the response is sent to client.
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package blackhole_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/proxy/blackhole"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
@@ -16,27 +19,58 @@ import (
|
||||
func TestBlackholeHTTPResponse(t *testing.T) {
|
||||
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
|
||||
handler, err := blackhole.New(ctx, &blackhole.Config{
|
||||
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
|
||||
Response: &blackhole.Response{Type: "http"},
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
reader, writer := pipe.New(pipe.WithoutSizeLimit())
|
||||
|
||||
var mb buf.MultiBuffer
|
||||
var rerr error
|
||||
dataCh := make(chan buf.MultiBuffer, 1)
|
||||
go func() {
|
||||
b, e := reader.ReadMultiBuffer()
|
||||
mb = b
|
||||
rerr = e
|
||||
mb := common.Must2(reader.ReadMultiBuffer())
|
||||
dataCh <- mb
|
||||
}()
|
||||
|
||||
link := transport.Link{
|
||||
Reader: reader,
|
||||
Writer: writer,
|
||||
}
|
||||
common.Must(handler.Process(ctx, &link, nil))
|
||||
common.Must(rerr)
|
||||
if mb.IsEmpty() {
|
||||
t.Error("expect http response, but nothing")
|
||||
mb := <-dataCh
|
||||
data := make([]byte, mb.Len())
|
||||
mb.Copy(data)
|
||||
resp := common.Must2(http.ReadResponse(bufio.NewReader(bytes.NewBuffer(data)), nil))
|
||||
if resp.StatusCode != 403 {
|
||||
t.Errorf("expected 403 response, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlackholeCustomResponse(t *testing.T) {
|
||||
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
|
||||
// slightly bigger than a buffer
|
||||
expected := make([]byte, buf.Size+1000)
|
||||
if _, err := rand.Read(expected); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
handler, err := blackhole.New(ctx, &blackhole.Config{
|
||||
Response: &blackhole.Response{
|
||||
Type: "custom",
|
||||
CustomResponseData: expected,
|
||||
},
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
reader, writer := pipe.New(pipe.WithoutSizeLimit())
|
||||
var actual buf.MultiBuffer
|
||||
var rerr error
|
||||
go func() {
|
||||
actual, rerr = reader.ReadMultiBuffer()
|
||||
}()
|
||||
|
||||
link := transport.Link{Reader: reader, Writer: writer}
|
||||
common.Must(handler.Process(ctx, &link, nil))
|
||||
common.Must(rerr)
|
||||
|
||||
if actual.String() != string(expected) {
|
||||
t.Errorf("custom response mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package blackhole
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
)
|
||||
|
||||
const (
|
||||
http403response = `HTTP/1.1 403 Forbidden
|
||||
Connection: close
|
||||
Cache-Control: max-age=3600, public
|
||||
Content-Length: 0
|
||||
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
// ResponseConfig is the configuration for blackhole responses.
|
||||
type ResponseConfig interface {
|
||||
// WriteTo writes a predefined response to the specified buffer.
|
||||
WriteTo(buf.Writer) int32
|
||||
}
|
||||
|
||||
// WriteTo implements ResponseConfig.WriteTo().
|
||||
func (*NoneResponse) WriteTo(buf.Writer) int32 { return 0 }
|
||||
|
||||
// WriteTo implements ResponseConfig.WriteTo().
|
||||
func (*HTTPResponse) WriteTo(writer buf.Writer) int32 {
|
||||
b := buf.New()
|
||||
common.Must2(b.WriteString(http403response))
|
||||
n := b.Len()
|
||||
writer.WriteMultiBuffer(buf.MultiBuffer{b})
|
||||
return n
|
||||
}
|
||||
|
||||
// GetInternalResponse converts response settings from proto to internal data structure.
|
||||
func (c *Config) GetInternalResponse() (ResponseConfig, error) {
|
||||
if c.GetResponse() == nil {
|
||||
return new(NoneResponse), nil
|
||||
}
|
||||
|
||||
config, err := c.GetResponse().GetInstance()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config.(ResponseConfig), nil
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
package blackhole
|
||||
|
||||
import (
|
||||
serial "github.com/xtls/xray-core/common/serial"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
@@ -22,26 +21,28 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type NoneResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
type Response struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
CustomResponseData []byte `protobuf:"bytes,2,opt,name=custom_response_data,json=customResponseData,proto3" json:"custom_response_data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NoneResponse) Reset() {
|
||||
*x = NoneResponse{}
|
||||
func (x *Response) Reset() {
|
||||
*x = Response{}
|
||||
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NoneResponse) String() string {
|
||||
func (x *Response) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NoneResponse) ProtoMessage() {}
|
||||
func (*Response) ProtoMessage() {}
|
||||
|
||||
func (x *NoneResponse) ProtoReflect() protoreflect.Message {
|
||||
func (x *Response) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -53,57 +54,35 @@ func (x *NoneResponse) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NoneResponse.ProtoReflect.Descriptor instead.
|
||||
func (*NoneResponse) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type HTTPResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HTTPResponse) Reset() {
|
||||
*x = HTTPResponse{}
|
||||
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *HTTPResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*HTTPResponse) ProtoMessage() {}
|
||||
|
||||
func (x *HTTPResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
||||
func (x *Response) GetType() string {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
return x.Type
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Deprecated: Use HTTPResponse.ProtoReflect.Descriptor instead.
|
||||
func (*HTTPResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}
|
||||
func (x *Response) GetCustomResponseData() []byte {
|
||||
if x != nil {
|
||||
return x.CustomResponseData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Response *serial.TypedMessage `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
||||
Response *Response `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_proxy_blackhole_config_proto_msgTypes[2]
|
||||
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -115,7 +94,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_blackhole_config_proto_msgTypes[2]
|
||||
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -128,10 +107,10 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{2}
|
||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Config) GetResponse() *serial.TypedMessage {
|
||||
func (x *Config) GetResponse() *Response {
|
||||
if x != nil {
|
||||
return x.Response
|
||||
}
|
||||
@@ -142,11 +121,12 @@ var File_proxy_blackhole_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proxy_blackhole_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1cproxy/blackhole/config.proto\x12\x14xray.proxy.blackhole\x1a!common/serial/typed_message.proto\"\x0e\n" +
|
||||
"\fNoneResponse\"\x0e\n" +
|
||||
"\fHTTPResponse\"F\n" +
|
||||
"\x06Config\x12<\n" +
|
||||
"\bresponse\x18\x01 \x01(\v2 .xray.common.serial.TypedMessageR\bresponseB^\n" +
|
||||
"\x1cproxy/blackhole/config.proto\x12\x14xray.proxy.blackhole\"P\n" +
|
||||
"\bResponse\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x120\n" +
|
||||
"\x14custom_response_data\x18\x02 \x01(\fR\x12customResponseData\"D\n" +
|
||||
"\x06Config\x12:\n" +
|
||||
"\bresponse\x18\x01 \x01(\v2\x1e.xray.proxy.blackhole.ResponseR\bresponseB^\n" +
|
||||
"\x18com.xray.proxy.blackholeP\x01Z)github.com/xtls/xray-core/proxy/blackhole\xaa\x02\x14Xray.Proxy.Blackholeb\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -161,15 +141,13 @@ func file_proxy_blackhole_config_proto_rawDescGZIP() []byte {
|
||||
return file_proxy_blackhole_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_proxy_blackhole_config_proto_goTypes = []any{
|
||||
(*NoneResponse)(nil), // 0: xray.proxy.blackhole.NoneResponse
|
||||
(*HTTPResponse)(nil), // 1: xray.proxy.blackhole.HTTPResponse
|
||||
(*Config)(nil), // 2: xray.proxy.blackhole.Config
|
||||
(*serial.TypedMessage)(nil), // 3: xray.common.serial.TypedMessage
|
||||
(*Response)(nil), // 0: xray.proxy.blackhole.Response
|
||||
(*Config)(nil), // 1: xray.proxy.blackhole.Config
|
||||
}
|
||||
var file_proxy_blackhole_config_proto_depIdxs = []int32{
|
||||
3, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.common.serial.TypedMessage
|
||||
0, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.proxy.blackhole.Response
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
@@ -188,7 +166,7 @@ func file_proxy_blackhole_config_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -6,12 +6,11 @@ option go_package = "github.com/xtls/xray-core/proxy/blackhole";
|
||||
option java_package = "com.xray.proxy.blackhole";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/serial/typed_message.proto";
|
||||
|
||||
message NoneResponse {}
|
||||
|
||||
message HTTPResponse {}
|
||||
message Response {
|
||||
string type = 1;
|
||||
bytes custom_response_data = 2;
|
||||
}
|
||||
|
||||
message Config {
|
||||
xray.common.serial.TypedMessage response = 1;
|
||||
Response response = 1;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
package blackhole_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net/http"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
. "github.com/xtls/xray-core/proxy/blackhole"
|
||||
"github.com/xtls/xray-core/proxy/blackhole"
|
||||
)
|
||||
|
||||
func TestHTTPResponse(t *testing.T) {
|
||||
buffer := buf.New()
|
||||
|
||||
httpResponse := new(HTTPResponse)
|
||||
httpResponse.WriteTo(buf.NewWriter(buffer))
|
||||
|
||||
reader := bufio.NewReader(buffer)
|
||||
response, err := http.ReadResponse(reader, nil)
|
||||
handler, err := blackhole.New(context.Background(), &blackhole.Config{
|
||||
Response: &blackhole.Response{Type: "http"},
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
if response.StatusCode != 403 {
|
||||
t.Error("expected status code 403, but got ", response.StatusCode)
|
||||
if handler == nil {
|
||||
t.Error("expected HTTP response handler")
|
||||
}
|
||||
}
|
||||
|
||||
+90
-101
@@ -53,6 +53,10 @@ 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 {
|
||||
@@ -89,9 +93,11 @@ type FinalRule struct {
|
||||
|
||||
// Handler handles Freedom connections.
|
||||
type Handler struct {
|
||||
policyManager policy.Manager
|
||||
config *Config
|
||||
finalRules []*FinalRule
|
||||
policyManager policy.Manager
|
||||
config *Config
|
||||
finalRules []*FinalRule
|
||||
resolveStrategy internet.DomainStrategy
|
||||
usesDialerProxy bool
|
||||
}
|
||||
|
||||
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
|
||||
@@ -168,22 +174,6 @@ 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) {
|
||||
@@ -196,13 +186,6 @@ 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
|
||||
@@ -237,6 +220,20 @@ 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
|
||||
@@ -284,61 +281,53 @@ 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 {
|
||||
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())
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
} else {
|
||||
if rule := h.matchFinalRule(destination.Network, destination.Address, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
|
||||
blockedDest = &destination
|
||||
blockedRule = rule
|
||||
return nil
|
||||
}
|
||||
if len(ips) == 0 { // SRV/TXT, lookup failed
|
||||
return errors.New("failed to get IP address for domain ", domain)
|
||||
}
|
||||
if addr := net.IPAddress(ips[dice.Roll(len(ips))]); addr != nil {
|
||||
dialDest.Address = addr
|
||||
errors.LogInfo(ctx, "dialing to ", dialDest)
|
||||
}
|
||||
}
|
||||
if rule := h.matchFinalRule(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
|
||||
blockedDest = &dialDest
|
||||
blockedRule = rule
|
||||
return nil
|
||||
}
|
||||
|
||||
rawConn, err := dialer.Dial(ctx, dialDest)
|
||||
rawConn, err := dialer.Dial(ctx, destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -350,20 +339,21 @@ 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 {
|
||||
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
|
||||
return h.blackhole(ctx, input, output, blockedRule, blockedDest)
|
||||
}
|
||||
if destination.Address.Family().IsDomain() && (defaultRule != nil || len(h.finalRules) > 0) {
|
||||
if h.usesDialerProxy {
|
||||
errors.LogInfo(ctx, "skipping final rule check for proxied remote endpoint, original target: ", destination)
|
||||
} else {
|
||||
// pre-check may fail or dialer may select another IP
|
||||
remoteDest := net.DestinationFromAddr(conn.RemoteAddr())
|
||||
if rule := h.matchFinalRule(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
|
||||
conn.Close()
|
||||
return h.blackhole(ctx, input, output, rule, &remoteDest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
|
||||
version := byte(h.config.ProxyProtocol)
|
||||
srcAddr := inbound.Source.RawNetAddr()
|
||||
@@ -408,7 +398,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)
|
||||
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination, outGateway)
|
||||
if h.config.Noises != nil {
|
||||
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
||||
writer = &NoisePacketWriter{
|
||||
@@ -512,7 +502,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
}
|
||||
udpAddr := d.(*net.UDPAddr)
|
||||
sourceAddr := net.IPAddress(udpAddr.IP)
|
||||
if r.Handler.applyFinalRules(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule) == RuleAction_Block {
|
||||
if rule := r.Handler.matchFinalRule(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule); rule != nil && rule.action == RuleAction_Block {
|
||||
continue
|
||||
}
|
||||
b.Resize(0, int32(n))
|
||||
@@ -537,7 +527,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) buf.Writer {
|
||||
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination, outGateway net.Address) buf.Writer {
|
||||
iConn := conn
|
||||
statConn, ok := iConn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
@@ -561,9 +551,8 @@ func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverr
|
||||
DefaultRule: defaultRule,
|
||||
UDPOverride: UDPOverride,
|
||||
ResolvedUDPAddr: resolvedUDPAddr,
|
||||
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
|
||||
OutGateway: outGateway,
|
||||
}
|
||||
|
||||
}
|
||||
return &buf.SequentialWriter{Writer: conn}
|
||||
}
|
||||
@@ -580,7 +569,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]
|
||||
LocalAddr net.Address
|
||||
OutGateway net.Address
|
||||
}
|
||||
|
||||
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
@@ -603,21 +592,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 w.Handler.config.DomainStrategy.HasStrategy() {
|
||||
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), w.Handler.config.DomainStrategy, w.LocalAddr)
|
||||
shouldUseSystemResolver := true
|
||||
if strategy := w.Handler.resolveStrategy; strategy.HasStrategy() {
|
||||
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), strategy, w.OutGateway)
|
||||
if err != nil {
|
||||
// drop packet if resolve failed when forceIP
|
||||
if w.Handler.config.DomainStrategy.ForceIP() {
|
||||
if strategy.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()
|
||||
@@ -631,7 +620,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.applyFinalRules(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule) == RuleAction_Block {
|
||||
if rule := w.matchFinalRule(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule); rule != nil && rule.action == RuleAction_Block {
|
||||
b.Release()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -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 bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
|
||||
if len(ResponseHeader1xx) >= 4 && bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
|
||||
break
|
||||
}
|
||||
if len(ResponseHeader1xx) > 1024 {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A malformed upstream response containing a bare '\n' before the real
|
||||
// status line used to crash readResponseAndHandle100Continue: the first
|
||||
// ReadSlice('\n') returns fewer than 4 bytes, and slicing
|
||||
// ResponseHeader1xx[len(ResponseHeader1xx)-4:] panicked with a negative
|
||||
// index instead of returning an error.
|
||||
func TestReadResponseAndHandle100ContinueDoesNotPanicOnEarlyNewline(t *testing.T) {
|
||||
payload := "X\nHTTP/1.1 100 Continue\r\n\r\n" + strings.Repeat("A", 40)
|
||||
r := bufio.NewReader(bytes.NewReader([]byte(payload)))
|
||||
req, err := http.NewRequest("GET", "http://example.com/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Must not panic; a parse error for the garbage trailing bytes is fine.
|
||||
_, _ = readResponseAndHandle100Continue(r, req, io.Discard)
|
||||
}
|
||||
|
||||
func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testing.T) {
|
||||
payload := "HTTP/1.1 100 Continue\r\n\r\n" +
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello"
|
||||
r := bufio.NewReader(bytes.NewReader([]byte(payload)))
|
||||
req, err := http.NewRequest("GET", "http://example.com/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var forwarded bytes.Buffer
|
||||
resp, err := readResponseAndHandle100Continue(r, req, &forwarded)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if !strings.Contains(forwarded.String(), "100 Continue") {
|
||||
t.Fatalf("expected 1xx response to be forwarded, got %q", forwarded.String())
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,7 @@ 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())
|
||||
|
||||
+145
-137
@@ -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,13 +31,14 @@ func procyield(cycles uint32)
|
||||
type WindowsTun struct {
|
||||
sync.RWMutex
|
||||
|
||||
options *Config
|
||||
adapter *wintun.Adapter
|
||||
session wintun.Session
|
||||
readWait windows.Handle
|
||||
luid winipcfg.LUID
|
||||
changeCallback winipcfg.ChangeCallback
|
||||
closed bool
|
||||
options *Config
|
||||
adapter *wintun.Adapter
|
||||
session wintun.Session
|
||||
readWait windows.Handle
|
||||
luid winipcfg.LUID
|
||||
cbr winipcfg.ChangeCallback
|
||||
cbi winipcfg.ChangeCallback
|
||||
closed bool
|
||||
}
|
||||
|
||||
// WindowsTun implements Tun
|
||||
@@ -85,23 +86,37 @@ func open(name, desc string) (*wintun.Adapter, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
}
|
||||
|
||||
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 _, ip := range allowedIPs {
|
||||
for _, cidr := range t.options.AutoSystemRoutingTable {
|
||||
prefix := netip.MustParsePrefix(cidr)
|
||||
route := winipcfg.RouteData{
|
||||
Destination: ip.Masked(),
|
||||
Destination: prefix.Masked(),
|
||||
Metric: 0,
|
||||
}
|
||||
if ip.Addr().Is4() {
|
||||
has4 = true
|
||||
if prefix.Addr().Is4() {
|
||||
route4 = true
|
||||
route.NextHop = netip.IPv4Unspecified()
|
||||
} else {
|
||||
has6 = true
|
||||
route6 = true
|
||||
route.NextHop = netip.IPv6Unspecified()
|
||||
}
|
||||
routesMap[route] = struct{}{}
|
||||
@@ -111,24 +126,40 @@ func (t *WindowsTun) Start() error {
|
||||
r := route
|
||||
routesData = append(routesData, &r)
|
||||
}
|
||||
err := t.luid.SetRoutes(routesData)
|
||||
if err != nil {
|
||||
return errors.New("unable to set routes").Base(err)
|
||||
}
|
||||
|
||||
if len(t.options.Gateway) > 0 {
|
||||
addresses := make([]netip.Prefix, 0, len(t.options.Gateway))
|
||||
for _, address := range t.options.Gateway {
|
||||
addresses = append(addresses, netip.MustParsePrefix(address))
|
||||
}
|
||||
err := t.luid.SetIPAddresses(addresses)
|
||||
if err != nil {
|
||||
return errors.New("unable to set ips").Base(err)
|
||||
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)
|
||||
}
|
||||
|
||||
if has4 {
|
||||
ipif, err := t.luid.IPInterface(windows.AF_INET)
|
||||
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 family == windows.AF_INET && address4 || family == windows.AF_INET6 && address6 {
|
||||
err = t.luid.SetIPAddressesForFamily(family, addresses)
|
||||
if err != nil {
|
||||
firstErr = errors.New("unable to set ips").Base(err)
|
||||
if err == windows.ERROR_NOT_FOUND {
|
||||
goto startOver
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
}
|
||||
ipif, err := t.luid.IPInterface(family)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -136,56 +167,45 @@ func (t *WindowsTun) Start() error {
|
||||
ipif.DadTransmits = 0
|
||||
ipif.ManagedAddressConfigurationSupported = false
|
||||
ipif.OtherStatefulConfigurationSupported = false
|
||||
ipif.NLMTU = t.options.MTU
|
||||
ipif.UseAutomaticMetric = false
|
||||
ipif.Metric = 0
|
||||
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
|
||||
}
|
||||
err = ipif.Set()
|
||||
if err != nil {
|
||||
return err
|
||||
firstErr = errors.New("unable to set metric and MTU").Base(err)
|
||||
if err == windows.ERROR_NOT_FOUND {
|
||||
goto startOver
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
}
|
||||
if has6 {
|
||||
ipif, err := t.luid.IPInterface(windows.AF_INET6)
|
||||
err = t.luid.SetDNS(family, dns, nil)
|
||||
if err != nil {
|
||||
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
|
||||
firstErr = errors.New("unable to set DNS").Base(err)
|
||||
if err == windows.ERROR_NOT_FOUND {
|
||||
goto startOver
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
}
|
||||
|
||||
if updater != nil {
|
||||
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
||||
t.cbr, err = winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) {
|
||||
updater.Update()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.cbi, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
||||
updater.Update()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -197,12 +217,26 @@ func (t *WindowsTun) Close() error {
|
||||
}
|
||||
t.closed = true
|
||||
|
||||
if t.changeCallback != nil {
|
||||
t.changeCallback.Unregister()
|
||||
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()
|
||||
}
|
||||
t.session.End()
|
||||
_ = t.adapter.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -311,75 +345,49 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
|
||||
}
|
||||
|
||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||
interfaces, err := net.Interfaces()
|
||||
if fixedName != "" {
|
||||
return net.InterfaceByName(fixedName)
|
||||
}
|
||||
|
||||
r, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
||||
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
|
||||
}
|
||||
|
||||
if fixedName != "" {
|
||||
for _, iface := range interfaces {
|
||||
if iface.Index != tunIndex && iface.Name == fixedName {
|
||||
return &iface, nil
|
||||
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
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var candidates []struct {
|
||||
index int
|
||||
score int
|
||||
if ifrow.Type == windows.IF_TYPE_IEEE80211 {
|
||||
if r[i].Metric+iface.Metric < lowestMetricWifi {
|
||||
lowestMetricWifi = r[i].Metric + iface.Metric
|
||||
indexWifi = r[i].InterfaceIndex
|
||||
}
|
||||
continue
|
||||
}
|
||||
if r[i].Metric+iface.Metric < lowestMetric {
|
||||
lowestMetric = r[i].Metric + iface.Metric
|
||||
index = r[i].InterfaceIndex
|
||||
}
|
||||
}
|
||||
for i, iface := range interfaces {
|
||||
if iface.Index == tunIndex {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(iface.Name, "vEthernet") {
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil || len(addrs) == 0 {
|
||||
continue
|
||||
}
|
||||
candidates = append(candidates, struct {
|
||||
index int
|
||||
score int
|
||||
}{i, scoreWindowsInterface(&iface, addrs)})
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
return net.InterfaceByIndex(int(index))
|
||||
}
|
||||
|
||||
@@ -202,8 +202,6 @@ 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())
|
||||
|
||||
@@ -82,9 +82,14 @@ func TestResolveIP(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Tag: "direct",
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
DomainStrategy: internet.DomainStrategy_USE_IP,
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
SocketSettings: &internet.SocketConfig{
|
||||
DomainStrategy: internet.DomainStrategy_USE_IP,
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestPassiveConnection(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxy(t *testing.T) {
|
||||
func TestDialerProxy(t *testing.T) {
|
||||
tcpServer := tcp.Server{
|
||||
MsgProcessor: xor,
|
||||
}
|
||||
@@ -187,8 +187,10 @@ func TestProxy(t *testing.T) {
|
||||
},
|
||||
}),
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
ProxySettings: &internet.ProxyConfig{
|
||||
Tag: "proxy",
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
SocketSettings: &internet.SocketConfig{
|
||||
DialerProxy: "proxy",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
@@ -218,7 +220,7 @@ func TestProxy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyOverKCP(t *testing.T) {
|
||||
func TestDialerProxyOverKCP(t *testing.T) {
|
||||
tcpServer := tcp.Server{
|
||||
MsgProcessor: xor,
|
||||
}
|
||||
@@ -321,11 +323,11 @@ func TestProxyOverKCP(t *testing.T) {
|
||||
},
|
||||
}),
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
ProxySettings: &internet.ProxyConfig{
|
||||
Tag: "proxy",
|
||||
},
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
ProtocolName: "mkcp",
|
||||
SocketSettings: &internet.SocketConfig{
|
||||
DialerProxy: "proxy",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -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 broken fingerprint: android, 360
|
||||
// Known working fingerprint: chrome, firefox, safari
|
||||
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", "ios", "edge", "qq"}
|
||||
fingerPrints := []string{"chrome", "firefox", "safari"}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(fingerPrints))
|
||||
for _, fp := range fingerPrints {
|
||||
|
||||
@@ -10,7 +10,7 @@ type ConfigCreator func() interface{}
|
||||
|
||||
var globalTransportConfigCreatorCache = make(map[string]ConfigCreator)
|
||||
|
||||
var strategy = [][]byte{
|
||||
var strategy = [11][3]byte{
|
||||
// name strategy, prefer, fallback
|
||||
{0, 0, 0}, // AsIs none, /, /
|
||||
{1, 0, 0}, // UseIP use, both, none
|
||||
@@ -25,8 +25,6 @@ var strategy = [][]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()
|
||||
@@ -91,10 +89,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -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{6, 0}
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{5, 0}
|
||||
}
|
||||
|
||||
type TransportConfig struct {
|
||||
@@ -614,58 +614,6 @@ 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"`
|
||||
@@ -680,7 +628,7 @@ type CustomSockopt struct {
|
||||
|
||||
func (x *CustomSockopt) Reset() {
|
||||
*x = CustomSockopt{}
|
||||
mi := &file_transport_internet_config_proto_msgTypes[5]
|
||||
mi := &file_transport_internet_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -692,7 +640,7 @@ func (x *CustomSockopt) String() string {
|
||||
func (*CustomSockopt) ProtoMessage() {}
|
||||
|
||||
func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_config_proto_msgTypes[5]
|
||||
mi := &file_transport_internet_config_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -705,7 +653,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{5}
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *CustomSockopt) GetSystem() string {
|
||||
@@ -785,7 +733,7 @@ type SocketConfig struct {
|
||||
|
||||
func (x *SocketConfig) Reset() {
|
||||
*x = SocketConfig{}
|
||||
mi := &file_transport_internet_config_proto_msgTypes[6]
|
||||
mi := &file_transport_internet_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -797,7 +745,7 @@ func (x *SocketConfig) String() string {
|
||||
func (*SocketConfig) ProtoMessage() {}
|
||||
|
||||
func (x *SocketConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_config_proto_msgTypes[6]
|
||||
mi := &file_transport_internet_config_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -810,7 +758,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{6}
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *SocketConfig) GetMark() int32 {
|
||||
@@ -972,7 +920,7 @@ type HappyEyeballsConfig struct {
|
||||
|
||||
func (x *HappyEyeballsConfig) Reset() {
|
||||
*x = HappyEyeballsConfig{}
|
||||
mi := &file_transport_internet_config_proto_msgTypes[7]
|
||||
mi := &file_transport_internet_config_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -984,7 +932,7 @@ func (x *HappyEyeballsConfig) String() string {
|
||||
func (*HappyEyeballsConfig) ProtoMessage() {}
|
||||
|
||||
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_config_proto_msgTypes[7]
|
||||
mi := &file_transport_internet_config_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -997,7 +945,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{7}
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
|
||||
@@ -1078,10 +1026,7 @@ const file_transport_internet_config_proto_rawDesc = "" +
|
||||
"disableGSO\x18\x0f \x01(\bR\n" +
|
||||
"disableGSO\x120\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" +
|
||||
"\x17disable_stateless_reset\x18\x11 \x01(\bR\x15disableStatelessReset\"\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" +
|
||||
@@ -1165,7 +1110,7 @@ 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, 8)
|
||||
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_transport_internet_config_proto_goTypes = []any{
|
||||
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
|
||||
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
|
||||
@@ -1174,28 +1119,27 @@ var file_transport_internet_config_proto_goTypes = []any{
|
||||
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
|
||||
(*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
|
||||
(*CustomSockopt)(nil), // 7: xray.transport.internet.CustomSockopt
|
||||
(*SocketConfig)(nil), // 8: xray.transport.internet.SocketConfig
|
||||
(*HappyEyeballsConfig)(nil), // 9: xray.transport.internet.HappyEyeballsConfig
|
||||
(*serial.TypedMessage)(nil), // 10: xray.common.serial.TypedMessage
|
||||
(*net.IPOrDomain)(nil), // 11: xray.common.net.IPOrDomain
|
||||
}
|
||||
var file_transport_internet_config_proto_depIdxs = []int32{
|
||||
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
|
||||
10, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
||||
11, // 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
|
||||
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
|
||||
10, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
||||
10, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
|
||||
10, // 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
|
||||
8, // 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
|
||||
7, // 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
|
||||
9, // 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
|
||||
@@ -1214,7 +1158,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: 8,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -90,11 +90,6 @@ message QuicParams {
|
||||
bool disable_stateless_reset = 17;
|
||||
}
|
||||
|
||||
message ProxyConfig {
|
||||
string tag = 1;
|
||||
bool transportLayerProxy = 2;
|
||||
}
|
||||
|
||||
message CustomSockopt {
|
||||
string system = 1;
|
||||
string network = 2;
|
||||
|
||||
@@ -231,8 +231,8 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
case "", "unix":
|
||||
u = &url.URL{Scheme: "http", Host: "localhost"}
|
||||
path := u.Path
|
||||
u = &url.URL{Scheme: "http", Host: "localhost"}
|
||||
dialer := &net.Dialer{Timeout: 30 * time.Second}
|
||||
transport = transport.Clone()
|
||||
transport.Proxy = nil
|
||||
|
||||
@@ -169,41 +169,42 @@ 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
// 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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
|
||||
}
|
||||
c.transportConfig.FillStreamRequest(req, sessionId, "")
|
||||
|
||||
wrc = &WaitReadCloser{Wait: make(chan struct{})}
|
||||
wrc = &WaitReadCloser{wait: done.New()}
|
||||
go func() {
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
@@ -188,38 +188,35 @@ func (c *DefaultDialerClient) Close() error {
|
||||
}
|
||||
|
||||
type WaitReadCloser struct {
|
||||
Wait chan struct{}
|
||||
io.ReadCloser
|
||||
wait *done.Instance
|
||||
reader atomic.Pointer[io.ReadCloser]
|
||||
}
|
||||
|
||||
func (w *WaitReadCloser) Set(rc io.ReadCloser) {
|
||||
w.ReadCloser = rc
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
rc.Close()
|
||||
w.reader.Store(&rc)
|
||||
if w.wait.Done() {
|
||||
if p := w.reader.Swap(nil); p != nil {
|
||||
(*p).Close()
|
||||
}
|
||||
}()
|
||||
close(w.Wait)
|
||||
}
|
||||
w.wait.Close()
|
||||
}
|
||||
|
||||
func (w *WaitReadCloser) Read(b []byte) (int, error) {
|
||||
if w.ReadCloser == nil {
|
||||
if <-w.Wait; w.ReadCloser == nil {
|
||||
rc := w.reader.Load()
|
||||
if rc == nil {
|
||||
<-w.wait.Wait()
|
||||
if rc = w.reader.Load(); rc == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
return w.ReadCloser.Read(b)
|
||||
return (*rc).Read(b)
|
||||
}
|
||||
|
||||
func (w *WaitReadCloser) Close() error {
|
||||
if w.ReadCloser != nil {
|
||||
return w.ReadCloser.Close()
|
||||
w.wait.Close()
|
||||
if p := w.reader.Swap(nil); p != nil {
|
||||
return (*p).Close()
|
||||
}
|
||||
defer func() {
|
||||
if recover() != nil && w.ReadCloser != nil {
|
||||
w.ReadCloser.Close()
|
||||
}
|
||||
}()
|
||||
close(w.Wait)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -175,6 +175,25 @@ 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
|
||||
|
||||
@@ -63,8 +63,8 @@ func New(opts ...Option) (*Reader, *Writer) {
|
||||
}
|
||||
|
||||
return &Reader{
|
||||
pipe: p,
|
||||
}, &Writer{
|
||||
pipe: p,
|
||||
}
|
||||
pipe: p,
|
||||
}, &Writer{
|
||||
pipe: p,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user