Compare commits

..
1 Commits
Author SHA1 Message Date
Fangliding 544eccca83 bump REALITY 2026-03-22 05:33:25 +08:00
35 changed files with 367 additions and 634 deletions
@@ -33,7 +33,7 @@ jobs:
- name: Update Geodat - name: Update Geodat
id: update id: update
uses: nick-fields/retry@v4 uses: nick-fields/retry@v3
with: with:
timeout_minutes: 60 timeout_minutes: 60
retry_wait_seconds: 60 retry_wait_seconds: 60
@@ -82,7 +82,7 @@ jobs:
- name: Update Wintun - name: Update Wintun
id: update id: update
uses: nick-fields/retry@v4 uses: nick-fields/retry@v3
with: with:
timeout_minutes: 60 timeout_minutes: 60
retry_wait_seconds: 60 retry_wait_seconds: 60
-3
View File
@@ -67,7 +67,6 @@
- [Marzban](https://github.com/Gozargah/Marzban) - [Marzban](https://github.com/Gozargah/Marzban)
- [Hiddify](https://github.com/hiddify/Hiddify-Manager) - [Hiddify](https://github.com/hiddify/Hiddify-Manager)
- [TX-UI](https://github.com/AghayeCoder/tx-ui) - [TX-UI](https://github.com/AghayeCoder/tx-ui)
- [CELERITY](https://github.com/ClickDevTech/CELERITY-panel)
- One Click - One Click
- [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz) - [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz)
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless) - [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
@@ -123,7 +122,6 @@
- [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) - [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) - [Streisand](https://apps.apple.com/app/streisand/id6450534064)
- [OneXray](https://github.com/OneXray/OneXray) - [OneXray](https://github.com/OneXray/OneXray)
- [INCY](https://apps.apple.com/en/app/incy/id6756943388)
- macOS arm64 & x64 - macOS arm64 & x64
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) - [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973)
- [V2rayU](https://github.com/yanue/V2rayU) - [V2rayU](https://github.com/yanue/V2rayU)
@@ -134,7 +132,6 @@
- [AnyPortal](https://github.com/AnyPortal/AnyPortal) - [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [v2rayN](https://github.com/2dust/v2rayN) - [v2rayN](https://github.com/2dust/v2rayN)
- [GenyConnect](https://github.com/genyleap/GenyConnect) - [GenyConnect](https://github.com/genyleap/GenyConnect)
- [INCY](https://apps.apple.com/en/app/incy/id6756943388)
- Linux - Linux
- [v2rayA](https://github.com/v2rayA/v2rayA) - [v2rayA](https://github.com/v2rayA/v2rayA)
- [Furious](https://github.com/LorenEteval/Furious) - [Furious](https://github.com/LorenEteval/Furious)
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build !windows && !wasm && !illumos && !openbsd //go:build !windows && !wasm && !illumos
// +build !windows,!wasm,!illumos,!openbsd // +build !windows,!wasm,!illumos
package buf package buf
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build !wasm && !openbsd //go:build !wasm
// +build !wasm,!openbsd // +build !wasm
package buf package buf
-17
View File
@@ -1,17 +0,0 @@
//go:build wasm || openbsd
// +build wasm openbsd
package buf
import (
"io"
"syscall"
"github.com/xtls/xray-core/features/stats"
)
const useReadv = false
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
panic("not implemented")
}
+15
View File
@@ -0,0 +1,15 @@
//go:build wasm
// +build wasm
package buf
import (
"io"
"syscall"
)
const useReadv = false
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn) Reader {
panic("not implemented")
}
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build !wasm && !openbsd //go:build !wasm
// +build !wasm,!openbsd // +build !wasm
package buf_test package buf_test
+2 -2
View File
@@ -36,7 +36,7 @@ type serverityLogger struct {
func NewLogger(logWriterCreator WriterCreator) Handler { func NewLogger(logWriterCreator WriterCreator) Handler {
return &generalLogger{ return &generalLogger{
creator: logWriterCreator, creator: logWriterCreator,
buffer: make(chan Message, 128), buffer: make(chan Message, 16),
access: semaphore.New(1), access: semaphore.New(1),
done: done.New(), done: done.New(),
} }
@@ -46,7 +46,7 @@ func ReplaceWithSeverityLogger(serverity Severity) {
w := CreateStdoutLogWriter() w := CreateStdoutLogWriter()
g := &generalLogger{ g := &generalLogger{
creator: w, creator: w,
buffer: make(chan Message, 128), buffer: make(chan Message, 16),
access: semaphore.New(1), access: semaphore.New(1),
done: done.New(), done: done.New(),
} }
+1 -1
View File
@@ -53,7 +53,7 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
return return
} }
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP && if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun" || inbound.Name == "wireguard") { (inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
h := blake3.New(8, BaseKey) h := blake3.New(8, BaseKey)
h.Write([]byte(inbound.Source.String())) h.Write([]byte(inbound.Source.String()))
copy(globalID[:], h.Sum(nil)) copy(globalID[:], h.Sum(nil))
+2 -2
View File
@@ -19,8 +19,8 @@ import (
var ( var (
Version_x byte = 26 Version_x byte = 26
Version_y byte = 3 Version_y byte = 2
Version_z byte = 27 Version_z byte = 6
) )
var ( var (
+4 -4
View File
@@ -18,7 +18,7 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.49.0 golang.org/x/crypto v0.49.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
@@ -26,8 +26,8 @@ require (
golang.org/x/sync v0.20.0 golang.org/x/sync v0.20.0
golang.org/x/sys v0.42.0 golang.org/x/sys v0.42.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.80.0 google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3 h12.io/socks v1.0.3
@@ -48,7 +48,7 @@ require (
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.42.0 // indirect golang.org/x/tools v0.42.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+10 -10
View File
@@ -65,8 +65,8 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 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-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI= github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
@@ -129,14 +129,14 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+9 -18
View File
@@ -1425,11 +1425,10 @@ func (c *FragmentMask) Build() (proto.Message, error) {
} }
type NoiseItem struct { type NoiseItem struct {
Rand Int32Range `json:"rand"` Rand Int32Range `json:"rand"`
RandRange *Int32Range `json:"randRange"` Type string `json:"type"`
Type string `json:"type"` Packet json.RawMessage `json:"packet"`
Packet json.RawMessage `json:"packet"` Delay Int32Range `json:"delay"`
Delay Int32Range `json:"delay"`
} }
type NoiseMask struct { type NoiseMask struct {
@@ -1446,24 +1445,16 @@ func (c *NoiseMask) Build() (proto.Message, error) {
noiseSlice := make([]*noise.Item, 0, len(c.Noise)) noiseSlice := make([]*noise.Item, 0, len(c.Noise))
for _, item := range c.Noise { for _, item := range c.Noise {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errors.New("invalid randRange")
}
var err error var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil { if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err return nil, err
} }
noiseSlice = append(noiseSlice, &noise.Item{ noiseSlice = append(noiseSlice, &noise.Item{
RandMin: int64(item.Rand.From), RandMin: int64(item.Rand.From),
RandMax: int64(item.Rand.To), RandMax: int64(item.Rand.To),
RandRangeMin: item.RandRange.From, Packet: item.Packet,
RandRangeMax: item.RandRange.To, DelayMin: int64(item.Delay.From),
Packet: item.Packet, DelayMax: int64(item.Delay.To),
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
}) })
} }
+8 -40
View File
@@ -78,13 +78,8 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
return nil, 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 != nil && account.Reverse.Tag == "" {
if account.Reverse.Tag == "" { return nil, 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 nil, errors.New(`VLESS clients: inbound's "reverse" can't have "sniffing"`)
}
} }
user.Account = serial.ToTypedMessage(account) user.Account = serial.ToTypedMessage(account)
@@ -202,28 +197,6 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
return config, nil return config, nil
} }
type VLessReverseConfig struct {
Tag string `json:"tag"`
Sniffing *SniffingConfig `json:"sniffing"`
}
func (c *VLessReverseConfig) Build() (*vless.Reverse, error) {
if c.Tag == "" {
return nil, errors.New(`VLESS reverse: "tag" can't be empty`)
}
r := &vless.Reverse{
Tag: c.Tag,
}
if c.Sniffing != nil {
sc, err := c.Sniffing.Build()
if err != nil {
return nil, errors.New(`VLESS reverse: invalid "sniffing" config`).Base(err)
}
r.Sniffing = sc
}
return r, nil
}
type VLessOutboundVnext struct { type VLessOutboundVnext struct {
Address *Address `json:"address"` Address *Address `json:"address"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
@@ -239,7 +212,7 @@ type VLessOutboundConfig struct {
Flow string `json:"flow"` Flow string `json:"flow"`
Seed string `json:"seed"` Seed string `json:"seed"`
Encryption string `json:"encryption"` Encryption string `json:"encryption"`
Reverse *VLessReverseConfig `json:"reverse"` Reverse *vless.Reverse `json:"reverse"`
Testpre uint32 `json:"testpre"` Testpre uint32 `json:"testpre"`
Testseed []uint32 `json:"testseed"` Testseed []uint32 `json:"testseed"`
Vnext []*VLessOutboundVnext `json:"vnext"` Vnext []*VLessOutboundVnext `json:"vnext"`
@@ -287,22 +260,13 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
account.Flow = c.Flow account.Flow = c.Flow
//account.Seed = c.Seed //account.Seed = c.Seed
account.Encryption = c.Encryption account.Encryption = c.Encryption
if c.Reverse != nil { account.Reverse = c.Reverse
rvs, err := c.Reverse.Build()
if err != nil {
return nil, err
}
account.Reverse = rvs
}
account.Testpre = c.Testpre account.Testpre = c.Testpre
account.Testseed = c.Testseed account.Testseed = c.Testseed
} else { } else {
if err := json.Unmarshal(rawUser, account); err != nil { if err := json.Unmarshal(rawUser, account); err != nil {
return nil, errors.New(`VLESS users: invalid user`).Base(err) return nil, errors.New(`VLESS users: invalid user`).Base(err)
} }
if account.Reverse != nil { // may not be reached: error json unmarshal
return nil, errors.New(`VLESS users: please use simplified outbound's config style to use "reverse"`)
}
} }
u, err := uuid.ParseString(account.Id) u, err := uuid.ParseString(account.Id)
@@ -362,6 +326,10 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption) return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption)
} }
if account.Reverse != nil && account.Reverse.Tag == "" {
return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`)
}
user.Account = serial.ToTypedMessage(account) user.Account = serial.ToTypedMessage(account)
spec.User = user spec.User = user
break break
+1 -1
View File
@@ -130,7 +130,7 @@ func ParseWireGuardKey(str string) (string, error) {
return "", errors.New("key must not be empty") return "", errors.New("key must not be empty")
} }
if len(str) == 64 { if len(str)%2 == 0 {
_, err = hex.DecodeString(str) _, err = hex.DecodeString(str)
if err == nil { if err == nil {
return str, nil return str, nil
+2 -3
View File
@@ -83,12 +83,10 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
inbound.Name = "hysteria" inbound.Name = "hysteria"
inbound.CanSpliceCopy = 3 inbound.CanSpliceCopy = 3
iConn := stat.TryUnwrapStatsConn(conn)
var useremail string var useremail string
var userlevel uint32 var userlevel uint32
type User interface{ User() *protocol.MemoryUser } type User interface{ User() *protocol.MemoryUser }
if v, ok := iConn.(User); ok { if v, ok := conn.(User); ok {
inbound.User = v.User() inbound.User = v.User()
if inbound.User != nil { if inbound.User != nil {
useremail = inbound.User.Email useremail = inbound.User.Email
@@ -96,6 +94,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
} }
} }
iConn := stat.TryUnwrapStatsConn(conn)
if _, ok := iConn.(*hysteria.InterUdpConn); ok { if _, ok := iConn.(*hysteria.InterUdpConn); ok {
r := io.Reader(conn) r := io.Reader(conn)
b := make([]byte, MaxUDPSize) b := make([]byte, MaxUDPSize)
-3
View File
@@ -46,9 +46,6 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
ctx = session.ContextWithContent(ctx, content) ctx = session.ContextWithContent(ctx, content)
inbound := session.InboundFromContext(ctx) inbound := session.InboundFromContext(ctx)
if inbound == nil {
inbound = &session.Inbound{}
}
inbound.Tag = l.config.InboundTag inbound.Tag = l.config.InboundTag
+29 -37
View File
@@ -322,7 +322,6 @@ func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink boo
func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
var isPadding *bool var isPadding *bool
var switchToDirectCopy *bool var switchToDirectCopy *bool
var spliceReadyInbound *session.Inbound
if w.isUplink { if w.isUplink {
isPadding = &w.trafficState.Outbound.IsPadding isPadding = &w.trafficState.Outbound.IsPadding
switchToDirectCopy = &w.trafficState.Outbound.UplinkWriterDirectCopy switchToDirectCopy = &w.trafficState.Outbound.UplinkWriterDirectCopy
@@ -334,7 +333,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if *switchToDirectCopy { if *switchToDirectCopy {
if inbound := session.InboundFromContext(w.ctx); inbound != nil { if inbound := session.InboundFromContext(w.ctx); inbound != nil {
if !w.isUplink && inbound.CanSpliceCopy == 2 { if !w.isUplink && inbound.CanSpliceCopy == 2 {
spliceReadyInbound = inbound inbound.CanSpliceCopy = 1
} }
// if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // TODO: enable uplink splice // if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // TODO: enable uplink splice
// w.ob.CanSpliceCopy = 1 // w.ob.CanSpliceCopy = 1
@@ -356,51 +355,43 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if *isPadding { if *isPadding {
if len(mb) == 1 && mb[0] == nil { if len(mb) == 1 && mb[0] == nil {
mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx, w.testseed) // we do a long padding to hide vless header mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx, w.testseed) // we do a long padding to hide vless header
} else { return w.Writer.WriteMultiBuffer(mb)
isComplete := IsCompleteRecord(mb) }
mb = ReshapeMultiBuffer(w.ctx, mb) isComplete := IsCompleteRecord(mb)
longPadding := w.trafficState.IsTLS mb = ReshapeMultiBuffer(w.ctx, mb)
for i, b := range mb { longPadding := w.trafficState.IsTLS
if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) && isComplete { for i, b := range mb {
if w.trafficState.EnableXtls { if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) && isComplete {
*switchToDirectCopy = true if w.trafficState.EnableXtls {
} *switchToDirectCopy = true
var command byte = CommandPaddingContinue
if i == len(mb)-1 {
command = CommandPaddingEnd
if w.trafficState.EnableXtls {
command = CommandPaddingDirect
}
}
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed)
*isPadding = false // padding going to end
longPadding = false
continue
} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early
*isPadding = false
mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
break
} }
var command byte = CommandPaddingContinue var command byte = CommandPaddingContinue
if i == len(mb)-1 && !*isPadding { if i == len(mb)-1 {
command = CommandPaddingEnd command = CommandPaddingEnd
if w.trafficState.EnableXtls { if w.trafficState.EnableXtls {
command = CommandPaddingDirect command = CommandPaddingDirect
} }
} }
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed) mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed)
*isPadding = false // padding going to end
longPadding = false
continue
} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early
*isPadding = false
mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
break
} }
var command byte = CommandPaddingContinue
if i == len(mb)-1 && !*isPadding {
command = CommandPaddingEnd
if w.trafficState.EnableXtls {
command = CommandPaddingDirect
}
}
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
} }
} }
if err := w.Writer.WriteMultiBuffer(mb); err != nil { return w.Writer.WriteMultiBuffer(mb)
return err
}
if spliceReadyInbound != nil && spliceReadyInbound.CanSpliceCopy == 2 {
// Enable splice only after this write has completed to avoid racing
// concurrent direct writes to the same TCP connection.
spliceReadyInbound.CanSpliceCopy = 1
}
return nil
} }
// IsCompleteRecord Is complete tls data record // IsCompleteRecord Is complete tls data record
@@ -753,6 +744,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
errors.LogDebug(ctx, "CopyRawConn splice") errors.LogDebug(ctx, "CopyRawConn splice")
statWriter, _ := writer.(*dispatcher.SizeStatWriter) statWriter, _ := writer.(*dispatcher.SizeStatWriter)
//runtime.Gosched() // necessary //runtime.Gosched() // necessary
time.Sleep(time.Millisecond) // without this, there will be a rare ssl error for freedom splice
timer.SetTimeout(24 * time.Hour) // prevent leak, just in case timer.SetTimeout(24 * time.Hour) // prevent leak, just in case
if inTimer != nil { if inTimer != nil {
inTimer.SetTimeout(24 * time.Hour) inTimer.SetTimeout(24 * time.Hour)
+12 -24
View File
@@ -7,7 +7,6 @@
package vless package vless
import ( import (
proxyman "github.com/xtls/xray-core/app/proxyman"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
@@ -23,9 +22,8 @@ const (
) )
type Reverse struct { type Reverse struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
Sniffing *proxyman.SniffingConfig `protobuf:"bytes,2,opt,name=sniffing,proto3" json:"sniffing,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -67,13 +65,6 @@ func (x *Reverse) GetTag() string {
return "" return ""
} }
func (x *Reverse) GetSniffing() *proxyman.SniffingConfig {
if x != nil {
return x.Sniffing
}
return nil
}
type Account struct { type Account struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57".
@@ -188,10 +179,9 @@ var File_proxy_vless_account_proto protoreflect.FileDescriptor
const file_proxy_vless_account_proto_rawDesc = "" + const file_proxy_vless_account_proto_rawDesc = "" +
"\n" + "\n" +
"\x19proxy/vless/account.proto\x12\x10xray.proxy.vless\x1a\x19app/proxyman/config.proto\"Z\n" + "\x19proxy/vless/account.proto\x12\x10xray.proxy.vless\"\x1b\n" +
"\aReverse\x12\x10\n" + "\aReverse\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x12=\n" + "\x03tag\x18\x01 \x01(\tR\x03tag\"\x86\x02\n" +
"\bsniffing\x18\x02 \x01(\v2!.xray.app.proxyman.SniffingConfigR\bsniffing\"\x86\x02\n" +
"\aAccount\x12\x0e\n" + "\aAccount\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
"\x04flow\x18\x02 \x01(\tR\x04flow\x12\x1e\n" + "\x04flow\x18\x02 \x01(\tR\x04flow\x12\x1e\n" +
@@ -220,18 +210,16 @@ func file_proxy_vless_account_proto_rawDescGZIP() []byte {
var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_vless_account_proto_goTypes = []any{ var file_proxy_vless_account_proto_goTypes = []any{
(*Reverse)(nil), // 0: xray.proxy.vless.Reverse (*Reverse)(nil), // 0: xray.proxy.vless.Reverse
(*Account)(nil), // 1: xray.proxy.vless.Account (*Account)(nil), // 1: xray.proxy.vless.Account
(*proxyman.SniffingConfig)(nil), // 2: xray.app.proxyman.SniffingConfig
} }
var file_proxy_vless_account_proto_depIdxs = []int32{ var file_proxy_vless_account_proto_depIdxs = []int32{
2, // 0: xray.proxy.vless.Reverse.sniffing:type_name -> xray.app.proxyman.SniffingConfig 0, // 0: xray.proxy.vless.Account.reverse:type_name -> xray.proxy.vless.Reverse
0, // 1: xray.proxy.vless.Account.reverse:type_name -> xray.proxy.vless.Reverse 1, // [1:1] is the sub-list for method output_type
2, // [2:2] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type
2, // [2:2] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee
2, // [2:2] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name
0, // [0:2] is the sub-list for field type_name
} }
func init() { file_proxy_vless_account_proto_init() } func init() { file_proxy_vless_account_proto_init() }
-3
View File
@@ -6,11 +6,8 @@ option go_package = "github.com/xtls/xray-core/proxy/vless";
option java_package = "com.xray.proxy.vless"; option java_package = "com.xray.proxy.vless";
option java_multiple_files = true; option java_multiple_files = true;
import "app/proxyman/config.proto";
message Reverse { message Reverse {
string tag = 1; string tag = 1;
xray.app.proxyman.SniffingConfig sniffing = 2;
} }
message Account { message Account {
+5 -17
View File
@@ -97,26 +97,14 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
} }
if a.Reverse != nil { if a.Reverse != nil {
rvsCtx := session.ContextWithInbound(ctx, &session.Inbound{
Tag: a.Reverse.Tag,
User: handler.server.User, // TODO: email
})
if sc := a.Reverse.Sniffing; sc != nil && sc.Enabled {
rvsCtx = session.ContextWithContent(rvsCtx, &session.Content{
SniffingRequest: session.SniffingRequest{
Enabled: sc.Enabled,
OverrideDestinationForProtocol: sc.DestinationOverride,
ExcludeForDomain: sc.DomainsExcluded,
MetadataOnly: sc.MetadataOnly,
RouteOnly: sc.RouteOnly,
},
})
}
handler.reverse = &Reverse{ handler.reverse = &Reverse{
tag: a.Reverse.Tag, tag: a.Reverse.Tag,
dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher), dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher),
ctx: rvsCtx, ctx: session.ContextWithInbound(ctx, &session.Inbound{
handler: handler, Tag: a.Reverse.Tag,
User: handler.server.User, // TODO: email
}),
handler: handler,
} }
handler.reverse.monitorTask = &task.Periodic{ handler.reverse.monitorTask = &task.Periodic{
Execute: handler.reverse.monitor, Execute: handler.reverse.monitor,
+41 -47
View File
@@ -2,24 +2,27 @@ package wireguard
import ( import (
"context" "context"
gonet "net" "errors"
"net/netip" "net/netip"
"runtime"
"strconv" "strconv"
"sync"
"golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"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"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
) )
type netReadInfo struct { type netReadInfo struct {
buff *buf.Buffer // status
waiter sync.WaitGroup
// param
buff []byte
// result
bytes int
endpoint conn.Endpoint endpoint conn.Endpoint
err error
} }
// reduce duplicated code // reduce duplicated code
@@ -29,7 +32,6 @@ type netBind struct {
workers int workers int
readQueue chan *netReadInfo readQueue chan *netReadInfo
closedCh chan struct{}
} }
// SetMark implements conn.Bind // SetMark implements conn.Bind
@@ -77,24 +79,26 @@ func (bind *netBind) BatchSize() int {
// Open implements conn.Bind // Open implements conn.Bind
func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) { func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
bind.closedCh = make(chan struct{}) bind.readQueue = make(chan *netReadInfo)
errors.LogDebug(context.Background(), "bind opened")
fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) { fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
select { defer func() {
case r := <-bind.readQueue: if r := recover(); r != nil {
sizes[0], eps[0] = copy(bufs[0], r.buff.Bytes()), r.endpoint n = 0
r.buff.Release() err = errors.New("channel closed")
return 1, nil }
case <-bind.closedCh: }()
errors.LogDebug(context.Background(), "recv func closed")
return 0, gonet.ErrClosed r := &netReadInfo{
buff: bufs[0],
} }
r.waiter.Add(1)
bind.readQueue <- r
r.waiter.Wait() // wait read goroutine done, or we will miss the result
sizes[0], eps[0] = r.bytes, r.endpoint
return 1, r.err
} }
workers := bind.workers workers := bind.workers
if workers <= 0 {
workers = runtime.NumCPU()
}
if workers <= 0 { if workers <= 0 {
workers = 1 workers = 1
} }
@@ -108,9 +112,8 @@ func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
// Close implements conn.Bind // Close implements conn.Bind
func (bind *netBind) Close() error { func (bind *netBind) Close() error {
errors.LogDebug(context.Background(), "bind closed") if bind.readQueue != nil {
if bind.closedCh != nil { close(bind.readQueue)
close(bind.closedCh)
} }
return nil return nil
} }
@@ -130,38 +133,30 @@ func (bind *netBindClient) connectTo(endpoint *netEndpoint) error {
} }
endpoint.conn = c endpoint.conn = c
go func() { go func(readQueue <-chan *netReadInfo, endpoint *netEndpoint) {
for { for {
buff := buf.NewWithSize(device.MaxMessageSize) v, ok := <-readQueue
n, err := buff.ReadFrom(c) if !ok {
if err != nil {
buff.Release()
endpoint.conn = nil
c.Close()
return return
} }
i, err := c.Read(v.buff)
rawBytes := buff.Bytes() if i > 3 {
if n > 3 { v.buff[1] = 0
rawBytes[1] = 0 v.buff[2] = 0
rawBytes[2] = 0 v.buff[3] = 0
rawBytes[3] = 0
} }
select { v.bytes = i
case bind.readQueue <- &netReadInfo{ v.endpoint = endpoint
buff: buff, v.err = err
endpoint: endpoint, v.waiter.Done()
}: if err != nil {
case <-bind.closedCh:
buff.Release()
endpoint.conn = nil endpoint.conn = nil
c.Close()
return return
} }
} }
}() }(bind.readQueue, endpoint)
return nil return nil
} }
@@ -205,8 +200,7 @@ func (bind *netBindServer) Send(buff [][]byte, endpoint conn.Endpoint) error {
} }
if nend.conn == nil { if nend.conn == nil {
errors.LogDebug(context.Background(), nend.dst.NetAddr(), " send on closed peer") return errors.New("connection not open yet")
return errors.New("peer closed")
} }
for _, buff := range buff { for _, buff := range buff {
+1 -38
View File
@@ -121,8 +121,7 @@ func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer)
IPv4Enable: h.hasIPv4, IPv4Enable: h.hasIPv4,
IPv6Enable: h.hasIPv6, IPv6Enable: h.hasIPv6,
}, },
workers: int(h.conf.NumWorkers), workers: int(h.conf.NumWorkers),
readQueue: make(chan *netReadInfo),
}, },
ctx: ctx, ctx: ctx,
dialer: dialer, dialer: dialer,
@@ -228,11 +227,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
defer conn.Close() defer conn.Close()
conn = &udpConnClient{
Conn: conn,
dest: destination,
}
requestFunc = func() error { requestFunc = func() error {
defer timer.SetTimeout(p.Timeouts.DownlinkOnly) defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
@@ -342,34 +336,3 @@ func (h *Handler) createIPCRequest() string {
return request.String()[:request.Len()] return request.String()[:request.Len()]
} }
type udpConnClient struct {
net.Conn
dest net.Destination
}
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New()
b.Resize(0, buf.Size)
n, addr, err := c.Conn.(net.PacketConn).ReadFrom(b.Bytes())
if err != nil {
b.Release()
return nil, err
}
if addr == nil { // should never hit
addr = c.dest.RawNetAddr()
}
b.Resize(0, int32(n))
b.UDP = &net.Destination{
Address: net.IPAddress(addr.(*net.UDPAddr).IP),
Port: net.Port(addr.(*net.UDPAddr).Port),
Network: net.Network_UDP,
}
return buf.MultiBuffer{b}, nil
}
func (c *udpConnClient) Write(p []byte) (int, error) {
return c.Conn.(net.PacketConn).WriteTo(p, c.dest.RawNetAddr())
}
+21 -18
View File
@@ -31,7 +31,6 @@ type netTun struct {
ep *channel.Endpoint ep *channel.Endpoint
stack *stack.Stack stack *stack.Stack
events chan tun.Event events chan tun.Event
notifyHandle *channel.NotificationHandle
incomingPacket chan *buffer.View incomingPacket chan *buffer.View
mtu int mtu int
hasV4, hasV6 bool hasV4, hasV6 bool
@@ -49,17 +48,12 @@ func CreateNetTUN(localAddresses []netip.Addr, mtu int, promiscuousMode bool) (t
dev := &netTun{ dev := &netTun{
ep: channel.New(1024, uint32(mtu), ""), ep: channel.New(1024, uint32(mtu), ""),
stack: stack.New(opts), stack: stack.New(opts),
events: make(chan tun.Event, 10), events: make(chan tun.Event, 1),
incomingPacket: make(chan *buffer.View), incomingPacket: make(chan *buffer.View),
mtu: mtu, mtu: mtu,
} }
sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default dev.ep.AddNotify(dev)
tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt) tcpipErr := dev.stack.CreateNIC(1, dev.ep)
if tcpipErr != nil {
return nil, nil, dev.stack, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
}
dev.notifyHandle = dev.ep.AddNotify(dev)
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
if tcpipErr != nil { if tcpipErr != nil {
return nil, nil, dev.stack, fmt.Errorf("CreateNIC: %v", tcpipErr) return nil, nil, dev.stack, fmt.Errorf("CreateNIC: %v", tcpipErr)
} }
@@ -96,10 +90,20 @@ func CreateNetTUN(localAddresses []netip.Addr, mtu int, promiscuousMode bool) (t
dev.stack.SetSpoofing(1, true) dev.stack.SetSpoofing(1, true)
} }
opt := tcpip.CongestionControlOption("cubic")
if err := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
return nil, nil, dev.stack, fmt.Errorf("SetTransportProtocolOption(%d, &%T(%s)): %s", tcp.ProtocolNumber, opt, opt, err)
}
dev.events <- tun.EventUp dev.events <- tun.EventUp
return dev, (*Net)(dev), dev.stack, nil return dev, (*Net)(dev), dev.stack, nil
} }
// BatchSize implements tun.Device
func (tun *netTun) BatchSize() int {
return 1
}
// Name implements tun.Device // Name implements tun.Device
func (tun *netTun) Name() (string, error) { func (tun *netTun) Name() (string, error) {
return "go", nil return "go", nil
@@ -116,6 +120,7 @@ func (tun *netTun) Events() <-chan tun.Event {
} }
// Read implements tun.Device // Read implements tun.Device
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) { func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
view, ok := <-tun.incomingPacket view, ok := <-tun.incomingPacket
if !ok { if !ok {
@@ -164,16 +169,20 @@ func (tun *netTun) WriteNotify() {
tun.incomingPacket <- view tun.incomingPacket <- view
} }
// Flush implements tun.Device
func (tun *netTun) Flush() error {
return nil
}
// Close implements tun.Device // Close implements tun.Device
func (tun *netTun) Close() error { func (tun *netTun) Close() error {
tun.closeOnce.Do(func() { tun.closeOnce.Do(func() {
tun.stack.RemoveNIC(1) tun.stack.RemoveNIC(1)
tun.stack.Close()
tun.ep.RemoveNotify(tun.notifyHandle)
tun.ep.Close()
close(tun.events) close(tun.events)
tun.ep.Close()
close(tun.incomingPacket) close(tun.incomingPacket)
}) })
return nil return nil
@@ -184,11 +193,6 @@ func (tun *netTun) MTU() (int, error) {
return tun.mtu, nil return tun.mtu, nil
} }
// BatchSize implements tun.Device
func (tun *netTun) BatchSize() int {
return 1
}
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) { func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
var protoNumber tcpip.NetworkProtocolNumber var protoNumber tcpip.NetworkProtocolNumber
if endpoint.Addr().Is4() { if endpoint.Addr().Is4() {
@@ -220,7 +224,6 @@ func (net *Net) DialUDPAddrPort(laddr, raddr netip.AddrPort) (*gonet.UDPConn, er
var addr tcpip.FullAddress var addr tcpip.FullAddress
addr, pn = convertToFullAddr(raddr) addr, pn = convertToFullAddr(raddr)
rfa = &addr rfa = &addr
rfa = nil // do not ep connect
} }
return gonet.DialUDP(net.stack, lfa, rfa, pn) return gonet.DialUDP(net.stack, lfa, rfa, pn)
} }
+54 -36
View File
@@ -2,18 +2,22 @@ package wireguard
import ( import (
"context" "context"
goerrors "errors"
"io"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
c "github.com/xtls/xray-core/common/ctx" c "github.com/xtls/xray-core/common/ctx"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
) )
@@ -27,10 +31,10 @@ type Server struct {
} }
type routingInfo struct { type routingInfo struct {
ctx context.Context ctx context.Context
dispatcher routing.Dispatcher dispatcher routing.Dispatcher
inboundTag *session.Inbound inboundTag *session.Inbound
contentTag *session.Content contentTag *session.Content
} }
func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) { func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
@@ -49,8 +53,6 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
IPv4Enable: hasIPv4, IPv4Enable: hasIPv4,
IPv6Enable: hasIPv6, IPv6Enable: hasIPv6,
}, },
workers: int(conf.NumWorkers),
readQueue: make(chan *netReadInfo),
}, },
}, },
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
@@ -93,31 +95,25 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
reader := buf.NewPacketReader(conn) reader := buf.NewPacketReader(conn)
for { for {
mb, err := reader.ReadMultiBuffer() mpayload, err := reader.ReadMultiBuffer()
if err != nil { if err != nil {
nep.conn = nil
buf.ReleaseMulti(mb)
return err return err
} }
for i, b := range mb { for _, payload := range mpayload {
v, ok := <-s.bindServer.readQueue
rawBytes := b.Bytes() if !ok {
if b.Len() > 3 { return nil
rawBytes[1] = 0
rawBytes[2] = 0
rawBytes[3] = 0
} }
i, err := payload.Read(v.buff)
select { v.bytes = i
case s.bindServer.readQueue <- &netReadInfo{ v.endpoint = nep
buff: b, v.err = err
endpoint: nep, v.waiter.Done()
}: if err != nil && goerrors.Is(err, io.EOF) {
case <-s.bindServer.closedCh:
nep.conn = nil nep.conn = nil
buf.ReleaseMulti(mb[i:]) return nil
return errors.New("bind closed")
} }
} }
} }
@@ -128,6 +124,7 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
errors.LogError(s.info.ctx, "unexpected: dispatcher == nil") errors.LogError(s.info.ctx, "unexpected: dispatcher == nil")
return return
} }
defer conn.Close()
ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx)) ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx))
sid := session.NewID() sid := session.NewID()
@@ -144,13 +141,14 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
// Currently we have no way to link to the original source address // Currently we have no way to link to the original source address
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr()) inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
ctx = session.ContextWithInbound(ctx, &inbound) ctx = session.ContextWithInbound(ctx, &inbound)
content := new(session.Content)
if s.info.contentTag != nil { if s.info.contentTag != nil {
content.SniffingRequest = s.info.contentTag.SniffingRequest ctx = session.ContextWithContent(ctx, s.info.contentTag)
} }
ctx = session.ContextWithContent(ctx, content)
ctx = session.SubContextFromMuxInbound(ctx) ctx = session.SubContextFromMuxInbound(ctx)
plcy := s.policyManager.ForLevel(0)
timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: nullDestination, From: nullDestination,
To: dest, To: dest,
@@ -158,15 +156,35 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
Reason: "", Reason: "",
}) })
err := s.info.dispatcher.DispatchLink(ctx, dest, &transport.Link{ link, err := s.info.dispatcher.Dispatch(ctx, dest)
Reader: buf.NewReader(conn),
Writer: buf.NewWriter(conn),
})
if err != nil { if err != nil {
errors.LogInfoInner(ctx, err, "connection ends") errors.LogErrorInner(ctx, err, "dispatch connection")
}
defer cancel()
requestDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
if err := buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport all TCP request").Base(err)
}
return nil
} }
cancel() responseDone := func() error {
conn.Close() defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
if err := buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport all TCP response").Base(err)
}
return nil
}
requestDonePost := task.OnSuccess(requestDone, task.Close(link.Writer))
if err := task.Run(ctx, requestDonePost, responseDone); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
errors.LogDebugInner(ctx, err, "connection ends")
return
}
} }
+38 -220
View File
@@ -3,7 +3,6 @@ package wireguard
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/netip" "net/netip"
"runtime" "runtime"
"strconv" "strconv"
@@ -11,17 +10,12 @@ import (
"sync" "sync"
"time" "time"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/proxy/wireguard/gvisortun" "github.com/xtls/xray-core/proxy/wireguard/gvisortun"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp" "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter" "gvisor.dev/gvisor/pkg/waiter"
@@ -144,7 +138,7 @@ func (g *gvisorNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, erro
func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error) { func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error) {
out := &gvisorNet{} out := &gvisorNet{}
tun, n, gstack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil) tun, n, stack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -153,236 +147,60 @@ func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
// handler is only used for promiscuous mode // handler is only used for promiscuous mode
// capture all packets and send to handler // capture all packets and send to handler
tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) { tcpForwarder := tcp.NewForwarder(stack, 0, 65535, func(r *tcp.ForwarderRequest) {
go func(r *tcp.ForwarderRequest) { go func(r *tcp.ForwarderRequest) {
var wq waiter.Queue var (
var id = r.ID() wq waiter.Queue
id = r.ID()
)
// Perform a TCP three-way handshake.
ep, err := r.CreateEndpoint(&wq) ep, err := r.CreateEndpoint(&wq)
if err != nil { if err != nil {
errors.LogError(context.Background(), err.String()) errors.LogError(context.Background(), err.String())
r.Complete(true) r.Complete(true)
return return
} }
options := ep.SocketOptions()
options.SetKeepAlive(false)
options.SetReuseAddress(true)
options.SetReusePort(true)
handler(net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewTCPConn(&wq, ep))
ep.Close()
r.Complete(false) r.Complete(false)
defer ep.Close()
// enable tcp keep-alive to prevent hanging connections
ep.SocketOptions().SetKeepAlive(true)
// local address is actually destination
handler(net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewTCPConn(&wq, ep))
}(r) }(r)
}) })
gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket) stack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
manager := &udpManager{ udpForwarder := udp.NewForwarder(stack, func(r *udp.ForwarderRequest) bool {
stack: gstack, go func(r *udp.ForwarderRequest) {
handler: handler, var (
m: make(map[string]*udpConn), wq waiter.Queue
} id = r.ID()
)
ep, err := r.CreateEndpoint(&wq)
if err != nil {
errors.LogError(context.Background(), err.String())
return
}
defer ep.Close()
// prevents hanging connections and ensure timely release
ep.SocketOptions().SetLinger(tcpip.LingerOption{
Enabled: true,
Timeout: 15 * time.Second,
})
handler(net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewUDPConn(&wq, ep))
}(r)
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
data := pkt.Clone().Data().AsRange().ToSlice()
// if len(data) == 0 {
// return false
// }
src := net.UDPDestination(net.IPAddress(id.RemoteAddress.AsSlice()), net.Port(id.RemotePort))
dst := net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort))
manager.feed(src, dst, data)
return true return true
}) })
stack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
} }
out.tun, out.net = tun, n out.tun, out.net = tun, n
return out, nil return out, nil
} }
type udpManager struct {
stack *stack.Stack
handler func(dest net.Destination, conn net.Conn)
m map[string]*udpConn
mutex sync.RWMutex
}
func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte) {
m.mutex.RLock()
uc, ok := m.m[src.NetAddr()]
if ok {
select {
case uc.ch <- data:
default:
}
m.mutex.RUnlock()
return
}
m.mutex.RUnlock()
m.mutex.Lock()
defer m.mutex.Unlock()
uc, ok = m.m[src.NetAddr()]
if !ok {
uc = &udpConn{
ch: make(chan []byte, 1024),
src: src,
dst: dst,
}
uc.writeFunc = m.writeRawUDPPacket
uc.closeFunc = func() {
m.mutex.Lock()
m.close(uc)
m.mutex.Unlock()
}
m.m[src.NetAddr()] = uc
go m.handler(dst, uc)
}
select {
case uc.ch <- data:
default:
}
}
func (m *udpManager) close(uc *udpConn) {
if !uc.closed {
uc.closed = true
close(uc.ch)
delete(m.m, uc.src.NetAddr())
}
}
func (m *udpManager) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error {
udpLen := header.UDPMinimumSize + len(payload)
srcIP := tcpip.AddrFromSlice(src.Address.IP())
dstIP := tcpip.AddrFromSlice(dst.Address.IP())
// build packet with appropriate IP header size
isIPv4 := dst.Address.Family().IsIPv4()
ipHdrSize := header.IPv6MinimumSize
ipProtocol := header.IPv6ProtocolNumber
if isIPv4 {
ipHdrSize = header.IPv4MinimumSize
ipProtocol = header.IPv4ProtocolNumber
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: ipHdrSize + header.UDPMinimumSize,
Payload: buffer.MakeWithData(payload),
})
defer pkt.DecRef()
// Build UDP header
udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
udpHdr.Encode(&header.UDPFields{
SrcPort: uint16(src.Port),
DstPort: uint16(dst.Port),
Length: uint16(udpLen),
})
// Calculate and set UDP checksum
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
// Build IP header
if isIPv4 {
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
ipHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(header.IPv4MinimumSize + udpLen),
TTL: 64,
Protocol: uint8(header.UDPProtocolNumber),
SrcAddr: srcIP,
DstAddr: dstIP,
})
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
} else {
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
ipHdr.Encode(&header.IPv6Fields{
PayloadLength: uint16(udpLen),
TransportProtocol: header.UDPProtocolNumber,
HopLimit: 64,
SrcAddr: srcIP,
DstAddr: dstIP,
})
}
// dispatch the packet
err := m.stack.WriteRawPacket(1, ipProtocol, buffer.MakeWithView(pkt.ToView()))
if err != nil {
return errors.New("failed to write raw udp packet back to stack err ", err)
}
return nil
}
type udpConn struct {
ch chan []byte
src net.Destination
dst net.Destination
writeFunc func(payload []byte, src net.Destination, dst net.Destination) error
closeFunc func()
closed bool
}
func (c *udpConn) Read(p []byte) (int, error) {
b, ok := <-c.ch
if !ok {
return 0, io.EOF
}
n := copy(p, b)
if n != len(b) {
return 0, io.ErrShortBuffer
}
return n, nil
}
func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb {
dst := c.dst
if b.UDP != nil {
dst = *b.UDP
}
err := c.writeFunc(b.Bytes(), dst, c.src)
if err != nil {
buf.ReleaseMulti(mb[i:])
return err
}
b.Release()
}
return nil
}
func (c *udpConn) Write(p []byte) (int, error) {
err := c.writeFunc(p, c.dst, c.src)
if err != nil {
return 0, err
}
return len(p), nil
}
func (c *udpConn) Close() error {
c.closeFunc()
return nil
}
func (c *udpConn) LocalAddr() net.Addr {
return c.src.RawNetAddr() // fake
}
func (c *udpConn) RemoteAddr() net.Addr {
return c.src.RawNetAddr() // src
}
func (c *udpConn) SetDeadline(t time.Time) error {
return nil
}
func (c *udpConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *udpConn) SetWriteDeadline(t time.Time) error {
return nil
}
+17
View File
@@ -8,8 +8,25 @@ import (
"strings" "strings"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/log"
"golang.zx2c4.com/wireguard/device"
) )
var wgLogger = &device.Logger{
Verbosef: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Debug,
Content: fmt.Sprintf(format, args...),
})
},
Errorf: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Error,
Content: fmt.Sprintf(format, args...),
})
},
}
func init() { func init() {
common.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
deviceConfig := config.(*DeviceConfig) deviceConfig := config.(*DeviceConfig)
@@ -25,11 +25,9 @@ type Item struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
RandMin int64 `protobuf:"varint,1,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"` RandMin int64 `protobuf:"varint,1,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
RandMax int64 `protobuf:"varint,2,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"` RandMax int64 `protobuf:"varint,2,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
RandRangeMin int32 `protobuf:"varint,3,opt,name=rand_range_min,json=randRangeMin,proto3" json:"rand_range_min,omitempty"` Packet []byte `protobuf:"bytes,3,opt,name=packet,proto3" json:"packet,omitempty"`
RandRangeMax int32 `protobuf:"varint,4,opt,name=rand_range_max,json=randRangeMax,proto3" json:"rand_range_max,omitempty"` DelayMin int64 `protobuf:"varint,4,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
Packet []byte `protobuf:"bytes,5,opt,name=packet,proto3" json:"packet,omitempty"` DelayMax int64 `protobuf:"varint,5,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
DelayMin int64 `protobuf:"varint,6,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
DelayMax int64 `protobuf:"varint,7,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -78,20 +76,6 @@ func (x *Item) GetRandMax() int64 {
return 0 return 0
} }
func (x *Item) GetRandRangeMin() int32 {
if x != nil {
return x.RandRangeMin
}
return 0
}
func (x *Item) GetRandRangeMax() int32 {
if x != nil {
return x.RandRangeMax
}
return 0
}
func (x *Item) GetPacket() []byte { func (x *Item) GetPacket() []byte {
if x != nil { if x != nil {
return x.Packet return x.Packet
@@ -177,15 +161,13 @@ var File_transport_internet_finalmask_noise_config_proto protoreflect.FileDescri
const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" + const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" +
"\n" + "\n" +
"/transport/internet/finalmask/noise/config.proto\x12'xray.transport.internet.finalmask.noise\"\xda\x01\n" + "/transport/internet/finalmask/noise/config.proto\x12'xray.transport.internet.finalmask.noise\"\x8e\x01\n" +
"\x04Item\x12\x19\n" + "\x04Item\x12\x19\n" +
"\brand_min\x18\x01 \x01(\x03R\arandMin\x12\x19\n" + "\brand_min\x18\x01 \x01(\x03R\arandMin\x12\x19\n" +
"\brand_max\x18\x02 \x01(\x03R\arandMax\x12$\n" + "\brand_max\x18\x02 \x01(\x03R\arandMax\x12\x16\n" +
"\x0erand_range_min\x18\x03 \x01(\x05R\frandRangeMin\x12$\n" + "\x06packet\x18\x03 \x01(\fR\x06packet\x12\x1b\n" +
"\x0erand_range_max\x18\x04 \x01(\x05R\frandRangeMax\x12\x16\n" + "\tdelay_min\x18\x04 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\x06packet\x18\x05 \x01(\fR\x06packet\x12\x1b\n" + "\tdelay_max\x18\x05 \x01(\x03R\bdelayMax\"\x87\x01\n" +
"\tdelay_min\x18\x06 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\a \x01(\x03R\bdelayMax\"\x87\x01\n" +
"\x06Config\x12\x1b\n" + "\x06Config\x12\x1b\n" +
"\treset_min\x18\x01 \x01(\x03R\bresetMin\x12\x1b\n" + "\treset_min\x18\x01 \x01(\x03R\bresetMin\x12\x1b\n" +
"\treset_max\x18\x02 \x01(\x03R\bresetMax\x12C\n" + "\treset_max\x18\x02 \x01(\x03R\bresetMax\x12C\n" +
@@ -9,11 +9,9 @@ option java_multiple_files = true;
message Item { message Item {
int64 rand_min = 1; int64 rand_min = 1;
int64 rand_max = 2; int64 rand_max = 2;
int32 rand_range_min = 3; bytes packet = 3;
int32 rand_range_max = 4; int64 delay_min = 4;
bytes packet = 5; int64 delay_max = 5;
int64 delay_min = 6;
int64 delay_max = 7;
} }
message Config { message Config {
+3 -1
View File
@@ -1,10 +1,12 @@
package noise package noise
import ( import (
"crypto/rand"
"net" "net"
"sync" "sync"
"time" "time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/crypto"
) )
@@ -75,7 +77,7 @@ func (c *noiseConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
for _, item := range c.config.Items { for _, item := range c.config.Items {
if item.RandMax > 0 { if item.RandMax > 0 {
item.Packet = make([]byte, crypto.RandBetween(item.RandMin, item.RandMax)) item.Packet = make([]byte, crypto.RandBetween(item.RandMin, item.RandMax))
crypto.RandBytesBetween(item.Packet, byte(item.RandRangeMin), byte(item.RandRangeMax)) common.Must2(rand.Read(item.Packet))
} }
c.PacketConn.WriteTo(item.Packet, addr) c.PacketConn.WriteTo(item.Packet, addr)
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond) time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
+20 -27
View File
@@ -100,39 +100,32 @@ func (m *udpSessionManagerServer) run() {
func (m *udpSessionManagerServer) feed(id uint32, d []byte) { func (m *udpSessionManagerServer) feed(id uint32, d []byte) {
m.mutex.RLock() m.mutex.RLock()
udpConn, ok := m.m[id] udpConn, ok := m.m[id]
if ok {
select {
case udpConn.ch <- d:
default:
}
m.mutex.RUnlock()
return
}
m.mutex.RUnlock() m.mutex.RUnlock()
m.mutex.Lock()
defer m.mutex.Unlock()
udpConn, ok = m.m[id]
if !ok { if !ok {
udpConn = &InterUdpConn{ m.mutex.Lock()
conn: m.conn, udpConn, ok = m.m[id]
local: m.conn.LocalAddr(), if !ok {
remote: m.conn.RemoteAddr(), udpConn = &InterUdpConn{
conn: m.conn,
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: id, id: id,
ch: make(chan []byte, udpMessageChanSize), ch: make(chan []byte, udpMessageChanSize),
last: time.Now(), last: time.Now(),
user: m.user, user: m.user,
}
udpConn.closeFunc = func() {
m.mutex.Lock()
defer m.mutex.Unlock()
m.close(udpConn)
}
m.m[id] = udpConn
m.addConn(udpConn)
} }
udpConn.closeFunc = func() { m.mutex.Unlock()
m.mutex.Lock()
m.close(udpConn)
m.mutex.Unlock()
}
m.m[id] = udpConn
m.addConn(udpConn)
} }
select { select {
+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 { if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 {
err := ApplyECH(c, config) err := ApplyECH(c, config)
if err != nil { 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; string ech_config_list = 19;
// Deprecated
string ech_force_query = 20; string ech_force_query = 20;
SocketConfig ech_socket_settings = 21; SocketConfig ech_socket_settings = 21;
+33 -15
View File
@@ -17,6 +17,7 @@ import (
utls "github.com/refraction-networking/utls" utls "github.com/refraction-networking/utls"
"github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/crypto"
dns2 "github.com/xtls/xray-core/features/dns"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"github.com/miekg/dns" "github.com/miekg/dns"
@@ -48,10 +49,20 @@ func ApplyECH(c *Config, config *tls.Config) error {
// for client // for client
if len(c.EchConfigList) != 0 { 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() { defer func() {
// if failed to get ECHConfig, use an invalid one to make connection fail // if failed to get ECHConfig, use an invalid one to make connection fail
if len(ECHConfig) == 0 { if err != nil || len(ECHConfig) == 0 {
ECHConfig = []byte{1, 1, 4, 5, 1, 4} if ECHForceQuery == "full" {
ECHConfig = []byte{1, 1, 4, 5, 1, 4}
}
} }
config.EncryptedClientHelloConfigList = ECHConfig config.EncryptedClientHelloConfigList = ECHConfig
}() }()
@@ -72,7 +83,7 @@ func ApplyECH(c *Config, config *tls.Config) error {
if nameToQuery == "" { 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") 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 { if err != nil {
return errors.New("Failed to query ECH DNS record for domain: ", nameToQuery, " at server: ", DNSServer).Base(err) 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 { type echConfigRecord struct {
config []byte config []byte
expire time.Time expire time.Time
err error
} }
var ( var (
@@ -113,34 +125,39 @@ func ECHCacheKey(server, domain string, sockopt *internet.SocketConfig) string {
// Update updates the ECH config for given domain and server. // 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. // 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. // 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 { if !isLockedUpdate {
c.UpdateLock.Lock() c.UpdateLock.Lock()
defer c.UpdateLock.Unlock() defer c.UpdateLock.Unlock()
} }
// Double check cache after acquiring lock // Double check cache after acquiring lock
configRecord := c.configRecord.Load() 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) 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 // Query ECH config from DNS server
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server) errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
echConfig, ttl, err := dnsQuery(server, domain, sockopt) echConfig, ttl, err := dnsQuery(server, domain, sockopt)
if err != nil { // if in "full", directly return
if err != nil && forceQuery == "full" {
return nil, err return nil, err
} }
if ttl == 0 {
ttl = dns2.DefaultTTL
}
configRecord = &echConfigRecord{ configRecord = &echConfigRecord{
config: echConfig, config: echConfig,
expire: time.Now().Add(time.Duration(ttl) * time.Second), expire: time.Now().Add(time.Duration(ttl) * time.Second),
err: err,
} }
c.configRecord.Store(configRecord) c.configRecord.Store(configRecord)
return configRecord.config, nil return configRecord.config, configRecord.err
} }
// QueryRecord returns the ECH config for given domain. // 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. // 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) GlobalECHConfigCacheKey := ECHCacheKey(server, domain, sockopt)
echConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey) echConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey)
if !ok { if !ok {
@@ -149,25 +166,25 @@ func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) (
echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache) echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache)
} }
configRecord := echConfigCache.configRecord.Load() 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) 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 // 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 // otherwise return old value immediately and update in a goroutine
// but if the cache is too old, wait for update // but if the cache is too old, wait for update
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) { 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 { } else {
// If someone already acquired the lock, it means it is updating, do not start another update goroutine // If someone already acquired the lock, it means it is updating, do not start another update goroutine
if echConfigCache.UpdateLock.TryLock() { if echConfigCache.UpdateLock.TryLock() {
go func() { go func() {
defer echConfigCache.UpdateLock.Unlock() 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") var ErrInvalidLen = errors.New("goech: invalid length")
+15 -6
View File
@@ -3,7 +3,6 @@ package tls
import ( import (
"io" "io"
"net/http" "net/http"
"slices"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@@ -60,11 +59,21 @@ func TestECHDial(t *testing.T) {
func TestECHDialFail(t *testing.T) { func TestECHDialFail(t *testing.T) {
config := &Config{ config := &Config{
ServerName: "cloudflare.com", ServerName: "cloudflare.com",
EchConfigList: "udp://0.0.0.0", EchConfigList: "udp://127.0.0.1",
EchForceQuery: "half",
} }
tlsConfig := config.GetTLSConfig() config.GetTLSConfig()
ApplyECH(config, tlsConfig) // check cache
if !slices.Equal(tlsConfig.EncryptedClientHelloConfigList, []byte{1, 1, 4, 5, 1, 4}) { echConfigCache, ok := GlobalECHConfigCache.Load(ECHCacheKey("udp://127.0.0.1", "cloudflare.com", nil))
t.Error("ECH config should be invalid when query failed", " but got ", tlsConfig.EncryptedClientHelloConfigList) 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")
} }
} }