mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-02 13:56:39 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8056be3237 |
@@ -67,7 +67,9 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
cache: false
|
cache: false
|
||||||
- name: Check Format
|
- name: Check Format
|
||||||
run: go run ./infra/vformat/main.go -mode check -pwd ./
|
run: |
|
||||||
|
go install -v mvdan.cc/gofumpt@latest
|
||||||
|
go run ./infra/vformat/main.go -mode check -pwd ./
|
||||||
|
|
||||||
test:
|
test:
|
||||||
needs: check-assets
|
needs: check-assets
|
||||||
|
|||||||
+22
-11
@@ -330,6 +330,7 @@ type SenderConfig struct {
|
|||||||
// Send traffic through the given IP. Only IP is allowed.
|
// Send traffic through the given IP. Only IP is allowed.
|
||||||
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
|
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
|
||||||
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
|
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
|
||||||
|
ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"`
|
||||||
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
|
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
|
||||||
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
|
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
|
||||||
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
|
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
|
||||||
@@ -381,6 +382,13 @@ func (x *SenderConfig) GetStreamSettings() *internet.StreamConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SenderConfig) GetProxySettings() *internet.ProxyConfig {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProxySettings
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
|
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.MultiplexSettings
|
return x.MultiplexSettings
|
||||||
@@ -498,13 +506,14 @@ const file_app_proxyman_config_proto_rawDesc = "" +
|
|||||||
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
|
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
|
||||||
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
|
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
|
||||||
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
|
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
|
||||||
"\x0eOutboundConfig\"\xd6\x02\n" +
|
"\x0eOutboundConfig\"\x9d\x03\n" +
|
||||||
"\fSenderConfig\x12-\n" +
|
"\fSenderConfig\x12-\n" +
|
||||||
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
|
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
|
||||||
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12T\n" +
|
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12K\n" +
|
||||||
|
"\x0eproxy_settings\x18\x03 \x01(\v2$.xray.transport.internet.ProxyConfigR\rproxySettings\x12T\n" +
|
||||||
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
|
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
|
||||||
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
|
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
|
||||||
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategyJ\x04\b\x03\x10\x04\"\xa4\x01\n" +
|
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategy\"\xa4\x01\n" +
|
||||||
"\x12MultiplexingConfig\x12\x18\n" +
|
"\x12MultiplexingConfig\x12\x18\n" +
|
||||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
|
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
|
||||||
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
|
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
|
||||||
@@ -539,7 +548,8 @@ var file_app_proxyman_config_proto_goTypes = []any{
|
|||||||
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
||||||
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
|
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
|
||||||
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
|
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
|
||||||
(internet.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy
|
(*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig
|
||||||
|
(internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy
|
||||||
}
|
}
|
||||||
var file_app_proxyman_config_proto_depIdxs = []int32{
|
var file_app_proxyman_config_proto_depIdxs = []int32{
|
||||||
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
|
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
|
||||||
@@ -552,13 +562,14 @@ var file_app_proxyman_config_proto_depIdxs = []int32{
|
|||||||
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
||||||
10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
||||||
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||||
6, // 10: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
13, // 10: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
|
||||||
13, // 11: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
6, // 11: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
||||||
12, // [12:12] is the sub-list for method output_type
|
14, // 12: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||||
12, // [12:12] is the sub-list for method input_type
|
13, // [13:13] is the sub-list for method output_type
|
||||||
12, // [12:12] is the sub-list for extension type_name
|
13, // [13:13] is the sub-list for method input_type
|
||||||
12, // [12:12] is the sub-list for extension extendee
|
13, // [13:13] is the sub-list for extension type_name
|
||||||
0, // [0:12] is the sub-list for field type_name
|
13, // [13:13] is the sub-list for extension extendee
|
||||||
|
0, // [0:13] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_app_proxyman_config_proto_init() }
|
func init() { file_app_proxyman_config_proto_init() }
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ message SenderConfig {
|
|||||||
// Send traffic through the given IP. Only IP is allowed.
|
// Send traffic through the given IP. Only IP is allowed.
|
||||||
xray.common.net.IPOrDomain via = 1;
|
xray.common.net.IPOrDomain via = 1;
|
||||||
xray.transport.internet.StreamConfig stream_settings = 2;
|
xray.transport.internet.StreamConfig stream_settings = 2;
|
||||||
reserved 3;
|
xray.transport.internet.ProxyConfig proxy_settings = 3;
|
||||||
MultiplexingConfig multiplex_settings = 4;
|
MultiplexingConfig multiplex_settings = 4;
|
||||||
string via_cidr = 5;
|
string via_cidr = 5;
|
||||||
xray.transport.internet.DomainStrategy target_strategy = 6;
|
xray.transport.internet.DomainStrategy target_strategy = 6;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/mux"
|
"github.com/xtls/xray-core/common/mux"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
@@ -25,6 +26,8 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"github.com/xtls/xray-core/transport/pipe"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,6 +63,7 @@ type Handler struct {
|
|||||||
streamSettings *internet.MemoryStreamConfig
|
streamSettings *internet.MemoryStreamConfig
|
||||||
proxyConfig proto.Message
|
proxyConfig proto.Message
|
||||||
proxy proxy.Outbound
|
proxy proxy.Outbound
|
||||||
|
outboundManager outbound.Manager
|
||||||
mux *mux.ClientManager
|
mux *mux.ClientManager
|
||||||
xudp *mux.ClientManager
|
xudp *mux.ClientManager
|
||||||
udp443 string
|
udp443 string
|
||||||
@@ -73,6 +77,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
|||||||
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
|
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
tag: config.Tag,
|
tag: config.Tag,
|
||||||
|
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
|
||||||
uplinkCounter: uplinkCounter,
|
uplinkCounter: uplinkCounter,
|
||||||
downlinkCounter: downlinkCounter,
|
downlinkCounter: downlinkCounter,
|
||||||
}
|
}
|
||||||
@@ -103,11 +108,9 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
|||||||
|
|
||||||
ctx = session.ContextWithFullHandler(ctx, h)
|
ctx = session.ContextWithFullHandler(ctx, h)
|
||||||
|
|
||||||
if h.streamSettings != nil {
|
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
|
||||||
ctx = session.ContextWithStreamSettings(ctx, h.streamSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -194,6 +197,7 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
|
|||||||
common.Interrupt(link.Reader)
|
common.Interrupt(link.Reader)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
unchangedDomain := ob.Target.Address.Domain()
|
unchangedDomain := ob.Target.Address.Domain()
|
||||||
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
||||||
@@ -266,26 +270,66 @@ func (h *Handler) DestIpAddress() net.IP {
|
|||||||
|
|
||||||
// Dial implements internet.Dialer.
|
// Dial implements internet.Dialer.
|
||||||
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||||
if h.senderSettings != nil && h.senderSettings.Via != nil {
|
if h.senderSettings != nil {
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
|
||||||
ob := outbounds[len(outbounds)-1]
|
if h.senderSettings.ProxySettings.HasTag() {
|
||||||
h.SetOutboundGateway(ctx, ob)
|
|
||||||
|
tag := h.senderSettings.ProxySettings.Tag
|
||||||
|
handler := h.outboundManager.GetHandler(tag)
|
||||||
|
if handler != nil {
|
||||||
|
errors.LogDebug(ctx, "proxying to ", tag, " for dest ", dest)
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
|
||||||
|
Target: dest,
|
||||||
|
Tag: tag,
|
||||||
|
})) // add another outbound in session ctx
|
||||||
|
opts := pipe.OptionsFromContext(ctx)
|
||||||
|
uplinkReader, uplinkWriter := pipe.New(opts...)
|
||||||
|
downlinkReader, downlinkWriter := pipe.New(opts...)
|
||||||
|
|
||||||
|
go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
|
||||||
|
conn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))
|
||||||
|
|
||||||
|
if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
|
||||||
|
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
|
||||||
|
conn = tls.Client(conn, tlsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.getStatCouterConnection(conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogError(ctx, "failed to get outbound handler with tag: ", tag)
|
||||||
|
return nil, errors.New("failed to get outbound handler with tag: " + tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.senderSettings.Via != nil {
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
h.SetOutboundGateway(ctx, ob)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := internet.Dial(ctx, dest, h.streamSettings)
|
conn, err := internet.Dial(ctx, dest, h.streamSettings)
|
||||||
conn = h.getStatCouterConnection(conn)
|
conn = h.getStatCouterConnection(conn)
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
if outbounds != nil {
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
ob.Conn = conn
|
||||||
|
} else {
|
||||||
|
// for Vision's pre-connect
|
||||||
|
}
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
|
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
|
||||||
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil &&
|
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) {
|
||||||
(h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
|
||||||
var domain string
|
var domain string
|
||||||
addr := h.senderSettings.Via.AsAddress()
|
addr := h.senderSettings.Via.AsAddress()
|
||||||
domain = h.senderSettings.Via.GetDomain()
|
domain = h.senderSettings.Via.GetDomain()
|
||||||
switch {
|
switch {
|
||||||
case h.senderSettings.ViaCidr != "":
|
case h.senderSettings.ViaCidr != "":
|
||||||
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
|
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
|
||||||
|
|
||||||
case domain == "origin":
|
case domain == "origin":
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
|
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
|
||||||
@@ -300,9 +344,11 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
|
|||||||
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: // case addr.Family().IsDomain():
|
// case addr.Family().IsDomain():
|
||||||
|
default:
|
||||||
ob.Gateway = addr
|
ob.Gateway = addr
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type windowsReader struct {
|
type windowsReader struct {
|
||||||
bufs []syscall.WSABuf
|
bufs []syscall.WSABuf
|
||||||
ready bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Init(bs []*Buffer) {
|
func (r *windowsReader) Init(bs []*Buffer) {
|
||||||
@@ -16,7 +15,6 @@ func (r *windowsReader) Init(bs []*Buffer) {
|
|||||||
for _, b := range bs {
|
for _, b := range bs {
|
||||||
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
|
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
|
||||||
}
|
}
|
||||||
r.ready = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Clear() {
|
func (r *windowsReader) Clear() {
|
||||||
@@ -27,14 +25,6 @@ func (r *windowsReader) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Read(fd uintptr) int32 {
|
func (r *windowsReader) Read(fd uintptr) int32 {
|
||||||
// On the first invocation, we return -1 to indicate "not ready"
|
|
||||||
// to make rawConn.Read wait for readability using the runtime's own mechanism
|
|
||||||
// because syscall.WSARecv() is a blocking call when used with nil OVERLAPPED
|
|
||||||
if !r.ready {
|
|
||||||
r.ready = true
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
var nBytes uint32
|
var nBytes uint32
|
||||||
var flags uint32
|
var flags uint32
|
||||||
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
|
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
|
||||||
|
|||||||
@@ -118,9 +118,7 @@ func (w *BufferedWriter) Write(b []byte) (int, error) {
|
|||||||
|
|
||||||
nBytes, err := w.buffer.Write(b)
|
nBytes, err := w.buffer.Write(b)
|
||||||
totalBytes += nBytes
|
totalBytes += nBytes
|
||||||
|
if err != nil {
|
||||||
// ErrBufferFull means a partial write, so flush below and continue
|
|
||||||
if err != nil && err != ErrBufferFull {
|
|
||||||
return totalBytes, err
|
return totalBytes, err
|
||||||
}
|
}
|
||||||
if !w.buffered || w.buffer.IsFull() {
|
if !w.buffered || w.buffer.IsFull() {
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import (
|
|||||||
|
|
||||||
// [,)
|
// [,)
|
||||||
func RandBetween(from int64, to int64) int64 {
|
func RandBetween(from int64, to int64) int64 {
|
||||||
|
if from == to {
|
||||||
|
return from
|
||||||
|
}
|
||||||
if from > to {
|
if from > to {
|
||||||
from, to = to, from
|
from, to = to, from
|
||||||
}
|
}
|
||||||
if d := to - from; d == 0 || d == 1 {
|
|
||||||
return from
|
|
||||||
}
|
|
||||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
||||||
return from + bigInt.Int64()
|
return from + bigInt.Int64()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
package quic
|
package quic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
_ "crypto/tls"
|
_ "crypto/tls"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CipherSuiteTLS13 struct {
|
||||||
|
ID uint16
|
||||||
|
KeyLen int
|
||||||
|
AEAD func(key, fixedNonce []byte) cipher.AEAD
|
||||||
|
Hash crypto.Hash
|
||||||
|
}
|
||||||
|
|
||||||
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
||||||
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package quic
|
|||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@@ -27,43 +28,22 @@ func (s SniffHeader) Domain() string {
|
|||||||
return s.domain
|
return s.domain
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
const (
|
||||||
errNotQUIC = errors.New("not quic")
|
versionDraft29 uint32 = 0xff00001d
|
||||||
errNotQUICInitial = errors.New("not initial packet")
|
version1 uint32 = 0x1
|
||||||
)
|
)
|
||||||
|
|
||||||
type quicVersionSpec struct {
|
|
||||||
ver uint32
|
|
||||||
typeInitial byte
|
|
||||||
initialSalt []byte
|
|
||||||
labelPrefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
quicDraft29 = quicVersionSpec{
|
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||||
ver: 0xff00001d,
|
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||||
typeInitial: 0b00,
|
initialSuite = &CipherSuiteTLS13{
|
||||||
initialSalt: []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99},
|
ID: tls.TLS_AES_128_GCM_SHA256,
|
||||||
labelPrefix: "quic",
|
KeyLen: 16,
|
||||||
}
|
AEAD: AEADAESGCMTLS13,
|
||||||
quicV1 = quicVersionSpec{
|
Hash: crypto.SHA256,
|
||||||
ver: 0x1,
|
|
||||||
typeInitial: 0b00,
|
|
||||||
initialSalt: []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a},
|
|
||||||
labelPrefix: "quic",
|
|
||||||
}
|
|
||||||
quicV2 = quicVersionSpec{
|
|
||||||
ver: 0x6b3343cf,
|
|
||||||
typeInitial: 0b01,
|
|
||||||
initialSalt: []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9},
|
|
||||||
labelPrefix: "quicv2",
|
|
||||||
}
|
|
||||||
|
|
||||||
quicVersionSpecMap = map[uint32]*quicVersionSpec{
|
|
||||||
quicDraft29.ver: &quicDraft29,
|
|
||||||
quicV1.ver: &quicV1,
|
|
||||||
quicV2.ver: &quicV2,
|
|
||||||
}
|
}
|
||||||
|
errNotQuic = errors.New("not quic")
|
||||||
|
errNotQuicInitial = errors.New("not initial packet")
|
||||||
)
|
)
|
||||||
|
|
||||||
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||||
@@ -83,61 +63,60 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
buffer := buf.FromBytes(b)
|
buffer := buf.FromBytes(b)
|
||||||
typeByte, err := buffer.ReadByte()
|
typeByte, err := buffer.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
isLongHeader := typeByte&0x80 > 0
|
isLongHeader := typeByte&0x80 > 0
|
||||||
if !isLongHeader || typeByte&0x40 == 0 {
|
if !isLongHeader || typeByte&0x40 == 0 {
|
||||||
return nil, errNotQUICInitial
|
return nil, errNotQuicInitial
|
||||||
}
|
}
|
||||||
|
|
||||||
vb, err := buffer.ReadBytes(4)
|
vb, err := buffer.ReadBytes(4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
versionNumber := binary.BigEndian.Uint32(vb)
|
versionNumber := binary.BigEndian.Uint32(vb)
|
||||||
var s *quicVersionSpec
|
if versionNumber != 0 && typeByte&0x40 == 0 {
|
||||||
if v, ok := quicVersionSpecMap[versionNumber]; ok {
|
return nil, errNotQuic
|
||||||
s = v
|
} else if versionNumber != versionDraft29 && versionNumber != version1 {
|
||||||
} else {
|
return nil, errNotQuic
|
||||||
return nil, errNotQUIC
|
|
||||||
}
|
|
||||||
|
|
||||||
var destConnID []byte
|
|
||||||
if l, err := buffer.ReadByte(); err != nil {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
}
|
|
||||||
|
|
||||||
if l, err := buffer.ReadByte(); err != nil {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
}
|
}
|
||||||
|
|
||||||
packetType := (typeByte & 0x30) >> 4
|
packetType := (typeByte & 0x30) >> 4
|
||||||
isQUICInitial := packetType == s.typeInitial
|
isQuicInitial := packetType == 0x0
|
||||||
|
|
||||||
if isQUICInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
var destConnID []byte
|
||||||
tokenLen, err := readShortQUICVarint(buffer)
|
if l, err := buffer.ReadByte(); err != nil {
|
||||||
|
return nil, errNotQuic
|
||||||
|
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
||||||
|
return nil, errNotQuic
|
||||||
|
}
|
||||||
|
|
||||||
|
if l, err := buffer.ReadByte(); err != nil {
|
||||||
|
return nil, errNotQuic
|
||||||
|
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
||||||
|
return nil, errNotQuic
|
||||||
|
}
|
||||||
|
|
||||||
|
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
||||||
|
tokenLen, err := readShortQuicVarint(buffer)
|
||||||
if err != nil || tokenLen > int32(len(b)) {
|
if err != nil || tokenLen > int32(len(b)) {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packetLen, err := readShortQUICVarint(buffer)
|
packetLen, err := readShortQuicVarint(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
// packetLen is impossible to be shorter than this
|
// packetLen is impossible to be shorter than this
|
||||||
if packetLen < 4 {
|
if packetLen < 4 {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
hdrLen := len(b) - int(buffer.Len())
|
hdrLen := len(b) - int(buffer.Len())
|
||||||
@@ -146,22 +125,26 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restPayload := b[hdrLen+int(packetLen):]
|
restPayload := b[hdrLen+int(packetLen):]
|
||||||
if !isQUICInitial { // Skip this packet if it's not initial packet
|
if !isQuicInitial { // Skip this packet if it's not initial packet
|
||||||
b = restPayload
|
b = restPayload
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
salt := s.initialSalt
|
var salt []byte
|
||||||
label := s.labelPrefix
|
if versionNumber == version1 {
|
||||||
|
salt = quicSalt
|
||||||
|
} else {
|
||||||
|
salt = quicSaltOld
|
||||||
|
}
|
||||||
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
||||||
secret := hkdfExpandLabel(initialSecret, "client in", crypto.SHA256.Size())
|
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
|
||||||
hpKey := hkdfExpandLabel(secret, label+" hp", 16)
|
hpKey := hkdfExpandLabel(initialSuite.Hash, secret, []byte{}, "quic hp", initialSuite.KeyLen)
|
||||||
block, err := aes.NewCipher(hpKey)
|
block, err := aes.NewCipher(hpKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(b) < hdrLen+4+block.BlockSize() {
|
if len(b) < hdrLen+4+block.BlockSize() {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
cache.Clear()
|
cache.Clear()
|
||||||
mask := cache.Extend(int32(block.BlockSize()))
|
mask := cache.Extend(int32(block.BlockSize()))
|
||||||
@@ -172,8 +155,8 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
b[hdrLen+i] ^= mask[i+1]
|
b[hdrLen+i] ^= mask[i+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
key := hkdfExpandLabel(secret, label+" key", 16)
|
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
||||||
iv := hkdfExpandLabel(secret, label+" iv", 12)
|
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
||||||
cipher := AEADAESGCMTLS13(key, iv)
|
cipher := AEADAESGCMTLS13(key, iv)
|
||||||
|
|
||||||
nonce := cache.Extend(int32(cipher.NonceSize()))
|
nonce := cache.Extend(int32(cipher.NonceSize()))
|
||||||
@@ -198,44 +181,44 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
case 0x00: // PADDING frame
|
case 0x00: // PADDING frame
|
||||||
case 0x01: // PING frame
|
case 0x01: // PING frame
|
||||||
case 0x02, 0x03: // ACK frame
|
case 0x02, 0x03: // ACK frame
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Largest Acknowledged
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Largest Acknowledged
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Delay
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Delay
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
ackRangeCount, err := readShortQUICVarint(buffer) // Field: ACK Range Count
|
ackRangeCount, err := readShortQuicVarint(buffer) // Field: ACK Range Count
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: First ACK Range
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: First ACK Range
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if frameType == 0x03 {
|
if frameType == 0x03 {
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
if _, err = readShortQuicVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 0x06: // CRYPTO frame, we will use this frame
|
case 0x06: // CRYPTO frame, we will use this frame
|
||||||
offset, err := readShortQUICVarint(buffer) // Field: Offset
|
offset, err := readShortQuicVarint(buffer) // Field: Offset
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
length, err := readShortQUICVarint(buffer) // Field: Length
|
length, err := readShortQuicVarint(buffer) // Field: Length
|
||||||
if err != nil || length > buffer.Len() {
|
if err != nil || length > buffer.Len() {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
@@ -251,13 +234,13 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Error Code
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Error Code
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Frame Type
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Frame Type
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
length, err := readShortQUICVarint(buffer) // Field: Reason Phrase Length
|
length, err := readShortQuicVarint(buffer) // Field: Reason Phrase Length
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
@@ -267,7 +250,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
default:
|
default:
|
||||||
// Only above frame types are permitted in initial packet.
|
// Only above frame types are permitted in initial packet.
|
||||||
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
||||||
return nil, errNotQUICInitial
|
return nil, errNotQuicInitial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,33 +268,35 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, protocol.ErrProtoNeedMoreData
|
return nil, protocol.ErrProtoNeedMoreData
|
||||||
}
|
}
|
||||||
|
|
||||||
func hkdfExpandLabel(secret []byte, label string, length int) []byte {
|
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
||||||
b := make([]byte, 0, 2+1+6+len(label)+1)
|
b := make([]byte, 3, 3+6+len(label)+1+len(context))
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(length))
|
binary.BigEndian.PutUint16(b, uint16(length))
|
||||||
b = append(b, byte(6+len(label)))
|
b[2] = uint8(6 + len(label))
|
||||||
b = append(b, "tls13 "...)
|
b = append(b, []byte("tls13 ")...)
|
||||||
b = append(b, label...)
|
b = append(b, []byte(label)...)
|
||||||
b = append(b, 0) // context
|
b = b[:3+6+len(label)+1]
|
||||||
|
b[3+6+len(label)] = uint8(len(context))
|
||||||
|
b = append(b, context...)
|
||||||
|
|
||||||
out := make([]byte, length)
|
out := make([]byte, length)
|
||||||
n, err := hkdf.Expand(crypto.SHA256.New, secret, b).Read(out)
|
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
|
||||||
if err != nil || n != length {
|
if err != nil || n != length {
|
||||||
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// readShortQUICVarint wraps quicvarint.Read with a max limit for length related fields.
|
// readShortQuicVarint wraps quicvarint.Read with a max limit for length related fields.
|
||||||
// we only handle QUIC Initial so these numbers should not exceed 65535
|
// we only handle QUIC Initial so these numbers should not exceed 65535
|
||||||
// returns int32 to reduce type conversion
|
// returns int32 to reduce type conversion
|
||||||
func readShortQUICVarint(reader io.ByteReader) (int32, error) {
|
func readShortQuicVarint(reader io.ByteReader) (int32, error) {
|
||||||
v, err := quicvarint.Read(reader)
|
v, err := quicvarint.Read(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if v > 65535 {
|
if v > 65535 {
|
||||||
// not used(
|
// not used(
|
||||||
return 0, errNotQUICInitial
|
return 0, errNotQuicInitial
|
||||||
}
|
}
|
||||||
return int32(v), nil
|
return int32(v), nil
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -70,6 +70,8 @@ type Outbound struct {
|
|||||||
Tag string
|
Tag string
|
||||||
// Name of the outbound proxy that handles the connection.
|
// Name of the outbound proxy that handles the connection.
|
||||||
Name string
|
Name string
|
||||||
|
// Unused. Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings
|
||||||
|
Conn net.Conn
|
||||||
// CanSpliceCopy is a property for this connection
|
// CanSpliceCopy is a property for this connection
|
||||||
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
|
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
|
||||||
CanSpliceCopy int
|
CanSpliceCopy int
|
||||||
|
|||||||
+2
-2
@@ -19,8 +19,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Version_x byte = 26
|
Version_x byte = 26
|
||||||
Version_y byte = 9
|
Version_y byte = 7
|
||||||
Version_z byte = 9
|
Version_z byte = 28
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
module github.com/xtls/xray-core
|
module github.com/xtls/xray-core
|
||||||
|
|
||||||
go 1.27
|
go 1.26
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
|
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
|
||||||
github.com/cloudflare/circl v1.6.5
|
github.com/cloudflare/circl v1.6.5
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
@@ -11,7 +11,6 @@ require (
|
|||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0
|
github.com/klauspost/cpuid/v2 v2.4.0
|
||||||
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138
|
|
||||||
github.com/miekg/dns v1.1.73
|
github.com/miekg/dns v1.1.73
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pion/stun/v3 v3.1.7
|
github.com/pion/stun/v3 v3.1.7
|
||||||
@@ -22,34 +21,29 @@ require (
|
|||||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/stretchr/testify v1.12.1
|
github.com/stretchr/testify v1.12.1
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0
|
github.com/xtls/reality v0.0.0-20260827183302-8530a57042be
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.55.0
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
golang.org/x/net v0.59.0
|
golang.org/x/net v0.58.0
|
||||||
golang.org/x/sync v0.23.0
|
golang.org/x/sync v0.22.0
|
||||||
golang.org/x/sys v0.48.0
|
golang.org/x/sys v0.47.0
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||||
golang.zx2c4.com/wireguard/windows v1.0.1
|
golang.zx2c4.com/wireguard/windows v1.0.1
|
||||||
google.golang.org/grpc v1.83.2
|
google.golang.org/grpc v1.83.1
|
||||||
google.golang.org/protobuf v1.36.12
|
google.golang.org/protobuf v1.36.12
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
lukechampine.com/blake3 v1.4.1
|
lukechampine.com/blake3 v1.4.1
|
||||||
mvdan.cc/gofumpt v0.12.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/gopacket v1.1.19 // indirect
|
|
||||||
github.com/huin/goupnp v1.2.0 // indirect
|
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
|
||||||
github.com/juju/ratelimit v1.0.2 // indirect
|
github.com/juju/ratelimit v1.0.2 // indirect
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/koron/go-ssdp v0.0.4 // indirect
|
github.com/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/dtls/v3 v3.1.5 // indirect
|
||||||
github.com/pion/logging v0.2.4 // indirect
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
github.com/pion/transport/v4 v4.1.0 // indirect
|
github.com/pion/transport/v4 v4.1.0 // indirect
|
||||||
@@ -57,9 +51,8 @@ require (
|
|||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
golang.org/x/text v0.42.0 // indirect
|
golang.org/x/text v0.41.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.49.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
|
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkdYbw5JPGwBqIHs2J8tiEP7Py9lPqkN2I=
|
||||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
|
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA=
|
github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA=
|
||||||
github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c=
|
github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
|
|
||||||
github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
|
|
||||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
@@ -22,34 +21,22 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
|||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
|
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
|
||||||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
|
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
|
||||||
github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY=
|
|
||||||
github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
|
||||||
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||||
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
|
||||||
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 h1:YohuNPT/1k3VcThCQlBZ43PCPWPfMRS1zcxWBF2SLK8=
|
|
||||||
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138/go.mod h1:TXQg5tfSy+bUjnhT5728j5j/MBj7keIYqqZ1+8k/ui8=
|
|
||||||
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
|
|
||||||
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
|
|
||||||
github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=
|
github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=
|
||||||
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
|
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
@@ -66,16 +53,14 @@ github.com/pion/transport/v4 v4.1.0 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97B
|
|||||||
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||||
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI=
|
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI=
|
||||||
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
||||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
|
||||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
|
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||||
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||||
@@ -88,8 +73,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
|
|||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0 h1:rb+fKQFhz+5I2PPuQsNYxI5mUU840XWYtRF0ZBjvkws=
|
github.com/xtls/reality v0.0.0-20260827183302-8530a57042be h1:0MCMg+ylSR2kIWtRUgMH+1zk+lRksbH7pIY4lGHSFKY=
|
||||||
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
|
github.com/xtls/reality v0.0.0-20260827183302-8530a57042be/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
@@ -111,22 +96,20 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -134,22 +117,19 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
|
||||||
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -163,8 +143,8 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
|||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
|
||||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -179,5 +159,3 @@ h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
|||||||
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho=
|
|
||||||
mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA=
|
|
||||||
|
|||||||
+30
-20
@@ -1,42 +1,52 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/json"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResponseConfig struct {
|
type NoneResponse struct{}
|
||||||
Type string `json:"type"`
|
|
||||||
CustomResponseData string `json:"customResponseData"`
|
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 BlackholeConfig struct {
|
type BlackholeConfig struct {
|
||||||
Response *ResponseConfig `json:"response"`
|
Response json.RawMessage `json:"response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *BlackholeConfig) Build() (proto.Message, error) {
|
func (v *BlackholeConfig) Build() (proto.Message, error) {
|
||||||
config := new(blackhole.Config)
|
config := new(blackhole.Config)
|
||||||
if v.Response != nil {
|
if v.Response != nil {
|
||||||
responseName := strings.ToLower(v.Response.Type)
|
response, _, err := configLoader.Load(v.Response)
|
||||||
switch responseName {
|
if err != nil {
|
||||||
case "none", "":
|
return nil, errors.New("Config: Failed to parse Blackhole response config.").Base(err)
|
||||||
config.Response = &blackhole.Response{Type: "none"}
|
|
||||||
case "http":
|
|
||||||
config.Response = &blackhole.Response{Type: "http"}
|
|
||||||
case "custom":
|
|
||||||
data, err := base64.StdEncoding.DecodeString(v.Response.CustomResponseData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to decode custom response data: " + err.Error())
|
|
||||||
}
|
|
||||||
config.Response = &blackhole.Response{Type: "custom", CustomResponseData: data}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown blackhole response: " + responseName)
|
|
||||||
}
|
}
|
||||||
|
responseSettings, err := response.(Buildable).Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Response = serial.ToTypedMessage(responseSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var configLoader = NewJSONConfigLoader(
|
||||||
|
ConfigCreatorCache{
|
||||||
|
"none": func() interface{} { return new(NoneResponse) },
|
||||||
|
"http": func() interface{} { return new(HTTPResponse) },
|
||||||
|
},
|
||||||
|
"type",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package conf_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
)
|
)
|
||||||
@@ -21,7 +22,7 @@ func TestHTTPResponseJSON(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
Parser: loadJSON(creator),
|
Parser: loadJSON(creator),
|
||||||
Output: &blackhole.Config{
|
Output: &blackhole.Config{
|
||||||
Response: &blackhole.Response{Type: "http"},
|
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -31,27 +32,3 @@ 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"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (o *ObservatoryConfig) Build() (proto.Message, error) {
|
|||||||
type BurstObservatoryConfig struct {
|
type BurstObservatoryConfig struct {
|
||||||
SubjectSelector []string `json:"subjectSelector"`
|
SubjectSelector []string `json:"subjectSelector"`
|
||||||
// health check settings
|
// health check settings
|
||||||
HealthCheck *HealthCheckSettings `json:"pingConfig,omitempty"`
|
HealthCheck *healthCheckSettings `json:"pingConfig,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BurstObservatoryConfig) Build() (proto.Message, error) {
|
func (b BurstObservatoryConfig) Build() (proto.Message, error) {
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ type strategyLeastLoadConfig struct {
|
|||||||
Tolerance float64 `json:"tolerance,omitempty"`
|
Tolerance float64 `json:"tolerance,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthCheckSettings holds settings for health Checker
|
// healthCheckSettings holds settings for health Checker
|
||||||
type HealthCheckSettings struct {
|
type healthCheckSettings struct {
|
||||||
Destination string `json:"destination"`
|
Destination string `json:"destination"`
|
||||||
Connectivity string `json:"connectivity"`
|
Connectivity string `json:"connectivity"`
|
||||||
Interval duration.Duration `json:"interval"`
|
Interval duration.Duration `json:"interval"`
|
||||||
@@ -53,7 +53,7 @@ type HealthCheckSettings struct {
|
|||||||
HttpMethod string `json:"httpMethod"`
|
HttpMethod string `json:"httpMethod"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HealthCheckSettings) Build() (proto.Message, error) {
|
func (h healthCheckSettings) Build() (proto.Message, error) {
|
||||||
var httpMethod string
|
var httpMethod string
|
||||||
if h.HttpMethod == "" {
|
if h.HttpMethod == "" {
|
||||||
httpMethod = "HEAD"
|
httpMethod = "HEAD"
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
|
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
|
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/udphop"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
|
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
|
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/xmc"
|
"github.com/xtls/xray-core/transport/internet/finalmask/xmc"
|
||||||
@@ -84,7 +83,6 @@ var (
|
|||||||
"xdns": func() interface{} { return new(Xdns) },
|
"xdns": func() interface{} { return new(Xdns) },
|
||||||
"xicmp": func() interface{} { return new(Xicmp) },
|
"xicmp": func() interface{} { return new(Xicmp) },
|
||||||
"realm": func() interface{} { return new(Realm) },
|
"realm": func() interface{} { return new(Realm) },
|
||||||
"udphop": func() interface{} { return new(UDPHop) },
|
|
||||||
}, "type", "settings")
|
}, "type", "settings")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -818,11 +816,9 @@ func (c *Xicmp) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Realm struct {
|
type Realm struct {
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
StunServers []string `json:"stunServers"`
|
StunServers []string `json:"stunServers"`
|
||||||
TlsConfig *TLSConfig `json:"tlsConfig"`
|
TlsConfig *TLSConfig `json:"tlsConfig"`
|
||||||
IPMode string `json:"ipMode"`
|
|
||||||
PortMapping *realm.PortMapping `json:"portMapping"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Realm) Build() (proto.Message, error) {
|
func (c *Realm) Build() (proto.Message, error) {
|
||||||
@@ -902,54 +898,6 @@ func (c *Realm) Build() (proto.Message, error) {
|
|||||||
ID: id,
|
ID: id,
|
||||||
StunServers: stunServers,
|
StunServers: stunServers,
|
||||||
TlsConfig: tlsConfig,
|
TlsConfig: tlsConfig,
|
||||||
IPMode: strings.ToLower(c.IPMode),
|
|
||||||
PortMapping: c.PortMapping,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UDPHop struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Interval Int32Range `json:"interval"`
|
|
||||||
RemoteIPs []string `json:"remoteIPs"`
|
|
||||||
RemotePorts PortList `json:"remotePorts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UDPHop) Build() (proto.Message, error) {
|
|
||||||
var local, remote, remoteOnce bool
|
|
||||||
for _, mode := range strings.Split(c.Mode, ",") {
|
|
||||||
switch strings.ToLower(mode) {
|
|
||||||
case "intervallocal":
|
|
||||||
local = true
|
|
||||||
case "intervalremote":
|
|
||||||
remote = true
|
|
||||||
case "perconnremote":
|
|
||||||
remoteOnce = true
|
|
||||||
default:
|
|
||||||
return nil, errors.New("invalid mode ", mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var remoteIPs []string
|
|
||||||
for _, ip := range c.RemoteIPs {
|
|
||||||
prefix, err := netip.ParsePrefix(ip)
|
|
||||||
if err == nil {
|
|
||||||
remoteIPs = append(remoteIPs, prefix.String())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addr, err := netip.ParseAddr(ip)
|
|
||||||
if err == nil {
|
|
||||||
remoteIPs = append(remoteIPs, netip.PrefixFrom(addr, addr.BitLen()).String())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, errors.New("invalid ip ", ip)
|
|
||||||
}
|
|
||||||
return &udphop.Config{
|
|
||||||
Local: local,
|
|
||||||
Remote: remote,
|
|
||||||
RemoteOnce: remoteOnce,
|
|
||||||
IntervalMin: int64(c.Interval.From),
|
|
||||||
IntervalMax: int64(c.Interval.To),
|
|
||||||
RemoteIPs: remoteIPs,
|
|
||||||
RemotePorts: c.RemotePorts.Build().Ports(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,23 +928,20 @@ func (c *Mask) Build(tcp bool) (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type QuicParamsConfig struct {
|
type QuicParamsConfig struct {
|
||||||
Congestion string `json:"congestion"`
|
Congestion string `json:"congestion"`
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
BbrProfile string `json:"bbrProfile"`
|
BbrProfile string `json:"bbrProfile"`
|
||||||
BrutalUp Bandwidth `json:"brutalUp"`
|
BrutalUp Bandwidth `json:"brutalUp"`
|
||||||
BrutalDown Bandwidth `json:"brutalDown"`
|
BrutalDown Bandwidth `json:"brutalDown"`
|
||||||
BrutalDisableLossCompensation bool `json:"brutalDisableLossCompensation"`
|
UdpHop UdpHop `json:"udpHop"`
|
||||||
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
|
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
|
||||||
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
|
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
|
||||||
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
|
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
|
||||||
MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"`
|
MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"`
|
||||||
MaxIdleTimeout int64 `json:"maxIdleTimeout"`
|
MaxIdleTimeout int64 `json:"maxIdleTimeout"`
|
||||||
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
|
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
|
||||||
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"`
|
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"`
|
||||||
DisableChromeParrot bool `json:"disableChromeParrot"`
|
MaxIncomingStreams int64 `json:"maxIncomingStreams"`
|
||||||
DisableGSO bool `json:"disableGSO"`
|
|
||||||
MaxIncomingStreams int64 `json:"maxIncomingStreams"`
|
|
||||||
DisableStatelessReset bool `json:"disableStatelessReset"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FinalMask struct {
|
type FinalMask struct {
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ func (p TransportProtocol) Build() (string, error) {
|
|||||||
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
|
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
|
||||||
case "hysteria":
|
case "hysteria":
|
||||||
return "hysteria", nil
|
return "hysteria", nil
|
||||||
case "xdrive":
|
|
||||||
return "xdrive", nil
|
|
||||||
default:
|
default:
|
||||||
return "", errors.New("Config: unknown transport protocol: ", p)
|
return "", errors.New("Config: unknown transport protocol: ", p)
|
||||||
}
|
}
|
||||||
@@ -61,7 +59,6 @@ type StreamConfig struct {
|
|||||||
WSSettings *WebSocketConfig `json:"wsSettings"`
|
WSSettings *WebSocketConfig `json:"wsSettings"`
|
||||||
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
|
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
|
||||||
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
|
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
|
||||||
XDRIVESettings *XDriveConfig `json:"xdriveSettings"`
|
|
||||||
SocketSettings *SocketConfig `json:"sockopt"`
|
SocketSettings *SocketConfig `json:"sockopt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,16 +192,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
Settings: serial.ToTypedMessage(hs),
|
Settings: serial.ToTypedMessage(hs),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if c.XDRIVESettings != nil {
|
|
||||||
xs, err := c.XDRIVESettings.Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Failed to build XDRIVE config.").Base(err)
|
|
||||||
}
|
|
||||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
||||||
ProtocolName: "xdrive",
|
|
||||||
Settings: serial.ToTypedMessage(xs),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if c.SocketSettings != nil {
|
if c.SocketSettings != nil {
|
||||||
ss, err := c.SocketSettings.Build()
|
ss, err := c.SocketSettings.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -266,6 +253,10 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
|
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c.FinalMask.QuicParams.UdpHop.Interval.From != 0 && c.FinalMask.QuicParams.UdpHop.Interval.From < 5) || (c.FinalMask.QuicParams.UdpHop.Interval.To != 0 && c.FinalMask.QuicParams.UdpHop.Interval.To < 5) {
|
||||||
|
return nil, errors.New("Interval must be at least 5")
|
||||||
|
}
|
||||||
|
|
||||||
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
|
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
|
||||||
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
|
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
|
||||||
}
|
}
|
||||||
@@ -294,25 +285,44 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.QuicParams = &internet.QuicParams{
|
config.QuicParams = &internet.QuicParams{
|
||||||
Congestion: c.FinalMask.QuicParams.Congestion,
|
Congestion: c.FinalMask.QuicParams.Congestion,
|
||||||
BbrProfile: profile,
|
BbrProfile: profile,
|
||||||
BrutalUp: up,
|
BrutalUp: up,
|
||||||
BrutalDown: down,
|
BrutalDown: down,
|
||||||
BrutalDisableLossCompensation: c.FinalMask.QuicParams.BrutalDisableLossCompensation,
|
UdpHop: &internet.UdpHop{
|
||||||
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
|
Ports: c.FinalMask.QuicParams.UdpHop.PortList.Build().Ports(),
|
||||||
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
|
IntervalMin: int64(c.FinalMask.QuicParams.UdpHop.Interval.From),
|
||||||
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
|
IntervalMax: int64(c.FinalMask.QuicParams.UdpHop.Interval.To),
|
||||||
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
|
},
|
||||||
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
|
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
|
||||||
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
|
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
|
||||||
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
|
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
|
||||||
DisableChromeParrot: c.FinalMask.QuicParams.DisableChromeParrot,
|
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
|
||||||
DisableGSO: c.FinalMask.QuicParams.DisableGSO,
|
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
|
||||||
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
|
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
|
||||||
DisableStatelessReset: c.FinalMask.QuicParams.DisableStatelessReset,
|
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
|
||||||
|
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProxyConfig struct {
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
|
||||||
|
// TransportLayerProxy: For compatibility.
|
||||||
|
TransportLayerProxy bool `json:"transportLayer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build implements Buildable.
|
||||||
|
func (v *ProxyConfig) Build() (*internet.ProxyConfig, error) {
|
||||||
|
if v.Tag == "" {
|
||||||
|
return nil, errors.New("Proxy tag is not set.")
|
||||||
|
}
|
||||||
|
return &internet.ProxyConfig{
|
||||||
|
Tag: v.Tag,
|
||||||
|
TransportLayerProxy: v.TransportLayerProxy,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -23,7 +24,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport/internet/splithttp"
|
"github.com/xtls/xray-core/transport/internet/splithttp"
|
||||||
"github.com/xtls/xray-core/transport/internet/tcp"
|
"github.com/xtls/xray-core/transport/internet/tcp"
|
||||||
"github.com/xtls/xray-core/transport/internet/websocket"
|
"github.com/xtls/xray-core/transport/internet/websocket"
|
||||||
"github.com/xtls/xray-core/transport/internet/xdrive"
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -534,6 +534,10 @@ type KCPConfig struct {
|
|||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
func (c *KCPConfig) Build() (proto.Message, error) {
|
func (c *KCPConfig) Build() (proto.Message, error) {
|
||||||
|
if c.HeaderConfig != nil || c.Seed != nil {
|
||||||
|
return nil, errors.PrintRemovedFeatureError("mkcp header & seed", "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm")
|
||||||
|
}
|
||||||
|
|
||||||
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
|
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
|
||||||
|
|
||||||
if c.Mtu != nil {
|
if c.Mtu != nil {
|
||||||
@@ -556,16 +560,16 @@ func (c *KCPConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Mtu < 21 {
|
if config.Mtu < 21 {
|
||||||
return nil, errors.New("MTU must be at least 21")
|
return nil, errors.New("Mtu must be at least 21").AtError()
|
||||||
}
|
}
|
||||||
if config.Tti < 10 || config.Tti > 1000 {
|
if config.Tti < 10 || config.Tti > 1000 {
|
||||||
return nil, errors.New("TTI must be between 10 and 1000")
|
return nil, errors.New("invalid mKCP TTI: ", c.Tti).AtError()
|
||||||
}
|
}
|
||||||
if config.CwndMultiplier < 1 {
|
if config.CwndMultiplier < 1 {
|
||||||
return nil, errors.New("CwndMultiplier must be at least 1")
|
return nil, errors.New("CwndMultiplier must be at least 1").AtError()
|
||||||
}
|
}
|
||||||
if config.GetSendingBufferSize() == 0 {
|
if config.GetSendingBufferSize() == 0 {
|
||||||
return nil, errors.New("MaxSendingWindow must be at least ", config.Mtu)
|
return nil, errors.New("MaxSendingWindow must be >= Mtu").AtError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
@@ -735,6 +739,11 @@ func (b Bandwidth) Bps() (uint64, error) {
|
|||||||
return uint64(val*float64(mul)) / 8, nil
|
return uint64(val*float64(mul)) / 8, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UdpHop struct {
|
||||||
|
PortList PortList `json:"ports"`
|
||||||
|
Interval Int32Range `json:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
type Masquerade struct {
|
type Masquerade struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
@@ -742,7 +751,6 @@ type Masquerade struct {
|
|||||||
|
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
RewriteHost bool `json:"rewriteHost"`
|
RewriteHost bool `json:"rewriteHost"`
|
||||||
XForwarded bool `json:"xForwarded"`
|
|
||||||
Insecure bool `json:"insecure"`
|
Insecure bool `json:"insecure"`
|
||||||
|
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
@@ -751,8 +759,14 @@ type Masquerade struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HysteriaConfig struct {
|
type HysteriaConfig struct {
|
||||||
Version int32 `json:"version"`
|
Version int32 `json:"version"`
|
||||||
Auth string `json:"auth"`
|
Auth string `json:"auth"`
|
||||||
|
|
||||||
|
Congestion *string `json:"congestion"`
|
||||||
|
Up *Bandwidth `json:"up"`
|
||||||
|
Down *Bandwidth `json:"down"`
|
||||||
|
UdpHop *UdpHop `json:"udphop"`
|
||||||
|
|
||||||
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
|
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
|
||||||
Masquerade Masquerade `json:"masquerade"`
|
Masquerade Masquerade `json:"masquerade"`
|
||||||
}
|
}
|
||||||
@@ -762,6 +776,10 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
|||||||
return nil, errors.New("version != 2")
|
return nil, errors.New("version != 2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {
|
||||||
|
errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams")
|
||||||
|
}
|
||||||
|
|
||||||
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
|
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
|
||||||
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
|
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
|
||||||
}
|
}
|
||||||
@@ -773,7 +791,6 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
|||||||
config.MasqFile = c.Masquerade.Dir
|
config.MasqFile = c.Masquerade.Dir
|
||||||
config.MasqUrl = c.Masquerade.Url
|
config.MasqUrl = c.Masquerade.Url
|
||||||
config.MasqUrlRewriteHost = c.Masquerade.RewriteHost
|
config.MasqUrlRewriteHost = c.Masquerade.RewriteHost
|
||||||
config.MasqUrlXForwarded = c.Masquerade.XForwarded
|
|
||||||
config.MasqUrlInsecure = c.Masquerade.Insecure
|
config.MasqUrlInsecure = c.Masquerade.Insecure
|
||||||
config.MasqString = c.Masquerade.Content
|
config.MasqString = c.Masquerade.Content
|
||||||
config.MasqStringHeaders = c.Masquerade.Headers
|
config.MasqStringHeaders = c.Masquerade.Headers
|
||||||
@@ -795,50 +812,3 @@ func readFileOrString(f string, s []string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return nil, errors.New("both file and bytes are empty.")
|
return nil, errors.New("both file and bytes are empty.")
|
||||||
}
|
}
|
||||||
|
|
||||||
type XDriveConfig struct {
|
|
||||||
RemoteFolder string `json:"remoteFolder"`
|
|
||||||
Service string `json:"service"`
|
|
||||||
Secrets []string `json:"secrets"`
|
|
||||||
SegmentBytes uint32 `json:"segmentBytes"`
|
|
||||||
FlushIntervalMs uint32 `json:"flushIntervalMs"`
|
|
||||||
PollIntervalMs uint32 `json:"pollIntervalMs"`
|
|
||||||
MaxPollIntervalMs uint32 `json:"maxPollIntervalMs"`
|
|
||||||
SessionTTLSeconds uint32 `json:"sessionTtlSeconds"`
|
|
||||||
Concurrency uint32 `json:"concurrency"`
|
|
||||||
EagerWindowMs uint32 `json:"eagerWindowMs"`
|
|
||||||
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
|
|
||||||
Template json.RawMessage `json:"template"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build implements Buildable.
|
|
||||||
func (c *XDriveConfig) Build() (proto.Message, error) {
|
|
||||||
switch c.Service {
|
|
||||||
case "local":
|
|
||||||
case "Google Drive":
|
|
||||||
if len(c.Secrets) != 3 {
|
|
||||||
return nil, errors.New("Google Drive needs 3 secrets in order of ClientID, ClientSecret, RefreshToken")
|
|
||||||
}
|
|
||||||
case "template":
|
|
||||||
if len(c.Template) == 0 {
|
|
||||||
return nil, errors.New(`service "template" needs a "template" object`)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unsupported service")
|
|
||||||
}
|
|
||||||
config := &xdrive.Config{
|
|
||||||
RemoteFolder: c.RemoteFolder,
|
|
||||||
Service: c.Service,
|
|
||||||
Secrets: c.Secrets,
|
|
||||||
SegmentBytes: c.SegmentBytes,
|
|
||||||
FlushIntervalMs: c.FlushIntervalMs,
|
|
||||||
PollIntervalMs: c.PollIntervalMs,
|
|
||||||
MaxPollIntervalMs: c.MaxPollIntervalMs,
|
|
||||||
SessionTtlSeconds: c.SessionTTLSeconds,
|
|
||||||
Concurrency: c.Concurrency,
|
|
||||||
EagerWindowMs: c.EagerWindowMs,
|
|
||||||
HoleTimeoutMs: c.HoleTimeoutMs,
|
|
||||||
Template: string(c.Template),
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -113,10 +113,10 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
|
|||||||
config.MinClientVer[i] = byte(u)
|
config.MinClientVer[i] = byte(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// errors.LogWarning(context.Background(), `REALITY: Changing "minClientVer" will increase the likelihood of your server's IP being blocked by the GFW`)
|
errors.LogWarning(context.Background(), `REALITY: Changing "minClientVer" will increase the likelihood of your server's IP being blocked by the GFW`)
|
||||||
} else {
|
} else {
|
||||||
// config.MinClientVer = []byte{26, 3, 27} // change it at your own risk: https://github.com/XTLS/Xray-core/commit/af7eb68028732a8ee3c0e5d6ab2b8a657bb2e770
|
config.MinClientVer = []byte{26, 3, 27} // change it at your own risk: https://github.com/XTLS/Xray-core/commit/af7eb68028732a8ee3c0e5d6ab2b8a657bb2e770
|
||||||
// errors.LogWarning(context.Background(), `REALITY: The default minimal client version is Xray-core v26.3.27, other clients may be refused to connect`)
|
errors.LogWarning(context.Background(), `REALITY: The default minimal client version is Xray-core v26.3.27, other clients may be refused to connect`)
|
||||||
}
|
}
|
||||||
if c.MaxClientVer != "" {
|
if c.MaxClientVer != "" {
|
||||||
config.MaxClientVer = make([]byte, 3)
|
config.MaxClientVer = make([]byte, 3)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CustomSockoptConfig struct {
|
type CustomSockoptConfig struct {
|
||||||
System string `json:"system"`
|
Syetem string `json:"system"`
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
Opt string `json:"opt"`
|
Opt string `json:"opt"`
|
||||||
@@ -124,7 +124,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||||||
|
|
||||||
for _, copt := range c.CustomSockopt {
|
for _, copt := range c.CustomSockopt {
|
||||||
customSockopt := &internet.CustomSockopt{
|
customSockopt := &internet.CustomSockopt{
|
||||||
System: copt.System,
|
System: copt.Syetem,
|
||||||
Network: copt.Network,
|
Network: copt.Network,
|
||||||
Level: copt.Level,
|
Level: copt.Level,
|
||||||
Opt: copt.Opt,
|
Opt: copt.Opt,
|
||||||
|
|||||||
@@ -291,76 +291,3 @@ func TestHeaderCustomUDPBuildRejectsExprWithoutArgs(t *testing.T) {
|
|||||||
t.Fatalf("expected transform arg rejection, got %v", err)
|
t.Fatalf("expected transform arg rejection, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestXDriveStreamConfig(t *testing.T) {
|
|
||||||
config := new(StreamConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"method": "xdrive",
|
|
||||||
"xdriveSettings": {
|
|
||||||
"remoteFolder": "/tmp/xdrive",
|
|
||||||
"service": "local"
|
|
||||||
}
|
|
||||||
}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Build: %v", err)
|
|
||||||
}
|
|
||||||
if built.ProtocolName != "xdrive" {
|
|
||||||
t.Fatalf("ProtocolName is %q, want %q", built.ProtocolName, "xdrive")
|
|
||||||
}
|
|
||||||
if len(built.TransportSettings) != 1 || built.TransportSettings[0].ProtocolName != "xdrive" {
|
|
||||||
t.Fatalf("TransportSettings is %v, want a single xdrive entry", built.TransportSettings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXDriveRejectsUnknownService(t *testing.T) {
|
|
||||||
config := new(XDriveConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); err == nil {
|
|
||||||
t.Fatal("Build accepted an unsupported service")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXDriveTemplateStreamConfig(t *testing.T) {
|
|
||||||
config := new(StreamConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"method": "xdrive",
|
|
||||||
"xdriveSettings": {
|
|
||||||
"remoteFolder": "folder",
|
|
||||||
"service": "template",
|
|
||||||
"secrets": ["user", "pass"],
|
|
||||||
"template": {
|
|
||||||
"flatten": true,
|
|
||||||
"auth": {"type": "basic", "username": "{secret0}", "password": "{secret1}"},
|
|
||||||
"put": {"method": "PUT", "url": "https://dav.example/{folder}/{name}"},
|
|
||||||
"get": {"method": "GET", "url": "https://dav.example/{folder}/{name}"},
|
|
||||||
"delete": {"method": "DELETE", "url": "https://dav.example/{folder}/{name}"},
|
|
||||||
"list": {"method": "PROPFIND", "url": "https://dav.example/{folder}/", "namesRegex": "<d:href>/folder/([^<]+)</d:href>"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Build: %v", err)
|
|
||||||
}
|
|
||||||
if built.ProtocolName != "xdrive" {
|
|
||||||
t.Fatalf("ProtocolName is %q, want xdrive", built.ProtocolName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXDriveTemplateNeedsTemplate(t *testing.T) {
|
|
||||||
config := new(XDriveConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); err == nil {
|
|
||||||
t.Fatal("Build accepted a template service without a template")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ type TunConfig struct {
|
|||||||
UserLevel uint32 `json:"userLevel"`
|
UserLevel uint32 `json:"userLevel"`
|
||||||
AutoSystemRoutingTable []string `json:"autoSystemRoutingTable"`
|
AutoSystemRoutingTable []string `json:"autoSystemRoutingTable"`
|
||||||
AutoOutboundsInterface *string `json:"autoOutboundsInterface"`
|
AutoOutboundsInterface *string `json:"autoOutboundsInterface"`
|
||||||
Stack string `json:"stack"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TunConfig) Build() (proto.Message, error) {
|
func (v *TunConfig) Build() (proto.Message, error) {
|
||||||
@@ -32,7 +31,6 @@ func (v *TunConfig) Build() (proto.Message, error) {
|
|||||||
DNS: v.DNS,
|
DNS: v.DNS,
|
||||||
UserLevel: v.UserLevel,
|
UserLevel: v.UserLevel,
|
||||||
AutoSystemRoutingTable: v.AutoSystemRoutingTable,
|
AutoSystemRoutingTable: v.AutoSystemRoutingTable,
|
||||||
Stack: v.Stack,
|
|
||||||
}
|
}
|
||||||
if v.AutoOutboundsInterface != nil {
|
if v.AutoOutboundsInterface != nil {
|
||||||
config.AutoOutboundsInterface = *v.AutoOutboundsInterface
|
config.AutoOutboundsInterface = *v.AutoOutboundsInterface
|
||||||
@@ -54,11 +52,6 @@ func (v *TunConfig) Build() (proto.Message, error) {
|
|||||||
if config.MTU == 0 {
|
if config.MTU == 0 {
|
||||||
config.MTU = 1500
|
config.MTU = 1500
|
||||||
}
|
}
|
||||||
switch config.Stack {
|
|
||||||
case "", "gvisor", "system":
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown tun stack: %s (must be \"gvisor\" or \"system\")", config.Stack)
|
|
||||||
}
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -312,9 +312,6 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
|||||||
if err := json.Unmarshal(rawUser, account); err != nil {
|
if err := json.Unmarshal(rawUser, account); err != nil {
|
||||||
return nil, errors.New(`VLESS users: invalid user`).Base(err)
|
return nil, errors.New(`VLESS users: invalid user`).Base(err)
|
||||||
}
|
}
|
||||||
// validateOutboundTransportSecurity needs to see these
|
|
||||||
c.Encryption = account.Encryption
|
|
||||||
c.Address = rec.Address
|
|
||||||
if account.Reverse != nil { // may not be reached: error json unmarshal
|
if account.Reverse != nil { // may not be reached: error json unmarshal
|
||||||
return nil, errors.New(`VLESS users: please use simplified outbound's config style to use "reverse"`)
|
return nil, errors.New(`VLESS users: please use simplified outbound's config style to use "reverse"`)
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-7
@@ -59,13 +59,14 @@ func (c *WireGuardPeerConfig) Build() (*wireguard.PeerConfig, error) {
|
|||||||
type WireGuardConfig struct {
|
type WireGuardConfig struct {
|
||||||
IsClient bool `json:""`
|
IsClient bool `json:""`
|
||||||
|
|
||||||
NoKernelTun bool `json:"noKernelTun"`
|
NoKernelTun bool `json:"noKernelTun"`
|
||||||
SecretKey string `json:"secretKey"`
|
SecretKey string `json:"secretKey"`
|
||||||
Address []string `json:"address"`
|
Address []string `json:"address"`
|
||||||
Peers []*WireGuardPeerConfig `json:"peers"`
|
Peers []*WireGuardPeerConfig `json:"peers"`
|
||||||
MTU int32 `json:"mtu"`
|
MTU int32 `json:"mtu"`
|
||||||
Reserved []byte `json:"reserved"`
|
Reserved []byte `json:"reserved"`
|
||||||
DNS []string `json:"remoteDNS"`
|
DomainStrategy string `json:"domainStrategy"`
|
||||||
|
DNS []string `json:"remoteDNS"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WireGuardConfig) Build() (proto.Message, error) {
|
func (c *WireGuardConfig) Build() (proto.Message, error) {
|
||||||
@@ -124,6 +125,21 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
config.Reserved = c.Reserved
|
config.Reserved = c.Reserved
|
||||||
|
|
||||||
|
switch strings.ToLower(c.DomainStrategy) {
|
||||||
|
case "forceip", "":
|
||||||
|
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP
|
||||||
|
case "forceipv4":
|
||||||
|
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP4
|
||||||
|
case "forceipv6":
|
||||||
|
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP6
|
||||||
|
case "forceipv4v6":
|
||||||
|
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP46
|
||||||
|
case "forceipv6v4":
|
||||||
|
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP64
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported domain strategy: ", c.DomainStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
config.IsClient = c.IsClient
|
config.IsClient = c.IsClient
|
||||||
config.NoKernelTun = c.NoKernelTun
|
config.NoKernelTun = c.NoKernelTun
|
||||||
config.DNS = c.DNS
|
config.DNS = c.DNS
|
||||||
|
|||||||
+35
-32
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/proxy/freedom"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -217,11 +216,21 @@ type OutboundDetourConfig struct {
|
|||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Settings *json.RawMessage `json:"settings"`
|
Settings *json.RawMessage `json:"settings"`
|
||||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||||
ProxySettings *json.RawMessage `json:"proxySettings"`
|
ProxySettings *ProxyConfig `json:"proxySettings"`
|
||||||
MuxSettings *MuxConfig `json:"mux"`
|
MuxSettings *MuxConfig `json:"mux"`
|
||||||
TargetStrategy string `json:"targetStrategy"`
|
TargetStrategy string `json:"targetStrategy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *OutboundDetourConfig) checkChainProxyConfig() error {
|
||||||
|
if c.StreamSetting == nil || c.ProxySettings == nil || c.StreamSetting.SocketSettings == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(c.ProxySettings.Tag) > 0 && len(c.StreamSetting.SocketSettings.DialerProxy) > 0 {
|
||||||
|
return errors.New("proxySettings.tag is conflicted with sockopt.dialerProxy").AtWarning()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func requiresTransportSecurity(address *Address) bool {
|
func requiresTransportSecurity(address *Address) bool {
|
||||||
if address == nil || address.Address == nil {
|
if address == nil || address.Address == nil {
|
||||||
return false
|
return false
|
||||||
@@ -242,7 +251,7 @@ func validateOutboundTransportSecurity(rawConfig interface{}, senderSettings *pr
|
|||||||
if vlessCfg.Encryption != "" && vlessCfg.Encryption != "none" {
|
if vlessCfg.Encryption != "" && vlessCfg.Encryption != "none" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if requiresTransportSecurity(vlessCfg.Address) {
|
if requiresTransportSecurity(vlessCfg.Vnext[0].Address) {
|
||||||
return errors.New("vless without TLS or other encryption is prohibited unless the server address is a private IP or domain")
|
return errors.New("vless without TLS or other encryption is prohibited unless the server address is a private IP or domain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,10 +267,6 @@ func validateOutboundTransportSecurity(rawConfig interface{}, senderSettings *pr
|
|||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
||||||
if c.ProxySettings != nil {
|
|
||||||
return nil, errors.PrintRemovedFeatureError(`outbound "proxySettings"`, `"streamSettings.sockopt.dialerProxy"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
senderSettings := &proxyman.SenderConfig{}
|
senderSettings := &proxyman.SenderConfig{}
|
||||||
switch strings.ToLower(c.TargetStrategy) {
|
switch strings.ToLower(c.TargetStrategy) {
|
||||||
case "asis", "":
|
case "asis", "":
|
||||||
@@ -289,6 +294,9 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
default:
|
default:
|
||||||
return nil, errors.New("unsupported target domain strategy: ", c.TargetStrategy)
|
return nil, errors.New("unsupported target domain strategy: ", c.TargetStrategy)
|
||||||
}
|
}
|
||||||
|
if err := c.checkChainProxyConfig(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if c.SendThrough != nil {
|
if c.SendThrough != nil {
|
||||||
address := ParseSendThough(c.SendThrough)
|
address := ParseSendThough(c.SendThrough)
|
||||||
@@ -314,6 +322,26 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
senderSettings.StreamSettings = ss
|
senderSettings.StreamSettings = ss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.ProxySettings != nil {
|
||||||
|
ps, err := c.ProxySettings.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid outbound detour proxy settings").Base(err)
|
||||||
|
}
|
||||||
|
if ps.TransportLayerProxy {
|
||||||
|
if senderSettings.StreamSettings != nil {
|
||||||
|
if senderSettings.StreamSettings.SocketSettings != nil {
|
||||||
|
senderSettings.StreamSettings.SocketSettings.DialerProxy = ps.Tag
|
||||||
|
} else {
|
||||||
|
senderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{DialerProxy: ps.Tag}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
senderSettings.StreamSettings = &internet.StreamConfig{SocketSettings: &internet.SocketConfig{DialerProxy: ps.Tag}}
|
||||||
|
}
|
||||||
|
ps = nil
|
||||||
|
}
|
||||||
|
senderSettings.ProxySettings = ps
|
||||||
|
}
|
||||||
|
|
||||||
if c.MuxSettings != nil {
|
if c.MuxSettings != nil {
|
||||||
ms, err := c.MuxSettings.Build()
|
ms, err := c.MuxSettings.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -338,31 +366,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fc, ok := ts.(*freedom.Config); ok {
|
|
||||||
if senderSettings.StreamSettings != nil &&
|
|
||||||
senderSettings.StreamSettings.SocketSettings != nil &&
|
|
||||||
senderSettings.StreamSettings.SocketSettings.AddressPortStrategy != internet.AddressPortStrategy_None {
|
|
||||||
return nil, errors.New(`freedom outbound does not support "sockopt.addressPortStrategy"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
var strategy internet.DomainStrategy
|
|
||||||
if strategy = senderSettings.TargetStrategy; strategy != internet.DomainStrategy_AS_IS {
|
|
||||||
errors.LogWarning(context.Background(), `The "outbound.targetStrategy" setting is not supported directly by freedom and has been automatically migrated to "sockopt.domainStrategy" with no behavior change.`)
|
|
||||||
senderSettings.TargetStrategy = internet.DomainStrategy_AS_IS
|
|
||||||
} else if strategy = fc.DomainStrategy; strategy != internet.DomainStrategy_AS_IS {
|
|
||||||
errors.LogWarning(context.Background(), `The "freedom.domainStrategy" setting is deprecated and will be removed. For compatibility, its value has been automatically migrated to "sockopt.domainStrategy". Please update your config before removal.`)
|
|
||||||
}
|
|
||||||
if strategy != internet.DomainStrategy_AS_IS {
|
|
||||||
if senderSettings.StreamSettings == nil {
|
|
||||||
senderSettings.StreamSettings = &internet.StreamConfig{}
|
|
||||||
}
|
|
||||||
if senderSettings.StreamSettings.SocketSettings == nil {
|
|
||||||
senderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{}
|
|
||||||
}
|
|
||||||
senderSettings.StreamSettings.SocketSettings.DomainStrategy = strategy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &core.OutboundHandlerConfig{
|
return &core.OutboundHandlerConfig{
|
||||||
SenderSettings: serial.ToTypedMessage(senderSettings),
|
SenderSettings: serial.ToTypedMessage(senderSettings),
|
||||||
Tag: c.Tag,
|
Tag: c.Tag,
|
||||||
|
|||||||
+138
-272
@@ -1,18 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/build"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"mvdan.cc/gofumpt/format"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -26,27 +23,101 @@ var (
|
|||||||
isFormat bool
|
isFormat bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func getModuleInfo(pwd string) (modPath, langVersion string, err error) {
|
// envFile returns the name of the Go environment configuration file.
|
||||||
data, err := os.ReadFile(filepath.Join(pwd, "go.mod"))
|
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
|
||||||
if err != nil {
|
func envFile() (string, error) {
|
||||||
return "", "", err
|
if file := os.Getenv("GOENV"); file != "" {
|
||||||
}
|
if file == "off" {
|
||||||
for _, line := range strings.Split(string(data), "\n") {
|
return "", errors.New("GOENV=off")
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) >= 2 {
|
|
||||||
switch fields[0] {
|
|
||||||
case "module":
|
|
||||||
modPath = fields[1]
|
|
||||||
case "go":
|
|
||||||
langVersion = "go" + strings.TrimPrefix(fields[1], "go")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return file, nil
|
||||||
}
|
}
|
||||||
return modPath, langVersion, nil
|
dir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
return "", errors.New("missing user-config dir")
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, "go", "env"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatGoSource(src []byte, opts format.Options) ([]byte, error) {
|
// GetRuntimeEnv returns the value of runtime environment variable,
|
||||||
return format.Source(src, opts)
|
// that is set by running following command: `go env -w key=value`.
|
||||||
|
func GetRuntimeEnv(key string) (string, error) {
|
||||||
|
file, err := envFile()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if file == "" {
|
||||||
|
return "", errors.New("missing runtime env file")
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
var runtimeEnv string
|
||||||
|
data, readErr := os.ReadFile(file)
|
||||||
|
if readErr != nil {
|
||||||
|
return "", readErr
|
||||||
|
}
|
||||||
|
envStrings := strings.Split(string(data), "\n")
|
||||||
|
for _, envItem := range envStrings {
|
||||||
|
envItem = strings.TrimSuffix(envItem, "\r")
|
||||||
|
envKeyValue := strings.Split(envItem, "=")
|
||||||
|
if len(envKeyValue) == 2 && strings.TrimSpace(envKeyValue[0]) == key {
|
||||||
|
runtimeEnv = strings.TrimSpace(envKeyValue[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runtimeEnv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
|
||||||
|
func GetGOBIN() string {
|
||||||
|
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
|
||||||
|
GOBIN := os.Getenv("GOBIN")
|
||||||
|
if GOBIN == "" {
|
||||||
|
var err error
|
||||||
|
// The one set by user by running `go env -w GOBIN=/path`
|
||||||
|
GOBIN, err = GetRuntimeEnv("GOBIN")
|
||||||
|
if err != nil {
|
||||||
|
// The default one that Golang uses
|
||||||
|
return filepath.Join(build.Default.GOPATH, "bin")
|
||||||
|
}
|
||||||
|
if GOBIN == "" {
|
||||||
|
return filepath.Join(build.Default.GOPATH, "bin")
|
||||||
|
}
|
||||||
|
return GOBIN
|
||||||
|
}
|
||||||
|
return GOBIN
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(binary string, args []string) ([]byte, error) {
|
||||||
|
cmd := exec.Command(binary, args...)
|
||||||
|
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||||
|
output, cmdErr := cmd.CombinedOutput()
|
||||||
|
if cmdErr != nil {
|
||||||
|
return nil, cmdErr
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunMany(binary string, args, files []string) bool {
|
||||||
|
fmt.Println("Processing with", binary, args, "...")
|
||||||
|
|
||||||
|
formatRequired := false
|
||||||
|
maxTasks := make(chan struct{}, runtime.NumCPU())
|
||||||
|
for _, file := range files {
|
||||||
|
maxTasks <- struct{}{}
|
||||||
|
go func(file string) {
|
||||||
|
output, err := Run(binary, append(args, file))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else if len(output) > 0 {
|
||||||
|
fmt.Println(string(output))
|
||||||
|
formatRequired = true
|
||||||
|
}
|
||||||
|
<-maxTasks
|
||||||
|
}(file)
|
||||||
|
}
|
||||||
|
return formatRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -79,76 +150,26 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pwd := *directory
|
pwd := *directory
|
||||||
modPath, langVersion, modErr := getModuleInfo(pwd)
|
GOBIN := GetGOBIN()
|
||||||
if modErr != nil {
|
binPath := os.Getenv("PATH")
|
||||||
fmt.Println("Error reading go.mod:", modErr)
|
pathSlice := []string{pwd, GOBIN, binPath}
|
||||||
|
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
|
||||||
|
os.Setenv("PATH", binPath)
|
||||||
|
|
||||||
|
suffix := ""
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
suffix = ".exe"
|
||||||
|
}
|
||||||
|
gofmt := "gofumpt" + suffix
|
||||||
|
|
||||||
|
if gofmtPath, err := exec.LookPath(gofmt); err != nil {
|
||||||
|
fmt.Println("Can not find", gofmt, "in system path or current working directory.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
} else {
|
||||||
opts := format.Options{
|
gofmt = gofmtPath
|
||||||
LangVersion: langVersion,
|
|
||||||
ModulePath: modPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFormat {
|
|
||||||
fmt.Println("Formatting Go source files...")
|
|
||||||
} else if isCheck {
|
|
||||||
fmt.Println("Checking files thar are not properly formatted...")
|
|
||||||
}
|
|
||||||
|
|
||||||
jobs := make(chan string, runtime.NumCPU())
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var formatRequired atomic.Bool
|
|
||||||
var hasErrors atomic.Bool
|
|
||||||
|
|
||||||
for i := 0; i < runtime.NumCPU(); i++ {
|
|
||||||
wg.Go(func() {
|
|
||||||
for path := range jobs {
|
|
||||||
src, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error reading %s: %v\n", path, err)
|
|
||||||
hasErrors.Store(true)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
formatted, err := formatGoSource(src, opts)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error formatting %s: %v\n", path, err)
|
|
||||||
hasErrors.Store(true)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(src, formatted) {
|
|
||||||
var diffText []byte
|
|
||||||
if isDryrun {
|
|
||||||
newName := filepath.ToSlash(path)
|
|
||||||
oldName := newName + ".orig"
|
|
||||||
diffText = diff(oldName, src, newName, formatted)
|
|
||||||
}
|
|
||||||
if isFormat {
|
|
||||||
info, statErr := os.Stat(path)
|
|
||||||
if statErr != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error stating %s: %v\n", path, statErr)
|
|
||||||
hasErrors.Store(true)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if writeErr := os.WriteFile(path, formatted, info.Mode().Perm()); writeErr != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", path, writeErr)
|
|
||||||
hasErrors.Store(true)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
formatRequired.Store(true)
|
|
||||||
if isDryrun && len(diffText) > 0 {
|
|
||||||
fmt.Printf("%s\n%s", path, diffText)
|
|
||||||
} else {
|
|
||||||
fmt.Println(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rawFilesSlice := make([]string, 0, 1000)
|
||||||
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
|
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -165,206 +186,51 @@ func main() {
|
|||||||
!strings.HasSuffix(filename, ".pb.go") &&
|
!strings.HasSuffix(filename, ".pb.go") &&
|
||||||
!strings.Contains(dir, filepath.Join("testing", "mocks")) &&
|
!strings.Contains(dir, filepath.Join("testing", "mocks")) &&
|
||||||
!strings.Contains(path, filepath.Join("main", "distro", "all", "all.go")) {
|
!strings.Contains(path, filepath.Join("main", "distro", "all", "all.go")) {
|
||||||
jobs <- path
|
rawFilesSlice = append(rawFilesSlice, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
close(jobs)
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
if walkErr != nil {
|
if walkErr != nil {
|
||||||
fmt.Println(walkErr)
|
fmt.Println(walkErr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasErrors.Load() {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFormat {
|
if isFormat {
|
||||||
if formatRequired.Load() {
|
gofmtArgs := []string{
|
||||||
fmt.Println("Do NOT forget to commit file changes.")
|
"-l", "-e", "-w",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Formatting Go source files...")
|
||||||
|
RunMany(gofmt, gofmtArgs, rawFilesSlice)
|
||||||
|
fmt.Println("Do NOT forget to commit file changes.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCheck {
|
if isCheck {
|
||||||
if formatRequired.Load() {
|
gofmtListArgs := []string{
|
||||||
|
"-l", "-e",
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Checking files thar are not properly formatted...")
|
||||||
|
formatRequired := RunMany(gofmt, gofmtListArgs, rawFilesSlice)
|
||||||
|
if formatRequired {
|
||||||
fmt.Println("Format problem(s) found.")
|
fmt.Println("Format problem(s) found.")
|
||||||
fmt.Println("Please run 'go run ./infra/vformat/main.go' to format the Go source files.")
|
}
|
||||||
|
|
||||||
|
if isDryrun {
|
||||||
|
if formatRequired {
|
||||||
|
gofmtShowArgs := []string{
|
||||||
|
"-d", "-e",
|
||||||
|
}
|
||||||
|
RunMany(gofmt, gofmtShowArgs, rawFilesSlice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatRequired {
|
||||||
|
fmt.Println("Please run 'go install -v mvdan.cc/gofumpt@latest', then run 'go run ./infra/vformat/main.go' to format the Go source files.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("All Go source file format check has been passed.")
|
fmt.Println("All Go source file format check has been passed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// diff algorithm copied from mvdan.cc/gofumpt/internal/govendor/diff
|
|
||||||
type pair struct{ x, y int }
|
|
||||||
|
|
||||||
func diff(oldName string, old []byte, newName string, new []byte) []byte {
|
|
||||||
if bytes.Equal(old, new) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
x := diffLines(old)
|
|
||||||
y := diffLines(new)
|
|
||||||
|
|
||||||
var out bytes.Buffer
|
|
||||||
fmt.Fprintf(&out, "diff %s %s\n", oldName, newName)
|
|
||||||
fmt.Fprintf(&out, "--- %s\n", oldName)
|
|
||||||
fmt.Fprintf(&out, "+++ %s\n", newName)
|
|
||||||
|
|
||||||
var (
|
|
||||||
done pair
|
|
||||||
chunk pair
|
|
||||||
count pair
|
|
||||||
ctext []string
|
|
||||||
)
|
|
||||||
for _, m := range diffTgs(x, y) {
|
|
||||||
if m.x < done.x {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
start := m
|
|
||||||
for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] {
|
|
||||||
start.x--
|
|
||||||
start.y--
|
|
||||||
}
|
|
||||||
end := m
|
|
||||||
for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] {
|
|
||||||
end.x++
|
|
||||||
end.y++
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range x[done.x:start.x] {
|
|
||||||
ctext = append(ctext, "-"+s)
|
|
||||||
count.x++
|
|
||||||
}
|
|
||||||
for _, s := range y[done.y:start.y] {
|
|
||||||
ctext = append(ctext, "+"+s)
|
|
||||||
count.y++
|
|
||||||
}
|
|
||||||
|
|
||||||
const C = 3
|
|
||||||
if (end.x < len(x) || end.y < len(y)) &&
|
|
||||||
(end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) {
|
|
||||||
for _, s := range x[start.x:end.x] {
|
|
||||||
ctext = append(ctext, " "+s)
|
|
||||||
count.x++
|
|
||||||
count.y++
|
|
||||||
}
|
|
||||||
done = end
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ctext) > 0 {
|
|
||||||
n := end.x - start.x
|
|
||||||
if n > C {
|
|
||||||
n = C
|
|
||||||
}
|
|
||||||
for _, s := range x[start.x : start.x+n] {
|
|
||||||
ctext = append(ctext, " "+s)
|
|
||||||
count.x++
|
|
||||||
count.y++
|
|
||||||
}
|
|
||||||
done = pair{start.x + n, start.y + n}
|
|
||||||
|
|
||||||
if count.x > 0 {
|
|
||||||
chunk.x++
|
|
||||||
}
|
|
||||||
if count.y > 0 {
|
|
||||||
chunk.y++
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y)
|
|
||||||
for _, s := range ctext {
|
|
||||||
out.WriteString(s)
|
|
||||||
}
|
|
||||||
count.x = 0
|
|
||||||
count.y = 0
|
|
||||||
ctext = ctext[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if end.x >= len(x) && end.y >= len(y) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk = pair{end.x - C, end.y - C}
|
|
||||||
for _, s := range x[chunk.x:end.x] {
|
|
||||||
ctext = append(ctext, " "+s)
|
|
||||||
count.x++
|
|
||||||
count.y++
|
|
||||||
}
|
|
||||||
done = end
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func diffLines(x []byte) []string {
|
|
||||||
l := strings.SplitAfter(string(x), "\n")
|
|
||||||
if l[len(l)-1] == "" {
|
|
||||||
l = l[:len(l)-1]
|
|
||||||
} else {
|
|
||||||
l[len(l)-1] += "\n\\ No newline at end of file\n"
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func diffTgs(x, y []string) []pair {
|
|
||||||
m := make(map[string]int)
|
|
||||||
for _, s := range x {
|
|
||||||
if c := m[s]; c > -2 {
|
|
||||||
m[s] = c - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range y {
|
|
||||||
if c := m[s]; c > -8 {
|
|
||||||
m[s] = c - 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var xi, yi, inv []int
|
|
||||||
for i, s := range y {
|
|
||||||
if m[s] == -5 {
|
|
||||||
m[s] = len(yi)
|
|
||||||
yi = append(yi, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, s := range x {
|
|
||||||
if j, ok := m[s]; ok && j >= 0 {
|
|
||||||
xi = append(xi, i)
|
|
||||||
inv = append(inv, j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
J := inv
|
|
||||||
n := len(xi)
|
|
||||||
T := make([]int, n)
|
|
||||||
L := make([]int, n)
|
|
||||||
for i := range T {
|
|
||||||
T[i] = n + 1
|
|
||||||
}
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
k := sort.Search(n, func(k int) bool {
|
|
||||||
return T[k] >= J[i]
|
|
||||||
})
|
|
||||||
T[k] = J[i]
|
|
||||||
L[i] = k + 1
|
|
||||||
}
|
|
||||||
k := 0
|
|
||||||
for _, v := range L {
|
|
||||||
if k < v {
|
|
||||||
k = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
seq := make([]pair, 2+k)
|
|
||||||
seq[1+k] = pair{len(x), len(y)}
|
|
||||||
lastj := n
|
|
||||||
for i := n - 1; i >= 0; i-- {
|
|
||||||
if L[i] == k && J[i] < lastj {
|
|
||||||
seq[k] = pair{xi[i], yi[J[i]]}
|
|
||||||
k--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
seq[0] = pair{0, 0}
|
|
||||||
return seq
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ import (
|
|||||||
_ "github.com/xtls/xray-core/transport/internet/tls"
|
_ "github.com/xtls/xray-core/transport/internet/tls"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/udp"
|
_ "github.com/xtls/xray-core/transport/internet/udp"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/websocket"
|
_ "github.com/xtls/xray-core/transport/internet/websocket"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/xdrive"
|
|
||||||
|
|
||||||
// Transport headers
|
// Transport headers
|
||||||
_ "github.com/xtls/xray-core/transport/internet/headers/http"
|
_ "github.com/xtls/xray-core/transport/internet/headers/http"
|
||||||
|
|||||||
@@ -2,15 +2,12 @@
|
|||||||
package blackhole
|
package blackhole
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/dice"
|
"github.com/xtls/xray-core/common/dice"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/common/signal"
|
"github.com/xtls/xray-core/common/signal"
|
||||||
@@ -20,34 +17,14 @@ import (
|
|||||||
|
|
||||||
// Handler is an outbound connection that silently swallow the entire payload.
|
// Handler is an outbound connection that silently swallow the entire payload.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
response []byte
|
response ResponseConfig
|
||||||
}
|
|
||||||
|
|
||||||
var http403response = http.Response{
|
|
||||||
StatusCode: 403,
|
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
|
||||||
Header: http.Header{
|
|
||||||
"Connection": {"close"},
|
|
||||||
"Cache-Control": {"max-age=3600, public"},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new blackhole handler.
|
// New creates a new blackhole handler.
|
||||||
func New(ctx context.Context, config *Config) (*Handler, error) {
|
func New(ctx context.Context, config *Config) (*Handler, error) {
|
||||||
response := []byte{}
|
response, err := config.GetInternalResponse()
|
||||||
if config.Response != nil {
|
if err != nil {
|
||||||
switch config.Response.Type {
|
return nil, err
|
||||||
case "", "none":
|
|
||||||
case "http":
|
|
||||||
var data bytes.Buffer
|
|
||||||
common.Must(http403response.Write(&data))
|
|
||||||
response = data.Bytes()
|
|
||||||
case "custom":
|
|
||||||
response = config.Response.CustomResponseData
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown blackhole response: " + config.Response.Type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &Handler{
|
return &Handler{
|
||||||
response: response,
|
response: response,
|
||||||
@@ -60,10 +37,8 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
ob := outbounds[len(outbounds)-1]
|
ob := outbounds[len(outbounds)-1]
|
||||||
ob.Name = "blackhole"
|
ob.Name = "blackhole"
|
||||||
|
|
||||||
if len(h.response) > 0 {
|
nBytes := h.response.WriteTo(link.Writer)
|
||||||
mbc := buf.MultiBufferContainer{}
|
if nBytes > 0 {
|
||||||
common.Must2(mbc.Write(h.response))
|
|
||||||
link.Writer.WriteMultiBuffer(mbc.MultiBuffer)
|
|
||||||
// Sleep a little here to make sure the response is sent to client.
|
// Sleep a little here to make sure the response is sent to client.
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package blackhole_test
|
package blackhole_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
@@ -19,58 +16,27 @@ import (
|
|||||||
func TestBlackholeHTTPResponse(t *testing.T) {
|
func TestBlackholeHTTPResponse(t *testing.T) {
|
||||||
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
|
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
|
||||||
handler, err := blackhole.New(ctx, &blackhole.Config{
|
handler, err := blackhole.New(ctx, &blackhole.Config{
|
||||||
Response: &blackhole.Response{Type: "http"},
|
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
|
||||||
})
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
reader, writer := pipe.New(pipe.WithoutSizeLimit())
|
reader, writer := pipe.New(pipe.WithoutSizeLimit())
|
||||||
|
|
||||||
dataCh := make(chan buf.MultiBuffer, 1)
|
var mb buf.MultiBuffer
|
||||||
|
var rerr error
|
||||||
go func() {
|
go func() {
|
||||||
mb := common.Must2(reader.ReadMultiBuffer())
|
b, e := reader.ReadMultiBuffer()
|
||||||
dataCh <- mb
|
mb = b
|
||||||
|
rerr = e
|
||||||
}()
|
}()
|
||||||
|
|
||||||
link := transport.Link{
|
link := transport.Link{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
}
|
}
|
||||||
common.Must(handler.Process(ctx, &link, nil))
|
common.Must(handler.Process(ctx, &link, nil))
|
||||||
mb := <-dataCh
|
|
||||||
data := make([]byte, mb.Len())
|
|
||||||
mb.Copy(data)
|
|
||||||
resp := common.Must2(http.ReadResponse(bufio.NewReader(bytes.NewBuffer(data)), nil))
|
|
||||||
if resp.StatusCode != 403 {
|
|
||||||
t.Errorf("expected 403 response, got %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBlackholeCustomResponse(t *testing.T) {
|
|
||||||
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
|
|
||||||
// slightly bigger than a buffer
|
|
||||||
expected := make([]byte, buf.Size+1000)
|
|
||||||
if _, err := rand.Read(expected); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
handler, err := blackhole.New(ctx, &blackhole.Config{
|
|
||||||
Response: &blackhole.Response{
|
|
||||||
Type: "custom",
|
|
||||||
CustomResponseData: expected,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
common.Must(err)
|
|
||||||
|
|
||||||
reader, writer := pipe.New(pipe.WithoutSizeLimit())
|
|
||||||
var actual buf.MultiBuffer
|
|
||||||
var rerr error
|
|
||||||
go func() {
|
|
||||||
actual, rerr = reader.ReadMultiBuffer()
|
|
||||||
}()
|
|
||||||
|
|
||||||
link := transport.Link{Reader: reader, Writer: writer}
|
|
||||||
common.Must(handler.Process(ctx, &link, nil))
|
|
||||||
common.Must(rerr)
|
common.Must(rerr)
|
||||||
|
if mb.IsEmpty() {
|
||||||
if actual.String() != string(expected) {
|
t.Error("expect http response, but nothing")
|
||||||
t.Errorf("custom response mismatch")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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,6 +7,7 @@
|
|||||||
package blackhole
|
package blackhole
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
serial "github.com/xtls/xray-core/common/serial"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,28 +22,26 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Response struct {
|
type NoneResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
unknownFields protoimpl.UnknownFields
|
||||||
CustomResponseData []byte `protobuf:"bytes,2,opt,name=custom_response_data,json=customResponseData,proto3" json:"custom_response_data,omitempty"`
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Response) Reset() {
|
func (x *NoneResponse) Reset() {
|
||||||
*x = Response{}
|
*x = NoneResponse{}
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Response) String() string {
|
func (x *NoneResponse) String() string {
|
||||||
return protoimpl.X.MessageStringOf(x)
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Response) ProtoMessage() {}
|
func (*NoneResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Response) ProtoReflect() protoreflect.Message {
|
func (x *NoneResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@@ -54,35 +53,57 @@ func (x *Response) ProtoReflect() protoreflect.Message {
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
|
// Deprecated: Use NoneResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*Response) Descriptor() ([]byte, []int) {
|
func (*NoneResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0}
|
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Response) GetType() string {
|
type HTTPResponse struct {
|
||||||
if x != nil {
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
return x.Type
|
unknownFields protoimpl.UnknownFields
|
||||||
}
|
sizeCache protoimpl.SizeCache
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Response) GetCustomResponseData() []byte {
|
func (x *HTTPResponse) Reset() {
|
||||||
|
*x = HTTPResponse{}
|
||||||
|
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HTTPResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HTTPResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *HTTPResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.CustomResponseData
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
}
|
}
|
||||||
return nil
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use HTTPResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*HTTPResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Response *Response `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
Response *serial.TypedMessage `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
*x = Config{}
|
*x = Config{}
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
mi := &file_proxy_blackhole_config_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -94,7 +115,7 @@ func (x *Config) String() string {
|
|||||||
func (*Config) ProtoMessage() {}
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
mi := &file_proxy_blackhole_config_proto_msgTypes[2]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -107,10 +128,10 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}
|
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetResponse() *Response {
|
func (x *Config) GetResponse() *serial.TypedMessage {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Response
|
return x.Response
|
||||||
}
|
}
|
||||||
@@ -121,12 +142,11 @@ var File_proxy_blackhole_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_proxy_blackhole_config_proto_rawDesc = "" +
|
const file_proxy_blackhole_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x1cproxy/blackhole/config.proto\x12\x14xray.proxy.blackhole\"P\n" +
|
"\x1cproxy/blackhole/config.proto\x12\x14xray.proxy.blackhole\x1a!common/serial/typed_message.proto\"\x0e\n" +
|
||||||
"\bResponse\x12\x12\n" +
|
"\fNoneResponse\"\x0e\n" +
|
||||||
"\x04type\x18\x01 \x01(\tR\x04type\x120\n" +
|
"\fHTTPResponse\"F\n" +
|
||||||
"\x14custom_response_data\x18\x02 \x01(\fR\x12customResponseData\"D\n" +
|
"\x06Config\x12<\n" +
|
||||||
"\x06Config\x12:\n" +
|
"\bresponse\x18\x01 \x01(\v2 .xray.common.serial.TypedMessageR\bresponseB^\n" +
|
||||||
"\bresponse\x18\x01 \x01(\v2\x1e.xray.proxy.blackhole.ResponseR\bresponseB^\n" +
|
|
||||||
"\x18com.xray.proxy.blackholeP\x01Z)github.com/xtls/xray-core/proxy/blackhole\xaa\x02\x14Xray.Proxy.Blackholeb\x06proto3"
|
"\x18com.xray.proxy.blackholeP\x01Z)github.com/xtls/xray-core/proxy/blackhole\xaa\x02\x14Xray.Proxy.Blackholeb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -141,13 +161,15 @@ func file_proxy_blackhole_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_proxy_blackhole_config_proto_rawDescData
|
return file_proxy_blackhole_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||||
var file_proxy_blackhole_config_proto_goTypes = []any{
|
var file_proxy_blackhole_config_proto_goTypes = []any{
|
||||||
(*Response)(nil), // 0: xray.proxy.blackhole.Response
|
(*NoneResponse)(nil), // 0: xray.proxy.blackhole.NoneResponse
|
||||||
(*Config)(nil), // 1: xray.proxy.blackhole.Config
|
(*HTTPResponse)(nil), // 1: xray.proxy.blackhole.HTTPResponse
|
||||||
|
(*Config)(nil), // 2: xray.proxy.blackhole.Config
|
||||||
|
(*serial.TypedMessage)(nil), // 3: xray.common.serial.TypedMessage
|
||||||
}
|
}
|
||||||
var file_proxy_blackhole_config_proto_depIdxs = []int32{
|
var file_proxy_blackhole_config_proto_depIdxs = []int32{
|
||||||
0, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.proxy.blackhole.Response
|
3, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.common.serial.TypedMessage
|
||||||
1, // [1:1] is the sub-list for method output_type
|
1, // [1:1] is the sub-list for method output_type
|
||||||
1, // [1:1] is the sub-list for method input_type
|
1, // [1:1] is the sub-list for method input_type
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
@@ -166,7 +188,7 @@ func file_proxy_blackhole_config_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 2,
|
NumMessages: 3,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ option go_package = "github.com/xtls/xray-core/proxy/blackhole";
|
|||||||
option java_package = "com.xray.proxy.blackhole";
|
option java_package = "com.xray.proxy.blackhole";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
message Response {
|
import "common/serial/typed_message.proto";
|
||||||
string type = 1;
|
|
||||||
bytes custom_response_data = 2;
|
message NoneResponse {}
|
||||||
}
|
|
||||||
|
message HTTPResponse {}
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
Response response = 1;
|
xray.common.serial.TypedMessage response = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
package blackhole_test
|
package blackhole_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"bufio"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
. "github.com/xtls/xray-core/proxy/blackhole"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTTPResponse(t *testing.T) {
|
func TestHTTPResponse(t *testing.T) {
|
||||||
handler, err := blackhole.New(context.Background(), &blackhole.Config{
|
buffer := buf.New()
|
||||||
Response: &blackhole.Response{Type: "http"},
|
|
||||||
})
|
httpResponse := new(HTTPResponse)
|
||||||
|
httpResponse.WriteTo(buf.NewWriter(buffer))
|
||||||
|
|
||||||
|
reader := bufio.NewReader(buffer)
|
||||||
|
response, err := http.ReadResponse(reader, nil)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if handler == nil {
|
|
||||||
t.Error("expected HTTP response handler")
|
if response.StatusCode != 403 {
|
||||||
|
t.Error("expected status code 403, but got ", response.StatusCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+102
-96
@@ -53,10 +53,6 @@ func reloadEnvSettings() error {
|
|||||||
func init() {
|
func init() {
|
||||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
h := new(Handler)
|
h := new(Handler)
|
||||||
if streamSettings, ok := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig); ok && streamSettings.SocketSettings != nil {
|
|
||||||
h.resolveStrategy = streamSettings.SocketSettings.DomainStrategy
|
|
||||||
h.usesDialerProxy = len(streamSettings.SocketSettings.DialerProxy) > 0
|
|
||||||
}
|
|
||||||
if err := core.RequireFeatures(ctx, func(pm policy.Manager) error {
|
if err := core.RequireFeatures(ctx, func(pm policy.Manager) error {
|
||||||
return h.Init(config.(*Config), pm)
|
return h.Init(config.(*Config), pm)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -93,11 +89,9 @@ type FinalRule struct {
|
|||||||
|
|
||||||
// Handler handles Freedom connections.
|
// Handler handles Freedom connections.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
config *Config
|
config *Config
|
||||||
finalRules []*FinalRule
|
finalRules []*FinalRule
|
||||||
resolveStrategy internet.DomainStrategy
|
|
||||||
usesDialerProxy bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
|
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
|
||||||
@@ -174,6 +168,22 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) shouldResolveDomainBeforeFinalRules(dialDest net.Destination, defaultRule *FinalRule) bool {
|
||||||
|
if !dialDest.Address.Family().IsDomain() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(h.finalRules) > 0 {
|
||||||
|
rule := h.finalRules[0]
|
||||||
|
if rule.action == RuleAction_Allow && rule.network[dialDest.Network] && len(rule.port) == 0 && rule.ip == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if defaultRule != nil || len(h.finalRules) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule {
|
func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule {
|
||||||
for _, rule := range h.finalRules {
|
for _, rule := range h.finalRules {
|
||||||
if rule.Apply(network, address, port) {
|
if rule.Apply(network, address, port) {
|
||||||
@@ -186,16 +196,17 @@ func (h *Handler) matchFinalRule(network net.Network, address net.Address, port
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) applyFinalRules(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) RuleAction {
|
||||||
|
if rule := h.matchFinalRule(network, address, port, defaultRule); rule != nil {
|
||||||
|
return rule.action
|
||||||
|
}
|
||||||
|
return RuleAction_Allow
|
||||||
|
}
|
||||||
|
|
||||||
// Init initializes the Handler with necessary parameters.
|
// Init initializes the Handler with necessary parameters.
|
||||||
func (h *Handler) Init(config *Config, pm policy.Manager) error {
|
func (h *Handler) Init(config *Config, pm policy.Manager) error {
|
||||||
h.config = config
|
h.config = config
|
||||||
h.policyManager = pm
|
h.policyManager = pm
|
||||||
if h.usesDialerProxy { // freedom is not the final outbound, final rules do not apply
|
|
||||||
if len(config.FinalRules) > 0 {
|
|
||||||
errors.LogWarning(context.Background(), `The "finalRules" setting is ignored when "sockopt.dialerProxy" is set, since freedom is not the final outbound.`)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
h.finalRules = make([]*FinalRule, 0, len(config.FinalRules))
|
h.finalRules = make([]*FinalRule, 0, len(config.FinalRules))
|
||||||
for _, rc := range config.FinalRules {
|
for _, rc := range config.FinalRules {
|
||||||
rule, err := buildFinalRule(rc)
|
rule, err := buildFinalRule(rc)
|
||||||
@@ -226,20 +237,6 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
|
|||||||
return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
|
return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
|
|
||||||
delay := h.blockDelay(rule)
|
|
||||||
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
|
|
||||||
timer := time.AfterFunc(delay, func() {
|
|
||||||
common.Interrupt(input)
|
|
||||||
common.Interrupt(output)
|
|
||||||
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
|
|
||||||
})
|
|
||||||
defer timer.Stop()
|
|
||||||
defer common.Close(output)
|
|
||||||
_ = buf.Copy(input, buf.Discard)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidAddress(addr *net.IPOrDomain) bool {
|
func isValidAddress(addr *net.IPOrDomain) bool {
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
return false
|
return false
|
||||||
@@ -259,10 +256,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
ob.Name = "freedom"
|
ob.Name = "freedom"
|
||||||
ob.CanSpliceCopy = 1
|
ob.CanSpliceCopy = 1
|
||||||
inbound := session.InboundFromContext(ctx)
|
inbound := session.InboundFromContext(ctx)
|
||||||
var defaultRule *FinalRule
|
defaultRule := getDefaultFinalRule(inbound)
|
||||||
if !h.usesDialerProxy { // freedom is not the final outbound, final rules do not apply (and the domain is not resolved)
|
|
||||||
defaultRule = getDefaultFinalRule(inbound)
|
|
||||||
}
|
|
||||||
|
|
||||||
destination := ob.Target
|
destination := ob.Target
|
||||||
origTargetAddr := ob.OriginalTarget.Address
|
origTargetAddr := ob.OriginalTarget.Address
|
||||||
@@ -290,53 +284,61 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
var conn stat.Connection
|
var conn stat.Connection
|
||||||
var blockedDest *net.Destination
|
var blockedDest *net.Destination
|
||||||
var blockedRule *FinalRule
|
var blockedRule *FinalRule
|
||||||
|
firstResolve := true
|
||||||
err := retry.ExponentialBackoff(5, 100).On(func() error {
|
err := retry.ExponentialBackoff(5, 100).On(func() error {
|
||||||
if destination.Address.Family().IsDomain() {
|
dialDest := destination
|
||||||
if defaultRule != nil || len(h.finalRules) > 0 {
|
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() {
|
||||||
if strategy := h.resolveStrategy; strategy.HasStrategy() {
|
strategy := h.config.DomainStrategy
|
||||||
ips, err := internet.LookupForIP(destination.Address.Domain(), strategy, outGateway)
|
if destination.Network == net.Network_UDP && origTargetAddr != nil && outGateway == nil {
|
||||||
if err != nil { // non-force may still dial with system DNS
|
strategy = strategy.GetDynamicStrategy(origTargetAddr.Family())
|
||||||
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 {
|
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
|
||||||
if rule := h.matchFinalRule(destination.Network, destination.Address, destination.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
|
if err != nil {
|
||||||
blockedDest = &destination
|
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
|
||||||
blockedRule = rule
|
if h.config.DomainStrategy.ForceIP() || h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) {
|
||||||
return nil
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dialDest = net.Destination{
|
||||||
|
Network: dialDest.Network,
|
||||||
|
Address: net.IPAddress(ips[dice.Roll(len(ips))]),
|
||||||
|
Port: dialDest.Port,
|
||||||
|
}
|
||||||
|
errors.LogInfo(ctx, "dialing to ", dialDest)
|
||||||
|
}
|
||||||
|
} else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules
|
||||||
|
domain := dialDest.Address.Domain()
|
||||||
|
var ips []net.IP
|
||||||
|
if firstResolve {
|
||||||
|
firstResolve = false
|
||||||
|
supportIPv4, supportIPv6 := utils.CheckRoutes()
|
||||||
|
if supportIPv4 {
|
||||||
|
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip4", domain)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 && supportIPv6 {
|
||||||
|
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip6", domain)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return errors.New("failed to get IP address for domain ", domain)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip", domain)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 { // SRV/TXT, lookup failed
|
||||||
|
return errors.New("failed to get IP address for domain ", domain)
|
||||||
|
}
|
||||||
|
if addr := net.IPAddress(ips[dice.Roll(len(ips))]); addr != nil {
|
||||||
|
dialDest.Address = addr
|
||||||
|
errors.LogInfo(ctx, "dialing to ", dialDest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rule := h.matchFinalRule(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
|
||||||
|
blockedDest = &dialDest
|
||||||
|
blockedRule = rule
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
rawConn, err := dialer.Dial(ctx, destination)
|
rawConn, err := dialer.Dial(ctx, dialDest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -348,17 +350,20 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
return errors.New("failed to open connection to ", destination).Base(err)
|
return errors.New("failed to open connection to ", destination).Base(err)
|
||||||
}
|
}
|
||||||
if blockedDest != nil {
|
if blockedDest != nil {
|
||||||
return h.blackhole(ctx, input, output, blockedRule, blockedDest)
|
delay := h.blockDelay(blockedRule)
|
||||||
}
|
errors.LogInfo(ctx, "blocked target: ", *blockedDest, ", blackholing connection for ", delay)
|
||||||
if destination.Address.Family().IsDomain() && (defaultRule != nil || len(h.finalRules) > 0) {
|
timer := time.AfterFunc(delay, func() {
|
||||||
// pre-check may fail or dialer may select another IP
|
common.Interrupt(input)
|
||||||
remoteDest := net.DestinationFromAddr(conn.RemoteAddr())
|
common.Interrupt(output)
|
||||||
if rule := h.matchFinalRule(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
|
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *blockedDest)
|
||||||
conn.Close()
|
})
|
||||||
return h.blackhole(ctx, input, output, rule, &remoteDest)
|
defer timer.Stop()
|
||||||
|
defer common.Close(output)
|
||||||
|
if err := buf.Copy(input, buf.Discard); err != nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
|
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
|
||||||
version := byte(h.config.ProxyProtocol)
|
version := byte(h.config.ProxyProtocol)
|
||||||
srcAddr := inbound.Source.RawNetAddr()
|
srcAddr := inbound.Source.RawNetAddr()
|
||||||
@@ -403,7 +408,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
writer = buf.NewWriter(conn)
|
writer = buf.NewWriter(conn)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination, outGateway)
|
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination)
|
||||||
if h.config.Noises != nil {
|
if h.config.Noises != nil {
|
||||||
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
||||||
writer = &NoisePacketWriter{
|
writer = &NoisePacketWriter{
|
||||||
@@ -507,7 +512,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|||||||
}
|
}
|
||||||
udpAddr := d.(*net.UDPAddr)
|
udpAddr := d.(*net.UDPAddr)
|
||||||
sourceAddr := net.IPAddress(udpAddr.IP)
|
sourceAddr := net.IPAddress(udpAddr.IP)
|
||||||
if rule := r.Handler.matchFinalRule(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule); rule != nil && rule.action == RuleAction_Block {
|
if r.Handler.applyFinalRules(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule) == RuleAction_Block {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
b.Resize(0, int32(n))
|
b.Resize(0, int32(n))
|
||||||
@@ -532,7 +537,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialDest means the dial target used in the dialer when creating conn
|
// DialDest means the dial target used in the dialer when creating conn
|
||||||
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination, outGateway net.Address) buf.Writer {
|
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Writer {
|
||||||
iConn := conn
|
iConn := conn
|
||||||
statConn, ok := iConn.(*stat.CounterConnection)
|
statConn, ok := iConn.(*stat.CounterConnection)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -556,8 +561,9 @@ func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverr
|
|||||||
DefaultRule: defaultRule,
|
DefaultRule: defaultRule,
|
||||||
UDPOverride: UDPOverride,
|
UDPOverride: UDPOverride,
|
||||||
ResolvedUDPAddr: resolvedUDPAddr,
|
ResolvedUDPAddr: resolvedUDPAddr,
|
||||||
OutGateway: outGateway,
|
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return &buf.SequentialWriter{Writer: conn}
|
return &buf.SequentialWriter{Writer: conn}
|
||||||
}
|
}
|
||||||
@@ -574,7 +580,7 @@ type PacketWriter struct {
|
|||||||
// Resulting in these packets being sent to many different IPs randomly
|
// Resulting in these packets being sent to many different IPs randomly
|
||||||
// So, cache and keep the resolve result
|
// So, cache and keep the resolve result
|
||||||
ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address]
|
ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address]
|
||||||
OutGateway net.Address
|
LocalAddr net.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||||
@@ -597,21 +603,21 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
|
if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
|
||||||
b.UDP.Address = ip
|
b.UDP.Address = ip
|
||||||
} else {
|
} else {
|
||||||
shouldUseSystemResolver := true
|
ShouldUseSystemResolver := true
|
||||||
if strategy := w.Handler.resolveStrategy; strategy.HasStrategy() {
|
if w.Handler.config.DomainStrategy.HasStrategy() {
|
||||||
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), strategy, w.OutGateway)
|
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), w.Handler.config.DomainStrategy, w.LocalAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// drop packet if resolve failed when forceIP
|
// drop packet if resolve failed when forceIP
|
||||||
if strategy.ForceIP() {
|
if w.Handler.config.DomainStrategy.ForceIP() {
|
||||||
b.Release()
|
b.Release()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ip = net.IPAddress(ips[dice.Roll(len(ips))])
|
ip = net.IPAddress(ips[dice.Roll(len(ips))])
|
||||||
shouldUseSystemResolver = false
|
ShouldUseSystemResolver = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if shouldUseSystemResolver {
|
if ShouldUseSystemResolver {
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
|
udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Release()
|
b.Release()
|
||||||
@@ -625,7 +631,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rule := w.matchFinalRule(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule); rule != nil && rule.action == RuleAction_Block {
|
if w.applyFinalRules(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule) == RuleAction_Block {
|
||||||
b.Release()
|
b.Release()
|
||||||
continue
|
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)
|
return nil, errors.New("failed to read http 1xx response").Base(err)
|
||||||
}
|
}
|
||||||
ResponseHeader1xx = append(ResponseHeader1xx, data...)
|
ResponseHeader1xx = append(ResponseHeader1xx, data...)
|
||||||
if len(ResponseHeader1xx) >= 4 && bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
|
if bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if len(ResponseHeader1xx) > 1024 {
|
if len(ResponseHeader1xx) > 1024 {
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A malformed upstream response containing a bare '\n' before the real
|
|
||||||
// status line used to crash readResponseAndHandle100Continue: the first
|
|
||||||
// ReadSlice('\n') returns fewer than 4 bytes, and slicing
|
|
||||||
// ResponseHeader1xx[len(ResponseHeader1xx)-4:] panicked with a negative
|
|
||||||
// index instead of returning an error.
|
|
||||||
func TestReadResponseAndHandle100ContinueDoesNotPanicOnEarlyNewline(t *testing.T) {
|
|
||||||
payload := "X\nHTTP/1.1 100 Continue\r\n\r\n" + strings.Repeat("A", 40)
|
|
||||||
r := bufio.NewReader(bytes.NewReader([]byte(payload)))
|
|
||||||
req, err := http.NewRequest("GET", "http://example.com/", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must not panic; a parse error for the garbage trailing bytes is fine.
|
|
||||||
_, _ = readResponseAndHandle100Continue(r, req, io.Discard)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testing.T) {
|
|
||||||
payload := "HTTP/1.1 100 Continue\r\n\r\n" +
|
|
||||||
"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello"
|
|
||||||
r := bufio.NewReader(bytes.NewReader([]byte(payload)))
|
|
||||||
req, err := http.NewRequest("GET", "http://example.com/", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var forwarded bytes.Buffer
|
|
||||||
resp, err := readResponseAndHandle100Continue(r, req, &forwarded)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
t.Fatalf("expected status 200, got %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
if !strings.Contains(forwarded.String(), "100 Continue") {
|
|
||||||
t.Fatalf("expected 1xx response to be forwarded, got %q", forwarded.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -236,14 +236,14 @@ type UDPReader struct {
|
|||||||
|
|
||||||
func (r *UDPReader) ReadFrom(p []byte) (n int, addr *net.Destination, err error) {
|
func (r *UDPReader) ReadFrom(p []byte) (n int, addr *net.Destination, err error) {
|
||||||
for {
|
for {
|
||||||
var packet [1500]byte
|
var buf [hysteria.MaxDatagramFrameSize]byte
|
||||||
|
|
||||||
n, err := r.reader.Read(packet[:])
|
n, err := r.reader.Read(buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := ParseUDPMessage(packet[:n])
|
msg, err := ParseUDPMessage(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to connect to server").Base(err)
|
return errors.New("failed to connect to server").Base(err)
|
||||||
}
|
}
|
||||||
defer connection.Close()
|
|
||||||
|
|
||||||
if session.TimeoutOnlyFromContext(ctx) {
|
if session.TimeoutOnlyFromContext(ctx) {
|
||||||
ctx, _ = context.WithCancel(context.Background())
|
ctx, _ = context.WithCancel(context.Background())
|
||||||
|
|||||||
+2
-11
@@ -32,7 +32,6 @@ type Config struct {
|
|||||||
AutoSystemRoutingTable []string `protobuf:"bytes,6,rep,name=auto_system_routing_table,json=autoSystemRoutingTable,proto3" json:"auto_system_routing_table,omitempty"`
|
AutoSystemRoutingTable []string `protobuf:"bytes,6,rep,name=auto_system_routing_table,json=autoSystemRoutingTable,proto3" json:"auto_system_routing_table,omitempty"`
|
||||||
AutoOutboundsInterface string `protobuf:"bytes,7,opt,name=auto_outbounds_interface,json=autoOutboundsInterface,proto3" json:"auto_outbounds_interface,omitempty"`
|
AutoOutboundsInterface string `protobuf:"bytes,7,opt,name=auto_outbounds_interface,json=autoOutboundsInterface,proto3" json:"auto_outbounds_interface,omitempty"`
|
||||||
Desc string `protobuf:"bytes,8,opt,name=desc,proto3" json:"desc,omitempty"`
|
Desc string `protobuf:"bytes,8,opt,name=desc,proto3" json:"desc,omitempty"`
|
||||||
Stack string `protobuf:"bytes,9,opt,name=stack,proto3" json:"stack,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -123,18 +122,11 @@ func (x *Config) GetDesc() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetStack() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Stack
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_proxy_tun_config_proto protoreflect.FileDescriptor
|
var File_proxy_tun_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_proxy_tun_config_proto_rawDesc = "" +
|
const file_proxy_tun_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\x98\x02\n" +
|
"\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\x82\x02\n" +
|
||||||
"\x06Config\x12\x12\n" +
|
"\x06Config\x12\x12\n" +
|
||||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" +
|
"\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" +
|
||||||
"\x03MTU\x18\x02 \x01(\rR\x03MTU\x12\x18\n" +
|
"\x03MTU\x18\x02 \x01(\rR\x03MTU\x12\x18\n" +
|
||||||
@@ -144,8 +136,7 @@ const file_proxy_tun_config_proto_rawDesc = "" +
|
|||||||
"user_level\x18\x05 \x01(\rR\tuserLevel\x129\n" +
|
"user_level\x18\x05 \x01(\rR\tuserLevel\x129\n" +
|
||||||
"\x19auto_system_routing_table\x18\x06 \x03(\tR\x16autoSystemRoutingTable\x128\n" +
|
"\x19auto_system_routing_table\x18\x06 \x03(\tR\x16autoSystemRoutingTable\x128\n" +
|
||||||
"\x18auto_outbounds_interface\x18\a \x01(\tR\x16autoOutboundsInterface\x12\x12\n" +
|
"\x18auto_outbounds_interface\x18\a \x01(\tR\x16autoOutboundsInterface\x12\x12\n" +
|
||||||
"\x04desc\x18\b \x01(\tR\x04desc\x12\x14\n" +
|
"\x04desc\x18\b \x01(\tR\x04descBL\n" +
|
||||||
"\x05stack\x18\t \x01(\tR\x05stackBL\n" +
|
|
||||||
"\x12com.xray.proxy.tunP\x01Z#github.com/xtls/xray-core/proxy/tun\xaa\x02\x0eXray.Proxy.Tunb\x06proto3"
|
"\x12com.xray.proxy.tunP\x01Z#github.com/xtls/xray-core/proxy/tun\xaa\x02\x0eXray.Proxy.Tunb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -15,5 +15,4 @@ message Config {
|
|||||||
repeated string auto_system_routing_table = 6;
|
repeated string auto_system_routing_table = 6;
|
||||||
string auto_outbounds_interface = 7;
|
string auto_outbounds_interface = 7;
|
||||||
string desc = 8;
|
string desc = 8;
|
||||||
string stack = 9;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-35
@@ -37,25 +37,6 @@ type Handler struct {
|
|||||||
downlinkCounter stats.Counter
|
downlinkCounter stats.Counter
|
||||||
}
|
}
|
||||||
|
|
||||||
type tunUDPStatsWriter struct {
|
|
||||||
writer buf.Writer
|
|
||||||
counter stats.Counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *tunUDPStatsWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
||||||
for len(mb) > 0 {
|
|
||||||
remaining, packet := buf.SplitFirst(mb)
|
|
||||||
packetSize := packet.Len()
|
|
||||||
if err := w.writer.WriteMultiBuffer(buf.MultiBuffer{packet}); err != nil {
|
|
||||||
buf.ReleaseMulti(remaining)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.counter.Add(int64(packetSize))
|
|
||||||
mb = remaining
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectionHandler interface with the only method that stack is going to push new connections to
|
// ConnectionHandler interface with the only method that stack is going to push new connections to
|
||||||
type ConnectionHandler interface {
|
type ConnectionHandler interface {
|
||||||
HandleConnection(conn net.Conn, destination net.Destination)
|
HandleConnection(conn net.Conn, destination net.Destination)
|
||||||
@@ -123,7 +104,7 @@ func (t *Handler) Start() error {
|
|||||||
iface := updater.Get()
|
iface := updater.Get()
|
||||||
if iface == nil {
|
if iface == nil {
|
||||||
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
|
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
|
||||||
return errors.New("iface not found")
|
return nil
|
||||||
}
|
}
|
||||||
return c.Control(func(fd uintptr) {
|
return c.Control(func(fd uintptr) {
|
||||||
addrPort, _ := netip.ParseAddrPort(address)
|
addrPort, _ := netip.ParseAddrPort(address)
|
||||||
@@ -143,9 +124,7 @@ func (t *Handler) Start() error {
|
|||||||
|
|
||||||
tunStackOptions := StackOptions{
|
tunStackOptions := StackOptions{
|
||||||
Tun: tunInterface,
|
Tun: tunInterface,
|
||||||
MTU: t.config.MTU,
|
|
||||||
IdleTimeout: t.policyManager.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,
|
IdleTimeout: t.policyManager.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,
|
||||||
Backend: t.config.Stack,
|
|
||||||
}
|
}
|
||||||
tunStack, err := NewStack(t.ctx, tunStackOptions, t)
|
tunStack, err := NewStack(t.ctx, tunStackOptions, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -192,8 +171,7 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
source := net.DestinationFromAddr(remote)
|
source := net.DestinationFromAddr(remote)
|
||||||
isUDP := destination.Network == net.Network_UDP
|
if t.uplinkCounter != nil || t.downlinkCounter != nil {
|
||||||
if !isUDP && (t.uplinkCounter != nil || t.downlinkCounter != nil) {
|
|
||||||
conn = &stat.CounterConnection{
|
conn = &stat.CounterConnection{
|
||||||
Connection: conn,
|
Connection: conn,
|
||||||
ReadCounter: t.uplinkCounter,
|
ReadCounter: t.uplinkCounter,
|
||||||
@@ -225,18 +203,9 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
|||||||
})
|
})
|
||||||
errors.LogInfo(ctx, "processing from ", source, " to ", destination)
|
errors.LogInfo(ctx, "processing from ", source, " to ", destination)
|
||||||
|
|
||||||
reader := &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)}
|
|
||||||
writer := buf.NewWriter(conn)
|
|
||||||
if isUDP {
|
|
||||||
reader.Counter = t.uplinkCounter
|
|
||||||
if t.downlinkCounter != nil {
|
|
||||||
writer = &tunUDPStatsWriter{writer: writer, counter: t.downlinkCounter}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
link := &transport.Link{
|
link := &transport.Link{
|
||||||
Reader: reader,
|
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
||||||
Writer: writer,
|
Writer: buf.NewWriter(conn),
|
||||||
}
|
}
|
||||||
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
|
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
|
||||||
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stack interface implement ip protocol stack, bridging raw network packets and data streams
|
// Stack interface implement ip protocol stack, bridging raw network packets and data streams
|
||||||
@@ -16,39 +13,5 @@ type Stack interface {
|
|||||||
// StackOptions for the stack implementation
|
// StackOptions for the stack implementation
|
||||||
type StackOptions struct {
|
type StackOptions struct {
|
||||||
Tun Tun
|
Tun Tun
|
||||||
MTU uint32
|
|
||||||
IdleTimeout time.Duration
|
IdleTimeout time.Duration
|
||||||
// Backend selects the concrete Stack implementation, see NewStack.
|
|
||||||
Backend string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StackGVisor selects the full-featured gVisor based stack (default).
|
|
||||||
StackGVisor = "gvisor"
|
|
||||||
// StackSystem selects the lightweight, Xray-native stack, see newSystemStack.
|
|
||||||
StackSystem = "system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewStack builds the ip stack selected by options.Backend.
|
|
||||||
//
|
|
||||||
// gVisor (the default/"gvisor" backend) is a general purpose stack, built
|
|
||||||
// with the semantics needed for a real, lossy public network in mind:
|
|
||||||
// congestion control, SACK/RACK loss recovery, retransmission timers, etc.
|
|
||||||
// TUN traffic instead travels over a local, kernel-to-userspace channel that
|
|
||||||
// neither reorders nor drops packets in normal operation, so none of that
|
|
||||||
// complexity is actually required to shuffle bytes between it and the
|
|
||||||
// dispatcher. The "system" backend trades gVisor's generality for a much
|
|
||||||
// smaller, more direct code path tailored to that trusted, in-order channel:
|
|
||||||
// no congestion control, no SACK/RACK, minimal buffering, and a plain RTO
|
|
||||||
// timer as a safety net for the rare real loss, rather than a full
|
|
||||||
// re-implementation of one. See stack_system.go for details.
|
|
||||||
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
|
|
||||||
switch options.Backend {
|
|
||||||
case "", StackGVisor:
|
|
||||||
return newGVisorStack(ctx, options, handler)
|
|
||||||
case StackSystem:
|
|
||||||
return newSystemStack(ctx, options, handler)
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown tun stack: ", options.Backend)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ type stackGVisor struct {
|
|||||||
endpoint stack.LinkEndpoint
|
endpoint stack.LinkEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// newGVisorStack builds new ip stack (using gVisor)
|
// NewStack builds new ip stack (using gVisor)
|
||||||
func newGVisorStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
|
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
|
||||||
gStack := &stackGVisor{
|
gStack := &stackGVisor{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
tun: options.Tun,
|
tun: options.Tun,
|
||||||
|
|||||||
@@ -1,370 +0,0 @@
|
|||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
xerrors "github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
tunicmp "github.com/xtls/xray-core/proxy/tun/icmp"
|
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
)
|
|
||||||
|
|
||||||
// stackSystem is the lightweight, Xray-native ip stack, see NewStack.
|
|
||||||
//
|
|
||||||
// It reads and parses IPv4/IPv6 packets directly off the tun device (through
|
|
||||||
// the GVisorDevice interface, already implemented for every supported
|
|
||||||
// platform), without involving gVisor's stack.Stack, NIC or routing
|
|
||||||
// machinery. UDP and ICMP echo reuse the exact same handlers as the gVisor
|
|
||||||
// backend (udpConnectionHandler, tun/icmp) since those were already
|
|
||||||
// implemented in terms of raw bytes. TCP is handled by a small dedicated
|
|
||||||
// state machine, see stack_system_tcp.go.
|
|
||||||
type stackSystem struct {
|
|
||||||
ctx context.Context
|
|
||||||
device GVisorDevice
|
|
||||||
mtu uint32
|
|
||||||
idleTimeout time.Duration
|
|
||||||
// handler is stored as the narrower ConnectionHandler interface (which
|
|
||||||
// *Handler satisfies) rather than *Handler itself, so the stack can be
|
|
||||||
// exercised in tests with a lightweight fake, the same way stack_system_test.go does.
|
|
||||||
handler ConnectionHandler
|
|
||||||
|
|
||||||
udp *udpConnectionHandler
|
|
||||||
|
|
||||||
tcpMu sync.Mutex
|
|
||||||
tcp map[tcpKey]*tcpConn
|
|
||||||
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
const systemStackDefaultMTU = 1500
|
|
||||||
|
|
||||||
// newSystemStack builds the lightweight "system" ip stack, see NewStack.
|
|
||||||
func newSystemStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
|
|
||||||
device, ok := options.Tun.(GVisorDevice)
|
|
||||||
if !ok {
|
|
||||||
return nil, xerrors.New("tun stack \"system\" is not supported by this tun device")
|
|
||||||
}
|
|
||||||
mtu := options.MTU
|
|
||||||
if mtu == 0 {
|
|
||||||
mtu = systemStackDefaultMTU
|
|
||||||
}
|
|
||||||
return &stackSystem{
|
|
||||||
ctx: ctx,
|
|
||||||
device: device,
|
|
||||||
mtu: mtu,
|
|
||||||
idleTimeout: options.IdleTimeout,
|
|
||||||
handler: handler,
|
|
||||||
tcp: make(map[tcpKey]*tcpConn),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start is called by Handler to bring the stack to life
|
|
||||||
func (s *stackSystem) Start() error {
|
|
||||||
ctx, cancel := context.WithCancel(s.ctx)
|
|
||||||
s.cancel = cancel
|
|
||||||
s.udp = newUdpConnectionHandler(s.handler.HandleConnection, s.writeRawUDPPacket)
|
|
||||||
|
|
||||||
go s.dispatchLoop(ctx)
|
|
||||||
go s.idleReapLoop(ctx)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is called by Handler to shut down the stack
|
|
||||||
func (s *stackSystem) Close() error {
|
|
||||||
if s.cancel != nil {
|
|
||||||
s.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
s.tcpMu.Lock()
|
|
||||||
conns := make([]*tcpConn, 0, len(s.tcp))
|
|
||||||
for _, c := range s.tcp {
|
|
||||||
conns = append(conns, c)
|
|
||||||
}
|
|
||||||
s.tcp = make(map[tcpKey]*tcpConn)
|
|
||||||
s.tcpMu.Unlock()
|
|
||||||
|
|
||||||
for _, c := range conns {
|
|
||||||
c.abort(errStackClosed)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatchLoop reads and demultiplexes packets off the tun device, until ctx
|
|
||||||
// is cancelled or the device fails permanently. It mirrors LinkEndpoint's own
|
|
||||||
// dispatchLoop (stack_gvisor_endpoint.go), reusing the exact same GVisorDevice
|
|
||||||
// contract, but hands packets to this file's own IPv4/IPv6 parsing instead of
|
|
||||||
// gVisor's NIC/stack.Stack.
|
|
||||||
func (s *stackSystem) dispatchLoop(ctx context.Context) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
version, packet, err := s.device.ReadPacket()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, ErrQueueEmpty) {
|
|
||||||
s.device.Wait()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.handlePacket(version, packet)
|
|
||||||
packet.DecRef()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) handlePacket(version byte, packet *stack.PacketBuffer) {
|
|
||||||
data := concatSlices(packet.AsSlices())
|
|
||||||
if len(data) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch version {
|
|
||||||
case 4:
|
|
||||||
s.handleIPv4(data)
|
|
||||||
case 6:
|
|
||||||
s.handleIPv6(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func concatSlices(slices [][]byte) []byte {
|
|
||||||
if len(slices) == 1 {
|
|
||||||
return slices[0]
|
|
||||||
}
|
|
||||||
total := 0
|
|
||||||
for _, sl := range slices {
|
|
||||||
total += len(sl)
|
|
||||||
}
|
|
||||||
if total == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data := make([]byte, 0, total)
|
|
||||||
for _, sl := range slices {
|
|
||||||
data = append(data, sl...)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) handleIPv4(data []byte) {
|
|
||||||
hdr := header.IPv4(data)
|
|
||||||
if !hdr.IsValid(len(data)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// fragmentation is not supported: the tun MTU is expected to keep locally
|
|
||||||
// generated packets from ever needing it, same as the gVisor backend's
|
|
||||||
// default configuration
|
|
||||||
if hdr.More() || hdr.FragmentOffset() != 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.handleTransport(header.IPv4ProtocolNumber, hdr.TransportProtocol(), hdr.SourceAddress(), hdr.DestinationAddress(), hdr.Payload())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) handleIPv6(data []byte) {
|
|
||||||
hdr := header.IPv6(data)
|
|
||||||
if !hdr.IsValid(len(data)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// only directly-encapsulated transport headers are handled, IPv6
|
|
||||||
// extension headers (rare for ordinary locally generated traffic) are not
|
|
||||||
// walked, same limitation as the fragmentation one above
|
|
||||||
s.handleTransport(header.IPv6ProtocolNumber, hdr.TransportProtocol(), hdr.SourceAddress(), hdr.DestinationAddress(), hdr.Payload())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) handleTransport(netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, srcIP, dstIP tcpip.Address, payload []byte) {
|
|
||||||
switch transProto {
|
|
||||||
case header.TCPProtocolNumber:
|
|
||||||
s.handleTCP(netProto, srcIP, dstIP, payload)
|
|
||||||
case header.UDPProtocolNumber:
|
|
||||||
s.handleUDP(netProto, srcIP, dstIP, payload)
|
|
||||||
case header.ICMPv4ProtocolNumber:
|
|
||||||
if netProto == header.IPv4ProtocolNumber {
|
|
||||||
s.handleICMP(netProto, srcIP, dstIP, payload)
|
|
||||||
}
|
|
||||||
case header.ICMPv6ProtocolNumber:
|
|
||||||
if netProto == header.IPv6ProtocolNumber {
|
|
||||||
s.handleICMP(netProto, srcIP, dstIP, payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) handleUDP(netProto tcpip.NetworkProtocolNumber, srcIP, dstIP tcpip.Address, payload []byte) {
|
|
||||||
if len(payload) < header.UDPMinimumSize {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
udpHdr := header.UDP(payload)
|
|
||||||
length := udpHdr.Length()
|
|
||||||
if int(length) < header.UDPMinimumSize || int(length) > len(payload) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// source/destination of the packet we process as incoming are, in other terms,
|
|
||||||
// src is the side behind tun, dst is the side behind the dispatcher
|
|
||||||
src := net.UDPDestination(net.IPAddress(srcIP.AsSlice()), net.Port(udpHdr.SourcePort()))
|
|
||||||
dst := net.UDPDestination(net.IPAddress(dstIP.AsSlice()), net.Port(udpHdr.DestinationPort()))
|
|
||||||
s.udp.HandlePacket(src, dst, payload[header.UDPMinimumSize:length])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) handleICMP(netProto tcpip.NetworkProtocolNumber, srcIP, dstIP tcpip.Address, message []byte) {
|
|
||||||
ident, sequence, ok := tunicmp.ParseEchoRequest(netProto, message)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reply, err := tunicmp.BuildLocalEchoReply(netProto, message, dstIP, srcIP)
|
|
||||||
if err != nil {
|
|
||||||
xerrors.LogInfoInner(s.ctx, err, "[tun] failed to build local icmp echo reply")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
xerrors.LogDebug(s.ctx, "[tun][icmp] ", tunicmp.ProtocolLabel(netProto), " local echo reply ", dstIP, " -> ", srcIP, " id=", ident, " seq=", sequence)
|
|
||||||
|
|
||||||
transProto := header.ICMPv4ProtocolNumber
|
|
||||||
if netProto == header.IPv6ProtocolNumber {
|
|
||||||
transProto = header.ICMPv6ProtocolNumber
|
|
||||||
}
|
|
||||||
if err := s.writeTransportSegment(netProto, tcpip.TransportProtocolNumber(transProto), dstIP, srcIP, reply); err != nil {
|
|
||||||
xerrors.LogInfoInner(s.ctx, err, "[tun] failed to write local icmp echo reply")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error {
|
|
||||||
udpLen := header.UDPMinimumSize + len(payload)
|
|
||||||
srcIP := tcpip.AddrFromSlice(src.Address.IP())
|
|
||||||
dstIP := tcpip.AddrFromSlice(dst.Address.IP())
|
|
||||||
|
|
||||||
netProto := header.IPv4ProtocolNumber
|
|
||||||
if !dst.Address.Family().IsIPv4() {
|
|
||||||
netProto = header.IPv6ProtocolNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
segment := make([]byte, udpLen)
|
|
||||||
udpHdr := header.UDP(segment)
|
|
||||||
udpHdr.Encode(&header.UDPFields{
|
|
||||||
SrcPort: uint16(src.Port),
|
|
||||||
DstPort: uint16(dst.Port),
|
|
||||||
Length: uint16(udpLen),
|
|
||||||
})
|
|
||||||
copy(segment[header.UDPMinimumSize:], payload)
|
|
||||||
|
|
||||||
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))
|
|
||||||
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
|
|
||||||
|
|
||||||
return s.writeTransportSegment(netProto, header.UDPProtocolNumber, srcIP, dstIP, segment)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeTransportSegment wraps a fully built, already checksummed transport
|
|
||||||
// layer segment (UDP, ICMP or TCP) with an IP header and writes it to the tun
|
|
||||||
// device.
|
|
||||||
func (s *stackSystem) writeTransportSegment(netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, srcIP, dstIP tcpip.Address, segment []byte) error {
|
|
||||||
ipHdrSize := header.IPv4MinimumSize
|
|
||||||
if netProto == header.IPv6ProtocolNumber {
|
|
||||||
ipHdrSize = header.IPv6MinimumSize
|
|
||||||
}
|
|
||||||
|
|
||||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
||||||
ReserveHeaderBytes: ipHdrSize,
|
|
||||||
Payload: buffer.MakeWithData(segment),
|
|
||||||
})
|
|
||||||
defer pkt.DecRef()
|
|
||||||
|
|
||||||
if netProto == header.IPv4ProtocolNumber {
|
|
||||||
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
|
|
||||||
ipHdr.Encode(&header.IPv4Fields{
|
|
||||||
TotalLength: uint16(header.IPv4MinimumSize + len(segment)),
|
|
||||||
TTL: 64,
|
|
||||||
Protocol: uint8(transProto),
|
|
||||||
SrcAddr: srcIP,
|
|
||||||
DstAddr: dstIP,
|
|
||||||
})
|
|
||||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
||||||
} else {
|
|
||||||
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
|
|
||||||
ipHdr.Encode(&header.IPv6Fields{
|
|
||||||
PayloadLength: uint16(len(segment)),
|
|
||||||
TransportProtocol: transProto,
|
|
||||||
HopLimit: 64,
|
|
||||||
SrcAddr: srcIP,
|
|
||||||
DstAddr: dstIP,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.device.WritePacket(pkt); err != nil {
|
|
||||||
return xerrors.New("failed to write raw packet: ", err.String())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// idleReapLoop periodically aborts tcp connections that have seen no traffic
|
|
||||||
// for longer than idleTimeout, finally putting that option to use (it was
|
|
||||||
// tracked but never read anywhere before the "system" backend existed).
|
|
||||||
func (s *stackSystem) idleReapLoop(ctx context.Context) {
|
|
||||||
if s.idleTimeout <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
interval := s.idleTimeout / 4
|
|
||||||
if interval < time.Second {
|
|
||||||
interval = time.Second
|
|
||||||
}
|
|
||||||
ticker := time.NewTicker(interval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
s.reapIdleConnections()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) reapIdleConnections() {
|
|
||||||
deadline := time.Now().Add(-s.idleTimeout)
|
|
||||||
|
|
||||||
s.tcpMu.Lock()
|
|
||||||
var idle []*tcpConn
|
|
||||||
for _, c := range s.tcp {
|
|
||||||
if c.lastActiveTime().Before(deadline) {
|
|
||||||
idle = append(idle, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.tcpMu.Unlock()
|
|
||||||
|
|
||||||
for _, c := range idle {
|
|
||||||
c.abort(errConnIdleTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) removeTCPConn(key tcpKey, c *tcpConn) {
|
|
||||||
s.tcpMu.Lock()
|
|
||||||
if existing, ok := s.tcp[key]; ok && existing == c {
|
|
||||||
delete(s.tcp, key)
|
|
||||||
}
|
|
||||||
s.tcpMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// randomSequenceNumber returns a random initial sequence number for a new
|
|
||||||
// connection. It doesn't need to be cryptographically unpredictable (the tun
|
|
||||||
// channel is local and trusted), just varied enough to avoid confusion with
|
|
||||||
// prior incarnations of the same 4-tuple.
|
|
||||||
func randomSequenceNumber() seqnum.Value {
|
|
||||||
var b [4]byte
|
|
||||||
_, _ = rand.Read(b[:])
|
|
||||||
return seqnum.Value(binary.BigEndian.Uint32(b[:]))
|
|
||||||
}
|
|
||||||
@@ -1,725 +0,0 @@
|
|||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
xerrors "github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file implements a small, dedicated TCP state machine for the "system"
|
|
||||||
// tun stack, see stack_system.go. It intentionally does not implement window
|
|
||||||
// scaling, SACK, timestamps, congestion control, fast retransmit or
|
|
||||||
// out-of-order reassembly: the tun channel only ever carries packets produced
|
|
||||||
// by the local OS network stack and handed to us directly, so it neither
|
|
||||||
// reorders nor drops them the way the public internet does; a single RTO
|
|
||||||
// timer (also used for zero-window probing) is enough to make the connection
|
|
||||||
// robust against the rare occasions a segment does not make it through.
|
|
||||||
const (
|
|
||||||
minRTO = 300 * time.Millisecond
|
|
||||||
maxRTO = 30 * time.Second
|
|
||||||
maxRTORetries = 12
|
|
||||||
lingerDuration = 5 * time.Second
|
|
||||||
|
|
||||||
// maxSendBuffer/maxRecvBuffer match the gVisor backend's own default
|
|
||||||
// buffer sizes (tcp.DefaultSendBufferSize/DefaultReceiveBufferSize), so
|
|
||||||
// switching between backends does not change buffering expectations.
|
|
||||||
maxSendBuffer = 1 << 20
|
|
||||||
maxRecvBuffer = 1 << 20
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errStackClosed = xerrors.New("tun stack closed")
|
|
||||||
errConnReset = xerrors.New("connection reset by peer")
|
|
||||||
errConnClosed = xerrors.New("use of closed network connection")
|
|
||||||
errConnIdleTimeout = xerrors.New("connection idle timeout")
|
|
||||||
errConnTimedOut = xerrors.New("connection timed out")
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpState uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
stateSynRcvd tcpState = iota
|
|
||||||
stateEstablished
|
|
||||||
stateCloseWait // peer's FIN was received; we may still send until we close too
|
|
||||||
stateClosing // our FIN was sent (from Established or CloseWait)
|
|
||||||
stateTimeWait // both FINs exchanged and acked; short linger before removal
|
|
||||||
stateClosed // terminal, removed from the connection table
|
|
||||||
)
|
|
||||||
|
|
||||||
// tcpKey identifies a tcp connection the same way it appears on the wire
|
|
||||||
// flowing from the app behind the tun device towards its destination.
|
|
||||||
type tcpKey struct {
|
|
||||||
netProto tcpip.NetworkProtocolNumber
|
|
||||||
srcAddr tcpip.Address
|
|
||||||
srcPort uint16
|
|
||||||
dstAddr tcpip.Address
|
|
||||||
dstPort uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// tcpConn is a minimal TCP endpoint implementing net.Conn. It deliberately
|
|
||||||
// exposes only plain Read/Write (never ReadMultiBuffer/WriteMultiBuffer) so
|
|
||||||
// that stat.CounterConnection in handler.go keeps accounting traffic
|
|
||||||
// correctly, matching the udpConn precedent in udp_fullcone.go.
|
|
||||||
type tcpConn struct {
|
|
||||||
stack *stackSystem
|
|
||||||
key tcpKey
|
|
||||||
src net.Destination
|
|
||||||
dst net.Destination
|
|
||||||
|
|
||||||
ourMSS int
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
cond *sync.Cond
|
|
||||||
|
|
||||||
state tcpState
|
|
||||||
|
|
||||||
// send side. sendQueue[0] always holds the byte at sequence sndUna: acked
|
|
||||||
// bytes are trimmed off the front, so no separate "acked" bookkeeping is
|
|
||||||
// needed. sendQueue[:unsentOffset] has been transmitted at least once;
|
|
||||||
// sendQueue[unsentOffset:] never has.
|
|
||||||
iss seqnum.Value
|
|
||||||
sndUna seqnum.Value
|
|
||||||
sndNxt seqnum.Value
|
|
||||||
sndMSS int
|
|
||||||
peerWindow uint32
|
|
||||||
sendQueue []byte
|
|
||||||
unsentOffset int
|
|
||||||
closeCalled bool
|
|
||||||
finSent bool
|
|
||||||
finAcked bool
|
|
||||||
finSeq seqnum.Value
|
|
||||||
|
|
||||||
// receive side.
|
|
||||||
irs seqnum.Value
|
|
||||||
rcvNxt seqnum.Value
|
|
||||||
recvQueue [][]byte
|
|
||||||
recvOffset int
|
|
||||||
recvBuffered int
|
|
||||||
recvClosed bool
|
|
||||||
|
|
||||||
err error
|
|
||||||
|
|
||||||
lastActive time.Time
|
|
||||||
|
|
||||||
rtoTimer *time.Timer
|
|
||||||
rtoBackoff int
|
|
||||||
lingerTimer *time.Timer
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Conn = (*tcpConn)(nil)
|
|
||||||
|
|
||||||
// outgoingMSS returns the MSS we can use without ever needing IP
|
|
||||||
// fragmentation (unsupported), given the tun device's MTU.
|
|
||||||
func outgoingMSS(mtu uint32, netProto tcpip.NetworkProtocolNumber) int {
|
|
||||||
ipHdrSize := header.IPv4MinimumSize
|
|
||||||
if netProto == header.IPv6ProtocolNumber {
|
|
||||||
ipHdrSize = header.IPv6MinimumSize
|
|
||||||
}
|
|
||||||
mss := int(mtu) - ipHdrSize - header.TCPMinimumSize
|
|
||||||
const minMSS = 88
|
|
||||||
if mss < minMSS {
|
|
||||||
mss = minMSS
|
|
||||||
}
|
|
||||||
return mss
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleTCP is the tcp entry point from stackSystem.handleTransport.
|
|
||||||
func (s *stackSystem) handleTCP(netProto tcpip.NetworkProtocolNumber, srcIP, dstIP tcpip.Address, payload []byte) {
|
|
||||||
if len(payload) < header.TCPMinimumSize {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tcpHdr := header.TCP(payload)
|
|
||||||
if _, _, ok := header.TCPValid(tcpHdr, nil, 0, tcpip.Address{}, tcpip.Address{}, true); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := tcpKey{
|
|
||||||
netProto: netProto,
|
|
||||||
srcAddr: srcIP,
|
|
||||||
srcPort: tcpHdr.SourcePort(),
|
|
||||||
dstAddr: dstIP,
|
|
||||||
dstPort: tcpHdr.DestinationPort(),
|
|
||||||
}
|
|
||||||
|
|
||||||
s.tcpMu.Lock()
|
|
||||||
conn, ok := s.tcp[key]
|
|
||||||
s.tcpMu.Unlock()
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
conn.handleSegment(tcpHdr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := tcpHdr.Flags()
|
|
||||||
if flags&header.TCPFlagRst != 0 {
|
|
||||||
return // never generate a reset in response to a reset
|
|
||||||
}
|
|
||||||
if flags&header.TCPFlagSyn != 0 && flags&header.TCPFlagAck == 0 {
|
|
||||||
s.newTCPConn(key, tcpHdr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// any other segment referencing an unknown connection: let the peer know
|
|
||||||
// promptly it no longer/never existed, same as a real kernel would
|
|
||||||
s.sendRawTCPReset(key, tcpHdr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackSystem) newTCPConn(key tcpKey, tcpHdr header.TCP) {
|
|
||||||
synOpts := header.ParseSynOptions(tcpHdr.Options(), false)
|
|
||||||
|
|
||||||
c := &tcpConn{
|
|
||||||
stack: s,
|
|
||||||
key: key,
|
|
||||||
src: net.TCPDestination(net.IPAddress(key.srcAddr.AsSlice()), net.Port(key.srcPort)),
|
|
||||||
dst: net.TCPDestination(net.IPAddress(key.dstAddr.AsSlice()), net.Port(key.dstPort)),
|
|
||||||
state: stateSynRcvd,
|
|
||||||
}
|
|
||||||
c.cond = sync.NewCond(&c.mu)
|
|
||||||
|
|
||||||
c.iss = randomSequenceNumber()
|
|
||||||
c.sndUna = c.iss
|
|
||||||
c.sndNxt = c.iss.Add(1)
|
|
||||||
|
|
||||||
c.irs = seqnum.Value(tcpHdr.SequenceNumber())
|
|
||||||
c.rcvNxt = c.irs.Add(1)
|
|
||||||
|
|
||||||
c.ourMSS = outgoingMSS(s.mtu, key.netProto)
|
|
||||||
c.sndMSS = int(synOpts.MSS)
|
|
||||||
if c.sndMSS <= 0 || c.sndMSS > c.ourMSS {
|
|
||||||
c.sndMSS = c.ourMSS
|
|
||||||
}
|
|
||||||
c.lastActive = time.Now()
|
|
||||||
|
|
||||||
s.tcpMu.Lock()
|
|
||||||
s.tcp[key] = c
|
|
||||||
s.tcpMu.Unlock()
|
|
||||||
|
|
||||||
c.mu.Lock()
|
|
||||||
c.sendSynAckLocked()
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendRawTCPReset replies to a segment that does not match any known
|
|
||||||
// connection, following the rules of RFC 9293 §3.10.7.1.
|
|
||||||
func (s *stackSystem) sendRawTCPReset(key tcpKey, tcpHdr header.TCP) {
|
|
||||||
flags := tcpHdr.Flags()
|
|
||||||
segLen := seqnum.Size(len(tcpHdr.Payload()))
|
|
||||||
if flags&header.TCPFlagSyn != 0 {
|
|
||||||
segLen++
|
|
||||||
}
|
|
||||||
if flags&header.TCPFlagFin != 0 {
|
|
||||||
segLen++
|
|
||||||
}
|
|
||||||
|
|
||||||
var seq, ack seqnum.Value
|
|
||||||
var ackFlag header.TCPFlags
|
|
||||||
if flags&header.TCPFlagAck != 0 {
|
|
||||||
seq = seqnum.Value(tcpHdr.AckNumber())
|
|
||||||
} else {
|
|
||||||
ack = seqnum.Value(tcpHdr.SequenceNumber()).Add(segLen)
|
|
||||||
ackFlag = header.TCPFlagAck
|
|
||||||
}
|
|
||||||
|
|
||||||
segment := make([]byte, header.TCPMinimumSize)
|
|
||||||
rst := header.TCP(segment)
|
|
||||||
rst.Encode(&header.TCPFields{
|
|
||||||
SrcPort: key.dstPort,
|
|
||||||
DstPort: key.srcPort,
|
|
||||||
SeqNum: uint32(seq),
|
|
||||||
AckNum: uint32(ack),
|
|
||||||
DataOffset: header.TCPMinimumSize,
|
|
||||||
Flags: header.TCPFlagRst | ackFlag,
|
|
||||||
WindowSize: 0,
|
|
||||||
})
|
|
||||||
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, key.dstAddr, key.srcAddr, uint16(len(segment)))
|
|
||||||
rst.SetChecksum(^rst.CalculateChecksum(xsum))
|
|
||||||
|
|
||||||
if err := s.writeTransportSegment(key.netProto, header.TCPProtocolNumber, key.dstAddr, key.srcAddr, segment); err != nil {
|
|
||||||
xerrors.LogInfoInner(s.ctx, err, "[tun] failed to write tcp reset")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) lastActiveTime() time.Time {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.lastActive
|
|
||||||
}
|
|
||||||
|
|
||||||
// abort is the externally callable (unlocked) equivalent of abortLocked,
|
|
||||||
// used by the idle reaper and by Close's callers indirectly through it.
|
|
||||||
func (c *tcpConn) abort(err error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.abortLocked(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) abortLocked(err error) {
|
|
||||||
if c.state == stateClosed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.state = stateClosed
|
|
||||||
c.stopRTOLocked()
|
|
||||||
c.stopLingerLocked()
|
|
||||||
c.err = err
|
|
||||||
c.cond.Broadcast()
|
|
||||||
// deliberately does not send an RST: if the peer sends anything else for
|
|
||||||
// this connection later, it will miss the (now removed) table entry and
|
|
||||||
// get a fresh, correctly-addressed reset from sendRawTCPReset above.
|
|
||||||
c.stack.removeTCPConn(c.key, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleSegment processes one already-demultiplexed incoming segment.
|
|
||||||
func (c *tcpConn) handleSegment(tcpHdr header.TCP) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if c.state == stateClosed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.lastActive = time.Now()
|
|
||||||
|
|
||||||
flags := tcpHdr.Flags()
|
|
||||||
|
|
||||||
if flags&header.TCPFlagRst != 0 {
|
|
||||||
c.abortLocked(errConnReset)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.state == stateSynRcvd {
|
|
||||||
c.handleSynRcvdSegmentLocked(tcpHdr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if flags&header.TCPFlagSyn != 0 {
|
|
||||||
// unexpected SYN on an already-established connection is not
|
|
||||||
// modeled; treat it like the peer abandoned and reset it
|
|
||||||
c.abortLocked(errConnReset)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if flags&header.TCPFlagAck != 0 {
|
|
||||||
c.handleAckLocked(seqnum.Value(tcpHdr.AckNumber()), tcpHdr.WindowSize())
|
|
||||||
}
|
|
||||||
|
|
||||||
c.acceptInOrderLocked(seqnum.Value(tcpHdr.SequenceNumber()), tcpHdr.Payload(), flags&header.TCPFlagFin != 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) handleSynRcvdSegmentLocked(tcpHdr header.TCP) {
|
|
||||||
flags := tcpHdr.Flags()
|
|
||||||
|
|
||||||
if flags&header.TCPFlagSyn != 0 {
|
|
||||||
// peer's retransmission of the original SYN, our SYN-ACK likely
|
|
||||||
// hasn't reached them yet: resend it and rely entirely on their own
|
|
||||||
// retransmission timer rather than running one on our side too
|
|
||||||
c.sendSynAckLocked()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if flags&header.TCPFlagAck == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if seqnum.Value(tcpHdr.AckNumber()) != c.sndNxt {
|
|
||||||
// does not acknowledge our SYN correctly; a well-behaved peer will
|
|
||||||
// simply retry, so it is safe to just ignore this segment
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.state = stateEstablished
|
|
||||||
go c.stack.handler.HandleConnection(c, c.dst)
|
|
||||||
|
|
||||||
c.handleAckLocked(seqnum.Value(tcpHdr.AckNumber()), tcpHdr.WindowSize())
|
|
||||||
c.acceptInOrderLocked(seqnum.Value(tcpHdr.SequenceNumber()), tcpHdr.Payload(), flags&header.TCPFlagFin != 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// acceptInOrderLocked handles the data/FIN portion of a segment once it is
|
|
||||||
// known to be neither a SYN nor a RST. Only strictly in-order segments are
|
|
||||||
// accepted; anything else is dropped (relying on the peer's retransmission)
|
|
||||||
// since the tun channel is expected to already deliver packets in order.
|
|
||||||
func (c *tcpConn) acceptInOrderLocked(seq seqnum.Value, payload []byte, fin bool) {
|
|
||||||
if seq != c.rcvNxt {
|
|
||||||
c.sendAckLocked()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accept := payload
|
|
||||||
if room := c.recvWindowLocked(); uint32(len(accept)) > room {
|
|
||||||
accept = accept[:room]
|
|
||||||
}
|
|
||||||
if len(accept) > 0 {
|
|
||||||
c.enqueueRecvLocked(accept)
|
|
||||||
c.rcvNxt = c.rcvNxt.Add(seqnum.Size(len(accept)))
|
|
||||||
}
|
|
||||||
|
|
||||||
finAccepted := false
|
|
||||||
if fin && len(accept) == len(payload) {
|
|
||||||
c.onFinLocked()
|
|
||||||
c.rcvNxt = c.rcvNxt.Add(1)
|
|
||||||
finAccepted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(accept) > 0 || finAccepted || len(accept) < len(payload) {
|
|
||||||
c.sendAckLocked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) onFinLocked() {
|
|
||||||
if c.recvClosed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.recvClosed = true
|
|
||||||
c.cond.Broadcast()
|
|
||||||
if c.state == stateEstablished {
|
|
||||||
c.state = stateCloseWait
|
|
||||||
}
|
|
||||||
c.maybeFinishCloseLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) handleAckLocked(ackNum seqnum.Value, windowSize uint16) {
|
|
||||||
if ackNum.LessThan(c.sndUna) {
|
|
||||||
// old/duplicate ack: no fast-retransmit heuristics implemented
|
|
||||||
c.peerWindow = uint32(windowSize)
|
|
||||||
c.trySendLocked()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.sndNxt.LessThan(ackNum) {
|
|
||||||
// acknowledges more than we ever sent: lenient clamp instead of
|
|
||||||
// rejecting the segment outright
|
|
||||||
ackNum = c.sndNxt
|
|
||||||
}
|
|
||||||
|
|
||||||
if advanced := c.sndUna.Size(ackNum); advanced > 0 {
|
|
||||||
c.sndUna = ackNum
|
|
||||||
n := int(advanced)
|
|
||||||
if n > len(c.sendQueue) {
|
|
||||||
n = len(c.sendQueue)
|
|
||||||
}
|
|
||||||
c.sendQueue = c.sendQueue[n:]
|
|
||||||
c.unsentOffset -= n
|
|
||||||
if c.unsentOffset < 0 {
|
|
||||||
c.unsentOffset = 0
|
|
||||||
}
|
|
||||||
c.rtoBackoff = 0
|
|
||||||
if c.finSent && c.sndUna == c.sndNxt {
|
|
||||||
c.finAcked = true
|
|
||||||
}
|
|
||||||
c.cond.Broadcast()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.peerWindow = uint32(windowSize)
|
|
||||||
c.trySendLocked()
|
|
||||||
c.maybeFinishCloseLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) maybeFinishCloseLocked() {
|
|
||||||
if c.state == stateClosing && c.finAcked && c.recvClosed {
|
|
||||||
c.state = stateTimeWait
|
|
||||||
c.startLingerLocked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) recvWindowLocked() uint32 {
|
|
||||||
room := maxRecvBuffer - c.recvBuffered
|
|
||||||
if room < 0 {
|
|
||||||
room = 0
|
|
||||||
}
|
|
||||||
if room > 0xffff {
|
|
||||||
room = 0xffff
|
|
||||||
}
|
|
||||||
return uint32(room)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) enqueueRecvLocked(payload []byte) {
|
|
||||||
data := make([]byte, len(payload))
|
|
||||||
copy(data, payload)
|
|
||||||
c.recvQueue = append(c.recvQueue, data)
|
|
||||||
c.recvBuffered += len(data)
|
|
||||||
c.cond.Broadcast()
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendOneChunkLocked transmits up to maxLen bytes of never-yet-sent data (if
|
|
||||||
// any remains), advancing sndNxt/unsentOffset. It returns the number of
|
|
||||||
// bytes sent, 0 if none remained.
|
|
||||||
func (c *tcpConn) sendOneChunkLocked(maxLen int) int {
|
|
||||||
remaining := len(c.sendQueue) - c.unsentOffset
|
|
||||||
if remaining <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if maxLen > remaining {
|
|
||||||
maxLen = remaining
|
|
||||||
}
|
|
||||||
if maxLen > c.sndMSS {
|
|
||||||
maxLen = c.sndMSS
|
|
||||||
}
|
|
||||||
if maxLen <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
data := c.sendQueue[c.unsentOffset : c.unsentOffset+maxLen]
|
|
||||||
c.sendDataSegmentLocked(c.sndNxt, data, false)
|
|
||||||
c.sndNxt = c.sndNxt.Add(seqnum.Size(maxLen))
|
|
||||||
c.unsentOffset += maxLen
|
|
||||||
return maxLen
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) trySendLocked() {
|
|
||||||
switch c.state {
|
|
||||||
case stateSynRcvd, stateTimeWait, stateClosed:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
inFlight := int(c.sndUna.Size(c.sndNxt))
|
|
||||||
windowLeft := int(c.peerWindow) - inFlight
|
|
||||||
if windowLeft <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if c.sendOneChunkLocked(windowLeft) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.closeCalled && !c.finSent && c.unsentOffset == len(c.sendQueue) {
|
|
||||||
c.finSeq = c.sndNxt
|
|
||||||
c.sendDataSegmentLocked(c.finSeq, nil, true)
|
|
||||||
c.sndNxt = c.sndNxt.Add(1)
|
|
||||||
c.finSent = true
|
|
||||||
}
|
|
||||||
|
|
||||||
c.refreshRTOLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) outstandingLocked() bool {
|
|
||||||
if c.unsentOffset > 0 {
|
|
||||||
return true // already-transmitted data pending ack
|
|
||||||
}
|
|
||||||
if len(c.sendQueue) > c.unsentOffset && c.peerWindow == 0 {
|
|
||||||
return true // blocked purely by a zero window; need to probe
|
|
||||||
}
|
|
||||||
if c.finSent && !c.finAcked {
|
|
||||||
return true // FIN transmitted but not yet acked
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) refreshRTOLocked() {
|
|
||||||
if c.outstandingLocked() {
|
|
||||||
c.scheduleRTOLocked()
|
|
||||||
} else {
|
|
||||||
c.stopRTOLocked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) rtoDurationLocked() time.Duration {
|
|
||||||
d := minRTO * time.Duration(uint64(1)<<uint(c.rtoBackoff))
|
|
||||||
if d > maxRTO || d <= 0 {
|
|
||||||
d = maxRTO
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) scheduleRTOLocked() {
|
|
||||||
d := c.rtoDurationLocked()
|
|
||||||
if c.rtoTimer == nil {
|
|
||||||
c.rtoTimer = time.AfterFunc(d, c.onRTOTimerFired)
|
|
||||||
} else {
|
|
||||||
c.rtoTimer.Reset(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) stopRTOLocked() {
|
|
||||||
if c.rtoTimer != nil {
|
|
||||||
c.rtoTimer.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) onRTOTimerFired() {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.onRTOFireLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) onRTOFireLocked() {
|
|
||||||
if c.state == stateClosed || !c.outstandingLocked() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.rtoBackoff >= maxRTORetries {
|
|
||||||
c.abortLocked(errConnTimedOut)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.rtoBackoff++
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case c.unsentOffset > 0:
|
|
||||||
c.sendDataSegmentLocked(c.sndUna, c.sendQueue[:c.unsentOffset], false)
|
|
||||||
case len(c.sendQueue) > c.unsentOffset:
|
|
||||||
// nothing in flight, but blocked by a zero peer window: probe with
|
|
||||||
// exactly one new byte, per RFC 9293 §3.8.6.1
|
|
||||||
c.sendOneChunkLocked(1)
|
|
||||||
case c.finSent && !c.finAcked:
|
|
||||||
c.sendDataSegmentLocked(c.finSeq, nil, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.refreshRTOLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) startLingerLocked() {
|
|
||||||
c.stopRTOLocked()
|
|
||||||
c.lingerTimer = time.AfterFunc(lingerDuration, func() {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.abortLocked(errConnClosed)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) stopLingerLocked() {
|
|
||||||
if c.lingerTimer != nil {
|
|
||||||
c.lingerTimer.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// transmitLocked builds, checksums and writes a single tcp segment.
|
|
||||||
func (c *tcpConn) transmitLocked(seq, ack seqnum.Value, flags header.TCPFlags, payload []byte, options []byte) {
|
|
||||||
headerLen := header.TCPMinimumSize + len(options)
|
|
||||||
segment := make([]byte, headerLen+len(payload))
|
|
||||||
tcpHdr := header.TCP(segment)
|
|
||||||
tcpHdr.Encode(&header.TCPFields{
|
|
||||||
SrcPort: c.key.dstPort,
|
|
||||||
DstPort: c.key.srcPort,
|
|
||||||
SeqNum: uint32(seq),
|
|
||||||
AckNum: uint32(ack),
|
|
||||||
DataOffset: uint8(headerLen),
|
|
||||||
Flags: flags,
|
|
||||||
WindowSize: uint16(c.recvWindowLocked()),
|
|
||||||
})
|
|
||||||
copy(tcpHdr.Options(), options)
|
|
||||||
copy(segment[headerLen:], payload)
|
|
||||||
|
|
||||||
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, c.key.dstAddr, c.key.srcAddr, uint16(len(segment)))
|
|
||||||
xsum = checksum.Checksum(payload, xsum)
|
|
||||||
tcpHdr.SetChecksum(^tcpHdr.CalculateChecksum(xsum))
|
|
||||||
|
|
||||||
if err := c.stack.writeTransportSegment(c.key.netProto, header.TCPProtocolNumber, c.key.dstAddr, c.key.srcAddr, segment); err != nil {
|
|
||||||
xerrors.LogInfoInner(c.stack.ctx, err, "[tun] failed to write tcp segment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) sendSynAckLocked() {
|
|
||||||
var optBuf [header.TCPOptionMSSLength]byte
|
|
||||||
n := header.EncodeMSSOption(uint32(c.ourMSS), optBuf[:])
|
|
||||||
c.transmitLocked(c.iss, c.rcvNxt, header.TCPFlagSyn|header.TCPFlagAck, nil, optBuf[:n])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) sendAckLocked() {
|
|
||||||
c.transmitLocked(c.sndNxt, c.rcvNxt, header.TCPFlagAck, nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) sendDataSegmentLocked(seq seqnum.Value, payload []byte, fin bool) {
|
|
||||||
flags := header.TCPFlagAck
|
|
||||||
if fin {
|
|
||||||
flags |= header.TCPFlagFin
|
|
||||||
}
|
|
||||||
c.transmitLocked(seq, c.rcvNxt, flags, payload, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements net.Conn.
|
|
||||||
func (c *tcpConn) Read(p []byte) (int, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
for len(c.recvQueue) == 0 && c.err == nil && !c.recvClosed {
|
|
||||||
c.cond.Wait()
|
|
||||||
}
|
|
||||||
if c.err != nil {
|
|
||||||
return 0, c.err
|
|
||||||
}
|
|
||||||
if len(c.recvQueue) == 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
before := c.recvWindowLocked()
|
|
||||||
|
|
||||||
chunk := c.recvQueue[0]
|
|
||||||
n := copy(p, chunk[c.recvOffset:])
|
|
||||||
c.recvOffset += n
|
|
||||||
c.recvBuffered -= n
|
|
||||||
if c.recvOffset == len(chunk) {
|
|
||||||
c.recvQueue = c.recvQueue[1:]
|
|
||||||
c.recvOffset = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// let the peer know promptly if reading just freed up a previously
|
|
||||||
// exhausted window, instead of waiting for it to probe us for an update
|
|
||||||
if after := c.recvWindowLocked(); before == 0 && after > 0 {
|
|
||||||
c.sendAckLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements net.Conn.
|
|
||||||
func (c *tcpConn) Write(p []byte) (int, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if c.closeCalled {
|
|
||||||
return 0, errConnClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
total := 0
|
|
||||||
for total < len(p) {
|
|
||||||
if c.err != nil {
|
|
||||||
return total, c.err
|
|
||||||
}
|
|
||||||
if c.closeCalled {
|
|
||||||
return total, errConnClosed
|
|
||||||
}
|
|
||||||
room := maxSendBuffer - len(c.sendQueue)
|
|
||||||
if room <= 0 {
|
|
||||||
c.cond.Wait()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n := len(p) - total
|
|
||||||
if n > room {
|
|
||||||
n = room
|
|
||||||
}
|
|
||||||
c.sendQueue = append(c.sendQueue, p[total:total+n]...)
|
|
||||||
total += n
|
|
||||||
}
|
|
||||||
|
|
||||||
c.trySendLocked()
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements net.Conn.
|
|
||||||
func (c *tcpConn) Close() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if c.closeCalled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.closeCalled = true
|
|
||||||
c.cond.Broadcast()
|
|
||||||
|
|
||||||
switch c.state {
|
|
||||||
case stateEstablished, stateCloseWait:
|
|
||||||
c.state = stateClosing
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.trySendLocked()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) LocalAddr() net.Addr { return c.dst.RawNetAddr() }
|
|
||||||
func (c *tcpConn) RemoteAddr() net.Addr { return c.src.RawNetAddr() }
|
|
||||||
|
|
||||||
func (c *tcpConn) SetDeadline(t time.Time) error { return nil }
|
|
||||||
func (c *tcpConn) SetReadDeadline(t time.Time) error { return nil }
|
|
||||||
func (c *tcpConn) SetWriteDeadline(t time.Time) error { return nil }
|
|
||||||
@@ -1,448 +0,0 @@
|
|||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fakeGVisorDevice is an in-memory GVisorDevice used to script conversations
|
|
||||||
// with the "system" stack without any real tun device or privileges.
|
|
||||||
type fakeGVisorDevice struct {
|
|
||||||
inbound chan []byte
|
|
||||||
outbound chan []byte
|
|
||||||
notify chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeGVisorDevice() *fakeGVisorDevice {
|
|
||||||
return &fakeGVisorDevice{
|
|
||||||
inbound: make(chan []byte, 256),
|
|
||||||
outbound: make(chan []byte, 256),
|
|
||||||
notify: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *fakeGVisorDevice) push(data []byte) {
|
|
||||||
d.inbound <- data
|
|
||||||
select {
|
|
||||||
case d.notify <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *fakeGVisorDevice) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
|
||||||
select {
|
|
||||||
case data := <-d.inbound:
|
|
||||||
version := data[0] >> 4
|
|
||||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(data)})
|
|
||||||
return version, pkt, nil
|
|
||||||
default:
|
|
||||||
return 0, nil, ErrQueueEmpty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *fakeGVisorDevice) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
|
||||||
var data []byte
|
|
||||||
for _, s := range packet.AsSlices() {
|
|
||||||
data = append(data, s...)
|
|
||||||
}
|
|
||||||
d.outbound <- data
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *fakeGVisorDevice) Wait() {
|
|
||||||
select {
|
|
||||||
case <-d.notify:
|
|
||||||
case <-time.After(20 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *fakeGVisorDevice) recv(t *testing.T, timeout time.Duration) []byte {
|
|
||||||
t.Helper()
|
|
||||||
select {
|
|
||||||
case data := <-d.outbound:
|
|
||||||
return data
|
|
||||||
case <-time.After(timeout):
|
|
||||||
t.Fatal("timed out waiting for outbound packet")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ GVisorDevice = (*fakeGVisorDevice)(nil)
|
|
||||||
|
|
||||||
// echoHandler is a ConnectionHandler that echoes back everything it reads on
|
|
||||||
// each connection, and records connections/destinations it has seen.
|
|
||||||
type echoHandler struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
conns []net.Conn
|
|
||||||
dests []net.Destination
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEchoHandler() *echoHandler {
|
|
||||||
return &echoHandler{done: make(chan struct{}, 8)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *echoHandler) HandleConnection(conn net.Conn, dest net.Destination) {
|
|
||||||
h.mu.Lock()
|
|
||||||
h.conns = append(h.conns, conn)
|
|
||||||
h.dests = append(h.dests, dest)
|
|
||||||
h.mu.Unlock()
|
|
||||||
|
|
||||||
_, _ = io.Copy(conn, conn)
|
|
||||||
_ = conn.Close()
|
|
||||||
h.done <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestStackSystem(device GVisorDevice, handler ConnectionHandler, idleTimeout time.Duration) (*stackSystem, context.CancelFunc) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
s := &stackSystem{
|
|
||||||
ctx: ctx,
|
|
||||||
device: device,
|
|
||||||
mtu: 1500,
|
|
||||||
idleTimeout: idleTimeout,
|
|
||||||
handler: handler,
|
|
||||||
tcp: make(map[tcpKey]*tcpConn),
|
|
||||||
}
|
|
||||||
return s, cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
func testIP(s string) tcpip.Address {
|
|
||||||
return tcpip.AddrFrom4Slice(net.ParseIP(s).To4())
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
testPeerIP = "10.0.0.2"
|
|
||||||
testTargetIP = "10.0.0.1"
|
|
||||||
testPeerPort = uint16(51234)
|
|
||||||
testDstPort = uint16(8080)
|
|
||||||
)
|
|
||||||
|
|
||||||
// buildIPv4TCP builds a raw IPv4+TCP segment, computing valid checksums.
|
|
||||||
func buildIPv4TCP(src, dst tcpip.Address, srcPort, dstPort uint16, seq, ack uint32, flags header.TCPFlags, window uint16, payload []byte, options []byte) []byte {
|
|
||||||
headerLen := header.TCPMinimumSize + len(options)
|
|
||||||
totalLen := header.IPv4MinimumSize + headerLen + len(payload)
|
|
||||||
data := make([]byte, totalLen)
|
|
||||||
|
|
||||||
ipHdr := header.IPv4(data)
|
|
||||||
ipHdr.Encode(&header.IPv4Fields{
|
|
||||||
TotalLength: uint16(totalLen),
|
|
||||||
TTL: 64,
|
|
||||||
Protocol: uint8(header.TCPProtocolNumber),
|
|
||||||
SrcAddr: src,
|
|
||||||
DstAddr: dst,
|
|
||||||
})
|
|
||||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
||||||
|
|
||||||
tcpHdr := header.TCP(data[header.IPv4MinimumSize:])
|
|
||||||
tcpHdr.Encode(&header.TCPFields{
|
|
||||||
SrcPort: srcPort,
|
|
||||||
DstPort: dstPort,
|
|
||||||
SeqNum: seq,
|
|
||||||
AckNum: ack,
|
|
||||||
DataOffset: uint8(headerLen),
|
|
||||||
Flags: flags,
|
|
||||||
WindowSize: window,
|
|
||||||
})
|
|
||||||
copy(tcpHdr.Options(), options)
|
|
||||||
copy(data[header.IPv4MinimumSize+headerLen:], payload)
|
|
||||||
|
|
||||||
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, src, dst, uint16(headerLen+len(payload)))
|
|
||||||
xsum = checksum.Checksum(payload, xsum)
|
|
||||||
tcpHdr.SetChecksum(^tcpHdr.CalculateChecksum(xsum))
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIPv4TCP(t *testing.T, data []byte) header.TCP {
|
|
||||||
t.Helper()
|
|
||||||
ipHdr := header.IPv4(data)
|
|
||||||
if !ipHdr.IsValid(len(data)) {
|
|
||||||
t.Fatalf("invalid ipv4 packet")
|
|
||||||
}
|
|
||||||
return header.TCP(ipHdr.Payload())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSystemStackTCPHandshakeEchoClose(t *testing.T) {
|
|
||||||
device := newFakeGVisorDevice()
|
|
||||||
handler := newEchoHandler()
|
|
||||||
s, cancel := newTestStackSystem(device, handler, time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
if err := s.Start(); err != nil {
|
|
||||||
t.Fatalf("Start: %v", err)
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
src := testIP(testPeerIP)
|
|
||||||
dst := testIP(testTargetIP)
|
|
||||||
|
|
||||||
iss := uint32(1000)
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss, 0, header.TCPFlagSyn, 65535, nil, nil))
|
|
||||||
|
|
||||||
synAck := parseIPv4TCP(t, device.recv(t, time.Second))
|
|
||||||
if synAck.Flags() != header.TCPFlagSyn|header.TCPFlagAck {
|
|
||||||
t.Fatalf("expected SYN-ACK, got flags %v", synAck.Flags())
|
|
||||||
}
|
|
||||||
if synAck.AckNumber() != iss+1 {
|
|
||||||
t.Fatalf("unexpected ack number %d, want %d", synAck.AckNumber(), iss+1)
|
|
||||||
}
|
|
||||||
serverISS := synAck.SequenceNumber()
|
|
||||||
|
|
||||||
// final handshake ACK
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss+1, serverISS+1, header.TCPFlagAck, 65535, nil, nil))
|
|
||||||
|
|
||||||
// send data
|
|
||||||
payload := []byte("hello world")
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss+1, serverISS+1, header.TCPFlagAck|header.TCPFlagPsh, 65535, payload, nil))
|
|
||||||
|
|
||||||
// drain outbound packets until the full echo has been observed, acking
|
|
||||||
// any data segments as they arrive so the connection can make progress
|
|
||||||
var echoed []byte
|
|
||||||
deadline := time.After(2 * time.Second)
|
|
||||||
for len(echoed) < len(payload) {
|
|
||||||
select {
|
|
||||||
case raw := <-device.outbound:
|
|
||||||
tcpHdr := parseIPv4TCP(t, raw)
|
|
||||||
if len(tcpHdr.Payload()) > 0 {
|
|
||||||
echoed = append(echoed, tcpHdr.Payload()...)
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort,
|
|
||||||
iss+1+uint32(len(payload)), tcpHdr.SequenceNumber()+uint32(len(tcpHdr.Payload())),
|
|
||||||
header.TCPFlagAck, 65535, nil, nil))
|
|
||||||
}
|
|
||||||
case <-deadline:
|
|
||||||
t.Fatalf("timed out waiting for echo, got %q so far", echoed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if string(echoed) != string(payload) {
|
|
||||||
t.Fatalf("echo mismatch: got %q want %q", echoed, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := handler
|
|
||||||
h.mu.Lock()
|
|
||||||
if len(h.dests) != 1 || h.dests[0].NetAddr() != "10.0.0.1:8080" {
|
|
||||||
t.Fatalf("unexpected destination recorded: %+v", h.dests)
|
|
||||||
}
|
|
||||||
h.mu.Unlock()
|
|
||||||
|
|
||||||
// peer sends FIN
|
|
||||||
finSeq := iss + 1 + uint32(len(payload))
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, finSeq, serverISS+1+uint32(len(payload)), header.TCPFlagFin|header.TCPFlagAck, 65535, nil, nil))
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-handler.done:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
t.Fatal("echo handler never finished after peer FIN")
|
|
||||||
}
|
|
||||||
|
|
||||||
var sawAckOfFin, sawOurFin bool
|
|
||||||
var ourFinSeq uint32
|
|
||||||
deadline = time.After(2 * time.Second)
|
|
||||||
for !sawAckOfFin || !sawOurFin {
|
|
||||||
select {
|
|
||||||
case raw := <-device.outbound:
|
|
||||||
tcpHdr := parseIPv4TCP(t, raw)
|
|
||||||
if tcpHdr.Flags()&header.TCPFlagFin != 0 {
|
|
||||||
sawOurFin = true
|
|
||||||
ourFinSeq = tcpHdr.SequenceNumber()
|
|
||||||
}
|
|
||||||
if tcpHdr.AckNumber() == finSeq+1 {
|
|
||||||
sawAckOfFin = true
|
|
||||||
}
|
|
||||||
case <-deadline:
|
|
||||||
t.Fatalf("timed out waiting for our fin/ack (sawAckOfFin=%v sawOurFin=%v)", sawAckOfFin, sawOurFin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ack our FIN, completing a graceful close
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, finSeq+1, ourFinSeq+1, header.TCPFlagAck, 65535, nil, nil))
|
|
||||||
|
|
||||||
deadline = time.After(2 * time.Second)
|
|
||||||
for {
|
|
||||||
s.tcpMu.Lock()
|
|
||||||
var conn *tcpConn
|
|
||||||
for _, c := range s.tcp {
|
|
||||||
conn = c
|
|
||||||
}
|
|
||||||
s.tcpMu.Unlock()
|
|
||||||
if conn == nil {
|
|
||||||
t.Fatal("connection unexpectedly removed before linger")
|
|
||||||
}
|
|
||||||
conn.mu.Lock()
|
|
||||||
state := conn.state
|
|
||||||
conn.mu.Unlock()
|
|
||||||
if state == stateTimeWait {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-deadline:
|
|
||||||
t.Fatalf("connection did not reach TimeWait, state=%d", state)
|
|
||||||
case <-time.After(10 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSystemStackTCPUnknownConnectionReset(t *testing.T) {
|
|
||||||
device := newFakeGVisorDevice()
|
|
||||||
handler := newEchoHandler()
|
|
||||||
s, cancel := newTestStackSystem(device, handler, time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
if err := s.Start(); err != nil {
|
|
||||||
t.Fatalf("Start: %v", err)
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
src := testIP(testPeerIP)
|
|
||||||
dst := testIP(testTargetIP)
|
|
||||||
|
|
||||||
// an ACK referencing a connection the stack has never seen
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, 5000, 0, header.TCPFlagAck, 65535, nil, nil))
|
|
||||||
|
|
||||||
rst := parseIPv4TCP(t, device.recv(t, time.Second))
|
|
||||||
if rst.Flags()&header.TCPFlagRst == 0 {
|
|
||||||
t.Fatalf("expected RST, got flags %v", rst.Flags())
|
|
||||||
}
|
|
||||||
if rst.SequenceNumber() != 5000 {
|
|
||||||
t.Fatalf("expected reset seq to echo the ack number 5000, got %d", rst.SequenceNumber())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSystemStackTCPRetransmit(t *testing.T) {
|
|
||||||
device := newFakeGVisorDevice()
|
|
||||||
handler := newEchoHandler()
|
|
||||||
s, cancel := newTestStackSystem(device, handler, time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
if err := s.Start(); err != nil {
|
|
||||||
t.Fatalf("Start: %v", err)
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
src := testIP(testPeerIP)
|
|
||||||
dst := testIP(testTargetIP)
|
|
||||||
|
|
||||||
iss := uint32(2000)
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss, 0, header.TCPFlagSyn, 65535, nil, nil))
|
|
||||||
synAck := parseIPv4TCP(t, device.recv(t, time.Second))
|
|
||||||
serverISS := synAck.SequenceNumber()
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss+1, serverISS+1, header.TCPFlagAck, 65535, nil, nil))
|
|
||||||
|
|
||||||
payload := []byte("hi")
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, iss+1, serverISS+1, header.TCPFlagAck|header.TCPFlagPsh, 65535, payload, nil))
|
|
||||||
|
|
||||||
// consume the data-ack and the first echoed data segment, but do NOT ack
|
|
||||||
// the echoed data, forcing a retransmit
|
|
||||||
var first []byte
|
|
||||||
deadline := time.After(2 * time.Second)
|
|
||||||
for len(first) == 0 {
|
|
||||||
select {
|
|
||||||
case raw := <-device.outbound:
|
|
||||||
tcpHdr := parseIPv4TCP(t, raw)
|
|
||||||
if len(tcpHdr.Payload()) > 0 {
|
|
||||||
first = append([]byte(nil), tcpHdr.Payload()...)
|
|
||||||
}
|
|
||||||
case <-deadline:
|
|
||||||
t.Fatal("timed out waiting for first echoed segment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now wait for a retransmission of the same bytes, without acking
|
|
||||||
deadline = time.After(2 * time.Second)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case raw := <-device.outbound:
|
|
||||||
tcpHdr := parseIPv4TCP(t, raw)
|
|
||||||
if string(tcpHdr.Payload()) == string(first) {
|
|
||||||
return // retransmit observed, test passes
|
|
||||||
}
|
|
||||||
case <-deadline:
|
|
||||||
t.Fatal("timed out waiting for retransmission of unacked data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSystemStackIdleReap(t *testing.T) {
|
|
||||||
device := newFakeGVisorDevice()
|
|
||||||
handler := newEchoHandler()
|
|
||||||
s, cancel := newTestStackSystem(device, handler, time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
if err := s.Start(); err != nil {
|
|
||||||
t.Fatalf("Start: %v", err)
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
src := testIP(testPeerIP)
|
|
||||||
dst := testIP(testTargetIP)
|
|
||||||
|
|
||||||
device.push(buildIPv4TCP(src, dst, testPeerPort, testDstPort, 1, 0, header.TCPFlagSyn, 65535, nil, nil))
|
|
||||||
device.recv(t, time.Second) // SYN-ACK
|
|
||||||
|
|
||||||
deadline := time.After(2 * time.Second)
|
|
||||||
for {
|
|
||||||
s.tcpMu.Lock()
|
|
||||||
n := len(s.tcp)
|
|
||||||
s.tcpMu.Unlock()
|
|
||||||
if n == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-deadline:
|
|
||||||
t.Fatal("idle connection was not reaped")
|
|
||||||
case <-time.After(10 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSystemStackUDPEcho(t *testing.T) {
|
|
||||||
device := newFakeGVisorDevice()
|
|
||||||
handler := newEchoHandler()
|
|
||||||
s, cancel := newTestStackSystem(device, handler, time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
if err := s.Start(); err != nil {
|
|
||||||
t.Fatalf("Start: %v", err)
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
src := testIP(testPeerIP)
|
|
||||||
dst := testIP(testTargetIP)
|
|
||||||
|
|
||||||
payload := []byte("ping")
|
|
||||||
udpLen := header.UDPMinimumSize + len(payload)
|
|
||||||
totalLen := header.IPv4MinimumSize + udpLen
|
|
||||||
data := make([]byte, totalLen)
|
|
||||||
ipHdr := header.IPv4(data)
|
|
||||||
ipHdr.Encode(&header.IPv4Fields{
|
|
||||||
TotalLength: uint16(totalLen),
|
|
||||||
TTL: 64,
|
|
||||||
Protocol: uint8(header.UDPProtocolNumber),
|
|
||||||
SrcAddr: src,
|
|
||||||
DstAddr: dst,
|
|
||||||
})
|
|
||||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
||||||
udpHdr := header.UDP(data[header.IPv4MinimumSize:])
|
|
||||||
udpHdr.Encode(&header.UDPFields{SrcPort: testPeerPort, DstPort: testDstPort, Length: uint16(udpLen)})
|
|
||||||
copy(data[header.IPv4MinimumSize+header.UDPMinimumSize:], payload)
|
|
||||||
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, src, dst, uint16(udpLen))
|
|
||||||
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
|
|
||||||
|
|
||||||
device.push(data)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-handler.done:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("udp handler never invoked/finished")
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.mu.Lock()
|
|
||||||
defer handler.mu.Unlock()
|
|
||||||
if len(handler.dests) != 1 || handler.dests[0].Network != net.Network_UDP {
|
|
||||||
t.Fatalf("unexpected udp destination recorded: %+v", handler.dests)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,12 +7,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
)
|
)
|
||||||
@@ -25,22 +22,6 @@ type AndroidTun struct {
|
|||||||
// DefaultTun implements Tun
|
// DefaultTun implements Tun
|
||||||
var _ Tun = (*AndroidTun)(nil)
|
var _ Tun = (*AndroidTun)(nil)
|
||||||
|
|
||||||
// AndroidTun implements GVisorDevice, used by the "system" (lite) ip stack
|
|
||||||
var _ GVisorDevice = (*AndroidTun)(nil)
|
|
||||||
|
|
||||||
// fdReadWriter adapts a raw, already non-blocking file descriptor to io.Reader/io.Writer,
|
|
||||||
// so it can be used with buf.Buffer.ReadFrom, without the ownership/finalizer overhead of
|
|
||||||
// wrapping it in an *os.File (the fd is owned and closed elsewhere).
|
|
||||||
type fdReadWriter int
|
|
||||||
|
|
||||||
func (f fdReadWriter) Read(p []byte) (int, error) {
|
|
||||||
return unix.Read(int(f), p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fdReadWriter) Write(p []byte) (int, error) {
|
|
||||||
return unix.Write(int(f), p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTun builds new tun interface handler
|
// NewTun builds new tun interface handler
|
||||||
func NewTun(options *Config) (Tun, error) {
|
func NewTun(options *Config) (Tun, error) {
|
||||||
fd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "0" }))
|
fd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "0" }))
|
||||||
@@ -97,72 +78,6 @@ func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPacket implements GVisorDevice method to read one packet from the tun device, used by
|
|
||||||
// the "system" (lite) ip stack. The gVisor backed stack instead talks to the fd directly through
|
|
||||||
// fdbased.New above, for lower overhead batched IO, bypassing GVisorDevice entirely.
|
|
||||||
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
|
|
||||||
// which will make the stack call Wait which should implement desired push-back
|
|
||||||
func (t *AndroidTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
|
||||||
// request memory to write from reusable buffer pool
|
|
||||||
b := buf.NewWithSize(int32(t.options.MTU))
|
|
||||||
|
|
||||||
// read the bytes from the interface file descriptor, which is already non-blocking
|
|
||||||
n, err := b.ReadFrom(fdReadWriter(t.tunFd))
|
|
||||||
if err == unix.EAGAIN || err == unix.EWOULDBLOCK || err == unix.EINTR {
|
|
||||||
b.Release()
|
|
||||||
return 0, nil, ErrQueueEmpty
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
b.Release()
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// discard empty packets
|
|
||||||
if n == 0 {
|
|
||||||
b.Release()
|
|
||||||
return 0, nil, ErrQueueEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
// network protocol version from the first nibble of the raw packet
|
|
||||||
version := b.Byte(0) >> 4
|
|
||||||
packetBuffer := buffer.MakeWithData(b.Bytes())
|
|
||||||
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
||||||
Payload: packetBuffer,
|
|
||||||
IsForwardedPacket: true,
|
|
||||||
OnRelease: func() {
|
|
||||||
b.Release()
|
|
||||||
},
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
|
||||||
func (t *AndroidTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
|
||||||
// request memory to write from reusable buffer pool
|
|
||||||
b := buf.NewWithSize(int32(t.options.MTU))
|
|
||||||
defer b.Release()
|
|
||||||
|
|
||||||
// copy the bytes of slices that compose the packet into the allocated buffer, no
|
|
||||||
// extra header is needed here, unlike Darwin/FreeBSD's utun devices
|
|
||||||
for _, packetElement := range packet.AsSlices() {
|
|
||||||
_, _ = b.Write(packetElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fdReadWriter(t.tunFd).Write(b.Bytes()); err != nil {
|
|
||||||
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
|
|
||||||
return &tcpip.ErrWouldBlock{}
|
|
||||||
}
|
|
||||||
return &tcpip.ErrAborted{}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait blocks until the tun fd is likely readable again, rather than spinning the CPU.
|
|
||||||
// A bounded timeout keeps this responsive to a Close() racing a call already parked here.
|
|
||||||
func (t *AndroidTun) Wait() {
|
|
||||||
fds := []unix.PollFd{{Fd: int32(t.tunFd), Events: unix.POLLIN}}
|
|
||||||
_, _ = unix.Poll(fds, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||||
return unix.BindToDevice(int(fd), iface.Name)
|
return unix.BindToDevice(int(fd), iface.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-727
@@ -3,60 +3,28 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
_ "unsafe"
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
|
||||||
"golang.org/x/net/route"
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
xerrors "github.com/xtls/xray-core/common/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const tunHeaderSize = 4
|
||||||
tunHeaderSize = 4
|
|
||||||
defaultFreeBSDGateway = "169.254.10.1/30"
|
|
||||||
|
|
||||||
// escapeFib is the routing table outbound sockets are switched to so
|
|
||||||
// their traffic bypasses the TUN routes installed in the default FIB
|
|
||||||
// (FreeBSD's substitute for the per-socket interface binding other
|
|
||||||
// platforms use). Requires the boot tunable net.fibs >= 2.
|
|
||||||
escapeFib = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:linkname procyield runtime.procyield
|
//go:linkname procyield runtime.procyield
|
||||||
func procyield(cycles uint32)
|
func procyield(cycles uint32)
|
||||||
|
|
||||||
type FreeBSDTun struct {
|
type FreeBSDTun struct {
|
||||||
device tun.Device
|
device tun.Device
|
||||||
options *Config
|
mtu uint32
|
||||||
tunIndex int
|
|
||||||
autoInterface bool
|
|
||||||
|
|
||||||
systemRoutes []netip.Prefix
|
|
||||||
escapeMu sync.Mutex
|
|
||||||
escapeRoutes []escapeRoute
|
|
||||||
routeMonitor *os.File
|
|
||||||
routeMonitorOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
// escapeRoute remembers one route written into the escape FIB, in the exact
|
|
||||||
// shape needed to delete it again. A zero gateway means an interface route.
|
|
||||||
type escapeRoute struct {
|
|
||||||
prefix netip.Prefix
|
|
||||||
ifIndex int
|
|
||||||
gateway netip.Addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -66,172 +34,20 @@ var (
|
|||||||
|
|
||||||
// NewTun builds new tun interface handler
|
// NewTun builds new tun interface handler
|
||||||
func NewTun(options *Config) (Tun, error) {
|
func NewTun(options *Config) (Tun, error) {
|
||||||
gateway, local, err := selectFreeBSDGateway(options.Gateway)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// net.fibs is a boot-time constant, so validate the escape routing table
|
|
||||||
// before the shared handler registers a dialer controller that would
|
|
||||||
// otherwise steer every outbound socket into a table that was never set up.
|
|
||||||
if options.AutoOutboundsInterface != "" {
|
|
||||||
if err := checkEscapeFib(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tunDev, err := tun.CreateTUN(options.Name, int(options.MTU))
|
tunDev, err := tun.CreateTUN(options.Name, int(options.MTU))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
name, err := tunDev.Name()
|
return &FreeBSDTun{device: tunDev, mtu: options.MTU}, nil
|
||||||
if err != nil {
|
|
||||||
_ = tunDev.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// From here the interface exists in the kernel; the wireguard library does
|
|
||||||
// not remove it on Close, so every failure path must destroy it too or the
|
|
||||||
// next start fails with "interface already exists".
|
|
||||||
iface, err := net.InterfaceByName(name)
|
|
||||||
if err != nil {
|
|
||||||
_ = tunDev.Close()
|
|
||||||
destroyInterface(name)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := setIPAddress(name, gateway, local, iface.Index); err != nil {
|
|
||||||
_ = tunDev.Close()
|
|
||||||
destroyInterface(name)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FreeBSDTun{
|
|
||||||
device: tunDev,
|
|
||||||
options: options,
|
|
||||||
tunIndex: iface.Index,
|
|
||||||
autoInterface: options.AutoOutboundsInterface != "",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectFreeBSDGateway picks the first IPv4 prefix from the configured gateway
|
|
||||||
// list and the local address derived from it (the darwin semantics: the
|
|
||||||
// gateway is the remote side of the point-to-point pair, the local address is
|
|
||||||
// the next one after it), falling back to the same link-local default.
|
|
||||||
func selectFreeBSDGateway(configured []string) (netip.Prefix, netip.Addr, error) {
|
|
||||||
gateway := netip.MustParsePrefix(defaultFreeBSDGateway)
|
|
||||||
if len(configured) > 0 {
|
|
||||||
found := false
|
|
||||||
for _, value := range configured {
|
|
||||||
prefix, err := netip.ParsePrefix(value)
|
|
||||||
if err != nil {
|
|
||||||
return netip.Prefix{}, netip.Addr{}, xerrors.New("invalid FreeBSD gateway ", value).Base(err)
|
|
||||||
}
|
|
||||||
if prefix.Addr().Is4() {
|
|
||||||
gateway, found = prefix, true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return netip.Prefix{}, netip.Addr{}, xerrors.New("FreeBSD gateway requires at least one IPv4 prefix")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local, ok := nextLocalIPv4(gateway)
|
|
||||||
if !ok || !gateway.Contains(local) {
|
|
||||||
return netip.Prefix{}, netip.Addr{}, xerrors.New("FreeBSD gateway ", gateway.String(), " must contain at least one usable local IPv4 address after the gateway address")
|
|
||||||
}
|
|
||||||
return gateway, local, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextLocalIPv4(gateway netip.Prefix) (netip.Addr, bool) {
|
|
||||||
local4 := gateway.Addr().As4()
|
|
||||||
for i := len(local4) - 1; i >= 0; i-- {
|
|
||||||
local4[i]++
|
|
||||||
if local4[i] != 0 {
|
|
||||||
return netip.AddrFrom4(local4), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return netip.Addr{}, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FreeBSDTun) Start() error {
|
func (t *FreeBSDTun) Start() error {
|
||||||
if err := t.setSystemRoutes(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gate on this instance's own option, not the package-global updater,
|
|
||||||
// which a previously-removed inbound may have left set. checkEscapeFib
|
|
||||||
// already ran in NewTun, before the dialer controller was registered.
|
|
||||||
if t.autoInterface {
|
|
||||||
if err := t.syncEscapeFib(); err != nil {
|
|
||||||
_ = t.unsetSystemRoutes()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.unsetEscapeFib()
|
|
||||||
_ = t.unsetSystemRoutes()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.routeMonitor = os.NewFile(uintptr(fd), "xray-route-monitor")
|
|
||||||
go t.monitorRouteChanges()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// monitorRouteChanges refreshes the outbound interface and the escape FIB
|
|
||||||
// mirror whenever the system routing table changes.
|
|
||||||
func (t *FreeBSDTun) monitorRouteChanges() {
|
|
||||||
buffer := make([]byte, 64*1024)
|
|
||||||
for {
|
|
||||||
if _, err := t.routeMonitor.Read(buffer); err != nil {
|
|
||||||
if !errors.Is(err, os.ErrClosed) {
|
|
||||||
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to monitor route changes")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if updater != nil {
|
|
||||||
updater.Update()
|
|
||||||
}
|
|
||||||
if err := t.syncEscapeFib(); err != nil {
|
|
||||||
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to refresh escape routes")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *FreeBSDTun) Close() error {
|
func (t *FreeBSDTun) Close() error {
|
||||||
t.routeMonitorOnce.Do(func() {
|
return t.device.Close()
|
||||||
if t.routeMonitor != nil {
|
|
||||||
_ = t.routeMonitor.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.unsetEscapeFib()
|
|
||||||
routeErr := t.unsetSystemRoutes()
|
|
||||||
name, nameErr := t.Name()
|
|
||||||
closeErr := t.device.Close()
|
|
||||||
// The wireguard tun device does not tear the interface down on FreeBSD,
|
|
||||||
// so an unclean shutdown would leave utun<n> behind and the next start
|
|
||||||
// would fail with "interface already exists"; destroy it explicitly.
|
|
||||||
if nameErr == nil {
|
|
||||||
destroyInterface(name)
|
|
||||||
}
|
|
||||||
return xerrors.Combine(routeErr, closeErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func destroyInterface(name string) {
|
|
||||||
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer unix.Close(fd)
|
|
||||||
// struct ifreq: 16-byte name + a 16-byte union (SIOCIFDESTROY's encoded
|
|
||||||
// length is 32 bytes on amd64, and the kernel copies in all of it).
|
|
||||||
var req struct {
|
|
||||||
Name [unix.IFNAMSIZ]byte
|
|
||||||
_ [16]byte
|
|
||||||
}
|
|
||||||
copy(req.Name[:], name)
|
|
||||||
_ = ioctlPtr(fd, unix.SIOCIFDESTROY, unsafe.Pointer(&req))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FreeBSDTun) Name() (string, error) {
|
func (t *FreeBSDTun) Name() (string, error) {
|
||||||
@@ -239,13 +55,21 @@ func (t *FreeBSDTun) Name() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *FreeBSDTun) Index() (int, error) {
|
func (t *FreeBSDTun) Index() (int, error) {
|
||||||
return t.tunIndex, nil
|
name, err := t.Name()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return iface.Index, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
||||||
func (t *FreeBSDTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
func (t *FreeBSDTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
||||||
// request memory to write from reusable buffer pool
|
// request memory to write from reusable buffer pool
|
||||||
b := buf.NewWithSize(int32(t.options.MTU) + tunHeaderSize)
|
b := buf.NewWithSize(int32(t.mtu) + tunHeaderSize)
|
||||||
defer b.Release()
|
defer b.Release()
|
||||||
|
|
||||||
// prepare Unix specific packet header
|
// prepare Unix specific packet header
|
||||||
@@ -280,7 +104,7 @@ func (t *FreeBSDTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
|||||||
// which will make the stack call Wait which should implement desired push-back
|
// which will make the stack call Wait which should implement desired push-back
|
||||||
func (t *FreeBSDTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
func (t *FreeBSDTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
||||||
// request memory to write from reusable buffer pool
|
// request memory to write from reusable buffer pool
|
||||||
b := buf.NewWithSize(int32(t.options.MTU) + tunHeaderSize)
|
b := buf.NewWithSize(int32(t.mtu) + tunHeaderSize)
|
||||||
|
|
||||||
// read the bytes to the interface file
|
// read the bytes to the interface file
|
||||||
n, err := b.ReadFrom(t.device.File())
|
n, err := b.ReadFrom(t.device.File())
|
||||||
@@ -317,551 +141,23 @@ func (t *FreeBSDTun) Wait() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *FreeBSDTun) newEndpoint() (stack.LinkEndpoint, error) {
|
func (t *FreeBSDTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||||
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
return &LinkEndpoint{deviceMTU: t.mtu, device: t}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h
|
|
||||||
ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
|
|
||||||
)
|
|
||||||
|
|
||||||
// ifAliasReq4 is struct in_aliasreq from netinet/in_var.h in the 64-byte
|
|
||||||
// layout unix.SIOCAIFADDR encodes (name + addr/dstaddr/mask sockaddrs).
|
|
||||||
type ifAliasReq4 struct {
|
|
||||||
Name [unix.IFNAMSIZ]byte
|
|
||||||
Addr unix.RawSockaddrInet4
|
|
||||||
Dstaddr unix.RawSockaddrInet4
|
|
||||||
Mask unix.RawSockaddrInet4
|
|
||||||
}
|
|
||||||
|
|
||||||
// ifAliasReq6 is struct in6_aliasreq from netinet6/in6_var.h. The trailing
|
|
||||||
// Vhid field matters: unix.SIOCAIFADDR_IN6 is not in x/sys/unix, so
|
|
||||||
// siocaifaddrIn6 is derived from this struct's size, and the kernel only
|
|
||||||
// accepts the ioctl whose encoded length matches the real struct.
|
|
||||||
type ifAliasReq6 struct {
|
|
||||||
Name [unix.IFNAMSIZ]byte
|
|
||||||
Addr unix.RawSockaddrInet6
|
|
||||||
Dstaddr unix.RawSockaddrInet6
|
|
||||||
Prefixmask unix.RawSockaddrInet6
|
|
||||||
Flags int32
|
|
||||||
Lifetime addrLifetime6
|
|
||||||
Vhid int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// addrLifetime6 is struct in6_addrlifetime (time_t is int64 on freebsd/amd64).
|
|
||||||
type addrLifetime6 struct {
|
|
||||||
Expire int64
|
|
||||||
Preferred int64
|
|
||||||
Vltime uint32
|
|
||||||
Pltime uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIOCAIFADDR_IN6 = _IOW('i', 27, struct in6_aliasreq); x/sys/unix does not
|
|
||||||
// carry the netinet6 ioctls, so encode it from the struct size like the
|
|
||||||
// header macro does.
|
|
||||||
const siocaifaddrIn6 = 0x80000000 | (uintptr(unsafe.Sizeof(ifAliasReq6{})) << 16) | ('i' << 8) | 27
|
|
||||||
|
|
||||||
// setIPAddress assigns the local/remote point-to-point IPv4 pair and a
|
|
||||||
// link-local IPv6 address to the interface, required for the routing to work
|
|
||||||
// (same scheme as the darwin implementation: local address is the one right
|
|
||||||
// after the gateway address).
|
|
||||||
func setIPAddress(name string, gateway netip.Prefix, local netip.Addr, ifIndex int) error {
|
|
||||||
socket4, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer unix.Close(socket4)
|
|
||||||
|
|
||||||
local4 := local.As4()
|
|
||||||
|
|
||||||
ifReq4 := ifAliasReq4{
|
|
||||||
Addr: unix.RawSockaddrInet4{
|
|
||||||
Len: unix.SizeofSockaddrInet4,
|
|
||||||
Family: unix.AF_INET,
|
|
||||||
Addr: local4,
|
|
||||||
},
|
|
||||||
Dstaddr: unix.RawSockaddrInet4{
|
|
||||||
Len: unix.SizeofSockaddrInet4,
|
|
||||||
Family: unix.AF_INET,
|
|
||||||
Addr: gateway.Addr().As4(),
|
|
||||||
},
|
|
||||||
Mask: unix.RawSockaddrInet4{
|
|
||||||
Len: unix.SizeofSockaddrInet4,
|
|
||||||
Family: unix.AF_INET,
|
|
||||||
Addr: prefixMask4(gateway.Bits()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
copy(ifReq4.Name[:], name)
|
|
||||||
if err = ioctlPtr(socket4, unix.SIOCAIFADDR, unsafe.Pointer(&ifReq4)); err != nil {
|
|
||||||
return os.NewSyscallError("SIOCAIFADDR", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket6, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer unix.Close(socket6)
|
|
||||||
|
|
||||||
// link-local ipv6 address with suffix from ipv4, enough for v6 interface
|
|
||||||
// routes to be attachable (darwin parity); a link-local address needs its
|
|
||||||
// scope, which for FreeBSD ioctls is the interface index
|
|
||||||
local6 := netip.AddrFrom16([16]byte{0: 0xfe, 1: 0x80, 12: local4[0], 13: local4[1], 14: local4[2], 15: local4[3]})
|
|
||||||
|
|
||||||
ifReq6 := ifAliasReq6{
|
|
||||||
Addr: unix.RawSockaddrInet6{
|
|
||||||
Len: unix.SizeofSockaddrInet6,
|
|
||||||
Family: unix.AF_INET6,
|
|
||||||
Addr: local6.As16(),
|
|
||||||
Scope_id: uint32(ifIndex),
|
|
||||||
},
|
|
||||||
Prefixmask: unix.RawSockaddrInet6{
|
|
||||||
Len: unix.SizeofSockaddrInet6,
|
|
||||||
Family: unix.AF_INET6,
|
|
||||||
Addr: prefixMask6(64),
|
|
||||||
},
|
|
||||||
Flags: IN6_IFF_NODAD,
|
|
||||||
Lifetime: addrLifetime6{
|
|
||||||
Vltime: ND6_INFINITE_LIFETIME,
|
|
||||||
Pltime: ND6_INFINITE_LIFETIME,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
copy(ifReq6.Name[:], name)
|
|
||||||
if err = ioctlPtr(socket6, uint(siocaifaddrIn6), unsafe.Pointer(&ifReq6)); err != nil {
|
|
||||||
// non-fatal: FreeBSD auto-configures a link-local address on UP
|
|
||||||
// interfaces, which is all the v6 interface routes need
|
|
||||||
xerrors.LogInfoInner(context.Background(), os.NewSyscallError("SIOCAIFADDR_IN6", err), "[tun] failed to assign the IPv6 link-local address")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
|
|
||||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
|
|
||||||
if errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixMask4(bits int) [4]byte {
|
|
||||||
var mask [4]byte
|
|
||||||
copy(mask[:], net.CIDRMask(bits, 32))
|
|
||||||
return mask
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixMask6(bits int) [16]byte {
|
|
||||||
var mask [16]byte
|
|
||||||
copy(mask[:], net.CIDRMask(bits, 128))
|
|
||||||
return mask
|
|
||||||
}
|
|
||||||
|
|
||||||
// setinterface is the per-socket half of autoOutboundsInterface. FreeBSD has
|
|
||||||
// no SO_BINDTODEVICE/IP_BOUND_IF equivalent, so the socket is pointed at the
|
|
||||||
// escape FIB instead, where Start() mirrors the physical default route; the
|
|
||||||
// iface argument is resolved by the shared updater but unused here (the escape
|
|
||||||
// is table-based, not a per-socket interface bind). checkEscapeFib in NewTun
|
|
||||||
// guarantees the FIB exists before this can run.
|
|
||||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||||
return unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SETFIB, escapeFib)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *FreeBSDTun) setSystemRoutes() error {
|
|
||||||
routes, err := buildSystemRoutes(t.options.AutoSystemRoutingTable)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Route through the interface, not a gateway: the tun(4) device is a
|
|
||||||
// broadcast interface here, so its point-to-point peer address doubles as
|
|
||||||
// the subnet broadcast and the kernel refuses to route to it (EACCES).
|
|
||||||
// Interface routes sidestep the gateway entirely (what wg-quick does on
|
|
||||||
// FreeBSD).
|
|
||||||
for _, destination := range routes {
|
|
||||||
if err := execRoute(-1, unix.RTM_ADD, t.tunIndex, destination, netip.Addr{}); err != nil {
|
|
||||||
_ = t.unsetSystemRoutes()
|
|
||||||
return xerrors.New("failed to add system route ", destination).Base(err)
|
|
||||||
}
|
|
||||||
t.systemRoutes = append(t.systemRoutes, destination)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FreeBSDTun) unsetSystemRoutes() error {
|
|
||||||
var errs []error
|
|
||||||
for i := len(t.systemRoutes) - 1; i >= 0; i-- {
|
|
||||||
destination := t.systemRoutes[i]
|
|
||||||
if err := execRoute(-1, unix.RTM_DELETE, t.tunIndex, destination, netip.Addr{}); err != nil && !errors.Is(err, unix.ESRCH) {
|
|
||||||
errs = append(errs, xerrors.New("failed to delete system route ", destination).Base(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.systemRoutes = nil
|
|
||||||
return xerrors.Combine(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildSystemRoutes(configured []string) ([]netip.Prefix, error) {
|
|
||||||
routes := make([]netip.Prefix, 0, len(configured))
|
|
||||||
seen := make(map[netip.Prefix]struct{})
|
|
||||||
|
|
||||||
appendRoute := func(prefix netip.Prefix) {
|
|
||||||
prefix = prefix.Masked()
|
|
||||||
if _, found := seen[prefix]; found {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
seen[prefix] = struct{}{}
|
|
||||||
routes = append(routes, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, value := range configured {
|
|
||||||
prefix, err := netip.ParsePrefix(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.New("invalid system route ", value).Base(err)
|
|
||||||
}
|
|
||||||
if prefix.Bits() == 0 {
|
|
||||||
for _, protected := range protectedDefaultRoutes(prefix.Addr().Is4()) {
|
|
||||||
appendRoute(protected)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
appendRoute(prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// protectedDefaultRoutes splits a full default route into eight more-specific
|
|
||||||
// prefixes covering everything but the zero /8, so the system's real default
|
|
||||||
// route stays in place for outbound interface discovery (darwin parity).
|
|
||||||
func protectedDefaultRoutes(ipv4 bool) []netip.Prefix {
|
|
||||||
routes := make([]netip.Prefix, 0, 8)
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
if ipv4 {
|
|
||||||
var address [4]byte
|
|
||||||
address[0] = 1 << i
|
|
||||||
routes = append(routes, netip.PrefixFrom(netip.AddrFrom4(address), 8-i))
|
|
||||||
} else {
|
|
||||||
var address [16]byte
|
|
||||||
address[0] = 1 << i
|
|
||||||
routes = append(routes, netip.PrefixFrom(netip.AddrFrom16(address), 8-i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return routes
|
|
||||||
}
|
|
||||||
|
|
||||||
// execRoute writes one RTM message to a routing socket. fib >= 0 targets that
|
|
||||||
// routing table via SO_SETFIB on the routing socket (what route(8) -fib
|
|
||||||
// does); fib -1 leaves the process default table. An invalid gateway produces
|
|
||||||
// an interface route pinned to interfaceIndex instead of a gateway route.
|
|
||||||
func execRoute(fib int, messageType int, interfaceIndex int, destination netip.Prefix, gateway netip.Addr) error {
|
|
||||||
message := route.RouteMessage{
|
|
||||||
Type: messageType,
|
|
||||||
Version: unix.RTM_VERSION,
|
|
||||||
Flags: unix.RTF_STATIC | unix.RTF_GATEWAY,
|
|
||||||
Seq: 1,
|
|
||||||
}
|
|
||||||
if messageType == unix.RTM_ADD {
|
|
||||||
message.Flags |= unix.RTF_UP
|
|
||||||
}
|
|
||||||
|
|
||||||
var gatewayAddr route.Addr
|
|
||||||
switch {
|
|
||||||
case !gateway.IsValid():
|
|
||||||
message.Flags &^= unix.RTF_GATEWAY
|
|
||||||
message.Index = interfaceIndex
|
|
||||||
gatewayAddr = &route.LinkAddr{Index: interfaceIndex}
|
|
||||||
case gateway.Is4():
|
|
||||||
gatewayAddr = &route.Inet4Addr{IP: gateway.As4()}
|
|
||||||
default:
|
|
||||||
gatewayAddr = &route.Inet6Addr{IP: gateway.As16()}
|
|
||||||
}
|
|
||||||
|
|
||||||
if destination.Addr().Is4() {
|
|
||||||
message.Addrs = []route.Addr{
|
|
||||||
unix.RTAX_DST: &route.Inet4Addr{IP: destination.Addr().As4()},
|
|
||||||
unix.RTAX_NETMASK: &route.Inet4Addr{IP: prefixMask4(destination.Bits())},
|
|
||||||
unix.RTAX_GATEWAY: gatewayAddr,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message.Addrs = []route.Addr{
|
|
||||||
unix.RTAX_DST: &route.Inet6Addr{IP: destination.Addr().As16()},
|
|
||||||
unix.RTAX_NETMASK: &route.Inet6Addr{IP: prefixMask6(destination.Bits())},
|
|
||||||
unix.RTAX_GATEWAY: gatewayAddr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request, err := message.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer unix.Close(fd)
|
|
||||||
if fib >= 0 {
|
|
||||||
if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SETFIB, fib); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = unix.Write(fd, request)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
if fixedName != "" {
|
if fixedName == "" {
|
||||||
iface, err := net.InterfaceByName(fixedName)
|
return nil, errors.New("automatic outbound interface selection is not supported on this platform")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if iface.Index == tunIndex {
|
|
||||||
return nil, errors.New("outbound interface cannot be the TUN interface")
|
|
||||||
}
|
|
||||||
return iface, nil
|
|
||||||
}
|
}
|
||||||
|
iface, err := net.InterfaceByName(fixedName)
|
||||||
physical, err := physicalDefaultRoutes(tunIndex, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, family := range []int{unix.AF_INET, unix.AF_INET6} {
|
if iface.Index == tunIndex {
|
||||||
for _, route := range physical {
|
return nil, errors.New("outbound interface cannot be the TUN interface")
|
||||||
if route.family == family {
|
|
||||||
return route.iface, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("default route not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// physicalRoute describes one physical default route: the interface it
|
|
||||||
// leaves through, its gateway, and the connected prefix that makes the
|
|
||||||
// gateway resolvable.
|
|
||||||
type physicalRoute struct {
|
|
||||||
family int
|
|
||||||
iface *net.Interface
|
|
||||||
gateway netip.Addr
|
|
||||||
connected netip.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
// physicalDefaultRoutes scans the default routing table for default routes
|
|
||||||
// that do not go through the TUN interface, at most one per address family
|
|
||||||
// (the first usable one wins, matching the darwin implementation's
|
|
||||||
// preference order). A non-zero onlyIndex restricts the scan to that
|
|
||||||
// interface, for the fixed-name mode of autoOutboundsInterface.
|
|
||||||
func physicalDefaultRoutes(tunIndex int, onlyIndex int) ([]physicalRoute, error) {
|
|
||||||
rib, err := route.FetchRIB(unix.AF_UNSPEC, route.RIBTypeRoute, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
messages, err := route.ParseRIB(route.RIBTypeRoute, rib)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
found := make([]physicalRoute, 0, 2)
|
|
||||||
seen := make(map[int]bool)
|
|
||||||
for _, message := range messages {
|
|
||||||
routeMessage, ok := message.(*route.RouteMessage)
|
|
||||||
if !ok || routeMessage.Index == tunIndex {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if onlyIndex != 0 && routeMessage.Index != onlyIndex {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if routeMessage.Flags&unix.RTF_UP == 0 || routeMessage.Flags&unix.RTF_GATEWAY == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
family, ok := defaultRouteFamily(routeMessage)
|
|
||||||
if !ok || seen[family] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
iface, err := usableInterface(routeMessage.Index)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
gatewayAddr, ok := routeAddrToNetip(routeMessage.Addrs[unix.RTAX_GATEWAY])
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
connected, err := connectedPrefix(iface, gatewayAddr)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[family] = true
|
|
||||||
found = append(found, physicalRoute{
|
|
||||||
family: family,
|
|
||||||
iface: iface,
|
|
||||||
gateway: gatewayAddr,
|
|
||||||
connected: connected,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(found) == 0 {
|
|
||||||
return nil, errors.New("default route not found")
|
|
||||||
}
|
|
||||||
return found, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultRouteFamily reports the address family of a RIB message that
|
|
||||||
// represents a true default route (unspecified destination, zero mask).
|
|
||||||
func defaultRouteFamily(message *route.RouteMessage) (int, bool) {
|
|
||||||
if len(message.Addrs) <= unix.RTAX_NETMASK {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch destination := message.Addrs[unix.RTAX_DST].(type) {
|
|
||||||
case *route.Inet4Addr:
|
|
||||||
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr)
|
|
||||||
if !ok || destination.IP != netip.IPv4Unspecified().As4() {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
ones, bits := net.IPMask(mask.IP[:]).Size()
|
|
||||||
return unix.AF_INET, ones == 0 && bits == 32
|
|
||||||
case *route.Inet6Addr:
|
|
||||||
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet6Addr)
|
|
||||||
if !ok || destination.IP != netip.IPv6Unspecified().As16() {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
ones, bits := net.IPMask(mask.IP[:]).Size()
|
|
||||||
return unix.AF_INET6, ones == 0 && bits == 128
|
|
||||||
default:
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func usableInterface(index int) (*net.Interface, error) {
|
|
||||||
iface, err := net.InterfaceByIndex(index)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
|
||||||
return nil, errors.New("default route interface is not usable")
|
|
||||||
}
|
}
|
||||||
return iface, nil
|
return iface, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func routeAddrToNetip(addr route.Addr) (netip.Addr, bool) {
|
|
||||||
switch typed := addr.(type) {
|
|
||||||
case *route.Inet4Addr:
|
|
||||||
return netip.AddrFrom4(typed.IP), true
|
|
||||||
case *route.Inet6Addr:
|
|
||||||
return netip.AddrFrom16(typed.IP), true
|
|
||||||
default:
|
|
||||||
return netip.Addr{}, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkEscapeFib verifies the system can host the escape routing table at
|
|
||||||
// all: FIBs are a boot-time resource on FreeBSD.
|
|
||||||
func checkEscapeFib() error {
|
|
||||||
fibs, err := unix.SysctlUint32("net.fibs")
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.New("failed to read net.fibs").Base(err)
|
|
||||||
}
|
|
||||||
if fibs < 2 {
|
|
||||||
return errors.New("automatic outbound interface on FreeBSD needs a second routing table: add net.fibs=2 to /boot/loader.conf and reboot")
|
|
||||||
}
|
|
||||||
current, err := unix.SysctlUint32("net.my_fibnum")
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.New("failed to read net.my_fibnum").Base(err)
|
|
||||||
}
|
|
||||||
if current == escapeFib {
|
|
||||||
return errors.New("xray runs inside routing table 1, which is reserved as the escape table; start it in another FIB")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// syncEscapeFib mirrors the physical default routes (and the connected
|
|
||||||
// prefixes their gateways resolve through) into the escape FIB, replacing
|
|
||||||
// whatever mirror a previous call installed. On discovery failure the old
|
|
||||||
// mirror is kept, since a stale escape route beats none during a transient
|
|
||||||
// route flap.
|
|
||||||
func (t *FreeBSDTun) syncEscapeFib() error {
|
|
||||||
var onlyIndex int
|
|
||||||
if t.options.AutoOutboundsInterface != "" && updater != nil {
|
|
||||||
if iface := updater.Get(); iface != nil {
|
|
||||||
onlyIndex = iface.Index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
physical, err := physicalDefaultRoutes(t.tunIndex, onlyIndex)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
desired := make([]escapeRoute, 0, 2*len(physical))
|
|
||||||
for _, p := range physical {
|
|
||||||
desired = append(desired,
|
|
||||||
escapeRoute{prefix: p.connected, ifIndex: p.iface.Index},
|
|
||||||
escapeRoute{prefix: defaultPrefix(p.family), ifIndex: p.iface.Index, gateway: p.gateway},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.escapeMu.Lock()
|
|
||||||
defer t.escapeMu.Unlock()
|
|
||||||
|
|
||||||
// The route monitor hears our own escape FIB writes too; rewriting an
|
|
||||||
// unchanged mirror on every wake-up would ping-pong forever.
|
|
||||||
if slices.Equal(t.escapeRoutes, desired) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
t.unsetEscapeFibLocked()
|
|
||||||
for _, entry := range desired {
|
|
||||||
err := execRoute(escapeFib, unix.RTM_ADD, entry.ifIndex, entry.prefix, entry.gateway)
|
|
||||||
if err != nil && !errors.Is(err, unix.EEXIST) {
|
|
||||||
return xerrors.New("failed to add escape route ", entry.prefix).Base(err)
|
|
||||||
}
|
|
||||||
t.escapeRoutes = append(t.escapeRoutes, entry)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *FreeBSDTun) unsetEscapeFib() {
|
|
||||||
t.escapeMu.Lock()
|
|
||||||
defer t.escapeMu.Unlock()
|
|
||||||
t.unsetEscapeFibLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *FreeBSDTun) unsetEscapeFibLocked() {
|
|
||||||
for i := len(t.escapeRoutes) - 1; i >= 0; i-- {
|
|
||||||
entry := t.escapeRoutes[i]
|
|
||||||
err := execRoute(escapeFib, unix.RTM_DELETE, entry.ifIndex, entry.prefix, entry.gateway)
|
|
||||||
if err != nil && !errors.Is(err, unix.ESRCH) {
|
|
||||||
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to delete escape route ", entry.prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.escapeRoutes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultPrefix(family int) netip.Prefix {
|
|
||||||
if family == unix.AF_INET {
|
|
||||||
return netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
|
||||||
}
|
|
||||||
return netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// connectedPrefix finds the interface's address prefix containing the
|
|
||||||
// gateway, which the escape FIB needs as an interface route so the mirrored
|
|
||||||
// default route's gateway is resolvable there.
|
|
||||||
func connectedPrefix(iface *net.Interface, gateway netip.Addr) (netip.Prefix, error) {
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return netip.Prefix{}, err
|
|
||||||
}
|
|
||||||
for _, addr := range addrs {
|
|
||||||
ipNet, ok := addr.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ip, ok := netip.AddrFromSlice(ipNet.IP)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ip = ip.Unmap()
|
|
||||||
ones, _ := ipNet.Mask.Size()
|
|
||||||
prefix := netip.PrefixFrom(ip, ones).Masked()
|
|
||||||
if prefix.Contains(gateway.WithZone("").Unmap()) {
|
|
||||||
return prefix, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return netip.Prefix{}, errors.New("no connected prefix contains the gateway")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
//go:build freebsd
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/route"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSelectFreeBSDGatewayDefault(t *testing.T) {
|
|
||||||
gateway, local, err := selectFreeBSDGateway(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if gateway != netip.MustParsePrefix(defaultFreeBSDGateway) {
|
|
||||||
t.Fatal("expected default gateway, got ", gateway)
|
|
||||||
}
|
|
||||||
if local != netip.MustParseAddr("169.254.10.2") {
|
|
||||||
t.Fatal("wrong local address: ", local)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelectFreeBSDGatewayPicksFirstIPv4(t *testing.T) {
|
|
||||||
gateway, local, err := selectFreeBSDGateway([]string{"fd00::1/64", "10.0.0.1/30", "10.9.9.9/24"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if gateway != netip.MustParsePrefix("10.0.0.1/30") {
|
|
||||||
t.Fatal("wrong gateway: ", gateway)
|
|
||||||
}
|
|
||||||
if local != netip.MustParseAddr("10.0.0.2") {
|
|
||||||
t.Fatal("wrong local address: ", local)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelectFreeBSDGatewayRequiresIPv4(t *testing.T) {
|
|
||||||
if _, _, err := selectFreeBSDGateway([]string{"fd00::1/64"}); err == nil {
|
|
||||||
t.Fatal("expected error when no IPv4 gateway is configured")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelectFreeBSDGatewayRejectsGarbage(t *testing.T) {
|
|
||||||
if _, _, err := selectFreeBSDGateway([]string{"not-a-prefix"}); err == nil {
|
|
||||||
t.Fatal("expected error for invalid gateway")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelectFreeBSDGatewayRejectsFullPrefix(t *testing.T) {
|
|
||||||
// 10.0.0.255/30: the "next" local address falls outside the prefix
|
|
||||||
if _, _, err := selectFreeBSDGateway([]string{"10.0.0.255/30"}); err == nil {
|
|
||||||
t.Fatal("expected error when no usable local address follows the gateway")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNextLocalIPv4(t *testing.T) {
|
|
||||||
local, ok := nextLocalIPv4(netip.MustParsePrefix("169.254.10.1/30"))
|
|
||||||
if !ok || local != netip.MustParseAddr("169.254.10.2") {
|
|
||||||
t.Fatal("wrong local address: ", local)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildSystemRoutesSplitsDefault(t *testing.T) {
|
|
||||||
routes, err := buildSystemRoutes([]string{"0.0.0.0/0"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
expected := []string{
|
|
||||||
"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5",
|
|
||||||
"16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1",
|
|
||||||
}
|
|
||||||
if len(routes) != len(expected) {
|
|
||||||
t.Fatal("expected ", len(expected), " routes, got ", routes)
|
|
||||||
}
|
|
||||||
for i, want := range expected {
|
|
||||||
if routes[i] != netip.MustParsePrefix(want) {
|
|
||||||
t.Fatal("route ", i, ": expected ", want, ", got ", routes[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildSystemRoutesSplitsDefaultIPv6(t *testing.T) {
|
|
||||||
routes, err := buildSystemRoutes([]string{"::/0"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(routes) != 8 || routes[7] != netip.MustParsePrefix("8000::/1") {
|
|
||||||
t.Fatal("unexpected v6 split: ", routes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildSystemRoutesDeduplicates(t *testing.T) {
|
|
||||||
routes, err := buildSystemRoutes([]string{"10.0.0.0/8", "10.1.2.3/8", "0.0.0.0/0", "0.0.0.0/0"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(routes) != 9 { // 10.0.0.0/8 once + 8 splits once
|
|
||||||
t.Fatal("expected 9 routes, got ", routes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildSystemRoutesRejectsGarbage(t *testing.T) {
|
|
||||||
if _, err := buildSystemRoutes([]string{"10.0.0.0/33"}); err == nil {
|
|
||||||
t.Fatal("expected error for invalid route")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func routeMessage(dst, mask route.Addr) *route.RouteMessage {
|
|
||||||
addrs := make([]route.Addr, unix.RTAX_NETMASK+1)
|
|
||||||
addrs[unix.RTAX_DST] = dst
|
|
||||||
addrs[unix.RTAX_NETMASK] = mask
|
|
||||||
return &route.RouteMessage{Addrs: addrs}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultRouteFamilyMatchesIPv4Default(t *testing.T) {
|
|
||||||
family, ok := defaultRouteFamily(routeMessage(&route.Inet4Addr{}, &route.Inet4Addr{}))
|
|
||||||
if !ok || family != unix.AF_INET {
|
|
||||||
t.Fatal("expected IPv4 default route match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultRouteFamilyMatchesIPv6Default(t *testing.T) {
|
|
||||||
family, ok := defaultRouteFamily(routeMessage(&route.Inet6Addr{}, &route.Inet6Addr{}))
|
|
||||||
if !ok || family != unix.AF_INET6 {
|
|
||||||
t.Fatal("expected IPv6 default route match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultRouteFamilyRejectsNonDefault(t *testing.T) {
|
|
||||||
if _, ok := defaultRouteFamily(routeMessage(
|
|
||||||
&route.Inet4Addr{IP: [4]byte{10, 0, 0, 0}},
|
|
||||||
&route.Inet4Addr{IP: [4]byte{255, 0, 0, 0}},
|
|
||||||
)); ok {
|
|
||||||
t.Fatal("non-default destination must not match")
|
|
||||||
}
|
|
||||||
if _, ok := defaultRouteFamily(routeMessage(
|
|
||||||
&route.Inet4Addr{},
|
|
||||||
&route.Inet4Addr{IP: [4]byte{255, 0, 0, 0}},
|
|
||||||
)); ok {
|
|
||||||
t.Fatal("non-zero mask must not match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultRouteFamilyRejectsShortAddrs(t *testing.T) {
|
|
||||||
if _, ok := defaultRouteFamily(&route.RouteMessage{}); ok {
|
|
||||||
t.Fatal("message without addresses must not match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,12 +10,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
)
|
)
|
||||||
@@ -38,22 +35,6 @@ type LinuxTun struct {
|
|||||||
// LinuxTun implements Tun
|
// LinuxTun implements Tun
|
||||||
var _ Tun = (*LinuxTun)(nil)
|
var _ Tun = (*LinuxTun)(nil)
|
||||||
|
|
||||||
// LinuxTun implements GVisorDevice, used by the "system" (lite) ip stack
|
|
||||||
var _ GVisorDevice = (*LinuxTun)(nil)
|
|
||||||
|
|
||||||
// fdReadWriter adapts a raw, already non-blocking file descriptor to io.Reader/io.Writer,
|
|
||||||
// so it can be used with buf.Buffer.ReadFrom, without the ownership/finalizer overhead of
|
|
||||||
// wrapping it in an *os.File (the fd is owned and closed elsewhere, see LinuxTun.Close).
|
|
||||||
type fdReadWriter int
|
|
||||||
|
|
||||||
func (f fdReadWriter) Read(p []byte) (int, error) {
|
|
||||||
return unix.Read(int(f), p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fdReadWriter) Write(p []byte) (int, error) {
|
|
||||||
return unix.Write(int(f), p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTun builds new tun interface handler (linux specific)
|
// NewTun builds new tun interface handler (linux specific)
|
||||||
func NewTun(options *Config) (Tun, error) {
|
func NewTun(options *Config) (Tun, error) {
|
||||||
tunFd, tunLink, fdProvided, err := openFromEnv(options.Name)
|
tunFd, tunLink, fdProvided, err := openFromEnv(options.Name)
|
||||||
@@ -247,72 +228,6 @@ func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPacket implements GVisorDevice method to read one packet from the tun device, used by
|
|
||||||
// the "system" (lite) ip stack. The gVisor backed stack instead talks to the fd directly through
|
|
||||||
// fdbased.New above, for lower overhead batched IO, bypassing GVisorDevice entirely.
|
|
||||||
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
|
|
||||||
// which will make the stack call Wait which should implement desired push-back
|
|
||||||
func (t *LinuxTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
|
||||||
// request memory to write from reusable buffer pool
|
|
||||||
b := buf.NewWithSize(int32(t.options.MTU))
|
|
||||||
|
|
||||||
// read the bytes from the interface file descriptor, which is already non-blocking
|
|
||||||
n, err := b.ReadFrom(fdReadWriter(t.tunFd))
|
|
||||||
if err == unix.EAGAIN || err == unix.EWOULDBLOCK || err == unix.EINTR {
|
|
||||||
b.Release()
|
|
||||||
return 0, nil, ErrQueueEmpty
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
b.Release()
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// discard empty packets
|
|
||||||
if n == 0 {
|
|
||||||
b.Release()
|
|
||||||
return 0, nil, ErrQueueEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
// network protocol version from the first nibble of the raw packet
|
|
||||||
version := b.Byte(0) >> 4
|
|
||||||
packetBuffer := buffer.MakeWithData(b.Bytes())
|
|
||||||
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
||||||
Payload: packetBuffer,
|
|
||||||
IsForwardedPacket: true,
|
|
||||||
OnRelease: func() {
|
|
||||||
b.Release()
|
|
||||||
},
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
|
||||||
func (t *LinuxTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
|
||||||
// request memory to write from reusable buffer pool
|
|
||||||
b := buf.NewWithSize(int32(t.options.MTU))
|
|
||||||
defer b.Release()
|
|
||||||
|
|
||||||
// copy the bytes of slices that compose the packet into the allocated buffer, no
|
|
||||||
// Linux specific header is needed here, unlike Darwin/FreeBSD's utun devices
|
|
||||||
for _, packetElement := range packet.AsSlices() {
|
|
||||||
_, _ = b.Write(packetElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fdReadWriter(t.tunFd).Write(b.Bytes()); err != nil {
|
|
||||||
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
|
|
||||||
return &tcpip.ErrWouldBlock{}
|
|
||||||
}
|
|
||||||
return &tcpip.ErrAborted{}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait blocks until the tun fd is likely readable again, rather than spinning the CPU.
|
|
||||||
// A bounded timeout keeps this responsive to a Close() racing a call already parked here.
|
|
||||||
func (t *LinuxTun) Wait() {
|
|
||||||
fds := []unix.PollFd{{Fd: int32(t.tunFd), Events: unix.POLLIN}}
|
|
||||||
_, _ = unix.Poll(fds, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||||
return unix.BindToDevice(int(fd), iface.Name)
|
return unix.BindToDevice(int(fd), iface.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
+134
-142
@@ -3,14 +3,14 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
go_errors "errors"
|
go_errors "errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -31,14 +31,13 @@ func procyield(cycles uint32)
|
|||||||
type WindowsTun struct {
|
type WindowsTun struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
options *Config
|
options *Config
|
||||||
adapter *wintun.Adapter
|
adapter *wintun.Adapter
|
||||||
session wintun.Session
|
session wintun.Session
|
||||||
readWait windows.Handle
|
readWait windows.Handle
|
||||||
luid winipcfg.LUID
|
luid winipcfg.LUID
|
||||||
cbr winipcfg.ChangeCallback
|
changeCallback winipcfg.ChangeCallback
|
||||||
cbi winipcfg.ChangeCallback
|
closed bool
|
||||||
closed bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WindowsTun implements Tun
|
// WindowsTun implements Tun
|
||||||
@@ -86,37 +85,23 @@ func open(name, desc string) (*wintun.Adapter, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *WindowsTun) Start() (err error) {
|
func (t *WindowsTun) Start() error {
|
||||||
var address4, address6 bool
|
var has4, has6 bool
|
||||||
addresses := make([]netip.Prefix, 0, len(t.options.Gateway))
|
allowedIPs := make([]netip.Prefix, 0, len(t.options.AutoSystemRoutingTable))
|
||||||
for _, cidr := range t.options.Gateway {
|
for _, route := range t.options.AutoSystemRoutingTable {
|
||||||
prefix := netip.MustParsePrefix(cidr)
|
allowedIPs = append(allowedIPs, netip.MustParsePrefix(route))
|
||||||
if prefix.Addr().Is4() {
|
|
||||||
address4 = true
|
|
||||||
} else {
|
|
||||||
address6 = true
|
|
||||||
}
|
|
||||||
addresses = append(addresses, prefix)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dns := make([]netip.Addr, 0, len(t.options.DNS))
|
|
||||||
for _, ip := range t.options.DNS {
|
|
||||||
dns = append(dns, netip.MustParseAddr(ip))
|
|
||||||
}
|
|
||||||
|
|
||||||
var route4, route6 bool
|
|
||||||
routesMap := make(map[winipcfg.RouteData]struct{})
|
routesMap := make(map[winipcfg.RouteData]struct{})
|
||||||
for _, cidr := range t.options.AutoSystemRoutingTable {
|
for _, ip := range allowedIPs {
|
||||||
prefix := netip.MustParsePrefix(cidr)
|
|
||||||
route := winipcfg.RouteData{
|
route := winipcfg.RouteData{
|
||||||
Destination: prefix.Masked(),
|
Destination: ip.Masked(),
|
||||||
Metric: 0,
|
Metric: 0,
|
||||||
}
|
}
|
||||||
if prefix.Addr().Is4() {
|
if ip.Addr().Is4() {
|
||||||
route4 = true
|
has4 = true
|
||||||
route.NextHop = netip.IPv4Unspecified()
|
route.NextHop = netip.IPv4Unspecified()
|
||||||
} else {
|
} else {
|
||||||
route6 = true
|
has6 = true
|
||||||
route.NextHop = netip.IPv6Unspecified()
|
route.NextHop = netip.IPv6Unspecified()
|
||||||
}
|
}
|
||||||
routesMap[route] = struct{}{}
|
routesMap[route] = struct{}{}
|
||||||
@@ -126,40 +111,24 @@ func (t *WindowsTun) Start() (err error) {
|
|||||||
r := route
|
r := route
|
||||||
routesData = append(routesData, &r)
|
routesData = append(routesData, &r)
|
||||||
}
|
}
|
||||||
|
err := t.luid.SetRoutes(routesData)
|
||||||
var retryTimes int
|
if err != nil {
|
||||||
var firstErr error
|
return errors.New("unable to set routes").Base(err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
retryTimes++
|
|
||||||
for _, family := range []winipcfg.AddressFamily{windows.AF_INET, windows.AF_INET6} {
|
if len(t.options.Gateway) > 0 {
|
||||||
if family == windows.AF_INET && route4 || family == windows.AF_INET6 && route6 {
|
addresses := make([]netip.Prefix, 0, len(t.options.Gateway))
|
||||||
err = t.luid.SetRoutesForFamily(family, routesData)
|
for _, address := range t.options.Gateway {
|
||||||
if err != nil {
|
addresses = append(addresses, netip.MustParsePrefix(address))
|
||||||
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.SetIPAddresses(addresses)
|
||||||
err = t.luid.SetIPAddressesForFamily(family, addresses)
|
if err != nil {
|
||||||
if err != nil {
|
return errors.New("unable to set ips").Base(err)
|
||||||
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 has4 {
|
||||||
|
ipif, err := t.luid.IPInterface(windows.AF_INET)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -167,45 +136,56 @@ startOver:
|
|||||||
ipif.DadTransmits = 0
|
ipif.DadTransmits = 0
|
||||||
ipif.ManagedAddressConfigurationSupported = false
|
ipif.ManagedAddressConfigurationSupported = false
|
||||||
ipif.OtherStatefulConfigurationSupported = false
|
ipif.OtherStatefulConfigurationSupported = false
|
||||||
if family == windows.AF_INET && (address4 || route4) || family == windows.AF_INET6 && (address6 || route6) {
|
ipif.NLMTU = t.options.MTU
|
||||||
ipif.NLMTU = t.options.MTU
|
ipif.UseAutomaticMetric = false
|
||||||
}
|
ipif.Metric = 0
|
||||||
if family == windows.AF_INET && route4 || family == windows.AF_INET6 && route6 {
|
|
||||||
ipif.UseAutomaticMetric = false
|
|
||||||
ipif.Metric = 0
|
|
||||||
}
|
|
||||||
err = ipif.Set()
|
err = ipif.Set()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
firstErr = errors.New("unable to set metric and MTU").Base(err)
|
return err
|
||||||
if err == windows.ERROR_NOT_FOUND {
|
|
||||||
goto startOver
|
|
||||||
}
|
|
||||||
return firstErr
|
|
||||||
}
|
}
|
||||||
err = t.luid.SetDNS(family, dns, nil)
|
}
|
||||||
|
if has6 {
|
||||||
|
ipif, err := t.luid.IPInterface(windows.AF_INET6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
firstErr = errors.New("unable to set DNS").Base(err)
|
return err
|
||||||
if err == windows.ERROR_NOT_FOUND {
|
}
|
||||||
goto startOver
|
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
||||||
}
|
ipif.DadTransmits = 0
|
||||||
return firstErr
|
ipif.ManagedAddressConfigurationSupported = false
|
||||||
|
ipif.OtherStatefulConfigurationSupported = false
|
||||||
|
ipif.NLMTU = t.options.MTU
|
||||||
|
ipif.UseAutomaticMetric = false
|
||||||
|
ipif.Metric = 0
|
||||||
|
err = ipif.Set()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.options.DNS) > 0 {
|
||||||
|
dns := make([]netip.Addr, 0, len(t.options.DNS))
|
||||||
|
for _, ip := range t.options.DNS {
|
||||||
|
dns = append(dns, netip.MustParseAddr(ip))
|
||||||
|
}
|
||||||
|
err := t.luid.SetDNS(windows.AF_INET, dns, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = t.luid.SetDNS(windows.AF_INET6, dns, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if updater != nil {
|
if updater != nil {
|
||||||
t.cbr, err = winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) {
|
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
||||||
updater.Update()
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.cbi, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
|
||||||
updater.Update()
|
updater.Update()
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,26 +197,12 @@ func (t *WindowsTun) Close() error {
|
|||||||
}
|
}
|
||||||
t.closed = true
|
t.closed = true
|
||||||
|
|
||||||
if t.cbr != nil {
|
if t.changeCallback != nil {
|
||||||
t.cbr.Unregister()
|
t.changeCallback.Unregister()
|
||||||
}
|
|
||||||
if t.cbi != nil {
|
|
||||||
t.cbi.Unregister()
|
|
||||||
}
|
|
||||||
if t.luid != 0 {
|
|
||||||
t.luid.FlushRoutes(windows.AF_INET)
|
|
||||||
t.luid.FlushIPAddresses(windows.AF_INET)
|
|
||||||
t.luid.FlushDNS(windows.AF_INET)
|
|
||||||
t.luid.FlushRoutes(windows.AF_INET6)
|
|
||||||
t.luid.FlushIPAddresses(windows.AF_INET6)
|
|
||||||
t.luid.FlushDNS(windows.AF_INET6)
|
|
||||||
}
|
|
||||||
if t.session != (wintun.Session{}) {
|
|
||||||
t.session.End()
|
|
||||||
}
|
|
||||||
if t.adapter != nil {
|
|
||||||
t.adapter.Close()
|
|
||||||
}
|
}
|
||||||
|
t.session.End()
|
||||||
|
_ = t.adapter.Close()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,49 +311,75 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
if fixedName != "" {
|
interfaces, err := net.Interfaces()
|
||||||
return net.InterfaceByName(fixedName)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
lowestMetric := ^uint32(0)
|
|
||||||
index := uint32(0)
|
|
||||||
lowestMetricWifi := ^uint32(0)
|
|
||||||
indexWifi := uint32(0)
|
|
||||||
for i := range r {
|
|
||||||
if r[i].DestinationPrefix.PrefixLength != 0 || r[i].InterfaceIndex == uint32(tunIndex) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ifrow, err := r[i].InterfaceLUID.Interface()
|
|
||||||
if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
iface, err := r[i].InterfaceLUID.IPInterface(windows.AF_INET)
|
if fixedName != "" {
|
||||||
if err != nil {
|
for _, iface := range interfaces {
|
||||||
iface, err = r[i].InterfaceLUID.IPInterface(windows.AF_INET6)
|
if iface.Index != tunIndex && iface.Name == fixedName {
|
||||||
if err != nil {
|
return &iface, nil
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
if ifrow.Type == windows.IF_TYPE_IEEE80211 {
|
var candidates []struct {
|
||||||
if r[i].Metric+iface.Metric < lowestMetricWifi {
|
index int
|
||||||
lowestMetricWifi = r[i].Metric + iface.Metric
|
score int
|
||||||
indexWifi = r[i].InterfaceIndex
|
}
|
||||||
}
|
for i, iface := range interfaces {
|
||||||
|
if iface.Index == tunIndex {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r[i].Metric+iface.Metric < lowestMetric {
|
if strings.Contains(iface.Name, "vEthernet") {
|
||||||
lowestMetric = r[i].Metric + iface.Metric
|
continue
|
||||||
index = r[i].InterfaceIndex
|
|
||||||
}
|
}
|
||||||
|
if iface.Flags&net.FlagUp == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if iface.Flags&net.FlagLoopback != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil || len(addrs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
candidates = append(candidates, struct {
|
||||||
|
index int
|
||||||
|
score int
|
||||||
|
}{i, scoreWindowsInterface(&iface, addrs)})
|
||||||
}
|
}
|
||||||
if indexWifi != 0 {
|
|
||||||
index = indexWifi
|
sort.Slice(candidates, func(i, j int) bool {
|
||||||
|
if candidates[i].score != candidates[j].score {
|
||||||
|
return candidates[i].score > candidates[j].score
|
||||||
|
}
|
||||||
|
return interfaces[candidates[i].index].Name < interfaces[candidates[j].index].Name
|
||||||
|
})
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
return net.InterfaceByIndex(int(index))
|
|
||||||
|
iface := interfaces[candidates[0].index]
|
||||||
|
return &iface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scoreWindowsInterface(iface *net.Interface, addrs []net.Addr) int {
|
||||||
|
score := 0
|
||||||
|
|
||||||
|
name := strings.ToLower(iface.Name)
|
||||||
|
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
|
||||||
|
score += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if strings.HasPrefix(addr.String(), "192.168.") {
|
||||||
|
score++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,6 +202,8 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
ob.Conn = conn // for Vision's pre-connect
|
||||||
|
|
||||||
iConn := stat.TryUnwrapStatsConn(conn)
|
iConn := stat.TryUnwrapStatsConn(conn)
|
||||||
target := ob.Target
|
target := ob.Target
|
||||||
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination.NetAddr())
|
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination.NetAddr())
|
||||||
|
|||||||
+138
-113
@@ -3,6 +3,7 @@ package wireguard
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
gonet "net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -27,10 +28,14 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
got []net.IP
|
||||||
|
time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
conf *DeviceConfig
|
conf *DeviceConfig
|
||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
@@ -44,6 +49,11 @@ type Handler struct {
|
|||||||
tnet *Net
|
tnet *Net
|
||||||
dev *device.Device
|
dev *device.Device
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
|
// TODO: cache cleanup loop
|
||||||
|
local bool
|
||||||
|
cache map[string]entry
|
||||||
|
cacheMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
|
func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
|
||||||
@@ -99,10 +109,15 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local := false
|
||||||
dns := conf.DNS
|
dns := conf.DNS
|
||||||
if len(dns) == 0 {
|
if len(dns) == 0 {
|
||||||
dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"}
|
dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"}
|
||||||
}
|
}
|
||||||
|
if len(dns) == 1 && dns[0] == "local" {
|
||||||
|
local = true
|
||||||
|
dns = nil
|
||||||
|
}
|
||||||
dnses := make([]netip.Addr, 0, len(dns))
|
dnses := make([]netip.Addr, 0, len(dns))
|
||||||
for _, dns := range dns {
|
for _, dns := range dns {
|
||||||
dnses = append(dnses, netip.MustParseAddr(dns))
|
dnses = append(dnses, netip.MustParseAddr(dns))
|
||||||
@@ -136,6 +151,9 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
|
|||||||
|
|
||||||
tun: tun,
|
tun: tun,
|
||||||
tnet: tnet,
|
tnet: tnet,
|
||||||
|
|
||||||
|
local: local,
|
||||||
|
cache: make(map[string]entry),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +172,22 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addr netip.Addr
|
||||||
|
if ob.Target.Address.Family().IsDomain() {
|
||||||
|
ip, err := h.resolveRemote(ob.Target.Address.String())
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to resolve domain").Base(err)
|
||||||
|
}
|
||||||
|
addr, _ = netip.AddrFromSlice(ip)
|
||||||
|
} else {
|
||||||
|
addr, _ = netip.AddrFromSlice(ob.Target.Address.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
addrPort := netip.AddrPortFrom(addr, ob.Target.Port.Value())
|
||||||
|
if !addrPort.IsValid() {
|
||||||
|
return errors.New("invalid target ", ob.Target)
|
||||||
|
}
|
||||||
|
|
||||||
var newCtx context.Context
|
var newCtx context.Context
|
||||||
var newCancel context.CancelFunc
|
var newCancel context.CancelFunc
|
||||||
if session.TimeoutOnlyFromContext(ctx) {
|
if session.TimeoutOnlyFromContext(ctx) {
|
||||||
@@ -182,10 +216,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
var err error
|
var err error
|
||||||
if sessionPolicy.Timeouts.Handshake != 0 {
|
if sessionPolicy.Timeouts.Handshake != 0 {
|
||||||
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
|
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
|
||||||
conn, err = h.tnet.DialContext(timeoutCtx, "tcp", ob.Target.NetAddr())
|
conn, err = h.tnet.DialContextTCPAddrPort(timeoutCtx, addrPort)
|
||||||
timeoutCancel()
|
timeoutCancel()
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.tnet.Dial("tcp", ob.Target.NetAddr())
|
conn, err = h.tnet.DialContextTCPAddrPort(ctx, addrPort)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to create TCP connection").Base(err)
|
return errors.New("failed to create TCP connection").Base(err)
|
||||||
@@ -194,14 +228,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
reader = buf.NewReader(conn)
|
reader = buf.NewReader(conn)
|
||||||
writer = buf.NewWriter(conn)
|
writer = buf.NewWriter(conn)
|
||||||
case net.Network_UDP:
|
case net.Network_UDP:
|
||||||
conn, err := h.tnet.Dial("udp", ob.Target.NetAddr())
|
conn, err := h.tnet.DialUDPAddrPort(netip.AddrPort{}, addrPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to create UDP connection").Base(err)
|
return errors.New("failed to create UDP connection").Base(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
c := &udpConnClient{
|
c := &udpConnClient{
|
||||||
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
|
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
|
||||||
dest: conn.RemoteAddr().(*net.UDPAddr),
|
resolveFunc: h.resolveRemote,
|
||||||
|
dest: gonet.UDPAddrFromAddrPort(addrPort),
|
||||||
}
|
}
|
||||||
reader = c
|
reader = c
|
||||||
writer = c
|
writer = c
|
||||||
@@ -258,26 +293,26 @@ func (h *Handler) init(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var pktConn net.PacketConn
|
var pktConn net.PacketConn
|
||||||
if h.streamSettings.FinalMask != nil {
|
switch c := conn.(type) {
|
||||||
conn, err := h.streamSettings.FinalMask.DialUDP(ctx, dest)
|
case *internet.PacketConnWrapper:
|
||||||
|
pktConn = c.PacketConn
|
||||||
|
case *cnc.Connection:
|
||||||
|
pktConn = &internet.FakePacketConn{Conn: c}
|
||||||
|
default:
|
||||||
|
panic(reflect.TypeOf(c))
|
||||||
|
}
|
||||||
|
if h.streamSettings.UdpmaskManager != nil {
|
||||||
|
newConn, err := h.streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to dial to dest").Base(err)
|
pktConn.Close()
|
||||||
}
|
return nil, errors.New("mask err").Base(err)
|
||||||
pktConn = conn.(*finalmask.PacketConnWrapper).PacketConn
|
|
||||||
} else {
|
|
||||||
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to dial to dest").Base(err)
|
|
||||||
}
|
|
||||||
switch c := conn.(type) {
|
|
||||||
case *internet.PacketConnWrapper:
|
|
||||||
pktConn = c.PacketConn
|
|
||||||
case *cnc.Connection:
|
|
||||||
pktConn = &internet.FakePacketConn{Conn: c}
|
|
||||||
default:
|
|
||||||
panic(reflect.TypeOf(c))
|
|
||||||
}
|
}
|
||||||
|
pktConn = newConn
|
||||||
}
|
}
|
||||||
if h.uplinkCounter != nil || h.downlinkCounter != nil {
|
if h.uplinkCounter != nil || h.downlinkCounter != nil {
|
||||||
pktConn = &PacketCounterConnection{
|
pktConn = &PacketCounterConnection{
|
||||||
@@ -336,48 +371,87 @@ func (h *Handler) init(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) resolveLocal(host string) (net.IP, error) {
|
func (h *Handler) resolveLocal(host string) (net.IP, error) {
|
||||||
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
|
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
|
||||||
|
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) resolveRemote(host string) (net.IP, error) {
|
||||||
|
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
|
||||||
|
if h.local {
|
||||||
|
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
|
||||||
|
}
|
||||||
|
return h.tnet.LookupHost(host)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, uint32, error)) (net.IP, error) {
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
h.cacheMu.Lock()
|
||||||
|
if entry, ok := h.cache[host]; ok {
|
||||||
|
if time.Now().Before(entry.time) {
|
||||||
|
h.cacheMu.Unlock()
|
||||||
|
return entry.got[dice.Roll(len(entry.got))], nil
|
||||||
|
}
|
||||||
|
delete(h.cache, host)
|
||||||
|
}
|
||||||
|
h.cacheMu.Unlock()
|
||||||
|
ips, ttl, err := lookupIP(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
got := ips
|
if len(ips) == 0 {
|
||||||
if h.streamSettings.SocketSettings != nil {
|
return nil, dns.ErrEmptyResponse
|
||||||
var got4, got6 []net.IP
|
}
|
||||||
for _, ip := range ips {
|
var got4, got6 []net.IP
|
||||||
if ip.To4() != nil {
|
for _, ip := range ips {
|
||||||
got4 = append(got4, ip)
|
if ip.To4() != nil {
|
||||||
} else {
|
got4 = append(got4, ip)
|
||||||
got6 = append(got6, ip)
|
} else {
|
||||||
}
|
got6 = append(got6, ip)
|
||||||
}
|
|
||||||
switch h.streamSettings.SocketSettings.DomainStrategy {
|
|
||||||
case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
|
|
||||||
got = ips
|
|
||||||
case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
|
|
||||||
got = got4
|
|
||||||
case internet.DomainStrategy_USE_IP6, internet.DomainStrategy_FORCE_IP6:
|
|
||||||
got = got6
|
|
||||||
case internet.DomainStrategy_USE_IP46, internet.DomainStrategy_FORCE_IP46:
|
|
||||||
got = got4
|
|
||||||
if len(got) == 0 {
|
|
||||||
got = got6
|
|
||||||
}
|
|
||||||
case internet.DomainStrategy_USE_IP64, internet.DomainStrategy_FORCE_IP64:
|
|
||||||
got = got6
|
|
||||||
if len(got) == 0 {
|
|
||||||
got = got4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(got) == 0 {
|
|
||||||
return nil, dns.ErrEmptyResponse
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var got []net.IP
|
||||||
|
switch strategy {
|
||||||
|
case DeviceConfig_FORCE_IP:
|
||||||
|
got = ips
|
||||||
|
return ips[dice.Roll(len(ips))], nil
|
||||||
|
case DeviceConfig_FORCE_IP4:
|
||||||
|
got = got4
|
||||||
|
case DeviceConfig_FORCE_IP6:
|
||||||
|
got = got6
|
||||||
|
case DeviceConfig_FORCE_IP46:
|
||||||
|
got = got4
|
||||||
|
if len(got) == 0 {
|
||||||
|
got = got6
|
||||||
|
}
|
||||||
|
case DeviceConfig_FORCE_IP64:
|
||||||
|
got = got6
|
||||||
|
if len(got) == 0 {
|
||||||
|
got = got4
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(strategy)
|
||||||
|
}
|
||||||
|
if len(got) == 0 {
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
entry := entry{
|
||||||
|
got: got,
|
||||||
|
time: time.Now().Add(time.Duration(ttl) * time.Second),
|
||||||
|
}
|
||||||
|
h.cacheMu.Lock()
|
||||||
|
h.cache[host] = entry
|
||||||
|
h.cacheMu.Unlock()
|
||||||
return got[dice.Roll(len(got))], nil
|
return got[dice.Roll(len(got))], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type udpConnClient struct {
|
type udpConnClient struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
dest *net.UDPAddr
|
resolveFunc func(host string) (net.IP, error)
|
||||||
|
dest *net.UDPAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||||
@@ -404,8 +478,15 @@ func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
dst := c.dest
|
dst := c.dest
|
||||||
if b.UDP != nil {
|
if b.UDP != nil {
|
||||||
if b.UDP.Address.Family().IsDomain() {
|
if b.UDP.Address.Family().IsDomain() {
|
||||||
if b.UDP.Port != net.Port(dst.Port) {
|
ip, err := c.resolveFunc(b.UDP.Address.String())
|
||||||
dst = &net.UDPAddr{IP: dst.IP, Port: int(b.UDP.Port)}
|
if err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "drop packet to ", b.UDP, " with size ", len(b.Bytes()))
|
||||||
|
b.Release()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dst = &net.UDPAddr{
|
||||||
|
IP: ip,
|
||||||
|
Port: int(b.UDP.Port),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dst = b.UDP.RawNetAddr().(*net.UDPAddr)
|
dst = b.UDP.RawNetAddr().(*net.UDPAddr)
|
||||||
@@ -442,59 +523,3 @@ func (c *PacketCounterConnection) WriteTo(p []byte, addr net.Addr) (n int, err e
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type entry struct {
|
|
||||||
saddr []string
|
|
||||||
deadline time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type cache struct {
|
|
||||||
running bool
|
|
||||||
m map[string]entry
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cache) run() {
|
|
||||||
if c.running {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.running = true
|
|
||||||
c.m = make(map[string]entry)
|
|
||||||
go c.gc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cache) gc() {
|
|
||||||
ticker := time.NewTicker(time.Minute)
|
|
||||||
for {
|
|
||||||
now := <-ticker.C
|
|
||||||
c.mu.Lock()
|
|
||||||
for key, entry := range c.m {
|
|
||||||
if now.After(entry.deadline) {
|
|
||||||
delete(c.m, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cache) LookupHost(host string) []string {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.run()
|
|
||||||
if entry, ok := c.m[host]; ok {
|
|
||||||
if time.Now().Before(entry.deadline) {
|
|
||||||
return entry.saddr
|
|
||||||
}
|
|
||||||
delete(c.m, host)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cache) Cache(host string, saddr []string, ttl uint32) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.m[host] = entry{
|
|
||||||
saddr: saddr,
|
|
||||||
deadline: time.Now().Add(time.Second * time.Duration(ttl)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+102
-26
@@ -22,6 +22,61 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DeviceConfig_DomainStrategy int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
DeviceConfig_FORCE_IP DeviceConfig_DomainStrategy = 0
|
||||||
|
DeviceConfig_FORCE_IP4 DeviceConfig_DomainStrategy = 1
|
||||||
|
DeviceConfig_FORCE_IP6 DeviceConfig_DomainStrategy = 2
|
||||||
|
DeviceConfig_FORCE_IP46 DeviceConfig_DomainStrategy = 3
|
||||||
|
DeviceConfig_FORCE_IP64 DeviceConfig_DomainStrategy = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for DeviceConfig_DomainStrategy.
|
||||||
|
var (
|
||||||
|
DeviceConfig_DomainStrategy_name = map[int32]string{
|
||||||
|
0: "FORCE_IP",
|
||||||
|
1: "FORCE_IP4",
|
||||||
|
2: "FORCE_IP6",
|
||||||
|
3: "FORCE_IP46",
|
||||||
|
4: "FORCE_IP64",
|
||||||
|
}
|
||||||
|
DeviceConfig_DomainStrategy_value = map[string]int32{
|
||||||
|
"FORCE_IP": 0,
|
||||||
|
"FORCE_IP4": 1,
|
||||||
|
"FORCE_IP6": 2,
|
||||||
|
"FORCE_IP46": 3,
|
||||||
|
"FORCE_IP64": 4,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x DeviceConfig_DomainStrategy) Enum() *DeviceConfig_DomainStrategy {
|
||||||
|
p := new(DeviceConfig_DomainStrategy)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DeviceConfig_DomainStrategy) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DeviceConfig_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_proxy_wireguard_config_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DeviceConfig_DomainStrategy) Type() protoreflect.EnumType {
|
||||||
|
return &file_proxy_wireguard_config_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DeviceConfig_DomainStrategy) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeviceConfig_DomainStrategy.Descriptor instead.
|
||||||
|
func (DeviceConfig_DomainStrategy) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_proxy_wireguard_config_proto_rawDescGZIP(), []int{1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
type PeerConfig struct {
|
type PeerConfig struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
||||||
@@ -99,18 +154,19 @@ func (x *PeerConfig) GetAllowedIps() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DeviceConfig struct {
|
type DeviceConfig struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
|
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
|
||||||
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
|
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
|
||||||
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
|
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
|
||||||
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
|
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
|
||||||
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
|
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
|
||||||
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
|
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
|
||||||
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
|
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"`
|
||||||
NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"`
|
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
|
||||||
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
|
NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
|
||||||
sizeCache protoimpl.SizeCache
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *DeviceConfig) Reset() {
|
func (x *DeviceConfig) Reset() {
|
||||||
@@ -185,6 +241,13 @@ func (x *DeviceConfig) GetReserved() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *DeviceConfig) GetDomainStrategy() DeviceConfig_DomainStrategy {
|
||||||
|
if x != nil {
|
||||||
|
return x.DomainStrategy
|
||||||
|
}
|
||||||
|
return DeviceConfig_FORCE_IP
|
||||||
|
}
|
||||||
|
|
||||||
func (x *DeviceConfig) GetIsClient() bool {
|
func (x *DeviceConfig) GetIsClient() bool {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.IsClient
|
return x.IsClient
|
||||||
@@ -220,7 +283,7 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
|
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
|
||||||
"\vallowed_ips\x18\x05 \x03(\tR\n" +
|
"\vallowed_ips\x18\x05 \x03(\tR\n" +
|
||||||
"allowedIps\"\xb4\x02\n" +
|
"allowedIps\"\xee\x03\n" +
|
||||||
"\fDeviceConfig\x12\x1d\n" +
|
"\fDeviceConfig\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
|
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
|
||||||
@@ -228,11 +291,20 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
|
|||||||
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" +
|
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" +
|
||||||
"\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" +
|
"\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" +
|
||||||
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
|
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
|
||||||
"\breserved\x18\x06 \x01(\fR\breserved\x12\x1b\n" +
|
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" +
|
||||||
|
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
|
||||||
"\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" +
|
"\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" +
|
||||||
"\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\x12\x10\n" +
|
"\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\x12\x10\n" +
|
||||||
"\x03DNS\x18\n" +
|
"\x03DNS\x18\n" +
|
||||||
" \x03(\tR\x03DNSB^\n" +
|
" \x03(\tR\x03DNS\"\\\n" +
|
||||||
|
"\x0eDomainStrategy\x12\f\n" +
|
||||||
|
"\bFORCE_IP\x10\x00\x12\r\n" +
|
||||||
|
"\tFORCE_IP4\x10\x01\x12\r\n" +
|
||||||
|
"\tFORCE_IP6\x10\x02\x12\x0e\n" +
|
||||||
|
"\n" +
|
||||||
|
"FORCE_IP46\x10\x03\x12\x0e\n" +
|
||||||
|
"\n" +
|
||||||
|
"FORCE_IP64\x10\x04B^\n" +
|
||||||
"\x18com.xray.proxy.wireguardP\x01Z)github.com/xtls/xray-core/proxy/wireguard\xaa\x02\x14Xray.Proxy.WireGuardb\x06proto3"
|
"\x18com.xray.proxy.wireguardP\x01Z)github.com/xtls/xray-core/proxy/wireguard\xaa\x02\x14Xray.Proxy.WireGuardb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -247,20 +319,23 @@ func file_proxy_wireguard_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_proxy_wireguard_config_proto_rawDescData
|
return file_proxy_wireguard_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var file_proxy_wireguard_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
var file_proxy_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
var file_proxy_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
var file_proxy_wireguard_config_proto_goTypes = []any{
|
var file_proxy_wireguard_config_proto_goTypes = []any{
|
||||||
(*PeerConfig)(nil), // 0: xray.proxy.wireguard.PeerConfig
|
(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy
|
||||||
(*DeviceConfig)(nil), // 1: xray.proxy.wireguard.DeviceConfig
|
(*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig
|
||||||
(*protocol.User)(nil), // 2: xray.common.protocol.User
|
(*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig
|
||||||
|
(*protocol.User)(nil), // 3: xray.common.protocol.User
|
||||||
}
|
}
|
||||||
var file_proxy_wireguard_config_proto_depIdxs = []int32{
|
var file_proxy_wireguard_config_proto_depIdxs = []int32{
|
||||||
0, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
|
1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
|
||||||
2, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
|
3, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
|
||||||
2, // [2:2] is the sub-list for method output_type
|
0, // 2: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy
|
||||||
2, // [2:2] is the sub-list for method input_type
|
3, // [3:3] is the sub-list for method output_type
|
||||||
2, // [2:2] is the sub-list for extension type_name
|
3, // [3:3] is the sub-list for method input_type
|
||||||
2, // [2:2] is the sub-list for extension extendee
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
0, // [0:2] is the sub-list for field type_name
|
3, // [3:3] is the sub-list for extension extendee
|
||||||
|
0, // [0:3] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_proxy_wireguard_config_proto_init() }
|
func init() { file_proxy_wireguard_config_proto_init() }
|
||||||
@@ -273,13 +348,14 @@ func file_proxy_wireguard_config_proto_init() {
|
|||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 1,
|
||||||
NumMessages: 2,
|
NumMessages: 2,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
GoTypes: file_proxy_wireguard_config_proto_goTypes,
|
GoTypes: file_proxy_wireguard_config_proto_goTypes,
|
||||||
DependencyIndexes: file_proxy_wireguard_config_proto_depIdxs,
|
DependencyIndexes: file_proxy_wireguard_config_proto_depIdxs,
|
||||||
|
EnumInfos: file_proxy_wireguard_config_proto_enumTypes,
|
||||||
MessageInfos: file_proxy_wireguard_config_proto_msgTypes,
|
MessageInfos: file_proxy_wireguard_config_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_proxy_wireguard_config_proto = out.File
|
File_proxy_wireguard_config_proto = out.File
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ message PeerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message DeviceConfig {
|
message DeviceConfig {
|
||||||
|
enum DomainStrategy {
|
||||||
|
FORCE_IP = 0;
|
||||||
|
FORCE_IP4 = 1;
|
||||||
|
FORCE_IP6 = 2;
|
||||||
|
FORCE_IP46 = 3;
|
||||||
|
FORCE_IP64 = 4;
|
||||||
|
}
|
||||||
string secret_key = 1;
|
string secret_key = 1;
|
||||||
repeated string endpoint = 2;
|
repeated string endpoint = 2;
|
||||||
repeated PeerConfig peers = 3;
|
repeated PeerConfig peers = 3;
|
||||||
@@ -24,6 +31,7 @@ message DeviceConfig {
|
|||||||
int32 mtu = 4;
|
int32 mtu = 4;
|
||||||
|
|
||||||
bytes reserved = 6;
|
bytes reserved = 6;
|
||||||
|
DomainStrategy domain_strategy = 7;
|
||||||
bool is_client = 8;
|
bool is_client = 8;
|
||||||
bool no_kernel_tun = 9;
|
bool no_kernel_tun = 9;
|
||||||
repeated string DNS = 10;
|
repeated string DNS = 10;
|
||||||
|
|||||||
+14
-157
@@ -15,8 +15,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -44,7 +42,6 @@ type netTun struct {
|
|||||||
events chan tun.Event
|
events chan tun.Event
|
||||||
notifyHandle *channel.NotificationHandle
|
notifyHandle *channel.NotificationHandle
|
||||||
incomingPacket chan *buffer.View
|
incomingPacket chan *buffer.View
|
||||||
closed chan struct{}
|
|
||||||
mtu int
|
mtu int
|
||||||
dnsServers []netip.Addr
|
dnsServers []netip.Addr
|
||||||
hasV4, hasV6 bool
|
hasV4, hasV6 bool
|
||||||
@@ -61,7 +58,6 @@ func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handleLocal
|
|||||||
stack: stack.New(opts),
|
stack: stack.New(opts),
|
||||||
events: make(chan tun.Event, 10),
|
events: make(chan tun.Event, 10),
|
||||||
incomingPacket: make(chan *buffer.View),
|
incomingPacket: make(chan *buffer.View),
|
||||||
closed: make(chan struct{}),
|
|
||||||
dnsServers: dnsServers,
|
dnsServers: dnsServers,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
}
|
}
|
||||||
@@ -128,10 +124,8 @@ func (tun *netTun) Events() <-chan tun.Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
|
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
|
||||||
var view *buffer.View
|
view, ok := <-tun.incomingPacket
|
||||||
select {
|
if !ok {
|
||||||
case view = <-tun.incomingPacket:
|
|
||||||
case <-tun.closed:
|
|
||||||
return 0, os.ErrClosed
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,10 +166,7 @@ func (tun *netTun) WriteNotify() {
|
|||||||
view := pkt.ToView()
|
view := pkt.ToView()
|
||||||
pkt.DecRef()
|
pkt.DecRef()
|
||||||
|
|
||||||
select {
|
tun.incomingPacket <- view
|
||||||
case tun.incomingPacket <- view:
|
|
||||||
case <-tun.closed:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *netTun) Close() error {
|
func (tun *netTun) Close() error {
|
||||||
@@ -188,9 +179,8 @@ func (tun *netTun) Close() error {
|
|||||||
close(tun.events)
|
close(tun.events)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't close incomingPacket, because WriteNotify may be mid-send on it (DNS lookup) and would panic.
|
if tun.incomingPacket != nil {
|
||||||
if tun.closed != nil {
|
close(tun.incomingPacket)
|
||||||
close(tun.closed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -229,7 +219,6 @@ type Net struct {
|
|||||||
DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error)
|
DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error)
|
||||||
dnsServers []netip.Addr
|
dnsServers []netip.Addr
|
||||||
hasV4, hasV6 bool
|
hasV4, hasV6 bool
|
||||||
cache cache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
|
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
|
||||||
@@ -257,12 +246,9 @@ var (
|
|||||||
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
|
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
|
||||||
errCanceled = errors.New("operation was canceled")
|
errCanceled = errors.New("operation was canceled")
|
||||||
errTimeout = errors.New("i/o timeout")
|
errTimeout = errors.New("i/o timeout")
|
||||||
errNumericPort = errors.New("port must be numeric")
|
|
||||||
errNoSuitableAddress = errors.New("no suitable address found")
|
|
||||||
errMissingAddress = errors.New("missing address")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (net *Net) LookupHost(host string) (addrs []string, err error) {
|
func (net *Net) LookupHost(host string) (addrs []net.IP, ttl uint32, err error) {
|
||||||
return net.LookupContextHost(context.Background(), host)
|
return net.LookupContextHost(context.Background(), host)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,12 +567,9 @@ func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.T
|
|||||||
return dnsmessage.Parser{}, "", lastErr
|
return dnsmessage.Parser{}, "", lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) {
|
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP, uint32, error) {
|
||||||
if saddr := tnet.cache.LookupHost(host); saddr != nil {
|
|
||||||
return saddr, nil
|
|
||||||
}
|
|
||||||
if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
|
if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
|
||||||
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
|
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
|
||||||
}
|
}
|
||||||
zlen := len(host)
|
zlen := len(host)
|
||||||
if strings.IndexByte(host, ':') != -1 {
|
if strings.IndexByte(host, ':') != -1 {
|
||||||
@@ -595,11 +578,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
|
if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
|
||||||
return []string{ip.String()}, nil
|
return []net.IP{ip.AsSlice()}, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isDomainName(host) {
|
if !isDomainName(host) {
|
||||||
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
|
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
|
||||||
}
|
}
|
||||||
type result struct {
|
type result struct {
|
||||||
p dnsmessage.Parser
|
p dnsmessage.Parser
|
||||||
@@ -700,137 +683,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(addrs) == 0 && lastErr != nil {
|
if len(addrs) == 0 && lastErr != nil {
|
||||||
return nil, lastErr
|
return nil, 0, lastErr
|
||||||
}
|
}
|
||||||
saddrs := make([]string, 0, len(addrs))
|
ips := make([]net.IP, 0, len(addrs))
|
||||||
for _, ip := range addrs {
|
for _, ip := range addrs {
|
||||||
saddrs = append(saddrs, ip.String())
|
ips = append(ips, ip.AsSlice())
|
||||||
}
|
}
|
||||||
tnet.cache.Cache(host, saddrs, ttl)
|
return ips, ttl, nil
|
||||||
return saddrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) {
|
|
||||||
if deadline.IsZero() {
|
|
||||||
return deadline, nil
|
|
||||||
}
|
|
||||||
timeRemaining := deadline.Sub(now)
|
|
||||||
if timeRemaining <= 0 {
|
|
||||||
return time.Time{}, errTimeout
|
|
||||||
}
|
|
||||||
timeout := timeRemaining / time.Duration(addrsRemaining)
|
|
||||||
const saneMinimum = 2 * time.Second
|
|
||||||
if timeout < saneMinimum {
|
|
||||||
if timeRemaining < saneMinimum {
|
|
||||||
timeout = timeRemaining
|
|
||||||
} else {
|
|
||||||
timeout = saneMinimum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return now.Add(timeout), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var protoSplitter = regexp.MustCompile(`^(tcp|udp|ping)(4|6)?$`)
|
|
||||||
|
|
||||||
func (tnet *Net) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
if ctx == nil {
|
|
||||||
panic("nil context")
|
|
||||||
}
|
|
||||||
var acceptV4, acceptV6 bool
|
|
||||||
matches := protoSplitter.FindStringSubmatch(network)
|
|
||||||
if matches == nil {
|
|
||||||
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
|
|
||||||
} else if len(matches[2]) == 0 {
|
|
||||||
acceptV4 = true
|
|
||||||
acceptV6 = true
|
|
||||||
} else {
|
|
||||||
acceptV4 = matches[2][0] == '4'
|
|
||||||
acceptV6 = !acceptV4
|
|
||||||
}
|
|
||||||
var host string
|
|
||||||
var port int
|
|
||||||
if matches[1] == "ping" {
|
|
||||||
host = address
|
|
||||||
} else {
|
|
||||||
var sport string
|
|
||||||
var err error
|
|
||||||
host, sport, err = net.SplitHostPort(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &net.OpError{Op: "dial", Err: err}
|
|
||||||
}
|
|
||||||
port, err = strconv.Atoi(sport)
|
|
||||||
if err != nil || port < 0 || port > 65535 {
|
|
||||||
return nil, &net.OpError{Op: "dial", Err: errNumericPort}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allAddr, err := tnet.LookupContextHost(ctx, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &net.OpError{Op: "dial", Err: err}
|
|
||||||
}
|
|
||||||
var addrs []netip.AddrPort
|
|
||||||
for _, addr := range allAddr {
|
|
||||||
ip, err := netip.ParseAddr(addr)
|
|
||||||
if err == nil && ((ip.Is4() && acceptV4) || (ip.Is6() && acceptV6)) {
|
|
||||||
addrs = append(addrs, netip.AddrPortFrom(ip, uint16(port)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(addrs) == 0 && len(allAddr) != 0 {
|
|
||||||
return nil, &net.OpError{Op: "dial", Err: errNoSuitableAddress}
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstErr error
|
|
||||||
for i, addr := range addrs {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
err := ctx.Err()
|
|
||||||
if err == context.Canceled {
|
|
||||||
err = errCanceled
|
|
||||||
} else if err == context.DeadlineExceeded {
|
|
||||||
err = errTimeout
|
|
||||||
}
|
|
||||||
return nil, &net.OpError{Op: "dial", Err: err}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
dialCtx := ctx
|
|
||||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
|
||||||
partialDeadline, err := partialDeadline(time.Now(), deadline, len(addrs)-i)
|
|
||||||
if err != nil {
|
|
||||||
if firstErr == nil {
|
|
||||||
firstErr = &net.OpError{Op: "dial", Err: err}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if partialDeadline.Before(deadline) {
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
dialCtx, cancel = context.WithDeadline(ctx, partialDeadline)
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var c net.Conn
|
|
||||||
switch matches[1] {
|
|
||||||
case "tcp":
|
|
||||||
c, err = tnet.DialContextTCPAddrPort(dialCtx, addr)
|
|
||||||
case "udp":
|
|
||||||
c, err = tnet.DialUDPAddrPort(netip.AddrPort{}, addr)
|
|
||||||
case "ping":
|
|
||||||
err = errors.New("not support")
|
|
||||||
// c, err = tnet.DialPingAddr(netip.Addr{}, addr.Addr())
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
if firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if firstErr == nil {
|
|
||||||
firstErr = &net.OpError{Op: "dial", Err: errMissingAddress}
|
|
||||||
}
|
|
||||||
return nil, firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tnet *Net) Dial(network, address string) (net.Conn, error) {
|
|
||||||
return tnet.DialContext(context.Background(), network, address)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,16 +258,18 @@ func (s *Server) Start() error {
|
|||||||
return errors.New("address is domain")
|
return errors.New("address is domain")
|
||||||
}
|
}
|
||||||
listenFunc := func() (net.PacketConn, error) {
|
listenFunc := func() (net.PacketConn, error) {
|
||||||
var pktConn net.PacketConn
|
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings)
|
||||||
var err error
|
|
||||||
if s.streamSettings.FinalMask != nil {
|
|
||||||
pktConn, err = s.streamSettings.FinalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)})
|
|
||||||
} else {
|
|
||||||
pktConn, err = internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if s.streamSettings.UdpmaskManager != nil {
|
||||||
|
newConn, err := s.streamSettings.UdpmaskManager.WrapPacketConnServer(pktConn)
|
||||||
|
if err != nil {
|
||||||
|
pktConn.Close()
|
||||||
|
return nil, errors.New("mask err").Base(err)
|
||||||
|
}
|
||||||
|
pktConn = newConn
|
||||||
|
}
|
||||||
if s.uplinkCounter != nil || s.downlinkCounter != nil {
|
if s.uplinkCounter != nil || s.downlinkCounter != nil {
|
||||||
pktConn = &PacketCounterConnection{
|
pktConn = &PacketCounterConnection{
|
||||||
PacketConn: pktConn,
|
PacketConn: pktConn,
|
||||||
|
|||||||
@@ -82,14 +82,9 @@ func TestResolveIP(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Tag: "direct",
|
Tag: "direct",
|
||||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
StreamSettings: &internet.StreamConfig{
|
DomainStrategy: internet.DomainStrategy_USE_IP,
|
||||||
SocketSettings: &internet.SocketConfig{
|
|
||||||
DomainStrategy: internet.DomainStrategy_USE_IP,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func TestPassiveConnection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDialerProxy(t *testing.T) {
|
func TestProxy(t *testing.T) {
|
||||||
tcpServer := tcp.Server{
|
tcpServer := tcp.Server{
|
||||||
MsgProcessor: xor,
|
MsgProcessor: xor,
|
||||||
}
|
}
|
||||||
@@ -187,10 +187,8 @@ func TestDialerProxy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||||
StreamSettings: &internet.StreamConfig{
|
ProxySettings: &internet.ProxyConfig{
|
||||||
SocketSettings: &internet.SocketConfig{
|
Tag: "proxy",
|
||||||
DialerProxy: "proxy",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -220,7 +218,7 @@ func TestDialerProxy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDialerProxyOverKCP(t *testing.T) {
|
func TestProxyOverKCP(t *testing.T) {
|
||||||
tcpServer := tcp.Server{
|
tcpServer := tcp.Server{
|
||||||
MsgProcessor: xor,
|
MsgProcessor: xor,
|
||||||
}
|
}
|
||||||
@@ -323,11 +321,11 @@ func TestDialerProxyOverKCP(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||||
|
ProxySettings: &internet.ProxyConfig{
|
||||||
|
Tag: "proxy",
|
||||||
|
},
|
||||||
StreamSettings: &internet.StreamConfig{
|
StreamSettings: &internet.StreamConfig{
|
||||||
ProtocolName: "mkcp",
|
ProtocolName: "mkcp",
|
||||||
SocketSettings: &internet.SocketConfig{
|
|
||||||
DialerProxy: "proxy",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -509,7 +509,7 @@ func TestVlessXtlsVisionReality(t *testing.T) {
|
|||||||
|
|
||||||
// This testing test all known utls fingerprint in tls.PresetFingerprints that support reality (expect unsafe and random*)
|
// This testing test all known utls fingerprint in tls.PresetFingerprints that support reality (expect unsafe and random*)
|
||||||
// Beacuse figerprint support may be broken after utls/reality update
|
// Beacuse figerprint support may be broken after utls/reality update
|
||||||
// Known working fingerprint: chrome, firefox, safari
|
// Known broken fingerprint: android, 360
|
||||||
func TestVlessRealityFingerprints(t *testing.T) {
|
func TestVlessRealityFingerprints(t *testing.T) {
|
||||||
TestFingerprint := func(fingerprint string) error {
|
TestFingerprint := func(fingerprint string) error {
|
||||||
tcpServer := tcp.Server{
|
tcpServer := tcp.Server{
|
||||||
@@ -641,7 +641,7 @@ func TestVlessRealityFingerprints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fingerPrints := []string{"chrome", "firefox", "safari"}
|
fingerPrints := []string{"chrome", "firefox", "safari", "ios", "edge", "qq"}
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(len(fingerPrints))
|
wg.Add(len(fingerPrints))
|
||||||
for _, fp := range fingerPrints {
|
for _, fp := range fingerPrints {
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ func TestWireguard(t *testing.T) {
|
|||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -105,7 +104,6 @@ func TestWireguard(t *testing.T) {
|
|||||||
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
|
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
|
||||||
}},
|
}},
|
||||||
}),
|
}),
|
||||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type ConfigCreator func() interface{}
|
|||||||
|
|
||||||
var globalTransportConfigCreatorCache = make(map[string]ConfigCreator)
|
var globalTransportConfigCreatorCache = make(map[string]ConfigCreator)
|
||||||
|
|
||||||
var strategy = [11][3]byte{
|
var strategy = [][]byte{
|
||||||
// name strategy, prefer, fallback
|
// name strategy, prefer, fallback
|
||||||
{0, 0, 0}, // AsIs none, /, /
|
{0, 0, 0}, // AsIs none, /, /
|
||||||
{1, 0, 0}, // UseIP use, both, none
|
{1, 0, 0}, // UseIP use, both, none
|
||||||
@@ -25,6 +25,8 @@ var strategy = [11][3]byte{
|
|||||||
{2, 6, 4}, // ForceIPv6v4 force, 6, 4
|
{2, 6, 4}, // ForceIPv6v4 force, 6, 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unknownProtocol = "unknown"
|
||||||
|
|
||||||
func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error {
|
func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error {
|
||||||
if _, found := globalTransportConfigCreatorCache[name]; found {
|
if _, found := globalTransportConfigCreatorCache[name]; found {
|
||||||
return errors.New("protocol ", name, " is already registered").AtError()
|
return errors.New("protocol ", name, " is already registered").AtError()
|
||||||
@@ -89,6 +91,10 @@ func (c *StreamConfig) HasSecuritySettings() bool {
|
|||||||
return len(c.SecuritySettings) > 0
|
return len(c.SecuritySettings) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ProxyConfig) HasTag() bool {
|
||||||
|
return c != nil && len(c.Tag) > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (m SocketConfig_TProxyMode) IsEnabled() bool {
|
func (m SocketConfig_TProxyMode) IsEnabled() bool {
|
||||||
return m != SocketConfig_Off
|
return m != SocketConfig_Off
|
||||||
}
|
}
|
||||||
|
|||||||
+179
-86
@@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
|
|||||||
|
|
||||||
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
|
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
|
||||||
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
|
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{4, 0}
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{6, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransportConfig struct {
|
type TransportConfig struct {
|
||||||
@@ -382,31 +382,88 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UdpHop struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Ports []uint32 `protobuf:"varint,1,rep,packed,name=ports,proto3" json:"ports,omitempty"`
|
||||||
|
IntervalMin int64 `protobuf:"varint,2,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
|
||||||
|
IntervalMax int64 `protobuf:"varint,3,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UdpHop) Reset() {
|
||||||
|
*x = UdpHop{}
|
||||||
|
mi := &file_transport_internet_config_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UdpHop) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UdpHop) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *UdpHop) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_transport_internet_config_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use UdpHop.ProtoReflect.Descriptor instead.
|
||||||
|
func (*UdpHop) Descriptor() ([]byte, []int) {
|
||||||
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UdpHop) GetPorts() []uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ports
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UdpHop) GetIntervalMin() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.IntervalMin
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UdpHop) GetIntervalMax() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.IntervalMax
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
type QuicParams struct {
|
type QuicParams struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
|
Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
|
||||||
BbrProfile string `protobuf:"bytes,2,opt,name=bbr_profile,json=bbrProfile,proto3" json:"bbr_profile,omitempty"`
|
BbrProfile string `protobuf:"bytes,2,opt,name=bbr_profile,json=bbrProfile,proto3" json:"bbr_profile,omitempty"`
|
||||||
BrutalUp uint64 `protobuf:"varint,3,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"`
|
BrutalUp uint64 `protobuf:"varint,3,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"`
|
||||||
BrutalDown uint64 `protobuf:"varint,4,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"`
|
BrutalDown uint64 `protobuf:"varint,4,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"`
|
||||||
BrutalDisableLossCompensation bool `protobuf:"varint,5,opt,name=brutal_disable_loss_compensation,json=brutalDisableLossCompensation,proto3" json:"brutal_disable_loss_compensation,omitempty"`
|
UdpHop *UdpHop `protobuf:"bytes,5,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,omitempty"`
|
||||||
InitStreamReceiveWindow uint64 `protobuf:"varint,6,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
|
InitStreamReceiveWindow uint64 `protobuf:"varint,6,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
|
||||||
MaxStreamReceiveWindow uint64 `protobuf:"varint,7,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
|
MaxStreamReceiveWindow uint64 `protobuf:"varint,7,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
|
||||||
InitConnReceiveWindow uint64 `protobuf:"varint,8,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
|
InitConnReceiveWindow uint64 `protobuf:"varint,8,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
|
||||||
MaxConnReceiveWindow uint64 `protobuf:"varint,9,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
|
MaxConnReceiveWindow uint64 `protobuf:"varint,9,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
|
||||||
MaxIdleTimeout int64 `protobuf:"varint,10,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
|
MaxIdleTimeout int64 `protobuf:"varint,10,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
|
||||||
KeepAlivePeriod int64 `protobuf:"varint,11,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
|
KeepAlivePeriod int64 `protobuf:"varint,11,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
|
||||||
DisablePathMtuDiscovery bool `protobuf:"varint,12,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
|
DisablePathMtuDiscovery bool `protobuf:"varint,12,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
|
||||||
DisableChromeParrot bool `protobuf:"varint,13,opt,name=disable_chrome_parrot,json=disableChromeParrot,proto3" json:"disable_chrome_parrot,omitempty"`
|
MaxIncomingStreams int64 `protobuf:"varint,13,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
|
||||||
DisableGSO bool `protobuf:"varint,14,opt,name=disableGSO,proto3" json:"disableGSO,omitempty"`
|
unknownFields protoimpl.UnknownFields
|
||||||
MaxIncomingStreams int64 `protobuf:"varint,15,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
|
sizeCache protoimpl.SizeCache
|
||||||
DisableStatelessReset bool `protobuf:"varint,16,opt,name=disable_stateless_reset,json=disableStatelessReset,proto3" json:"disable_stateless_reset,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *QuicParams) Reset() {
|
func (x *QuicParams) Reset() {
|
||||||
*x = QuicParams{}
|
*x = QuicParams{}
|
||||||
mi := &file_transport_internet_config_proto_msgTypes[2]
|
mi := &file_transport_internet_config_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -418,7 +475,7 @@ func (x *QuicParams) String() string {
|
|||||||
func (*QuicParams) ProtoMessage() {}
|
func (*QuicParams) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *QuicParams) ProtoReflect() protoreflect.Message {
|
func (x *QuicParams) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_transport_internet_config_proto_msgTypes[2]
|
mi := &file_transport_internet_config_proto_msgTypes[3]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -431,7 +488,7 @@ func (x *QuicParams) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.
|
// Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.
|
||||||
func (*QuicParams) Descriptor() ([]byte, []int) {
|
func (*QuicParams) Descriptor() ([]byte, []int) {
|
||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *QuicParams) GetCongestion() string {
|
func (x *QuicParams) GetCongestion() string {
|
||||||
@@ -462,11 +519,11 @@ func (x *QuicParams) GetBrutalDown() uint64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *QuicParams) GetBrutalDisableLossCompensation() bool {
|
func (x *QuicParams) GetUdpHop() *UdpHop {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.BrutalDisableLossCompensation
|
return x.UdpHop
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *QuicParams) GetInitStreamReceiveWindow() uint64 {
|
func (x *QuicParams) GetInitStreamReceiveWindow() uint64 {
|
||||||
@@ -518,20 +575,6 @@ func (x *QuicParams) GetDisablePathMtuDiscovery() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *QuicParams) GetDisableChromeParrot() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.DisableChromeParrot
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *QuicParams) GetDisableGSO() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.DisableGSO
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *QuicParams) GetMaxIncomingStreams() int64 {
|
func (x *QuicParams) GetMaxIncomingStreams() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.MaxIncomingStreams
|
return x.MaxIncomingStreams
|
||||||
@@ -539,9 +582,54 @@ func (x *QuicParams) GetMaxIncomingStreams() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *QuicParams) GetDisableStatelessReset() bool {
|
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 {
|
if x != nil {
|
||||||
return x.DisableStatelessReset
|
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
|
return false
|
||||||
}
|
}
|
||||||
@@ -560,7 +648,7 @@ type CustomSockopt struct {
|
|||||||
|
|
||||||
func (x *CustomSockopt) Reset() {
|
func (x *CustomSockopt) Reset() {
|
||||||
*x = CustomSockopt{}
|
*x = CustomSockopt{}
|
||||||
mi := &file_transport_internet_config_proto_msgTypes[3]
|
mi := &file_transport_internet_config_proto_msgTypes[5]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -572,7 +660,7 @@ func (x *CustomSockopt) String() string {
|
|||||||
func (*CustomSockopt) ProtoMessage() {}
|
func (*CustomSockopt) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
|
func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_transport_internet_config_proto_msgTypes[3]
|
mi := &file_transport_internet_config_proto_msgTypes[5]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -585,7 +673,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
|
||||||
func (*CustomSockopt) Descriptor() ([]byte, []int) {
|
func (*CustomSockopt) Descriptor() ([]byte, []int) {
|
||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CustomSockopt) GetSystem() string {
|
func (x *CustomSockopt) GetSystem() string {
|
||||||
@@ -665,7 +753,7 @@ type SocketConfig struct {
|
|||||||
|
|
||||||
func (x *SocketConfig) Reset() {
|
func (x *SocketConfig) Reset() {
|
||||||
*x = SocketConfig{}
|
*x = SocketConfig{}
|
||||||
mi := &file_transport_internet_config_proto_msgTypes[4]
|
mi := &file_transport_internet_config_proto_msgTypes[6]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -677,7 +765,7 @@ func (x *SocketConfig) String() string {
|
|||||||
func (*SocketConfig) ProtoMessage() {}
|
func (*SocketConfig) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *SocketConfig) ProtoReflect() protoreflect.Message {
|
func (x *SocketConfig) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_transport_internet_config_proto_msgTypes[4]
|
mi := &file_transport_internet_config_proto_msgTypes[6]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -690,7 +778,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
|
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
|
||||||
func (*SocketConfig) Descriptor() ([]byte, []int) {
|
func (*SocketConfig) Descriptor() ([]byte, []int) {
|
||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SocketConfig) GetMark() int32 {
|
func (x *SocketConfig) GetMark() int32 {
|
||||||
@@ -852,7 +940,7 @@ type HappyEyeballsConfig struct {
|
|||||||
|
|
||||||
func (x *HappyEyeballsConfig) Reset() {
|
func (x *HappyEyeballsConfig) Reset() {
|
||||||
*x = HappyEyeballsConfig{}
|
*x = HappyEyeballsConfig{}
|
||||||
mi := &file_transport_internet_config_proto_msgTypes[5]
|
mi := &file_transport_internet_config_proto_msgTypes[7]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -864,7 +952,7 @@ func (x *HappyEyeballsConfig) String() string {
|
|||||||
func (*HappyEyeballsConfig) ProtoMessage() {}
|
func (*HappyEyeballsConfig) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
|
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_transport_internet_config_proto_msgTypes[5]
|
mi := &file_transport_internet_config_proto_msgTypes[7]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -877,7 +965,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
|
// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
|
||||||
func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
|
func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
|
||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{7}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
|
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
|
||||||
@@ -928,7 +1016,11 @@ const file_transport_internet_config_proto_rawDesc = "" +
|
|||||||
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" +
|
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" +
|
||||||
"\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
|
"\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
|
||||||
"quicParams\x12N\n" +
|
"quicParams\x12N\n" +
|
||||||
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"\x8d\x06\n" +
|
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"d\n" +
|
||||||
|
"\x06UdpHop\x12\x14\n" +
|
||||||
|
"\x05ports\x18\x01 \x03(\rR\x05ports\x12!\n" +
|
||||||
|
"\finterval_min\x18\x02 \x01(\x03R\vintervalMin\x12!\n" +
|
||||||
|
"\finterval_max\x18\x03 \x01(\x03R\vintervalMax\"\xf2\x04\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"QuicParams\x12\x1e\n" +
|
"QuicParams\x12\x1e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
@@ -938,8 +1030,8 @@ const file_transport_internet_config_proto_rawDesc = "" +
|
|||||||
"bbrProfile\x12\x1b\n" +
|
"bbrProfile\x12\x1b\n" +
|
||||||
"\tbrutal_up\x18\x03 \x01(\x04R\bbrutalUp\x12\x1f\n" +
|
"\tbrutal_up\x18\x03 \x01(\x04R\bbrutalUp\x12\x1f\n" +
|
||||||
"\vbrutal_down\x18\x04 \x01(\x04R\n" +
|
"\vbrutal_down\x18\x04 \x01(\x04R\n" +
|
||||||
"brutalDown\x12G\n" +
|
"brutalDown\x128\n" +
|
||||||
" brutal_disable_loss_compensation\x18\x05 \x01(\bR\x1dbrutalDisableLossCompensation\x12;\n" +
|
"\audp_hop\x18\x05 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" +
|
||||||
"\x1ainit_stream_receive_window\x18\x06 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
|
"\x1ainit_stream_receive_window\x18\x06 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
|
||||||
"\x19max_stream_receive_window\x18\a \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
|
"\x19max_stream_receive_window\x18\a \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
|
||||||
"\x18init_conn_receive_window\x18\b \x01(\x04R\x15initConnReceiveWindow\x125\n" +
|
"\x18init_conn_receive_window\x18\b \x01(\x04R\x15initConnReceiveWindow\x125\n" +
|
||||||
@@ -947,13 +1039,11 @@ const file_transport_internet_config_proto_rawDesc = "" +
|
|||||||
"\x10max_idle_timeout\x18\n" +
|
"\x10max_idle_timeout\x18\n" +
|
||||||
" \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
|
" \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
|
||||||
"\x11keep_alive_period\x18\v \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
|
"\x11keep_alive_period\x18\v \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
|
||||||
"\x1adisable_path_mtu_discovery\x18\f \x01(\bR\x17disablePathMtuDiscovery\x122\n" +
|
"\x1adisable_path_mtu_discovery\x18\f \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
|
||||||
"\x15disable_chrome_parrot\x18\r \x01(\bR\x13disableChromeParrot\x12\x1e\n" +
|
"\x14max_incoming_streams\x18\r \x01(\x03R\x12maxIncomingStreams\"Q\n" +
|
||||||
"\n" +
|
"\vProxyConfig\x12\x10\n" +
|
||||||
"disableGSO\x18\x0e \x01(\bR\n" +
|
"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
|
||||||
"disableGSO\x120\n" +
|
"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
|
||||||
"\x14max_incoming_streams\x18\x0f \x01(\x03R\x12maxIncomingStreams\x126\n" +
|
|
||||||
"\x17disable_stateless_reset\x18\x10 \x01(\bR\x15disableStatelessReset\"\x93\x01\n" +
|
|
||||||
"\rCustomSockopt\x12\x16\n" +
|
"\rCustomSockopt\x12\x16\n" +
|
||||||
"\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" +
|
"\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" +
|
||||||
"\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" +
|
"\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" +
|
||||||
@@ -1037,39 +1127,42 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||||
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||||
var file_transport_internet_config_proto_goTypes = []any{
|
var file_transport_internet_config_proto_goTypes = []any{
|
||||||
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
|
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
|
||||||
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
|
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
|
||||||
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
|
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
|
||||||
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
|
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
|
||||||
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
|
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
|
||||||
(*QuicParams)(nil), // 5: xray.transport.internet.QuicParams
|
(*UdpHop)(nil), // 5: xray.transport.internet.UdpHop
|
||||||
(*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt
|
(*QuicParams)(nil), // 6: xray.transport.internet.QuicParams
|
||||||
(*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig
|
(*ProxyConfig)(nil), // 7: xray.transport.internet.ProxyConfig
|
||||||
(*HappyEyeballsConfig)(nil), // 8: xray.transport.internet.HappyEyeballsConfig
|
(*CustomSockopt)(nil), // 8: xray.transport.internet.CustomSockopt
|
||||||
(*serial.TypedMessage)(nil), // 9: xray.common.serial.TypedMessage
|
(*SocketConfig)(nil), // 9: xray.transport.internet.SocketConfig
|
||||||
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
(*HappyEyeballsConfig)(nil), // 10: xray.transport.internet.HappyEyeballsConfig
|
||||||
|
(*serial.TypedMessage)(nil), // 11: xray.common.serial.TypedMessage
|
||||||
|
(*net.IPOrDomain)(nil), // 12: xray.common.net.IPOrDomain
|
||||||
}
|
}
|
||||||
var file_transport_internet_config_proto_depIdxs = []int32{
|
var file_transport_internet_config_proto_depIdxs = []int32{
|
||||||
9, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
11, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
||||||
10, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
|
12, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
|
||||||
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
|
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
|
||||||
9, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
11, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
||||||
9, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
|
11, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
|
||||||
9, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
|
11, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
|
||||||
5, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
|
6, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
|
||||||
7, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
9, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
||||||
2, // 8: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
5, // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop
|
||||||
0, // 9: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
2, // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
||||||
6, // 10: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
0, // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||||
1, // 11: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
|
8, // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
||||||
8, // 12: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
|
1, // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
|
||||||
13, // [13:13] is the sub-list for method output_type
|
10, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
|
||||||
13, // [13:13] is the sub-list for method input_type
|
14, // [14:14] is the sub-list for method output_type
|
||||||
13, // [13:13] is the sub-list for extension type_name
|
14, // [14:14] is the sub-list for method input_type
|
||||||
13, // [13:13] is the sub-list for extension extendee
|
14, // [14:14] is the sub-list for extension type_name
|
||||||
0, // [0:13] is the sub-list for field type_name
|
14, // [14:14] is the sub-list for extension extendee
|
||||||
|
0, // [0:14] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_transport_internet_config_proto_init() }
|
func init() { file_transport_internet_config_proto_init() }
|
||||||
@@ -1083,7 +1176,7 @@ func file_transport_internet_config_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
|
||||||
NumEnums: 3,
|
NumEnums: 3,
|
||||||
NumMessages: 6,
|
NumMessages: 8,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,12 +64,18 @@ message StreamConfig {
|
|||||||
SocketConfig socket_settings = 6;
|
SocketConfig socket_settings = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message UdpHop {
|
||||||
|
repeated uint32 ports = 1;
|
||||||
|
int64 interval_min = 2;
|
||||||
|
int64 interval_max = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message QuicParams {
|
message QuicParams {
|
||||||
string congestion = 1;
|
string congestion = 1;
|
||||||
string bbr_profile = 2;
|
string bbr_profile = 2;
|
||||||
uint64 brutal_up = 3;
|
uint64 brutal_up = 3;
|
||||||
uint64 brutal_down = 4;
|
uint64 brutal_down = 4;
|
||||||
bool brutal_disable_loss_compensation = 5;
|
UdpHop udp_hop = 5;
|
||||||
uint64 init_stream_receive_window = 6;
|
uint64 init_stream_receive_window = 6;
|
||||||
uint64 max_stream_receive_window = 7;
|
uint64 max_stream_receive_window = 7;
|
||||||
uint64 init_conn_receive_window = 8;
|
uint64 init_conn_receive_window = 8;
|
||||||
@@ -77,10 +83,12 @@ message QuicParams {
|
|||||||
int64 max_idle_timeout = 10;
|
int64 max_idle_timeout = 10;
|
||||||
int64 keep_alive_period = 11;
|
int64 keep_alive_period = 11;
|
||||||
bool disable_path_mtu_discovery = 12;
|
bool disable_path_mtu_discovery = 12;
|
||||||
bool disable_chrome_parrot = 13;
|
int64 max_incoming_streams = 13;
|
||||||
bool disableGSO = 14;
|
}
|
||||||
int64 max_incoming_streams = 15;
|
|
||||||
bool disable_stateless_reset = 16;
|
message ProxyConfig {
|
||||||
|
string tag = 1;
|
||||||
|
bool transportLayerProxy = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CustomSockopt {
|
message CustomSockopt {
|
||||||
|
|||||||
@@ -2,291 +2,106 @@ package finalmask
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dialer struct {
|
type Udpmask interface {
|
||||||
DialTCP func(net.Destination) (net.Conn, error)
|
UDP()
|
||||||
DialUDP func(net.Destination) (net.Conn, error)
|
|
||||||
|
WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
|
||||||
|
WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenConfig struct {
|
type UdpmaskManager struct {
|
||||||
Listen func(net.Addr) (net.Listener, error)
|
udpmasks []Udpmask
|
||||||
ListenPacket func(net.Addr) (net.PacketConn, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TCPMask interface {
|
func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
|
||||||
WrapConnClient(net.Conn, *net.Destination, *Dialer) (net.Conn, error)
|
return &UdpmaskManager{
|
||||||
WrapConnServer(net.Conn) (net.Conn, error)
|
udpmasks: udpmasks,
|
||||||
// Listen(net.Listener) (net.Listener, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UDPMask interface {
|
|
||||||
WrapPacketConnClient(net.PacketConn, *net.Destination, *Dialer) (net.PacketConn, error)
|
|
||||||
WrapPacketConnServer(net.PacketConn, net.Addr, *ListenConfig) (net.PacketConn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FinalMask struct {
|
|
||||||
tcpMasks []TCPMask
|
|
||||||
udpMasks []UDPMask
|
|
||||||
dialTCP func(context.Context, net.Destination) (net.Conn, error)
|
|
||||||
listen func(context.Context, net.Addr) (net.Listener, error)
|
|
||||||
dialUDP func(context.Context, net.Destination) (net.PacketConn, net.Addr, error)
|
|
||||||
listenPacket func(context.Context, net.Addr) (net.PacketConn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFinalMask(tcpMasks []TCPMask, udpMasks []UDPMask, dialTCP func(context.Context, net.Destination) (net.Conn, error), listen func(context.Context, net.Addr) (net.Listener, error), dialUDP func(context.Context, net.Destination) (net.PacketConn, net.Addr, error), listenPacket func(context.Context, net.Addr) (net.PacketConn, error)) *FinalMask {
|
|
||||||
slices.Reverse(tcpMasks)
|
|
||||||
slices.Reverse(udpMasks)
|
|
||||||
return &FinalMask{
|
|
||||||
tcpMasks: tcpMasks,
|
|
||||||
udpMasks: udpMasks,
|
|
||||||
dialTCP: dialTCP,
|
|
||||||
dialUDP: dialUDP,
|
|
||||||
listen: listen,
|
|
||||||
listenPacket: listenPacket,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FinalMask) DialTCP(ctx context.Context, dest net.Destination) (net.Conn, error) {
|
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
|
||||||
if len(fm.tcpMasks) == 0 {
|
|
||||||
return fm.dialTCP(ctx, dest)
|
|
||||||
}
|
|
||||||
for i := range fm.tcpMasks {
|
|
||||||
if i > 0 {
|
|
||||||
if _, ok := fm.tcpMasks[i].(interface{ HandleDial() }); ok {
|
|
||||||
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.tcpMasks[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var conn net.Conn
|
|
||||||
var err error
|
|
||||||
if _, ok := fm.tcpMasks[0].(interface{ HandleDial() }); !ok {
|
|
||||||
conn, err = fm.dialTCP(ctx, dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialer := &Dialer{
|
|
||||||
DialTCP: func(dest net.Destination) (net.Conn, error) {
|
|
||||||
return fm.dialTCP(ctx, dest)
|
|
||||||
},
|
|
||||||
DialUDP: func(dest net.Destination) (net.Conn, error) {
|
|
||||||
conn, addr, err := fm.dialUDP(ctx, dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range fm.tcpMasks {
|
|
||||||
var newConn net.Conn
|
|
||||||
newConn, err = fm.tcpMasks[i].WrapConnClient(conn, &dest, dialer)
|
|
||||||
if err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = newConn
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FinalMask) Listen(ctx context.Context, addr net.Addr) (net.Listener, error) {
|
|
||||||
if len(fm.tcpMasks) == 0 {
|
|
||||||
return fm.listen(ctx, addr)
|
|
||||||
}
|
|
||||||
off := 0
|
|
||||||
listener, err := fm.listen(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i := range fm.tcpMasks {
|
|
||||||
if _, ok := fm.tcpMasks[i].(interface {
|
|
||||||
Listen(net.Listener) (net.Listener, error)
|
|
||||||
}); ok {
|
|
||||||
if i-off == 0 {
|
|
||||||
l, err := fm.tcpMasks[i].(interface {
|
|
||||||
Listen(net.Listener) (net.Listener, error)
|
|
||||||
}).Listen(listener)
|
|
||||||
if err != nil {
|
|
||||||
listener.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
listener = l
|
|
||||||
} else {
|
|
||||||
l, err := fm.tcpMasks[i].(interface {
|
|
||||||
Listen(net.Listener) (net.Listener, error)
|
|
||||||
}).Listen(&TCPListener{Listener: listener, tcpMasks: fm.tcpMasks[off:i]})
|
|
||||||
if err != nil {
|
|
||||||
listener.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
listener = l
|
|
||||||
}
|
|
||||||
off = i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if off < len(fm.tcpMasks) {
|
|
||||||
return &TCPListener{Listener: listener, tcpMasks: fm.tcpMasks[off:]}, nil
|
|
||||||
}
|
|
||||||
return listener, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FinalMask) DialUDP(ctx context.Context, dest net.Destination) (net.Conn, error) {
|
|
||||||
if len(fm.udpMasks) == 0 {
|
|
||||||
conn, addr, err := fm.dialUDP(ctx, dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, nil
|
|
||||||
}
|
|
||||||
for i := range fm.udpMasks {
|
|
||||||
if i > 0 {
|
|
||||||
if _, ok := fm.udpMasks[i].(interface{ HandleDial() }); ok {
|
|
||||||
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.udpMasks[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var conn net.PacketConn
|
|
||||||
var addr net.Addr
|
|
||||||
var err error
|
|
||||||
if _, ok := fm.udpMasks[0].(interface{ HandleDial() }); !ok {
|
|
||||||
conn, addr, err = fm.dialUDP(ctx, dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialer := &Dialer{
|
|
||||||
DialTCP: func(dest net.Destination) (net.Conn, error) {
|
|
||||||
return fm.dialTCP(ctx, dest)
|
|
||||||
},
|
|
||||||
DialUDP: func(dest net.Destination) (net.Conn, error) {
|
|
||||||
conn, addr, err := fm.dialUDP(ctx, dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var sizes []int
|
var sizes []int
|
||||||
var conns []net.PacketConn
|
var conns []net.PacketConn
|
||||||
for i := range fm.udpMasks {
|
for i, mask := range slices.Backward(m.udpmasks) {
|
||||||
var newConn net.PacketConn
|
if _, ok := mask.(headerConn); ok {
|
||||||
if _, ok := fm.udpMasks[i].(interface{ HeaderConn() }); ok {
|
conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1)
|
||||||
newConn, err = fm.udpMasks[i].WrapPacketConnClient(nil, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sizes = append(sizes, newConn.(interface{ Size() int }).Size())
|
sizes = append(sizes, conn.(headerSize).Size())
|
||||||
conns = append(conns, newConn)
|
conns = append(conns, conn)
|
||||||
} else {
|
} else {
|
||||||
if len(conns) > 0 {
|
if len(conns) > 0 {
|
||||||
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
|
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
|
||||||
sizes = nil
|
sizes = nil
|
||||||
conns = nil
|
conns = nil
|
||||||
}
|
}
|
||||||
newConn, err = fm.udpMasks[i].WrapPacketConnClient(conn, &dest, dialer)
|
var err error
|
||||||
|
raw, err = mask.WrapPacketConnClient(raw, i, len(m.udpmasks)-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn = newConn
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conns) > 0 {
|
if len(conns) > 0 {
|
||||||
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
|
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
|
||||||
sizes = nil
|
sizes = nil
|
||||||
conns = nil
|
conns = nil
|
||||||
}
|
}
|
||||||
if addr == nil {
|
return raw, nil
|
||||||
addr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
|
|
||||||
}
|
|
||||||
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FinalMask) ListenPacket(ctx context.Context, addr net.Addr) (net.PacketConn, error) {
|
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
|
||||||
if len(fm.udpMasks) == 0 {
|
|
||||||
return fm.listenPacket(ctx, addr)
|
|
||||||
}
|
|
||||||
for i := range fm.udpMasks {
|
|
||||||
if i > 0 {
|
|
||||||
if _, ok := fm.udpMasks[i].(interface{ HandleListen() }); ok {
|
|
||||||
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.udpMasks[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var conn net.PacketConn
|
|
||||||
var err error
|
|
||||||
if _, ok := fm.udpMasks[0].(interface{ HandleListen() }); !ok {
|
|
||||||
conn, err = fm.listenPacket(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lc := &ListenConfig{
|
|
||||||
Listen: func(addr net.Addr) (net.Listener, error) { return fm.listen(ctx, addr) },
|
|
||||||
ListenPacket: func(addr net.Addr) (net.PacketConn, error) { return fm.listenPacket(ctx, addr) },
|
|
||||||
}
|
|
||||||
var sizes []int
|
var sizes []int
|
||||||
var conns []net.PacketConn
|
var conns []net.PacketConn
|
||||||
for i := range fm.udpMasks {
|
for i, mask := range slices.Backward(m.udpmasks) {
|
||||||
var newConn net.PacketConn
|
if _, ok := mask.(headerConn); ok {
|
||||||
if _, ok := fm.udpMasks[i].(interface{ HeaderConn() }); ok {
|
conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1)
|
||||||
newConn, err = fm.udpMasks[i].WrapPacketConnServer(nil, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sizes = append(sizes, newConn.(interface{ Size() int }).Size())
|
sizes = append(sizes, conn.(headerSize).Size())
|
||||||
conns = append(conns, newConn)
|
conns = append(conns, conn)
|
||||||
} else {
|
} else {
|
||||||
if len(conns) > 0 {
|
if len(conns) > 0 {
|
||||||
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
|
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
|
||||||
sizes = nil
|
sizes = nil
|
||||||
conns = nil
|
conns = nil
|
||||||
}
|
}
|
||||||
newConn, err = fm.udpMasks[i].WrapPacketConnServer(conn, addr, lc)
|
var err error
|
||||||
|
raw, err = mask.WrapPacketConnServer(raw, i, len(m.udpmasks)-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn = newConn
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conns) > 0 {
|
if len(conns) > 0 {
|
||||||
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
|
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
|
||||||
sizes = nil
|
sizes = nil
|
||||||
conns = nil
|
conns = nil
|
||||||
}
|
}
|
||||||
return conn, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UDPSize = 4096
|
UDPSize = 4096
|
||||||
)
|
)
|
||||||
|
|
||||||
type PacketConnWrapper struct {
|
type headerConn interface {
|
||||||
net.PacketConn
|
HeaderConn()
|
||||||
udpAddr net.Addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PacketConnWrapper) RemoteAddr() net.Addr {
|
type headerSize interface {
|
||||||
return c.udpAddr
|
Size() int
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConnWrapper) Read(b []byte) (n int, err error) {
|
|
||||||
n, _, err = c.PacketConn.ReadFrom(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConnWrapper) Write(b []byte) (n int, err error) {
|
|
||||||
return c.PacketConn.WriteTo(b, c.udpAddr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type headerManagerConn struct {
|
type headerManagerConn struct {
|
||||||
@@ -379,27 +194,75 @@ func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error)
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type TCPListener struct {
|
type Tcpmask interface {
|
||||||
net.Listener
|
TCP()
|
||||||
tcpMasks []TCPMask
|
|
||||||
|
WrapConnClient(net.Conn) (net.Conn, error)
|
||||||
|
WrapConnServer(net.Conn) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *TCPListener) Accept() (net.Conn, error) {
|
type TcpmaskManager struct {
|
||||||
|
tcpmasks []Tcpmask
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager {
|
||||||
|
return &TcpmaskManager{
|
||||||
|
tcpmasks: tcpmasks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
for _, mask := range slices.Backward(m.tcpmasks) {
|
||||||
|
raw, err = mask.WrapConnClient(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
for _, mask := range slices.Backward(m.tcpmasks) {
|
||||||
|
raw, err = mask.WrapConnServer(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TcpmaskManager) WrapListener(l net.Listener) (net.Listener, error) {
|
||||||
|
return NewTcpListener(m, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcpListener struct {
|
||||||
|
m *TcpmaskManager
|
||||||
|
net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTcpListener(m *TcpmaskManager, l net.Listener) (net.Listener, error) {
|
||||||
|
return &tcpListener{
|
||||||
|
m: m,
|
||||||
|
Listener: l,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *tcpListener) Accept() (net.Conn, error) {
|
||||||
conn, err := l.Listener.Accept()
|
conn, err := l.Listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range l.tcpMasks {
|
newConn, err := l.m.WrapConnServer(conn)
|
||||||
var newConn net.Conn
|
if err != nil {
|
||||||
newConn, err = l.tcpMasks[i].WrapConnServer(conn)
|
errors.LogDebugInner(context.Background(), err, "mask err")
|
||||||
if err != nil {
|
_ = conn.Close()
|
||||||
_ = conn.Close()
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = newConn
|
|
||||||
}
|
}
|
||||||
return conn, nil
|
|
||||||
|
return newConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type TcpMaskConn interface {
|
type TcpMaskConn interface {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package fragment
|
package fragment
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
|
func (c *Config) TCP() {
|
||||||
return NewConnClient(c, conn, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
|
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||||
return NewConnServer(c, conn, true)
|
return NewConnClient(c, raw, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||||
|
return NewConnServer(c, raw, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,35 @@
|
|||||||
package custom
|
package custom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *TCPConfig) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
|
func (c *TCPConfig) TCP() {}
|
||||||
return NewConnClientTCP(c, conn)
|
|
||||||
|
func (c *TCPConfig) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||||
|
return NewConnClientTCP(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TCPConfig) WrapConnServer(conn net.Conn) (net.Conn, error) {
|
func (c *TCPConfig) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||||
return NewConnServerTCP(c, conn)
|
return NewConnServerTCP(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UDPConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *UDPConfig) UDP() {}
|
||||||
return NewConnClientUDP(c, conn)
|
|
||||||
|
func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
|
return NewConnClientUDP(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UDPConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServerUDP(c, conn)
|
return NewConnServerUDP(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UDPStandaloneConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *UDPStandaloneConfig) UDP() {}
|
||||||
return NewConnClientUDPStandalone(c, conn)
|
|
||||||
|
func (c *UDPStandaloneConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
|
return NewConnClientUDPStandalone(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UDPStandaloneConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *UDPStandaloneConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServerUDPStandalone(c, conn)
|
return NewConnServerUDPStandalone(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
|
func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
|
||||||
@@ -154,7 +156,7 @@ func TestMetadataUDPStandaloneWriteUsesRemotePort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
|
client, err := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg}).WrapPacketConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -299,7 +301,7 @@ func TestMetadataTCPHandshakeUsesEndpointPorts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
|
client, err := clientCfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) {
|
func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) {
|
||||||
@@ -46,6 +48,7 @@ func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
|
||||||
|
|
||||||
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -59,11 +62,11 @@ func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
|
client, err := maskManager.WrapPacketConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
|
server, err := maskManager.WrapPacketConnServer(serverRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func TestDSLTCPHandshakeReusesCapturedValue(t *testing.T) {
|
|||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
client, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
client, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ func TestDSLTCPClientRejectsMismatchedResponseSequence(t *testing.T) {
|
|||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
|
client, err := clientCfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package aes128gcm
|
package aes128gcm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Config) UDP() {}
|
||||||
|
|
||||||
func (c *Config) HeaderConn() {}
|
func (c *Config) HeaderConn() {}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnClient(c, conn)
|
return NewConnClient(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServer(c, conn)
|
return NewConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package header
|
package header
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Config) UDP() {}
|
||||||
|
|
||||||
func (c *Config) HeaderConn() {}
|
func (c *Config) HeaderConn() {}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnClient(c, conn)
|
return NewConnClient(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServer(c, conn)
|
return NewConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package original
|
package original
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Config) UDP() {}
|
||||||
|
|
||||||
func (c *Config) HeaderConn() {}
|
func (c *Config) HeaderConn() {}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnClient(c, conn)
|
return NewConnClient(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServer(c, conn)
|
return NewConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package noise
|
package noise
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *Config) UDP() {
|
||||||
return NewConnClient(c, conn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServer(c, conn)
|
return NewConnClient(c, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
|
return NewConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/stun/v3"
|
"github.com/pion/stun/v3"
|
||||||
@@ -16,67 +15,35 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type realmConnClient struct {
|
type realmConnClient struct {
|
||||||
wg sync.WaitGroup
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
peer *net.UDPAddr
|
peer *net.UDPAddr
|
||||||
|
|
||||||
realmClient *Client
|
realmClient *Client
|
||||||
realmID string
|
realmID string
|
||||||
stunServers []string
|
stunServers []string
|
||||||
family Family
|
|
||||||
mapper *PortMapper
|
|
||||||
stunTimeout time.Duration
|
stunTimeout time.Duration
|
||||||
punchTimeout time.Duration
|
punchTimeout time.Duration
|
||||||
punchInterval time.Duration
|
punchInterval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnClient(config *Config, raw net.PacketConn) (net.PacketConn, error) {
|
func NewConnClient(config *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
family := Family_Dual
|
|
||||||
switch config.IPMode {
|
|
||||||
case "dual":
|
|
||||||
case "v4":
|
|
||||||
family = Family_V4
|
|
||||||
case "v6":
|
|
||||||
family = Family_V6
|
|
||||||
}
|
|
||||||
|
|
||||||
var mapper *PortMapper
|
|
||||||
if config.PortMapping != nil && config.PortMapping.Enabled {
|
|
||||||
var err error
|
|
||||||
start := time.Now()
|
|
||||||
mapper, err = NewPortMapper(context.Background(), raw.LocalAddr().(*net.UDPAddr).Port, PortMapConfig{Timeout: time.Duration(config.PortMapping.Timeout) * time.Second, Lifetime: time.Duration(config.PortMapping.Lifetime) * time.Second})
|
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(context.Background(), err, "[realm] [port mapping] [", raw.LocalAddr().(*net.UDPAddr).Port, "] init failed after ", time.Since(start))
|
|
||||||
} else {
|
|
||||||
errors.LogDebug(context.Background(), "[realm] [port mapping] [", mapper.InternalPort(), "] gateway ", mapper.GatewayType(), ", external ", mapper.ExternalAddr())
|
|
||||||
errors.LogDebug(context.Background(), "[realm] [port mapping] [", mapper.InternalPort(), "] init success with ", time.Since(start))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &realmConnClient{
|
conn := &realmConnClient{
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
PacketConn: raw,
|
PacketConn: raw,
|
||||||
|
|
||||||
realmClient: NewClient(config.Scheme, config.Host, config.Port, config.Token, config.TlsConfig),
|
realmClient: NewClient(config.Scheme, config.Host, config.Port, config.Token, config.TlsConfig),
|
||||||
realmID: config.ID,
|
realmID: config.ID,
|
||||||
stunServers: config.StunServers,
|
stunServers: config.StunServers,
|
||||||
family: family,
|
|
||||||
mapper: mapper,
|
|
||||||
stunTimeout: defaultSTUNTimeout,
|
stunTimeout: defaultSTUNTimeout,
|
||||||
punchTimeout: defaultPunchTimeout,
|
punchTimeout: defaultPunchTimeout,
|
||||||
punchInterval: defaultPunchInterval,
|
punchInterval: defaultPunchInterval,
|
||||||
}
|
}
|
||||||
|
|
||||||
return conn.getpeer()
|
return conn.getpeer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realmConnClient) getpeer() (net.PacketConn, error) {
|
func (c *realmConnClient) getpeer() (net.PacketConn, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
servers := resolveSTUNServers(c.PacketConn.LocalAddr().(*net.UDPAddr).IP, c.stunServers, c.family)
|
servers := resolveSTUNServers(c.PacketConn.LocalAddr().(*net.UDPAddr).IP, c.stunServers)
|
||||||
errors.LogDebug(context.Background(), "[realm] update stun servers ", servers, " with ", time.Since(start))
|
errors.LogDebug(context.Background(), "[realm] update stun servers ", servers, " with ", time.Since(start))
|
||||||
if len(servers) == 0 {
|
if len(servers) == 0 {
|
||||||
return nil, errors.New("empty locals")
|
return nil, errors.New("empty locals")
|
||||||
@@ -103,7 +70,7 @@ func (c *realmConnClient) getpeer() (net.PacketConn, error) {
|
|||||||
|
|
||||||
peers, _ := parseAddrPorts(resp.Addresses)
|
peers, _ := parseAddrPorts(resp.Addresses)
|
||||||
errors.LogDebug(context.Background(), "[realm] update peers ", peers)
|
errors.LogDebug(context.Background(), "[realm] update peers ", peers)
|
||||||
filteredPeers, seen := candidatePunchAddrs(locals, peers, c.family)
|
filteredPeers, seen := candidatePunchAddrs(locals, peers)
|
||||||
errors.LogDebug(context.Background(), "[realm] filtered peers ", filteredPeers)
|
errors.LogDebug(context.Background(), "[realm] filtered peers ", filteredPeers)
|
||||||
expandedPeers := expandSymmetricNATCandidates(filteredPeers, seen)
|
expandedPeers := expandSymmetricNATCandidates(filteredPeers, seen)
|
||||||
errors.LogDebug(context.Background(), "[realm] expanded peers ", expandedPeers)
|
errors.LogDebug(context.Background(), "[realm] expanded peers ", expandedPeers)
|
||||||
@@ -119,11 +86,6 @@ func (c *realmConnClient) getpeer() (net.PacketConn, error) {
|
|||||||
}
|
}
|
||||||
errors.LogDebug(context.Background(), "[realm] punch peer ", peer, " with ", time.Since(start))
|
errors.LogDebug(context.Background(), "[realm] punch peer ", peer, " with ", time.Since(start))
|
||||||
|
|
||||||
if c.mapper != nil {
|
|
||||||
c.wg.Add(1)
|
|
||||||
go portMapLoop(c.ctx, c.mapper, c.wg.Done)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.peer = peer
|
c.peer = peer
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
@@ -154,12 +116,10 @@ func (c *realmConnClient) discover(servers []*net.UDPAddr) []netip.AddrPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.PacketConn.SetReadDeadline(time.Time{})
|
c.PacketConn.SetReadDeadline(time.Time{})
|
||||||
if c.mapper != nil {
|
|
||||||
results = insertAddr(results, c.mapper.ExternalAddr())
|
|
||||||
}
|
|
||||||
slices.SortFunc(results, func(a, b netip.AddrPort) int {
|
slices.SortFunc(results, func(a, b netip.AddrPort) int {
|
||||||
return strings.Compare(a.String(), b.String())
|
return strings.Compare(a.String(), b.String())
|
||||||
})
|
})
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,48 +169,3 @@ func (c *realmConnClient) punch(meta PunchMetadata, peers []netip.AddrPort) (*ne
|
|||||||
func (c *realmConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
func (c *realmConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
return c.PacketConn.WriteTo(p, c.peer)
|
return c.PacketConn.WriteTo(p, c.peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realmConnClient) Close() error {
|
|
||||||
// Sadly, closing the core does not first close the sockets created for outbound connections
|
|
||||||
c.cancel()
|
|
||||||
c.wg.Wait()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func portMapLoop(ctx context.Context, mapper *PortMapper, done func()) {
|
|
||||||
defer func() {
|
|
||||||
err := mapper.Close()
|
|
||||||
done()
|
|
||||||
errors.LogDebug(context.Background(), "[realm] [port mapping] [", mapper.InternalPort(), "] removed with ", err)
|
|
||||||
}()
|
|
||||||
interval := mapper.Lifetime() / 2
|
|
||||||
if interval <= 0 {
|
|
||||||
interval = time.Minute
|
|
||||||
}
|
|
||||||
t := time.NewTicker(interval)
|
|
||||||
defer t.Stop()
|
|
||||||
failing := false
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-t.C:
|
|
||||||
changed, err := mapper.Renew(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !failing {
|
|
||||||
errors.LogError(context.Background(), "[realm] [port mapping] [", mapper.InternalPort(), "] renewal failed")
|
|
||||||
failing = true
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
errors.LogDebug(context.Background(), "[realm] [port mapping] [", mapper.InternalPort(), "] external ", mapper.ExternalAddr(), ", changed ", changed)
|
|
||||||
if failing {
|
|
||||||
errors.LogError(context.Background(), "[realm] [port mapping] [", mapper.InternalPort(), "] recovered")
|
|
||||||
failing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
package realm
|
package realm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *Config) UDP() {}
|
||||||
return NewConnClient(c, conn)
|
|
||||||
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
|
_, ok1 := raw.(*internet.FakePacketConn)
|
||||||
|
_, ok2 := raw.(*udphop.UdpHopPacketConn)
|
||||||
|
if level != 0 || ok1 || ok2 {
|
||||||
|
return nil, errors.New("realm requires being at the outermost level")
|
||||||
|
}
|
||||||
|
return NewConnClient(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServer(c, conn)
|
if level != 0 {
|
||||||
|
return nil, errors.New("realm requires being at the outermost level")
|
||||||
|
}
|
||||||
|
return NewConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,115 +22,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Family int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
Family_Dual Family = 0
|
|
||||||
Family_V4 Family = 1
|
|
||||||
Family_V6 Family = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// Enum value maps for Family.
|
|
||||||
var (
|
|
||||||
Family_name = map[int32]string{
|
|
||||||
0: "Dual",
|
|
||||||
1: "V4",
|
|
||||||
2: "V6",
|
|
||||||
}
|
|
||||||
Family_value = map[string]int32{
|
|
||||||
"Dual": 0,
|
|
||||||
"V4": 1,
|
|
||||||
"V6": 2,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (x Family) Enum() *Family {
|
|
||||||
p := new(Family)
|
|
||||||
*p = x
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x Family) String() string {
|
|
||||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Family) Descriptor() protoreflect.EnumDescriptor {
|
|
||||||
return file_transport_internet_finalmask_realm_config_proto_enumTypes[0].Descriptor()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Family) Type() protoreflect.EnumType {
|
|
||||||
return &file_transport_internet_finalmask_realm_config_proto_enumTypes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x Family) Number() protoreflect.EnumNumber {
|
|
||||||
return protoreflect.EnumNumber(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Family.Descriptor instead.
|
|
||||||
func (Family) EnumDescriptor() ([]byte, []int) {
|
|
||||||
return file_transport_internet_finalmask_realm_config_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PortMapping struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
|
||||||
Timeout int64 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"`
|
|
||||||
Lifetime int64 `protobuf:"varint,3,opt,name=lifetime,proto3" json:"lifetime,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *PortMapping) Reset() {
|
|
||||||
*x = PortMapping{}
|
|
||||||
mi := &file_transport_internet_finalmask_realm_config_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *PortMapping) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*PortMapping) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *PortMapping) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_transport_internet_finalmask_realm_config_proto_msgTypes[0]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use PortMapping.ProtoReflect.Descriptor instead.
|
|
||||||
func (*PortMapping) Descriptor() ([]byte, []int) {
|
|
||||||
return file_transport_internet_finalmask_realm_config_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *PortMapping) GetEnabled() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.Enabled
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *PortMapping) GetTimeout() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Timeout
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *PortMapping) GetLifetime() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Lifetime
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Scheme string `protobuf:"bytes,1,opt,name=scheme,proto3" json:"scheme,omitempty"`
|
Scheme string `protobuf:"bytes,1,opt,name=scheme,proto3" json:"scheme,omitempty"`
|
||||||
@@ -140,15 +31,13 @@ type Config struct {
|
|||||||
ID string `protobuf:"bytes,5,opt,name=ID,proto3" json:"ID,omitempty"`
|
ID string `protobuf:"bytes,5,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
StunServers []string `protobuf:"bytes,6,rep,name=stun_servers,json=stunServers,proto3" json:"stun_servers,omitempty"`
|
StunServers []string `protobuf:"bytes,6,rep,name=stun_servers,json=stunServers,proto3" json:"stun_servers,omitempty"`
|
||||||
TlsConfig *tls.Config `protobuf:"bytes,7,opt,name=tls_config,json=tlsConfig,proto3" json:"tls_config,omitempty"`
|
TlsConfig *tls.Config `protobuf:"bytes,7,opt,name=tls_config,json=tlsConfig,proto3" json:"tls_config,omitempty"`
|
||||||
IPMode string `protobuf:"bytes,8,opt,name=IPMode,proto3" json:"IPMode,omitempty"`
|
|
||||||
PortMapping *PortMapping `protobuf:"bytes,9,opt,name=port_mapping,json=portMapping,proto3" json:"port_mapping,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
*x = Config{}
|
*x = Config{}
|
||||||
mi := &file_transport_internet_finalmask_realm_config_proto_msgTypes[1]
|
mi := &file_transport_internet_finalmask_realm_config_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -160,7 +49,7 @@ func (x *Config) String() string {
|
|||||||
func (*Config) ProtoMessage() {}
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_transport_internet_finalmask_realm_config_proto_msgTypes[1]
|
mi := &file_transport_internet_finalmask_realm_config_proto_msgTypes[0]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -173,7 +62,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
return file_transport_internet_finalmask_realm_config_proto_rawDescGZIP(), []int{1}
|
return file_transport_internet_finalmask_realm_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetScheme() string {
|
func (x *Config) GetScheme() string {
|
||||||
@@ -225,29 +114,11 @@ func (x *Config) GetTlsConfig() *tls.Config {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetIPMode() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.IPMode
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetPortMapping() *PortMapping {
|
|
||||||
if x != nil {
|
|
||||||
return x.PortMapping
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_transport_internet_finalmask_realm_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_finalmask_realm_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_transport_internet_finalmask_realm_config_proto_rawDesc = "" +
|
const file_transport_internet_finalmask_realm_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"/transport/internet/finalmask/realm/config.proto\x12'xray.transport.internet.finalmask.realm\x1a#transport/internet/tls/config.proto\"]\n" +
|
"/transport/internet/finalmask/realm/config.proto\x12'xray.transport.internet.finalmask.realm\x1a#transport/internet/tls/config.proto\"\xd5\x01\n" +
|
||||||
"\vPortMapping\x12\x18\n" +
|
|
||||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x12\x18\n" +
|
|
||||||
"\atimeout\x18\x02 \x01(\x03R\atimeout\x12\x1a\n" +
|
|
||||||
"\blifetime\x18\x03 \x01(\x03R\blifetime\"\xc6\x02\n" +
|
|
||||||
"\x06Config\x12\x16\n" +
|
"\x06Config\x12\x16\n" +
|
||||||
"\x06scheme\x18\x01 \x01(\tR\x06scheme\x12\x12\n" +
|
"\x06scheme\x18\x01 \x01(\tR\x06scheme\x12\x12\n" +
|
||||||
"\x04host\x18\x02 \x01(\tR\x04host\x12\x12\n" +
|
"\x04host\x18\x02 \x01(\tR\x04host\x12\x12\n" +
|
||||||
@@ -256,13 +127,7 @@ const file_transport_internet_finalmask_realm_config_proto_rawDesc = "" +
|
|||||||
"\x02ID\x18\x05 \x01(\tR\x02ID\x12!\n" +
|
"\x02ID\x18\x05 \x01(\tR\x02ID\x12!\n" +
|
||||||
"\fstun_servers\x18\x06 \x03(\tR\vstunServers\x12B\n" +
|
"\fstun_servers\x18\x06 \x03(\tR\vstunServers\x12B\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"tls_config\x18\a \x01(\v2#.xray.transport.internet.tls.ConfigR\ttlsConfig\x12\x16\n" +
|
"tls_config\x18\a \x01(\v2#.xray.transport.internet.tls.ConfigR\ttlsConfigB\x97\x01\n" +
|
||||||
"\x06IPMode\x18\b \x01(\tR\x06IPMode\x12W\n" +
|
|
||||||
"\fport_mapping\x18\t \x01(\v24.xray.transport.internet.finalmask.realm.PortMappingR\vportMapping*\"\n" +
|
|
||||||
"\x06Family\x12\b\n" +
|
|
||||||
"\x04Dual\x10\x00\x12\x06\n" +
|
|
||||||
"\x02V4\x10\x01\x12\x06\n" +
|
|
||||||
"\x02V6\x10\x02B\x97\x01\n" +
|
|
||||||
"+com.xray.transport.internet.finalmask.realmP\x01Z<github.com/xtls/xray-core/transport/internet/finalmask/realm\xaa\x02'Xray.Transport.Internet.Finalmask.Realmb\x06proto3"
|
"+com.xray.transport.internet.finalmask.realmP\x01Z<github.com/xtls/xray-core/transport/internet/finalmask/realm\xaa\x02'Xray.Transport.Internet.Finalmask.Realmb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -277,22 +142,18 @@ func file_transport_internet_finalmask_realm_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_transport_internet_finalmask_realm_config_proto_rawDescData
|
return file_transport_internet_finalmask_realm_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_transport_internet_finalmask_realm_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
var file_transport_internet_finalmask_realm_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
var file_transport_internet_finalmask_realm_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
|
||||||
var file_transport_internet_finalmask_realm_config_proto_goTypes = []any{
|
var file_transport_internet_finalmask_realm_config_proto_goTypes = []any{
|
||||||
(Family)(0), // 0: xray.transport.internet.finalmask.realm.Family
|
(*Config)(nil), // 0: xray.transport.internet.finalmask.realm.Config
|
||||||
(*PortMapping)(nil), // 1: xray.transport.internet.finalmask.realm.PortMapping
|
(*tls.Config)(nil), // 1: xray.transport.internet.tls.Config
|
||||||
(*Config)(nil), // 2: xray.transport.internet.finalmask.realm.Config
|
|
||||||
(*tls.Config)(nil), // 3: xray.transport.internet.tls.Config
|
|
||||||
}
|
}
|
||||||
var file_transport_internet_finalmask_realm_config_proto_depIdxs = []int32{
|
var file_transport_internet_finalmask_realm_config_proto_depIdxs = []int32{
|
||||||
3, // 0: xray.transport.internet.finalmask.realm.Config.tls_config:type_name -> xray.transport.internet.tls.Config
|
1, // 0: xray.transport.internet.finalmask.realm.Config.tls_config:type_name -> xray.transport.internet.tls.Config
|
||||||
1, // 1: xray.transport.internet.finalmask.realm.Config.port_mapping:type_name -> xray.transport.internet.finalmask.realm.PortMapping
|
1, // [1:1] is the sub-list for method output_type
|
||||||
2, // [2:2] is the sub-list for method output_type
|
1, // [1:1] is the sub-list for method input_type
|
||||||
2, // [2:2] is the sub-list for method input_type
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
2, // [2:2] is the sub-list for extension type_name
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
2, // [2:2] is the sub-list for extension extendee
|
0, // [0:1] is the sub-list for field type_name
|
||||||
0, // [0:2] is the sub-list for field type_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_transport_internet_finalmask_realm_config_proto_init() }
|
func init() { file_transport_internet_finalmask_realm_config_proto_init() }
|
||||||
@@ -305,14 +166,13 @@ func file_transport_internet_finalmask_realm_config_proto_init() {
|
|||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_realm_config_proto_rawDesc), len(file_transport_internet_finalmask_realm_config_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_realm_config_proto_rawDesc), len(file_transport_internet_finalmask_realm_config_proto_rawDesc)),
|
||||||
NumEnums: 1,
|
NumEnums: 0,
|
||||||
NumMessages: 2,
|
NumMessages: 1,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
GoTypes: file_transport_internet_finalmask_realm_config_proto_goTypes,
|
GoTypes: file_transport_internet_finalmask_realm_config_proto_goTypes,
|
||||||
DependencyIndexes: file_transport_internet_finalmask_realm_config_proto_depIdxs,
|
DependencyIndexes: file_transport_internet_finalmask_realm_config_proto_depIdxs,
|
||||||
EnumInfos: file_transport_internet_finalmask_realm_config_proto_enumTypes,
|
|
||||||
MessageInfos: file_transport_internet_finalmask_realm_config_proto_msgTypes,
|
MessageInfos: file_transport_internet_finalmask_realm_config_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_transport_internet_finalmask_realm_config_proto = out.File
|
File_transport_internet_finalmask_realm_config_proto = out.File
|
||||||
|
|||||||
@@ -8,18 +8,6 @@ option java_multiple_files = true;
|
|||||||
|
|
||||||
import "transport/internet/tls/config.proto";
|
import "transport/internet/tls/config.proto";
|
||||||
|
|
||||||
enum Family {
|
|
||||||
Dual = 0;
|
|
||||||
V4 = 1;
|
|
||||||
V6 = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PortMapping {
|
|
||||||
bool enabled = 1;
|
|
||||||
int64 timeout = 2;
|
|
||||||
int64 lifetime = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
string scheme = 1;
|
string scheme = 1;
|
||||||
string host = 2;
|
string host = 2;
|
||||||
@@ -28,6 +16,4 @@ message Config {
|
|||||||
string ID = 5;
|
string ID = 5;
|
||||||
repeated string stun_servers = 6;
|
repeated string stun_servers = 6;
|
||||||
xray.transport.internet.tls.Config tls_config = 7;
|
xray.transport.internet.tls.Config tls_config = 7;
|
||||||
string IPMode = 8;
|
|
||||||
PortMapping port_mapping = 9;
|
|
||||||
}
|
}
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
package realm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/libp2p/go-nat"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultPortMapTimeout = 10 * time.Second
|
|
||||||
defaultPortMapLifetime = 10 * time.Minute
|
|
||||||
|
|
||||||
portMapDescription = "hysteria-realm"
|
|
||||||
portMapProtocol = "udp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrInvalidPortMapConfig = errors.New("invalid port mapping config")
|
|
||||||
|
|
||||||
type PortMapConfig struct {
|
|
||||||
Timeout time.Duration
|
|
||||||
Lifetime time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c PortMapConfig) withDefaults() (PortMapConfig, error) {
|
|
||||||
if c.Timeout == 0 {
|
|
||||||
c.Timeout = defaultPortMapTimeout
|
|
||||||
}
|
|
||||||
if c.Timeout < 0 {
|
|
||||||
return c, fmt.Errorf("%w: timeout must not be negative", ErrInvalidPortMapConfig)
|
|
||||||
}
|
|
||||||
if c.Lifetime == 0 {
|
|
||||||
c.Lifetime = defaultPortMapLifetime
|
|
||||||
}
|
|
||||||
if c.Lifetime < 0 {
|
|
||||||
return c, fmt.Errorf("%w: lifetime must not be negative", ErrInvalidPortMapConfig)
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PortMapper maintains a UDP port mapping on the local gateway via UPnP or
|
|
||||||
// NAT-PMP. It does not renew the mapping by itself; the caller is expected
|
|
||||||
// to call Renew periodically (typically every Lifetime/2).
|
|
||||||
type PortMapper struct {
|
|
||||||
gateway nat.NAT
|
|
||||||
internalPort int
|
|
||||||
config PortMapConfig
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
externalAddr netip.AddrPort
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPortMapper discovers the local gateway and maps internalPort for UDP.
|
|
||||||
// It blocks for up to 2x config.Timeout (discovery + mapping).
|
|
||||||
func NewPortMapper(ctx context.Context, internalPort int, config PortMapConfig) (*PortMapper, error) {
|
|
||||||
if internalPort <= 0 || internalPort > 65535 {
|
|
||||||
return nil, fmt.Errorf("%w: invalid internal port %d", ErrInvalidPortMapConfig, internalPort)
|
|
||||||
}
|
|
||||||
config, err := config.withDefaults()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
discoverCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
|
||||||
gateway, err := nat.DiscoverGateway(discoverCtx)
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("gateway discovery failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &PortMapper{
|
|
||||||
gateway: gateway,
|
|
||||||
internalPort: internalPort,
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
if _, err := m.Renew(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renew (re-)requests the port mapping and refreshes the external address.
|
|
||||||
// It reports whether the external address changed since the last call.
|
|
||||||
func (m *PortMapper) Renew(ctx context.Context) (bool, error) {
|
|
||||||
opCtx, cancel := context.WithTimeout(ctx, m.config.Timeout)
|
|
||||||
defer cancel()
|
|
||||||
externalPort, err := m.gateway.AddPortMapping(opCtx, portMapProtocol, m.internalPort, portMapDescription, m.config.Lifetime)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("add port mapping failed: %w", err)
|
|
||||||
}
|
|
||||||
externalIP, err := m.gateway.GetExternalAddress()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("get external address failed: %w", err)
|
|
||||||
}
|
|
||||||
addr, ok := netip.AddrFromSlice(externalIP)
|
|
||||||
if !ok || addr.IsUnspecified() || addr.IsLoopback() {
|
|
||||||
return false, fmt.Errorf("gateway returned unusable external address: %s", externalIP)
|
|
||||||
}
|
|
||||||
externalAddr := netip.AddrPortFrom(addr.Unmap(), uint16(externalPort))
|
|
||||||
|
|
||||||
m.mu.Lock()
|
|
||||||
changed := externalAddr != m.externalAddr
|
|
||||||
m.externalAddr = externalAddr
|
|
||||||
m.mu.Unlock()
|
|
||||||
return changed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExternalAddr returns the gateway's external IP and the mapped external port.
|
|
||||||
func (m *PortMapper) ExternalAddr() netip.AddrPort {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
return m.externalAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalPort returns the mapped local UDP port.
|
|
||||||
func (m *PortMapper) InternalPort() int {
|
|
||||||
return m.internalPort
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifetime returns the effective mapping lease duration.
|
|
||||||
func (m *PortMapper) Lifetime() time.Duration {
|
|
||||||
return m.config.Lifetime
|
|
||||||
}
|
|
||||||
|
|
||||||
// GatewayType returns the protocol used to talk to the gateway ("UPnP" or "NAT-PMP").
|
|
||||||
func (m *PortMapper) GatewayType() string {
|
|
||||||
return m.gateway.Type()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes the port mapping from the gateway. Best-effort.
|
|
||||||
func (m *PortMapper) Close() error {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), m.config.Timeout)
|
|
||||||
defer cancel()
|
|
||||||
return m.gateway.DeletePortMapping(ctx, portMapProtocol, m.internalPort)
|
|
||||||
}
|
|
||||||
@@ -33,16 +33,14 @@ type STUNPacketEvent struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type realmConnServer struct {
|
type realmConnServer struct {
|
||||||
wg sync.WaitGroup
|
cleaned chan struct{}
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
|
|
||||||
realmClient *Client
|
realmClient *Client
|
||||||
realmID string
|
realmID string
|
||||||
stunServers []string
|
stunServers []string
|
||||||
family Family
|
|
||||||
mapper *PortMapper
|
|
||||||
stunTimeout time.Duration
|
stunTimeout time.Duration
|
||||||
punchTimeout time.Duration
|
punchTimeout time.Duration
|
||||||
punchInterval time.Duration
|
punchInterval time.Duration
|
||||||
@@ -59,29 +57,8 @@ type realmConnServer struct {
|
|||||||
func NewConnServer(config *Config, raw net.PacketConn) (net.PacketConn, error) {
|
func NewConnServer(config *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
family := Family_Dual
|
|
||||||
switch config.IPMode {
|
|
||||||
case "dual":
|
|
||||||
case "v4":
|
|
||||||
family = Family_V4
|
|
||||||
case "v6":
|
|
||||||
family = Family_V6
|
|
||||||
}
|
|
||||||
|
|
||||||
var mapper *PortMapper
|
|
||||||
if config.PortMapping != nil && config.PortMapping.Enabled {
|
|
||||||
var err error
|
|
||||||
start := time.Now()
|
|
||||||
mapper, err = NewPortMapper(context.Background(), raw.LocalAddr().(*net.UDPAddr).Port, PortMapConfig{Timeout: time.Duration(config.PortMapping.Timeout) * time.Second, Lifetime: time.Duration(config.PortMapping.Lifetime) * time.Second})
|
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(context.Background(), err, "[realm] [port mapping] [", raw.LocalAddr().(*net.UDPAddr).Port, "] init failed after ", time.Since(start))
|
|
||||||
} else {
|
|
||||||
errors.LogDebug(context.Background(), "[realm] [port mapping] [", mapper.InternalPort(), "] gateway ", mapper.GatewayType(), ", external ", mapper.ExternalAddr())
|
|
||||||
errors.LogDebug(context.Background(), "[realm] [port mapping] [", mapper.InternalPort(), "] init success with ", time.Since(start))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &realmConnServer{
|
conn := &realmConnServer{
|
||||||
|
cleaned: make(chan struct{}),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
PacketConn: raw,
|
PacketConn: raw,
|
||||||
@@ -89,8 +66,6 @@ func NewConnServer(config *Config, raw net.PacketConn) (net.PacketConn, error) {
|
|||||||
realmClient: NewClient(config.Scheme, config.Host, config.Port, config.Token, config.TlsConfig),
|
realmClient: NewClient(config.Scheme, config.Host, config.Port, config.Token, config.TlsConfig),
|
||||||
realmID: config.ID,
|
realmID: config.ID,
|
||||||
stunServers: config.StunServers,
|
stunServers: config.StunServers,
|
||||||
family: family,
|
|
||||||
mapper: mapper,
|
|
||||||
stunTimeout: defaultSTUNTimeout,
|
stunTimeout: defaultSTUNTimeout,
|
||||||
punchTimeout: defaultPunchTimeout,
|
punchTimeout: defaultPunchTimeout,
|
||||||
punchInterval: defaultPunchInterval,
|
punchInterval: defaultPunchInterval,
|
||||||
@@ -99,12 +74,6 @@ func NewConnServer(config *Config, raw net.PacketConn) (net.PacketConn, error) {
|
|||||||
stun: make(chan STUNPacketEvent, defaultEventBuffer),
|
stun: make(chan STUNPacketEvent, defaultEventBuffer),
|
||||||
}
|
}
|
||||||
|
|
||||||
if mapper != nil {
|
|
||||||
conn.wg.Add(1)
|
|
||||||
go portMapLoop(ctx, mapper, conn.wg.Done)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.wg.Add(1)
|
|
||||||
go conn.run()
|
go conn.run()
|
||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
@@ -168,8 +137,6 @@ func (c *realmConnServer) discover(servers []*net.UDPAddr) []netip.AddrPort {
|
|||||||
results := make([]netip.AddrPort, 0, len(servers))
|
results := make([]netip.AddrPort, 0, len(servers))
|
||||||
for len(transactionIDs) > 0 {
|
for len(transactionIDs) > 0 {
|
||||||
select {
|
select {
|
||||||
case <-c.ctx.Done():
|
|
||||||
goto end
|
|
||||||
case <-deadline.C:
|
case <-deadline.C:
|
||||||
goto end
|
goto end
|
||||||
case ev := <-c.stun:
|
case ev := <-c.stun:
|
||||||
@@ -181,9 +148,6 @@ func (c *realmConnServer) discover(servers []*net.UDPAddr) []netip.AddrPort {
|
|||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
deadline.Stop()
|
deadline.Stop()
|
||||||
if c.mapper != nil {
|
|
||||||
results = insertAddr(results, c.mapper.ExternalAddr())
|
|
||||||
}
|
|
||||||
slices.SortFunc(results, func(a, b netip.AddrPort) int {
|
slices.SortFunc(results, func(a, b netip.AddrPort) int {
|
||||||
return strings.Compare(a.String(), b.String())
|
return strings.Compare(a.String(), b.String())
|
||||||
})
|
})
|
||||||
@@ -195,7 +159,7 @@ func (c *realmConnServer) getlocals(force bool) []netip.AddrPort {
|
|||||||
c.localsMu.Lock()
|
c.localsMu.Lock()
|
||||||
if force || time.Since(c.localsLast) > defaultStunCacheTTL {
|
if force || time.Since(c.localsLast) > defaultStunCacheTTL {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
servers := resolveSTUNServers(c.PacketConn.LocalAddr().(*net.UDPAddr).IP, c.stunServers, c.family)
|
servers := resolveSTUNServers(c.PacketConn.LocalAddr().(*net.UDPAddr).IP, c.stunServers)
|
||||||
errors.LogDebug(context.Background(), "[realm] update stun servers ", servers, " with ", time.Since(start))
|
errors.LogDebug(context.Background(), "[realm] update stun servers ", servers, " with ", time.Since(start))
|
||||||
if len(servers) > 0 {
|
if len(servers) > 0 {
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
@@ -268,7 +232,7 @@ retry:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "[realm] ", c.realmID, " register session err retry in ", backoff)
|
errors.LogErrorInner(context.Background(), err, "[realm] ", c.realmID, " register session err retry in ", backoff)
|
||||||
if c.waitctx(c.ctx, backoff) {
|
if c.waitctx(c.ctx, backoff) {
|
||||||
c.wg.Done()
|
close(c.cleaned)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
backoff *= 2
|
backoff *= 2
|
||||||
@@ -295,7 +259,7 @@ retry:
|
|||||||
case <-c.ctx.Done():
|
case <-c.ctx.Done():
|
||||||
_ = c.realmClient.Deregister(context.Background(), c.realmID, resp.SessionID)
|
_ = c.realmClient.Deregister(context.Background(), c.realmID, resp.SessionID)
|
||||||
errors.LogDebug(context.Background(), "[realm] ", c.realmID, " ", resp.SessionID, " deregistered")
|
errors.LogDebug(context.Background(), "[realm] ", c.realmID, " ", resp.SessionID, " deregistered")
|
||||||
c.wg.Done()
|
close(c.cleaned)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
goto retry
|
goto retry
|
||||||
@@ -396,7 +360,7 @@ func (c *realmConnServer) punchEvent(ctx context.Context, sid string, ev *PunchE
|
|||||||
|
|
||||||
peers, _ := parseAddrPorts(ev.Addresses)
|
peers, _ := parseAddrPorts(ev.Addresses)
|
||||||
errors.LogDebug(context.Background(), "[realm] ", ev.Nonce, " update peers ", peers)
|
errors.LogDebug(context.Background(), "[realm] ", ev.Nonce, " update peers ", peers)
|
||||||
filteredPeers, seen := candidatePunchAddrs(locals, peers, c.family)
|
filteredPeers, seen := candidatePunchAddrs(locals, peers)
|
||||||
errors.LogDebug(context.Background(), "[realm] ", ev.Nonce, " filtered peers ", filteredPeers)
|
errors.LogDebug(context.Background(), "[realm] ", ev.Nonce, " filtered peers ", filteredPeers)
|
||||||
expandedPeers := expandSymmetricNATCandidates(filteredPeers, seen)
|
expandedPeers := expandSymmetricNATCandidates(filteredPeers, seen)
|
||||||
errors.LogDebug(context.Background(), "[realm] ", ev.Nonce, " expanded peers ", expandedPeers)
|
errors.LogDebug(context.Background(), "[realm] ", ev.Nonce, " expanded peers ", expandedPeers)
|
||||||
@@ -434,6 +398,6 @@ func (c *realmConnServer) ReadFrom(p []byte) (int, net.Addr, error) {
|
|||||||
|
|
||||||
func (c *realmConnServer) Close() error {
|
func (c *realmConnServer) Close() error {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
c.wg.Wait()
|
<-c.cleaned
|
||||||
return c.PacketConn.Close()
|
return c.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,23 +23,14 @@ const (
|
|||||||
symmetricNATMaxPortsPerHost = 32
|
symmetricNATMaxPortsPerHost = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
func resolveSTUNServers(local net.IP, servers []string, family Family) []*net.UDPAddr {
|
func resolveSTUNServers(local net.IP, servers []string) []*net.UDPAddr {
|
||||||
var network string
|
var network string
|
||||||
if family == Family_Dual {
|
if local.IsUnspecified() {
|
||||||
if local.IsUnspecified() {
|
network = "ip"
|
||||||
network = "ip"
|
|
||||||
} else {
|
|
||||||
if local.To4() != nil {
|
|
||||||
network = "ip4"
|
|
||||||
} else {
|
|
||||||
network = "ip6"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if family == Family_V4 {
|
if local.To4() != nil {
|
||||||
network = "ip4"
|
network = "ip4"
|
||||||
}
|
} else {
|
||||||
if family == Family_V6 {
|
|
||||||
network = "ip6"
|
network = "ip6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +104,7 @@ func netIPPortToAddrPort(ip net.IP, port int) (netip.AddrPort, error) {
|
|||||||
return netip.AddrPortFrom(netip.AddrFrom16(addr), uint16(port)), nil
|
return netip.AddrPortFrom(netip.AddrFrom16(addr), uint16(port)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func candidatePunchAddrs(locals, peers []netip.AddrPort, family Family) ([]netip.AddrPort, map[netip.AddrPort]struct{}) {
|
func candidatePunchAddrs(locals, peers []netip.AddrPort) ([]netip.AddrPort, map[netip.AddrPort]struct{}) {
|
||||||
var allow4, allow6 bool
|
var allow4, allow6 bool
|
||||||
for _, local := range locals {
|
for _, local := range locals {
|
||||||
if local.Addr().Is4() {
|
if local.Addr().Is4() {
|
||||||
@@ -125,12 +116,6 @@ func candidatePunchAddrs(locals, peers []netip.AddrPort, family Family) ([]netip
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if family == Family_V4 {
|
|
||||||
allow6 = false
|
|
||||||
}
|
|
||||||
if family == Family_V6 {
|
|
||||||
allow4 = false
|
|
||||||
}
|
|
||||||
seen := make(map[netip.AddrPort]struct{}, len(peers))
|
seen := make(map[netip.AddrPort]struct{}, len(peers))
|
||||||
candidates := make([]netip.AddrPort, 0, len(peers))
|
candidates := make([]netip.AddrPort, 0, len(peers))
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
@@ -233,17 +218,3 @@ func parseAddrPorts(addrs []string) ([]netip.AddrPort, error) {
|
|||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertAddr(addrs []netip.AddrPort, addr netip.AddrPort) []netip.AddrPort {
|
|
||||||
if !addr.IsValid() {
|
|
||||||
return addrs
|
|
||||||
}
|
|
||||||
out := append([]netip.AddrPort(nil), addrs...)
|
|
||||||
i, found := slices.BinarySearchFunc(out, addr, func(a, b netip.AddrPort) int {
|
|
||||||
return strings.Compare(a.String(), b.String())
|
|
||||||
})
|
|
||||||
if found {
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
return slices.Insert(out, i, addr)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
package salamander
|
package salamander
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Config) UDP() {}
|
||||||
|
|
||||||
func (c *Config) HeaderConn() {}
|
func (c *Config) HeaderConn() {}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewSalamanderConnClient(c, conn)
|
return NewSalamanderConnClient(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewSalamanderConnServer(c, conn)
|
return NewSalamanderConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GeckoConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *GeckoConfig) UDP() {}
|
||||||
return NewGeckoConnClient(c, conn)
|
|
||||||
|
func (c *GeckoConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
|
return NewGeckoConnClient(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GeckoConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *GeckoConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewGeckoConnServer(c, conn)
|
return NewGeckoConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
package sudoku
|
package sudoku
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Config) TCP() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) UDP() {
|
||||||
|
}
|
||||||
|
|
||||||
// Sudoku in finalmask mode is a pure appearance transform with no standalone handshake.
|
// Sudoku in finalmask mode is a pure appearance transform with no standalone handshake.
|
||||||
// TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes.
|
// TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes.
|
||||||
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
|
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||||
return newPackedDirectionalConn(conn, c, true)
|
return newPackedDirectionalConn(raw, c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
|
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||||
return newPackedDirectionalConn(conn, c, false)
|
return newPackedDirectionalConn(raw, c, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (net.Conn, error) {
|
func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (net.Conn, error) {
|
||||||
@@ -35,10 +42,16 @@ func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (ne
|
|||||||
return newWrappedConn(raw, reader, writer), nil
|
return newWrappedConn(raw, reader, writer), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewUDPConn(conn, c)
|
if level != levelCount {
|
||||||
|
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
|
||||||
|
}
|
||||||
|
return NewUDPConn(raw, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewUDPConn(conn, c)
|
if level != levelCount {
|
||||||
|
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
|
||||||
|
}
|
||||||
|
return NewUDPConn(raw, c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ package finalmask_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
gonet "net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
)
|
)
|
||||||
@@ -22,14 +20,11 @@ func mustSendRecvTcp(
|
|||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
waitCh := make(chan error)
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, err := from.Write(msg)
|
_, err := from.Write(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
close(waitCh)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
@@ -45,23 +40,18 @@ func mustSendRecvTcp(
|
|||||||
if !bytes.Equal(buf[:n], msg) {
|
if !bytes.Equal(buf[:n], msg) {
|
||||||
t.Fatalf("unexpected data %q", buf[:n])
|
t.Fatalf("unexpected data %q", buf[:n])
|
||||||
}
|
}
|
||||||
|
|
||||||
<-waitCh
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type layerMaskTcp struct {
|
type layerMaskTcp struct {
|
||||||
name string
|
name string
|
||||||
mask finalmask.TCPMask
|
mask finalmask.Tcpmask
|
||||||
}
|
}
|
||||||
|
|
||||||
type failingWrapMask struct{}
|
type failingWrapMask struct{}
|
||||||
|
|
||||||
func (failingWrapMask) TCP() {}
|
func (failingWrapMask) TCP() {}
|
||||||
func (f failingWrapMask) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
|
func (f failingWrapMask) WrapConnClient(raw net.Conn) (net.Conn, error) { return raw, nil }
|
||||||
return conn, nil
|
func (f failingWrapMask) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||||
}
|
|
||||||
|
|
||||||
func (f failingWrapMask) WrapConnServer(conn net.Conn) (net.Conn, error) {
|
|
||||||
return nil, io.ErrClosedPipe
|
return nil, io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,31 +92,32 @@ func TestConnReadWrite(t *testing.T) {
|
|||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
mask := c.mask
|
mask := c.mask
|
||||||
|
|
||||||
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
|
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{mask})
|
||||||
return net.Dial("tcp", dest.NetAddr())
|
|
||||||
}
|
|
||||||
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
|
|
||||||
return net.Listen("tcp", addr.String())
|
|
||||||
}
|
|
||||||
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{mask}, nil, dialTCP, listen, nil, nil)
|
|
||||||
|
|
||||||
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { listener.Close() })
|
|
||||||
|
|
||||||
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
|
client, err := net.Dial("tcp", ln.Addr().String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { client.Close() })
|
|
||||||
|
|
||||||
server, err := listener.Accept()
|
client, err = maskManager.WrapConnClient(client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err = maskManager.WrapConnServer(server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { server.Close() })
|
|
||||||
|
|
||||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||||
@@ -159,32 +150,34 @@ func TestTCPcustomStaticHandshakeRoundTrip(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{cfg})
|
||||||
|
|
||||||
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
return net.Dial("tcp", dest.NetAddr())
|
|
||||||
}
|
|
||||||
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
|
|
||||||
return net.Listen("tcp", addr.String())
|
|
||||||
}
|
|
||||||
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{cfg}, nil, dialTCP, listen, nil, nil)
|
|
||||||
|
|
||||||
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer ln.Close()
|
||||||
|
|
||||||
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
|
clientRaw, err := net.Dial("tcp", ln.Addr().String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer clientRaw.Close()
|
||||||
|
|
||||||
server, err := listener.Accept()
|
serverRaw, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer serverRaw.Close()
|
||||||
|
|
||||||
|
client, err := maskManager.WrapConnClient(clientRaw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
server, err := maskManager.WrapConnServer(serverRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||||
@@ -227,11 +220,11 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRaw, serverRaw := gonet.Pipe()
|
clientRaw, serverRaw := net.Pipe()
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
|
client, err := clientCfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -264,37 +257,42 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) {
|
func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) {
|
||||||
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
|
clientManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}})
|
||||||
return net.Dial("tcp", dest.NetAddr())
|
serverManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}})
|
||||||
}
|
|
||||||
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
|
|
||||||
return net.Listen("tcp", addr.String())
|
|
||||||
}
|
|
||||||
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{failingWrapMask{}}, nil, dialTCP, listen, nil, nil)
|
|
||||||
|
|
||||||
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
|
rawLn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rawLn.Close()
|
||||||
|
|
||||||
|
ln, err := serverManager.WrapListener(rawLn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
accepted := make(chan struct {
|
accepted := make(chan struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
err error
|
err error
|
||||||
}, 1)
|
}, 1)
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := listener.Accept()
|
conn, err := ln.Accept()
|
||||||
accepted <- struct {
|
accepted <- struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
err error
|
err error
|
||||||
}{conn: conn, err: err}
|
}{conn: conn, err: err}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
|
clientRaw, err := net.Dial("tcp", rawLn.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer clientRaw.Close()
|
||||||
|
|
||||||
|
client, err := clientManager.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ package finalmask_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
gonet "net"
|
"net"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/proxy"
|
"github.com/xtls/xray-core/proxy"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
@@ -53,7 +51,7 @@ func mustSendRecv(
|
|||||||
|
|
||||||
type layerMask struct {
|
type layerMask struct {
|
||||||
name string
|
name string
|
||||||
mask finalmask.UDPMask
|
mask finalmask.Udpmask
|
||||||
layers int
|
layers int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,23 +213,25 @@ func newStandaloneStunLikeUDPServerConfig() *custom.UDPStandaloneConfig {
|
|||||||
func newUDPClientServerPair(t *testing.T, cfg *custom.UDPStandaloneConfig) (net.PacketConn, net.PacketConn, net.PacketConn, net.PacketConn) {
|
func newUDPClientServerPair(t *testing.T, cfg *custom.UDPStandaloneConfig) (net.PacketConn, net.PacketConn, net.PacketConn, net.PacketConn) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { _ = clientRaw.Close() })
|
t.Cleanup(func() { _ = clientRaw.Close() })
|
||||||
|
|
||||||
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { _ = serverRaw.Close() })
|
t.Cleanup(func() { _ = serverRaw.Close() })
|
||||||
|
|
||||||
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
|
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
|
||||||
|
|
||||||
|
client, err := maskManager.WrapPacketConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
|
server, err := maskManager.WrapPacketConnServer(serverRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -348,39 +348,31 @@ func TestPacketConnReadWrite(t *testing.T) {
|
|||||||
if layers <= 0 {
|
if layers <= 0 {
|
||||||
layers = 1
|
layers = 1
|
||||||
}
|
}
|
||||||
masks := make([]finalmask.UDPMask, 0, layers)
|
masks := make([]finalmask.Udpmask, 0, layers)
|
||||||
for i := 0; i < layers; i++ {
|
for i := 0; i < layers; i++ {
|
||||||
masks = append(masks, mask)
|
masks = append(masks, mask)
|
||||||
}
|
}
|
||||||
|
maskManager := finalmask.NewUdpmaskManager(masks)
|
||||||
|
|
||||||
dialUDP := func(ctx context.Context, dest net.Destination) (net.PacketConn, net.Addr, error) {
|
client, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
conn, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return conn, udpAddr, nil
|
|
||||||
}
|
|
||||||
listenPacket := func(ctx context.Context, addr net.Addr) (net.PacketConn, error) {
|
|
||||||
return gonet.ListenPacket(addr.Network(), addr.String())
|
|
||||||
}
|
|
||||||
finalMask := finalmask.NewFinalMask(nil, masks, nil, nil, dialUDP, listenPacket)
|
|
||||||
|
|
||||||
server, err := finalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: net.LocalHostIP.IP()})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { server.Close() })
|
|
||||||
|
|
||||||
clientConn, err := finalMask.DialUDP(context.Background(), net.UDPDestination(net.IPAddress(server.LocalAddr().(*net.UDPAddr).IP), net.Port(server.LocalAddr().(*net.UDPAddr).Port)))
|
client, err = maskManager.WrapPacketConnClient(client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err = maskManager.WrapPacketConnServer(server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { clientConn.Close() })
|
|
||||||
client := clientConn.(*finalmask.PacketConnWrapper).PacketConn
|
|
||||||
|
|
||||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||||
@@ -405,20 +397,21 @@ func TestUDPcustomStaticHeaderWireShape(t *testing.T) {
|
|||||||
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
|
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
|
||||||
|
|
||||||
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
|
|
||||||
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
|
client, err := maskManager.WrapPacketConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -649,11 +642,11 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
Ascii: "prefer_ascii",
|
Ascii: "prefer_ascii",
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRaw, serverRaw := gonet.Pipe()
|
clientRaw, serverRaw := net.Pipe()
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
clientConn, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -690,11 +683,11 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
PaddingMax: 0,
|
PaddingMax: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRaw, serverRaw := gonet.Pipe()
|
clientRaw, serverRaw := net.Pipe()
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
clientConn, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -745,10 +738,10 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
countWireBytes := func(wrapServer func(net.Conn, *sudoku.Config) (net.Conn, error), cfg *sudoku.Config) int64 {
|
countWireBytes := func(wrapServer func(net.Conn, *sudoku.Config) (net.Conn, error), cfg *sudoku.Config) int64 {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
clientRaw, serverRaw := gonet.Pipe()
|
clientRaw, serverRaw := net.Pipe()
|
||||||
watchedServerRaw := &countingConn{Conn: serverRaw}
|
watchedServerRaw := &countingConn{Conn: serverRaw}
|
||||||
|
|
||||||
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
clientConn, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -800,11 +793,11 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
CustomTables: []string{"xpxvvpvv", "vxpvxvvp"},
|
CustomTables: []string{"xpxvvpvv", "vxpvxvvp"},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRaw, serverRaw := gonet.Pipe()
|
clientRaw, serverRaw := net.Pipe()
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
clientConn, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -842,11 +835,11 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
PaddingMax: 0,
|
PaddingMax: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRaw, serverRaw := gonet.Pipe()
|
clientRaw, serverRaw := net.Pipe()
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
clientConn, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -875,6 +868,19 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("GivenSudokuUDPMask_WhenNotInnermost_ThenWrapFails", func(t *testing.T) {
|
||||||
|
cfg := &sudoku.Config{Password: "sudoku-udp"}
|
||||||
|
raw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer raw.Close()
|
||||||
|
|
||||||
|
if _, err := cfg.WrapPacketConnClient(raw, 0, 1); err == nil {
|
||||||
|
t.Fatal("expected innermost check failure")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("GivenSudokuMultiTableUDPMask_WhenClientSendsMultipleDatagrams_ThenPayloadMatches", func(t *testing.T) {
|
t.Run("GivenSudokuMultiTableUDPMask_WhenClientSendsMultipleDatagrams_ThenPayloadMatches", func(t *testing.T) {
|
||||||
cfg := &sudoku.Config{
|
cfg := &sudoku.Config{
|
||||||
Password: "sudoku-udp-multi",
|
Password: "sudoku-udp-multi",
|
||||||
@@ -883,24 +889,25 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
PaddingMin: 0,
|
PaddingMin: 0,
|
||||||
PaddingMax: 0,
|
PaddingMax: 0,
|
||||||
}
|
}
|
||||||
|
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
|
||||||
|
|
||||||
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
|
|
||||||
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
|
client, err := maskManager.WrapPacketConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
|
server, err := maskManager.WrapPacketConnServer(serverRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -954,7 +961,7 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
clientConn, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -1001,11 +1008,11 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
Ascii: "prefer_entropy",
|
Ascii: "prefer_entropy",
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRaw, serverRaw := gonet.Pipe()
|
clientRaw, serverRaw := net.Pipe()
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
clientConn, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -1025,11 +1032,11 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
Ascii: "prefer_entropy",
|
Ascii: "prefer_entropy",
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRaw, serverRaw := gonet.Pipe()
|
clientRaw, serverRaw := net.Pipe()
|
||||||
defer clientRaw.Close()
|
defer clientRaw.Close()
|
||||||
defer serverRaw.Close()
|
defer serverRaw.Close()
|
||||||
|
|
||||||
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
|
clientConn, err := cfg.WrapConnClient(clientRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package udphop
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Config) HandleDial() {}
|
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
|
||||||
return NewUDPHopConn(c, dest, dialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
|
||||||
return nil, errors.New("udphop: client only")
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.36.11
|
|
||||||
// protoc v6.33.5
|
|
||||||
// source: transport/internet/finalmask/udphop/config.proto
|
|
||||||
|
|
||||||
package udphop
|
|
||||||
|
|
||||||
import (
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
unsafe "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Local bool `protobuf:"varint,2,opt,name=local,proto3" json:"local,omitempty"`
|
|
||||||
Remote bool `protobuf:"varint,3,opt,name=remote,proto3" json:"remote,omitempty"`
|
|
||||||
RemoteOnce bool `protobuf:"varint,4,opt,name=remote_once,json=remoteOnce,proto3" json:"remote_once,omitempty"`
|
|
||||||
IntervalMin int64 `protobuf:"varint,5,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
|
|
||||||
IntervalMax int64 `protobuf:"varint,6,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
|
|
||||||
RemoteIPs []string `protobuf:"bytes,7,rep,name=remoteIPs,proto3" json:"remoteIPs,omitempty"`
|
|
||||||
RemotePorts []uint32 `protobuf:"varint,8,rep,packed,name=remote_ports,json=remotePorts,proto3" json:"remote_ports,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
|
||||||
*x = Config{}
|
|
||||||
mi := &file_transport_internet_finalmask_udphop_config_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Config) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_transport_internet_finalmask_udphop_config_proto_msgTypes[0]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
|
||||||
return file_transport_internet_finalmask_udphop_config_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetLocal() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.Local
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetRemote() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.Remote
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetRemoteOnce() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.RemoteOnce
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetIntervalMin() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.IntervalMin
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetIntervalMax() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.IntervalMax
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetRemoteIPs() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.RemoteIPs
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetRemotePorts() []uint32 {
|
|
||||||
if x != nil {
|
|
||||||
return x.RemotePorts
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_transport_internet_finalmask_udphop_config_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
const file_transport_internet_finalmask_udphop_config_proto_rawDesc = "" +
|
|
||||||
"\n" +
|
|
||||||
"0transport/internet/finalmask/udphop/config.proto\x12(xray.transport.internet.finalmask.udphop\"\xe4\x01\n" +
|
|
||||||
"\x06Config\x12\x14\n" +
|
|
||||||
"\x05local\x18\x02 \x01(\bR\x05local\x12\x16\n" +
|
|
||||||
"\x06remote\x18\x03 \x01(\bR\x06remote\x12\x1f\n" +
|
|
||||||
"\vremote_once\x18\x04 \x01(\bR\n" +
|
|
||||||
"remoteOnce\x12!\n" +
|
|
||||||
"\finterval_min\x18\x05 \x01(\x03R\vintervalMin\x12!\n" +
|
|
||||||
"\finterval_max\x18\x06 \x01(\x03R\vintervalMax\x12\x1c\n" +
|
|
||||||
"\tremoteIPs\x18\a \x03(\tR\tremoteIPs\x12!\n" +
|
|
||||||
"\fremote_ports\x18\b \x03(\rR\vremotePortsJ\x04\b\x01\x10\x02B\x9a\x01\n" +
|
|
||||||
",com.xray.transport.internet.finalmask.udphopP\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/udphop\xaa\x02(Xray.Transport.Internet.Finalmask.Udphopb\x06proto3"
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_transport_internet_finalmask_udphop_config_proto_rawDescOnce sync.Once
|
|
||||||
file_transport_internet_finalmask_udphop_config_proto_rawDescData []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_transport_internet_finalmask_udphop_config_proto_rawDescGZIP() []byte {
|
|
||||||
file_transport_internet_finalmask_udphop_config_proto_rawDescOnce.Do(func() {
|
|
||||||
file_transport_internet_finalmask_udphop_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_udphop_config_proto_rawDesc), len(file_transport_internet_finalmask_udphop_config_proto_rawDesc)))
|
|
||||||
})
|
|
||||||
return file_transport_internet_finalmask_udphop_config_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_transport_internet_finalmask_udphop_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
|
||||||
var file_transport_internet_finalmask_udphop_config_proto_goTypes = []any{
|
|
||||||
(*Config)(nil), // 0: xray.transport.internet.finalmask.udphop.Config
|
|
||||||
}
|
|
||||||
var file_transport_internet_finalmask_udphop_config_proto_depIdxs = []int32{
|
|
||||||
0, // [0:0] is the sub-list for method output_type
|
|
||||||
0, // [0:0] is the sub-list for method input_type
|
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
|
||||||
0, // [0:0] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_transport_internet_finalmask_udphop_config_proto_init() }
|
|
||||||
func file_transport_internet_finalmask_udphop_config_proto_init() {
|
|
||||||
if File_transport_internet_finalmask_udphop_config_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_udphop_config_proto_rawDesc), len(file_transport_internet_finalmask_udphop_config_proto_rawDesc)),
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 1,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 0,
|
|
||||||
},
|
|
||||||
GoTypes: file_transport_internet_finalmask_udphop_config_proto_goTypes,
|
|
||||||
DependencyIndexes: file_transport_internet_finalmask_udphop_config_proto_depIdxs,
|
|
||||||
MessageInfos: file_transport_internet_finalmask_udphop_config_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_transport_internet_finalmask_udphop_config_proto = out.File
|
|
||||||
file_transport_internet_finalmask_udphop_config_proto_goTypes = nil
|
|
||||||
file_transport_internet_finalmask_udphop_config_proto_depIdxs = nil
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package xray.transport.internet.finalmask.udphop;
|
|
||||||
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Udphop";
|
|
||||||
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/udphop";
|
|
||||||
option java_package = "com.xray.transport.internet.finalmask.udphop";
|
|
||||||
option java_multiple_files = true;
|
|
||||||
|
|
||||||
message Config {
|
|
||||||
reserved 1;
|
|
||||||
bool local = 2;
|
|
||||||
bool remote = 3;
|
|
||||||
bool remote_once = 4;
|
|
||||||
int64 interval_min = 5;
|
|
||||||
int64 interval_max = 6;
|
|
||||||
repeated string remoteIPs = 7;
|
|
||||||
repeated uint32 remote_ports = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
package udphop
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
goerrors "errors"
|
|
||||||
"io"
|
|
||||||
mrand "math/rand"
|
|
||||||
"net/netip"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
|
||||||
|
|
||||||
var pool = sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
return make([]byte, finalmask.UDPSize)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type packet struct {
|
|
||||||
p []byte
|
|
||||||
addr net.Addr
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type udpHopConn struct {
|
|
||||||
dialer *finalmask.Dialer
|
|
||||||
local bool
|
|
||||||
remote bool
|
|
||||||
|
|
||||||
intervalMin int64
|
|
||||||
intervalMax int64
|
|
||||||
remoteIPs []netip.Prefix
|
|
||||||
remotePorts []uint32
|
|
||||||
|
|
||||||
deadline time.Time
|
|
||||||
readDeadline time.Time
|
|
||||||
writeDeadline time.Time
|
|
||||||
|
|
||||||
pre net.PacketConn
|
|
||||||
cur net.PacketConn
|
|
||||||
addr *net.UDPAddr
|
|
||||||
readCh chan packet
|
|
||||||
closeCh chan struct{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUDPHopConn(c *Config, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
|
||||||
if c.IntervalMin < 5 || c.IntervalMax < 5 {
|
|
||||||
return nil, errors.New("invalid interval")
|
|
||||||
}
|
|
||||||
remoteIPs := make([]netip.Prefix, 0, len(c.RemoteIPs))
|
|
||||||
for _, ip := range c.RemoteIPs {
|
|
||||||
remoteIPs = append(remoteIPs, netip.MustParsePrefix(ip))
|
|
||||||
}
|
|
||||||
remotePorts := c.RemotePorts
|
|
||||||
if c.Remote || c.RemoteOnce {
|
|
||||||
if len(remoteIPs) > 0 {
|
|
||||||
dest.Address = net.IPAddress(randPrefix(remoteIPs[mrand.Intn(len(remoteIPs))]))
|
|
||||||
}
|
|
||||||
if len(remotePorts) > 0 {
|
|
||||||
dest.Port = net.Port(remotePorts[mrand.Intn(len(remotePorts))])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn, err := dialer.DialUDP(*dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cur := conn.(*finalmask.PacketConnWrapper).PacketConn
|
|
||||||
addr := conn.RemoteAddr().(*net.UDPAddr)
|
|
||||||
client := &udpHopConn{
|
|
||||||
dialer: dialer,
|
|
||||||
local: c.Local,
|
|
||||||
remote: c.Remote,
|
|
||||||
|
|
||||||
intervalMin: c.IntervalMin,
|
|
||||||
intervalMax: c.IntervalMax,
|
|
||||||
remoteIPs: remoteIPs,
|
|
||||||
remotePorts: remotePorts,
|
|
||||||
|
|
||||||
cur: cur,
|
|
||||||
addr: addr,
|
|
||||||
readCh: make(chan packet),
|
|
||||||
closeCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
go client.run()
|
|
||||||
client.wg.Add(1)
|
|
||||||
go client.recv(client.cur)
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) closed() bool {
|
|
||||||
select {
|
|
||||||
case <-c.closeCh:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) run() {
|
|
||||||
ticker := time.NewTicker(time.Second * time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1)))
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-c.closeCh:
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
ticker.Reset(time.Second * time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1)))
|
|
||||||
c.hop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) hop() {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if c.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
oldIP := c.addr.IP
|
|
||||||
oldPort := c.addr.Port
|
|
||||||
if c.remote {
|
|
||||||
if len(c.remoteIPs) > 0 {
|
|
||||||
c.addr.IP = randPrefix(c.remoteIPs[mrand.Intn(len(c.remoteIPs))])
|
|
||||||
}
|
|
||||||
if len(c.remotePorts) > 0 {
|
|
||||||
c.addr.Port = int(c.remotePorts[mrand.Intn(len(c.remotePorts))])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.local {
|
|
||||||
conn, err := c.dialer.DialUDP(net.UDPDestination(net.IPAddress(c.addr.IP), net.Port(c.addr.Port)))
|
|
||||||
if err != nil {
|
|
||||||
c.addr.IP = oldIP
|
|
||||||
c.addr.Port = oldPort
|
|
||||||
errors.LogErrorInner(context.Background(), err, "hop err")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
conn.SetDeadline(c.deadline)
|
|
||||||
conn.SetReadDeadline(c.readDeadline)
|
|
||||||
conn.SetWriteDeadline(c.writeDeadline)
|
|
||||||
if c.pre != nil {
|
|
||||||
_ = c.pre.Close()
|
|
||||||
}
|
|
||||||
c.pre = c.cur
|
|
||||||
c.cur = conn.(*finalmask.PacketConnWrapper).PacketConn
|
|
||||||
c.wg.Add(1)
|
|
||||||
go c.recv(c.cur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) recv(conn net.PacketConn) {
|
|
||||||
defer c.wg.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
p := pool.Get().([]byte)
|
|
||||||
n, addr, err := conn.ReadFrom(p)
|
|
||||||
if err != nil {
|
|
||||||
pool.Put(p[:cap(p)])
|
|
||||||
if c.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var netErr net.Error
|
|
||||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
|
||||||
select {
|
|
||||||
case c.readCh <- packet{err: err}:
|
|
||||||
case <-c.closeCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
errors.LogErrorInner(context.Background(), err, "recv err")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case c.readCh <- packet{p: p[:n], addr: addr}:
|
|
||||||
case <-c.closeCh:
|
|
||||||
pool.Put(p[:cap(p)])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
||||||
packet, ok := <-c.readCh
|
|
||||||
if ok {
|
|
||||||
if packet.p != nil {
|
|
||||||
n = copy(p, packet.p)
|
|
||||||
pool.Put(packet.p[:cap(packet.p)])
|
|
||||||
}
|
|
||||||
return n, packet.addr, packet.err
|
|
||||||
}
|
|
||||||
return 0, nil, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
||||||
c.mu.RLock()
|
|
||||||
defer c.mu.RUnlock()
|
|
||||||
_, err = c.cur.WriteTo(p, c.addr)
|
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(context.Background(), err, "send err")
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) Close() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if c.closed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
close(c.closeCh)
|
|
||||||
if c.pre != nil {
|
|
||||||
_ = c.pre.Close()
|
|
||||||
}
|
|
||||||
_ = c.cur.Close()
|
|
||||||
c.wg.Wait()
|
|
||||||
select {
|
|
||||||
case packet := <-c.readCh:
|
|
||||||
if packet.p != nil {
|
|
||||||
pool.Put(packet.p[:cap(packet.p)])
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
close(c.readCh)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) LocalAddr() net.Addr {
|
|
||||||
c.mu.RLock()
|
|
||||||
defer c.mu.RUnlock()
|
|
||||||
return c.cur.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) SetDeadline(t time.Time) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.deadline = t
|
|
||||||
if c.pre != nil {
|
|
||||||
_ = c.pre.SetDeadline(t)
|
|
||||||
}
|
|
||||||
return c.cur.SetDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) SetReadDeadline(t time.Time) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.readDeadline = t
|
|
||||||
if c.pre != nil {
|
|
||||||
_ = c.pre.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
return c.cur.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpHopConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.writeDeadline = t
|
|
||||||
if c.pre != nil {
|
|
||||||
_ = c.pre.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
return c.cur.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func randPrefix(p netip.Prefix) []byte {
|
|
||||||
if p.IsSingleIP() {
|
|
||||||
return p.Addr().AsSlice()
|
|
||||||
}
|
|
||||||
b := p.Addr().AsSlice()
|
|
||||||
prefix := p.Bits()
|
|
||||||
var new [16]byte
|
|
||||||
common.Must2(rand.Read(new[:len(b)]))
|
|
||||||
i := prefix / 8
|
|
||||||
j := prefix % 8
|
|
||||||
if i+1 < len(b) {
|
|
||||||
copy(b[i+1:], new[i+1:])
|
|
||||||
}
|
|
||||||
mask := byte(0xff << (8 - j))
|
|
||||||
b[i] = (b[i] & mask) | (new[i] &^ mask)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,24 @@
|
|||||||
package xdns
|
package xdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
func (c *Config) UDP() {
|
||||||
return NewConnClient(c, conn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServer(c, conn)
|
// _, ok1 := raw.(*internet.FakePacketConn)
|
||||||
|
// _, ok2 := raw.(*udphop.UdpHopPacketConn)
|
||||||
|
// if level != 0 || ok1 || ok2 {
|
||||||
|
// return nil, errors.New("xdns requires being at the outermost level")
|
||||||
|
// }
|
||||||
|
return NewConnClient(c, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
|
// if level != 0 {
|
||||||
|
// return nil, errors.New("xdns requires being at the outermost level")
|
||||||
|
// }
|
||||||
|
return NewConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
goerrors "errors"
|
goerrors "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
mrand "math/rand"
|
mathrand "math/rand"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -16,7 +17,6 @@ import (
|
|||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||||
"golang.org/x/net/icmp"
|
"golang.org/x/net/icmp"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
@@ -36,21 +36,20 @@ type packet struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type xicmpConnClient struct {
|
type xicmpConnClient struct {
|
||||||
|
conn net.PacketConn
|
||||||
icmp4 *icmp.PacketConn
|
icmp4 *icmp.PacketConn
|
||||||
icmp6 *icmp.PacketConn
|
icmp6 *icmp.PacketConn
|
||||||
udp bool
|
udp bool
|
||||||
ips []netip.Addr
|
ips []netip.Addr
|
||||||
ip net.IP
|
|
||||||
clientID [8]byte
|
clientID [8]byte
|
||||||
id int
|
id int
|
||||||
seq int
|
seq int
|
||||||
readCh chan packet
|
readCh chan packet
|
||||||
closeCh chan struct{}
|
closedCh chan struct{}
|
||||||
wg sync.WaitGroup
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnClient(c *Config, dest *net.Destination) (net.PacketConn, error) {
|
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||||
var icmp4, icmp6 *icmp.PacketConn
|
var icmp4, icmp6 *icmp.PacketConn
|
||||||
var err4, err6 error
|
var err4, err6 error
|
||||||
if c.DGRAM {
|
if c.DGRAM {
|
||||||
@@ -69,39 +68,35 @@ func NewConnClient(c *Config, dest *net.Destination) (net.PacketConn, error) {
|
|||||||
ips = append(ips, netip.MustParseAddr(ip))
|
ips = append(ips, netip.MustParseAddr(ip))
|
||||||
}
|
}
|
||||||
|
|
||||||
var ip net.IP
|
|
||||||
if len(ips) > 0 {
|
|
||||||
ip = ips[mrand.Intn(len(ips))].AsSlice()
|
|
||||||
} else {
|
|
||||||
ip = dest.Address.IP()
|
|
||||||
}
|
|
||||||
|
|
||||||
var clientID [8]byte
|
var clientID [8]byte
|
||||||
common.Must2(rand.Read(clientID[:]))
|
common.Must2(rand.Read(clientID[:]))
|
||||||
|
|
||||||
conn := &xicmpConnClient{
|
conn := &xicmpConnClient{
|
||||||
|
conn: raw,
|
||||||
icmp4: icmp4,
|
icmp4: icmp4,
|
||||||
icmp6: icmp6,
|
icmp6: icmp6,
|
||||||
udp: c.DGRAM,
|
udp: c.DGRAM,
|
||||||
ips: ips,
|
ips: ips,
|
||||||
ip: ip,
|
|
||||||
clientID: clientID,
|
clientID: clientID,
|
||||||
id: mrand.Intn(65536),
|
id: mathrand.Intn(65536),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
readCh: make(chan packet),
|
readCh: make(chan packet),
|
||||||
closeCh: make(chan struct{}),
|
closedCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.wg.Add(2)
|
|
||||||
go conn.recv4()
|
go conn.recv4()
|
||||||
go conn.recv6()
|
go conn.recv6()
|
||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *xicmpConnClient) ring(a, b uint16) uint16 {
|
||||||
|
return min(a-b, b-a)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *xicmpConnClient) closed() bool {
|
func (c *xicmpConnClient) closed() bool {
|
||||||
select {
|
select {
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -109,28 +104,26 @@ func (c *xicmpConnClient) closed() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnClient) recv4() {
|
func (c *xicmpConnClient) recv4() {
|
||||||
defer c.wg.Done()
|
|
||||||
|
|
||||||
var b [finalmask.UDPSize]byte
|
var b [finalmask.UDPSize]byte
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if c.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n, addr, err := c.icmp4.ReadFrom(b[:])
|
n, addr, err := c.icmp4.ReadFrom(b[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var netErr net.Error
|
var netErr net.Error
|
||||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
||||||
select {
|
select {
|
||||||
case c.readCh <- packet{
|
case c.readCh <- packet{
|
||||||
err: err,
|
err: err,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
errors.LogErrorInner(context.Background(), err, "recv err 4")
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := icmp.ParseMessage(1, b[:n])
|
msg, err := icmp.ParseMessage(1, b[:n])
|
||||||
@@ -153,6 +146,10 @@ func (c *xicmpConnClient) recv4() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.ring(uint16(echo.Seq), uint16(c.seq)) > 1000 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
|
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -169,7 +166,7 @@ func (c *xicmpConnClient) recv4() {
|
|||||||
p: p,
|
p: p,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
pool.Put(p)
|
pool.Put(p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -177,28 +174,26 @@ func (c *xicmpConnClient) recv4() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnClient) recv6() {
|
func (c *xicmpConnClient) recv6() {
|
||||||
defer c.wg.Done()
|
|
||||||
|
|
||||||
var b [finalmask.UDPSize]byte
|
var b [finalmask.UDPSize]byte
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if c.closed() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
n, addr, err := c.icmp6.ReadFrom(b[:])
|
n, addr, err := c.icmp6.ReadFrom(b[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var netErr net.Error
|
var netErr net.Error
|
||||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
||||||
select {
|
select {
|
||||||
case c.readCh <- packet{
|
case c.readCh <- packet{
|
||||||
err: err,
|
err: err,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
errors.LogErrorInner(context.Background(), err, "recv err 6")
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := icmp.ParseMessage(58, b[:n])
|
msg, err := icmp.ParseMessage(58, b[:n])
|
||||||
@@ -221,6 +216,10 @@ func (c *xicmpConnClient) recv6() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.ring(uint16(echo.Seq), uint16(c.seq)) > 1000 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
|
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -237,7 +236,7 @@ func (c *xicmpConnClient) recv6() {
|
|||||||
p: p,
|
p: p,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
pool.Put(p)
|
pool.Put(p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -245,15 +244,16 @@ func (c *xicmpConnClient) recv6() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
packet, ok := <-c.readCh
|
select {
|
||||||
if ok {
|
case packet := <-c.readCh:
|
||||||
if packet.p != nil {
|
if packet.p != nil {
|
||||||
n = copy(p, packet.p)
|
n = copy(p, packet.p)
|
||||||
pool.Put(packet.p)
|
pool.Put(packet.p)
|
||||||
}
|
}
|
||||||
return n, packet.addr, packet.err
|
return n, packet.addr, packet.err
|
||||||
|
case <-c.closedCh:
|
||||||
|
return 0, nil, io.EOF
|
||||||
}
|
}
|
||||||
return 0, nil, io.EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
@@ -268,9 +268,9 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|||||||
c.seq %= 65536
|
c.seq %= 65536
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
ip := c.ip
|
ip := addr.(*net.UDPAddr).IP
|
||||||
if len(c.ips) > 0 {
|
if len(c.ips) > 0 {
|
||||||
ip = c.ips[mrand.Intn(len(c.ips))].AsSlice()
|
ip = c.ips[mathrand.Intn(len(c.ips))].AsSlice()
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.udp {
|
if c.udp {
|
||||||
@@ -294,9 +294,10 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "send err")
|
errors.LogErrorInner(context.Background(), err, "xicmp write")
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,23 +307,15 @@ func (c *xicmpConnClient) Close() error {
|
|||||||
if c.closed() {
|
if c.closed() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
close(c.closeCh)
|
close(c.closedCh)
|
||||||
_ = c.icmp4.Close()
|
_ = c.icmp4.Close()
|
||||||
_ = c.icmp6.Close()
|
_ = c.icmp6.Close()
|
||||||
c.wg.Wait()
|
_ = c.conn.Close()
|
||||||
select {
|
|
||||||
case p := <-c.readCh:
|
|
||||||
if p.p != nil {
|
|
||||||
pool.Put(p.p)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
close(c.readCh)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnClient) LocalAddr() net.Addr {
|
func (c *xicmpConnClient) LocalAddr() net.Addr {
|
||||||
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
|
return c.conn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnClient) SetDeadline(t time.Time) error {
|
func (c *xicmpConnClient) SetDeadline(t time.Time) error {
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
package xicmp
|
package xicmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"net"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Config) HandleDial() {}
|
func (c *Config) UDP() {
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) HandleListen() {}
|
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
|
_, ok1 := raw.(*internet.FakePacketConn)
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
_, ok2 := raw.(*udphop.UdpHopPacketConn)
|
||||||
if dest.Address.Family().IsDomain() && len(c.IPs) == 0 {
|
if level != 0 || ok1 || ok2 {
|
||||||
return nil, errors.New("empty ip addresses")
|
return nil, errors.New("xicmp requires being at the outermost level")
|
||||||
}
|
}
|
||||||
return NewConnClient(c, dest)
|
return NewConnClient(c, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
|
||||||
return NewConnServer(c)
|
if level != 0 {
|
||||||
|
return nil, errors.New("xicmp requires being at the outermost level")
|
||||||
|
}
|
||||||
|
return NewConnServer(c, raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,17 +37,17 @@ type record struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type xicmpConnServer struct {
|
type xicmpConnServer struct {
|
||||||
icmp4 *icmp.PacketConn
|
conn net.PacketConn
|
||||||
icmp6 *icmp.PacketConn
|
icmp4 *icmp.PacketConn
|
||||||
ips map[netip.Addr]struct{}
|
icmp6 *icmp.PacketConn
|
||||||
rec map[string]record
|
ips map[netip.Addr]struct{}
|
||||||
readCh chan packet
|
rec map[string]record
|
||||||
closeCh chan struct{}
|
readCh chan packet
|
||||||
wg sync.WaitGroup
|
closedCh chan struct{}
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnServer(c *Config) (net.PacketConn, error) {
|
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||||
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -63,16 +63,16 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := &xicmpConnServer{
|
conn := &xicmpConnServer{
|
||||||
icmp4: icmp4,
|
conn: raw,
|
||||||
icmp6: icmp6,
|
icmp4: icmp4,
|
||||||
ips: ips,
|
icmp6: icmp6,
|
||||||
rec: make(map[string]record),
|
ips: ips,
|
||||||
readCh: make(chan packet),
|
rec: make(map[string]record),
|
||||||
closeCh: make(chan struct{}),
|
readCh: make(chan packet),
|
||||||
|
closedCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
go conn.clean()
|
go conn.clean()
|
||||||
conn.wg.Add(2)
|
|
||||||
go conn.recv4()
|
go conn.recv4()
|
||||||
go conn.recv6()
|
go conn.recv6()
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
|
|||||||
|
|
||||||
func (c *xicmpConnServer) closed() bool {
|
func (c *xicmpConnServer) closed() bool {
|
||||||
select {
|
select {
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -102,35 +102,33 @@ func (c *xicmpConnServer) clean() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) recv4() {
|
func (c *xicmpConnServer) recv4() {
|
||||||
defer c.wg.Done()
|
|
||||||
|
|
||||||
var b [finalmask.UDPSize]byte
|
var b [finalmask.UDPSize]byte
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if c.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n, addr, err := c.icmp4.ReadFrom(b[:])
|
n, addr, err := c.icmp4.ReadFrom(b[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var netErr net.Error
|
var netErr net.Error
|
||||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
||||||
select {
|
select {
|
||||||
case c.readCh <- packet{
|
case c.readCh <- packet{
|
||||||
err: err,
|
err: err,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
errors.LogErrorInner(context.Background(), err, "recv err 4")
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := icmp.ParseMessage(1, b[:n])
|
msg, err := icmp.ParseMessage(1, b[:n])
|
||||||
@@ -181,7 +179,7 @@ func (c *xicmpConnServer) recv4() {
|
|||||||
p: p,
|
p: p,
|
||||||
addr: cAddr,
|
addr: cAddr,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
pool.Put(p)
|
pool.Put(p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -189,28 +187,26 @@ func (c *xicmpConnServer) recv4() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) recv6() {
|
func (c *xicmpConnServer) recv6() {
|
||||||
defer c.wg.Done()
|
|
||||||
|
|
||||||
var b [finalmask.UDPSize]byte
|
var b [finalmask.UDPSize]byte
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if c.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n, addr, err := c.icmp6.ReadFrom(b[:])
|
n, addr, err := c.icmp6.ReadFrom(b[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var netErr net.Error
|
var netErr net.Error
|
||||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
||||||
select {
|
select {
|
||||||
case c.readCh <- packet{
|
case c.readCh <- packet{
|
||||||
err: err,
|
err: err,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
errors.LogErrorInner(context.Background(), err, "recv err 6")
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := icmp.ParseMessage(58, b[:n])
|
msg, err := icmp.ParseMessage(58, b[:n])
|
||||||
@@ -261,7 +257,7 @@ func (c *xicmpConnServer) recv6() {
|
|||||||
p: p,
|
p: p,
|
||||||
addr: cAddr,
|
addr: cAddr,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
pool.Put(p)
|
pool.Put(p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -269,15 +265,16 @@ func (c *xicmpConnServer) recv6() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
packet, ok := <-c.readCh
|
select {
|
||||||
if ok {
|
case packet := <-c.readCh:
|
||||||
if packet.p != nil {
|
if packet.p != nil {
|
||||||
n = copy(p, packet.p)
|
n = copy(p, packet.p)
|
||||||
pool.Put(packet.p)
|
pool.Put(packet.p)
|
||||||
}
|
}
|
||||||
return n, packet.addr, packet.err
|
return n, packet.addr, packet.err
|
||||||
|
case <-c.closedCh:
|
||||||
|
return 0, nil, io.EOF
|
||||||
}
|
}
|
||||||
return 0, nil, io.EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
@@ -313,9 +310,10 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "send err")
|
errors.LogErrorInner(context.Background(), err, "xicmp write")
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,23 +323,15 @@ func (c *xicmpConnServer) Close() error {
|
|||||||
if c.closed() {
|
if c.closed() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
close(c.closeCh)
|
close(c.closedCh)
|
||||||
_ = c.icmp4.Close()
|
_ = c.icmp4.Close()
|
||||||
_ = c.icmp6.Close()
|
_ = c.icmp6.Close()
|
||||||
c.wg.Wait()
|
_ = c.conn.Close()
|
||||||
select {
|
|
||||||
case p := <-c.readCh:
|
|
||||||
if p.p != nil {
|
|
||||||
pool.Put(p.p)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
close(c.readCh)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) LocalAddr() net.Addr {
|
func (c *xicmpConnServer) LocalAddr() net.Addr {
|
||||||
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
|
return c.conn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
|
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
|
||||||
|
|||||||
@@ -39,19 +39,19 @@ type record struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type xicmpConnServer struct {
|
type xicmpConnServer struct {
|
||||||
icmp4 *icmp.PacketConn
|
conn net.PacketConn
|
||||||
icmp6 *icmp.PacketConn
|
icmp4 *icmp.PacketConn
|
||||||
ipv4PC *ipv4.PacketConn
|
icmp6 *icmp.PacketConn
|
||||||
ipv6PC *ipv6.PacketConn
|
ipv4PC *ipv4.PacketConn
|
||||||
ips map[netip.Addr]struct{}
|
ipv6PC *ipv6.PacketConn
|
||||||
rec map[string]record
|
ips map[netip.Addr]struct{}
|
||||||
readCh chan packet
|
rec map[string]record
|
||||||
closeCh chan struct{}
|
readCh chan packet
|
||||||
wg sync.WaitGroup
|
closedCh chan struct{}
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnServer(c *Config) (net.PacketConn, error) {
|
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||||
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -67,21 +67,21 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := &xicmpConnServer{
|
conn := &xicmpConnServer{
|
||||||
icmp4: icmp4,
|
conn: raw,
|
||||||
icmp6: icmp6,
|
icmp4: icmp4,
|
||||||
ipv4PC: icmp4.IPv4PacketConn(),
|
icmp6: icmp6,
|
||||||
ipv6PC: icmp6.IPv6PacketConn(),
|
ipv4PC: icmp4.IPv4PacketConn(),
|
||||||
ips: ips,
|
ipv6PC: icmp6.IPv6PacketConn(),
|
||||||
rec: make(map[string]record),
|
ips: ips,
|
||||||
readCh: make(chan packet),
|
rec: make(map[string]record),
|
||||||
closeCh: make(chan struct{}),
|
readCh: make(chan packet),
|
||||||
|
closedCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
common.Must(conn.ipv4PC.SetControlMessage(ipv4.FlagDst, true))
|
common.Must(conn.ipv4PC.SetControlMessage(ipv4.FlagDst, true))
|
||||||
common.Must(conn.ipv6PC.SetControlMessage(ipv6.FlagDst, true))
|
common.Must(conn.ipv6PC.SetControlMessage(ipv6.FlagDst, true))
|
||||||
|
|
||||||
go conn.clean()
|
go conn.clean()
|
||||||
conn.wg.Add(2)
|
|
||||||
go conn.recv4()
|
go conn.recv4()
|
||||||
go conn.recv6()
|
go conn.recv6()
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
|
|||||||
|
|
||||||
func (c *xicmpConnServer) closed() bool {
|
func (c *xicmpConnServer) closed() bool {
|
||||||
select {
|
select {
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -111,35 +111,33 @@ func (c *xicmpConnServer) clean() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) recv4() {
|
func (c *xicmpConnServer) recv4() {
|
||||||
defer c.wg.Done()
|
|
||||||
|
|
||||||
var b [finalmask.UDPSize]byte
|
var b [finalmask.UDPSize]byte
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if c.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n, cm, addr, err := c.ipv4PC.ReadFrom(b[:])
|
n, cm, addr, err := c.ipv4PC.ReadFrom(b[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var netErr net.Error
|
var netErr net.Error
|
||||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
||||||
select {
|
select {
|
||||||
case c.readCh <- packet{
|
case c.readCh <- packet{
|
||||||
err: err,
|
err: err,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
errors.LogErrorInner(context.Background(), err, "recv err 4")
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := icmp.ParseMessage(1, b[:n])
|
msg, err := icmp.ParseMessage(1, b[:n])
|
||||||
@@ -191,7 +189,7 @@ func (c *xicmpConnServer) recv4() {
|
|||||||
p: p,
|
p: p,
|
||||||
addr: cAddr,
|
addr: cAddr,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
pool.Put(p)
|
pool.Put(p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -199,28 +197,26 @@ func (c *xicmpConnServer) recv4() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) recv6() {
|
func (c *xicmpConnServer) recv6() {
|
||||||
defer c.wg.Done()
|
|
||||||
|
|
||||||
var b [finalmask.UDPSize]byte
|
var b [finalmask.UDPSize]byte
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if c.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n, cm, addr, err := c.ipv6PC.ReadFrom(b[:])
|
n, cm, addr, err := c.ipv6PC.ReadFrom(b[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var netErr net.Error
|
var netErr net.Error
|
||||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
||||||
select {
|
select {
|
||||||
case c.readCh <- packet{
|
case c.readCh <- packet{
|
||||||
err: err,
|
err: err,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
errors.LogErrorInner(context.Background(), err, "recv err 6")
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := icmp.ParseMessage(58, b[:n])
|
msg, err := icmp.ParseMessage(58, b[:n])
|
||||||
@@ -272,7 +268,7 @@ func (c *xicmpConnServer) recv6() {
|
|||||||
p: p,
|
p: p,
|
||||||
addr: cAddr,
|
addr: cAddr,
|
||||||
}:
|
}:
|
||||||
case <-c.closeCh:
|
case <-c.closedCh:
|
||||||
pool.Put(p)
|
pool.Put(p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -280,15 +276,16 @@ func (c *xicmpConnServer) recv6() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
packet, ok := <-c.readCh
|
select {
|
||||||
if ok {
|
case packet := <-c.readCh:
|
||||||
if packet.p != nil {
|
if packet.p != nil {
|
||||||
n = copy(p, packet.p)
|
n = copy(p, packet.p)
|
||||||
pool.Put(packet.p)
|
pool.Put(packet.p)
|
||||||
}
|
}
|
||||||
return n, packet.addr, packet.err
|
return n, packet.addr, packet.err
|
||||||
|
case <-c.closedCh:
|
||||||
|
return 0, nil, io.EOF
|
||||||
}
|
}
|
||||||
return 0, nil, io.EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
@@ -324,9 +321,10 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "send err")
|
errors.LogErrorInner(context.Background(), err, "xicmp write")
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,23 +334,15 @@ func (c *xicmpConnServer) Close() error {
|
|||||||
if c.closed() {
|
if c.closed() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
close(c.closeCh)
|
close(c.closedCh)
|
||||||
_ = c.icmp4.Close()
|
_ = c.icmp4.Close()
|
||||||
_ = c.icmp6.Close()
|
_ = c.icmp6.Close()
|
||||||
c.wg.Wait()
|
_ = c.conn.Close()
|
||||||
select {
|
|
||||||
case p := <-c.readCh:
|
|
||||||
if p.p != nil {
|
|
||||||
pool.Put(p.p)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
close(c.readCh)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) LocalAddr() net.Addr {
|
func (c *xicmpConnServer) LocalAddr() net.Addr {
|
||||||
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
|
return c.conn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
|
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package xmc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
|
func (c *Config) TCP() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) WrapConnClient(conn net.Conn) (net.Conn, error) {
|
||||||
profiles, err := profilesFromConfig(c.Profiles)
|
profiles, err := profilesFromConfig(c.Profiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("minecraft finalmask: %w", err)
|
return nil, fmt.Errorf("minecraft finalmask: %w", err)
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
|
|||||||
}
|
}
|
||||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||||
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
||||||
|
sockopt := streamSettings.SocketSettings
|
||||||
grpcSettings := streamSettings.ProtocolSettings.(*Config)
|
grpcSettings := streamSettings.ProtocolSettings.(*Config)
|
||||||
|
|
||||||
if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found && client.GetState() != connectivity.Shutdown {
|
if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found && client.GetState() != connectivity.Shutdown {
|
||||||
@@ -123,13 +124,17 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
|
|||||||
gctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx))
|
gctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx))
|
||||||
gctx = session.ContextWithTimeoutOnly(gctx, true)
|
gctx = session.ContextWithTimeoutOnly(gctx, true)
|
||||||
|
|
||||||
var c net.Conn
|
c, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt)
|
||||||
if streamSettings.FinalMask != nil {
|
|
||||||
c, err = streamSettings.FinalMask.DialTCP(gctx, net.TCPDestination(address, port))
|
|
||||||
} else {
|
|
||||||
c, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if streamSettings.TcpmaskManager != nil {
|
||||||
|
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(c)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, errors.New("mask err").Base(err)
|
||||||
|
}
|
||||||
|
c = newConn
|
||||||
|
}
|
||||||
|
|
||||||
if tlsConfig != nil {
|
if tlsConfig != nil {
|
||||||
config := tlsConfig.GetTLSConfig(tls.WithDestination(dest))
|
config := tlsConfig.GetTLSConfig(tls.WithDestination(dest))
|
||||||
if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {
|
if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {
|
||||||
|
|||||||
@@ -104,20 +104,28 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i
|
|||||||
go func() {
|
go func() {
|
||||||
var streamListener net.Listener
|
var streamListener net.Listener
|
||||||
var err error
|
var err error
|
||||||
var addr net.Addr
|
|
||||||
if port == net.Port(0) { // unix
|
if port == net.Port(0) { // unix
|
||||||
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
|
streamListener, err = internet.ListenSystem(ctx, &net.UnixAddr{
|
||||||
|
Name: address.Domain(),
|
||||||
|
Net: "unix",
|
||||||
|
}, settings.SocketSettings)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to listen on ", address)
|
||||||
|
return
|
||||||
|
}
|
||||||
} else { // tcp
|
} else { // tcp
|
||||||
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
|
streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{
|
||||||
|
IP: address.IP(),
|
||||||
|
Port: int(port),
|
||||||
|
}, settings.SocketSettings)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to listen on ", address, ":", port)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if settings.FinalMask != nil {
|
|
||||||
streamListener, err = settings.FinalMask.Listen(ctx, addr)
|
if settings.TcpmaskManager != nil {
|
||||||
} else {
|
streamListener, _ = settings.TcpmaskManager.WrapListener(streamListener)
|
||||||
streamListener, err = internet.ListenSystem(ctx, addr, settings.SocketSettings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(ctx, err, "failed to listen on ", address, ":", port)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errors.LogDebug(ctx, "gRPC listen for service name `"+grpcSettings.getServiceName()+"` tun `"+grpcSettings.getTunStreamName()+"` multi tun `"+grpcSettings.getTunMultiStreamName()+"`")
|
errors.LogDebug(ctx, "gRPC listen for service name `"+grpcSettings.getServiceName()+"` tun `"+grpcSettings.getTunStreamName()+"` multi tun `"+grpcSettings.getTunMultiStreamName()+"`")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user