Compare commits

..

1 Commits

Author SHA1 Message Date
Fangliding 2f16233259 upd reality 2026-05-01 17:50:53 +08:00
77 changed files with 1613 additions and 2385 deletions
-5
View File
@@ -111,8 +111,6 @@
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
- [OneXray](https://github.com/OneXray/OneXray)
- [XrayUI-dev](https://github.com/PhoenixNil/XrayUI-dev)
- Android
- [v2rayNG](https://github.com/2dust/v2rayNG)
- [X-flutter](https://github.com/XTLS/X-flutter)
@@ -121,7 +119,6 @@
- [XrayFA](https://github.com/Q7DF1/XrayFA)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
- [OneXray](https://github.com/OneXray/OneXray)
- iOS & macOS arm64 & tvOS
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
@@ -146,12 +143,10 @@
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [v2rayN](https://github.com/2dust/v2rayN)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
- [OneXray](https://github.com/OneXray/OneXray)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
- iOS & macOS arm64 & tvOS
- [Anywhere](https://github.com/NodePassProject/Anywhere)
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
- [Egern](https://apps.apple.com/us/app/egern/id1616105820)
+4 -19
View File
@@ -4,14 +4,12 @@ import (
"context"
"net"
"sync"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/signal/done"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/transport/internet"
"google.golang.org/grpc"
)
@@ -75,27 +73,14 @@ func (c *Commander) Start() error {
}
}
if len(c.listen) > 0 {
var addr net.Addr
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
} else {
tcpAddr, err := net.ResolveTCPAddr("tcp", c.listen)
if err != nil {
errors.LogErrorInner(context.Background(), err, "API server failed to parse listen address ", c.listen)
return err
}
addr = tcpAddr
}
l, err := internet.ListenSystem(context.Background(), addr, nil)
if err != nil {
if l, err := net.Listen("tcp", c.listen); err != nil {
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
return err
} else {
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
go listen(l)
}
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
go listen(l)
return nil
}
+8 -8
View File
@@ -148,7 +148,7 @@ func TestUDPServerSubnet(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -210,7 +210,7 @@ func TestUDPServer(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -350,7 +350,7 @@ func TestPrioritizedDomain(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -421,7 +421,7 @@ func TestUDPServerIPv6(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -490,7 +490,7 @@ func TestStaticHostDomain(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -577,7 +577,7 @@ func TestIPMatch(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -696,7 +696,7 @@ func TestLocalDomain(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -953,7 +953,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+12 -8
View File
@@ -17,7 +17,7 @@ import (
type Holder struct {
domainToIP cache.Lru
ipRange *net.IPNet
mu sync.Mutex
mu *sync.Mutex
config *FakeDnsPool
}
@@ -49,7 +49,9 @@ func (fkdns *Holder) Start() error {
}
func (fkdns *Holder) Close() error {
// nothing to do for now, just wait GC
fkdns.domainToIP = nil
fkdns.ipRange = nil
fkdns.mu = nil
return nil
}
@@ -68,7 +70,7 @@ func NewFakeDNSHolder() (*Holder, error) {
}
func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {
return &Holder{config: conf}, nil
return &Holder{nil, nil, nil, conf}, nil
}
func (fkdns *Holder) initializeFromConfig() error {
@@ -90,6 +92,7 @@ func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
}
fkdns.domainToIP = cache.NewLru(lruSize)
fkdns.ipRange = ipRange
fkdns.mu = new(sync.Mutex)
return nil
}
@@ -100,7 +103,7 @@ func (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address {
if v, ok := fkdns.domainToIP.Get(domain); ok {
return []net.Address{v.(net.Address)}
}
currentTimeMillis := uint64(time.Now().UnixMilli())
currentTimeMillis := uint64(time.Now().UnixNano() / 1e6)
ones, bits := fkdns.ipRange.Mask.Size()
rooms := bits - ones
if rooms < 64 {
@@ -199,11 +202,12 @@ func (h *HolderMulti) Start() error {
}
func (h *HolderMulti) Close() error {
var errs []error
for _, v := range h.holders {
errs = append(errs, v.Close())
if err := v.Close(); err != nil {
return errors.New("Cannot close all fake dns pools").Base(err)
}
}
return errors.Combine(errs...)
return nil
}
func (h *HolderMulti) createHolderGroups() error {
@@ -218,7 +222,7 @@ func (h *HolderMulti) createHolderGroups() error {
}
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
holderMulti := &HolderMulti{config: conf}
holderMulti := &HolderMulti{nil, conf}
if err := holderMulti.createHolderGroups(); err != nil {
return nil, err
}
+6 -4
View File
@@ -18,9 +18,9 @@ import (
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy"
hysteria_proxy "github.com/xtls/xray-core/proxy/hysteria"
"github.com/xtls/xray-core/proxy/hysteria/account"
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tcp"
"github.com/xtls/xray-core/transport/internet/udp"
@@ -134,8 +134,10 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
func (w *tcpWorker) Start() error {
ctx := context.Background()
if v, ok := w.proxy.(*hysteria_proxy.Server); ok {
ctx = hysteria.ContextWithValidator(ctx, v.HysteriaInboundValidator())
type HysteriaInboundValidator interface{ HysteriaInboundValidator() *account.Validator }
if v, ok := w.proxy.(HysteriaInboundValidator); ok {
ctx = hyCtx.ContextWithRequireDatagram(ctx, true)
ctx = hyCtx.ContextWithValidator(ctx, v.HysteriaInboundValidator())
}
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
-14
View File
@@ -267,20 +267,6 @@ func (h *Handler) DestIpAddress() net.IP {
return internet.DestIpAddress()
}
func (h *Handler) SocketSettings() *internet.SocketConfig {
if h.streamSettings == nil {
return nil
}
return h.streamSettings.SocketSettings
}
func (h *Handler) UsesProxySettings() bool {
if h.senderSettings != nil && h.senderSettings.ProxySettings.HasTag() {
return true
}
return h.streamSettings != nil && h.streamSettings.SocketSettings != nil && len(h.streamSettings.SocketSettings.DialerProxy) > 0
}
// Dial implements internet.Dialer.
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
if h.senderSettings != nil {
+3 -3
View File
@@ -48,7 +48,7 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
Tag: "tag",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
})
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
_, ok := conn.(*stat.CounterConnection)
@@ -78,7 +78,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
Tag: "tag",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
})
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
_, ok := conn.(*stat.CounterConnection)
@@ -118,7 +118,7 @@ func TestTagsCache(t *testing.T) {
tag := fmt.Sprintf("%s%d", tags_prefix, idx)
cfg := &core.OutboundHandlerConfig{
Tag: tag,
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
}
if h, err := NewHandler(ctx, cfg); err == nil {
if err := ohm.AddHandler(ctx, h); err == nil {
-1
View File
@@ -17,7 +17,6 @@ const (
UseFreedomSplice = "xray.buf.splice"
UseVmessPadding = "xray.vmess.padding"
UseCone = "xray.cone.disabled"
UseStrictJSON = "xray.json.strict"
BufferSize = "xray.ray.buffer.size"
BrowserDialerAddress = "xray.browser.dialer"
-43
View File
@@ -1,43 +0,0 @@
package task
import (
"runtime"
"golang.org/x/sync/errgroup"
)
// ParallelForN runs fn(0..n-1) in parallel across runtime.GOMAXPROCS(0) worker
// goroutines. Indices are partitioned into contiguous chunks so the number of
// spawned goroutines stays bounded regardless of n.
//
// fn must be safe to call concurrently from different goroutines (each call
// receives its own unique index). Output collected by writing to indexed slots
// in a pre-allocated slice is a common safe pattern.
//
// Returns the first non-nil error reported by fn; other workers may still be
// finishing briefly afterwards.
func ParallelForN(n int, fn func(i int) error) error {
if n <= 0 {
return nil
}
workers := max(runtime.GOMAXPROCS(0), 1)
workers = min(workers, n)
chunk := (n + workers - 1) / workers
var eg errgroup.Group
for w := range workers {
start := w * chunk
end := min(start+chunk, n)
if start >= end {
break
}
eg.Go(func() error {
for i := start; i < end; i++ {
if err := fn(i); err != nil {
return err
}
}
return nil
})
}
return eg.Wait()
}
-50
View File
@@ -1,50 +0,0 @@
package task_test
import (
"errors"
"sync/atomic"
"testing"
"github.com/xtls/xray-core/common"
. "github.com/xtls/xray-core/common/task"
)
func TestParallelForN_Empty(t *testing.T) {
called := false
err := ParallelForN(0, func(i int) error {
called = true
return nil
})
common.Must(err)
if called {
t.Fatal("fn should not be called when n=0")
}
}
func TestParallelForN_AllIndicesCovered(t *testing.T) {
const N = 10000
var seen [N]int32
err := ParallelForN(N, func(i int) error {
atomic.AddInt32(&seen[i], 1)
return nil
})
common.Must(err)
for i := 0; i < N; i++ {
if seen[i] != 1 {
t.Fatalf("index %d called %d times, expected 1", i, seen[i])
}
}
}
func TestParallelForN_Error(t *testing.T) {
boom := errors.New("boom")
err := ParallelForN(1000, func(i int) error {
if i == 42 {
return boom
}
return nil
})
if err != boom {
t.Fatalf("expected %v, got %v", boom, err)
}
}
+3 -3
View File
@@ -53,7 +53,7 @@ func TestXrayDial(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -105,7 +105,7 @@ func TestXrayDialUDPConn(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -174,7 +174,7 @@ func TestXrayDialUDP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+3 -3
View File
@@ -3,7 +3,7 @@ module github.com/xtls/xray-core
go 1.26
require (
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6
github.com/cloudflare/circl v1.6.3
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1
@@ -14,12 +14,12 @@ require (
github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.12.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/robfig/cron/v3 v3.0.1
github.com/robfig/cron/v3 v3.0.0
github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/stretchr/testify v1.11.1
github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
github.com/xtls/reality v0.0.0-20260501094811-4379845b089d
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.50.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
+6 -4
View File
@@ -1,7 +1,7 @@
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkdYbw5JPGwBqIHs2J8tiEP7Py9lPqkN2I=
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6 h1:cbF95uMsQwCwAzH2i8+2lNO2TReoELLuqeeMfyBjFbY=
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
@@ -53,8 +53,8 @@ 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/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/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.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
@@ -69,6 +69,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/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/xtls/reality v0.0.0-20260501094811-4379845b089d h1:ca0n8upCDojatNr25id4npJBwUsMmLgtrvLYDj4J0Hg=
github.com/xtls/reality v0.0.0-20260501094811-4379845b089d/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+1 -1
View File
@@ -84,7 +84,7 @@ func (c *DNSOutboundConfig) Build() (proto.Message, error) {
if c.Rules != nil {
return nil, errors.New("legacy nonIPQuery and blockTypes cannot be mixed with rules")
}
errors.PrintDeprecatedFeatureWarning(`"nonIPQuery" and "blockTypes"`, `"rules"`)
errors.PrintDeprecatedFeatureWarning(`"nonIPQuery" and "blockTypes" in DNS outbound`, `"rules"`)
rules, err := c.buildLegacyDNSPolicy()
if err != nil {
return nil, err
+15 -72
View File
@@ -1,7 +1,6 @@
package conf
import (
"context"
"encoding/base64"
"encoding/hex"
"net"
@@ -9,7 +8,7 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata"
xnet "github.com/xtls/xray-core/common/net"
v2net "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/freedom"
"github.com/xtls/xray-core/transport/internet"
@@ -17,16 +16,15 @@ import (
)
type FreedomConfig struct {
TargetStrategy string `json:"targetStrategy"`
DomainStrategy string `json:"domainStrategy"`
Redirect string `json:"redirect"`
UserLevel uint32 `json:"userLevel"`
Fragment *Fragment `json:"fragment"`
Noise *Noise `json:"noise"`
Noises []*Noise `json:"noises"`
ProxyProtocol uint32 `json:"proxyProtocol"`
IPsBlocked *StringList `json:"ipsBlocked"`
FinalRules []*FreedomFinalRuleConfig `json:"finalRules"`
TargetStrategy string `json:"targetStrategy"`
DomainStrategy string `json:"domainStrategy"`
Redirect string `json:"redirect"`
UserLevel uint32 `json:"userLevel"`
Fragment *Fragment `json:"fragment"`
Noise *Noise `json:"noise"`
Noises []*Noise `json:"noises"`
ProxyProtocol uint32 `json:"proxyProtocol"`
IPsBlocked *StringList `json:"ipsBlocked"`
}
type Fragment struct {
@@ -43,21 +41,8 @@ type Noise struct {
ApplyTo string `json:"applyTo"`
}
type FreedomFinalRuleConfig struct {
Action string `json:"action"`
Network *NetworkList `json:"network"`
Port *PortList `json:"port"`
IP *StringList `json:"ip"`
BlockDelay *Int32Range `json:"blockDelay"`
}
// Build implements Buildable
func (c *FreedomConfig) Build() (proto.Message, error) {
if c.IPsBlocked != nil {
// todo: remove legacy
errors.LogWarning(context.Background(), `The feature "ipsBlocked" has been removed and migrated to "finalRules". Please update your config(s) according to release note and documentation.`)
}
config := new(freedom.Config)
targetStrategy := c.TargetStrategy
if targetStrategy == "" {
@@ -157,13 +142,12 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
}
config.UserLevel = c.UserLevel
if len(c.Redirect) > 0 {
host, portStr, err := net.SplitHostPort(c.Redirect)
if err != nil {
return nil, errors.New("invalid redirect address: ", c.Redirect, ": ", err).Base(err)
}
port, err := xnet.PortFromString(portStr)
port, err := v2net.PortFromString(portStr)
if err != nil {
return nil, errors.New("invalid redirect port: ", c.Redirect, ": ", err).Base(err)
}
@@ -174,22 +158,19 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
}
if len(host) > 0 {
config.DestinationOverride.Server.Address = xnet.NewIPOrDomain(xnet.ParseAddress(host))
config.DestinationOverride.Server.Address = v2net.NewIPOrDomain(v2net.ParseAddress(host))
}
}
if c.ProxyProtocol > 0 && c.ProxyProtocol <= 2 {
config.ProxyProtocol = c.ProxyProtocol
}
for _, r := range c.FinalRules {
rule, err := r.Build()
if c.IPsBlocked != nil {
rules, err := geodata.ParseIPRules(*c.IPsBlocked)
if err != nil {
return nil, err
}
config.FinalRules = append(config.FinalRules, rule)
config.IpsBlocked = &freedom.IPRules{Rules: rules}
}
return config, nil
}
@@ -248,41 +229,3 @@ func ParseNoise(noise *Noise) (*freedom.Noise, error) {
}
return NConfig, nil
}
func (c *FreedomFinalRuleConfig) Build() (*freedom.FinalRuleConfig, error) {
rule := &freedom.FinalRuleConfig{}
switch strings.ToLower(c.Action) {
case "allow":
rule.Action = freedom.RuleAction_Allow
case "block":
rule.Action = freedom.RuleAction_Block
default:
return nil, errors.New("unknown action: ", c.Action)
}
if c.Network != nil {
rule.Networks = c.Network.Build()
}
if c.Port != nil {
rule.PortList = c.Port.Build()
}
if c.IP != nil {
rules, err := geodata.ParseIPRules(*c.IP)
if err != nil {
return nil, err
}
rule.Ip = rules
}
if c.BlockDelay != nil {
rule.BlockDelay = &freedom.Range{
Min: uint64(c.BlockDelay.From),
Max: uint64(c.BlockDelay.To),
}
}
return rule, nil
}
-60
View File
@@ -3,7 +3,6 @@ package conf_test
import (
"testing"
"github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
. "github.com/xtls/xray-core/infra/conf"
@@ -39,64 +38,5 @@ func TestFreedomConfig(t *testing.T) {
UserLevel: 1,
},
},
{
Input: `{
"finalRules": [{
"action": "block",
"network": "tcp,udp",
"port": "53,443",
"ip": ["10.0.0.0/8", "2001:db8::/32"],
"blockDelay": "30-60"
}, {
"action": "allow",
"network": ["udp"]
}]
}`,
Parser: loadJSON(creator),
Output: &freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{
{
Action: freedom.RuleAction_Block,
Networks: []net.Network{net.Network_TCP, net.Network_UDP},
PortList: &net.PortList{
Range: []*net.PortRange{
{From: 53, To: 53},
{From: 443, To: 443},
},
},
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{
Ip: []byte{10, 0, 0, 0},
Prefix: 8,
},
},
},
},
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{
Ip: net.ParseAddress("2001:db8::").IP(),
Prefix: 32,
},
},
},
},
},
BlockDelay: &freedom.Range{
Min: 30,
Max: 60,
},
},
{
Action: freedom.RuleAction_Allow,
Networks: []net.Network{net.Network_UDP},
},
},
},
},
})
}
+6 -13
View File
@@ -4,7 +4,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/proxy/hysteria"
"github.com/xtls/xray-core/proxy/hysteria/account"
"google.golang.org/protobuf/proto"
@@ -45,22 +44,16 @@ type HysteriaServerConfig struct {
func (c *HysteriaServerConfig) Build() (proto.Message, error) {
config := new(hysteria.ServerConfig)
if len(c.Users) > 0 {
config.Users = make([]*protocol.User, len(c.Users))
processUser := func(idx int) error {
user := c.Users[idx]
acc := &account.Account{
if c.Users != nil {
for _, user := range c.Users {
account := &account.Account{
Auth: user.Auth,
}
config.Users[idx] = &protocol.User{
config.Users = append(config.Users, &protocol.User{
Email: user.Email,
Level: user.Level,
Account: serial.ToTypedMessage(acc),
}
return nil
}
if err := task.ParallelForN(len(c.Users), processUser); err != nil {
return nil, err
Account: serial.ToTypedMessage(account),
})
}
}
+1 -15
View File
@@ -5,22 +5,12 @@ import (
"io"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
creflect "github.com/xtls/xray-core/common/reflect"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/main/confloader"
)
// UseStrictJSON, when true, makes JSON config decoders skip the custom
// comment-stripping reader and parse input as strict RFC 8259 JSON.
//
// Enabled by setting the env variable xray.json.strict=true (or its normalized
// form XRAY_JSON_STRICT=true). Default false preserves backward-compatible
// behavior for human-edited configs that may contain comments or other
// JSON5/JSONC syntax.
var UseStrictJSON = platform.NewEnvFlag(platform.UseStrictJSON).GetValue(func() string { return "" }) == "true"
func MergeConfigFromFiles(files []*core.ConfigSource) (string, error) {
c, err := mergeConfigs(files)
if err != nil {
@@ -41,11 +31,7 @@ func mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) {
if err != nil {
return nil, errors.New("failed to read config: ", file).Base(err)
}
decoder := ReaderDecoderByFormat[file.Format]
if file.Format == "json" && UseStrictJSON {
decoder = DecodeJSONConfigStrict
}
c, err := decoder(r)
c, err := ReaderDecoderByFormat[file.Format](r)
if err != nil {
return nil, errors.New("failed to decode config: ", file).Base(err)
}
-21
View File
@@ -42,9 +42,6 @@ func findOffset(b []byte, o int) *offset {
// DecodeJSONConfig reads from reader and decode the config into *conf.Config
// syntax error could be detected.
//
// Permissive: accepts JSON with Java/Python-style comments via json_reader.Reader.
// Used for local files and stdin where the config is human-edited.
func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
jsonConfig := &conf.Config{}
@@ -72,24 +69,6 @@ func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
return jsonConfig, nil
}
// DecodeJSONConfigStrict reads standard RFC 8259 JSON without comment-stripping.
// Used for remote sources (http/https/http+unix) where the payload is produced by
// automated systems and cannot contain JSON5/JSONC extensions. Avoids the
// byte-by-byte comment stripper and TeeReader, which are significant overhead on
// large configs.
func DecodeJSONConfigStrict(reader io.Reader) (*conf.Config, error) {
data, err := io.ReadAll(reader)
if err != nil {
return nil, errors.New("failed to read config file").Base(err)
}
jsonConfig := &conf.Config{}
if err := json.Unmarshal(data, jsonConfig); err != nil {
return nil, errors.New("failed to parse remote JSON config").Base(err)
}
return jsonConfig, nil
}
func LoadJSONConfig(reader io.Reader) (*core.Config, error) {
jsonConfig, err := DecodeJSONConfig(reader)
if err != nil {
+19 -34
View File
@@ -8,7 +8,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/proxy/shadowsocks"
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
"google.golang.org/protobuf/proto"
@@ -60,31 +59,23 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
config.Network = v.NetworkList.Build()
if v.Users != nil {
if len(v.Users) > 0 {
config.Users = make([]*protocol.User, len(v.Users))
processUser := func(idx int) error {
user := v.Users[idx]
account := &shadowsocks.Account{
Password: user.Password,
CipherType: cipherFromString(user.Cipher),
}
if account.Password == "" {
return errors.New("Shadowsocks password is not specified.")
}
if account.CipherType < shadowsocks.CipherType_AES_128_GCM ||
account.CipherType > shadowsocks.CipherType_XCHACHA20_POLY1305 {
return errors.New("unsupported cipher method: ", user.Cipher)
}
config.Users[idx] = &protocol.User{
Email: user.Email,
Level: uint32(user.Level),
Account: serial.ToTypedMessage(account),
}
return nil
for _, user := range v.Users {
account := &shadowsocks.Account{
Password: user.Password,
CipherType: cipherFromString(user.Cipher),
}
if err := task.ParallelForN(len(v.Users), processUser); err != nil {
return nil, err
if account.Password == "" {
return nil, errors.New("Shadowsocks password is not specified.")
}
if account.CipherType < shadowsocks.CipherType_AES_128_GCM ||
account.CipherType > shadowsocks.CipherType_XCHACHA20_POLY1305 {
return nil, errors.New("unsupported cipher method: ", user.Cipher)
}
config.Users = append(config.Users, &protocol.User{
Email: user.Email,
Level: uint32(user.Level),
Account: serial.ToTypedMessage(account),
})
}
} else {
account := &shadowsocks.Account{
@@ -130,24 +121,18 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
config.Key = v.Password
config.Network = v.NetworkList.Build()
config.Users = make([]*protocol.User, len(v.Users))
processUser := func(idx int) error {
user := v.Users[idx]
for _, user := range v.Users {
if user.Cipher != "" {
return errors.New("shadowsocks 2022 (multi-user): users must have empty method")
return nil, errors.New("shadowsocks 2022 (multi-user): users must have empty method")
}
account := &shadowsocks_2022.Account{
Key: user.Password,
}
config.Users[idx] = &protocol.User{
config.Users = append(config.Users, &protocol.User{
Email: user.Email,
Level: uint32(user.Level),
Account: serial.ToTypedMessage(account),
}
return nil
}
if err := task.ParallelForN(len(v.Users), processUser); err != nil {
return nil, err
})
}
return config, nil
}
+2 -8
View File
@@ -12,7 +12,6 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/proxy/trojan"
"google.golang.org/protobuf/proto"
)
@@ -124,10 +123,9 @@ func (c *TrojanServerConfig) Build() (proto.Message, error) {
Users: make([]*protocol.User, len(c.Clients)),
}
processClient := func(idx int) error {
rawUser := c.Clients[idx]
for idx, rawUser := range c.Clients {
if rawUser.Flow != "" {
return errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)
return nil, errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)
}
config.Users[idx] = &protocol.User{
@@ -137,10 +135,6 @@ func (c *TrojanServerConfig) Build() (proto.Message, error) {
Password: rawUser.Password,
}),
}
return nil
}
if err := task.ParallelForN(len(c.Clients), processClient); err != nil {
return nil, err
}
for _, fb := range c.Fallbacks {
+8 -15
View File
@@ -13,7 +13,6 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/inbound"
@@ -47,20 +46,19 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
default:
return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`)
}
processClient := func(idx int) error {
rawUser := c.Clients[idx]
for idx, rawUser := range c.Clients {
user := new(protocol.User)
if err := json.Unmarshal(rawUser, user); err != nil {
return errors.New(`VLESS clients: invalid user`).Base(err)
return nil, errors.New(`VLESS clients: invalid user`).Base(err)
}
account := new(vless.Account)
if err := json.Unmarshal(rawUser, account); err != nil {
return errors.New(`VLESS clients: invalid user`).Base(err)
return nil, errors.New(`VLESS clients: invalid user`).Base(err)
}
u, err := uuid.ParseString(account.Id)
if err != nil {
return err
return nil, err
}
account.Id = u.String()
@@ -69,7 +67,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
account.Flow = c.Flow
case vless.XRV:
default:
return errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
}
if len(account.Testseed) < 4 {
@@ -77,25 +75,20 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
}
if account.Encryption != "" {
return errors.New(`VLESS clients: "encryption" should not be in inbound settings`)
return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`)
}
if account.Reverse != nil {
if account.Reverse.Tag == "" {
return errors.New(`VLESS clients: "tag" can't be empty for "reverse"`)
return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`)
}
if account.Reverse.Sniffing != nil { // may not be reached: error json unmarshal
return errors.New(`VLESS clients: inbound's "reverse" can't have "sniffing"`)
return nil, errors.New(`VLESS clients: inbound's "reverse" can't have "sniffing"`)
}
}
user.Account = serial.ToTypedMessage(account)
config.Clients[idx] = user
return nil
}
if err := task.ParallelForN(len(c.Clients), processClient); err != nil {
return nil, err
}
config.Decryption = c.Decryption
+4 -10
View File
@@ -7,7 +7,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/proxy/vmess"
"github.com/xtls/xray-core/proxy/vmess/inbound"
@@ -74,29 +73,24 @@ func (c *VMessInboundConfig) Build() (proto.Message, error) {
}
config.User = make([]*protocol.User, len(c.Users))
processUser := func(idx int) error {
rawData := c.Users[idx]
for idx, rawData := range c.Users {
user := new(protocol.User)
if err := json.Unmarshal(rawData, user); err != nil {
return errors.New("invalid VMess user").Base(err)
return nil, errors.New("invalid VMess user").Base(err)
}
account := new(VMessAccount)
if err := json.Unmarshal(rawData, account); err != nil {
return errors.New("invalid VMess user").Base(err)
return nil, errors.New("invalid VMess user").Base(err)
}
u, err := uuid.ParseString(account.ID)
if err != nil {
return err
return nil, err
}
account.ID = u.String()
user.Account = serial.ToTypedMessage(account.Build())
config.User[idx] = user
return nil
}
if err := task.ParallelForN(len(c.Users), processUser); err != nil {
return nil, err
}
return config, nil
-7
View File
@@ -41,13 +41,6 @@ func init() {
}
return cf.Build()
case io.Reader:
if serial.UseStrictJSON {
cfg, err := serial.DecodeJSONConfigStrict(v)
if err != nil {
return nil, err
}
return cfg.Build()
}
return serial.LoadJSONConfig(v)
default:
return nil, errors.New("unknown type")
+1 -15
View File
@@ -6,11 +6,7 @@ import (
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
)
@@ -42,17 +38,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
// Sleep a little here to make sure the response is sent to client.
time.Sleep(time.Second)
}
defer common.Interrupt(link.Writer)
defer common.Interrupt(link.Reader)
// wait to drain all the possible incoming UDP data
if ob.Target.Network == net.Network_UDP {
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, func() {
cancel()
}, time.Duration(30+dice.Roll(61))*time.Second)
go buf.Copy(link.Reader, buf.Discard, buf.UpdateActivity(timer))
<-ctx.Done()
}
common.Interrupt(link.Writer)
return nil
}
+1 -1
View File
@@ -95,7 +95,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
}
}
}
if dest.Port == 0 && port != "" {
if dest.Port == 0 {
dest.Port = net.Port(common.Must2(strconv.Atoi(port)))
}
if d.portMap != nil && d.portMap[port] != "" {
+1
View File
@@ -0,0 +1 @@
package freedom
+49 -200
View File
@@ -8,7 +8,6 @@ package freedom
import (
geodata "github.com/xtls/xray-core/common/geodata"
net "github.com/xtls/xray-core/common/net"
protocol "github.com/xtls/xray-core/common/protocol"
internet "github.com/xtls/xray-core/transport/internet"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
@@ -25,52 +24,6 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type RuleAction int32
const (
RuleAction_Allow RuleAction = 0
RuleAction_Block RuleAction = 1
)
// Enum value maps for RuleAction.
var (
RuleAction_name = map[int32]string{
0: "Allow",
1: "Block",
}
RuleAction_value = map[string]int32{
"Allow": 0,
"Block": 1,
}
)
func (x RuleAction) Enum() *RuleAction {
p := new(RuleAction)
*p = x
return p
}
func (x RuleAction) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (RuleAction) Descriptor() protoreflect.EnumDescriptor {
return file_proxy_freedom_config_proto_enumTypes[0].Descriptor()
}
func (RuleAction) Type() protoreflect.EnumType {
return &file_proxy_freedom_config_proto_enumTypes[0]
}
func (x RuleAction) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use RuleAction.Descriptor instead.
func (RuleAction) EnumDescriptor() ([]byte, []int) {
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{0}
}
type DestinationOverride struct {
state protoimpl.MessageState `protogen:"open.v1"`
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
@@ -299,28 +252,27 @@ func (x *Noise) GetApplyTo() string {
return ""
}
type Range struct {
type IPRules struct {
state protoimpl.MessageState `protogen:"open.v1"`
Min uint64 `protobuf:"varint,1,opt,name=min,proto3" json:"min,omitempty"`
Max uint64 `protobuf:"varint,2,opt,name=max,proto3" json:"max,omitempty"`
Rules []*geodata.IPRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Range) Reset() {
*x = Range{}
func (x *IPRules) Reset() {
*x = IPRules{}
mi := &file_proxy_freedom_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Range) String() string {
func (x *IPRules) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Range) ProtoMessage() {}
func (*IPRules) ProtoMessage() {}
func (x *Range) ProtoReflect() protoreflect.Message {
func (x *IPRules) ProtoReflect() protoreflect.Message {
mi := &file_proxy_freedom_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -332,97 +284,14 @@ func (x *Range) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use Range.ProtoReflect.Descriptor instead.
func (*Range) Descriptor() ([]byte, []int) {
// Deprecated: Use IPRules.ProtoReflect.Descriptor instead.
func (*IPRules) Descriptor() ([]byte, []int) {
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{3}
}
func (x *Range) GetMin() uint64 {
func (x *IPRules) GetRules() []*geodata.IPRule {
if x != nil {
return x.Min
}
return 0
}
func (x *Range) GetMax() uint64 {
if x != nil {
return x.Max
}
return 0
}
type FinalRuleConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.freedom.RuleAction" json:"action,omitempty"`
Networks []net.Network `protobuf:"varint,2,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"`
PortList *net.PortList `protobuf:"bytes,3,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"`
BlockDelay *Range `protobuf:"bytes,5,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *FinalRuleConfig) Reset() {
*x = FinalRuleConfig{}
mi := &file_proxy_freedom_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FinalRuleConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FinalRuleConfig) ProtoMessage() {}
func (x *FinalRuleConfig) ProtoReflect() protoreflect.Message {
mi := &file_proxy_freedom_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FinalRuleConfig.ProtoReflect.Descriptor instead.
func (*FinalRuleConfig) Descriptor() ([]byte, []int) {
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4}
}
func (x *FinalRuleConfig) GetAction() RuleAction {
if x != nil {
return x.Action
}
return RuleAction_Allow
}
func (x *FinalRuleConfig) GetNetworks() []net.Network {
if x != nil {
return x.Networks
}
return nil
}
func (x *FinalRuleConfig) GetPortList() *net.PortList {
if x != nil {
return x.PortList
}
return nil
}
func (x *FinalRuleConfig) GetIp() []*geodata.IPRule {
if x != nil {
return x.Ip
}
return nil
}
func (x *FinalRuleConfig) GetBlockDelay() *Range {
if x != nil {
return x.BlockDelay
return x.Rules
}
return nil
}
@@ -435,14 +304,14 @@ type Config struct {
Fragment *Fragment `protobuf:"bytes,5,opt,name=fragment,proto3" json:"fragment,omitempty"`
ProxyProtocol uint32 `protobuf:"varint,6,opt,name=proxy_protocol,json=proxyProtocol,proto3" json:"proxy_protocol,omitempty"`
Noises []*Noise `protobuf:"bytes,7,rep,name=noises,proto3" json:"noises,omitempty"`
FinalRules []*FinalRuleConfig `protobuf:"bytes,8,rep,name=final_rules,json=finalRules,proto3" json:"final_rules,omitempty"`
IpsBlocked *IPRules `protobuf:"bytes,8,opt,name=ips_blocked,json=ipsBlocked,proto3,oneof" json:"ips_blocked,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_proxy_freedom_config_proto_msgTypes[5]
mi := &file_proxy_freedom_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -454,7 +323,7 @@ func (x *Config) String() string {
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_freedom_config_proto_msgTypes[5]
mi := &file_proxy_freedom_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -467,7 +336,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{5}
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4}
}
func (x *Config) GetDomainStrategy() internet.DomainStrategy {
@@ -512,9 +381,9 @@ func (x *Config) GetNoises() []*Noise {
return nil
}
func (x *Config) GetFinalRules() []*FinalRuleConfig {
func (x *Config) GetIpsBlocked() *IPRules {
if x != nil {
return x.FinalRules
return x.IpsBlocked
}
return nil
}
@@ -523,7 +392,7 @@ var File_proxy_freedom_config_proto protoreflect.FileDescriptor
const file_proxy_freedom_config_proto_rawDesc = "" +
"\n" +
"\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"S\n" +
"\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x1bcommon/geodata/geodat.proto\"S\n" +
"\x13DestinationOverride\x12<\n" +
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" +
"\bFragment\x12!\n" +
@@ -546,17 +415,9 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
"\tdelay_min\x18\x03 \x01(\x04R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x04 \x01(\x04R\bdelayMax\x12\x16\n" +
"\x06packet\x18\x05 \x01(\fR\x06packet\x12\x19\n" +
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" +
"\x05Range\x12\x10\n" +
"\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" +
"\x03max\x18\x02 \x01(\x04R\x03max\"\xa0\x02\n" +
"\x0fFinalRuleConfig\x126\n" +
"\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" +
"\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" +
"\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" +
"\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12:\n" +
"\vblock_delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\n" +
"blockDelay\"\xaf\x03\n" +
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"<\n" +
"\aIPRules\x121\n" +
"\x05rules\x18\x01 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x05rules\"\xbc\x03\n" +
"\x06Config\x12P\n" +
"\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" +
"\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" +
@@ -564,13 +425,10 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
"user_level\x18\x04 \x01(\rR\tuserLevel\x128\n" +
"\bfragment\x18\x05 \x01(\v2\x1c.xray.proxy.freedom.FragmentR\bfragment\x12%\n" +
"\x0eproxy_protocol\x18\x06 \x01(\rR\rproxyProtocol\x121\n" +
"\x06noises\x18\a \x03(\v2\x19.xray.proxy.freedom.NoiseR\x06noises\x12D\n" +
"\vfinal_rules\x18\b \x03(\v2#.xray.proxy.freedom.FinalRuleConfigR\n" +
"finalRules*\"\n" +
"\n" +
"RuleAction\x12\t\n" +
"\x05Allow\x10\x00\x12\t\n" +
"\x05Block\x10\x01BX\n" +
"\x06noises\x18\a \x03(\v2\x19.xray.proxy.freedom.NoiseR\x06noises\x12A\n" +
"\vips_blocked\x18\b \x01(\v2\x1b.xray.proxy.freedom.IPRulesH\x00R\n" +
"ipsBlocked\x88\x01\x01B\x0e\n" +
"\f_ips_blockedBX\n" +
"\x16com.xray.proxy.freedomP\x01Z'github.com/xtls/xray-core/proxy/freedom\xaa\x02\x12Xray.Proxy.Freedomb\x06proto3"
var (
@@ -585,39 +443,30 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte {
return file_proxy_freedom_config_proto_rawDescData
}
var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_proxy_freedom_config_proto_goTypes = []any{
(RuleAction)(0), // 0: xray.proxy.freedom.RuleAction
(*DestinationOverride)(nil), // 1: xray.proxy.freedom.DestinationOverride
(*Fragment)(nil), // 2: xray.proxy.freedom.Fragment
(*Noise)(nil), // 3: xray.proxy.freedom.Noise
(*Range)(nil), // 4: xray.proxy.freedom.Range
(*FinalRuleConfig)(nil), // 5: xray.proxy.freedom.FinalRuleConfig
(*Config)(nil), // 6: xray.proxy.freedom.Config
(*protocol.ServerEndpoint)(nil), // 7: xray.common.protocol.ServerEndpoint
(net.Network)(0), // 8: xray.common.net.Network
(*net.PortList)(nil), // 9: xray.common.net.PortList
(*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule
(internet.DomainStrategy)(0), // 11: xray.transport.internet.DomainStrategy
(*DestinationOverride)(nil), // 0: xray.proxy.freedom.DestinationOverride
(*Fragment)(nil), // 1: xray.proxy.freedom.Fragment
(*Noise)(nil), // 2: xray.proxy.freedom.Noise
(*IPRules)(nil), // 3: xray.proxy.freedom.IPRules
(*Config)(nil), // 4: xray.proxy.freedom.Config
(*protocol.ServerEndpoint)(nil), // 5: xray.common.protocol.ServerEndpoint
(*geodata.IPRule)(nil), // 6: xray.common.geodata.IPRule
(internet.DomainStrategy)(0), // 7: xray.transport.internet.DomainStrategy
}
var file_proxy_freedom_config_proto_depIdxs = []int32{
7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
0, // 1: xray.proxy.freedom.FinalRuleConfig.action:type_name -> xray.proxy.freedom.RuleAction
8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network
9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList
10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule
4, // 5: xray.proxy.freedom.FinalRuleConfig.block_delay:type_name -> xray.proxy.freedom.Range
11, // 6: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
1, // 7: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
2, // 8: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
3, // 9: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
5, // 10: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig
11, // [11:11] is the sub-list for method output_type
11, // [11:11] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension type_name
11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
5, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
6, // 1: xray.proxy.freedom.IPRules.rules:type_name -> xray.common.geodata.IPRule
7, // 2: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
0, // 3: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
1, // 4: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
2, // 5: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
3, // 6: xray.proxy.freedom.Config.ips_blocked:type_name -> xray.proxy.freedom.IPRules
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_proxy_freedom_config_proto_init() }
@@ -625,19 +474,19 @@ func file_proxy_freedom_config_proto_init() {
if File_proxy_freedom_config_proto != nil {
return
}
file_proxy_freedom_config_proto_msgTypes[4].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_freedom_config_proto_rawDesc), len(file_proxy_freedom_config_proto_rawDesc)),
NumEnums: 1,
NumMessages: 6,
NumEnums: 0,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_freedom_config_proto_goTypes,
DependencyIndexes: file_proxy_freedom_config_proto_depIdxs,
EnumInfos: file_proxy_freedom_config_proto_enumTypes,
MessageInfos: file_proxy_freedom_config_proto_msgTypes,
}.Build()
File_proxy_freedom_config_proto = out.File
+3 -20
View File
@@ -8,8 +8,6 @@ option java_multiple_files = true;
import "common/protocol/server_spec.proto";
import "transport/internet/config.proto";
import "common/net/port.proto";
import "common/net/network.proto";
import "common/geodata/geodat.proto";
message DestinationOverride {
@@ -26,7 +24,6 @@ message Fragment {
uint64 max_split_min = 7;
uint64 max_split_max = 8;
}
message Noise {
uint64 length_min = 1;
uint64 length_max = 2;
@@ -36,22 +33,8 @@ message Noise {
string apply_to = 6;
}
message Range {
uint64 min = 1;
uint64 max = 2;
}
enum RuleAction {
Allow = 0;
Block = 1;
}
message FinalRuleConfig {
RuleAction action = 1;
repeated xray.common.net.Network networks = 2;
xray.common.net.PortList port_list = 3;
repeated xray.common.geodata.IPRule ip = 4;
Range block_delay = 5;
message IPRules {
repeated xray.common.geodata.IPRule rules = 1;
}
message Config {
@@ -61,5 +44,5 @@ message Config {
Fragment fragment = 5;
uint32 proxy_protocol = 6;
repeated Noise noises = 7;
repeated FinalRuleConfig final_rules = 8;
optional IPRules ips_blocked = 8;
}
+99 -297
View File
@@ -31,21 +31,36 @@ import (
)
var useSplice bool
var allNetworks [8]bool
var defaultBlockPrivateRule *FinalRule
var defaultBlockAllRule *FinalRule
var defaultPrivateBlockIP = []string{
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/3",
"::/127",
"fc00::/7",
"fe80::/10",
"ff00::/8",
}
var defaultPrivateBlockIPMatcher = func() geodata.IPMatcher {
rules := common.Must2(geodata.ParseIPRules(defaultPrivateBlockIP))
return common.Must2(geodata.IPReg.BuildIPMatcher(rules))
}()
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
h := new(Handler)
if handler, ok := session.FullHandlerFromContext(ctx).(handlerWithSocketSettings); ok {
if sockopt := handler.SocketSettings(); sockopt != nil {
h.socketStrategy = sockopt.DomainStrategy
}
}
if handler, ok := session.FullHandlerFromContext(ctx).(handlerWithProxySettings); ok {
h.usesProxySettings = handler.UsesProxySettings()
}
if err := core.RequireFeatures(ctx, func(pm policy.Manager) error {
return h.Init(config.(*Config), pm)
}); err != nil {
@@ -53,171 +68,31 @@ func init() {
}
return h, nil
}))
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
value := platform.NewEnvFlag(platform.UseFreedomSplice).GetValue(func() string { return defaultFlagValue })
switch value {
case defaultFlagValue, "auto", "enable":
useSplice = true
}
for i := range allNetworks {
allNetworks[i] = true
}
defaultBlockPrivateRule = &FinalRule{
action: RuleAction_Block,
network: allNetworks,
ip: common.Must2(geodata.IPReg.BuildIPMatcher(common.Must2(geodata.ParseIPRules([]string{
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/3",
"::/127",
"fc00::/7",
"fe80::/10",
"ff00::/8",
})))),
}
defaultBlockAllRule = &FinalRule{
action: RuleAction_Block,
network: allNetworks,
}
}
type handlerWithSocketSettings interface {
SocketSettings() *internet.SocketConfig
}
type handlerWithProxySettings interface {
UsesProxySettings() bool
}
type FinalRule struct {
action RuleAction
network [8]bool
port net.MemoryPortList
ip geodata.IPMatcher
blockDelay *Range
}
// Handler handles Freedom connections.
type Handler struct {
policyManager policy.Manager
config *Config
finalRules []*FinalRule
socketStrategy internet.DomainStrategy
usesProxySettings bool
}
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
rule := &FinalRule{
action: config.GetAction(),
blockDelay: config.GetBlockDelay(),
}
if len(config.Networks) == 0 {
rule.network = allNetworks
} else {
for _, network := range config.Networks {
rule.network[int(network)] = true
}
}
if config.PortList != nil {
rule.port = net.PortListFromProto(config.PortList)
}
if len(config.Ip) > 0 {
matcher, err := geodata.IPReg.BuildIPMatcher(config.Ip)
if err != nil {
return nil, err
}
rule.ip = matcher
}
return rule, nil
}
func (r *FinalRule) matchNetwork(network net.Network) bool {
return r.network[int(network)]
}
func (r *FinalRule) matchPort(port net.Port) bool {
if len(r.port) == 0 {
return true
}
return r.port.Contains(port)
}
func (r *FinalRule) matchIP(addr net.Address) bool {
if r.ip == nil {
return true
}
return addr != nil && addr.Family().IsIP() && r.ip.Match(addr.IP())
}
func (r *FinalRule) Apply(network net.Network, address net.Address, port net.Port) bool {
if !r.matchNetwork(network) {
return false
}
if !r.matchPort(port) {
return false
}
return r.matchIP(address)
}
func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
if inbound == nil {
return nil
}
switch inbound.Name {
case "vless-reverse":
return defaultBlockAllRule
case "vless", "vmess", "trojan", "hysteria", "wireguard":
return defaultBlockPrivateRule
default:
if strings.HasPrefix(inbound.Name, "shadowsocks") {
return defaultBlockPrivateRule
}
}
return nil
}
func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule {
for _, rule := range h.finalRules {
if rule.Apply(network, address, port) {
return rule
}
}
if defaultRule != nil && defaultRule.Apply(network, address, port) {
return defaultRule
}
return nil
policyManager policy.Manager
config *Config
blockedIPMatcher geodata.IPMatcher
}
// Init initializes the Handler with necessary parameters.
func (h *Handler) Init(config *Config, pm policy.Manager) error {
h.config = config
h.policyManager = pm
h.finalRules = make([]*FinalRule, 0, len(config.FinalRules))
for _, rc := range config.FinalRules {
rule, err := buildFinalRule(rc)
if config.IpsBlocked != nil && len(config.IpsBlocked.Rules) > 0 {
m, err := geodata.IPReg.BuildIPMatcher(config.IpsBlocked.Rules)
if err != nil {
return errors.New("failed to build final rule").Base(err)
return errors.New("failed to build blocked ip matcher").Base(err)
}
h.finalRules = append(h.finalRules, rule)
h.blockedIPMatcher = m
}
return nil
}
@@ -227,41 +102,6 @@ func (h *Handler) policy() policy.Session {
return p
}
func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
min := uint64(30)
max := uint64(90)
if rule.blockDelay != nil {
min = rule.blockDelay.Min
max = rule.blockDelay.Max
}
span := max - min
if max < min {
span = min - max
}
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 (h *Handler) udpDomainStrategy() internet.DomainStrategy {
if h.config.DomainStrategy.HasStrategy() {
return h.config.DomainStrategy
}
return h.socketStrategy
}
func isValidAddress(addr *net.IPOrDomain) bool {
if addr == nil {
return false
@@ -271,6 +111,32 @@ func isValidAddress(addr *net.IPOrDomain) bool {
return a != net.AnyIP && a != net.AnyIPv6
}
func (h *Handler) getBlockedIPMatcher(ctx context.Context, inbound *session.Inbound) geodata.IPMatcher {
if h.blockedIPMatcher != nil {
return h.blockedIPMatcher
}
if h.config.IpsBlocked != nil && len(h.config.IpsBlocked.Rules) == 0 { // "ipsBlocked": []
return nil
}
if inbound == nil {
return nil
}
switch inbound.Name {
case "vmess", "trojan", "hysteria", "wireguard":
errors.LogInfo(ctx, "applying default private IP blocking policy for inbound ", inbound.Name)
return defaultPrivateBlockIPMatcher
}
if strings.HasPrefix(inbound.Name, "vless") || strings.HasPrefix(inbound.Name, "shadowsocks") {
errors.LogInfo(ctx, "applying default private IP blocking policy for inbound ", inbound.Name)
return defaultPrivateBlockIPMatcher
}
return nil
}
func isBlockedAddress(matcher geodata.IPMatcher, addr net.Address) bool {
return matcher != nil && addr != nil && addr.Family().IsIP() && matcher.Match(addr.IP())
}
// Process implements proxy.Outbound.
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
outbounds := session.OutboundsFromContext(ctx)
@@ -281,7 +147,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ob.Name = "freedom"
ob.CanSpliceCopy = 1
inbound := session.InboundFromContext(ctx)
defaultRule := getDefaultFinalRule(inbound)
blockedIPMatcher := h.getBlockedIPMatcher(ctx, inbound)
destination := ob.Target
origTargetAddr := ob.OriginalTarget.Address
@@ -307,76 +173,26 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
output := link.Writer
var conn stat.Connection
var blockedDest *net.Destination
var blockedRule *FinalRule
err := retry.ExponentialBackoff(5, 100).On(func() error {
dialDest := destination
if dialDest.Address.Family().IsDomain() {
if strategy := h.config.DomainStrategy; strategy.HasStrategy() {
if destination.Network == net.Network_UDP && origTargetAddr != nil && outGateway == nil {
strategy = strategy.GetDynamicStrategy(origTargetAddr.Family())
}
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
if err != nil { // SRV/TXT
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
if h.config.DomainStrategy.ForceIP() || defaultRule != nil || len(h.finalRules) > 0 {
return err // retry
}
} else { // to ip
dialDest = net.Destination{
Network: dialDest.Network,
Address: net.IPAddress(ips[dice.Roll(len(ips))]),
Port: dialDest.Port,
}
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
}
}
} else if defaultRule != nil || len(h.finalRules) > 0 { // freedom asis + hasrules
if strategy := h.socketStrategy; strategy.HasStrategy() {
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
if err != nil { // SRV/TXT
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
if strategy.ForceIP() {
return err // retry
}
}
for _, ip := range ips {
if addr := net.IPAddress(ip); addr != nil {
if rule := h.matchFinalRule(dialDest.Network, addr, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &dialDest
blockedDest.Address = addr
blockedRule = rule
return nil
}
}
}
} else { // sockopt asis
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, dialDest.Address.Domain())
if err != nil { // SRV/TXT
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
}
for _, addr := range addrs {
if ipAddr := net.IPAddress(addr.IP); ipAddr != nil {
if rule := h.matchFinalRule(dialDest.Network, ipAddr, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &dialDest
blockedDest.Address = ipAddr
blockedRule = rule
return nil
}
}
}
}
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() {
strategy := h.config.DomainStrategy
if destination.Network == net.Network_UDP && origTargetAddr != nil && outGateway == nil {
strategy = strategy.GetDynamicStrategy(origTargetAddr.Family())
}
} else {
if rule := h.matchFinalRule(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
blockedDest = &dialDest
blockedRule = rule
return nil
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
if err != nil {
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
if h.config.DomainStrategy.ForceIP() {
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)
}
}
@@ -391,22 +207,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
if err != nil {
return errors.New("failed to open connection to ", destination).Base(err)
}
if blockedDest != nil {
return h.blackhole(ctx, input, output, blockedRule, blockedDest)
if remoteAddr := net.DestinationFromAddr(conn.RemoteAddr()).Address; isBlockedAddress(blockedIPMatcher, remoteAddr) {
conn.Close()
return errors.New("blocked target IP: ", remoteAddr).AtInfo()
}
if defaultRule != nil || len(h.finalRules) > 0 {
if h.usesProxySettings {
errors.LogInfo(ctx, "skipping final rule check for proxied remote endpoint, original target: ", destination)
} else {
// SRV/TXT, lookup failed
remoteDest := net.DestinationFromAddr(conn.RemoteAddr())
if rule := h.matchFinalRule(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
conn.Close()
return h.blackhole(ctx, input, output, rule, &remoteDest)
}
}
}
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
version := byte(h.config.ProxyProtocol)
srcAddr := inbound.Source.RawNetAddr()
@@ -451,7 +255,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
writer = buf.NewWriter(conn)
}
} else {
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination, outGateway)
writer = NewPacketWriter(conn, h, UDPOverride, destination, blockedIPMatcher)
if h.config.Noises != nil {
errors.LogDebug(ctx, "NOISE", h.config.Noises)
writer = &NoisePacketWriter{
@@ -486,7 +290,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
if destination.Network == net.Network_TCP {
reader = buf.NewReader(conn)
} else {
reader = NewPacketReader(conn, h, defaultRule, UDPOverride, destination)
reader = NewPacketReader(conn, UDPOverride, destination, blockedIPMatcher)
}
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to process response").Base(err)
@@ -505,7 +309,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return nil
}
func NewPacketReader(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination, blockedIPMatcher geodata.IPMatcher) buf.Reader {
iConn := conn
statConn, ok := iConn.(*stat.CounterConnection)
if ok {
@@ -524,8 +328,7 @@ func NewPacketReader(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverr
return &PacketReader{
PacketConnWrapper: c,
Counter: counter,
Handler: h,
DefaultRule: defaultRule,
BlockedIPMatcher: blockedIPMatcher,
IsOverridden: isOverridden,
InitUnchangedAddr: DialDest.Address,
InitChangedAddr: net.DestinationFromAddr(conn.RemoteAddr()).Address,
@@ -537,8 +340,7 @@ func NewPacketReader(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverr
type PacketReader struct {
*internet.PacketConnWrapper
stats.Counter
Handler *Handler
DefaultRule *FinalRule
BlockedIPMatcher geodata.IPMatcher
IsOverridden bool
InitUnchangedAddr net.Address
InitChangedAddr net.Address
@@ -555,7 +357,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
}
udpAddr := d.(*net.UDPAddr)
sourceAddr := net.IPAddress(udpAddr.IP)
if rule := r.Handler.matchFinalRule(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule); rule != nil && rule.action == RuleAction_Block {
if isBlockedAddress(r.BlockedIPMatcher, sourceAddr) {
continue
}
b.Resize(0, int32(n))
@@ -580,7 +382,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
}
// DialDest means the dial target used in the dialer when creating conn
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination, outGateway net.Address) buf.Writer {
func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, DialDest net.Destination, blockedIPMatcher geodata.IPMatcher) buf.Writer {
iConn := conn
statConn, ok := iConn.(*stat.CounterConnection)
if ok {
@@ -601,10 +403,10 @@ func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverr
PacketConnWrapper: c,
Counter: counter,
Handler: h,
DefaultRule: defaultRule,
BlockedIPMatcher: blockedIPMatcher,
UDPOverride: UDPOverride,
ResolvedUDPAddr: resolvedUDPAddr,
OutGateway: outGateway,
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
}
}
@@ -615,15 +417,15 @@ type PacketWriter struct {
*internet.PacketConnWrapper
stats.Counter
*Handler
DefaultRule *FinalRule
UDPOverride net.Destination
BlockedIPMatcher geodata.IPMatcher
UDPOverride net.Destination
// Dest of udp packets might be a domain, we will resolve them to IP
// But resolver will return a random one if the domain has many IPs
// Resulting in these packets being sent to many different IPs randomly
// So, cache and keep the resolve result
ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address]
OutGateway net.Address
LocalAddr net.Address
}
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
@@ -646,21 +448,21 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
b.UDP.Address = ip
} else {
shouldUseSystemResolver := true
if resolveStrategy := w.Handler.udpDomainStrategy(); resolveStrategy.HasStrategy() {
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), resolveStrategy, w.OutGateway)
ShouldUseSystemResolver := true
if w.Handler.config.DomainStrategy.HasStrategy() {
ips, err := internet.LookupForIP(b.UDP.Address.Domain(), w.Handler.config.DomainStrategy, w.LocalAddr)
if err != nil {
// drop packet if resolve failed when forceIP
if resolveStrategy.ForceIP() {
if w.Handler.config.DomainStrategy.ForceIP() {
b.Release()
continue
}
} else {
ip = net.IPAddress(ips[dice.Roll(len(ips))])
shouldUseSystemResolver = false
ShouldUseSystemResolver = false
}
}
if shouldUseSystemResolver {
if ShouldUseSystemResolver {
udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
if err != nil {
b.Release()
@@ -674,7 +476,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 isBlockedAddress(w.BlockedIPMatcher, b.UDP.Address) {
b.Release()
continue
}
+54 -44
View File
@@ -17,6 +17,7 @@ import (
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy"
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria"
@@ -55,7 +56,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
ob.CanSpliceCopy = 3
target := ob.Target
conn, err := dialer.Dial(hysteria.ContextWithDatagram(ctx, target.Network == net.Network_UDP), c.server.Destination)
conn, err := dialer.Dial(hyCtx.ContextWithRequireDatagram(ctx, target.Network == net.Network_UDP), c.server.Destination)
if err != nil {
return errors.New("failed to find an available destination").AtWarning().Base(err)
}
@@ -117,7 +118,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
if target.Network == net.Network_UDP {
iConn := stat.TryUnwrapStatsConn(conn)
_, ok := iConn.(*hysteria.InterConn)
_, ok := iConn.(*hysteria.InterUdpConn)
if !ok {
return errors.New("udp requires hysteria udp transport")
}
@@ -126,7 +127,8 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
writer := &UDPWriter{
writer: conn,
Writer: conn,
buf: make([]byte, MaxUDPSize),
addr: target.NetAddr(),
}
@@ -141,7 +143,8 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
reader := &UDPReader{
reader: conn,
Reader: conn,
buf: make([]byte, MaxUDPSize),
df: &Defragger{},
}
@@ -170,22 +173,28 @@ func init() {
}
type UDPWriter struct {
writer io.Writer
Writer io.Writer
buf []byte
addr string
buf [buf.Size]byte
}
func (w *UDPWriter) SendMessage(msg *UDPMessage) error {
msgN := msg.Serialize(w.buf[:])
func (w *UDPWriter) sendMsg(msg *UDPMessage) error {
msgN := msg.Serialize(w.buf)
if msgN < 0 {
return nil
}
_, err := w.writer.Write(w.buf[:msgN])
_, err := w.Writer.Write(w.buf[:msgN])
return err
}
func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb {
for {
mb2, b := buf.SplitFirst(mb)
mb = mb2
if b == nil {
break
}
addr := w.addr
if b.UDP != nil {
addr = b.UDP.NetAddr()
@@ -200,20 +209,22 @@ func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
Data: b.Bytes(),
}
err := w.SendMessage(msg)
err := w.sendMsg(msg)
var errTooLarge *quic.DatagramTooLargeError
if go_errors.As(err, &errTooLarge) {
msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1
fMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize))
for _, fMsg := range fMsgs {
err := w.SendMessage(&fMsg)
err := w.sendMsg(&fMsg)
if err != nil {
buf.ReleaseMulti(mb[i:])
b.Release()
buf.ReleaseMulti(mb)
return err
}
}
} else if err != nil {
buf.ReleaseMulti(mb[i:])
b.Release()
buf.ReleaseMulti(mb)
return err
}
@@ -224,21 +235,34 @@ func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
}
type UDPReader struct {
reader io.Reader
df *Defragger
firstBuf *buf.Buffer
Reader io.Reader
buf []byte
df *Defragger
firstMsg *UDPMessage
firstDest *net.Destination
}
func (r *UDPReader) ReadFrom(p []byte) (n int, addr *net.Destination, err error) {
for {
var buf [hysteria.MaxDatagramFrameSize]byte
n, err := r.reader.Read(buf[:])
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
if r.firstMsg != nil {
buffer := buf.New()
_, err := buffer.Write(r.firstMsg.Data)
if err != nil {
return 0, nil, err
return nil, err
}
buffer.UDP = r.firstDest
r.firstMsg = nil
r.firstDest = nil
return buf.MultiBuffer{buffer}, nil
}
for {
n, err := r.Reader.Read(r.buf)
if err != nil {
return nil, err
}
msg, err := ParseUDPMessage(buf[:n])
msg, err := ParseUDPMessage(r.buf[:n])
if err != nil {
continue
}
@@ -250,31 +274,17 @@ func (r *UDPReader) ReadFrom(p []byte) (n int, addr *net.Destination, err error)
dest, err := net.ParseDestination("udp:" + dfMsg.Addr)
if err != nil {
errors.LogDebug(context.Background(), dfMsg.Addr, " ParseDestination err ", err)
continue
}
if len(p) < len(dfMsg.Data) {
continue
buffer := buf.New()
if _, err := buffer.Write(dfMsg.Data); err != nil {
return nil, err
}
return copy(p, dfMsg.Data), &dest, nil
}
}
buffer.UDP = &dest
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
if r.firstBuf != nil {
mb := buf.MultiBuffer{r.firstBuf}
r.firstBuf = nil
return mb, nil
return buf.MultiBuffer{buffer}, nil
}
b := buf.New()
b.Resize(0, buf.Size)
n, addr, err := r.ReadFrom(b.Bytes())
if err != nil {
b.Release()
return nil, err
}
b.Resize(0, int32(n))
b.UDP = addr
return buf.MultiBuffer{b}, nil
}
+9
View File
@@ -1 +1,10 @@
package hysteria
import (
"github.com/xtls/xray-core/transport/internet/hysteria/padding"
)
var (
tcpRequestPadding = padding.Padding{Min: 64, Max: 512}
tcpResponsePadding = padding.Padding{Min: 128, Max: 1024}
)
+35
View File
@@ -0,0 +1,35 @@
package ctx
import (
"context"
"github.com/xtls/xray-core/proxy/hysteria/account"
)
type key int
const (
requireDatagram key = iota
validator
)
func ContextWithRequireDatagram(ctx context.Context, udp bool) context.Context {
if !udp {
return ctx
}
return context.WithValue(ctx, requireDatagram, struct{}{})
}
func RequireDatagramFromContext(ctx context.Context) bool {
_, ok := ctx.Value(requireDatagram).(struct{})
return ok
}
func ContextWithValidator(ctx context.Context, v *account.Validator) context.Context {
return context.WithValue(ctx, validator, v)
}
func ValidatorFromContext(ctx context.Context) *account.Validator {
v, _ := ctx.Value(validator).(*account.Validator)
return v
}
+73
View File
@@ -0,0 +1,73 @@
package hysteria
func FragUDPMessage(m *UDPMessage, maxSize int) []UDPMessage {
if m.Size() <= maxSize {
return []UDPMessage{*m}
}
fullPayload := m.Data
maxPayloadSize := maxSize - m.HeaderSize()
off := 0
fragID := uint8(0)
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
frags := make([]UDPMessage, fragCount)
for off < len(fullPayload) {
payloadSize := len(fullPayload) - off
if payloadSize > maxPayloadSize {
payloadSize = maxPayloadSize
}
frag := *m
frag.FragID = fragID
frag.FragCount = fragCount
frag.Data = fullPayload[off : off+payloadSize]
frags[fragID] = frag
off += payloadSize
fragID++
}
return frags
}
// Defragger handles the defragmentation of UDP messages.
// The current implementation can only handle one packet ID at a time.
// If another packet arrives before a packet has received all fragments
// in their entirety, any previous state is discarded.
type Defragger struct {
pktID uint16
frags []*UDPMessage
count uint8
size int // data size
}
func (d *Defragger) Feed(m *UDPMessage) *UDPMessage {
if m.FragCount <= 1 {
return m
}
if m.FragID >= m.FragCount {
// wtf is this?
return nil
}
if m.PacketID != d.pktID || m.FragCount != uint8(len(d.frags)) {
// new message, clear previous state
d.pktID = m.PacketID
d.frags = make([]*UDPMessage, m.FragCount)
d.frags[m.FragID] = m
d.count = 1
d.size = len(m.Data)
} else if d.frags[m.FragID] == nil {
d.frags[m.FragID] = m
d.count++
d.size += len(m.Data)
if int(d.count) == len(d.frags) {
// all fragments received, assemble
data := make([]byte, d.size)
off := 0
for _, frag := range d.frags {
off += copy(data[off:], frag.Data)
}
m.Data = data
m.FragID = 0
m.FragCount = 1
return m
}
}
return nil
}
+4 -75
View File
@@ -8,7 +8,6 @@ import (
"github.com/apernet/quic-go/quicvarint"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/hysteria"
)
const (
@@ -18,6 +17,8 @@ const (
MaxMessageLength = 2048
MaxPaddingLength = 4096
MaxUDPSize = 4096
maxVarInt1 = 63
maxVarInt2 = 16383
maxVarInt4 = 1073741823
@@ -61,7 +62,7 @@ func ReadTCPRequest(r io.Reader) (string, error) {
}
func WriteTCPRequest(w io.Writer, addr string) error {
padding := hysteria.TcpRequestPadding.String()
padding := tcpRequestPadding.String()
paddingLen := len(padding)
addrLen := len(addr)
sz := int(quicvarint.Len(uint64(addrLen))) + addrLen +
@@ -121,7 +122,7 @@ func ReadTCPResponse(r io.Reader) (bool, string, error) {
}
func WriteTCPResponse(w io.Writer, ok bool, msg string) error {
padding := hysteria.TcpResponsePadding.String()
padding := tcpResponsePadding.String()
paddingLen := len(padding)
msgLen := len(msg)
sz := 1 + int(quicvarint.Len(uint64(msgLen))) + msgLen +
@@ -246,75 +247,3 @@ func varintPut(b []byte, i uint64) int {
}
panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i))
}
func FragUDPMessage(m *UDPMessage, maxSize int) []UDPMessage {
if m.Size() <= maxSize {
return []UDPMessage{*m}
}
fullPayload := m.Data
maxPayloadSize := maxSize - m.HeaderSize()
off := 0
fragID := uint8(0)
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
frags := make([]UDPMessage, fragCount)
for off < len(fullPayload) {
payloadSize := len(fullPayload) - off
if payloadSize > maxPayloadSize {
payloadSize = maxPayloadSize
}
frag := *m
frag.FragID = fragID
frag.FragCount = fragCount
frag.Data = fullPayload[off : off+payloadSize]
frags[fragID] = frag
off += payloadSize
fragID++
}
return frags
}
// Defragger handles the defragmentation of UDP messages.
// The current implementation can only handle one packet ID at a time.
// If another packet arrives before a packet has received all fragments
// in their entirety, any previous state is discarded.
type Defragger struct {
pktID uint16
frags []*UDPMessage
count uint8
size int // data size
}
func (d *Defragger) Feed(m *UDPMessage) *UDPMessage {
if m.FragCount <= 1 {
return m
}
if m.FragID >= m.FragCount {
// wtf is this?
return nil
}
if m.PacketID != d.pktID || m.FragCount != uint8(len(d.frags)) {
// new message, clear previous state
d.pktID = m.PacketID
d.frags = make([]*UDPMessage, m.FragCount)
d.frags[m.FragID] = m
d.count = 1
d.size = len(m.Data)
} else if d.frags[m.FragID] == nil {
d.frags[m.FragID] = m
d.count++
d.size += len(m.Data)
if int(d.count) == len(d.frags) {
// all fragments received, assemble
data := make([]byte, d.size)
off := 0
for _, frag := range d.frags {
off += copy(data[off:], frag.Data)
}
m.Data = data
m.FragID = 0
m.FragCount = 1
return m
}
}
return nil
}
+43 -18
View File
@@ -2,6 +2,7 @@ package hysteria
import (
"context"
"io"
"time"
"github.com/xtls/xray-core/common"
@@ -90,30 +91,54 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
inbound.User = v.User()
}
if _, ok := iConn.(*hysteria.InterConn); ok {
if _, ok := iConn.(*hysteria.InterUdpConn); ok {
r := io.Reader(conn)
b := make([]byte, MaxUDPSize)
df := &Defragger{}
var firstMsg *UDPMessage
var firstDest net.Destination
for {
n, err := r.Read(b)
if err != nil {
return err
}
msg, err := ParseUDPMessage(b[:n])
if err != nil {
continue
}
dfMsg := df.Feed(msg)
if dfMsg == nil {
continue
}
firstMsg = dfMsg
firstDest, err = net.ParseDestination("udp:" + firstMsg.Addr)
if err != nil {
errors.LogDebug(context.Background(), dfMsg.Addr, " ParseDestination err ", err)
continue
}
break
}
reader := &UDPReader{
reader: conn,
df: &Defragger{},
Reader: r,
buf: b,
df: df,
firstMsg: firstMsg,
firstDest: &firstDest,
}
b := buf.New()
b.Resize(0, buf.Size)
n, addr, err := reader.ReadFrom(b.Bytes())
if err != nil {
b.Release()
return err
}
b.Resize(0, int32(n))
b.UDP = addr
reader.firstBuf = b
writer := &UDPWriter{
writer: conn,
addr: addr.NetAddr(),
Writer: conn,
buf: make([]byte, MaxUDPSize),
addr: firstMsg.Addr,
}
return dispatcher.DispatchLink(ctx, *addr, &transport.Link{
return dispatcher.DispatchLink(ctx, firstDest, &transport.Link{
Reader: reader,
Writer: writer,
})
+2 -4
View File
@@ -41,12 +41,10 @@ Here is simple Xray config snippet to enable the inbound:
- IPv4 and IPv6
- TCP and UDP
- ICMP Echo (ping)
## LIMITATION
- Only ICMP Echo request/reply is supported; other ICMP message types are ignored
- ICMP Echo replies are generated locally by the TUN stack; they do not validate real remote ICMP reachability
- No ICMP support
- Connections are established to any host, as connection success is only a mark of successful accepting packet for proxying. Hosts that are not accepting connections or don't even exists, will look like they opened a connection (SYN-ACK), and never send back a single byte, closing connection (RST) after some time. This is the side effect of the whole process actually being a proxy, and not real network layer 3 vpn
## CONSIDERATIONS
@@ -250,4 +248,4 @@ Set the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number b
Build using gomobile for iOS framework integration:
```
gomobile bind -target=ios
```
```
+12 -59
View File
@@ -3,8 +3,6 @@ package tun
import (
"context"
"net"
"sort"
"strings"
"sync"
"github.com/xtls/xray-core/common/errors"
@@ -47,53 +45,26 @@ func (updater *InterfaceUpdater) Update() {
}
var got *net.Interface
if updater.fixedName != "" {
for _, iface := range interfaces {
if iface.Index == updater.tunIndex {
continue
}
for _, iface := range interfaces {
if iface.Index == updater.tunIndex {
continue
}
if updater.fixedName != "" {
if iface.Name == updater.fixedName {
got = &iface
break
}
}
} else {
var ifs []struct {
index int
score int
}
for i, iface := range interfaces {
if iface.Index == updater.tunIndex {
continue
}
if strings.Contains(iface.Name, "vEthernet") {
continue
}
if iface.Flags&net.FlagUp == 0 {
continue
}
if iface.Flags&net.FlagLoopback != 0 {
continue
}
} else {
addrs, err := iface.Addrs()
if err != nil || len(addrs) == 0 {
if err != nil {
continue
}
ifs = append(ifs, struct {
index int
score int
}{i, score(&iface, addrs)})
}
sort.Slice(ifs, func(i, j int) bool {
if ifs[i].score != ifs[j].score {
return ifs[i].score > ifs[j].score
if (iface.Flags&net.FlagUp != 0) &&
(iface.Flags&net.FlagLoopback == 0) &&
len(addrs) > 0 {
got = &iface
break
}
return interfaces[ifs[i].index].Name < interfaces[ifs[j].index].Name
})
if len(ifs) > 0 {
iface := interfaces[ifs[0].index]
got = &iface
}
}
@@ -105,21 +76,3 @@ func (updater *InterfaceUpdater) Update() {
updater.iface = got
errors.LogInfo(context.Background(), "[tun] update interface ", got.Name, " ", got.Index)
}
func score(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 += 1
break
}
}
return score
}
-106
View File
@@ -1,106 +0,0 @@
package icmp
import (
"github.com/xtls/xray-core/common/errors"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
func ProtocolLabel(netProto tcpip.NetworkProtocolNumber) string {
switch netProto {
case header.IPv4ProtocolNumber:
return "ipv4"
case header.IPv6ProtocolNumber:
return "ipv6"
default:
return "unknown"
}
}
func ParseEchoRequest(netProto tcpip.NetworkProtocolNumber, message []byte) (uint16, uint16, bool) {
switch netProto {
case header.IPv4ProtocolNumber:
if len(message) < header.ICMPv4MinimumSize {
return 0, 0, false
}
icmpHdr := header.ICMPv4(message)
if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != header.ICMPv4UnusedCode {
return 0, 0, false
}
return icmpHdr.Ident(), icmpHdr.Sequence(), true
case header.IPv6ProtocolNumber:
if len(message) < header.ICMPv6MinimumSize {
return 0, 0, false
}
icmpHdr := header.ICMPv6(message)
if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != header.ICMPv6UnusedCode {
return 0, 0, false
}
return icmpHdr.Ident(), icmpHdr.Sequence(), true
default:
return 0, 0, false
}
}
func RewriteChecksum(netProto tcpip.NetworkProtocolNumber, message []byte, srcIP, dstIP tcpip.Address) error {
switch netProto {
case header.IPv4ProtocolNumber:
if len(message) < header.ICMPv4MinimumSize {
return errors.New("invalid icmpv4 packet")
}
icmpHdr := header.ICMPv4(message)
icmpHdr.SetChecksum(0)
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
return nil
case header.IPv6ProtocolNumber:
if len(message) < header.ICMPv6MinimumSize {
return errors.New("invalid icmpv6 packet")
}
icmpHdr := header.ICMPv6(message)
icmpHdr.SetChecksum(0)
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpHdr[:header.ICMPv6MinimumSize],
Src: srcIP,
Dst: dstIP,
PayloadCsum: checksum.Checksum(icmpHdr.Payload(), 0),
PayloadLen: len(icmpHdr.Payload()),
}))
return nil
default:
return errors.New("unsupported icmp network protocol")
}
}
func BuildLocalEchoReply(netProto tcpip.NetworkProtocolNumber, request []byte, srcIP, dstIP tcpip.Address) ([]byte, error) {
reply := append([]byte(nil), request...)
switch netProto {
case header.IPv4ProtocolNumber:
if len(reply) < header.ICMPv4MinimumSize {
return nil, errors.New("invalid icmpv4 echo packet")
}
icmpHdr := header.ICMPv4(reply)
if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != header.ICMPv4UnusedCode {
return nil, errors.New("not an icmpv4 echo request")
}
reply[0] = byte(header.ICMPv4EchoReply)
case header.IPv6ProtocolNumber:
if len(reply) < header.ICMPv6MinimumSize {
return nil, errors.New("invalid icmpv6 echo packet")
}
icmpHdr := header.ICMPv6(reply)
if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != header.ICMPv6UnusedCode {
return nil, errors.New("not an icmpv6 echo request")
}
reply[0] = byte(header.ICMPv6EchoReply)
default:
return nil, errors.New("unsupported icmp network protocol")
}
if err := RewriteChecksum(netProto, reply, srcIP, dstIP); err != nil {
return nil, err
}
return reply, nil
}
-174
View File
@@ -1,174 +0,0 @@
package icmp
import (
"testing"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
func TestParseEchoRequest(t *testing.T) {
t.Run("ipv4 echo", func(t *testing.T) {
var zero tcpip.Address
packet := []byte{
byte(header.ICMPv4Echo), 0,
0, 0,
0x12, 0x34,
0x56, 0x78,
0xaa, 0xbb,
}
if err := RewriteChecksum(header.IPv4ProtocolNumber, packet, zero, zero); err != nil {
t.Fatal(err)
}
ident, sequence, ok := ParseEchoRequest(header.IPv4ProtocolNumber, packet)
if !ok {
t.Fatal("expected ipv4 echo request to parse")
}
if ident != 0x1234 || sequence != 0x5678 {
t.Fatalf("unexpected ident/sequence: %x/%x", ident, sequence)
}
})
t.Run("ipv6 echo", func(t *testing.T) {
packet := []byte{
byte(header.ICMPv6EchoRequest), 0,
0, 0,
0xab, 0xcd,
0xef, 0x01,
0xaa, 0xbb,
}
src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})
if err := RewriteChecksum(header.IPv6ProtocolNumber, packet, src, dst); err != nil {
t.Fatal(err)
}
ident, sequence, ok := ParseEchoRequest(header.IPv6ProtocolNumber, packet)
if !ok {
t.Fatal("expected ipv6 echo request to parse")
}
if ident != 0xabcd || sequence != 0xef01 {
t.Fatalf("unexpected ident/sequence: %x/%x", ident, sequence)
}
})
}
func TestRewriteChecksum(t *testing.T) {
t.Run("ipv4", func(t *testing.T) {
var zero tcpip.Address
packet := []byte{
byte(header.ICMPv4Echo), 0,
0xff, 0xff,
0x12, 0x34,
0x56, 0x78,
0xaa, 0xbb, 0xcc,
}
if err := RewriteChecksum(header.IPv4ProtocolNumber, packet, zero, zero); err != nil {
t.Fatal(err)
}
icmpHdr := header.ICMPv4(packet)
if got, want := icmpHdr.Checksum(), header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksumPayloadV4(icmpHdr.Payload())); got != want {
t.Fatalf("unexpected ipv4 checksum: got %x want %x", got, want)
}
})
t.Run("ipv6", func(t *testing.T) {
packet := []byte{
byte(header.ICMPv6EchoReply), 0,
0xff, 0xff,
0x12, 0x34,
0x56, 0x78,
0xaa, 0xbb, 0xcc,
}
src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})
if err := RewriteChecksum(header.IPv6ProtocolNumber, packet, src, dst); err != nil {
t.Fatal(err)
}
icmpHdr := header.ICMPv6(packet)
want := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpHdr[:header.ICMPv6MinimumSize],
Src: src,
Dst: dst,
PayloadLen: len(icmpHdr.Payload()),
PayloadCsum: checksumPayloadV6(icmpHdr.Payload()),
})
if got := icmpHdr.Checksum(); got != want {
t.Fatalf("unexpected ipv6 checksum: got %x want %x", got, want)
}
})
}
func TestBuildLocalEchoReply(t *testing.T) {
t.Run("ipv4", func(t *testing.T) {
request := []byte{
byte(header.ICMPv4Echo), 0,
0, 0,
0x12, 0x34,
0x56, 0x78,
0xaa, 0xbb, 0xcc,
}
src := tcpip.Address{}
dst := tcpip.Address{}
if err := RewriteChecksum(header.IPv4ProtocolNumber, request, src, dst); err != nil {
t.Fatal(err)
}
reply, err := BuildLocalEchoReply(header.IPv4ProtocolNumber, request, dst, src)
if err != nil {
t.Fatal(err)
}
if request[0] != byte(header.ICMPv4Echo) {
t.Fatal("request mutated")
}
icmpHdr := header.ICMPv4(reply)
if icmpHdr.Type() != header.ICMPv4EchoReply || icmpHdr.Code() != header.ICMPv4UnusedCode {
t.Fatalf("unexpected ipv4 reply type/code: %d/%d", icmpHdr.Type(), icmpHdr.Code())
}
if icmpHdr.Ident() != 0x1234 || icmpHdr.Sequence() != 0x5678 {
t.Fatalf("unexpected ipv4 ident/sequence: %x/%x", icmpHdr.Ident(), icmpHdr.Sequence())
}
})
t.Run("ipv6", func(t *testing.T) {
request := []byte{
byte(header.ICMPv6EchoRequest), 0,
0, 0,
0xab, 0xcd,
0xef, 0x01,
0xaa, 0xbb, 0xcc,
}
src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})
if err := RewriteChecksum(header.IPv6ProtocolNumber, request, src, dst); err != nil {
t.Fatal(err)
}
reply, err := BuildLocalEchoReply(header.IPv6ProtocolNumber, request, dst, src)
if err != nil {
t.Fatal(err)
}
if request[0] != byte(header.ICMPv6EchoRequest) {
t.Fatal("request mutated")
}
icmpHdr := header.ICMPv6(reply)
if icmpHdr.Type() != header.ICMPv6EchoReply || icmpHdr.Code() != header.ICMPv6UnusedCode {
t.Fatalf("unexpected ipv6 reply type/code: %d/%d", icmpHdr.Type(), icmpHdr.Code())
}
if icmpHdr.Ident() != 0xabcd || icmpHdr.Sequence() != 0xef01 {
t.Fatalf("unexpected ipv6 ident/sequence: %x/%x", icmpHdr.Ident(), icmpHdr.Sequence())
}
})
}
func checksumPayloadV4(payload []byte) uint16 {
return checksum.Checksum(payload, 0)
}
func checksumPayloadV6(payload []byte) uint16 {
return checksum.Checksum(payload, 0)
}
+1 -4
View File
@@ -14,7 +14,6 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
@@ -118,8 +117,6 @@ func (t *stackGVisor) Start() error {
udpForwarder.HandlePacket(src, dst, data)
return true
})
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, t.handleICMPv4Packet)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, t.handleICMPv6Packet)
t.stack = ipStack
t.endpoint = linkEndpoint
@@ -208,7 +205,7 @@ func (t *stackGVisor) Close() error {
func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
HandleLocal: false,
}
gStack := stack.New(opts)
-98
View File
@@ -1,98 +0,0 @@
package tun
import (
"github.com/xtls/xray-core/common/errors"
tunicmp "github.com/xtls/xray-core/proxy/tun/icmp"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
func (t *stackGVisor) handleICMPv4Packet(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
return t.handleICMPEchoPacket(header.IPv4ProtocolNumber, id, pkt)
}
func (t *stackGVisor) handleICMPv6Packet(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
return t.handleICMPEchoPacket(header.IPv6ProtocolNumber, id, pkt)
}
func (t *stackGVisor) handleICMPEchoPacket(netProto tcpip.NetworkProtocolNumber, id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
srcIP := id.RemoteAddress
dstIP := id.LocalAddress
if srcIP.Len() == 0 || dstIP.Len() == 0 {
return true
}
message := transportPacketBytes(pkt)
ident, sequence, ok := tunicmp.ParseEchoRequest(netProto, message)
if !ok {
return true
}
reply, err := tunicmp.BuildLocalEchoReply(netProto, message, dstIP, srcIP)
if err != nil {
errors.LogInfoInner(t.ctx, err, "[tun] failed to build local icmp echo reply")
return true
}
errors.LogDebug(t.ctx, "[tun][icmp] ", tunicmp.ProtocolLabel(netProto), " local echo reply ", dstIP, " -> ", srcIP, " id=", ident, " seq=", sequence)
if err := t.writeRawICMPPacket(netProto, reply, dstIP, srcIP); err != nil {
errors.LogInfoInner(t.ctx, err, "[tun] failed to write local icmp echo reply")
}
return true
}
func (t *stackGVisor) writeRawICMPPacket(netProto tcpip.NetworkProtocolNumber, message []byte, srcIP, dstIP tcpip.Address) error {
ipHeaderSize := header.IPv6MinimumSize
ipProtocol := header.IPv6ProtocolNumber
transportProtocol := header.ICMPv6ProtocolNumber
if netProto == header.IPv4ProtocolNumber {
ipHeaderSize = header.IPv4MinimumSize
ipProtocol = header.IPv4ProtocolNumber
transportProtocol = header.ICMPv4ProtocolNumber
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: ipHeaderSize,
Payload: buffer.MakeWithData(message),
})
defer pkt.DecRef()
if netProto == header.IPv4ProtocolNumber {
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
ipHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(header.IPv4MinimumSize + len(message)),
TTL: 64,
Protocol: uint8(transportProtocol),
SrcAddr: srcIP,
DstAddr: dstIP,
})
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
} else {
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
ipHdr.Encode(&header.IPv6Fields{
PayloadLength: uint16(len(message)),
TransportProtocol: transportProtocol,
HopLimit: 64,
SrcAddr: srcIP,
DstAddr: dstIP,
})
}
if err := t.stack.WriteRawPacket(defaultNIC, ipProtocol, buffer.MakeWithView(pkt.ToView())); err != nil {
return errors.New("failed to write raw icmp packet back to stack", err)
}
return nil
}
func transportPacketBytes(pkt *stack.PacketBuffer) []byte {
headerBytes := pkt.TransportHeader().Slice()
payloadBytes := pkt.Data().AsRange().ToSlice()
message := make([]byte, len(headerBytes)+len(payloadBytes))
copy(message, headerBytes)
copy(message[len(headerBytes):], payloadBytes)
return message
}
+3
View File
@@ -177,6 +177,9 @@ func (t *WindowsTun) Start() error {
if updater != nil {
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
if notificationType != winipcfg.MibDeleteInstance {
return
}
updater.Update()
})
if err != nil {
+7 -7
View File
@@ -71,7 +71,7 @@ func TestCommanderListenConfigurationItem(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
Tag: "default-outbound",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
@@ -166,7 +166,7 @@ func TestCommanderRemoveHandler(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
Tag: "default-outbound",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
@@ -262,7 +262,7 @@ func TestCommanderListHandlers(t *testing.T) {
{
Tag: "default-outbound",
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
@@ -388,7 +388,7 @@ func TestCommanderAddRemoveUser(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -428,7 +428,7 @@ func TestCommanderAddRemoveUser(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: u2.String(),
SecuritySettings: &protocol.SecurityConfig{
@@ -576,7 +576,7 @@ func TestCommanderStats(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -603,7 +603,7 @@ func TestCommanderStats(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
SecuritySettings: &protocol.SecurityConfig{
+2 -2
View File
@@ -60,7 +60,7 @@ func TestDokodemoTCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -160,7 +160,7 @@ func TestDokodemoUDP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+9 -9
View File
@@ -62,7 +62,7 @@ func TestPassiveConnection(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -123,7 +123,7 @@ func TestProxy(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -152,7 +152,7 @@ func TestProxy(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -252,7 +252,7 @@ func TestProxyOverKCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -281,7 +281,7 @@ func TestProxyOverKCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
@@ -400,7 +400,7 @@ func TestBlackhole(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
Tag: "direct",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
{
Tag: "blocked",
@@ -515,7 +515,7 @@ func TestUDPConnection(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -580,7 +580,7 @@ func TestDomainSniffing(t *testing.T) {
},
{
Tag: "direct",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
App: []*serial.TypedMessage{
@@ -667,7 +667,7 @@ func TestDialXray(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+5 -5
View File
@@ -48,7 +48,7 @@ func TestHttpConformance(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -111,7 +111,7 @@ func TestHttpError(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -161,7 +161,7 @@ func TestHTTPConnectMethod(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -243,7 +243,7 @@ func TestHttpPost(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -315,7 +315,7 @@ func TestHttpBasicAuth(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+1 -1
View File
@@ -63,7 +63,7 @@ func TestMetrics(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
Tag: "default-outbound",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
+2 -2
View File
@@ -85,7 +85,7 @@ func TestVMessClosing(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -193,7 +193,7 @@ func TestZeroBuffer(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+2 -2
View File
@@ -151,7 +151,7 @@ func TestReverseProxy(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
Tag: "freedom",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
{
Tag: "reverse",
@@ -340,7 +340,7 @@ func TestReverseProxyLongRunning(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
Tag: "freedom",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
{
Tag: "reverse",
+2 -2
View File
@@ -82,7 +82,7 @@ func testShadowsocks2022Tcp(t *testing.T, method string, password string) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -167,7 +167,7 @@ func testShadowsocks2022Udp(t *testing.T, method string, password string) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+5 -5
View File
@@ -53,7 +53,7 @@ func TestShadowsocksChaCha20Poly1305TCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -141,7 +141,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -236,7 +236,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -330,7 +330,7 @@ func TestShadowsocksAES128GCMUDPMux(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -425,7 +425,7 @@ func TestShadowsocksNone(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+5 -5
View File
@@ -51,7 +51,7 @@ func TestSocksBridgeTCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -128,7 +128,7 @@ func TestSocksWithHttpRequest(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -217,7 +217,7 @@ func TestSocksBridageUDP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
@@ -332,7 +332,7 @@ func TestSocksBridageUDPWithRouting(t *testing.T) {
},
{
Tag: "out",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
@@ -429,7 +429,7 @@ func TestSocksConformanceMod(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+10 -10
View File
@@ -69,7 +69,7 @@ func TestSimpleTLSConnection(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -182,7 +182,7 @@ func TestAutoIssuingCertificate(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -287,7 +287,7 @@ func TestTLSOverKCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -387,7 +387,7 @@ func TestTLSOverWebSocket(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -503,7 +503,7 @@ func TestGRPC(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -619,7 +619,7 @@ func TestGRPCMultiMode(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -728,7 +728,7 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -827,7 +827,7 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -925,7 +925,7 @@ func TestUTLSConnectionPinned(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -1025,7 +1025,7 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+1 -1
View File
@@ -63,7 +63,7 @@ func TestHTTPConnectionHeader(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+5 -5
View File
@@ -67,7 +67,7 @@ func TestVless(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -172,7 +172,7 @@ func TestVlessTls(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -295,7 +295,7 @@ func TestVlessXtlsVision(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -427,7 +427,7 @@ func TestVlessXtlsVisionReality(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -566,7 +566,7 @@ func TestVlessRealityFingerprints(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+12 -12
View File
@@ -62,7 +62,7 @@ func TestVMessGCM(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -162,7 +162,7 @@ func TestVMessGCMReadv(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -265,7 +265,7 @@ func TestVMessGCMUDP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -362,7 +362,7 @@ func TestVMessChacha20(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -460,7 +460,7 @@ func TestVMessNone(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -560,7 +560,7 @@ func TestVMessKCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -665,7 +665,7 @@ func TestVMessKCPLarge(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -771,7 +771,7 @@ func TestVMessGCMMux(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -884,7 +884,7 @@ func TestVMessGCMMuxUDP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -1007,7 +1007,7 @@ func TestVMessZero(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -1104,7 +1104,7 @@ func TestVMessGCMLengthAuth(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -1206,7 +1206,7 @@ func TestVMessGCMLengthAuthPlusNoTerminationSignal(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+1 -1
View File
@@ -63,7 +63,7 @@ func TestWireguard(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+21 -18
View File
@@ -20,10 +20,10 @@ import (
var webpage []byte
type task struct {
Method string `json:"method"`
URL string `json:"url"`
Extra any `json:"extra,omitempty"`
StreamResponse bool `json:"streamResponse"`
Method string `json:"method"`
URL string `json:"url"`
Extra any `json:"extra,omitempty"`
StreamResponse bool `json:"streamResponse"`
}
var conns chan *websocket.Conn
@@ -51,9 +51,9 @@ func Reload() {
if HasBrowserDialer() {
for len(conns) > 0 {
select {
case c := <-conns:
c.Close()
default:
case c := <-conns:
c.Close()
default:
}
}
conns = nil
@@ -75,7 +75,7 @@ func Reload() {
}
}
} else {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Origin", "*");
w.Write(webpage)
}
}),
@@ -94,13 +94,15 @@ type webSocketExtra struct {
func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
task := task{
Method: "WS",
URL: uri,
Method: "WS",
URL: uri,
StreamResponse: true,
}
task.Extra = webSocketExtra{
Protocol: base64.RawURLEncoding.EncodeToString(ed),
if ed != nil {
task.Extra = webSocketExtra{
Protocol: base64.RawURLEncoding.EncodeToString(ed),
}
}
return dialTask(task)
@@ -142,9 +144,9 @@ func httpExtraFromHeadersAndCookies(headers http.Header, cookies []*http.Cookie)
func DialGet(uri string, headers http.Header, cookies []*http.Cookie) (*websocket.Conn, error) {
task := task{
Method: "GET",
URL: uri,
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
Method: "GET",
URL: uri,
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
StreamResponse: true,
}
@@ -157,9 +159,9 @@ func DialPacket(method string, uri string, headers http.Header, cookies []*http.
func dialWithBody(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
task := task{
Method: method,
URL: uri,
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
Method: method,
URL: uri,
Extra: httpExtraFromHeadersAndCookies(headers, cookies),
StreamResponse: false,
}
@@ -220,3 +222,4 @@ func CheckOK(conn *websocket.Conn) error {
func init() {
Reload()
}
+3 -1
View File
@@ -12,7 +12,7 @@ var (
globalTransportConfigCreatorCache = make(map[string]ConfigCreator)
)
var strategy = [11][3]byte{
var strategy = [][]byte{
// name strategy, prefer, fallback
{0, 0, 0}, // AsIs none, /, /
{1, 0, 0}, // UseIP use, both, none
@@ -27,6 +27,8 @@ var strategy = [11][3]byte{
{2, 6, 4}, // ForceIPv6v4 force, 6, 4
}
const unknownProtocol = "unknown"
func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error {
if _, found := globalTransportConfigCreatorCache[name]; found {
return errors.New("protocol ", name, " is already registered").AtError()
@@ -279,7 +279,7 @@ func runVLESSRealityCase(t *testing.T, bin string, mode trafficMode, payloadSize
},
Outbound: []*core.OutboundHandlerConfig{
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
})},
},
})
@@ -399,7 +399,7 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
},
Outbound: []*core.OutboundHandlerConfig{
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
})},
},
})
@@ -517,7 +517,7 @@ func runVLesseEncCase(t *testing.T, bin string, mode trafficMode, payloadSize in
},
Outbound: []*core.OutboundHandlerConfig{
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
})},
},
})
@@ -617,7 +617,7 @@ func runVLESSXHTTPCase(t *testing.T, bin string, mode trafficMode, payloadSize i
},
Outbound: []*core.OutboundHandlerConfig{
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
})},
},
})
+23 -60
View File
@@ -1,82 +1,45 @@
package hysteria
import (
"context"
"math/rand"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/proxy/hysteria/account"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/padding"
)
const (
closeErrCodeOK = 0x100 // HTTP3 ErrCodeNoError
closeErrCodeProtocolError = 0x101 // HTTP3 ErrCodeGeneralProtocolError
URLHost = "hysteria"
URLPath = "/auth"
RequestHeaderAuth = "Hysteria-Auth"
ResponseHeaderUDPEnabled = "Hysteria-UDP"
CommonHeaderCCRX = "Hysteria-CC-RX"
CommonHeaderPadding = "Hysteria-Padding"
StatusAuthOK = 233
FrameTypeTCPRequest = 0x401
MaxDatagramFrameSize = 1200
udpMessageChanSize = 1024
idleCleanupInterval = 1 * time.Second
MaxDatagramFrameSize = 1200
URLHost = "hysteria"
URLPath = "/auth"
RequestHeaderAuth = "Hysteria-Auth"
ResponseHeaderUDPEnabled = "Hysteria-UDP"
CommonHeaderCCRX = "Hysteria-CC-RX"
CommonHeaderPadding = "Hysteria-Padding"
StatusAuthOK = 233
udpMessageChanSize = 1024
FrameTypeTCPRequest = 0x401
idleCleanupInterval = 1 * time.Second
)
const (
paddingChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
type padding struct {
Min int
Max int
}
func (p padding) String() string {
n := p.Min + rand.Intn(p.Max-p.Min)
bs := make([]byte, n)
for i := range bs {
bs[i] = paddingChars[rand.Intn(len(paddingChars))]
}
return string(bs)
}
var (
AuthRequestPadding = padding{Min: 256, Max: 2048}
AuthResponsePadding = padding{Min: 256, Max: 2048}
TcpRequestPadding = padding{Min: 64, Max: 512}
TcpResponsePadding = padding{Min: 128, Max: 1024}
authRequestPadding = padding.Padding{Min: 256, Max: 2048}
authResponsePadding = padding.Padding{Min: 256, Max: 2048}
)
type datagramKey struct{}
func ContextWithDatagram(ctx context.Context, v bool) context.Context {
return context.WithValue(ctx, datagramKey{}, v)
}
func DatagramFromContext(ctx context.Context) bool {
v, _ := ctx.Value(datagramKey{}).(bool)
return v
}
type validatorKey struct{}
func ContextWithValidator(ctx context.Context, v *account.Validator) context.Context {
return context.WithValue(ctx, validatorKey{}, v)
}
func ValidatorFromContext(ctx context.Context) *account.Validator {
v, _ := ctx.Value(validatorKey{}).(*account.Validator)
return v
}
type status int
type Status int
const (
StatusNull status = iota
StatusUnknown Status = iota
StatusActive
StatusInactive
)
+103 -240
View File
@@ -1,7 +1,6 @@
package hysteria
import (
"context"
"encoding/binary"
"io"
"sync"
@@ -9,10 +8,8 @@ import (
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/quicvarint"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/transport/internet"
)
type interConn struct {
@@ -21,278 +18,144 @@ type interConn struct {
remote net.Addr
client bool
user *protocol.MemoryUser
}
func (c *interConn) User() *protocol.MemoryUser {
return c.user
}
func (c *interConn) Read(b []byte) (int, error) {
return c.stream.Read(b)
}
func (c *interConn) Write(b []byte) (int, error) {
if c.client {
c.client = false
if _, err := c.stream.Write(append(quicvarint.Append(nil, FrameTypeTCPRequest), b...)); err != nil {
return 0, err
}
return len(b), nil
}
return c.stream.Write(b)
}
func (c *interConn) Close() error {
c.stream.CancelRead(0)
return c.stream.Close()
}
func (c *interConn) LocalAddr() net.Addr {
return c.local
}
func (c *interConn) RemoteAddr() net.Addr {
return c.remote
}
func (c *interConn) SetDeadline(t time.Time) error {
return c.stream.SetDeadline(t)
}
func (c *interConn) SetReadDeadline(t time.Time) error {
return c.stream.SetReadDeadline(t)
}
func (c *interConn) SetWriteDeadline(t time.Time) error {
return c.stream.SetWriteDeadline(t)
}
type InterConn struct {
local net.Addr
remote net.Addr
id uint32
ch chan []byte
time time.Time
mutex sync.Mutex
closed bool
write func(p []byte) error
close func()
user *protocol.MemoryUser
user *protocol.MemoryUser
}
func (i *InterConn) User() *protocol.MemoryUser {
func (i *interConn) User() *protocol.MemoryUser {
return i.user
}
func (c *InterConn) Time() time.Time {
c.mutex.Lock()
v := c.time
c.mutex.Unlock()
return v
func (i *interConn) Read(b []byte) (int, error) {
return i.stream.Read(b)
}
func (c *InterConn) Update() {
c.mutex.Lock()
c.time = time.Now()
c.mutex.Unlock()
func (i *interConn) Write(b []byte) (int, error) {
if i.client {
i.mutex.Lock()
defer i.mutex.Unlock()
if i.client {
buf := make([]byte, 0, quicvarint.Len(FrameTypeTCPRequest)+len(b))
buf = quicvarint.Append(buf, FrameTypeTCPRequest)
buf = append(buf, b...)
_, err := i.stream.Write(buf)
if err != nil {
return 0, err
}
i.client = false
return len(b), nil
}
}
return i.stream.Write(b)
}
func (c *InterConn) Read(p []byte) (int, error) {
b, ok := <-c.ch
func (i *interConn) Close() error {
i.stream.CancelRead(0)
return i.stream.Close()
}
func (i *interConn) LocalAddr() net.Addr {
return i.local
}
func (i *interConn) RemoteAddr() net.Addr {
return i.remote
}
func (i *interConn) SetDeadline(t time.Time) error {
return i.stream.SetDeadline(t)
}
func (i *interConn) SetReadDeadline(t time.Time) error {
return i.stream.SetReadDeadline(t)
}
func (i *interConn) SetWriteDeadline(t time.Time) error {
return i.stream.SetWriteDeadline(t)
}
type InterUdpConn struct {
conn *quic.Conn
local net.Addr
remote net.Addr
id uint32
ch chan []byte
closed bool
closeFunc func()
last time.Time
mutex sync.Mutex
user *protocol.MemoryUser
}
func (i *InterUdpConn) User() *protocol.MemoryUser {
return i.user
}
func (i *InterUdpConn) SetLast() {
i.mutex.Lock()
defer i.mutex.Unlock()
i.last = time.Now()
}
func (i *InterUdpConn) GetLast() time.Time {
i.mutex.Lock()
defer i.mutex.Unlock()
return i.last
}
func (i *InterUdpConn) Read(p []byte) (int, error) {
b, ok := <-i.ch
if !ok {
return 0, io.EOF
}
if len(p) < len(b) {
n := copy(p, b)
if n != len(b) {
return 0, io.ErrShortBuffer
}
c.Update()
return copy(p, b), nil
i.SetLast()
return n, nil
}
func (c *InterConn) Write(p []byte) (int, error) {
if c.closed {
return 0, io.ErrClosedPipe
}
binary.BigEndian.PutUint32(p, c.id)
if err := c.write(p); err != nil {
func (i *InterUdpConn) Write(p []byte) (int, error) {
i.SetLast()
binary.BigEndian.PutUint32(p, i.id)
if err := i.conn.SendDatagram(p); err != nil {
return 0, err
}
c.Update()
return len(p), nil
}
func (c *InterConn) Close() error {
c.close()
func (i *InterUdpConn) Close() error {
i.closeFunc()
return nil
}
func (c *InterConn) LocalAddr() net.Addr {
return c.local
func (i *InterUdpConn) LocalAddr() net.Addr {
return i.local
}
func (c *InterConn) RemoteAddr() net.Addr {
return c.remote
func (i *InterUdpConn) RemoteAddr() net.Addr {
return i.remote
}
func (c *InterConn) SetDeadline(t time.Time) error {
func (i *InterUdpConn) SetDeadline(t time.Time) error {
return nil
}
func (c *InterConn) SetReadDeadline(t time.Time) error {
func (i *InterUdpConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *InterConn) SetWriteDeadline(t time.Time) error {
func (i *InterUdpConn) SetWriteDeadline(t time.Time) error {
return nil
}
type udpSessionManager struct {
sync.RWMutex
conn *quic.Conn
m map[uint32]*InterConn
next uint32
closed bool
addConn internet.ConnHandler
udpIdleTimeout time.Duration
user *protocol.MemoryUser
}
func (m *udpSessionManager) close(udpConn *InterConn) {
if !udpConn.closed {
udpConn.closed = true
close(udpConn.ch)
delete(m.m, udpConn.id)
}
}
func (m *udpSessionManager) clean() {
ticker := time.NewTicker(idleCleanupInterval)
defer ticker.Stop()
for range ticker.C {
if m.closed {
return
}
m.RLock()
now := time.Now()
timeoutConn := make([]*InterConn, 0, len(m.m))
for _, udpConn := range m.m {
if now.Sub(udpConn.Time()) > m.udpIdleTimeout {
timeoutConn = append(timeoutConn, udpConn)
}
}
m.RUnlock()
for _, udpConn := range timeoutConn {
m.Lock()
m.close(udpConn)
m.Unlock()
}
}
}
func (m *udpSessionManager) run() {
for {
d, err := m.conn.ReceiveDatagram(context.Background())
if err != nil {
break
}
if len(d) < 4 {
continue
}
id := binary.BigEndian.Uint32(d[:4])
m.feed(id, d)
}
m.Lock()
defer m.Unlock()
m.closed = true
for _, udpConn := range m.m {
m.close(udpConn)
}
}
func (m *udpSessionManager) udp() (*InterConn, error) {
m.Lock()
defer m.Unlock()
if m.closed {
return nil, errors.New("closed")
}
udpConn := &InterConn{
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: m.next,
ch: make(chan []byte, udpMessageChanSize),
}
udpConn.write = m.conn.SendDatagram
udpConn.close = func() {
m.Lock()
m.close(udpConn)
m.Unlock()
}
m.m[m.next] = udpConn
m.next++
return udpConn, nil
}
func (m *udpSessionManager) feed(id uint32, d []byte) {
m.RLock()
udpConn, ok := m.m[id]
if ok {
select {
case udpConn.ch <- d:
default:
}
m.RUnlock()
return
}
m.RUnlock()
if m.addConn == nil {
return
}
m.Lock()
defer m.Unlock()
udpConn, ok = m.m[id]
if !ok {
udpConn = &InterConn{
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: id,
ch: make(chan []byte, udpMessageChanSize),
time: time.Now(),
}
udpConn.write = m.conn.SendDatagram
udpConn.close = func() {
m.Lock()
m.close(udpConn)
m.Unlock()
}
udpConn.user = m.user
m.m[id] = udpConn
m.addConn(udpConn)
}
select {
case udpConn.ch <- d:
default:
}
}
+261 -133
View File
@@ -3,10 +3,11 @@ package hysteria
import (
"context"
go_tls "crypto/tls"
"encoding/binary"
"math/rand"
"net/http"
"net/url"
"reflect"
"runtime"
"strconv"
"sync"
"time"
@@ -17,6 +18,8 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/task"
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
@@ -26,25 +29,107 @@ import (
"github.com/xtls/xray-core/transport/internet/tls"
)
type client struct {
sync.Mutex
type udpSessionManagerClient struct {
conn *quic.Conn
m map[uint32]*InterUdpConn
next uint32
closed bool
mutex sync.RWMutex
}
func (m *udpSessionManagerClient) close(udpConn *InterUdpConn) {
if !udpConn.closed {
udpConn.closed = true
close(udpConn.ch)
delete(m.m, udpConn.id)
}
}
func (m *udpSessionManagerClient) run() {
for {
d, err := m.conn.ReceiveDatagram(context.Background())
if err != nil {
break
}
if len(d) < 4 {
continue
}
id := binary.BigEndian.Uint32(d[:4])
m.feed(id, d)
}
m.mutex.Lock()
defer m.mutex.Unlock()
m.closed = true
for _, udpConn := range m.m {
m.close(udpConn)
}
}
func (m *udpSessionManagerClient) udp() (*InterUdpConn, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
if m.closed {
return nil, errors.New("closed")
}
udpConn := &InterUdpConn{
conn: m.conn,
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: m.next,
ch: make(chan []byte, udpMessageChanSize),
}
udpConn.closeFunc = func() {
m.mutex.Lock()
defer m.mutex.Unlock()
m.close(udpConn)
}
m.m[m.next] = udpConn
m.next++
return udpConn, nil
}
func (m *udpSessionManagerClient) feed(id uint32, d []byte) {
m.mutex.RLock()
defer m.mutex.RUnlock()
udpConn, ok := m.m[id]
if !ok {
return
}
select {
case udpConn.ch <- d:
default:
}
}
type client struct {
ctx context.Context
dest net.Destination
pktConn net.PacketConn
conn *quic.Conn
config *Config
tlsConfig *go_tls.Config
socketConfig *internet.SocketConfig
udpmaskManager *finalmask.UdpmaskManager
quicParams *internet.QuicParams
conn *quic.Conn
tr *quic.Transport
pktConn net.PacketConn
udpSM *udpSessionManager
udpSM *udpSessionManagerClient
mutex sync.Mutex
}
func (c *client) status() status {
func (c *client) status() Status {
if c.conn == nil {
return StatusNull
return StatusUnknown
}
select {
case <-c.conn.Context().Done():
@@ -55,12 +140,10 @@ func (c *client) status() status {
}
func (c *client) close() {
c.conn.CloseWithError(closeErrCodeOK, "")
c.tr.Close()
c.pktConn.Close()
c.conn = nil
c.tr = nil
_ = c.conn.CloseWithError(closeErrCodeOK, "")
_ = c.pktConn.Close()
c.pktConn = nil
c.conn = nil
c.udpSM = nil
}
@@ -81,6 +164,61 @@ func (c *client) dial() error {
}
}
var index int
if len(quicParams.UdpHop.Ports) > 0 {
index = rand.Intn(len(quicParams.UdpHop.Ports))
c.dest.Port = net.Port(quicParams.UdpHop.Ports[index])
}
raw, err := internet.DialSystem(c.ctx, c.dest, c.socketConfig)
if err != nil {
return errors.New("failed to dial to dest").Base(err)
}
var pktConn net.PacketConn
var remote *net.UDPAddr
switch conn := raw.(type) {
case *internet.PacketConnWrapper:
pktConn = conn.PacketConn
remote = conn.RemoteAddr().(*net.UDPAddr)
case *net.UDPConn:
pktConn = conn
remote = conn.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
fakeConn := &internet.FakePacketConn{Conn: conn}
pktConn = fakeConn
remote = fakeConn.RemoteAddr().(*net.UDPAddr)
if len(quicParams.UdpHop.Ports) > 0 {
raw.Close()
return errors.New("udphop requires being at the outermost level")
}
default:
raw.Close()
return errors.New("unknown conn ", reflect.TypeOf(conn))
}
if len(quicParams.UdpHop.Ports) > 0 {
addr := &udphop.UDPHopAddr{
IP: remote.IP,
Ports: quicParams.UdpHop.Ports,
}
pktConn, err = udphop.NewUDPHopPacketConn(addr, index, quicParams.UdpHop.IntervalMin, quicParams.UdpHop.IntervalMax, c.udphopDialer, pktConn)
if err != nil {
raw.Close()
return errors.New("udphop err").Base(err)
}
}
if c.udpmaskManager != nil {
pktConn, err = c.udpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
raw.Close()
return errors.New("mask err").Base(err)
}
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: quicParams.InitStreamReceiveWindow,
MaxStreamReceiveWindow: quicParams.MaxStreamReceiveWindow,
@@ -88,10 +226,9 @@ func (c *client) dial() error {
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
KeepAlivePeriod: time.Duration(quicParams.KeepAlivePeriod) * time.Second,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery,
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
OmitMaxDatagramFrameSize: time.Now().After(time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC)),
DisablePathManager: true,
}
if quicParams.InitStreamReceiveWindow == 0 {
@@ -113,56 +250,16 @@ func (c *client) dial() error {
// quicConfig.KeepAlivePeriod = 10 * time.Second
// }
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
var err error
udpAddr, err = net.ResolveUDPAddr("udp", c.dest.NetAddr())
if err != nil {
return err
}
if len(quicParams.UdpHop.Ports) > 0 {
pktConn, err = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, c.udpHopDialer)
if err != nil {
return err
}
} else {
conn, err := internet.DialSystem(context.Background(), c.dest, c.socketConfig)
if err != nil {
return err
}
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *net.UDPConn:
pktConn = c
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
}
if c.udpmaskManager != nil {
newConn, err := c.udpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return errors.New("mask err").Base(err)
}
pktConn = newConn
}
tr := &quic.Transport{Conn: pktConn}
var conn *quic.Conn
var quicConn *quic.Conn
rt := &http3.Transport{
TLSClientConfig: c.tlsConfig,
QUICConfig: quicConfig,
Dial: func(ctx context.Context, _ string, tlsCfg *go_tls.Config, cfg *quic.Config) (*quic.Conn, error) {
qc, err := tr.DialEarly(ctx, udpAddr, tlsCfg, cfg)
qc, err := quic.DialEarly(ctx, pktConn, remote, tlsCfg, cfg)
if err != nil {
return nil, err
}
conn = qc
quicConn = qc
return qc, nil
},
}
@@ -176,61 +273,75 @@ func (c *client) dial() error {
Header: http.Header{
RequestHeaderAuth: []string{c.config.Auth},
CommonHeaderCCRX: []string{strconv.FormatUint(quicParams.BrutalDown, 10)},
CommonHeaderPadding: []string{AuthRequestPadding.String()},
CommonHeaderPadding: []string{authRequestPadding.String()},
},
}
resp, err := rt.RoundTrip(req)
if err != nil {
if conn != nil {
_ = conn.CloseWithError(closeErrCodeProtocolError, "")
if quicConn != nil {
_ = quicConn.CloseWithError(closeErrCodeProtocolError, "")
}
_ = tr.Close()
_ = pktConn.Close()
return err
return errors.New("RoundTrip err").Base(err)
}
if resp.StatusCode != StatusAuthOK {
_ = conn.CloseWithError(closeErrCodeProtocolError, "")
_ = tr.Close()
_ = quicConn.CloseWithError(closeErrCodeProtocolError, "")
_ = pktConn.Close()
return errors.New("auth failed code ", resp.StatusCode)
return errors.New("auth failed")
}
_ = resp.Body.Close()
// udp, _ := strconv.ParseBool(resp.Header.Get(ResponseHeaderUDPEnabled))
down, _ := strconv.ParseUint(resp.Header.Get(CommonHeaderCCRX), 10, 64)
serverUdp, _ := strconv.ParseBool(resp.Header.Get(ResponseHeaderUDPEnabled))
serverAuto := resp.Header.Get(CommonHeaderCCRX)
serverDown, _ := strconv.ParseUint(serverAuto, 10, 64)
switch quicParams.Congestion {
case "reno":
errors.LogDebug(c.ctx, "congestion reno")
case "bbr":
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
case "", "brutal":
if quicParams.BrutalUp == 0 || down == 0 {
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
errors.LogDebug(c.ctx, "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
case "brutal", "":
if serverAuto == "auto" || quicParams.BrutalUp == 0 || serverDown == 0 {
errors.LogDebug(c.ctx, "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
} else {
congestion.UseBrutal(conn, min(quicParams.BrutalUp, down))
errors.LogDebug(c.ctx, "congestion brutal bytes per second ", min(quicParams.BrutalUp, serverDown))
congestion.UseBrutal(quicConn, min(quicParams.BrutalUp, serverDown))
}
case "force-brutal":
congestion.UseBrutal(conn, quicParams.BrutalUp)
errors.LogDebug(c.ctx, "congestion brutal bytes per second ", quicParams.BrutalUp)
congestion.UseBrutal(quicConn, quicParams.BrutalUp)
default:
panic(quicParams.Congestion)
errors.LogDebug(c.ctx, "congestion reno")
}
c.pktConn = pktConn
c.tr = tr
c.conn = conn
c.udpSM = &udpSessionManager{
conn: conn,
m: make(map[uint32]*InterConn),
next: 1,
c.conn = quicConn
if serverUdp {
c.udpSM = &udpSessionManagerClient{
conn: quicConn,
m: make(map[uint32]*InterUdpConn),
next: 1,
}
go c.udpSM.run()
}
go c.udpSM.run()
return nil
}
func (c *client) clean() {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.status() == StatusInactive {
c.close()
}
}
func (c *client) tcp() (stat.Connection, error) {
c.Lock()
defer c.Unlock()
c.mutex.Lock()
defer c.mutex.Unlock()
err := c.dial()
if err != nil {
@@ -252,43 +363,59 @@ func (c *client) tcp() (stat.Connection, error) {
}
func (c *client) udp() (stat.Connection, error) {
c.Lock()
defer c.Unlock()
c.mutex.Lock()
defer c.mutex.Unlock()
err := c.dial()
if err != nil {
return nil, err
}
if c.udpSM == nil {
return nil, errors.New("server does not support udp")
}
return c.udpSM.udp()
}
func (c *client) clean() {
c.Lock()
if c.status() == StatusInactive {
c.close()
}
c.Unlock()
func (c *client) setCtx(ctx context.Context) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.ctx = ctx
}
func (c *client) udpHopDialer(addr *net.UDPAddr) (net.PacketConn, error) {
conn, err := internet.DialSystem(context.Background(), net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig)
func (c *client) udphopDialer(addr *net.UDPAddr) (net.PacketConn, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.status() != StatusActive {
errors.LogDebug(context.Background(), "skip hop: disconnected QUIC")
return nil, errors.New()
}
raw, err := internet.DialSystem(c.ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig)
if err != nil {
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("failed to dial to dest").Base(err)
errors.LogDebug(context.Background(), "skip hop: failed to dial to dest")
raw.Close()
return nil, errors.New()
}
var pktConn net.PacketConn
switch c := conn.(type) {
switch conn := raw.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
pktConn = conn.PacketConn
case *net.UDPConn:
pktConn = c
pktConn = conn
case *cnc.Connection:
errors.LogDebug(context.Background(), "skip hop: udphop requires being at the outermost level")
raw.Close()
return nil, errors.New()
default:
errors.LogInfo(context.Background(), "skip hop: invalid conn ", reflect.TypeOf(c))
conn.Close()
return nil, errors.New("invalid conn ", reflect.TypeOf(c))
errors.LogDebug(context.Background(), "skip hop: unknown conn ", reflect.TypeOf(conn))
raw.Close()
return nil, errors.New()
}
return pktConn, nil
@@ -300,18 +427,16 @@ type dialerConf struct {
}
type clientManager struct {
sync.RWMutex
m map[dialerConf]*client
m map[dialerConf]*client
mutex sync.Mutex
}
func (m *clientManager) clean() {
ticker := time.NewTicker(idleCleanupInterval)
for range ticker.C {
m.RLock()
for _, c := range m.m {
c.clean()
}
m.RUnlock()
m.mutex.Lock()
defer m.mutex.Unlock()
for _, c := range m.m {
c.clean()
}
}
@@ -324,38 +449,41 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
return nil, errors.New("tls config is nil")
}
datagram := DatagramFromContext(ctx)
requireDatagram := hyCtx.RequireDatagramFromContext(ctx)
dest.Network = net.Network_UDP
config := streamSettings.ProtocolSettings.(*Config)
initmanager.Do(func() {
manager = &clientManager{
m: make(map[dialerConf]*client),
}
go manager.clean()
(&task.Periodic{
Interval: 30 * time.Second,
Execute: func() error {
manager.clean()
return nil
},
}).Start()
})
manager.RLock()
c := manager.m[dialerConf{dest, streamSettings}]
manager.RUnlock()
if c == nil {
manager.Lock()
c = manager.m[dialerConf{dest, streamSettings}]
if c == nil {
c = &client{
dest: dest,
config: streamSettings.ProtocolSettings.(*Config),
tlsConfig: tlsConfig.GetTLSConfig(),
socketConfig: streamSettings.SocketSettings,
udpmaskManager: streamSettings.UdpmaskManager,
quicParams: streamSettings.QuicParams,
}
manager.m[dialerConf{dest, streamSettings}] = c
manager.mutex.Lock()
c, ok := manager.m[dialerConf{Destination: dest, MemoryStreamConfig: streamSettings}]
if !ok {
c = &client{
ctx: ctx,
dest: dest,
config: config,
tlsConfig: tlsConfig.GetTLSConfig(),
socketConfig: streamSettings.SocketSettings,
udpmaskManager: streamSettings.UdpmaskManager,
quicParams: streamSettings.QuicParams,
}
manager.Unlock()
manager.m[dialerConf{Destination: dest, MemoryStreamConfig: streamSettings}] = c
}
c.setCtx(ctx)
manager.mutex.Unlock()
if datagram {
if requireDatagram {
return c.udp()
}
return c.tcp()
+210 -93
View File
@@ -3,10 +3,10 @@ package hysteria
import (
"context"
gotls "crypto/tls"
"encoding/binary"
"net/http"
"net/http/httputil"
"net/url"
"runtime"
"strconv"
"strings"
"sync"
@@ -20,41 +20,158 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/hysteria/account"
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/tls"
)
type httpHandler struct {
sync.Mutex
type udpSessionManagerServer struct {
conn *quic.Conn
m map[uint32]*InterUdpConn
addConn internet.ConnHandler
stopCh chan struct{}
udpIdleTimeout time.Duration
mutex sync.RWMutex
validator *account.Validator
config *Config
masqHandler http.Handler
quicParams *internet.QuicParams
addConn internet.ConnHandler
conn *quic.Conn
auth bool
user *protocol.MemoryUser
}
func (h *httpHandler) AuthHTTP(w http.ResponseWriter, r *http.Request) bool {
func (m *udpSessionManagerServer) close(udpConn *InterUdpConn) {
if !udpConn.closed {
udpConn.closed = true
close(udpConn.ch)
delete(m.m, udpConn.id)
}
}
func (m *udpSessionManagerServer) clean() {
ticker := time.NewTicker(idleCleanupInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
m.mutex.RLock()
now := time.Now()
timeoutConn := make([]*InterUdpConn, 0, len(m.m))
for _, udpConn := range m.m {
if now.Sub(udpConn.GetLast()) > m.udpIdleTimeout {
timeoutConn = append(timeoutConn, udpConn)
}
}
m.mutex.RUnlock()
for _, udpConn := range timeoutConn {
m.mutex.Lock()
m.close(udpConn)
m.mutex.Unlock()
}
case <-m.stopCh:
return
}
}
}
func (m *udpSessionManagerServer) run() {
for {
d, err := m.conn.ReceiveDatagram(context.Background())
if err != nil {
break
}
if len(d) < 4 {
continue
}
id := binary.BigEndian.Uint32(d[:4])
m.feed(id, d)
}
m.mutex.Lock()
defer m.mutex.Unlock()
close(m.stopCh)
for _, udpConn := range m.m {
m.close(udpConn)
}
}
func (m *udpSessionManagerServer) feed(id uint32, d []byte) {
m.mutex.RLock()
udpConn, ok := m.m[id]
if ok {
select {
case udpConn.ch <- d:
default:
}
m.mutex.RUnlock()
return
}
m.mutex.RUnlock()
m.mutex.Lock()
defer m.mutex.Unlock()
udpConn, ok = m.m[id]
if !ok {
udpConn = &InterUdpConn{
conn: m.conn,
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: id,
ch: make(chan []byte, udpMessageChanSize),
last: time.Now(),
user: m.user,
}
udpConn.closeFunc = func() {
m.mutex.Lock()
m.close(udpConn)
m.mutex.Unlock()
}
m.m[id] = udpConn
m.addConn(udpConn)
}
select {
case udpConn.ch <- d:
default:
}
}
type httpHandler struct {
ctx context.Context
conn *quic.Conn
addConn internet.ConnHandler
config *Config
quicParams *internet.QuicParams
validator *account.Validator
masqHandler http.Handler
auth bool
mutex sync.Mutex
user *protocol.MemoryUser
}
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost && r.Host == URLHost && r.URL.Path == URLPath {
h.Lock()
defer h.Unlock()
h.mutex.Lock()
defer h.mutex.Unlock()
if h.auth {
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(h.validator != nil))
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.quicParams.BrutalDown, 10))
w.Header().Set(CommonHeaderPadding, AuthResponsePadding.String())
w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
w.WriteHeader(StatusAuthOK)
return true
return
}
auth := r.Header.Get(RequestHeaderAuth)
down, _ := strconv.ParseUint(r.Header.Get(CommonHeaderCCRX), 10, 64)
clientDown, _ := strconv.ParseUint(r.Header.Get(CommonHeaderCCRX), 10, 64)
var user *protocol.MemoryUser
var ok bool
@@ -68,51 +185,49 @@ func (h *httpHandler) AuthHTTP(w http.ResponseWriter, r *http.Request) bool {
h.auth = true
h.user = user
conn := h.conn
quicParams := h.quicParams
switch quicParams.Congestion {
switch h.quicParams.Congestion {
case "reno":
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
case "bbr":
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
case "", "brutal":
if quicParams.BrutalUp == 0 || down == 0 {
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr ", h.quicParams.BbrProfile)
congestion.UseBBR(h.conn, bbr.Profile(h.quicParams.BbrProfile))
case "brutal", "":
if h.quicParams.BrutalUp == 0 || clientDown == 0 {
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr ", h.quicParams.BbrProfile)
congestion.UseBBR(h.conn, bbr.Profile(h.quicParams.BbrProfile))
} else {
congestion.UseBrutal(conn, min(quicParams.BrutalUp, down))
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", min(h.quicParams.BrutalUp, clientDown))
congestion.UseBrutal(h.conn, min(h.quicParams.BrutalUp, clientDown))
}
case "force-brutal":
congestion.UseBrutal(conn, quicParams.BrutalUp)
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", h.quicParams.BrutalUp)
congestion.UseBrutal(h.conn, h.quicParams.BrutalUp)
default:
panic(quicParams.Congestion)
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
}
if h.validator != nil {
udpSM := &udpSessionManager{
conn: h.conn,
m: make(map[uint32]*InterConn),
if hyCtx.RequireDatagramFromContext(h.ctx) {
udpSM := &udpSessionManagerServer{
conn: h.conn,
m: make(map[uint32]*InterUdpConn),
addConn: h.addConn,
stopCh: make(chan struct{}),
udpIdleTimeout: time.Duration(h.config.UdpIdleTimeout) * time.Second,
user: h.user,
user: h.user,
}
go udpSM.clean()
go udpSM.run()
}
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(h.validator != nil))
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.quicParams.BrutalDown, 10))
w.Header().Set(CommonHeaderPadding, AuthResponsePadding.String())
w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
w.WriteHeader(StatusAuthOK)
return true
return
}
}
return false
}
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.AuthHTTP(w, r) {
return
}
h.masqHandler.ServeHTTP(w, r)
}
@@ -141,41 +256,42 @@ func (h *httpHandler) StreamDispatcher(ft http3.FrameType, stream *quic.Stream,
}
type Listener struct {
validator *account.Validator
config *Config
masqHandler http.Handler
quicParams *internet.QuicParams
addConn internet.ConnHandler
ctx context.Context
pktConn net.PacketConn
tr *quic.Transport
listener *quic.Listener
addConn internet.ConnHandler
config *Config
quicParams *internet.QuicParams
validator *account.Validator
masqHandler http.Handler
}
func (l *Listener) handleClient(conn *quic.Conn) {
handler := &httpHandler{
validator: l.validator,
ctx: l.ctx,
conn: conn,
addConn: l.addConn,
config: l.config,
masqHandler: l.masqHandler,
quicParams: l.quicParams,
addConn: l.addConn,
conn: conn,
validator: l.validator,
masqHandler: l.masqHandler,
}
h3s := http3.Server{
h3 := http3.Server{
Handler: handler,
StreamDispatcher: handler.StreamDispatcher,
}
_ = h3s.ServeQUICConn(conn)
err := h3.ServeQUICConn(conn)
_ = conn.CloseWithError(closeErrCodeOK, "")
errors.LogDebug(context.Background(), conn.RemoteAddr(), " disconnected with err ", err)
}
func (l *Listener) keepAccepting() {
for {
conn, err := l.listener.Accept(context.Background())
if err != nil {
if err != quic.ErrServerClosed {
errors.LogErrorInner(context.Background(), err, "failed to serve hysteria")
}
errors.LogInfoInner(context.Background(), err, "failed to accept QUIC connection")
break
}
go l.handleClient(conn)
@@ -187,7 +303,9 @@ func (l *Listener) Addr() net.Addr {
}
func (l *Listener) Close() error {
return errors.Combine(l.listener.Close(), l.tr.Close(), l.pktConn.Close())
err := l.listener.Close()
_ = l.pktConn.Close()
return err
}
func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {
@@ -200,10 +318,11 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
return nil, errors.New("tls config is nil")
}
validator := ValidatorFromContext(ctx)
config := streamSettings.ProtocolSettings.(*Config)
if validator == nil && config.Auth == "" {
validator := hyCtx.ValidatorFromContext(ctx)
if config.Auth == "" && validator == nil {
return nil, errors.New("validator is nil")
}
@@ -253,6 +372,22 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
return nil, errors.New("unknown masq type")
}
raw, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
var pktConn net.PacketConn
pktConn = raw
if streamSettings.UdpmaskManager != nil {
pktConn, err = streamSettings.UdpmaskManager.WrapPacketConnServer(raw)
if err != nil {
raw.Close()
return nil, errors.New("mask err").Base(err)
}
}
quicParams := streamSettings.QuicParams
if quicParams == nil {
quicParams = &internet.QuicParams{
@@ -268,10 +403,9 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
MaxIncomingStreams: quicParams.MaxIncomingStreams,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery,
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
AssumePeerMaxDatagramFrameSize: MaxDatagramFrameSize,
DisablePathManager: true,
}
if quicParams.InitStreamReceiveWindow == 0 {
@@ -293,44 +427,27 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
quicConfig.MaxIncomingStreams = 1024
}
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
qListener, err := quic.Listen(pktConn, tlsConfig.GetTLSConfig(), quicConfig)
if err != nil {
return nil, err
}
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
tr := &quic.Transport{Conn: pktConn}
listener, err := tr.Listen(tlsConfig.GetTLSConfig(), quicConfig)
if err != nil {
_ = tr.Close()
_ = pktConn.Close()
return nil, err
}
l := &Listener{
validator: validator,
config: config,
masqHandler: masqHandler,
quicParams: quicParams,
addConn: handler,
listener := &Listener{
ctx: ctx,
pktConn: pktConn,
tr: tr,
listener: listener,
listener: qListener,
addConn: handler,
config: config,
quicParams: quicParams,
validator: validator,
masqHandler: masqHandler,
}
go l.keepAccepting()
go listener.keepAccepting()
return l, nil
return listener, nil
}
func init() {
@@ -0,0 +1,24 @@
package padding
import (
"math/rand"
)
const (
paddingChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
// padding specifies a half-open range [Min, Max).
type Padding struct {
Min int
Max int
}
func (p Padding) String() string {
n := p.Min + rand.Intn(p.Max-p.Min)
bs := make([]byte, n)
for i := range bs {
bs[i] = paddingChars[rand.Intn(len(paddingChars))]
}
return string(bs)
}
@@ -0,0 +1,65 @@
package udphop
import (
"fmt"
"net"
)
type InvalidPortError struct {
PortStr string
}
func (e InvalidPortError) Error() string {
return fmt.Sprintf("%s is not a valid port number or range", e.PortStr)
}
// UDPHopAddr contains an IP address and a list of ports.
type UDPHopAddr struct {
IP net.IP
Ports []uint32
PortStr string
}
func (a *UDPHopAddr) Network() string {
return "udphop"
}
func (a *UDPHopAddr) String() string {
return net.JoinHostPort(a.IP.String(), a.PortStr)
}
// addrs returns a list of net.Addr's, one for each port.
func (a *UDPHopAddr) addrs() ([]net.Addr, error) {
var addrs []net.Addr
for _, port := range a.Ports {
addr := &net.UDPAddr{
IP: a.IP,
Port: int(port),
}
addrs = append(addrs, addr)
}
return addrs, nil
}
// func ResolveUDPHopAddr(addr string) (*UDPHopAddr, error) {
// host, portStr, err := net.SplitHostPort(addr)
// if err != nil {
// return nil, err
// }
// ip, err := net.ResolveIPAddr("ip", host)
// if err != nil {
// return nil, err
// }
// result := &UDPHopAddr{
// IP: ip.IP,
// PortStr: portStr,
// }
// pu := utils.ParsePortUnion(portStr)
// if pu == nil {
// return nil, InvalidPortError{portStr}
// }
// result.Ports = pu.Ports()
// return result, nil
// }
+114 -78
View File
@@ -8,6 +8,7 @@ import (
"syscall"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
@@ -19,19 +20,19 @@ const (
)
type UdpHopPacketConn struct {
Addr net.Addr
Addrs []net.Addr
HopIntervalMin time.Duration
HopIntervalMax time.Duration
ListenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error)
HopIntervalMin int64
HopIntervalMax int64
ListenUDPFunc ListenUDPFunc
connMutex sync.RWMutex
prevConn net.PacketConn
currentConn net.PacketConn
addrIndex int
deadline time.Time
readDeadline time.Time
writeDeadline time.Time
readBufferSize int
writeBufferSize int
recvQueue chan *udpPacket
closeChan chan struct{}
@@ -47,36 +48,41 @@ type udpPacket struct {
Err error
}
func NewUDPHopPacketConn(addrs []net.Addr, hopIntervalMin time.Duration, hopIntervalMax time.Duration, listenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error)) (net.PacketConn, error) {
if len(addrs) == 0 {
panic("len(addrs) == 0")
type ListenUDPFunc = func(*net.UDPAddr) (net.PacketConn, error)
func NewUDPHopPacketConn(addr *UDPHopAddr, index int, intervalMin int64, intervalMax int64, listenUDPFunc ListenUDPFunc, pktConn net.PacketConn) (net.PacketConn, error) {
if intervalMin == 0 || intervalMax == 0 {
intervalMin = int64(defaultHopInterval)
intervalMax = int64(defaultHopInterval)
}
if hopIntervalMin == 0 {
hopIntervalMin = defaultHopInterval
}
if hopIntervalMax == 0 {
hopIntervalMax = defaultHopInterval
}
if hopIntervalMin < 5*time.Second {
panic("hopIntervalMin < 5*time.Second")
}
if hopIntervalMax < 5*time.Second {
panic("hopIntervalMax < 5*time.Second")
}
if hopIntervalMax < hopIntervalMin {
panic("hopIntervalMax < hopIntervalMin")
if intervalMin < 5 || intervalMax < 5 {
return nil, errors.New("hop interval must be at least 5 seconds")
}
// if listenUDPFunc == nil {
// listenUDPFunc = func() (net.PacketConn, error) {
// return net.ListenUDP("udp", nil)
// }
// }
if listenUDPFunc == nil {
panic("listenUDPFunc is nil")
return nil, errors.New("nil listenUDPFunc")
}
addrs, err := addr.addrs()
if err != nil {
return nil, err
}
// curConn, err := listenUDPFunc()
// if err != nil {
// return nil, err
// }
hConn := &UdpHopPacketConn{
Addr: addr,
Addrs: addrs,
HopIntervalMin: hopIntervalMin,
HopIntervalMax: hopIntervalMax,
HopIntervalMin: intervalMin,
HopIntervalMax: intervalMax,
ListenUDPFunc: listenUDPFunc,
prevConn: nil,
currentConn: nil,
addrIndex: rand.Intn(len(addrs)),
currentConn: pktConn,
addrIndex: index,
recvQueue: make(chan *udpPacket, packetQueueSize),
closeChan: make(chan struct{}),
bufPool: sync.Pool{
@@ -85,12 +91,7 @@ func NewUDPHopPacketConn(addrs []net.Addr, hopIntervalMin time.Duration, hopInte
},
},
}
var err error
hConn.currentConn, err = listenUDPFunc(hConn.Addrs[hConn.addrIndex].(*net.UDPAddr))
if err != nil {
return nil, err
}
go hConn.recvLoop(hConn.currentConn)
go hConn.recvLoop(pktConn)
go hConn.hopLoop()
return hConn, nil
}
@@ -103,64 +104,69 @@ func (u *UdpHopPacketConn) recvLoop(conn net.PacketConn) {
u.bufPool.Put(buf)
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
// Only pass through timeout errors here, not permanent errors
// like connection closed. Connection close is normal as we close
// the old connection to exit this loop every time we hop.
u.recvQueue <- &udpPacket{nil, 0, nil, netErr}
continue
}
return
}
select {
case u.recvQueue <- &udpPacket{buf, n, addr, nil}:
// Packet successfully queued
default:
// Queue is full, drop the packet
u.bufPool.Put(buf)
}
}
}
func (u *UdpHopPacketConn) hopLoop() {
timer := time.NewTimer(u.nextHopInterval())
defer timer.Stop()
ticker := time.NewTicker(time.Duration(crypto.RandBetween(u.HopIntervalMin, u.HopIntervalMax)) * time.Second)
defer ticker.Stop()
for {
select {
case <-timer.C:
case <-ticker.C:
u.hop()
timer.Reset(u.nextHopInterval())
ticker.Reset(time.Duration(crypto.RandBetween(u.HopIntervalMin, u.HopIntervalMax)) * time.Second)
case <-u.closeChan:
return
}
}
}
func (u *UdpHopPacketConn) nextHopInterval() time.Duration {
if u.HopIntervalMin == u.HopIntervalMax {
return u.HopIntervalMin
}
return u.HopIntervalMin + time.Duration(rand.Int63n(int64(u.HopIntervalMax-u.HopIntervalMin)+1))
}
func (u *UdpHopPacketConn) hop() {
u.connMutex.Lock()
defer u.connMutex.Unlock()
if u.closed {
return
}
// Update addrIndex to a new random value
u.addrIndex = rand.Intn(len(u.Addrs))
newConn, err := u.ListenUDPFunc(u.Addrs[u.addrIndex].(*net.UDPAddr))
if err != nil {
// Could be temporary, just skip this hop
return
}
// We need to keep receiving packets from the previous connection,
// because otherwise there will be packet loss due to the time gap
// between we hop to a new port and the server acknowledges this change.
// So we do the following:
// Close prevConn,
// move currentConn to prevConn,
// set newConn as currentConn,
// start recvLoop on newConn.
if u.prevConn != nil {
_ = u.prevConn.Close()
_ = u.prevConn.Close() // recvLoop for this conn will exit
}
u.prevConn = u.currentConn
u.currentConn = newConn
if !u.deadline.IsZero() {
_ = u.currentConn.SetDeadline(u.deadline)
// Set buffer sizes if previously set
if u.readBufferSize > 0 {
_ = trySetReadBuffer(u.currentConn, u.readBufferSize)
}
if !u.readDeadline.IsZero() {
_ = u.currentConn.SetReadDeadline(u.readDeadline)
}
if !u.writeDeadline.IsZero() {
_ = u.currentConn.SetWriteDeadline(u.writeDeadline)
if u.writeBufferSize > 0 {
_ = trySetWriteBuffer(u.currentConn, u.writeBufferSize)
}
go u.recvLoop(newConn)
}
@@ -172,9 +178,11 @@ func (u *UdpHopPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error)
if p.Err != nil {
return 0, nil, p.Err
}
// Currently we do not check whether the packet is from
// the server or not due to performance reasons.
n := copy(b, p.Buf[:p.N])
u.bufPool.Put(p.Buf)
return n, p.Addr, nil
return n, u.Addr, nil
case <-u.closeChan:
return 0, nil, net.ErrClosed
}
@@ -187,6 +195,8 @@ func (u *UdpHopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
if u.closed {
return 0, net.ErrClosed
}
// Skip the check for now, always write to the server,
// for the same reason as in ReadFrom.
return u.currentConn.WriteTo(b, u.Addrs[u.addrIndex])
}
@@ -196,13 +206,16 @@ func (u *UdpHopPacketConn) Close() error {
if u.closed {
return nil
}
// Close prevConn and currentConn
// Close closeChan to unblock ReadFrom & hopLoop
// Set closed flag to true to prevent double close
if u.prevConn != nil {
_ = u.prevConn.Close()
}
err := u.currentConn.Close()
close(u.closeChan)
u.closed = true
u.Addrs = nil
u.Addrs = nil // For GC
return err
}
@@ -213,11 +226,8 @@ func (u *UdpHopPacketConn) LocalAddr() net.Addr {
}
func (u *UdpHopPacketConn) SetDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = t
u.readDeadline = t
u.writeDeadline = t
u.connMutex.RLock()
defer u.connMutex.RUnlock()
if u.prevConn != nil {
_ = u.prevConn.SetDeadline(t)
}
@@ -225,10 +235,8 @@ func (u *UdpHopPacketConn) SetDeadline(t time.Time) error {
}
func (u *UdpHopPacketConn) SetReadDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = time.Time{}
u.readDeadline = t
u.connMutex.RLock()
defer u.connMutex.RUnlock()
if u.prevConn != nil {
_ = u.prevConn.SetReadDeadline(t)
}
@@ -236,16 +244,36 @@ func (u *UdpHopPacketConn) SetReadDeadline(t time.Time) error {
}
func (u *UdpHopPacketConn) SetWriteDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = time.Time{}
u.writeDeadline = t
u.connMutex.RLock()
defer u.connMutex.RUnlock()
if u.prevConn != nil {
_ = u.prevConn.SetWriteDeadline(t)
}
return u.currentConn.SetWriteDeadline(t)
}
// UDP-specific methods below
func (u *UdpHopPacketConn) SetReadBuffer(bytes int) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.readBufferSize = bytes
if u.prevConn != nil {
_ = trySetReadBuffer(u.prevConn, bytes)
}
return trySetReadBuffer(u.currentConn, bytes)
}
func (u *UdpHopPacketConn) SetWriteBuffer(bytes int) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.writeBufferSize = bytes
if u.prevConn != nil {
_ = trySetWriteBuffer(u.prevConn, bytes)
}
return trySetWriteBuffer(u.currentConn, bytes)
}
func (u *UdpHopPacketConn) SyscallConn() (syscall.RawConn, error) {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
@@ -256,14 +284,22 @@ func (u *UdpHopPacketConn) SyscallConn() (syscall.RawConn, error) {
return sc.SyscallConn()
}
func ToAddrs(ip net.IP, ports []uint32) []net.Addr {
var addrs []net.Addr
for _, port := range ports {
addr := &net.UDPAddr{
IP: ip,
Port: int(port),
}
addrs = append(addrs, addr)
func trySetReadBuffer(pc net.PacketConn, bytes int) error {
sc, ok := pc.(interface {
SetReadBuffer(bytes int) error
})
if ok {
return sc.SetReadBuffer(bytes)
}
return addrs
return nil
}
func trySetWriteBuffer(pc net.PacketConn, bytes int) error {
sc, ok := pc.(interface {
SetWriteBuffer(bytes int) error
})
if ok {
return sc.SetWriteBuffer(bytes)
}
return nil
}
+30 -16
View File
@@ -57,27 +57,41 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet
}
if streamSettings.UdpmaskManager != nil {
var pktConn net.PacketConn
var udpAddr = conn.RemoteAddr().(*net.UDPAddr)
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c.PacketConn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
c.PacketConn = pktConn
case *net.UDPConn:
pktConn = c
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: c.RemoteAddr().(*net.UDPAddr),
}
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
fakeConn := &internet.FakePacketConn{Conn: c}
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(fakeConn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: &net.UDPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
},
}
default:
panic(reflect.TypeOf(c))
}
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: udpAddr,
conn.Close()
return nil, errors.New("unknown conn ", reflect.TypeOf(c))
}
}
+68 -42
View File
@@ -5,11 +5,11 @@ import (
gotls "crypto/tls"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptrace"
"net/url"
reflect "reflect"
"runtime"
"strconv"
"sync"
"sync/atomic"
@@ -21,7 +21,6 @@ import (
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/transport/internet"
@@ -174,7 +173,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
KeepAlivePeriod: time.Duration(quicParams.KeepAlivePeriod) * time.Second,
MaxIncomingStreams: quicParams.MaxIncomingStreams,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery,
}
if quicParams.MaxIdleTimeout == 0 {
quicConfig.MaxIdleTimeout = net.ConnIdleTimeout
@@ -195,83 +194,110 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
QUICConfig: quicConfig,
TLSClientConfig: gotlsConfig,
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
udpHopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
udphopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), streamSettings.SocketSettings)
if err != nil {
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("failed to dial to dest").Base(err)
errors.LogDebug(context.Background(), "skip hop: failed to dial to dest")
conn.Close()
return nil, errors.New()
}
var pktConn net.PacketConn
var udpConn net.PacketConn
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpConn = c.PacketConn
case *net.UDPConn:
pktConn = c
udpConn = c
default:
errors.LogInfo(context.Background(), "skip hop: invalid conn ", reflect.TypeOf(c))
errors.LogDebug(context.Background(), "skip hop: udphop requires being at the outermost level ", reflect.TypeOf(c))
conn.Close()
return nil, errors.New("invalid conn ", reflect.TypeOf(c))
return nil, errors.New()
}
return pktConn, nil
return udpConn, nil
}
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
var err error
udpAddr, err = net.ResolveUDPAddr("udp", dest.NetAddr())
var index int
if len(quicParams.UdpHop.Ports) > 0 {
index = rand.Intn(len(quicParams.UdpHop.Ports))
dest.Port = net.Port(quicParams.UdpHop.Ports[index])
}
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
var udpConn net.PacketConn
var udpAddr *net.UDPAddr
switch c := conn.(type) {
case *internet.PacketConnWrapper:
udpConn = c.PacketConn
udpAddr, err = net.ResolveUDPAddr("udp", c.Dest.String())
if err != nil {
conn.Close()
return nil, err
}
case *net.UDPConn:
udpConn = c
udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
if err != nil {
conn.Close()
return nil, err
}
default:
udpConn = &internet.FakePacketConn{Conn: c}
udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
if err != nil {
conn.Close()
return nil, err
}
if len(quicParams.UdpHop.Ports) > 0 {
conn.Close()
return nil, errors.New("udphop requires being at the outermost level ", reflect.TypeOf(c))
}
}
if len(quicParams.UdpHop.Ports) > 0 {
pktConn, err = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer)
if err != nil {
return nil, err
addr := &udphop.UDPHopAddr{
IP: udpAddr.IP,
Ports: quicParams.UdpHop.Ports,
}
} else {
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
udpConn, err = udphop.NewUDPHopPacketConn(addr, index, quicParams.UdpHop.IntervalMin, quicParams.UdpHop.IntervalMax, udphopDialer, udpConn)
if err != nil {
return nil, err
}
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *net.UDPConn:
pktConn = c
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
conn.Close()
return nil, errors.New("udphop err").Base(err)
}
}
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
udpConn, err = streamSettings.UdpmaskManager.WrapPacketConnClient(udpConn)
if err != nil {
pktConn.Close()
conn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
conn, err := quic.DialEarly(ctx, pktConn, udpAddr, tlsCfg, cfg)
quicConn, err := quic.DialEarly(ctx, udpConn, udpAddr, tlsCfg, cfg)
if err != nil {
return nil, err
}
switch quicParams.Congestion {
case "reno":
case "", "bbr":
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
case "force-brutal":
congestion.UseBrutal(conn, quicParams.BrutalUp)
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion brutal bytes per second ", quicParams.BrutalUp)
congestion.UseBrutal(quicConn, quicParams.BrutalUp)
case "reno":
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion reno")
default:
panic(quicParams.Congestion)
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
}
return conn, nil
return quicConn, nil
},
}
} else if httpVersion == "2" {
+30 -41
View File
@@ -8,7 +8,6 @@ import (
"fmt"
"io"
"net/http"
"runtime"
"slices"
"strconv"
"strings"
@@ -441,7 +440,7 @@ type Listener struct {
server http.Server
h3server *http3.Server
listener net.Listener
h3listener Qface
h3listener *quic.EarlyListener
config *Config
addConn internet.ConnHandler
isH3 bool
@@ -488,12 +487,12 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
return nil, errors.New("failed to listen UDP for XHTTP/3 on ", address, ":", port).Base(err)
}
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(Conn)
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(Conn)
if err != nil {
Conn.Close()
return nil, errors.New("mask err").Base(err)
}
Conn = newConn
Conn = pktConn
}
quicParams := streamSettings.QuicParams
@@ -511,17 +510,13 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
MaxIncomingStreams: quicParams.MaxIncomingStreams,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery,
}
l.h3listener, err = quic.ListenEarly(Conn, tlsConfig, quicConfig)
if err != nil {
return nil, errors.New("failed to listen QUIC for XHTTP/3 on ", address, ":", port).Base(err)
}
l.h3listener = &QListener{
Qface: l.h3listener,
quicParams: quicParams,
}
errors.LogInfo(ctx, "listening QUIC for XHTTP/3 on ", address, ":", port)
handler.localAddr = l.h3listener.Addr()
@@ -530,8 +525,30 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
Handler: handler,
}
go func() {
if err := l.h3server.ServeListener(l.h3listener); err != nil {
errors.LogErrorInner(ctx, err, "failed to serve HTTP/3 for XHTTP/3")
for {
conn, err := l.h3listener.Accept(context.Background())
if err != nil {
errors.LogInfoInner(ctx, err, "XHTTP/3 listener closed")
return
}
switch quicParams.Congestion {
case "force-brutal":
errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion brutal bytes per second ", quicParams.BrutalUp)
congestion.UseBrutal(conn, quicParams.BrutalUp)
case "reno":
errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion reno")
default:
errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
}
go func() {
if err := l.h3server.ServeQUICConn(conn); err != nil {
errors.LogDebugInner(ctx, err, "XHTTP/3 connection ended")
}
_ = conn.CloseWithError(0, "")
}()
}
}()
} else { // tcp
@@ -597,8 +614,10 @@ func (ln *Listener) Addr() net.Addr {
func (ln *Listener) Close() error {
if ln.h3server != nil {
if err := ln.h3server.Close(); err != nil {
_ = ln.h3listener.Close()
return err
}
return ln.h3listener.Close()
} else if ln.listener != nil {
return ln.listener.Close()
}
@@ -614,33 +633,3 @@ func getTLSConfig(streamSettings *internet.MemoryStreamConfig) *gotls.Config {
func init() {
common.Must(internet.RegisterTransportListener(protocolName, ListenXH))
}
type Qface interface {
Accept(ctx context.Context) (*quic.Conn, error)
Addr() net.Addr
Close() error
}
var _ Qface = (*quic.EarlyListener)(nil)
type QListener struct {
Qface
quicParams *internet.QuicParams
}
func (l *QListener) Accept(ctx context.Context) (*quic.Conn, error) {
conn, err := l.Qface.Accept(ctx)
if err != nil {
return nil, err
}
switch l.quicParams.Congestion {
case "reno":
case "", "bbr":
congestion.UseBBR(conn, bbr.Profile(l.quicParams.BbrProfile))
case "force-brutal":
congestion.UseBrutal(conn, l.quicParams.BrutalUp)
default:
panic(l.quicParams.Congestion)
}
return conn, nil
}
+5 -1
View File
@@ -469,7 +469,11 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 {
err := ApplyECH(c, config)
if err != nil {
errors.LogError(context.Background(), err)
if c.EchForceQuery == "full" {
errors.LogError(context.Background(), err)
} else {
errors.LogInfo(context.Background(), err)
}
}
}
-1
View File
@@ -81,7 +81,6 @@ message Config {
string ech_config_list = 19;
// Deprecated
string ech_force_query = 20;
SocketConfig ech_socket_settings = 21;
+33 -15
View File
@@ -17,6 +17,7 @@ import (
utls "github.com/refraction-networking/utls"
"github.com/xtls/xray-core/common/crypto"
dns2 "github.com/xtls/xray-core/features/dns"
"golang.org/x/net/http2"
"github.com/miekg/dns"
@@ -48,10 +49,20 @@ func ApplyECH(c *Config, config *tls.Config) error {
// for client
if len(c.EchConfigList) != 0 {
ECHForceQuery := c.EchForceQuery
switch ECHForceQuery {
case "none", "half", "full":
case "":
ECHForceQuery = "full" // default to full
default:
panic("Invalid ECHForceQuery: " + c.EchForceQuery)
}
defer func() {
// if failed to get ECHConfig, use an invalid one to make connection fail
if len(ECHConfig) == 0 {
ECHConfig = []byte{1, 1, 4, 5, 1, 4}
if err != nil || len(ECHConfig) == 0 {
if ECHForceQuery == "full" {
ECHConfig = []byte{1, 1, 4, 5, 1, 4}
}
}
config.EncryptedClientHelloConfigList = ECHConfig
}()
@@ -72,7 +83,7 @@ func ApplyECH(c *Config, config *tls.Config) error {
if nameToQuery == "" {
return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query")
}
ECHConfig, err = QueryRecord(nameToQuery, DNSServer, c.EchSocketSettings)
ECHConfig, err = QueryRecord(nameToQuery, DNSServer, c.EchForceQuery, c.EchSocketSettings)
if err != nil {
return errors.New("Failed to query ECH DNS record for domain: ", nameToQuery, " at server: ", DNSServer).Base(err)
}
@@ -96,6 +107,7 @@ type ECHConfigCache struct {
type echConfigRecord struct {
config []byte
expire time.Time
err error
}
var (
@@ -113,34 +125,39 @@ func ECHCacheKey(server, domain string, sockopt *internet.SocketConfig) string {
// Update updates the ECH config for given domain and server.
// this method is concurrent safe, only one update request will be sent, others get the cache.
// if isLockedUpdate is true, it will not try to acquire the lock.
func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, sockopt *internet.SocketConfig) ([]byte, error) {
func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) {
if !isLockedUpdate {
c.UpdateLock.Lock()
defer c.UpdateLock.Unlock()
}
// Double check cache after acquiring lock
configRecord := c.configRecord.Load()
if configRecord.expire.After(time.Now()) {
if configRecord.expire.After(time.Now()) && configRecord.err == nil {
errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain)
return configRecord.config, nil
return configRecord.config, configRecord.err
}
// Query ECH config from DNS server
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
echConfig, ttl, err := dnsQuery(server, domain, sockopt)
if err != nil {
// if in "full", directly return
if err != nil && forceQuery == "full" {
return nil, err
}
if ttl == 0 {
ttl = dns2.DefaultTTL
}
configRecord = &echConfigRecord{
config: echConfig,
expire: time.Now().Add(time.Duration(ttl) * time.Second),
err: err,
}
c.configRecord.Store(configRecord)
return configRecord.config, nil
return configRecord.config, configRecord.err
}
// QueryRecord returns the ECH config for given domain.
// If the record is not in cache or expired, it will query the DNS server and update the cache.
func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) ([]byte, error) {
func QueryRecord(domain string, server string, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) {
GlobalECHConfigCacheKey := ECHCacheKey(server, domain, sockopt)
echConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey)
if !ok {
@@ -149,25 +166,25 @@ func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) (
echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache)
}
configRecord := echConfigCache.configRecord.Load()
if configRecord.expire.After(time.Now()) {
if configRecord.expire.After(time.Now()) && (configRecord.err == nil || forceQuery == "none") {
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
return configRecord.config, nil
return configRecord.config, configRecord.err
}
// If expire is zero value, it means we are in initial state, wait for the query to finish
// otherwise return old value immediately and update in a goroutine
// but if the cache is too old, wait for update
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
return echConfigCache.Update(domain, server, false, sockopt)
return echConfigCache.Update(domain, server, false, forceQuery, sockopt)
} else {
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
if echConfigCache.UpdateLock.TryLock() {
go func() {
defer echConfigCache.UpdateLock.Unlock()
echConfigCache.Update(domain, server, true, sockopt)
echConfigCache.Update(domain, server, true, forceQuery, sockopt)
}()
}
return configRecord.config, nil
return configRecord.config, configRecord.err
}
}
@@ -305,7 +322,8 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b
}
}
}
return nil, 0, errors.New("no valid ECH config found in DNS response")
// empty is valid, means no ECH config found
return nil, dns2.DefaultTTL, nil
}
var ErrInvalidLen = errors.New("goech: invalid length")
+15 -6
View File
@@ -3,7 +3,6 @@ package tls
import (
"io"
"net/http"
"slices"
"strings"
"sync"
"testing"
@@ -60,11 +59,21 @@ func TestECHDial(t *testing.T) {
func TestECHDialFail(t *testing.T) {
config := &Config{
ServerName: "cloudflare.com",
EchConfigList: "udp://0.0.0.0",
EchConfigList: "udp://127.0.0.1",
EchForceQuery: "half",
}
tlsConfig := config.GetTLSConfig()
ApplyECH(config, tlsConfig)
if !slices.Equal(tlsConfig.EncryptedClientHelloConfigList, []byte{1, 1, 4, 5, 1, 4}) {
t.Error("ECH config should be invalid when query failed", " but got ", tlsConfig.EncryptedClientHelloConfigList)
config.GetTLSConfig()
// check cache
echConfigCache, ok := GlobalECHConfigCache.Load(ECHCacheKey("udp://127.0.0.1", "cloudflare.com", nil))
if !ok {
t.Error("ECH config cache not found")
}
configRecord := echConfigCache.configRecord.Load()
if configRecord == nil {
t.Error("ECH config record not found in cache")
return
}
if configRecord.err == nil {
t.Error("unexpected nil error in ECH config record")
}
}
+10 -15
View File
@@ -5,7 +5,6 @@ import (
"crypto/rand"
"crypto/tls"
"math/big"
"slices"
"time"
utls "github.com/refraction-networking/utls"
@@ -91,24 +90,18 @@ func (c *UConn) HandshakeContextServerName(ctx context.Context) string {
return c.ConnectionState().ServerName
}
// WebsocketHandshakeContext basically calls UConn.Handshake inside it but it will try
// to build outer ALPN to `http/1.1` or `h2 http/1.1` (if manually specified for camouflage)
// WebsocketHandshake basically calls UConn.Handshake inside it but it will only send
// http/1.1 in its ALPN.
func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
config := *utils.AccessField[*utls.Config](c, "config")
ALPN := slices.Clone(config.NextProtos)
// set other kinds of ALPN to http/1.1
if !slices.Equal(ALPN, []string{"h2", "http/1.1"}) {
ALPN = []string{"http/1.1"}
}
// Build the handshake state. This will apply every variable of the TLS of the
// fingerprint in the UConn
if err := c.BuildHandshakeState(); err != nil {
return err
}
// Do not modify outer ALPN if ECH is used
// Outer ALPN will be h2,http/1.1, and real http/1.1 in config will be hidden in ECH
config := *utils.AccessField[*utls.Config](c, "config")
// Do not modify outer ALPN to http/1.1 if ECH is used
// Outer ALPN will be h2,http/1.1, and real ALPN in config will be hidden in ECH
if config.EncryptedClientHelloConfigList != nil {
config.NextProtos = []string{"http/1.1"}
return c.HandshakeContext(ctx)
}
// Iterate over extensions and check for utls.ALPNExtension
@@ -116,12 +109,12 @@ func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
for _, extension := range c.Extensions {
if alpn, ok := extension.(*utls.ALPNExtension); ok {
hasALPNExtension = true
alpn.AlpnProtocols = ALPN
alpn.AlpnProtocols = []string{"http/1.1"}
break
}
}
if !hasALPNExtension { // Append extension if doesn't exists
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: ALPN})
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}})
}
// Rebuild the client hello and do the handshake
if err := c.BuildHandshakeState(); err != nil {
@@ -153,7 +146,9 @@ func copyConfig(c *tls.Config) *utls.Config {
VerifyPeerCertificate: c.VerifyPeerCertificate,
KeyLogWriter: c.KeyLogWriter,
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
NextProtos: c.NextProtos,
}
if config.EncryptedClientHelloConfigList != nil {
config.NextProtos = c.NextProtos
}
return config
}
+34 -16
View File
@@ -25,30 +25,48 @@ func init() {
}
if streamSettings != nil && streamSettings.UdpmaskManager != nil {
var pktConn net.PacketConn
var udpAddr = conn.RemoteAddr().(*net.UDPAddr)
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c.PacketConn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
c.PacketConn = pktConn
errors.LogInfo(ctx, "finalmask udp dialer: wrapped existing PacketConnWrapper with ", reflect.TypeOf(pktConn))
case *net.UDPConn:
pktConn = c
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: c.RemoteAddr().(*net.UDPAddr),
}
errors.LogInfo(ctx, "finalmask udp dialer: wrapped UDPConn with ", reflect.TypeOf(pktConn))
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
fakeConn := &internet.FakePacketConn{Conn: c}
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(fakeConn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: &net.UDPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
},
}
errors.LogInfo(ctx, "finalmask udp dialer: wrapped cnc.Connection with ", reflect.TypeOf(pktConn))
default:
panic(reflect.TypeOf(c))
}
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: udpAddr,
conn.Close()
return nil, errors.New("unknown conn ", reflect.TypeOf(c))
}
}
// TODO: handle dialer options
return conn, nil
}))
}