Compare commits

...

21 Commits

Author SHA1 Message Date
Fangliding 805abcf5f9 Allow "h2" ws 2026-04-28 18:47:28 +08:00
RPRX b4650360d6 v26.4.17
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2026-04-17 23:04:05 +00:00
Meow d52f15060b Direct/Freedom outbound: Block UDP responses that are come from ipsBlocked as well (#5952)
https://github.com/XTLS/Xray-core/pull/5947#issuecomment-4258980670

https://github.com/XTLS/Xray-core/pull/5952#issuecomment-4259324234

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-04-17 22:56:27 +00:00
Meow 31ab22c33d Geodata: Support reversed CIDR rules in IP rules (#5951)
https://github.com/XTLS/Xray-core/pull/5947#issuecomment-4258063215

https://github.com/XTLS/Xray-core/pull/5951#issuecomment-4260093653
2026-04-17 22:13:35 +00:00
Meow d42c981f9c DomainMatcher: Fix Match() result slice aliasing race (#5959)
Fixes https://github.com/XTLS/Xray-core/pull/5814
2026-04-17 22:07:58 +00:00
Иван cb1106c2fb header-custom finalmask: Extend expression primitives for 1:1 handshakes (#5949)
https://github.com/XTLS/Xray-core/pull/5945
https://github.com/XTLS/Xray-core/pull/5920
2026-04-17 22:01:54 +00:00
风扇滑翔翼 df4b97097c Loopback outbound: Avoid directly modifying potential shared ctx (#5960)
Fixes https://github.com/XTLS/Xray-core/issues/5958
2026-04-17 21:41:10 +00:00
dependabot[bot] a9cec25b8d Bump github.com/pires/go-proxyproto from 0.11.0 to 0.12.0 (#5948)
Bumps [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-17 21:33:38 +00:00
RPRX c5edc122b7 v26.4.15
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2026-04-15 23:56:34 +00:00
RPRX 9dd17d55fb XUDP GetGlobalID(): Remove inbound.Name == "wireguard" for now
https://github.com/XTLS/Xray-core/pull/5947#issuecomment-4256423483
2026-04-15 23:47:21 +00:00
Meow 310b764811 Direct/Freedom outbound: Add ipsBlocked (supports IP, CIDR, "geoip:", "ext:") and apply a default safe policy (#5947)
https://github.com/XTLS/Xray-core/pull/5892#issuecomment-4254056911

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-04-15 23:41:11 +00:00
风扇滑翔翼 3691741440 Chore: Use buf.New() instead of buf.NewWithSize() (#5946)
https://github.com/XTLS/Xray-core/pull/5941#issuecomment-4252905907

https://github.com/XTLS/Xray-core/pull/5946#issuecomment-4253919073
2026-04-15 16:57:51 +00:00
Иван 05e259c8e4 header-custom finalmask: Add UDP standalone handshake mode (#5945)
https://github.com/XTLS/Xray-core/commit/175502d8079aa5a151242ed911d01a1b90b98b28
2026-04-15 16:21:23 +00:00
Иван 175502d807 header-custom finalmask: Add programmable handshake templates and runtime core (#5920)
https://github.com/XTLS/Xray-core/pull/5920#issuecomment-4252579201
https://github.com/XTLS/Xray-core/pull/5920#issuecomment-4231698135

https://t.me/projectXtls/1829
https://t.me/projectXtls/1640
2026-04-15 14:17:51 +00:00
Boris Korzun 6780045550 TUN inbound: Add FreeBSD support (#5891)
And reverts "refactor `mtu` to support setting IPv4/v6 separately" https://github.com/XTLS/Xray-core/pull/5891#issuecomment-4245677624

And fixes `autoOutboundsInterface` on Windows https://github.com/XTLS/Xray-core/pull/5887#issuecomment-4251719900

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-04-15 12:40:19 +00:00
LjhAUMEM ff6126463b Hysteria inbound: Use transport's authentication when there are no clients (#5942) 2026-04-15 12:13:15 +00:00
LjhAUMEM 5c3d639c09 Chore: Use buf.NewWithSize() (#5941) 2026-04-15 12:11:51 +00:00
Meow 7c56b7beea DNS: Log rule matches and client order when finalQuery returns early (#5936) 2026-04-15 12:06:13 +00:00
Meow 5b91b152bb DomainMatcher: Reduce startup time on Android as well (#5935)
https://github.com/XTLS/Xray-core/pull/5924#issuecomment-4242355080
2026-04-15 12:00:41 +00:00
Meow ef77a42063 DomainMatcher: Fix CompactDomainMatcher rule indices (#5934)
Fixes https://github.com/XTLS/Xray-core/pull/5924
2026-04-15 11:56:31 +00:00
dependabot[bot] dab99614dc Bump golang.zx2c4.com/wireguard/windows from 0.5.3 to 0.6.1 (#5932)
Bumps golang.zx2c4.com/wireguard/windows from 0.5.3 to 0.6.1.

---
updated-dependencies:
- dependency-name: golang.zx2c4.com/wireguard/windows
  dependency-version: 0.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 11:54:49 +00:00
80 changed files with 4978 additions and 649 deletions
+16 -10
View File
@@ -271,11 +271,11 @@ func (s *DNS) sortClients(domain string) []*Client {
// Priority domain matching
hasMatch := false
MatchSlice := s.domainMatcher.Match(strings.ToLower(domain))
sort.Slice(MatchSlice, func(i, j int) bool {
return MatchSlice[i] < MatchSlice[j]
matchSlice := s.domainMatcher.Match(strings.ToLower(domain))
sort.Slice(matchSlice, func(i, j int) bool {
return matchSlice[i] < matchSlice[j]
})
for _, match := range MatchSlice {
for _, match := range matchSlice {
info := s.matcherInfos[match]
client := s.clients[info.clientIdx]
domainRule := info.domainRule
@@ -288,6 +288,7 @@ func (s *DNS) sortClients(domain string) []*Client {
clientNames = append(clientNames, client.Name())
hasMatch = true
if client.finalQuery {
logDecision(s.ctx, domain, domainRules, clientNames)
return clients
}
}
@@ -302,17 +303,13 @@ func (s *DNS) sortClients(domain string) []*Client {
clients = append(clients, client)
clientNames = append(clientNames, client.Name())
if client.finalQuery {
logDecision(s.ctx, domain, domainRules, clientNames)
return clients
}
}
}
if len(domainRules) > 0 {
errors.LogDebug(s.ctx, "domain ", domain, " matches following rules: ", domainRules)
}
if len(clientNames) > 0 {
errors.LogDebug(s.ctx, "domain ", domain, " will use DNS in order: ", clientNames)
}
logDecision(s.ctx, domain, domainRules, clientNames)
if len(clients) == 0 {
if len(s.clients) > 0 {
@@ -327,6 +324,15 @@ func (s *DNS) sortClients(domain string) []*Client {
return clients
}
func logDecision(ctx context.Context, domain string, domainRules []string, clientNames []string) {
if len(domainRules) > 0 {
errors.LogDebug(ctx, "domain ", domain, " matches following rules: ", domainRules)
}
if len(clientNames) > 0 {
errors.LogDebug(ctx, "domain ", domain, " will use DNS in order: ", clientNames)
}
}
func mergeQueryErrors(domain string, errs []error) error {
if len(errs) == 0 {
return dns.ErrEmptyResponse
+36 -76
View File
@@ -147,7 +147,9 @@ func TestUDPServerSubnet(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -207,7 +209,9 @@ func TestUDPServer(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -345,7 +349,9 @@ func TestPrioritizedDomain(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -414,7 +420,9 @@ func TestUDPServerIPv6(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -481,7 +489,9 @@ func TestStaticHostDomain(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -538,15 +548,8 @@ func TestIPMatch(t *testing.T) {
Port: uint32(port),
},
ExpectedIp: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
// inner ip, will not match
Ip: []byte{192, 168, 11, 1},
Prefix: 32,
},
},
},
// inner ip, will not match
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{192, 168, 11, 1}, Prefix: 32}}}},
},
},
// second dns, match ip
@@ -561,22 +564,8 @@ func TestIPMatch(t *testing.T) {
Port: uint32(port),
},
ExpectedIp: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 8, 8},
Prefix: 32,
},
},
},
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 8, 4},
Prefix: 32,
},
},
},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32}}}},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 4}, Prefix: 32}}}},
},
},
},
@@ -587,7 +576,9 @@ func TestIPMatch(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -664,9 +655,9 @@ func TestLocalDomain(t *testing.T) {
},
ExpectedIp: []*geodata.IPRule{
// Will match localhost, localhost-a and localhost-b,
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 2}, Prefix: 32}}},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 3}, Prefix: 32}}},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 4}, Prefix: 32}}},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 2}, Prefix: 32}}}},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 3}, Prefix: 32}}}},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 4}, Prefix: 32}}}},
},
},
{
@@ -704,7 +695,9 @@ func TestLocalDomain(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -887,22 +880,8 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
},
ExpectedIp: []*geodata.IPRule{
// Will only match 8.8.8.8 and 8.8.4.4
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 8, 8},
Prefix: 32,
},
},
},
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 4, 4},
Prefix: 32,
},
},
},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32}}}},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 4, 4}, Prefix: 32}}}},
},
},
{
@@ -922,14 +901,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
},
ExpectedIp: []*geodata.IPRule{
// Will match 8.8.8.8 and 8.8.8.7, etc
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 8, 7},
Prefix: 24,
},
},
},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 7}, Prefix: 24}}}},
},
},
{
@@ -949,14 +921,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
},
ExpectedIp: []*geodata.IPRule{
// Will only match 8.8.7.7 (api.google.com)
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 7, 7},
Prefix: 32,
},
},
},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 7, 7}, Prefix: 32}}}},
},
},
{
@@ -976,14 +941,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
},
ExpectedIp: []*geodata.IPRule{
// Will only match 8.8.7.8 (v2.api.google.com)
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 7, 8},
Prefix: 32,
},
},
},
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 7, 8}, Prefix: 32}}}},
},
},
},
@@ -994,7 +952,9 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+1 -1
View File
@@ -308,7 +308,7 @@ func TestServiceTestRoute(t *testing.T) {
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
},
{
SourceIp: []*geodata.IPRule{{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
SourceIp: []*geodata.IPRule{{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}}},
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
},
{
+8 -12
View File
@@ -92,25 +92,22 @@ func TestRoutingRule(t *testing.T) {
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 8, 8},
Prefix: 32,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
},
},
},
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{8, 8, 8, 8},
Prefix: 32,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
},
},
},
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(),
Prefix: 128,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(), Prefix: 128},
},
},
},
@@ -140,9 +137,8 @@ func TestRoutingRule(t *testing.T) {
SourceIp: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{192, 168, 0, 0},
Prefix: 16,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
},
},
},
+6 -9
View File
@@ -159,9 +159,8 @@ func TestIPOnDemand(t *testing.T) {
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{192, 168, 0, 0},
Prefix: 16,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
},
},
},
@@ -204,9 +203,8 @@ func TestIPIfNonMatchDomain(t *testing.T) {
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{192, 168, 0, 0},
Prefix: 16,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
},
},
},
@@ -249,9 +247,8 @@ func TestIPIfNonMatchIP(t *testing.T) {
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{127, 0, 0, 0},
Prefix: 8,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
},
},
},
+19 -17
View File
@@ -11,7 +11,11 @@ import (
)
type DomainMatcher interface {
// Match returns the indices of all rules that match the input domain.
// The returned slice is owned by the caller and may be safely modified.
// Note: the slice may contain duplicates and the order is unspecified.
Match(input string) []uint32
MatchAny(input string) bool
}
@@ -95,48 +99,43 @@ func (f *CompactDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainM
matchers: make([]strmatcher.MatcherGroup, 0, len(rules)),
values: make([]uint32, 0, len(rules)),
}
custom := strmatcher.NewLinearValueMatcher()
var idx uint32
for _, r := range rules {
for i, r := range rules {
switch v := r.Value.(type) {
case *DomainRule_Custom:
m, err := parseDomain(v.Custom)
if err != nil {
return nil, err
}
custom.Add(m, 0)
if compact.custom == nil {
compact.custom = strmatcher.NewLinearValueMatcher()
}
compact.custom.Add(m, uint32(i))
case *DomainRule_Geosite:
m, err := f.getOrCreateFrom(v.Geosite)
if err != nil {
return nil, err
}
compact.matchers = append(compact.matchers, m)
compact.values = append(compact.values, idx)
idx++
compact.values = append(compact.values, uint32(i))
default:
panic("unknown domain rule type")
}
}
if len(compact.matchers) != len(rules) {
compact.matchers = append(compact.matchers, custom)
compact.values = append(compact.values, idx+1)
}
return compact, nil
}
type CompactDomainMatcher struct {
custom strmatcher.ValueMatcher
matchers []strmatcher.MatcherGroup
values []uint32
}
func (c *CompactDomainMatcher) Add(matcher strmatcher.MatcherGroup, value uint32) {
c.matchers = append(c.matchers, matcher)
c.values = append(c.values, value)
}
// Match implements DomainMatcher.
func (c *CompactDomainMatcher) Match(input string) []uint32 {
result := make([]uint32, 0)
var result []uint32
if c.custom != nil {
result = append(result, c.custom.Match(input)...)
}
for i, m := range c.matchers {
if m.MatchAny(input) {
result = append(result, c.values[i])
@@ -147,6 +146,9 @@ func (c *CompactDomainMatcher) Match(input string) []uint32 {
// MatchAny implements DomainMatcher.
func (c *CompactDomainMatcher) MatchAny(input string) bool {
if c.custom != nil && c.custom.MatchAny(input) {
return true
}
for _, m := range c.matchers {
if m.MatchAny(input) {
return true
@@ -175,7 +177,7 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
func newDomainMatcherFactory() DomainMatcherFactory {
switch runtime.GOOS {
case "ios":
case "ios", "android":
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
default:
return &MphDomainMatcherFactory{}
+72
View File
@@ -0,0 +1,72 @@
package geodata
import (
"path/filepath"
"reflect"
"slices"
"testing"
"github.com/xtls/xray-core/common/geodata/strmatcher"
)
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
})
if err != nil {
t.Fatalf("BuildMatcher() failed: %v", err)
}
got := matcher.Match("example.com")
slices.Sort(got)
want := []uint32{0, 1}
if !reflect.DeepEqual(got, want) {
t.Fatalf("Match() = %v, want %v", got, want)
}
}
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
})
if err != nil {
t.Fatalf("BuildMatcher() failed: %v", err)
}
got := matcher.Match("163.com")
slices.Sort(got)
want := []uint32{0, 1}
if !reflect.DeepEqual(got, want) {
t.Fatalf("Match() = %v, want %v", got, want)
}
}
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
matcher, err := (&MphDomainMatcherFactory{}).BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
})
if err != nil {
t.Fatalf("BuildMatcher() failed: %v", err)
}
got := matcher.Match("example.com")
if !reflect.DeepEqual(got, []uint32{0, 1}) {
t.Fatalf("Match() = %v, want %v", got, []uint32{0, 1})
}
got[0] = 1
gotAgain := matcher.Match("example.com")
if !reflect.DeepEqual(gotAgain, []uint32{0, 1}) {
t.Fatalf("Match() after caller mutation = %v, want %v", gotAgain, []uint32{0, 1})
}
}
+96 -39
View File
@@ -433,6 +433,58 @@ func (x *CIDR) GetPrefix() uint32 {
return 0
}
type CIDRRule struct {
state protoimpl.MessageState `protogen:"open.v1"`
Cidr *CIDR `protobuf:"bytes,1,opt,name=cidr,proto3" json:"cidr,omitempty"`
ReverseMatch bool `protobuf:"varint,2,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CIDRRule) Reset() {
*x = CIDRRule{}
mi := &file_common_geodata_geodat_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CIDRRule) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CIDRRule) ProtoMessage() {}
func (x *CIDRRule) ProtoReflect() protoreflect.Message {
mi := &file_common_geodata_geodat_proto_msgTypes[6]
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 CIDRRule.ProtoReflect.Descriptor instead.
func (*CIDRRule) Descriptor() ([]byte, []int) {
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{6}
}
func (x *CIDRRule) GetCidr() *CIDR {
if x != nil {
return x.Cidr
}
return nil
}
func (x *CIDRRule) GetReverseMatch() bool {
if x != nil {
return x.ReverseMatch
}
return false
}
type GeoIP struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
@@ -444,7 +496,7 @@ type GeoIP struct {
func (x *GeoIP) Reset() {
*x = GeoIP{}
mi := &file_common_geodata_geodat_proto_msgTypes[6]
mi := &file_common_geodata_geodat_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -456,7 +508,7 @@ func (x *GeoIP) String() string {
func (*GeoIP) ProtoMessage() {}
func (x *GeoIP) ProtoReflect() protoreflect.Message {
mi := &file_common_geodata_geodat_proto_msgTypes[6]
mi := &file_common_geodata_geodat_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -469,7 +521,7 @@ func (x *GeoIP) ProtoReflect() protoreflect.Message {
// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
func (*GeoIP) Descriptor() ([]byte, []int) {
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{6}
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{7}
}
func (x *GeoIP) GetCode() string {
@@ -502,7 +554,7 @@ type GeoIPList struct {
func (x *GeoIPList) Reset() {
*x = GeoIPList{}
mi := &file_common_geodata_geodat_proto_msgTypes[7]
mi := &file_common_geodata_geodat_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -514,7 +566,7 @@ func (x *GeoIPList) String() string {
func (*GeoIPList) ProtoMessage() {}
func (x *GeoIPList) ProtoReflect() protoreflect.Message {
mi := &file_common_geodata_geodat_proto_msgTypes[7]
mi := &file_common_geodata_geodat_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -527,7 +579,7 @@ func (x *GeoIPList) ProtoReflect() protoreflect.Message {
// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
func (*GeoIPList) Descriptor() ([]byte, []int) {
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{7}
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{8}
}
func (x *GeoIPList) GetEntry() []*GeoIP {
@@ -548,7 +600,7 @@ type GeoIPRule struct {
func (x *GeoIPRule) Reset() {
*x = GeoIPRule{}
mi := &file_common_geodata_geodat_proto_msgTypes[8]
mi := &file_common_geodata_geodat_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -560,7 +612,7 @@ func (x *GeoIPRule) String() string {
func (*GeoIPRule) ProtoMessage() {}
func (x *GeoIPRule) ProtoReflect() protoreflect.Message {
mi := &file_common_geodata_geodat_proto_msgTypes[8]
mi := &file_common_geodata_geodat_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -573,7 +625,7 @@ func (x *GeoIPRule) ProtoReflect() protoreflect.Message {
// Deprecated: Use GeoIPRule.ProtoReflect.Descriptor instead.
func (*GeoIPRule) Descriptor() ([]byte, []int) {
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{8}
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{9}
}
func (x *GeoIPRule) GetFile() string {
@@ -610,7 +662,7 @@ type IPRule struct {
func (x *IPRule) Reset() {
*x = IPRule{}
mi := &file_common_geodata_geodat_proto_msgTypes[9]
mi := &file_common_geodata_geodat_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -622,7 +674,7 @@ func (x *IPRule) String() string {
func (*IPRule) ProtoMessage() {}
func (x *IPRule) ProtoReflect() protoreflect.Message {
mi := &file_common_geodata_geodat_proto_msgTypes[9]
mi := &file_common_geodata_geodat_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -635,7 +687,7 @@ func (x *IPRule) ProtoReflect() protoreflect.Message {
// Deprecated: Use IPRule.ProtoReflect.Descriptor instead.
func (*IPRule) Descriptor() ([]byte, []int) {
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{9}
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{10}
}
func (x *IPRule) GetValue() isIPRule_Value {
@@ -654,7 +706,7 @@ func (x *IPRule) GetGeoip() *GeoIPRule {
return nil
}
func (x *IPRule) GetCustom() *CIDR {
func (x *IPRule) GetCustom() *CIDRRule {
if x != nil {
if x, ok := x.Value.(*IPRule_Custom); ok {
return x.Custom
@@ -672,7 +724,7 @@ type IPRule_Geoip struct {
}
type IPRule_Custom struct {
Custom *CIDR `protobuf:"bytes,2,opt,name=custom,proto3,oneof"`
Custom *CIDRRule `protobuf:"bytes,2,opt,name=custom,proto3,oneof"`
}
func (*IPRule_Geoip) isIPRule_Value() {}
@@ -693,7 +745,7 @@ type Domain_Attribute struct {
func (x *Domain_Attribute) Reset() {
*x = Domain_Attribute{}
mi := &file_common_geodata_geodat_proto_msgTypes[10]
mi := &file_common_geodata_geodat_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -705,7 +757,7 @@ func (x *Domain_Attribute) String() string {
func (*Domain_Attribute) ProtoMessage() {}
func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
mi := &file_common_geodata_geodat_proto_msgTypes[10]
mi := &file_common_geodata_geodat_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -807,7 +859,10 @@ const file_common_geodata_geodat_proto_rawDesc = "" +
"\x05value\".\n" +
"\x04CIDR\x12\x0e\n" +
"\x02ip\x18\x01 \x01(\fR\x02ip\x12\x16\n" +
"\x06prefix\x18\x02 \x01(\rR\x06prefix\"o\n" +
"\x06prefix\x18\x02 \x01(\rR\x06prefix\"^\n" +
"\bCIDRRule\x12-\n" +
"\x04cidr\x18\x01 \x01(\v2\x19.xray.common.geodata.CIDRR\x04cidr\x12#\n" +
"\rreverse_match\x18\x02 \x01(\bR\freverseMatch\"o\n" +
"\x05GeoIP\x12\x12\n" +
"\x04code\x18\x01 \x01(\tR\x04code\x12-\n" +
"\x04cidr\x18\x02 \x03(\v2\x19.xray.common.geodata.CIDRR\x04cidr\x12#\n" +
@@ -817,10 +872,10 @@ const file_common_geodata_geodat_proto_rawDesc = "" +
"\tGeoIPRule\x12\x12\n" +
"\x04file\x18\x01 \x01(\tR\x04file\x12\x12\n" +
"\x04code\x18\x02 \x01(\tR\x04code\x12#\n" +
"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"~\n" +
"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"\x82\x01\n" +
"\x06IPRule\x126\n" +
"\x05geoip\x18\x01 \x01(\v2\x1e.xray.common.geodata.GeoIPRuleH\x00R\x05geoip\x123\n" +
"\x06custom\x18\x02 \x01(\v2\x19.xray.common.geodata.CIDRH\x00R\x06customB\a\n" +
"\x05geoip\x18\x01 \x01(\v2\x1e.xray.common.geodata.GeoIPRuleH\x00R\x05geoip\x127\n" +
"\x06custom\x18\x02 \x01(\v2\x1d.xray.common.geodata.CIDRRuleH\x00R\x06customB\a\n" +
"\x05valueB[\n" +
"\x17com.xray.common.geodataP\x01Z(github.com/xtls/xray-core/common/geodata\xaa\x02\x13Xray.Common.Geodatab\x06proto3"
@@ -837,7 +892,7 @@ func file_common_geodata_geodat_proto_rawDescGZIP() []byte {
}
var file_common_geodata_geodat_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_common_geodata_geodat_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_common_geodata_geodat_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_common_geodata_geodat_proto_goTypes = []any{
(Domain_Type)(0), // 0: xray.common.geodata.Domain.Type
(*Domain)(nil), // 1: xray.common.geodata.Domain
@@ -846,28 +901,30 @@ var file_common_geodata_geodat_proto_goTypes = []any{
(*GeoSiteRule)(nil), // 4: xray.common.geodata.GeoSiteRule
(*DomainRule)(nil), // 5: xray.common.geodata.DomainRule
(*CIDR)(nil), // 6: xray.common.geodata.CIDR
(*GeoIP)(nil), // 7: xray.common.geodata.GeoIP
(*GeoIPList)(nil), // 8: xray.common.geodata.GeoIPList
(*GeoIPRule)(nil), // 9: xray.common.geodata.GeoIPRule
(*IPRule)(nil), // 10: xray.common.geodata.IPRule
(*Domain_Attribute)(nil), // 11: xray.common.geodata.Domain.Attribute
(*CIDRRule)(nil), // 7: xray.common.geodata.CIDRRule
(*GeoIP)(nil), // 8: xray.common.geodata.GeoIP
(*GeoIPList)(nil), // 9: xray.common.geodata.GeoIPList
(*GeoIPRule)(nil), // 10: xray.common.geodata.GeoIPRule
(*IPRule)(nil), // 11: xray.common.geodata.IPRule
(*Domain_Attribute)(nil), // 12: xray.common.geodata.Domain.Attribute
}
var file_common_geodata_geodat_proto_depIdxs = []int32{
0, // 0: xray.common.geodata.Domain.type:type_name -> xray.common.geodata.Domain.Type
11, // 1: xray.common.geodata.Domain.attribute:type_name -> xray.common.geodata.Domain.Attribute
12, // 1: xray.common.geodata.Domain.attribute:type_name -> xray.common.geodata.Domain.Attribute
1, // 2: xray.common.geodata.GeoSite.domain:type_name -> xray.common.geodata.Domain
2, // 3: xray.common.geodata.GeoSiteList.entry:type_name -> xray.common.geodata.GeoSite
4, // 4: xray.common.geodata.DomainRule.geosite:type_name -> xray.common.geodata.GeoSiteRule
1, // 5: xray.common.geodata.DomainRule.custom:type_name -> xray.common.geodata.Domain
6, // 6: xray.common.geodata.GeoIP.cidr:type_name -> xray.common.geodata.CIDR
7, // 7: xray.common.geodata.GeoIPList.entry:type_name -> xray.common.geodata.GeoIP
9, // 8: xray.common.geodata.IPRule.geoip:type_name -> xray.common.geodata.GeoIPRule
6, // 9: xray.common.geodata.IPRule.custom:type_name -> xray.common.geodata.CIDR
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
6, // 6: xray.common.geodata.CIDRRule.cidr:type_name -> xray.common.geodata.CIDR
6, // 7: xray.common.geodata.GeoIP.cidr:type_name -> xray.common.geodata.CIDR
8, // 8: xray.common.geodata.GeoIPList.entry:type_name -> xray.common.geodata.GeoIP
10, // 9: xray.common.geodata.IPRule.geoip:type_name -> xray.common.geodata.GeoIPRule
7, // 10: xray.common.geodata.IPRule.custom:type_name -> xray.common.geodata.CIDRRule
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
}
func init() { file_common_geodata_geodat_proto_init() }
@@ -879,11 +936,11 @@ func file_common_geodata_geodat_proto_init() {
(*DomainRule_Geosite)(nil),
(*DomainRule_Custom)(nil),
}
file_common_geodata_geodat_proto_msgTypes[9].OneofWrappers = []any{
file_common_geodata_geodat_proto_msgTypes[10].OneofWrappers = []any{
(*IPRule_Geoip)(nil),
(*IPRule_Custom)(nil),
}
file_common_geodata_geodat_proto_msgTypes[10].OneofWrappers = []any{
file_common_geodata_geodat_proto_msgTypes[11].OneofWrappers = []any{
(*Domain_Attribute_BoolValue)(nil),
(*Domain_Attribute_IntValue)(nil),
}
@@ -893,7 +950,7 @@ func file_common_geodata_geodat_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_geodata_geodat_proto_rawDesc), len(file_common_geodata_geodat_proto_rawDesc)),
NumEnums: 1,
NumMessages: 11,
NumMessages: 12,
NumExtensions: 0,
NumServices: 0,
},
+6 -1
View File
@@ -66,6 +66,11 @@ message CIDR {
uint32 prefix = 2;
}
message CIDRRule {
CIDR cidr = 1;
bool reverse_match = 2;
}
message GeoIP {
string code = 1;
repeated CIDR cidr = 2;
@@ -85,6 +90,6 @@ message GeoIPRule {
message IPRule {
oneof value {
GeoIPRule geoip = 1;
CIDR custom = 2;
CIDRRule custom = 2;
}
}
+26 -13
View File
@@ -940,45 +940,58 @@ func (f *IPSetFactory) createFrom(yield func(func(*CIDR)) error) (*IPSet, error)
func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error) {
n := len(rules)
custom := make([]*CIDR, 0, n)
pos := make([]*GeoIPRule, 0, n)
neg := make([]*GeoIPRule, 0, n)
posCustom := make([]*CIDR, 0, n)
negCustom := make([]*CIDR, 0, n)
posGeoip := make([]*GeoIPRule, 0, n)
negGeoip := make([]*GeoIPRule, 0, n)
for _, r := range rules {
switch v := r.Value.(type) {
case *IPRule_Custom:
custom = append(custom, v.Custom)
if !v.Custom.ReverseMatch {
posCustom = append(posCustom, v.Custom.Cidr)
} else {
negCustom = append(negCustom, v.Custom.Cidr)
}
case *IPRule_Geoip:
if !v.Geoip.ReverseMatch {
pos = append(pos, v.Geoip)
posGeoip = append(posGeoip, v.Geoip)
} else {
neg = append(neg, v.Geoip)
negGeoip = append(negGeoip, v.Geoip)
}
default:
panic("unknown ip rule type")
}
}
subs := make([]*HeuristicIPMatcher, 0, 3)
subs := make([]*HeuristicIPMatcher, 0, 4)
if len(custom) > 0 {
ipset, err := f.CreateFromCIDRs(custom)
if len(posCustom) > 0 {
ipset, err := f.CreateFromCIDRs(posCustom)
if err != nil {
return nil, err
}
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: false})
}
if len(pos) > 0 {
ipset, err := f.GetOrCreateFromGeoIPRules(pos)
if len(negCustom) > 0 {
ipset, err := f.CreateFromCIDRs(negCustom)
if err != nil {
return nil, err
}
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: true})
}
if len(posGeoip) > 0 {
ipset, err := f.GetOrCreateFromGeoIPRules(posGeoip)
if err != nil {
return nil, err
}
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: false})
}
if len(neg) > 0 {
ipset, err := f.GetOrCreateFromGeoIPRules(neg)
if len(negGeoip) > 0 {
ipset, err := f.GetOrCreateFromGeoIPRules(negGeoip)
if err != nil {
return nil, err
}
+28
View File
@@ -189,6 +189,34 @@ func TestIPReverseMatcher2(t *testing.T) {
}
}
func TestIPCustomReverseMatcher(t *testing.T) {
matcher := buildIPMatcher("!8.8.8.8/32")
testCases := []struct {
Input string
Output bool
}{
{
Input: "8.8.8.8",
Output: false,
},
{
Input: "1.1.1.1",
Output: true,
},
{
Input: "2001:cdba::3257:9652",
Output: false,
},
}
for _, test := range testCases {
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
t.Error("unexpected output: ", v, " for test case ", test)
}
}
}
func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
matcher := buildIPMatcher(
"8.8.8.8/32",
+21 -10
View File
@@ -17,6 +17,8 @@ func ParseIPRules(rules []string) ([]*IPRule, error) {
var ipRules []*IPRule
for i, r := range rules {
r, reverse := cutReversePrefix(r)
if strings.HasPrefix(r, "geoip:") {
r = "ext:" + DefaultGeoIPDat + ":" + r[len("geoip:"):]
}
@@ -32,9 +34,9 @@ func ParseIPRules(rules []string) ([]*IPRule, error) {
var rule isIPRule_Value
var err error
if prefix > 0 {
rule, err = parseGeoIPRule(r[prefix:])
rule, err = parseGeoIPRule(r[prefix:], reverse)
} else {
rule, err = parseCustomIPRule(r)
rule, err = parseCustomIPRule(r, reverse)
}
if err != nil {
return nil, errors.New("illegal ip rule: ", rules[i]).Base(err)
@@ -45,7 +47,16 @@ func ParseIPRules(rules []string) ([]*IPRule, error) {
return ipRules, nil
}
func parseGeoIPRule(rule string) (*IPRule_Geoip, error) {
func cutReversePrefix(s string) (string, bool) {
reverse := false
for strings.HasPrefix(s, "!") {
s = s[1:]
reverse = !reverse
}
return s, reverse
}
func parseGeoIPRule(rule string, reverse bool) (*IPRule_Geoip, error) {
file, code, ok := strings.Cut(rule, ":")
if !ok {
return nil, errors.New("syntax error")
@@ -55,11 +66,8 @@ func parseGeoIPRule(rule string) (*IPRule_Geoip, error) {
return nil, errors.New("empty file")
}
reverse := false
if strings.HasPrefix(code, "!") {
code = code[1:]
reverse = true
}
code, codeReverse := cutReversePrefix(code)
reverse = reverse != codeReverse
if code == "" {
return nil, errors.New("empty code")
}
@@ -78,13 +86,16 @@ func parseGeoIPRule(rule string) (*IPRule_Geoip, error) {
}, nil
}
func parseCustomIPRule(rule string) (*IPRule_Custom, error) {
func parseCustomIPRule(rule string, reverse bool) (*IPRule_Custom, error) {
cidr, err := parseCIDR(rule)
if err != nil {
return nil, err
}
return &IPRule_Custom{
Custom: cidr,
Custom: &CIDRRule{
Cidr: cidr,
ReverseMatch: reverse,
},
}, nil
}
+55
View File
@@ -13,12 +13,20 @@ func TestParseIPRules(t *testing.T) {
rules := []string{
"geoip:us",
"geoip:cn",
"!geoip:cn",
"!!geoip:cn",
"geoip:!cn",
"geoip:!!cn",
"!geoip:!cn",
"ext:geoip.dat:!cn",
"ext:geoip.dat:!!cn",
"ext:geoip.dat:ca",
"ext-ip:geoip.dat:!cn",
"ext-ip:geoip.dat:!ca",
"192.168.0.0/24",
"!192.168.0.0/24",
"!!192.168.0.0/24",
"!!!192.168.0.0/24",
"192.168.0.1",
"fe80::/64",
"fe80::",
@@ -30,6 +38,53 @@ func TestParseIPRules(t *testing.T) {
}
}
func TestParseIPRuleReverse(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
for _, tt := range []struct {
rule string
reverse bool
}{
{rule: "!192.168.0.0/24", reverse: true},
{rule: "!!192.168.0.0/24", reverse: false},
{rule: "!!!192.168.0.0/24", reverse: true},
{rule: "!!!!192.168.0.0/24", reverse: false},
{rule: "geoip:cn", reverse: false},
{rule: "!geoip:cn", reverse: true},
{rule: "!!geoip:cn", reverse: false},
{rule: "geoip:!cn", reverse: true},
{rule: "geoip:!!cn", reverse: false},
{rule: "!geoip:!cn", reverse: false},
{rule: "!!geoip:!cn", reverse: true},
{rule: "!geoip:!!cn", reverse: true},
{rule: "ext:geoip.dat:!!!cn", reverse: true},
} {
t.Run(tt.rule, func(t *testing.T) {
rules, err := geodata.ParseIPRules([]string{tt.rule})
if err != nil {
t.Fatalf("Failed to parse ip rules, got %s", err)
}
if len(rules) != 1 {
t.Fatalf("Expected 1 rule, got %d", len(rules))
}
switch rule := rules[0]; {
case rule.GetGeoip() != nil:
if rule.GetGeoip().GetReverseMatch() != tt.reverse {
t.Fatalf("Expected geoip reverse match to be %t", tt.reverse)
}
case rule.GetCustom() != nil:
if rule.GetCustom().GetReverseMatch() != tt.reverse {
t.Fatalf("Expected custom reverse match to be %t", tt.reverse)
}
default:
t.Fatal("Expected ip rule")
}
})
}
}
func TestParseDomainRules(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
+2 -2
View File
@@ -3,6 +3,7 @@ package strmatcher
import (
"errors"
"regexp"
"slices"
"strings"
"unicode/utf8"
@@ -253,13 +254,12 @@ func AddMatcherToGroup(g MatcherGroup, matcher Matcher, value uint32) error {
}
// CompositeMatches flattens the matches slice to produce a single matched indices slice.
// It is designed to avoid new memory allocation as possible.
func CompositeMatches(matches [][]uint32) []uint32 {
switch len(matches) {
case 0:
return nil
case 1:
return matches[0]
return slices.Clone(matches[0])
default:
result := make([]uint32, 0, 5)
for i := 0; i < len(matches); i++ {
+2
View File
@@ -62,6 +62,7 @@ type IndexMatcher interface {
// Match returns the indices of all matchers that matches the input.
// * Empty array is returned if no such matcher exists.
// * The order of returned matchers should follow priority specification.
// * The returned slice is owned by the caller and may be safely modified.
// Priority specification:
// 1. Priority between matcher types: full > domain > substr > regex.
// 2. Priority of same-priority matchers matching at same position: the early added takes precedence.
@@ -89,6 +90,7 @@ type ValueMatcher interface {
// * Empty array is returned if no such matcher exists.
// * The order of returned values should follow priority specification.
// * Same value may appear multiple times if multiple matched matchers were added with that value.
// * The returned slice is owned by the caller and may be safely modified.
// Priority specification:
// 1. Priority between matcher types: full > domain > substr > regex.
// 2. Priority of same-priority matchers matching at same position: the early added takes precedence.
+29
View File
@@ -4,6 +4,7 @@ import (
"testing"
. "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
)
func TestGetInstance(t *testing.T) {
@@ -22,3 +23,31 @@ func TestConvertingNilMessage(t *testing.T) {
t.Error("expect nil, but actually not")
}
}
func TestTypedMessageRoundTripPreservesFinalmaskCustomUDPMode(t *testing.T) {
msg := &custom.UDPConfig{
Mode: "standalone",
Client: []*custom.UDPItem{
{Rand: 12, Save: "txid"},
},
}
tm := ToTypedMessage(msg)
if tm == nil {
t.Fatal("expected typed message")
}
roundTrip, err := tm.GetInstance()
if err != nil {
t.Fatalf("GetInstance() failed: %v", err)
}
udp, ok := roundTrip.(*custom.UDPConfig)
if !ok {
t.Fatalf("unexpected round-trip type: %T", roundTrip)
}
if udp.GetMode() != "standalone" {
t.Fatalf("mode lost during typed message round-trip: got %q", udp.GetMode())
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
return
}
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.Write([]byte(inbound.Source.String()))
copy(globalID[:], h.Sum(nil))
+1 -1
View File
@@ -20,7 +20,7 @@ import (
var (
Version_x byte = 26
Version_y byte = 4
Version_z byte = 13
Version_z byte = 17
)
var (
+9 -3
View File
@@ -52,7 +52,9 @@ func TestXrayDial(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -102,7 +104,9 @@ func TestXrayDialUDPConn(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -169,7 +173,9 @@ func TestXrayDialUDP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+2 -2
View File
@@ -12,7 +12,7 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0
github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.11.0
github.com/pires/go-proxyproto v0.12.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-shadowsocks v0.2.7
@@ -27,7 +27,7 @@ require (
golang.org/x/sys v0.43.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v0.5.3
golang.zx2c4.com/wireguard/windows v0.6.1
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
+4 -4
View File
@@ -45,8 +45,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
@@ -131,8 +131,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
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-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
golang.zx2c4.com/wireguard/windows v0.6.1 h1:XMaKojH1Hs/raMrmnir4n35nTvzvWj7NmSYzHn2F4qU=
golang.zx2c4.com/wireguard/windows v0.6.1/go.mod h1:04aqInu5GYuTFvMuDw/rKBAF7mHrltW/3rekpfbbZDM=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
+17 -8
View File
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata"
v2net "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/freedom"
@@ -15,14 +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"`
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 {
@@ -162,6 +164,13 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
if c.ProxyProtocol > 0 && c.ProxyProtocol <= 2 {
config.ProxyProtocol = c.ProxyProtocol
}
if c.IPsBlocked != nil {
rules, err := geodata.ParseIPRules(*c.IPsBlocked)
if err != nil {
return nil, err
}
config.IpsBlocked = &freedom.IPRules{Rules: rules}
}
return config, nil
}
+8 -12
View File
@@ -135,17 +135,15 @@ func TestRouterConfig(t *testing.T) {
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{10, 0, 0, 0},
Prefix: 8,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
},
},
},
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Prefix: 128,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Prefix: 128},
},
},
},
@@ -216,17 +214,15 @@ func TestRouterConfig(t *testing.T) {
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{10, 0, 0, 0},
Prefix: 8,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
},
},
},
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Prefix: 128,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Prefix: 128},
},
},
},
+220 -19
View File
@@ -8,6 +8,7 @@ import (
"math"
"net/url"
"os"
"regexp"
"runtime"
"strconv"
"strings"
@@ -1231,6 +1232,8 @@ func PraseByteSlice(data json.RawMessage, typ string) ([]byte, error) {
}
var (
customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
"fragment": func() interface{} { return new(FragmentMask) },
@@ -1256,11 +1259,14 @@ var (
)
type TCPItem struct {
Delay Int32Range `json:"delay"`
Rand int32 `json:"rand"`
RandRange *Int32Range `json:"randRange"`
Type string `json:"type"`
Packet json.RawMessage `json:"packet"`
Delay Int32Range `json:"delay"`
Rand int32 `json:"rand"`
RandRange *Int32Range `json:"randRange"`
Capture string `json:"capture"`
Type string `json:"type"`
Reuse string `json:"reuse"`
Transform *CustomTransform `json:"transform"`
Packet json.RawMessage `json:"packet"`
}
type HeaderCustomTCP struct {
@@ -1272,22 +1278,22 @@ type HeaderCustomTCP struct {
func (c *HeaderCustomTCP) Build() (proto.Message, error) {
for _, value := range c.Clients {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
}
for _, value := range c.Servers {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
}
for _, value := range c.Errors {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
}
@@ -1308,6 +1314,10 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if err != nil {
return nil, err
}
clients[i].Sequence = append(clients[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
@@ -1315,6 +1325,9 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
}
@@ -1333,6 +1346,10 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if err != nil {
return nil, err
}
servers[i].Sequence = append(servers[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
@@ -1340,6 +1357,9 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
}
@@ -1358,6 +1378,10 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if err != nil {
return nil, err
}
errors[i].Sequence = append(errors[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
@@ -1365,6 +1389,9 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
}
@@ -1471,26 +1498,185 @@ func (c *NoiseMask) Build() (proto.Message, error) {
}
type UDPItem struct {
Rand int32 `json:"rand"`
RandRange *Int32Range `json:"randRange"`
Type string `json:"type"`
Packet json.RawMessage `json:"packet"`
Rand int32 `json:"rand"`
RandRange *Int32Range `json:"randRange"`
Capture string `json:"capture"`
Type string `json:"type"`
Reuse string `json:"reuse"`
Transform *CustomTransform `json:"transform"`
Packet json.RawMessage `json:"packet"`
}
type CustomTransform struct {
Op string `json:"op"`
Args []CustomTransformArg `json:"args"`
}
type CustomTransformArg struct {
Type string `json:"type"`
Bytes json.RawMessage `json:"bytes"`
U64 *uint64 `json:"u64"`
Reuse string `json:"reuse"`
Metadata string `json:"metadata"`
Transform *CustomTransform `json:"transform"`
}
func validateCustomVarName(name string) error {
if name == "" {
return nil
}
if !customVarNamePattern.MatchString(name) {
return errors.New("invalid variable name")
}
return nil
}
func validateCustomItemSpec(capture string, packet json.RawMessage, rand int32, reuse string, transform *CustomTransform) error {
if err := validateCustomVarName(capture); err != nil {
return err
}
if err := validateCustomVarName(reuse); err != nil {
return err
}
kindCount := 0
if len(packet) > 0 {
kindCount++
}
if rand > 0 {
kindCount++
}
if reuse != "" {
kindCount++
}
if transform != nil {
kindCount++
}
if kindCount > 1 {
return errors.New("exactly one item kind must be set")
}
if kindCount == 0 && capture != "" {
return errors.New("exactly one item kind must be set")
}
return nil
}
func buildCustomTransform(transform *CustomTransform) (*custom.Expr, error) {
if transform == nil {
return nil, nil
}
if transform.Op == "" {
return nil, errors.New("transform op is required")
}
if len(transform.Args) == 0 {
return nil, errors.New("transform args are required")
}
args := make([]*custom.ExprArg, 0, len(transform.Args))
for _, arg := range transform.Args {
parsedArg, err := buildCustomTransformArg(arg)
if err != nil {
return nil, err
}
args = append(args, parsedArg)
}
return &custom.Expr{
Op: transform.Op,
Args: args,
}, nil
}
func buildCustomTransformArg(arg CustomTransformArg) (*custom.ExprArg, error) {
kindCount := 0
if len(arg.Bytes) > 0 {
kindCount++
}
if arg.U64 != nil {
kindCount++
}
if arg.Reuse != "" {
kindCount++
}
if arg.Metadata != "" {
kindCount++
}
if arg.Transform != nil {
kindCount++
}
if kindCount != 1 {
return nil, errors.New("transform arg must set exactly one value")
}
if len(arg.Bytes) > 0 {
value, err := PraseByteSlice(arg.Bytes, arg.Type)
if err != nil {
return nil, err
}
return &custom.ExprArg{
Value: &custom.ExprArg_Bytes{
Bytes: value,
},
}, nil
}
if arg.U64 != nil {
return &custom.ExprArg{
Value: &custom.ExprArg_U64{
U64: *arg.U64,
},
}, nil
}
if arg.Reuse != "" {
if err := validateCustomVarName(arg.Reuse); err != nil {
return nil, err
}
return &custom.ExprArg{
Value: &custom.ExprArg_Var{
Var: arg.Reuse,
},
}, nil
}
if arg.Metadata != "" {
return &custom.ExprArg{
Value: &custom.ExprArg_Metadata{
Metadata: arg.Metadata,
},
}, nil
}
parsedExpr, err := buildCustomTransform(arg.Transform)
if err != nil {
return nil, err
}
return &custom.ExprArg{
Value: &custom.ExprArg_Expr{
Expr: parsedExpr,
},
}, nil
}
type HeaderCustomUDP struct {
Mode string `json:"mode"`
Client []UDPItem `json:"client"`
Server []UDPItem `json:"server"`
}
func (c *HeaderCustomUDP) Build() (proto.Message, error) {
switch c.Mode {
case "", "prefix", "standalone":
default:
return nil, errors.New("unknown udp mode")
}
for _, item := range c.Client {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
for _, item := range c.Server {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
return nil, err
}
}
@@ -1506,11 +1692,18 @@ func (c *HeaderCustomUDP) Build() (proto.Message, error) {
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if err != nil {
return nil, err
}
client = append(client, &custom.UDPItem{
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
@@ -1526,17 +1719,25 @@ func (c *HeaderCustomUDP) Build() (proto.Message, error) {
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
transform, err := buildCustomTransform(item.Transform)
if err != nil {
return nil, err
}
server = append(server, &custom.UDPItem{
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
Save: item.Capture,
Var: item.Reuse,
Expr: transform,
})
}
return &custom.UDPConfig{
Client: client,
Server: server,
Mode: c.Mode,
}, nil
}
+136
View File
@@ -2,10 +2,12 @@ package conf_test
import (
"encoding/json"
"strings"
"testing"
. "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/transport/internet"
finalmaskcustom "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"google.golang.org/protobuf/proto"
)
@@ -156,3 +158,137 @@ func TestSocketConfig(t *testing.T) {
t.Fatalf("unexpected parsed TFO value, which should be -1")
}
}
func TestHeaderCustomUDPBuild(t *testing.T) {
parser := loadJSON(func() Buildable { return new(HeaderCustomUDP) })
runMultiTestCase(t, []TestCase{
{
Input: `{
"client": [
{
"type": "hex",
"packet": "aabb"
},
{
"rand": 2,
"capture": "seed",
"randRange": "16-32"
}
],
"server": [
{
"capture": "txid",
"transform": {
"op": "concat",
"args": [
{"reuse": "seed"},
{"u64": 258},
{"type": "hex", "bytes": "c0de"}
]
}
},
{
"reuse": "txid"
}
],
"mode": "standalone"
}`,
Parser: parser,
Output: &finalmaskcustom.UDPConfig{
Mode: "standalone",
Client: []*finalmaskcustom.UDPItem{
{
RandMax: 255,
Packet: []byte{0xAA, 0xBB},
},
{
Rand: 2,
RandMin: 16,
RandMax: 32,
Save: "seed",
},
},
Server: []*finalmaskcustom.UDPItem{
{
RandMax: 255,
Save: "txid",
Expr: &finalmaskcustom.Expr{
Op: "concat",
Args: []*finalmaskcustom.ExprArg{
{
Value: &finalmaskcustom.ExprArg_Var{
Var: "seed",
},
},
{
Value: &finalmaskcustom.ExprArg_U64{
U64: 258,
},
},
{
Value: &finalmaskcustom.ExprArg_Bytes{
Bytes: []byte{0xC0, 0xDE},
},
},
},
},
},
{
RandMax: 255,
Var: "txid",
},
},
},
},
})
}
func TestHeaderCustomTCPBuildRejectsMixedItemKinds(t *testing.T) {
parser := loadJSON(func() Buildable { return new(HeaderCustomTCP) })
_, err := parser(`{
"clients": [[
{
"packet": [1, 2],
"reuse": "txid"
}
]]
}`)
if err == nil || !strings.Contains(err.Error(), "exactly one item kind") {
t.Fatalf("expected mixed item kind rejection, got %v", err)
}
}
func TestHeaderCustomUDPBuildRejectsInvalidVariableNames(t *testing.T) {
parser := loadJSON(func() Buildable { return new(HeaderCustomUDP) })
_, err := parser(`{
"client": [
{
"capture": "bad-name",
"rand": 4
}
]
}`)
if err == nil || !strings.Contains(err.Error(), "invalid variable name") {
t.Fatalf("expected invalid variable name rejection, got %v", err)
}
}
func TestHeaderCustomUDPBuildRejectsExprWithoutArgs(t *testing.T) {
parser := loadJSON(func() Buildable { return new(HeaderCustomUDP) })
_, err := parser(`{
"client": [
{
"transform": {
"op": "concat"
}
}
]
}`)
if err == nil || !strings.Contains(err.Error(), "transform args") {
t.Fatalf("expected transform arg rejection, got %v", err)
}
}
+3 -6
View File
@@ -7,7 +7,7 @@ import (
type TunConfig struct {
Name string `json:"name"`
MTU []uint32 `json:"mtu"`
MTU uint32 `json:"mtu"`
Gateway []string `json:"gateway"`
DNS []string `json:"dns"`
UserLevel uint32 `json:"userLevel"`
@@ -34,11 +34,8 @@ func (v *TunConfig) Build() (proto.Message, error) {
if config.Name == "" {
config.Name = "xray0"
}
if len(config.MTU) == 0 {
config.MTU = []uint32{1500, 1280}
}
if len(config.MTU) == 1 {
config.MTU = append(config.MTU, config.MTU[0])
if config.MTU == 0 {
config.MTU = 1500
}
return config, nil
}
+8 -5
View File
@@ -99,9 +99,8 @@ func TestXrayConfig(t *testing.T) {
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{10, 0, 0, 0},
Prefix: 8,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
},
},
},
@@ -216,8 +215,12 @@ func TestSniffingConfig_Build(t *testing.T) {
if rule == nil {
t.Fatalf("SniffingConfig.Build() produced a non-custom ip rule at index %d", i)
}
if !reflect.DeepEqual(rule.Ip, tc.ip) || rule.Prefix != tc.prefix {
t.Fatalf("SniffingConfig.Build() produced wrong ip rule at index %d: got (%v, %d), want (%v, %d)", i, rule.Ip, rule.Prefix, tc.ip, tc.prefix)
cidr := rule.GetCidr()
if cidr == nil {
t.Fatalf("SniffingConfig.Build() produced a custom ip rule without cidr at index %d", i)
}
if !reflect.DeepEqual(cidr.Ip, tc.ip) || cidr.Prefix != tc.prefix {
t.Fatalf("SniffingConfig.Build() produced wrong ip rule at index %d: got (%v, %d), want (%v, %d)", i, cidr.Ip, cidr.Prefix, tc.ip, tc.prefix)
}
}
}
+84 -21
View File
@@ -7,6 +7,7 @@
package freedom
import (
geodata "github.com/xtls/xray-core/common/geodata"
protocol "github.com/xtls/xray-core/common/protocol"
internet "github.com/xtls/xray-core/transport/internet"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
@@ -251,6 +252,50 @@ func (x *Noise) GetApplyTo() string {
return ""
}
type IPRules struct {
state protoimpl.MessageState `protogen:"open.v1"`
Rules []*geodata.IPRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
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 *IPRules) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IPRules) ProtoMessage() {}
func (x *IPRules) ProtoReflect() protoreflect.Message {
mi := &file_proxy_freedom_config_proto_msgTypes[3]
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 IPRules.ProtoReflect.Descriptor instead.
func (*IPRules) Descriptor() ([]byte, []int) {
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{3}
}
func (x *IPRules) GetRules() []*geodata.IPRule {
if x != nil {
return x.Rules
}
return nil
}
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
DomainStrategy internet.DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
@@ -259,13 +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"`
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[3]
mi := &file_proxy_freedom_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -277,7 +323,7 @@ func (x *Config) String() string {
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_proxy_freedom_config_proto_msgTypes[3]
mi := &file_proxy_freedom_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -290,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{3}
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4}
}
func (x *Config) GetDomainStrategy() internet.DomainStrategy {
@@ -335,11 +381,18 @@ func (x *Config) GetNoises() []*Noise {
return nil
}
func (x *Config) GetIpsBlocked() *IPRules {
if x != nil {
return x.IpsBlocked
}
return nil
}
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\"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" +
@@ -362,7 +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\"\xe9\x02\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" +
@@ -370,7 +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\x06noisesBX\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 (
@@ -385,26 +443,30 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte {
return file_proxy_freedom_config_proto_rawDescData
}
var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_proxy_freedom_config_proto_goTypes = []any{
(*DestinationOverride)(nil), // 0: xray.proxy.freedom.DestinationOverride
(*Fragment)(nil), // 1: xray.proxy.freedom.Fragment
(*Noise)(nil), // 2: xray.proxy.freedom.Noise
(*Config)(nil), // 3: xray.proxy.freedom.Config
(*protocol.ServerEndpoint)(nil), // 4: xray.common.protocol.ServerEndpoint
(internet.DomainStrategy)(0), // 5: xray.transport.internet.DomainStrategy
(*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{
4, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
5, // 1: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
0, // 2: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
1, // 3: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
2, // 4: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] 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() }
@@ -412,13 +474,14 @@ 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: 0,
NumMessages: 4,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},
+6
View File
@@ -8,6 +8,7 @@ option java_multiple_files = true;
import "common/protocol/server_spec.proto";
import "transport/internet/config.proto";
import "common/geodata/geodat.proto";
message DestinationOverride {
xray.common.protocol.ServerEndpoint server = 1;
@@ -32,6 +33,10 @@ message Noise {
string apply_to = 6;
}
message IPRules {
repeated xray.common.geodata.IPRule rules = 1;
}
message Config {
xray.transport.internet.DomainStrategy domain_strategy = 1;
DestinationOverride destination_override = 3;
@@ -39,4 +44,5 @@ message Config {
Fragment fragment = 5;
uint32 proxy_protocol = 6;
repeated Noise noises = 7;
optional IPRules ips_blocked = 8;
}
+121 -39
View File
@@ -4,6 +4,7 @@ import (
"context"
"crypto/rand"
"io"
"strings"
"time"
"github.com/pires/go-proxyproto"
@@ -12,6 +13,7 @@ import (
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/retry"
@@ -30,6 +32,32 @@ import (
var useSplice bool
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)
@@ -50,14 +78,22 @@ func init() {
// Handler handles Freedom connections.
type Handler struct {
policyManager policy.Manager
config *Config
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
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 blocked ip matcher").Base(err)
}
h.blockedIPMatcher = m
}
return nil
}
@@ -75,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)
@@ -85,6 +147,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ob.Name = "freedom"
ob.CanSpliceCopy = 1
inbound := session.InboundFromContext(ctx)
blockedIPMatcher := h.getBlockedIPMatcher(ctx, inbound)
destination := ob.Target
origTargetAddr := ob.OriginalTarget.Address
@@ -138,23 +201,26 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return err
}
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
version := byte(h.config.ProxyProtocol)
srcAddr := inbound.Source.RawNetAddr()
dstAddr := rawConn.RemoteAddr()
header := proxyproto.HeaderProxyFromAddrs(version, srcAddr, dstAddr)
if _, err = header.WriteTo(rawConn); err != nil {
rawConn.Close()
return err
}
}
conn = rawConn
return nil
})
if err != nil {
return errors.New("failed to open connection to ", destination).Base(err)
}
if remoteAddr := net.DestinationFromAddr(conn.RemoteAddr()).Address; isBlockedAddress(blockedIPMatcher, remoteAddr) {
conn.Close()
return errors.New("blocked target IP: ", remoteAddr).AtInfo()
}
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
version := byte(h.config.ProxyProtocol)
srcAddr := inbound.Source.RawNetAddr()
dstAddr := conn.RemoteAddr()
header := proxyproto.HeaderProxyFromAddrs(version, srcAddr, dstAddr)
if _, err = header.WriteTo(conn); err != nil {
conn.Close()
return errors.New("failed to set PROXY protocol v", version).Base(err)
}
}
defer conn.Close()
errors.LogInfo(ctx, "connection opened to ", destination, ", local endpoint ", conn.LocalAddr(), ", remote endpoint ", conn.RemoteAddr())
@@ -189,7 +255,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
writer = buf.NewWriter(conn)
}
} else {
writer = NewPacketWriter(conn, h, UDPOverride, destination)
writer = NewPacketWriter(conn, h, UDPOverride, destination, blockedIPMatcher)
if h.config.Noises != nil {
errors.LogDebug(ctx, "NOISE", h.config.Noises)
writer = &NoisePacketWriter{
@@ -224,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, 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)
@@ -243,7 +309,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return nil
}
func NewPacketReader(conn net.Conn, 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 {
@@ -262,6 +328,7 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.De
return &PacketReader{
PacketConnWrapper: c,
Counter: counter,
BlockedIPMatcher: blockedIPMatcher,
IsOverridden: isOverridden,
InitUnchangedAddr: DialDest.Address,
InitChangedAddr: net.DestinationFromAddr(conn.RemoteAddr()).Address,
@@ -273,6 +340,7 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.De
type PacketReader struct {
*internet.PacketConnWrapper
stats.Counter
BlockedIPMatcher geodata.IPMatcher
IsOverridden bool
InitUnchangedAddr net.Address
InitChangedAddr net.Address
@@ -280,34 +348,42 @@ type PacketReader struct {
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New()
b.Resize(0, buf.Size)
n, d, err := r.PacketConnWrapper.ReadFrom(b.Bytes())
if err != nil {
b.Release()
return nil, err
}
b.Resize(0, int32(n))
// if udp dest addr is changed, we are unable to get the correct src addr
// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr
if !r.IsOverridden {
address := net.IPAddress(d.(*net.UDPAddr).IP)
if r.InitChangedAddr == address {
address = r.InitUnchangedAddr
for {
b.Resize(0, buf.Size)
n, d, err := r.PacketConnWrapper.ReadFrom(b.Bytes())
if err != nil {
b.Release()
return nil, err
}
b.UDP = &net.Destination{
Address: address,
Port: net.Port(d.(*net.UDPAddr).Port),
Network: net.Network_UDP,
b.Resize(0, int32(n))
udpAddr := d.(*net.UDPAddr)
sourceAddr := net.IPAddress(udpAddr.IP)
if isBlockedAddress(r.BlockedIPMatcher, sourceAddr) {
continue
}
// if udp dest addr is changed, we are unable to get the correct src addr
// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr
if !r.IsOverridden {
if r.InitChangedAddr == sourceAddr {
sourceAddr = r.InitUnchangedAddr
}
b.UDP = &net.Destination{
Address: sourceAddr,
Port: net.Port(udpAddr.Port),
Network: net.Network_UDP,
}
}
if r.Counter != nil {
r.Counter.Add(int64(n))
}
return buf.MultiBuffer{b}, nil
}
if r.Counter != nil {
r.Counter.Add(int64(n))
}
return buf.MultiBuffer{b}, nil
}
// DialDest means the dial target used in the dialer when creating conn
func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, DialDest net.Destination) 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 {
@@ -328,6 +404,7 @@ func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, Dia
PacketConnWrapper: c,
Counter: counter,
Handler: h,
BlockedIPMatcher: blockedIPMatcher,
UDPOverride: UDPOverride,
ResolvedUDPAddr: resolvedUDPAddr,
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
@@ -341,7 +418,8 @@ type PacketWriter struct {
*internet.PacketConnWrapper
stats.Counter
*Handler
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
@@ -399,6 +477,10 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
}
}
}
if isBlockedAddress(w.BlockedIPMatcher, b.UDP.Address) {
b.Release()
continue
}
destAddr := b.UDP.RawNetAddr()
if destAddr == nil {
b.Release()
+9 -2
View File
@@ -245,10 +245,14 @@ type UDPReader struct {
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
if r.firstMsg != nil {
buffer := buf.New()
buffer.Write(r.firstMsg.Data)
_, err := buffer.Write(r.firstMsg.Data)
if err != nil {
return nil, err
}
buffer.UDP = r.firstDest
r.firstMsg = nil
r.firstDest = nil
return buf.MultiBuffer{buffer}, nil
}
@@ -275,7 +279,10 @@ func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
}
buffer := buf.New()
buffer.Write(dfMsg.Data)
if _, err := buffer.Write(dfMsg.Data); err != nil {
return nil, err
}
buffer.UDP = &dest
return buf.MultiBuffer{buffer}, nil
+4 -15
View File
@@ -82,24 +82,13 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
inbound := session.InboundFromContext(ctx)
inbound.Name = "hysteria"
inbound.CanSpliceCopy = 3
inbound.User = &protocol.MemoryUser{}
iConn := stat.TryUnwrapStatsConn(conn)
var useremail string
var userlevel uint32
type User interface{ User() *protocol.MemoryUser }
if v, ok := iConn.(User); ok {
if v, ok := iConn.(User); ok && v.User() != nil {
inbound.User = v.User()
if inbound.User != nil {
useremail = inbound.User.Email
userlevel = inbound.User.Level
}
} else {
// get a dummy user
inbound.User = &protocol.MemoryUser{
Email: "",
Level: 0,
}
}
if _, ok := iConn.(*hysteria.InterUdpConn); ok {
@@ -154,7 +143,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
Writer: writer,
})
} else {
sessionPolicy := s.policyManager.ForLevel(userlevel)
sessionPolicy := s.policyManager.ForLevel(inbound.User.Level)
common.Must(conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)))
addr, err := ReadTCPRequest(conn)
@@ -178,7 +167,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
To: dest,
Status: log.AccessAccepted,
Reason: "",
Email: useremail,
Email: inbound.User.Email,
})
errors.LogInfo(ctx, "tunnelling request to ", dest)
+5 -3
View File
@@ -45,9 +45,11 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
ctx = session.ContextWithContent(ctx, content)
inbound := session.InboundFromContext(ctx)
if inbound == nil {
inbound = &session.Inbound{}
inbound := &session.Inbound{}
originInbound := session.InboundFromContext(ctx)
if originInbound != nil {
// get a shallow copy to avoid modifying the inbound tag in upstream context
*inbound = *originInbound
}
inbound.Tag = l.config.InboundTag
+19
View File
@@ -173,6 +173,25 @@ Note on ipv6 support. \
Despite Windows also giving the adapter autoconfigured ipv6 address, the ipv6 is not possible until the interface has any _routable_ ipv6 address (given link-local address will not accept traffic from external addresses). \
So everything applicable for ipv4 above also works for ipv6, you only need to give the interface some address manually, e.g. anything private like fc00::a:b:c:d/64 will do just fine
## FreeBSD SUPPORT
FreeBSD support of the same functionality is implemented through tun(4).
Interface name in the configuration must comply to the scheme "tunN", where N is some number. \
It's necessary to set an IP address to the interface, ex.:
```
ifconfig tun0 inet 169.254.10.1/30
```
To attach routing to the interface, route command like following can be executed:
```
route add -net 1.1.1.0/24 -iface tun10
```
```
route add -inet6 -host 2606:4700:4700::1111 -iface tun10
route add -inet6 -host 2606:4700:4700::1001 -iface tun10
```
Important to remember that everything written above about Linux routing concept, also apply to FreeBSD. If you simply route default route through tun interface, that will result network loop and immediate network failure.
## MAC OS X SUPPORT
Darwin (Mac OS X) support of the same functionality is implemented through utun (userspace tunnel).
+4 -4
View File
@@ -24,7 +24,7 @@ const (
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
MTU []uint32 `protobuf:"varint,2,rep,packed,name=MTU,proto3" json:"MTU,omitempty"`
MTU uint32 `protobuf:"varint,2,opt,name=MTU,proto3" json:"MTU,omitempty"`
Gateway []string `protobuf:"bytes,3,rep,name=gateway,proto3" json:"gateway,omitempty"`
DNS []string `protobuf:"bytes,4,rep,name=DNS,proto3" json:"DNS,omitempty"`
UserLevel uint32 `protobuf:"varint,5,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
@@ -71,11 +71,11 @@ func (x *Config) GetName() string {
return ""
}
func (x *Config) GetMTU() []uint32 {
func (x *Config) GetMTU() uint32 {
if x != nil {
return x.MTU
}
return nil
return 0
}
func (x *Config) GetGateway() []string {
@@ -120,7 +120,7 @@ const file_proxy_tun_config_proto_rawDesc = "" +
"\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\xee\x01\n" +
"\x06Config\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" +
"\x03MTU\x18\x02 \x03(\rR\x03MTU\x12\x18\n" +
"\x03MTU\x18\x02 \x01(\rR\x03MTU\x12\x18\n" +
"\agateway\x18\x03 \x03(\tR\agateway\x12\x10\n" +
"\x03DNS\x18\x04 \x03(\tR\x03DNS\x12\x1d\n" +
"\n" +
+1 -1
View File
@@ -8,7 +8,7 @@ option java_multiple_files = true;
message Config {
string name = 1;
repeated uint32 MTU = 2;
uint32 MTU = 2;
repeated string gateway = 3;
repeated string DNS = 4;
uint32 user_level = 5;
+1 -2
View File
@@ -110,8 +110,7 @@ func (t *stackGVisor) Start() error {
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
if srcIP == nil || dstIP == nil {
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " > invalid ip address ", id.RemoteAddress.AsSlice(), " ", id.LocalAddress.AsSlice())
return true
panic(id)
}
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
+1 -1
View File
@@ -73,7 +73,7 @@ func (t *AndroidTun) Index() (int, error) {
func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
return fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd},
MTU: t.options.MTU[0],
MTU: t.options.MTU,
RXChecksumOffload: true,
})
}
+16 -12
View File
@@ -3,7 +3,7 @@
package tun
import (
go_errors "errors"
"errors"
"fmt"
"net"
"net/netip"
@@ -12,7 +12,6 @@ import (
"unsafe"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/buffer"
@@ -76,7 +75,7 @@ func NewTun(options *Config) (Tun, error) {
return nil, err
}
err = setup(options.Name, options.MTU[0])
err = setup(options.Name, options.MTU)
if err != nil {
_ = tunFile.Close()
return nil, err
@@ -121,7 +120,7 @@ func (t *DarwinTun) Index() (int, error) {
// WritePacket implements GVisorDevice method to write one packet to the tun device
func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.options.MTU[0]) + utunHeaderSize)
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
defer b.Release()
// prepare Darwin specific packet header
@@ -143,7 +142,7 @@ func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
b.SetByte(3, family)
if _, err := t.tunFile.Write(b.Bytes()); err != nil {
if go_errors.Is(err, unix.EAGAIN) {
if errors.Is(err, unix.EAGAIN) {
return &tcpip.ErrWouldBlock{}
}
return &tcpip.ErrAborted{}
@@ -156,11 +155,11 @@ func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
// which will make the stack call Wait which should implement desired push-back
func (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.options.MTU[0]) + utunHeaderSize)
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
// read the bytes to the interface file
n, err := b.ReadFrom(t.tunFile)
if go_errors.Is(err, unix.EAGAIN) || go_errors.Is(err, unix.EINTR) {
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
b.Release()
return 0, nil, ErrQueueEmpty
}
@@ -193,7 +192,7 @@ func (t *DarwinTun) Wait() {
}
func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
return &LinkEndpoint{deviceMTU: t.options.MTU[0], device: t}, nil
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
}
// open the interface, by creating new utunN if in the system and returning its file descriptor
@@ -373,12 +372,17 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
}
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
var err1, err2 error
switch network {
case "tcp4", "udp4", "ip4":
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
case "tcp6", "udp6", "ip6":
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
err1 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
fallthrough
case "tcp4", "udp4", "ip4":
err2 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
default:
return errors.New("unknown network ", network)
panic(network + " " + address)
}
return errors.Join(err1, err2)
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !linux && !windows && !android && !darwin
//go:build !linux && !windows && !android && !darwin && !freebsd
package tun
+147
View File
@@ -0,0 +1,147 @@
//go:build freebsd
package tun
import (
"errors"
"net"
_ "unsafe"
"golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"golang.org/x/sys/unix"
"github.com/xtls/xray-core/common/buf"
)
const tunHeaderSize = 4
//go:linkname procyield runtime.procyield
func procyield(cycles uint32)
type FreeBSDTun struct {
device tun.Device
mtu uint32
}
var _ Tun = (*FreeBSDTun)(nil)
var _ GVisorDevice = (*FreeBSDTun)(nil)
// NewTun builds new tun interface handler
func NewTun(options *Config) (Tun, error) {
tunDev, err := tun.CreateTUN(options.Name, int(options.MTU))
if err != nil {
return nil, err
}
return &FreeBSDTun{device: tunDev, mtu: options.MTU}, nil
}
func (t *FreeBSDTun) Start() error {
return nil
}
func (t *FreeBSDTun) Close() error {
return t.device.Close()
}
func (t *FreeBSDTun) Name() (string, error) {
return t.device.Name()
}
func (t *FreeBSDTun) Index() (int, error) {
name, err := t.Name()
if err != nil {
return 0, err
}
iface, err := net.InterfaceByName(name)
if err != nil {
return 0, err
}
return iface.Index, nil
}
// WritePacket implements GVisorDevice method to write one packet to the tun device
func (t *FreeBSDTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.mtu) + tunHeaderSize)
defer b.Release()
// prepare Unix specific packet header
_, _ = b.Write([]byte{0x0, 0x0, 0x0, 0x0})
// copy the bytes of slices that compose the packet into the allocated buffer
for _, packetElement := range packet.AsSlices() {
_, _ = b.Write(packetElement)
}
// fill Unix specific header from the first raw packet byte, that we can access now
var family byte
switch b.Byte(4) >> 4 {
case 4:
family = unix.AF_INET
case 6:
family = unix.AF_INET6
default:
return &tcpip.ErrAborted{}
}
b.SetByte(3, family)
if _, err := t.device.File().Write(b.Bytes()); err != nil {
if errors.Is(err, unix.EAGAIN) {
return &tcpip.ErrWouldBlock{}
}
return &tcpip.ErrAborted{}
}
return nil
}
// ReadPacket implements GVisorDevice method to read one packet from the tun device
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
// which will make the stack call Wait which should implement desired push-back
func (t *FreeBSDTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.mtu) + tunHeaderSize)
// read the bytes to the interface file
n, err := b.ReadFrom(t.device.File())
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
b.Release()
return 0, nil, ErrQueueEmpty
}
if err != nil {
b.Release()
return 0, nil, err
}
// discard empty or sub-empty packets
if n <= tunHeaderSize {
b.Release()
return 0, nil, ErrQueueEmpty
}
// network protocol version from first byte of the raw packet, the one that follows Unix specific header
version := b.Byte(tunHeaderSize) >> 4
packetBuffer := buffer.MakeWithData(b.BytesFrom(tunHeaderSize))
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: packetBuffer,
IsForwardedPacket: true,
OnRelease: func() {
b.Release()
},
}), nil
}
// Wait some cpu cycles
func (t *FreeBSDTun) Wait() {
procyield(1)
}
func (t *FreeBSDTun) newEndpoint() (stack.LinkEndpoint, error) {
return &LinkEndpoint{deviceMTU: t.mtu, device: t}, nil
}
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
return nil
}
+2 -2
View File
@@ -30,7 +30,7 @@ func NewTun(options *Config) (Tun, error) {
return nil, err
}
tunLink, err := setup(options.Name, int(options.MTU[0]))
tunLink, err := setup(options.Name, int(options.MTU))
if err != nil {
_ = unix.Close(tunFd)
return nil, err
@@ -121,7 +121,7 @@ func (t *LinuxTun) Index() (int, error) {
func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
return fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd},
MTU: t.options.MTU[0],
MTU: t.options.MTU,
RXChecksumOffload: true,
})
}
+15 -18
View File
@@ -134,7 +134,7 @@ func (t *WindowsTun) Start() error {
ipif.DadTransmits = 0
ipif.ManagedAddressConfigurationSupported = false
ipif.OtherStatefulConfigurationSupported = false
ipif.NLMTU = t.options.MTU[0]
ipif.NLMTU = t.options.MTU
ipif.UseAutomaticMetric = false
ipif.Metric = 0
err = ipif.Set()
@@ -151,7 +151,7 @@ func (t *WindowsTun) Start() error {
ipif.DadTransmits = 0
ipif.ManagedAddressConfigurationSupported = false
ipif.OtherStatefulConfigurationSupported = false
ipif.NLMTU = t.options.MTU[1]
ipif.NLMTU = t.options.MTU
ipif.UseAutomaticMetric = false
ipif.Metric = 0
err = ipif.Set()
@@ -278,7 +278,7 @@ func (t *WindowsTun) Wait() {
}
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
return &LinkEndpoint{deviceMTU: t.options.MTU[0], device: t}, nil
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
}
const (
@@ -290,26 +290,23 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
var index [4]byte
binary.BigEndian.PutUint32(index[:], uint32(iface.Index))
var err1, err2, err3, err4 error
switch network {
case "tcp4", "udp4", "ip4":
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_UNICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
if err != nil {
return err
}
if network == "udp4" {
return windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_MULTICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
}
case "tcp6", "udp6", "ip6":
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
if err != nil {
return err
}
err1 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
if network == "udp6" {
return windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_MULTICAST_IF, iface.Index)
err2 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_MULTICAST_IF, iface.Index)
}
fallthrough
case "tcp4", "udp4", "ip4":
err3 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_UNICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
if network == "udp4" || network == "udp6" {
err4 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_MULTICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
}
default:
return errors.New("unknown network ", network)
panic(network + " " + address)
}
return nil
return errors.Combine(err1, err2, err3, err4)
}
+12 -19
View File
@@ -74,7 +74,7 @@ func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destina
dest: &dst,
}:
default:
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full")
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full 2")
}
}
@@ -98,25 +98,18 @@ type udpConn struct {
}
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
for {
e, ok := <-c.egress
if !ok {
return nil, io.EOF
}
b := buf.New()
_, err := b.Write(e.data)
if err != nil {
errors.LogDebugInner(context.Background(), err, "drop udp with size ", len(e.data), " to ", e.dest.NetAddr(), " original ", c.dst.NetAddr())
b.Release()
continue
}
b.UDP = e.dest
return buf.MultiBuffer{b}, nil
e, ok := <-c.egress
if !ok {
return nil, io.EOF
}
b := buf.New()
if _, err := b.Write(e.data); err != nil {
return nil, err
}
b.UDP = e.dest
return buf.MultiBuffer{b}, nil
}
// Read packets from the connection
+1
View File
@@ -100,6 +100,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
if a.Reverse != nil {
rvsCtx := session.ContextWithInbound(ctx, &session.Inbound{
Tag: a.Reverse.Tag,
Name: "vless-reverse",
User: handler.server.User, // TODO: email
})
if sc := a.Reverse.Sniffing; sc != nil && sc.Enabled {
+14 -21
View File
@@ -192,8 +192,7 @@ func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
if srcIP == nil || dstIP == nil {
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " > invalid ip address ", id.RemoteAddress.AsSlice(), " ", id.LocalAddress.AsSlice())
return true
panic(id)
}
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
@@ -256,7 +255,7 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
dest: &dst,
}:
default:
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full")
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full 2")
}
}
@@ -346,25 +345,19 @@ type udpConn struct {
}
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
for {
q, ok := <-c.queue
if !ok {
return nil, io.EOF
}
b := buf.New()
_, err := b.Write(q.p)
if err != nil {
errors.LogDebugInner(context.Background(), err, "drop udp with size ", len(q.p), " to ", q.dest.NetAddr(), " original ", c.dst.NetAddr())
b.Release()
continue
}
b.UDP = q.dest
return buf.MultiBuffer{b}, nil
q, ok := <-c.queue
if !ok {
return nil, io.EOF
}
b := buf.New()
if _, err := b.Write(q.p); err != nil {
return nil, err
}
b.UDP = q.dest
return buf.MultiBuffer{b}, nil
}
func (c *udpConn) Read(p []byte) (int, error) {
+6 -2
View File
@@ -387,7 +387,9 @@ func TestCommanderAddRemoveUser(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -573,7 +575,9 @@ func TestCommanderStats(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+2 -3
View File
@@ -47,9 +47,8 @@ func TestResolveIP(t *testing.T) {
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDR{
Ip: []byte{127, 0, 0, 0},
Prefix: 8,
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
},
},
},
+8 -4
View File
@@ -59,7 +59,9 @@ func TestDokodemoTCP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -97,7 +99,7 @@ func TestDokodemoTCP(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(),
}),
@@ -157,7 +159,9 @@ func TestDokodemoUDP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -189,7 +193,7 @@ func TestDokodemoUDP(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(),
}),
+25 -11
View File
@@ -61,7 +61,9 @@ func TestPassiveConnection(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -120,7 +122,9 @@ func TestProxy(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -147,7 +151,9 @@ func TestProxy(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -173,7 +179,7 @@ func TestProxy(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: serverUserID.String(),
}),
@@ -192,7 +198,7 @@ func TestProxy(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(proxyPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: proxyUserID.String(),
}),
@@ -245,7 +251,9 @@ func TestProxyOverKCP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -272,7 +280,9 @@ func TestProxyOverKCP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
ProtocolName: "mkcp",
@@ -303,7 +313,7 @@ func TestProxyOverKCP(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: serverUserID.String(),
}),
@@ -325,7 +335,7 @@ func TestProxyOverKCP(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(proxyPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: proxyUserID.String(),
}),
@@ -504,7 +514,9 @@ func TestUDPConnection(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -654,7 +666,9 @@ func TestDialXray(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+15 -5
View File
@@ -47,7 +47,9 @@ func TestHttpConformance(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -108,7 +110,9 @@ func TestHttpError(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -156,7 +160,9 @@ func TestHTTPConnectMethod(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -236,7 +242,9 @@ func TestHttpPost(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -306,7 +314,9 @@ func TestHttpBasicAuth(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+8 -4
View File
@@ -84,7 +84,9 @@ func TestVMessClosing(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -122,7 +124,7 @@ func TestVMessClosing(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{
@@ -190,7 +192,9 @@ func TestZeroBuffer(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -222,7 +226,7 @@ func TestZeroBuffer(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{
+6 -2
View File
@@ -81,7 +81,9 @@ func testShadowsocks2022Tcp(t *testing.T, method string, password string) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -164,7 +166,9 @@ func testShadowsocks2022Udp(t *testing.T, method string, password string) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+21 -11
View File
@@ -52,7 +52,9 @@ func TestShadowsocksChaCha20Poly1305TCP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -75,10 +77,10 @@ func TestShadowsocksChaCha20Poly1305TCP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
Server: &protocol.ServerEndpoint{
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: account,
},
},
@@ -138,7 +140,9 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -170,7 +174,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: account,
},
},
@@ -231,7 +235,9 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -263,7 +269,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: account,
},
},
@@ -323,7 +329,9 @@ func TestShadowsocksAES128GCMUDPMux(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -361,7 +369,7 @@ func TestShadowsocksAES128GCMUDPMux(t *testing.T) {
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: account,
},
},
@@ -416,7 +424,9 @@ func TestShadowsocksNone(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -442,7 +452,7 @@ func TestShadowsocksNone(t *testing.T) {
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: account,
},
},
+12 -6
View File
@@ -50,7 +50,9 @@ func TestSocksBridgeTCP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -76,7 +78,7 @@ func TestSocksBridgeTCP(t *testing.T) {
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&socks.Account{
Username: "Test Account",
Password: "Test Password",
@@ -125,7 +127,9 @@ func TestSocksWithHttpRequest(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -151,7 +155,7 @@ func TestSocksWithHttpRequest(t *testing.T) {
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&http.Account{
Username: "Test Account",
Password: "Test Password",
@@ -251,7 +255,7 @@ func TestSocksBridageUDP(t *testing.T) {
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&socks.Account{
Username: "Test Account",
Password: "Test Password",
@@ -424,7 +428,9 @@ func TestSocksConformanceMod(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+30 -10
View File
@@ -68,7 +68,9 @@ func TestSimpleTLSConnection(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -179,7 +181,9 @@ func TestAutoIssuingCertificate(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -282,7 +286,9 @@ func TestTLSOverKCP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -380,7 +386,9 @@ func TestTLSOverWebSocket(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -494,7 +502,9 @@ func TestGRPC(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -608,7 +618,9 @@ func TestGRPCMultiMode(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -715,7 +727,9 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -812,7 +826,9 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -908,7 +924,9 @@ func TestUTLSConnectionPinned(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -1006,7 +1024,9 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+4 -2
View File
@@ -62,7 +62,9 @@ func TestHTTPConnectionHeader(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -88,7 +90,7 @@ func TestHTTPConnectionHeader(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(),
}),
+15 -5
View File
@@ -66,7 +66,9 @@ func TestVless(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -169,7 +171,9 @@ func TestVlessTls(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -290,7 +294,9 @@ func TestVlessXtlsVision(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -420,7 +426,9 @@ func TestVlessXtlsVisionReality(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -557,7 +565,9 @@ func TestVlessRealityFingerprints(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+36 -12
View File
@@ -61,7 +61,9 @@ func TestVMessGCM(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -159,7 +161,9 @@ func TestVMessGCMReadv(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -260,7 +264,9 @@ func TestVMessGCMUDP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -355,7 +361,9 @@ func TestVMessChacha20(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -451,7 +459,9 @@ func TestVMessNone(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -549,7 +559,9 @@ func TestVMessKCP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -652,7 +664,9 @@ func TestVMessKCPLarge(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -756,7 +770,9 @@ func TestVMessGCMMux(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -867,7 +883,9 @@ func TestVMessGCMMuxUDP(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -988,7 +1006,9 @@ func TestVMessZero(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -1083,7 +1103,9 @@ func TestVMessGCMLengthAuth(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -1183,7 +1205,9 @@ func TestVMessGCMLengthAuthPlusNoTerminationSignal(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+3 -1
View File
@@ -62,7 +62,9 @@ func TestWireguard(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+51 -9
View File
@@ -5,6 +5,7 @@ import (
"net"
"sync"
"github.com/xtls/xray-core/common/bytespool"
"github.com/xtls/xray-core/common/errors"
)
@@ -30,6 +31,19 @@ func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketCon
var conns []net.PacketConn
for i, mask := range m.udpmasks {
if _, ok := mask.(headerConn); ok {
if mode, ok := mask.(headerConnMode); ok && !mode.UseHeaderConn() {
if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
var err error
raw, err = mask.WrapPacketConnClient(raw, i, len(m.udpmasks)-1)
if err != nil {
return nil, err
}
continue
}
conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1)
if err != nil {
return nil, err
@@ -63,6 +77,19 @@ func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketCon
var conns []net.PacketConn
for i, mask := range m.udpmasks {
if _, ok := mask.(headerConn); ok {
if mode, ok := mask.(headerConnMode); ok && !mode.UseHeaderConn() {
if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
var err error
raw, err = mask.WrapPacketConnServer(raw, i, len(m.udpmasks)-1)
if err != nil {
return nil, err
}
continue
}
conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1)
if err != nil {
return nil, err
@@ -99,22 +126,34 @@ type headerConn interface {
HeaderConn()
}
type headerConnMode interface {
UseHeaderConn() bool
}
type headerSize interface {
Size() int
}
type headerManagerConn struct {
sizes []int
conns []net.PacketConn
sync.Mutex
net.PacketConn
m sync.Mutex
sizes []int
conns []net.PacketConn
writeBuf [UDPSize]byte
}
type headerReadAddrAware interface {
SetReadAddr(net.Addr)
}
func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buf := p
if len(buf) < UDPSize {
buf = make([]byte, UDPSize)
b := bytespool.Alloc(UDPSize)
b = b[:UDPSize]
defer bytespool.Free(b)
buf = b
}
n, addr, err = c.PacketConn.ReadFrom(buf)
@@ -134,6 +173,9 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
}
for i := range c.conns {
if aware, ok := c.conns[i].(headerReadAddrAware); ok {
aware.SetReadAddr(addr)
}
n, _, err = c.conns[i].ReadFrom(newBuf)
if n == 0 || err != nil {
errors.LogDebug(context.Background(), addr, " mask read err ", err)
@@ -153,8 +195,8 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
}
func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.m.Lock()
defer c.m.Unlock()
c.Lock()
defer c.Unlock()
sum := 0
for _, size := range c.sizes {
@@ -169,7 +211,7 @@ func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error)
n = copy(c.writeBuf[sum:], p)
for i := len(c.conns) - 1; i >= 0; i-- {
n, err = c.conns[i].WriteTo(c.writeBuf[sum-c.sizes[i]:n+sum], nil)
n, err = c.conns[i].WriteTo(c.writeBuf[sum-c.sizes[i]:n+sum], addr)
if n == 0 || err != nil {
errors.LogDebug(context.Background(), addr, " mask write err ", err)
return 0, nil
@@ -249,8 +291,8 @@ func (l *tcpListener) Accept() (net.Conn, error) {
newConn, err := l.m.WrapConnServer(conn)
if err != nil {
errors.LogDebugInner(context.Background(), err, "mask err")
// conn.Close()
return conn, nil
_ = conn.Close()
return nil, err
}
return newConn, nil
@@ -19,12 +19,22 @@ func (c *UDPConfig) UDP() {
}
func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if c.Mode == "standalone" {
return NewConnClientUDPStandalone(c, raw)
}
return NewConnClientUDP(c, raw)
}
func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if c.Mode == "standalone" {
return NewConnServerUDPStandalone(c, raw)
}
return NewConnServerUDP(c, raw)
}
func (c *UDPConfig) HeaderConn() {
}
func (c *UDPConfig) UseHeaderConn() bool {
return c.Mode != "standalone"
}
@@ -21,6 +21,188 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Expr struct {
state protoimpl.MessageState `protogen:"open.v1"`
Op string `protobuf:"bytes,1,opt,name=op,proto3" json:"op,omitempty"`
Args []*ExprArg `protobuf:"bytes,2,rep,name=args,proto3" json:"args,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Expr) Reset() {
*x = Expr{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Expr) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Expr) ProtoMessage() {}
func (x *Expr) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Expr.ProtoReflect.Descriptor instead.
func (*Expr) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{0}
}
func (x *Expr) GetOp() string {
if x != nil {
return x.Op
}
return ""
}
func (x *Expr) GetArgs() []*ExprArg {
if x != nil {
return x.Args
}
return nil
}
type ExprArg struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Value:
//
// *ExprArg_Bytes
// *ExprArg_U64
// *ExprArg_Var
// *ExprArg_Metadata
// *ExprArg_Expr
Value isExprArg_Value `protobuf_oneof:"value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ExprArg) Reset() {
*x = ExprArg{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ExprArg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ExprArg) ProtoMessage() {}
func (x *ExprArg) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
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 ExprArg.ProtoReflect.Descriptor instead.
func (*ExprArg) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{1}
}
func (x *ExprArg) GetValue() isExprArg_Value {
if x != nil {
return x.Value
}
return nil
}
func (x *ExprArg) GetBytes() []byte {
if x != nil {
if x, ok := x.Value.(*ExprArg_Bytes); ok {
return x.Bytes
}
}
return nil
}
func (x *ExprArg) GetU64() uint64 {
if x != nil {
if x, ok := x.Value.(*ExprArg_U64); ok {
return x.U64
}
}
return 0
}
func (x *ExprArg) GetVar() string {
if x != nil {
if x, ok := x.Value.(*ExprArg_Var); ok {
return x.Var
}
}
return ""
}
func (x *ExprArg) GetMetadata() string {
if x != nil {
if x, ok := x.Value.(*ExprArg_Metadata); ok {
return x.Metadata
}
}
return ""
}
func (x *ExprArg) GetExpr() *Expr {
if x != nil {
if x, ok := x.Value.(*ExprArg_Expr); ok {
return x.Expr
}
}
return nil
}
type isExprArg_Value interface {
isExprArg_Value()
}
type ExprArg_Bytes struct {
Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3,oneof"`
}
type ExprArg_U64 struct {
U64 uint64 `protobuf:"varint,2,opt,name=u64,proto3,oneof"`
}
type ExprArg_Var struct {
Var string `protobuf:"bytes,3,opt,name=var,proto3,oneof"`
}
type ExprArg_Metadata struct {
Metadata string `protobuf:"bytes,4,opt,name=metadata,proto3,oneof"`
}
type ExprArg_Expr struct {
Expr *Expr `protobuf:"bytes,5,opt,name=expr,proto3,oneof"`
}
func (*ExprArg_Bytes) isExprArg_Value() {}
func (*ExprArg_U64) isExprArg_Value() {}
func (*ExprArg_Var) isExprArg_Value() {}
func (*ExprArg_Metadata) isExprArg_Value() {}
func (*ExprArg_Expr) isExprArg_Value() {}
type TCPItem struct {
state protoimpl.MessageState `protogen:"open.v1"`
DelayMin int64 `protobuf:"varint,1,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
@@ -29,13 +211,16 @@ type TCPItem struct {
RandMin int32 `protobuf:"varint,4,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
RandMax int32 `protobuf:"varint,5,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
Packet []byte `protobuf:"bytes,6,opt,name=packet,proto3" json:"packet,omitempty"`
Save string `protobuf:"bytes,7,opt,name=save,proto3" json:"save,omitempty"`
Var string `protobuf:"bytes,8,opt,name=var,proto3" json:"var,omitempty"`
Expr *Expr `protobuf:"bytes,9,opt,name=expr,proto3" json:"expr,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TCPItem) Reset() {
*x = TCPItem{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -47,7 +232,7 @@ func (x *TCPItem) String() string {
func (*TCPItem) ProtoMessage() {}
func (x *TCPItem) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -60,7 +245,7 @@ func (x *TCPItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use TCPItem.ProtoReflect.Descriptor instead.
func (*TCPItem) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{0}
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{2}
}
func (x *TCPItem) GetDelayMin() int64 {
@@ -105,6 +290,27 @@ func (x *TCPItem) GetPacket() []byte {
return nil
}
func (x *TCPItem) GetSave() string {
if x != nil {
return x.Save
}
return ""
}
func (x *TCPItem) GetVar() string {
if x != nil {
return x.Var
}
return ""
}
func (x *TCPItem) GetExpr() *Expr {
if x != nil {
return x.Expr
}
return nil
}
type TCPSequence struct {
state protoimpl.MessageState `protogen:"open.v1"`
Sequence []*TCPItem `protobuf:"bytes,1,rep,name=sequence,proto3" json:"sequence,omitempty"`
@@ -114,7 +320,7 @@ type TCPSequence struct {
func (x *TCPSequence) Reset() {
*x = TCPSequence{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -126,7 +332,7 @@ func (x *TCPSequence) String() string {
func (*TCPSequence) ProtoMessage() {}
func (x *TCPSequence) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -139,7 +345,7 @@ func (x *TCPSequence) ProtoReflect() protoreflect.Message {
// Deprecated: Use TCPSequence.ProtoReflect.Descriptor instead.
func (*TCPSequence) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{1}
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{3}
}
func (x *TCPSequence) GetSequence() []*TCPItem {
@@ -160,7 +366,7 @@ type TCPConfig struct {
func (x *TCPConfig) Reset() {
*x = TCPConfig{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -172,7 +378,7 @@ func (x *TCPConfig) String() string {
func (*TCPConfig) ProtoMessage() {}
func (x *TCPConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -185,7 +391,7 @@ func (x *TCPConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use TCPConfig.ProtoReflect.Descriptor instead.
func (*TCPConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{2}
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{4}
}
func (x *TCPConfig) GetClients() []*TCPSequence {
@@ -215,13 +421,16 @@ type UDPItem struct {
RandMin int32 `protobuf:"varint,2,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
RandMax int32 `protobuf:"varint,3,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
Packet []byte `protobuf:"bytes,4,opt,name=packet,proto3" json:"packet,omitempty"`
Save string `protobuf:"bytes,5,opt,name=save,proto3" json:"save,omitempty"`
Var string `protobuf:"bytes,6,opt,name=var,proto3" json:"var,omitempty"`
Expr *Expr `protobuf:"bytes,7,opt,name=expr,proto3" json:"expr,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UDPItem) Reset() {
*x = UDPItem{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -233,7 +442,7 @@ func (x *UDPItem) String() string {
func (*UDPItem) ProtoMessage() {}
func (x *UDPItem) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -246,7 +455,7 @@ func (x *UDPItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use UDPItem.ProtoReflect.Descriptor instead.
func (*UDPItem) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{3}
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{5}
}
func (x *UDPItem) GetRand() int32 {
@@ -277,17 +486,39 @@ func (x *UDPItem) GetPacket() []byte {
return nil
}
func (x *UDPItem) GetSave() string {
if x != nil {
return x.Save
}
return ""
}
func (x *UDPItem) GetVar() string {
if x != nil {
return x.Var
}
return ""
}
func (x *UDPItem) GetExpr() *Expr {
if x != nil {
return x.Expr
}
return nil
}
type UDPConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Client []*UDPItem `protobuf:"bytes,1,rep,name=client,proto3" json:"client,omitempty"`
Server []*UDPItem `protobuf:"bytes,2,rep,name=server,proto3" json:"server,omitempty"`
Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UDPConfig) Reset() {
*x = UDPConfig{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -299,7 +530,7 @@ func (x *UDPConfig) String() string {
func (*UDPConfig) ProtoMessage() {}
func (x *UDPConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -312,7 +543,7 @@ func (x *UDPConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use UDPConfig.ProtoReflect.Descriptor instead.
func (*UDPConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{4}
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{6}
}
func (x *UDPConfig) GetClient() []*UDPItem {
@@ -329,32 +560,56 @@ func (x *UDPConfig) GetServer() []*UDPItem {
return nil
}
func (x *UDPConfig) GetMode() string {
if x != nil {
return x.Mode
}
return ""
}
var File_transport_internet_finalmask_header_custom_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_header_custom_config_proto_rawDesc = "" +
"\n" +
"7transport/internet/finalmask/header/custom/config.proto\x12/xray.transport.internet.finalmask.header.custom\"\xa5\x01\n" +
"7transport/internet/finalmask/header/custom/config.proto\x12/xray.transport.internet.finalmask.header.custom\"d\n" +
"\x04Expr\x12\x0e\n" +
"\x02op\x18\x01 \x01(\tR\x02op\x12L\n" +
"\x04args\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.ExprArgR\x04args\"\xbd\x01\n" +
"\aExprArg\x12\x16\n" +
"\x05bytes\x18\x01 \x01(\fH\x00R\x05bytes\x12\x12\n" +
"\x03u64\x18\x02 \x01(\x04H\x00R\x03u64\x12\x12\n" +
"\x03var\x18\x03 \x01(\tH\x00R\x03var\x12\x1c\n" +
"\bmetadata\x18\x04 \x01(\tH\x00R\bmetadata\x12K\n" +
"\x04expr\x18\x05 \x01(\v25.xray.transport.internet.finalmask.header.custom.ExprH\x00R\x04exprB\a\n" +
"\x05value\"\x96\x02\n" +
"\aTCPItem\x12\x1b\n" +
"\tdelay_min\x18\x01 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x02 \x01(\x03R\bdelayMax\x12\x12\n" +
"\x04rand\x18\x03 \x01(\x05R\x04rand\x12\x19\n" +
"\brand_min\x18\x04 \x01(\x05R\arandMin\x12\x19\n" +
"\brand_max\x18\x05 \x01(\x05R\arandMax\x12\x16\n" +
"\x06packet\x18\x06 \x01(\fR\x06packet\"c\n" +
"\x06packet\x18\x06 \x01(\fR\x06packet\x12\x12\n" +
"\x04save\x18\a \x01(\tR\x04save\x12\x10\n" +
"\x03var\x18\b \x01(\tR\x03var\x12I\n" +
"\x04expr\x18\t \x01(\v25.xray.transport.internet.finalmask.header.custom.ExprR\x04expr\"c\n" +
"\vTCPSequence\x12T\n" +
"\bsequence\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.TCPItemR\bsequence\"\x91\x02\n" +
"\tTCPConfig\x12V\n" +
"\aclients\x18\x01 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\aclients\x12V\n" +
"\aservers\x18\x02 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\aservers\x12T\n" +
"\x06errors\x18\x03 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\x06errors\"k\n" +
"\x06errors\x18\x03 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\x06errors\"\xdc\x01\n" +
"\aUDPItem\x12\x12\n" +
"\x04rand\x18\x01 \x01(\x05R\x04rand\x12\x19\n" +
"\brand_min\x18\x02 \x01(\x05R\arandMin\x12\x19\n" +
"\brand_max\x18\x03 \x01(\x05R\arandMax\x12\x16\n" +
"\x06packet\x18\x04 \x01(\fR\x06packet\"\xaf\x01\n" +
"\x06packet\x18\x04 \x01(\fR\x06packet\x12\x12\n" +
"\x04save\x18\x05 \x01(\tR\x04save\x12\x10\n" +
"\x03var\x18\x06 \x01(\tR\x03var\x12I\n" +
"\x04expr\x18\a \x01(\v25.xray.transport.internet.finalmask.header.custom.ExprR\x04expr\"\xc3\x01\n" +
"\tUDPConfig\x12P\n" +
"\x06client\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06client\x12P\n" +
"\x06server\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06serverB\xaf\x01\n" +
"\x06server\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06server\x12\x12\n" +
"\x04mode\x18\x03 \x01(\tR\x04modeB\xaf\x01\n" +
"3com.xray.transport.internet.finalmask.header.customP\x01ZDgithub.com/xtls/xray-core/transport/internet/finalmask/header/custom\xaa\x02/Xray.Transport.Internet.Finalmask.Header.Customb\x06proto3"
var (
@@ -369,26 +624,32 @@ func file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP()
return file_transport_internet_finalmask_header_custom_config_proto_rawDescData
}
var file_transport_internet_finalmask_header_custom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_transport_internet_finalmask_header_custom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_transport_internet_finalmask_header_custom_config_proto_goTypes = []any{
(*TCPItem)(nil), // 0: xray.transport.internet.finalmask.header.custom.TCPItem
(*TCPSequence)(nil), // 1: xray.transport.internet.finalmask.header.custom.TCPSequence
(*TCPConfig)(nil), // 2: xray.transport.internet.finalmask.header.custom.TCPConfig
(*UDPItem)(nil), // 3: xray.transport.internet.finalmask.header.custom.UDPItem
(*UDPConfig)(nil), // 4: xray.transport.internet.finalmask.header.custom.UDPConfig
(*Expr)(nil), // 0: xray.transport.internet.finalmask.header.custom.Expr
(*ExprArg)(nil), // 1: xray.transport.internet.finalmask.header.custom.ExprArg
(*TCPItem)(nil), // 2: xray.transport.internet.finalmask.header.custom.TCPItem
(*TCPSequence)(nil), // 3: xray.transport.internet.finalmask.header.custom.TCPSequence
(*TCPConfig)(nil), // 4: xray.transport.internet.finalmask.header.custom.TCPConfig
(*UDPItem)(nil), // 5: xray.transport.internet.finalmask.header.custom.UDPItem
(*UDPConfig)(nil), // 6: xray.transport.internet.finalmask.header.custom.UDPConfig
}
var file_transport_internet_finalmask_header_custom_config_proto_depIdxs = []int32{
0, // 0: xray.transport.internet.finalmask.header.custom.TCPSequence.sequence:type_name -> xray.transport.internet.finalmask.header.custom.TCPItem
1, // 1: xray.transport.internet.finalmask.header.custom.TCPConfig.clients:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
1, // 2: xray.transport.internet.finalmask.header.custom.TCPConfig.servers:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
1, // 3: xray.transport.internet.finalmask.header.custom.TCPConfig.errors:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
3, // 4: xray.transport.internet.finalmask.header.custom.UDPConfig.client:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
3, // 5: xray.transport.internet.finalmask.header.custom.UDPConfig.server:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
1, // 0: xray.transport.internet.finalmask.header.custom.Expr.args:type_name -> xray.transport.internet.finalmask.header.custom.ExprArg
0, // 1: xray.transport.internet.finalmask.header.custom.ExprArg.expr:type_name -> xray.transport.internet.finalmask.header.custom.Expr
0, // 2: xray.transport.internet.finalmask.header.custom.TCPItem.expr:type_name -> xray.transport.internet.finalmask.header.custom.Expr
2, // 3: xray.transport.internet.finalmask.header.custom.TCPSequence.sequence:type_name -> xray.transport.internet.finalmask.header.custom.TCPItem
3, // 4: xray.transport.internet.finalmask.header.custom.TCPConfig.clients:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
3, // 5: xray.transport.internet.finalmask.header.custom.TCPConfig.servers:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
3, // 6: xray.transport.internet.finalmask.header.custom.TCPConfig.errors:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
0, // 7: xray.transport.internet.finalmask.header.custom.UDPItem.expr:type_name -> xray.transport.internet.finalmask.header.custom.Expr
5, // 8: xray.transport.internet.finalmask.header.custom.UDPConfig.client:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
5, // 9: xray.transport.internet.finalmask.header.custom.UDPConfig.server:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_header_custom_config_proto_init() }
@@ -396,13 +657,20 @@ func file_transport_internet_finalmask_header_custom_config_proto_init() {
if File_transport_internet_finalmask_header_custom_config_proto != nil {
return
}
file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1].OneofWrappers = []any{
(*ExprArg_Bytes)(nil),
(*ExprArg_U64)(nil),
(*ExprArg_Var)(nil),
(*ExprArg_Metadata)(nil),
(*ExprArg_Expr)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_custom_config_proto_rawDesc), len(file_transport_internet_finalmask_header_custom_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 5,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
@@ -6,6 +6,21 @@ option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/head
option java_package = "com.xray.transport.internet.finalmask.header.custom";
option java_multiple_files = true;
message Expr {
string op = 1;
repeated ExprArg args = 2;
}
message ExprArg {
oneof value {
bytes bytes = 1;
uint64 u64 = 2;
string var = 3;
string metadata = 4;
Expr expr = 5;
}
}
message TCPItem {
int64 delay_min = 1;
int64 delay_max = 2;
@@ -13,6 +28,9 @@ message TCPItem {
int32 rand_min = 4;
int32 rand_max = 5;
bytes packet = 6;
string save = 7;
string var = 8;
Expr expr = 9;
}
message TCPSequence {
@@ -30,9 +48,13 @@ message UDPItem {
int32 rand_min = 2;
int32 rand_max = 3;
bytes packet = 4;
string save = 5;
string var = 6;
Expr expr = 7;
}
message UDPConfig {
repeated UDPItem client = 1;
repeated UDPItem server = 2;
}
string mode = 3;
}
@@ -0,0 +1,625 @@
package custom
import (
"encoding/binary"
"net"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
)
type evalValue struct {
bytes []byte
u64 *uint64
isBytes bool
}
type evalContext struct {
vars map[string][]byte
metadata map[string]evalValue
}
func newEvalContext() *evalContext {
return &evalContext{
vars: make(map[string][]byte),
metadata: make(map[string]evalValue),
}
}
func newEvalContextWithAddrs(local, remote net.Addr) *evalContext {
ctx := newEvalContext()
loadMetadata(ctx.metadata, "local", local)
loadMetadata(ctx.metadata, "remote", remote)
return ctx
}
func evaluateUDPItems(items []*UDPItem) ([]byte, error) {
return evaluateUDPItemsWithContext(items, newEvalContext())
}
func evaluateUDPItemsWithContext(items []*UDPItem, ctx *evalContext) ([]byte, error) {
var out []byte
for _, item := range items {
value, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
if err != nil {
return nil, err
}
out = append(out, value...)
}
return out, nil
}
func measureUDPItems(items []*UDPItem) (int, error) {
return measureUDPItemsWithFallback(items, nil)
}
func measureUDPItemsWithFallback(items []*UDPItem, fallback map[string]int) (int, error) {
sizeCtx := make(map[string]int)
for key, value := range fallback {
sizeCtx[key] = value
}
total := 0
for _, item := range items {
itemSize, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeCtx)
if err != nil {
return 0, err
}
total += itemSize
}
return total, nil
}
func collectSavedUDPSizes(items []*UDPItem) map[string]int {
sizeCtx := make(map[string]int)
for _, item := range items {
itemSize, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeCtx)
if err != nil {
continue
}
if item.Save != "" {
sizeCtx[item.Save] = itemSize
}
}
return sizeCtx
}
func measureItem(randLen int32, packet []byte, save, varName string, expr *Expr, sizeCtx map[string]int) (int, error) {
var size int
switch {
case randLen > 0:
size = int(randLen)
case len(packet) > 0:
size = len(packet)
case varName != "":
length, ok := sizeCtx[varName]
if !ok {
return 0, errors.New("unknown variable: ", varName)
}
size = length
case expr != nil:
exprSize, err := measureExpr(expr, sizeCtx)
if err != nil {
return 0, err
}
size = exprSize
default:
size = 0
}
if save != "" {
sizeCtx[save] = size
}
return size, nil
}
func evaluateTCPSequence(sequence *TCPSequence) ([]byte, error) {
ctx := newEvalContext()
var out []byte
for _, item := range sequence.Sequence {
value, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
if err != nil {
return nil, err
}
out = append(out, value...)
}
return out, nil
}
func evaluateItem(randLen, randMin, randMax int32, packet []byte, save, varName string, expr *Expr, ctx *evalContext) ([]byte, error) {
var value []byte
switch {
case randLen > 0:
value = make([]byte, randLen)
crypto.RandBytesBetween(value, byte(randMin), byte(randMax))
case len(packet) > 0:
value = append([]byte(nil), packet...)
case varName != "":
saved, ok := ctx.vars[varName]
if !ok {
return nil, errors.New("unknown variable: ", varName)
}
value = append([]byte(nil), saved...)
case expr != nil:
evaluated, err := evaluateExpr(expr, ctx)
if err != nil {
return nil, err
}
bytesValue, err := evaluated.asBytes()
if err != nil {
return nil, err
}
value = bytesValue
default:
value = nil
}
if save != "" {
ctx.vars[save] = append([]byte(nil), value...)
}
return value, nil
}
func evaluateExpr(expr *Expr, ctx *evalContext) (evalValue, error) {
switch expr.GetOp() {
case "concat":
var out []byte
for _, arg := range expr.GetArgs() {
value, err := evaluateExprArg(arg, ctx)
if err != nil {
return evalValue{}, err
}
bytesValue, err := value.asBytes()
if err != nil {
return evalValue{}, err
}
out = append(out, bytesValue...)
}
return evalValue{bytes: out, isBytes: true}, nil
case "slice":
if len(expr.GetArgs()) != 3 {
return evalValue{}, errors.New("slice expects 3 args")
}
source, err := evaluateExprArg(expr.GetArgs()[0], ctx)
if err != nil {
return evalValue{}, err
}
offset, err := evaluateExprArg(expr.GetArgs()[1], ctx)
if err != nil {
return evalValue{}, err
}
length, err := evaluateExprArg(expr.GetArgs()[2], ctx)
if err != nil {
return evalValue{}, err
}
sourceBytes, err := source.asBytes()
if err != nil {
return evalValue{}, err
}
offsetU64, err := offset.asU64()
if err != nil {
return evalValue{}, err
}
lengthU64, err := length.asU64()
if err != nil {
return evalValue{}, err
}
end := offsetU64 + lengthU64
if end > uint64(len(sourceBytes)) {
return evalValue{}, errors.New("slice out of bounds")
}
return evalValue{bytes: append([]byte(nil), sourceBytes[offsetU64:end]...), isBytes: true}, nil
case "xor16":
return evaluateXor(expr.GetArgs(), 0xFFFF, 2, ctx)
case "xor32":
return evaluateXor(expr.GetArgs(), 0xFFFFFFFF, 4, ctx)
case "be16":
return evaluatePack(expr.GetArgs(), "be16", 2, binary.BigEndian, ctx)
case "be32":
return evaluatePack(expr.GetArgs(), "be32", 4, binary.BigEndian, ctx)
case "le16":
return evaluatePack(expr.GetArgs(), "le16", 2, binary.LittleEndian, ctx)
case "le32":
return evaluatePack(expr.GetArgs(), "le32", 4, binary.LittleEndian, ctx)
case "le64":
return evaluatePack(expr.GetArgs(), "le64", 8, binary.LittleEndian, ctx)
case "pad":
return evaluatePad(expr.GetArgs(), ctx)
case "truncate":
return evaluateTruncate(expr.GetArgs(), ctx)
case "add":
return evaluateBinaryU64Op(expr.GetArgs(), "add", ctx, func(left, right uint64) (uint64, error) {
if left > ^uint64(0)-right {
return 0, errors.New("add overflow")
}
return left + right, nil
})
case "sub":
return evaluateBinaryU64Op(expr.GetArgs(), "sub", ctx, func(left, right uint64) (uint64, error) {
if left < right {
return 0, errors.New("sub underflow")
}
return left - right, nil
})
case "and":
return evaluateBinaryU64Op(expr.GetArgs(), "and", ctx, func(left, right uint64) (uint64, error) {
return left & right, nil
})
case "or":
return evaluateBinaryU64Op(expr.GetArgs(), "or", ctx, func(left, right uint64) (uint64, error) {
return left | right, nil
})
case "shl":
return evaluateShift(expr.GetArgs(), "shl", ctx, func(value uint64, shift uint) (uint64, error) {
if shift >= 64 {
return 0, errors.New("shift out of range")
}
if value > (^uint64(0) >> shift) {
return 0, errors.New("shl overflow")
}
return value << shift, nil
})
case "shr":
return evaluateShift(expr.GetArgs(), "shr", ctx, func(value uint64, shift uint) (uint64, error) {
if shift >= 64 {
return 0, errors.New("shift out of range")
}
return value >> shift, nil
})
default:
return evalValue{}, errors.New("unsupported expr op: ", expr.GetOp())
}
}
func evaluatePack(args []*ExprArg, name string, width int, order binary.ByteOrder, ctx *evalContext) (evalValue, error) {
if len(args) != 1 {
return evalValue{}, errors.New(name, " expects 1 arg")
}
value, err := evaluateExprArg(args[0], ctx)
if err != nil {
return evalValue{}, err
}
u64Value, err := value.asU64()
if err != nil {
return evalValue{}, err
}
switch width {
case 2:
if u64Value > 0xFFFF {
return evalValue{}, errors.New(name, " overflow")
}
out := make([]byte, 2)
order.PutUint16(out, uint16(u64Value))
return evalValue{bytes: out, isBytes: true}, nil
case 4:
if u64Value > 0xFFFFFFFF {
return evalValue{}, errors.New(name, " overflow")
}
out := make([]byte, 4)
order.PutUint32(out, uint32(u64Value))
return evalValue{bytes: out, isBytes: true}, nil
case 8:
out := make([]byte, 8)
order.PutUint64(out, u64Value)
return evalValue{bytes: out, isBytes: true}, nil
default:
return evalValue{}, errors.New("unsupported pack width")
}
}
func evaluatePad(args []*ExprArg, ctx *evalContext) (evalValue, error) {
if len(args) != 3 {
return evalValue{}, errors.New("pad expects 3 args")
}
source, err := evaluateExprArg(args[0], ctx)
if err != nil {
return evalValue{}, err
}
target, err := evaluateExprArg(args[1], ctx)
if err != nil {
return evalValue{}, err
}
fill, err := evaluateExprArg(args[2], ctx)
if err != nil {
return evalValue{}, err
}
sourceBytes, err := source.asBytes()
if err != nil {
return evalValue{}, err
}
targetU64, err := target.asU64()
if err != nil {
return evalValue{}, err
}
fillBytes, err := fill.asBytes()
if err != nil {
return evalValue{}, err
}
if len(fillBytes) == 0 {
return evalValue{}, errors.New("pad fill must not be empty")
}
if targetU64 < uint64(len(sourceBytes)) {
return evalValue{}, errors.New("pad target shorter than source")
}
out := append([]byte(nil), sourceBytes...)
for uint64(len(out)) < targetU64 {
remaining := int(targetU64) - len(out)
if remaining >= len(fillBytes) {
out = append(out, fillBytes...)
continue
}
out = append(out, fillBytes[:remaining]...)
}
return evalValue{bytes: out, isBytes: true}, nil
}
func evaluateTruncate(args []*ExprArg, ctx *evalContext) (evalValue, error) {
if len(args) != 2 {
return evalValue{}, errors.New("truncate expects 2 args")
}
source, err := evaluateExprArg(args[0], ctx)
if err != nil {
return evalValue{}, err
}
length, err := evaluateExprArg(args[1], ctx)
if err != nil {
return evalValue{}, err
}
sourceBytes, err := source.asBytes()
if err != nil {
return evalValue{}, err
}
lengthU64, err := length.asU64()
if err != nil {
return evalValue{}, err
}
if lengthU64 > uint64(len(sourceBytes)) {
return evalValue{}, errors.New("truncate out of bounds")
}
return evalValue{bytes: append([]byte(nil), sourceBytes[:lengthU64]...), isBytes: true}, nil
}
func evaluateBinaryU64Op(args []*ExprArg, name string, ctx *evalContext, op func(left, right uint64) (uint64, error)) (evalValue, error) {
if len(args) != 2 {
return evalValue{}, errors.New(name, " expects 2 args")
}
left, err := evaluateExprArg(args[0], ctx)
if err != nil {
return evalValue{}, err
}
right, err := evaluateExprArg(args[1], ctx)
if err != nil {
return evalValue{}, err
}
leftU64, err := left.asU64()
if err != nil {
return evalValue{}, err
}
rightU64, err := right.asU64()
if err != nil {
return evalValue{}, err
}
result, err := op(leftU64, rightU64)
if err != nil {
return evalValue{}, err
}
return evalValue{u64: &result}, nil
}
func evaluateShift(args []*ExprArg, name string, ctx *evalContext, op func(value uint64, shift uint) (uint64, error)) (evalValue, error) {
if len(args) != 2 {
return evalValue{}, errors.New(name, " expects 2 args")
}
value, err := evaluateExprArg(args[0], ctx)
if err != nil {
return evalValue{}, err
}
shift, err := evaluateExprArg(args[1], ctx)
if err != nil {
return evalValue{}, err
}
valueU64, err := value.asU64()
if err != nil {
return evalValue{}, err
}
shiftU64, err := shift.asU64()
if err != nil {
return evalValue{}, err
}
if shiftU64 >= 64 {
return evalValue{}, errors.New("shift out of range")
}
result, err := op(valueU64, uint(shiftU64))
if err != nil {
return evalValue{}, err
}
return evalValue{u64: &result}, nil
}
func evaluateXor(args []*ExprArg, mask uint64, width int, ctx *evalContext) (evalValue, error) {
if len(args) != 2 {
return evalValue{}, errors.New("xor expects 2 args")
}
left, err := evaluateExprArg(args[0], ctx)
if err != nil {
return evalValue{}, err
}
right, err := evaluateExprArg(args[1], ctx)
if err != nil {
return evalValue{}, err
}
leftU64, err := left.asU64()
if err != nil {
return evalValue{}, err
}
rightU64, err := right.asU64()
if err != nil {
return evalValue{}, err
}
if width == 2 && (leftU64 > 0xFFFF || rightU64 > 0xFFFF) {
return evalValue{}, errors.New("xor16 overflow")
}
if width == 4 && (leftU64 > 0xFFFFFFFF || rightU64 > 0xFFFFFFFF) {
return evalValue{}, errors.New("xor32 overflow")
}
result := (leftU64 ^ rightU64) & mask
return evalValue{u64: &result}, nil
}
func measureExpr(expr *Expr, sizeCtx map[string]int) (int, error) {
switch expr.GetOp() {
case "concat":
total := 0
for _, arg := range expr.GetArgs() {
size, err := measureExprArg(arg, sizeCtx)
if err != nil {
return 0, err
}
total += size
}
return total, nil
case "slice":
if len(expr.GetArgs()) != 3 {
return 0, errors.New("slice expects 3 args")
}
lengthArg := expr.GetArgs()[2]
if value, ok := lengthArg.GetValue().(*ExprArg_U64); ok {
return int(value.U64), nil
}
return 0, errors.New("slice length must be u64")
case "be16":
return 2, nil
case "be32":
return 4, nil
case "le16":
return 2, nil
case "le32":
return 4, nil
case "le64":
return 8, nil
case "pad":
if len(expr.GetArgs()) != 3 {
return 0, errors.New("pad expects 3 args")
}
lengthArg := expr.GetArgs()[1]
if value, ok := lengthArg.GetValue().(*ExprArg_U64); ok {
return int(value.U64), nil
}
return 0, errors.New("pad length must be u64")
case "truncate":
if len(expr.GetArgs()) != 2 {
return 0, errors.New("truncate expects 2 args")
}
lengthArg := expr.GetArgs()[1]
if value, ok := lengthArg.GetValue().(*ExprArg_U64); ok {
return int(value.U64), nil
}
return 0, errors.New("truncate length must be u64")
default:
return 0, errors.New("expr size is not bytes for op: ", expr.GetOp())
}
}
func evaluateExprArg(arg *ExprArg, ctx *evalContext) (evalValue, error) {
switch value := arg.GetValue().(type) {
case *ExprArg_Bytes:
return evalValue{bytes: append([]byte(nil), value.Bytes...), isBytes: true}, nil
case *ExprArg_U64:
return evalValue{u64: &value.U64}, nil
case *ExprArg_Var:
saved, ok := ctx.vars[value.Var]
if !ok {
return evalValue{}, errors.New("unknown variable: ", value.Var)
}
return evalValue{bytes: append([]byte(nil), saved...), isBytes: true}, nil
case *ExprArg_Metadata:
metadata, ok := ctx.metadata[value.Metadata]
if !ok {
return evalValue{}, errors.New("unknown metadata: ", value.Metadata)
}
return metadata, nil
case *ExprArg_Expr:
return evaluateExpr(value.Expr, ctx)
default:
return evalValue{}, errors.New("empty expr arg")
}
}
func measureExprArg(arg *ExprArg, sizeCtx map[string]int) (int, error) {
switch value := arg.GetValue().(type) {
case *ExprArg_Bytes:
return len(value.Bytes), nil
case *ExprArg_U64:
return 0, errors.New("u64 arg has no byte width")
case *ExprArg_Var:
length, ok := sizeCtx[value.Var]
if !ok {
return 0, errors.New("unknown variable: ", value.Var)
}
return length, nil
case *ExprArg_Metadata:
return 0, errors.New("metadata not implemented: ", value.Metadata)
case *ExprArg_Expr:
return measureExpr(value.Expr, sizeCtx)
default:
return 0, errors.New("empty expr arg")
}
}
func (v evalValue) asBytes() ([]byte, error) {
if v.isBytes {
return append([]byte(nil), v.bytes...), nil
}
return nil, errors.New("expr value is not bytes")
}
func (v evalValue) asU64() (uint64, error) {
if v.u64 != nil {
return *v.u64, nil
}
return 0, errors.New("expr value is not u64")
}
func sizeMapFromEvalContext(ctx *evalContext) map[string]int {
sizes := make(map[string]int, len(ctx.vars))
for key, value := range ctx.vars {
sizes[key] = len(value)
}
return sizes
}
func loadMetadata(dst map[string]evalValue, prefix string, addr net.Addr) {
if addr == nil {
return
}
switch value := addr.(type) {
case *net.UDPAddr:
loadIPPortMetadata(dst, prefix, value.IP, value.Port)
case *net.TCPAddr:
loadIPPortMetadata(dst, prefix, value.IP, value.Port)
}
}
func loadIPPortMetadata(dst map[string]evalValue, prefix string, ip net.IP, port int) {
portValue := uint64(port)
dst[prefix+"_port"] = evalValue{u64: &portValue}
if prefix == "remote" {
dst["src_port_u16"] = evalValue{u64: &portValue}
} else if prefix == "local" {
dst["dst_port_u16"] = evalValue{u64: &portValue}
}
if ip4 := ip.To4(); ip4 != nil {
ipValue := uint64(binary.BigEndian.Uint32(ip4))
dst[prefix+"_ip4_u32"] = evalValue{u64: &ipValue}
if prefix == "remote" {
dst["src_ip4_u32"] = evalValue{u64: &ipValue}
} else if prefix == "local" {
dst["dst_ip4_u32"] = evalValue{u64: &ipValue}
}
}
}
@@ -0,0 +1,491 @@
package custom
import (
"bytes"
"testing"
)
func TestEvaluatorSaveAndReuseWithinPacket(t *testing.T) {
items := []*UDPItem{
{
Rand: 4,
RandMin: 0x2A,
RandMax: 0x2A,
Save: "txid",
},
{
Var: "txid",
},
}
got, err := evaluateUDPItems(items)
if err != nil {
t.Fatal(err)
}
want := bytes.Repeat([]byte{0x2A}, 8)
if !bytes.Equal(got, want) {
t.Fatalf("unexpected output: %x", got)
}
}
func TestEvaluatorSliceReturnsWindow(t *testing.T) {
sequence := &TCPSequence{
Sequence: []*TCPItem{
{
Expr: &Expr{
Op: "slice",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
{Value: &ExprArg_U64{U64: 1}},
{Value: &ExprArg_U64{U64: 2}},
},
},
},
},
}
got, err := evaluateTCPSequence(sequence)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, []byte{2, 3}) {
t.Fatalf("unexpected output: %x", got)
}
}
func TestEvaluatorConcatPreservesOrder(t *testing.T) {
items := []*UDPItem{
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte("ab")}},
{Value: &ExprArg_Bytes{Bytes: []byte("cd")}},
{Value: &ExprArg_Bytes{Bytes: []byte("ef")}},
},
},
},
}
got, err := evaluateUDPItems(items)
if err != nil {
t.Fatal(err)
}
if string(got) != "abcdef" {
t.Fatalf("unexpected output: %q", got)
}
}
func TestEvaluatorBeXorProducesExpectedBytes(t *testing.T) {
items := []*UDPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "xor16",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 0x1234}},
{Value: &ExprArg_U64{U64: 0xFFFF}},
},
},
},
},
},
},
},
}
got, err := evaluateUDPItems(items)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, []byte{0xED, 0xCB}) {
t.Fatalf("unexpected output: %x", got)
}
}
func TestEvaluatorRejectsInvalidArgType(t *testing.T) {
items := []*UDPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{0x01}}},
},
},
},
}
_, err := evaluateUDPItems(items)
if err == nil {
t.Fatal("expected evaluator error")
}
}
func TestEvaluatorLittleEndianProducesExpectedBytes(t *testing.T) {
items := []*UDPItem{
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "le16",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 0x1234}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "le32",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 0xA1B2C3D4}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "le64",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 0x0102030405060708}},
},
},
},
},
},
},
},
}
got, err := evaluateUDPItems(items)
if err != nil {
t.Fatal(err)
}
want := []byte{
0x34, 0x12,
0xD4, 0xC3, 0xB2, 0xA1,
0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
}
if !bytes.Equal(got, want) {
t.Fatalf("unexpected output: %x", got)
}
}
func TestEvaluatorPadAndTruncateShapeBytes(t *testing.T) {
items := []*UDPItem{
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "pad",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{0xAA, 0xBB}}},
{Value: &ExprArg_U64{U64: 5}},
{Value: &ExprArg_Bytes{Bytes: []byte{0xCC, 0xDD}}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "truncate",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
{Value: &ExprArg_U64{U64: 2}},
},
},
},
},
},
},
},
}
got, err := evaluateUDPItems(items)
if err != nil {
t.Fatal(err)
}
want := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xCC, 0x01, 0x02}
if !bytes.Equal(got, want) {
t.Fatalf("unexpected output: %x", got)
}
}
func TestMeasureUDPItemsSupportsPadAndTruncate(t *testing.T) {
items := []*UDPItem{
{
Expr: &Expr{
Op: "pad",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{0xAA}}},
{Value: &ExprArg_U64{U64: 4}},
{Value: &ExprArg_Bytes{Bytes: []byte{0x00}}},
},
},
},
{
Expr: &Expr{
Op: "truncate",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
{Value: &ExprArg_U64{U64: 3}},
},
},
},
}
got, err := measureUDPItems(items)
if err != nil {
t.Fatal(err)
}
if got != 7 {
t.Fatalf("unexpected size: %d", got)
}
}
func TestEvaluatorArithmeticAndBitwiseProduceExpectedBytes(t *testing.T) {
items := []*UDPItem{
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "add",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 1}},
{Value: &ExprArg_U64{U64: 2}},
},
},
},
},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "sub",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 10}},
{Value: &ExprArg_U64{U64: 3}},
},
},
},
},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "and",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 0xF0F0}},
{Value: &ExprArg_U64{U64: 0x0FF0}},
},
},
},
},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "or",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "shl",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 1}},
{Value: &ExprArg_U64{U64: 8}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "shr",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 0x80}},
{Value: &ExprArg_U64{U64: 7}},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
}
got, err := evaluateUDPItems(items)
if err != nil {
t.Fatal(err)
}
want := []byte{
0x00, 0x03,
0x00, 0x07,
0x00, 0xF0,
0x01, 0x01,
}
if !bytes.Equal(got, want) {
t.Fatalf("unexpected output: %x", got)
}
}
func TestEvaluatorRejectsInvalidShapingAndArithmetic(t *testing.T) {
tests := []struct {
name string
items []*UDPItem
match string
}{
{
name: "pad with empty fill",
items: []*UDPItem{
{
Expr: &Expr{
Op: "pad",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{0xAA}}},
{Value: &ExprArg_U64{U64: 4}},
{Value: &ExprArg_Bytes{Bytes: []byte{}}},
},
},
},
},
match: "pad fill",
},
{
name: "truncate beyond source",
items: []*UDPItem{
{
Expr: &Expr{
Op: "truncate",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2}}},
{Value: &ExprArg_U64{U64: 3}},
},
},
},
},
match: "truncate",
},
{
name: "sub underflow",
items: []*UDPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "sub",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 1}},
{Value: &ExprArg_U64{U64: 2}},
},
},
},
},
},
},
},
},
match: "underflow",
},
{
name: "shift too large",
items: []*UDPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "shl",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 1}},
{Value: &ExprArg_U64{U64: 64}},
},
},
},
},
},
},
},
},
match: "shift",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := evaluateUDPItems(tt.items)
if err == nil {
t.Fatal("expected evaluator error")
}
if !bytes.Contains([]byte(err.Error()), []byte(tt.match)) {
t.Fatalf("unexpected error: %v", err)
}
})
}
}
@@ -0,0 +1,304 @@
package custom
import (
"bytes"
"encoding/binary"
"io"
"net"
"strings"
"testing"
"time"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
items := []*UDPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "nope"}},
},
},
},
}
_, err := evaluateUDPItemsWithContext(items, newEvalContext())
if err == nil || !strings.Contains(err.Error(), "unknown metadata") {
t.Fatalf("expected unknown metadata error, got %v", err)
}
}
func TestMetadataAliasesExposeSrcAndDstNames(t *testing.T) {
ctx := newEvalContextWithAddrs(
&net.UDPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 3478},
&net.UDPAddr{IP: net.IPv4(203, 0, 113, 9), Port: 54321},
)
items := []*UDPItem{
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "src_port_u16"}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be32",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "src_ip4_u32"}},
},
},
},
},
},
},
},
}
got, err := evaluateUDPItemsWithContext(items, ctx)
if err != nil {
t.Fatal(err)
}
want := []byte{0xD4, 0x31, 203, 0, 113, 9}
if !bytes.Equal(got, want) {
t.Fatalf("unexpected alias output: got=%x want=%x", got, want)
}
}
func TestMetadataAliasesExposeDstNames(t *testing.T) {
ctx := newEvalContextWithAddrs(
&net.UDPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 3478},
&net.UDPAddr{IP: net.IPv4(203, 0, 113, 9), Port: 54321},
)
items := []*UDPItem{
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "dst_port_u16"}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be32",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "dst_ip4_u32"}},
},
},
},
},
},
},
},
}
got, err := evaluateUDPItemsWithContext(items, ctx)
if err != nil {
t.Fatal(err)
}
want := []byte{0x0D, 0x96, 10, 0, 0, 1}
if !bytes.Equal(got, want) {
t.Fatalf("unexpected alias output: got=%x want=%x", got, want)
}
}
func TestMetadataUDPWriteUsesRemotePort(t *testing.T) {
cfg := &UDPConfig{
Client: []*UDPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
},
},
},
},
}
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg}).WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
payload := []byte("meta")
if _, err := client.WriteTo(payload, serverRaw.LocalAddr()); err != nil {
t.Fatal(err)
}
wire := make([]byte, 64)
_ = serverRaw.SetDeadline(time.Now().Add(time.Second))
n, _, err := serverRaw.ReadFrom(wire)
if err != nil {
t.Fatal(err)
}
if n != len(payload)+2 {
t.Fatalf("unexpected wire size: %d", n)
}
wantPort := uint16(serverRaw.LocalAddr().(*net.UDPAddr).Port)
if got := binary.BigEndian.Uint16(wire[:2]); got != wantPort {
t.Fatalf("unexpected encoded port: got=%d want=%d", got, wantPort)
}
if !bytes.Equal(wire[2:n], payload) {
t.Fatalf("unexpected payload: %q", wire[2:n])
}
}
func TestMetadataTCPHandshakeUsesEndpointPorts(t *testing.T) {
clientCfg := &TCPConfig{
Clients: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
},
},
},
},
},
},
Servers: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "local_port"}},
},
},
},
},
},
},
}
serverCfg := &TCPConfig{
Clients: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "local_port"}},
},
},
},
},
},
},
Servers: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
},
},
},
},
},
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
serverRawCh := make(chan net.Conn, 1)
errCh := make(chan error, 1)
go func() {
conn, err := listener.Accept()
if err != nil {
errCh <- err
return
}
serverRawCh <- conn
}()
clientRaw, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
var serverRaw net.Conn
select {
case serverRaw = <-serverRawCh:
case err := <-errCh:
t.Fatal(err)
case <-time.After(2 * time.Second):
t.Fatal("accept timeout")
}
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := serverCfg.WrapConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
writeErr := make(chan error, 1)
go func() {
_, err := client.Write([]byte("meta"))
writeErr <- err
}()
buf := make([]byte, 4)
if _, err := io.ReadFull(server, buf); err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf, []byte("meta")) {
t.Fatalf("unexpected payload: %q", buf)
}
if err := <-writeErr; err != nil {
t.Fatal(err)
}
}
@@ -0,0 +1,57 @@
package custom
import (
"sync"
"time"
)
type stateEntry struct {
vars map[string][]byte
expiresAt time.Time
}
type stateStore struct {
mu sync.Mutex
ttl time.Duration
entries map[string]stateEntry
}
func newStateStore(ttl time.Duration) *stateStore {
return &stateStore{
ttl: ttl,
entries: make(map[string]stateEntry),
}
}
func (s *stateStore) get(key string) (map[string][]byte, bool) {
s.mu.Lock()
defer s.mu.Unlock()
entry, ok := s.entries[key]
if !ok {
return nil, false
}
if !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) {
delete(s.entries, key)
return nil, false
}
return cloneVars(entry.vars), true
}
func (s *stateStore) set(key string, vars map[string][]byte) {
s.mu.Lock()
defer s.mu.Unlock()
s.entries[key] = stateEntry{
vars: cloneVars(vars),
expiresAt: time.Now().Add(s.ttl),
}
}
func cloneVars(vars map[string][]byte) map[string][]byte {
cloned := make(map[string][]byte, len(vars))
for key, value := range vars {
cloned[key] = append([]byte(nil), value...)
}
return cloned
}
@@ -0,0 +1,105 @@
package custom
import (
"bytes"
"net"
"testing"
"time"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) {
t.Helper()
go func() {
_, err := from.WriteTo(msg, to.LocalAddr())
if err != nil {
t.Error(err)
}
}()
buf := make([]byte, 1024)
n, _, err := to.ReadFrom(buf)
if err != nil {
t.Fatal(err)
}
if n != len(msg) {
t.Fatalf("unexpected size: %d", n)
}
if !bytes.Equal(buf[:n], msg) {
t.Fatalf("unexpected payload: %q", buf[:n])
}
}
func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
cfg := &UDPConfig{
Client: []*UDPItem{
{
Rand: 2,
RandMin: 0x2A,
RandMax: 0x2A,
Save: "txid",
},
},
Server: []*UDPItem{
{
Var: "txid",
},
},
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
_ = server.SetDeadline(time.Now().Add(2 * time.Second))
mustSendRecvUDP(t, client, server, []byte("client->server"))
mustSendRecvUDP(t, server, client, []byte("server->client"))
}
func TestStateStoreIsolatesKeys(t *testing.T) {
store := newStateStore(5 * time.Second)
store.set("a", map[string][]byte{"txid": {0x01}})
store.set("b", map[string][]byte{"txid": {0x02}})
varsA, ok := store.get("a")
if !ok || len(varsA["txid"]) != 1 || varsA["txid"][0] != 0x01 {
t.Fatalf("unexpected vars for key a: %v", varsA)
}
varsB, ok := store.get("b")
if !ok || len(varsB["txid"]) != 1 || varsB["txid"][0] != 0x02 {
t.Fatalf("unexpected vars for key b: %v", varsB)
}
}
func TestStateStoreExpiresEntries(t *testing.T) {
store := newStateStore(10 * time.Millisecond)
store.set("a", map[string][]byte{"txid": {0x01}})
time.Sleep(20 * time.Millisecond)
if _, ok := store.get("a"); ok {
t.Fatal("expected expired state entry to be removed")
}
}
@@ -14,6 +14,7 @@ import (
type tcpCustomClient struct {
clients []*TCPSequence
servers []*TCPSequence
state *stateStore
}
type tcpCustomClientConn struct {
@@ -31,6 +32,7 @@ func NewConnClientTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
header: &tcpCustomClient{
clients: c.Clients,
servers: c.Servers,
state: newStateStore(5 * time.Second),
},
}
@@ -63,16 +65,20 @@ func (c *tcpCustomClientConn) Read(p []byte) (n int, err error) {
func (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {
c.once.Do(func() {
ctx := newEvalContextWithAddrs(c.LocalAddr(), c.RemoteAddr())
if vars, ok := c.header.state.get(tcpStateKey(c.LocalAddr(), c.RemoteAddr())); ok {
ctx.vars = cloneVars(vars)
}
i := 0
j := 0
for i = range c.header.clients {
if !writeSequence(c.Conn, c.header.clients[i]) {
if !writeSequenceWithContext(c.Conn, c.header.clients[i], ctx) {
c.wg.Done()
return
}
if j < len(c.header.servers) {
if !readSequence(c.Conn, c.header.servers[j]) {
if !readSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
c.wg.Done()
return
}
@@ -81,13 +87,14 @@ func (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {
}
for j < len(c.header.servers) {
if !readSequence(c.Conn, c.header.servers[j]) {
if !readSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
c.wg.Done()
return
}
j++
}
c.header.state.set(tcpStateKey(c.LocalAddr(), c.RemoteAddr()), ctx.vars)
c.auth = true
c.wg.Done()
})
@@ -105,6 +112,7 @@ type tcpCustomServer struct {
clients []*TCPSequence
servers []*TCPSequence
errors []*TCPSequence
state *stateStore
}
type tcpCustomServerConn struct {
@@ -123,6 +131,7 @@ func NewConnServerTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
clients: c.Clients,
servers: c.Servers,
errors: c.Errors,
state: newStateStore(5 * time.Second),
},
}
@@ -145,19 +154,23 @@ func (c *tcpCustomServerConn) Splice() bool {
func (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {
c.once.Do(func() {
ctx := newEvalContextWithAddrs(c.LocalAddr(), c.RemoteAddr())
if vars, ok := c.header.state.get(tcpStateKey(c.LocalAddr(), c.RemoteAddr())); ok {
ctx.vars = cloneVars(vars)
}
i := 0
j := 0
for i = range c.header.clients {
if !readSequence(c.Conn, c.header.clients[i]) {
if !readSequenceWithContext(c.Conn, c.header.clients[i], ctx) {
if i < len(c.header.errors) {
writeSequence(c.Conn, c.header.errors[i])
writeSequenceWithContext(c.Conn, c.header.errors[i], ctx)
}
c.wg.Done()
return
}
if j < len(c.header.servers) {
if !writeSequence(c.Conn, c.header.servers[j]) {
if !writeSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
c.wg.Done()
return
}
@@ -166,13 +179,14 @@ func (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {
}
for j < len(c.header.servers) {
if !writeSequence(c.Conn, c.header.servers[j]) {
if !writeSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
c.wg.Done()
return
}
j++
}
c.header.state.set(tcpStateKey(c.LocalAddr(), c.RemoteAddr()), ctx.vars)
c.auth = true
c.wg.Done()
})
@@ -197,24 +211,56 @@ func (c *tcpCustomServerConn) Write(p []byte) (n int, err error) {
}
func readSequence(r io.Reader, sequence *TCPSequence) bool {
return readSequenceWithContext(r, sequence, newEvalContext())
}
func readSequenceWithContext(r io.Reader, sequence *TCPSequence, ctx *evalContext) bool {
for _, item := range sequence.Sequence {
length := max(int(item.Rand), len(item.Packet))
length, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeMapFromEvalContext(ctx))
if err != nil {
return false
}
buf := make([]byte, length)
n, err := io.ReadFull(r, buf)
if err != nil {
return false
}
if item.Rand > 0 && n != length {
if n != length {
return false
}
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, buf[:n]) {
return false
switch {
case item.Rand > 0:
case len(item.Packet) > 0:
if !bytes.Equal(item.Packet, buf[:n]) {
return false
}
case item.Var != "":
saved, ok := ctx.vars[item.Var]
if !ok || !bytes.Equal(saved, buf[:n]) {
return false
}
case item.Expr != nil:
evaluated, err := evaluateExpr(item.Expr, ctx)
if err != nil {
return false
}
expected, err := evaluated.asBytes()
if err != nil || !bytes.Equal(expected, buf[:n]) {
return false
}
}
if item.Save != "" {
ctx.vars[item.Save] = append([]byte(nil), buf[:n]...)
}
}
return true
}
func writeSequence(w io.Writer, sequence *TCPSequence) bool {
return writeSequenceWithContext(w, sequence, newEvalContext())
}
func writeSequenceWithContext(w io.Writer, sequence *TCPSequence, ctx *evalContext) bool {
var merged []byte
for _, item := range sequence.Sequence {
if item.DelayMax > 0 {
@@ -227,13 +273,11 @@ func writeSequence(w io.Writer, sequence *TCPSequence) bool {
}
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
}
if item.Rand > 0 {
buf := make([]byte, item.Rand)
crypto.RandBytesBetween(buf, byte(item.RandMin), byte(item.RandMax))
merged = append(merged, buf...)
} else {
merged = append(merged, item.Packet...)
evaluated, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
if err != nil {
return false
}
merged = append(merged, evaluated...)
}
if len(merged) > 0 {
_, err := w.Write(merged)
@@ -244,3 +288,15 @@ func writeSequence(w io.Writer, sequence *TCPSequence) bool {
}
return true
}
func tcpStateKey(local, remote net.Addr) string {
localKey := ""
if local != nil {
localKey = local.String()
}
remoteKey := ""
if remote != nil {
remoteKey = remote.String()
}
return localKey + "|" + remoteKey
}
@@ -0,0 +1,150 @@
package custom
import (
"io"
"net"
"strings"
"testing"
"time"
)
func TestDSLTCPHandshakeReusesCapturedValue(t *testing.T) {
cfg := &TCPConfig{
Clients: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Rand: 2,
RandMin: 0x2A,
RandMax: 0x2A,
Save: "txid",
},
},
},
},
Servers: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Var: "txid",
},
},
},
},
}
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
client, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := cfg.WrapConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
writeErr := make(chan error, 1)
go func() {
_, err := client.Write([]byte("payload"))
writeErr <- err
}()
buf := make([]byte, len("payload"))
if _, err := io.ReadFull(server, buf); err != nil {
t.Fatal(err)
}
if string(buf) != "payload" {
t.Fatalf("unexpected payload: %q", buf)
}
if err := <-writeErr; err != nil {
t.Fatal(err)
}
}
func TestDSLTCPClientRejectsMismatchedResponseSequence(t *testing.T) {
clientCfg := &TCPConfig{
Clients: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Rand: 2,
RandMin: 0x2A,
RandMax: 0x2A,
Save: "txid",
},
},
},
},
Servers: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Var: "txid",
},
},
},
},
}
serverCfg := &TCPConfig{
Clients: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Rand: 2,
Save: "txid",
},
},
},
},
Servers: []*TCPSequence{
{
Sequence: []*TCPItem{
{
Packet: []byte{0x01, 0x02},
},
},
},
},
}
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := serverCfg.WrapConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
writeErr := make(chan error, 1)
go func() {
_, err := client.Write([]byte("payload"))
writeErr <- err
}()
buf := make([]byte, len("payload"))
_, readErr := server.Read(buf)
if err := <-writeErr; err == nil || !strings.Contains(err.Error(), "header auth failed") {
t.Fatalf("expected client auth failure, got %v", err)
}
if readErr == nil {
t.Fatal("expected server read to fail")
}
if ne, ok := readErr.(net.Error); !ok || !ne.Timeout() {
t.Fatalf("expected server timeout after client auth failure, got %v", readErr)
}
}
+394 -75
View File
@@ -3,50 +3,46 @@ package custom
import (
"bytes"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
)
const udpStandaloneBufferSize = 4096
type udpCustomClient struct {
client []*UDPItem
server []*UDPItem
merged []byte
read int
addr net.Addr
state *stateStore
vars map[string][]byte
}
func (h *udpCustomClient) Serialize(b []byte) {
index := 0
for _, item := range h.client {
if item.Rand > 0 {
crypto.RandBytesBetween(h.merged[index:index+int(item.Rand)], byte(item.RandMin), byte(item.RandMax))
index += int(item.Rand)
} else {
index += len(item.Packet)
}
evaluated, err := evaluateUDPItems(h.client)
if err != nil || len(evaluated) != len(h.merged) {
copy(b, h.merged)
return
}
copy(b, h.merged)
copy(b, evaluated)
}
func (h *udpCustomClient) Match(b []byte) bool {
if len(b) < len(h.merged) {
return false
var initial map[string][]byte
if h.state != nil {
initial, _ = h.state.get(udpStateKey(h.addr))
}
data := b
match := true
for _, item := range h.server {
length := max(int(item.Rand), len(item.Packet))
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
match = false
break
vars, ok := matchUDPItems(h.server, b, h.read, initial)
if ok {
h.vars = vars
if h.state != nil {
h.state.set(udpStateKey(h.addr), vars)
}
data = data[length:]
}
return match
return ok
}
type udpCustomClientConn struct {
@@ -60,18 +56,19 @@ func NewConnClientUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error)
header: &udpCustomClient{
client: c.Client,
server: c.Server,
state: newStateStore(5 * time.Second),
vars: make(map[string][]byte),
},
}
index := 0
for _, item := range conn.header.client {
if item.Rand > 0 {
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
index += int(item.Rand)
} else {
conn.header.merged = append(conn.header.merged, item.Packet...)
index += len(item.Packet)
}
clientSavedSizes := collectSavedUDPSizes(conn.header.client)
size, err := measureUDPItems(conn.header.client)
if err != nil {
return nil, err
}
conn.header.merged = make([]byte, size)
conn.header.read, err = measureUDPItemsWithFallback(conn.header.server, clientSavedSizes)
if err != nil {
return nil, err
}
return conn, nil
@@ -86,54 +83,69 @@ func (c *udpCustomClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err erro
return 0, addr, errors.New("header mismatch")
}
return len(p) - len(c.header.merged), addr, nil
return len(p) - c.header.read, addr, nil
}
func (c *udpCustomClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.header.Serialize(p)
var localAddr net.Addr
if c.PacketConn != nil {
localAddr = c.PacketConn.LocalAddr()
}
ctx := newEvalContextWithAddrs(localAddr, addr)
if vars, ok := c.header.state.get(udpStateKey(addr)); ok {
ctx.vars = cloneVars(vars)
} else if len(c.header.vars) > 0 {
ctx.vars = cloneVars(c.header.vars)
}
evaluated, err := evaluateUDPItemsWithContext(c.header.client, ctx)
if err != nil {
return 0, err
}
if len(evaluated) != len(c.header.merged) {
return 0, errors.New("header size mismatch")
}
c.header.state.set(udpStateKey(addr), ctx.vars)
copy(p, evaluated)
return len(p), nil
}
func (c *udpCustomClientConn) SetReadAddr(addr net.Addr) {
c.header.addr = addr
}
type udpCustomServer struct {
client []*UDPItem
server []*UDPItem
merged []byte
read int
addr net.Addr
state *stateStore
vars map[string][]byte
}
func (h *udpCustomServer) Serialize(b []byte) {
index := 0
for _, item := range h.server {
if item.Rand > 0 {
crypto.RandBytesBetween(h.merged[index:index+int(item.Rand)], byte(item.RandMin), byte(item.RandMax))
index += int(item.Rand)
} else {
index += len(item.Packet)
}
evaluated, err := evaluateUDPItems(h.server)
if err != nil || len(evaluated) != len(h.merged) {
copy(b, h.merged)
return
}
copy(b, h.merged)
copy(b, evaluated)
}
func (h *udpCustomServer) Match(b []byte) bool {
if len(b) < len(h.merged) {
return false
var initial map[string][]byte
if h.state != nil {
initial, _ = h.state.get(udpStateKey(h.addr))
}
data := b
match := true
for _, item := range h.client {
length := max(int(item.Rand), len(item.Packet))
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
match = false
break
vars, ok := matchUDPItems(h.client, b, h.read, initial)
if ok {
h.vars = vars
if h.state != nil {
h.state.set(udpStateKey(h.addr), vars)
}
data = data[length:]
}
return match
return ok
}
type udpCustomServerConn struct {
@@ -147,18 +159,19 @@ func NewConnServerUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error)
header: &udpCustomServer{
client: c.Client,
server: c.Server,
state: newStateStore(5 * time.Second),
vars: make(map[string][]byte),
},
}
index := 0
for _, item := range conn.header.server {
if item.Rand > 0 {
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
index += int(item.Rand)
} else {
conn.header.merged = append(conn.header.merged, item.Packet...)
index += len(item.Packet)
}
clientSavedSizes := collectSavedUDPSizes(conn.header.client)
size, err := measureUDPItemsWithFallback(conn.header.server, clientSavedSizes)
if err != nil {
return nil, err
}
conn.header.merged = make([]byte, size)
conn.header.read, err = measureUDPItems(conn.header.client)
if err != nil {
return nil, err
}
return conn, nil
@@ -173,11 +186,317 @@ func (c *udpCustomServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err erro
return 0, addr, errors.New("header mismatch")
}
return len(p) - len(c.header.merged), addr, nil
return len(p) - c.header.read, addr, nil
}
func (c *udpCustomServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.header.Serialize(p)
var localAddr net.Addr
if c.PacketConn != nil {
localAddr = c.PacketConn.LocalAddr()
}
ctx := newEvalContextWithAddrs(localAddr, addr)
if vars, ok := c.header.state.get(udpStateKey(addr)); ok {
ctx.vars = cloneVars(vars)
} else if len(c.header.vars) > 0 {
ctx.vars = cloneVars(c.header.vars)
}
evaluated, err := evaluateUDPItemsWithContext(c.header.server, ctx)
if err != nil {
return 0, err
}
if len(evaluated) != len(c.header.merged) {
return 0, errors.New("header size mismatch")
}
c.header.state.set(udpStateKey(addr), ctx.vars)
copy(p, evaluated)
return len(p), nil
}
func (c *udpCustomServerConn) SetReadAddr(addr net.Addr) {
c.header.addr = addr
}
func matchUDPItems(items []*UDPItem, data []byte, totalSize int, initial map[string][]byte) (map[string][]byte, bool) {
if len(data) < totalSize {
return nil, false
}
ctx := newEvalContext()
ctx.vars = cloneVars(initial)
offset := 0
for _, item := range items {
length, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeMapFromEvalContext(ctx))
if err != nil {
return nil, false
}
if len(data[offset:]) < length {
return nil, false
}
segment := append([]byte(nil), data[offset:offset+length]...)
switch {
case item.Rand > 0:
case len(item.Packet) > 0:
if !bytes.Equal(item.Packet, segment) {
return nil, false
}
case item.Var != "":
saved, ok := ctx.vars[item.Var]
if !ok || !bytes.Equal(saved, segment) {
return nil, false
}
case item.Expr != nil:
evaluated, err := evaluateExpr(item.Expr, ctx)
if err != nil {
return nil, false
}
expected, err := evaluated.asBytes()
if err != nil || !bytes.Equal(expected, segment) {
return nil, false
}
}
if item.Save != "" {
ctx.vars[item.Save] = segment
}
offset += length
}
return ctx.vars, true
}
func udpStateKey(addr net.Addr) string {
if addr == nil {
return ""
}
return addr.String()
}
type udpCustomStandaloneClientConn struct {
net.PacketConn
client []*UDPItem
server []*UDPItem
state *stateStore
read int
mu sync.Mutex
once sync.Once
queue chan udpStandalonePacket
wait map[string]*udpStandaloneWaiter
}
type udpStandalonePacket struct {
data []byte
addr net.Addr
err error
}
type udpStandaloneWaiter struct {
vars map[string][]byte
done chan error
}
func NewConnClientUDPStandalone(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
clientSavedSizes := collectSavedUDPSizes(c.Client)
read, err := measureUDPItemsWithFallback(c.Server, clientSavedSizes)
if err != nil {
return nil, err
}
return &udpCustomStandaloneClientConn{
PacketConn: raw,
client: c.Client,
server: c.Server,
state: newStateStore(5 * time.Second),
read: read,
queue: make(chan udpStandalonePacket, 16),
wait: make(map[string]*udpStandaloneWaiter),
}, nil
}
func (c *udpCustomStandaloneClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
c.ensureReader()
packet, ok := <-c.queue
if !ok {
return 0, nil, net.ErrClosed
}
if packet.err != nil {
return 0, packet.addr, packet.err
}
if len(packet.data) > len(p) {
copy(p, packet.data[:len(p)])
return len(p), packet.addr, nil
}
copy(p, packet.data)
return len(packet.data), packet.addr, nil
}
func (c *udpCustomStandaloneClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.ensureReader()
key := udpStateKey(addr)
if _, ok := c.state.get(key); !ok {
var localAddr net.Addr
if c.PacketConn != nil {
localAddr = c.PacketConn.LocalAddr()
}
ctx := newEvalContextWithAddrs(localAddr, addr)
request, err := evaluateUDPItemsWithContext(c.client, ctx)
if err != nil {
return 0, err
}
waiter := c.registerWaiter(key, ctx.vars)
if _, err := c.PacketConn.WriteTo(request, addr); err != nil {
c.unregisterWaiter(key, waiter)
return 0, err
}
if err := <-waiter.done; err != nil {
return 0, err
}
}
return c.PacketConn.WriteTo(p, addr)
}
func (c *udpCustomStandaloneClientConn) ensureReader() {
c.once.Do(func() {
go c.readerLoop(c.queue)
})
}
func (c *udpCustomStandaloneClientConn) registerWaiter(key string, vars map[string][]byte) *udpStandaloneWaiter {
waiter := &udpStandaloneWaiter{
vars: cloneVars(vars),
done: make(chan error, 1),
}
c.mu.Lock()
c.wait[key] = waiter
c.mu.Unlock()
return waiter
}
func (c *udpCustomStandaloneClientConn) unregisterWaiter(key string, waiter *udpStandaloneWaiter) {
c.mu.Lock()
if c.wait[key] == waiter {
delete(c.wait, key)
}
c.mu.Unlock()
}
func (c *udpCustomStandaloneClientConn) readerLoop(queue chan udpStandalonePacket) {
buf := make([]byte, udpStandaloneBufferSize)
for {
n, addr, err := c.PacketConn.ReadFrom(buf)
if err != nil {
c.failWaiters(err)
queue <- udpStandalonePacket{addr: addr, err: err}
close(queue)
return
}
data := append([]byte(nil), buf[:n]...)
if c.tryCompleteHandshake(addr, data) {
continue
}
queue <- udpStandalonePacket{data: data, addr: addr}
}
}
func (c *udpCustomStandaloneClientConn) tryCompleteHandshake(addr net.Addr, data []byte) bool {
key := udpStateKey(addr)
c.mu.Lock()
waiter, ok := c.wait[key]
c.mu.Unlock()
if !ok || len(data) != c.read {
return false
}
vars, matched := matchUDPItems(c.server, data, c.read, waiter.vars)
if !matched {
return false
}
c.state.set(key, vars)
c.mu.Lock()
if c.wait[key] == waiter {
delete(c.wait, key)
}
c.mu.Unlock()
waiter.done <- nil
return true
}
func (c *udpCustomStandaloneClientConn) failWaiters(err error) {
c.mu.Lock()
waiters := c.wait
c.wait = make(map[string]*udpStandaloneWaiter)
c.mu.Unlock()
for _, waiter := range waiters {
waiter.done <- err
}
}
type udpCustomStandaloneServerConn struct {
net.PacketConn
client []*UDPItem
server []*UDPItem
state *stateStore
read int
}
func NewConnServerUDPStandalone(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
read, err := measureUDPItems(c.Client)
if err != nil {
return nil, err
}
return &udpCustomStandaloneServerConn{
PacketConn: raw,
client: c.Client,
server: c.Server,
state: newStateStore(5 * time.Second),
read: read,
}, nil
}
func (c *udpCustomStandaloneServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buf := p
copyBack := false
if len(buf) < udpStandaloneBufferSize {
buf = make([]byte, udpStandaloneBufferSize)
copyBack = true
}
for {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil {
return 0, addr, err
}
if n == c.read {
vars, ok := matchUDPItems(c.client, buf[:n], c.read, nil)
if ok {
var localAddr net.Addr
if c.PacketConn != nil {
localAddr = c.PacketConn.LocalAddr()
}
ctx := newEvalContextWithAddrs(localAddr, addr)
ctx.vars = cloneVars(vars)
response, err := evaluateUDPItemsWithContext(c.server, ctx)
if err != nil {
return 0, addr, err
}
if _, err := c.PacketConn.WriteTo(response, addr); err != nil {
return 0, addr, err
}
c.state.set(udpStateKey(addr), ctx.vars)
continue
}
}
if copyBack {
copy(p, buf[:n])
}
return n, addr, nil
}
}
func (c *udpCustomStandaloneServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
return c.PacketConn.WriteTo(p, addr)
}
@@ -0,0 +1,188 @@
package custom
import (
"bytes"
"net"
"testing"
)
func TestDSLUDPClientSizeTracksEvaluatedItems(t *testing.T) {
conn, err := NewConnClientUDP(&UDPConfig{
Client: []*UDPItem{
{
Rand: 2,
RandMin: 0x2A,
RandMax: 0x2A,
Save: "txid",
},
{
Var: "txid",
},
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{0xAB}}},
{Value: &ExprArg_Bytes{Bytes: []byte{0xCD}}},
},
},
},
},
}, nil)
if err != nil {
t.Fatal(err)
}
if got := conn.(*udpCustomClientConn).Size(); got != 6 {
t.Fatalf("unexpected header size: got=%d want=6", got)
}
}
func TestDSLUDPServerMatchCapturesSavedValues(t *testing.T) {
conn, err := NewConnServerUDP(&UDPConfig{
Client: []*UDPItem{
{
Rand: 2,
Save: "txid",
},
{
Var: "txid",
},
},
}, nil)
if err != nil {
t.Fatal(err)
}
server := conn.(*udpCustomServerConn)
if !server.header.Match([]byte{0x01, 0x02, 0x01, 0x02}) {
t.Fatal("expected packet to match")
}
if got := string(server.header.vars["txid"]); got != string([]byte{0x01, 0x02}) {
t.Fatalf("unexpected saved txid: %x", server.header.vars["txid"])
}
}
func TestDSLUDPServerRejectsMalformedVarReference(t *testing.T) {
conn, err := NewConnServerUDP(&UDPConfig{
Client: []*UDPItem{
{
Rand: 2,
Save: "txid",
},
{
Var: "txid",
},
},
}, nil)
if err != nil {
t.Fatal(err)
}
server := conn.(*udpCustomServerConn)
if server.header.Match([]byte{0x01, 0x02, 0x03, 0x04}) {
t.Fatal("expected packet mismatch")
}
}
func TestDSLUDPClientWriteSupportsExtendedExprOps(t *testing.T) {
conn, err := NewConnClientUDP(&UDPConfig{
Client: []*UDPItem{
{
Expr: &Expr{
Op: "le16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "add",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 1}},
{Value: &ExprArg_U64{U64: 2}},
},
},
},
},
},
},
},
{
Expr: &Expr{
Op: "pad",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{0xAA}}},
{Value: &ExprArg_U64{U64: 3}},
{Value: &ExprArg_Bytes{Bytes: []byte{0xBB}}},
},
},
},
{
Expr: &Expr{
Op: "truncate",
Args: []*ExprArg{
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
{Value: &ExprArg_U64{U64: 2}},
},
},
},
{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "or",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "shl",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 1}},
{Value: &ExprArg_U64{U64: 8}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "shr",
Args: []*ExprArg{
{Value: &ExprArg_U64{U64: 0x80}},
{Value: &ExprArg_U64{U64: 7}},
},
},
},
},
},
},
},
},
},
},
},
},
}, nil)
if err != nil {
t.Fatal(err)
}
client := conn.(*udpCustomClientConn)
buf := make([]byte, client.Size())
if _, err := client.WriteTo(buf, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 53}); err != nil {
t.Fatal(err)
}
want := []byte{
0x03, 0x00,
0xAA, 0xBB, 0xBB,
0x01, 0x02,
0x01, 0x01,
}
if !bytes.Equal(buf, want) {
t.Fatalf("unexpected encoded header: %x", buf)
}
}
@@ -278,7 +278,9 @@ func runVLESSRealityCase(t *testing.T, bin string, mode trafficMode, payloadSize
},
},
Outbound: []*core.OutboundHandlerConfig{
{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
})},
},
})
@@ -396,7 +398,9 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
},
},
Outbound: []*core.OutboundHandlerConfig{
{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
})},
},
})
@@ -512,7 +516,9 @@ func runVLesseEncCase(t *testing.T, bin string, mode trafficMode, payloadSize in
},
},
Outbound: []*core.OutboundHandlerConfig{
{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
})},
},
})
@@ -610,7 +616,9 @@ func runVLESSXHTTPCase(t *testing.T, bin string, mode trafficMode, payloadSize i
},
},
Outbound: []*core.OutboundHandlerConfig{
{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
})},
},
})
+194
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"io"
"net"
"strings"
"testing"
"time"
@@ -46,6 +47,14 @@ type layerMaskTcp struct {
mask finalmask.Tcpmask
}
type failingWrapMask struct{}
func (failingWrapMask) TCP() {}
func (f failingWrapMask) WrapConnClient(raw net.Conn) (net.Conn, error) { return raw, nil }
func (f failingWrapMask) WrapConnServer(raw net.Conn) (net.Conn, error) {
return nil, io.ErrClosedPipe
}
func TestConnReadWrite(t *testing.T) {
cases := []layerMaskTcp{
{
@@ -121,3 +130,188 @@ func TestConnReadWrite(t *testing.T) {
})
}
}
func TestTCPcustomStaticHandshakeRoundTrip(t *testing.T) {
cfg := &custom.TCPConfig{
Clients: []*custom.TCPSequence{
{
Sequence: []*custom.TCPItem{
{Packet: []byte("cli")},
{Rand: 2, RandMin: 0x10, RandMax: 0x20},
},
},
},
Servers: []*custom.TCPSequence{
{
Sequence: []*custom.TCPItem{
{Packet: []byte("srv")},
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
},
},
},
}
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{cfg})
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer ln.Close()
clientRaw, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := ln.Accept()
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := maskManager.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := maskManager.WrapConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
mustSendRecvTcp(t, client, server, []byte("custom tcp payload"))
mustSendRecvTcp(t, server, client, []byte("custom tcp response"))
}
func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
clientCfg := &custom.TCPConfig{
Clients: []*custom.TCPSequence{
{
Sequence: []*custom.TCPItem{
{Packet: []byte{0x01}},
},
},
},
Servers: []*custom.TCPSequence{
{
Sequence: []*custom.TCPItem{
{Packet: []byte{0x02}},
},
},
},
}
serverCfg := &custom.TCPConfig{
Clients: []*custom.TCPSequence{
{
Sequence: []*custom.TCPItem{
{Packet: []byte{0x01}},
},
},
},
Servers: []*custom.TCPSequence{
{
Sequence: []*custom.TCPItem{
{Packet: []byte{0x03}},
},
},
},
}
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := serverCfg.WrapConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
writeErr := make(chan error, 1)
go func() {
_, err := client.Write([]byte("boom"))
writeErr <- err
}()
buf := make([]byte, 4)
_, readErr := server.Read(buf)
if err := <-writeErr; err == nil || !strings.Contains(err.Error(), "header auth failed") {
t.Fatalf("expected client header auth failure, got %v", err)
}
if readErr == nil {
t.Fatal("expected server read to fail")
}
if ne, ok := readErr.(net.Error); !ok || !ne.Timeout() {
t.Fatalf("expected server timeout after client auth failure, got %v", readErr)
}
}
func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) {
clientManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}})
serverManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}})
rawLn, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer rawLn.Close()
ln, err := serverManager.WrapListener(rawLn)
if err != nil {
t.Fatal(err)
}
accepted := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, err := ln.Accept()
accepted <- struct {
conn net.Conn
err error
}{conn: conn, err: err}
}()
clientRaw, err := net.Dial("tcp", rawLn.Addr().String())
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
client, err := clientManager.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
_ = client.SetDeadline(time.Now().Add(time.Second))
writeErr := make(chan error, 1)
go func() {
_, err := client.Write([]byte("payload"))
writeErr <- err
}()
result := <-accepted
if result.err == nil {
if result.conn != nil {
result.conn.Close()
}
t.Fatal("expected wrapped listener accept to fail")
}
if result.conn != nil {
result.conn.Close()
t.Fatalf("expected no raw conn on wrapped listener failure, got %T", result.conn)
}
<-writeErr
}
+485
View File
@@ -2,12 +2,16 @@ package finalmask_test
import (
"bytes"
"context"
"encoding/binary"
"io"
"net"
"sync/atomic"
"testing"
"time"
singM "github.com/sagernet/sing/common/metadata"
singN "github.com/sagernet/sing/common/network"
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
@@ -73,6 +77,194 @@ func (c *countingConn) Written() int64 {
return c.written.Load()
}
type recordedPacketWrite struct {
payload []byte
addr net.Addr
}
type scriptedPacketConn struct {
local *net.UDPAddr
writes chan recordedPacketWrite
reads chan recordedPacketWrite
closed atomic.Bool
deadline atomic.Int64
}
func newScriptedPacketConn() *scriptedPacketConn {
return &scriptedPacketConn{
local: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 40000},
writes: make(chan recordedPacketWrite, 8),
reads: make(chan recordedPacketWrite, 8),
}
}
func (c *scriptedPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
item, ok := <-c.reads
if !ok {
return 0, nil, io.EOF
}
copy(p, item.payload)
return len(item.payload), item.addr, nil
}
func (c *scriptedPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.writes <- recordedPacketWrite{
payload: append([]byte(nil), p...),
addr: addr,
}
return len(p), nil
}
func (c *scriptedPacketConn) Close() error {
if c.closed.CompareAndSwap(false, true) {
close(c.reads)
}
return nil
}
func (c *scriptedPacketConn) LocalAddr() net.Addr { return c.local }
func (c *scriptedPacketConn) SetDeadline(t time.Time) error {
c.deadline.Store(t.UnixNano())
return nil
}
func (c *scriptedPacketConn) SetReadDeadline(t time.Time) error {
c.deadline.Store(t.UnixNano())
return nil
}
func (c *scriptedPacketConn) SetWriteDeadline(t time.Time) error {
c.deadline.Store(t.UnixNano())
return nil
}
type captureUDPHandler struct {
gotMetadata chan singM.Metadata
}
func (h *captureUDPHandler) NewConnection(_ context.Context, _ net.Conn, _ singM.Metadata) error {
return nil
}
func (h *captureUDPHandler) NewPacketConnection(_ context.Context, _ singN.PacketConn, metadata singM.Metadata) error {
select {
case h.gotMetadata <- metadata:
default:
}
return nil
}
func (h *captureUDPHandler) NewError(_ context.Context, _ error) {}
func newStandaloneEchoUDPConfig() *custom.UDPConfig {
return &custom.UDPConfig{
Mode: "standalone",
Client: []*custom.UDPItem{
{Packet: []byte{0xAA}},
{Rand: 2, Save: "txid"},
},
Server: []*custom.UDPItem{
{Packet: []byte{0xBB}},
{Var: "txid"},
},
}
}
func newStandaloneStunLikeUDPConfig() *custom.UDPConfig {
return &custom.UDPConfig{
Mode: "standalone",
Client: []*custom.UDPItem{
{Packet: []byte{0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xA4, 0x42}},
{Rand: 12, RandMin: 0x2A, RandMax: 0x2A, Save: "txid"},
},
Server: []*custom.UDPItem{
{Packet: []byte{0x01, 0x01, 0x00, 0x0C, 0x21, 0x12, 0xA4, 0x42}},
{Var: "txid"},
{Packet: []byte{0x00, 0x20, 0x00, 0x08, 0x00, 0x01}},
{Rand: 2, Save: "mapped_port"},
{Rand: 4, Save: "mapped_ip"},
},
}
}
func newStandaloneStunLikeUDPServerConfig() *custom.UDPConfig {
return &custom.UDPConfig{
Mode: "standalone",
Client: []*custom.UDPItem{
{Packet: []byte{0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xA4, 0x42}},
{Rand: 12, RandMin: 0x2A, RandMax: 0x2A, Save: "txid"},
},
Server: []*custom.UDPItem{
{Packet: []byte{0x01, 0x01, 0x00, 0x0C, 0x21, 0x12, 0xA4, 0x42}},
{Var: "txid"},
{Packet: []byte{0x00, 0x20, 0x00, 0x08, 0x00, 0x01}},
{
Expr: &custom.Expr{
Op: "be16",
Args: []*custom.ExprArg{
{
Value: &custom.ExprArg_Expr{
Expr: &custom.Expr{
Op: "xor16",
Args: []*custom.ExprArg{
{Value: &custom.ExprArg_Metadata{Metadata: "src_port_u16"}},
{Value: &custom.ExprArg_U64{U64: 0x2112}},
},
},
},
},
},
},
},
{
Expr: &custom.Expr{
Op: "be32",
Args: []*custom.ExprArg{
{
Value: &custom.ExprArg_Expr{
Expr: &custom.Expr{
Op: "xor32",
Args: []*custom.ExprArg{
{Value: &custom.ExprArg_Metadata{Metadata: "src_ip4_u32"}},
{Value: &custom.ExprArg_U64{U64: 0x2112A442}},
},
},
},
},
},
},
},
},
}
}
func newUDPClientServerPair(t *testing.T, cfg *custom.UDPConfig) (net.PacketConn, net.PacketConn, net.PacketConn, net.PacketConn) {
t.Helper()
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = clientRaw.Close() })
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = serverRaw.Close() })
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
return clientRaw, serverRaw, client, server
}
func TestPacketConnReadWrite(t *testing.T) {
cases := []layerMask{
{
@@ -215,6 +407,299 @@ func TestPacketConnReadWrite(t *testing.T) {
}
}
func TestUDPcustomStaticHeaderWireShape(t *testing.T) {
cfg := &custom.UDPConfig{
Client: []*custom.UDPItem{
{Packet: []byte{0xAA, 0xBB}},
{Rand: 2, RandMin: 0x10, RandMax: 0x20},
},
Server: []*custom.UDPItem{
{Packet: []byte{0xCC}},
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
},
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
payload := []byte("udp-custom-wire")
if _, err := client.WriteTo(payload, serverRaw.LocalAddr()); err != nil {
t.Fatal(err)
}
buf := make([]byte, 1024)
_ = serverRaw.SetDeadline(time.Now().Add(time.Second))
n, _, err := serverRaw.ReadFrom(buf)
if err != nil {
t.Fatal(err)
}
if n != len(payload)+4 {
t.Fatalf("unexpected wire size: got=%d want=%d", n, len(payload)+4)
}
if !bytes.Equal(buf[:2], []byte{0xAA, 0xBB}) {
t.Fatalf("unexpected static header prefix: %x", buf[:2])
}
for i, b := range buf[2:4] {
if b < 0x10 || b > 0x20 {
t.Fatalf("rand byte %d out of range: %x", i, b)
}
}
if !bytes.Equal(buf[4:n], payload) {
t.Fatalf("unexpected payload: %q", buf[4:n])
}
}
func TestUDPcustomServerRejectsMismatchedStaticHeader(t *testing.T) {
cfg := &custom.UDPConfig{
Client: []*custom.UDPItem{
{Packet: []byte{0x01, 0x02}},
},
Server: []*custom.UDPItem{
{Packet: []byte{0x03}},
},
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
_ = server.SetDeadline(time.Now().Add(200 * time.Millisecond))
if _, err := clientRaw.WriteTo([]byte{0x09, 0x09, 'b', 'a', 'd'}, server.LocalAddr()); err != nil {
t.Fatal(err)
}
buf := make([]byte, 128)
n, _, err := server.ReadFrom(buf)
if n != 0 {
t.Fatalf("expected no payload on mismatched header, got %d bytes", n)
}
if err != nil {
t.Fatalf("expected mismatch to be dropped without surfaced error, got %v", err)
}
}
func TestUDPcustomStandaloneClientSendsDetachedHandshakeBeforePayload(t *testing.T) {
_, serverRaw, client, _ := newUDPClientServerPair(t, newStandaloneEchoUDPConfig())
payload := []byte("standalone-payload")
writeErr := make(chan error, 1)
go func() {
_, err := client.WriteTo(payload, serverRaw.LocalAddr())
writeErr <- err
}()
wire := make([]byte, 128)
_ = serverRaw.SetDeadline(time.Now().Add(time.Second))
n, addr, err := serverRaw.ReadFrom(wire)
if err != nil {
t.Fatal(err)
}
if n != 3 {
t.Fatalf("unexpected handshake size: got=%d want=3", n)
}
if !bytes.Equal(wire[:1], []byte{0xAA}) {
t.Fatalf("unexpected handshake prefix: %x", wire[:1])
}
txid := append([]byte(nil), wire[1:n]...)
if _, err := serverRaw.WriteTo(append([]byte{0xBB}, txid...), addr); err != nil {
t.Fatal(err)
}
n, _, err = serverRaw.ReadFrom(wire)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(wire[:n], payload) {
t.Fatalf("unexpected payload after handshake: %q", wire[:n])
}
if err := <-writeErr; err != nil {
t.Fatal(err)
}
}
func TestUDPcustomStandaloneServerConsumesHandshakeAndAutoResponds(t *testing.T) {
clientRaw, _, _, server := newUDPClientServerPair(t, newStandaloneEchoUDPConfig())
_ = clientRaw.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
readPayload := make(chan []byte, 1)
readErr := make(chan error, 1)
go func() {
buf := make([]byte, 128)
n, _, err := server.ReadFrom(buf)
if err != nil {
readErr <- err
return
}
readPayload <- append([]byte(nil), buf[:n]...)
}()
txid := []byte{0x10, 0x20}
if _, err := clientRaw.WriteTo(append([]byte{0xAA}, txid...), server.LocalAddr()); err != nil {
t.Fatal(err)
}
buf := make([]byte, 128)
n, _, err := clientRaw.ReadFrom(buf)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf[:n], append([]byte{0xBB}, txid...)) {
t.Fatalf("unexpected auto-response: %x", buf[:n])
}
payload := []byte("server-side-standalone")
if _, err := clientRaw.WriteTo(payload, server.LocalAddr()); err != nil {
t.Fatal(err)
}
select {
case got := <-readPayload:
if !bytes.Equal(got, payload) {
t.Fatalf("unexpected payload: %q", got)
}
case err := <-readErr:
t.Fatal(err)
case <-time.After(2 * time.Second):
t.Fatal("payload read timeout")
}
}
func TestUDPcustomStandaloneStunLikeExchangeUsesSavedTxidAndSrcMetadata(t *testing.T) {
clientRaw, _, _, server := newUDPClientServerPair(t, newStandaloneStunLikeUDPServerConfig())
_ = clientRaw.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
readPayload := make(chan []byte, 1)
readErr := make(chan error, 1)
go func() {
buf := make([]byte, 64)
n, _, err := server.ReadFrom(buf)
if err != nil {
readErr <- err
return
}
readPayload <- append([]byte(nil), buf[:n]...)
}()
txid := bytes.Repeat([]byte{0x2A}, 12)
request := append([]byte{0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xA4, 0x42}, txid...)
if _, err := clientRaw.WriteTo(request, server.LocalAddr()); err != nil {
t.Fatal(err)
}
buf := make([]byte, 64)
n, _, err := clientRaw.ReadFrom(buf)
if err != nil {
t.Fatal(err)
}
want := make([]byte, 0, 32)
want = append(want, []byte{0x01, 0x01, 0x00, 0x0C, 0x21, 0x12, 0xA4, 0x42}...)
want = append(want, txid...)
want = append(want, []byte{0x00, 0x20, 0x00, 0x08, 0x00, 0x01}...)
clientAddr := clientRaw.LocalAddr().(*net.UDPAddr)
xPort := uint16(clientAddr.Port) ^ 0x2112
xIP := binary.BigEndian.Uint32(clientAddr.IP.To4()) ^ 0x2112A442
want = append(want, byte(xPort>>8), byte(xPort))
want = append(want, byte(xIP>>24), byte(xIP>>16), byte(xIP>>8), byte(xIP))
if !bytes.Equal(buf[:n], want) {
t.Fatalf("unexpected stun-like response: got=%x want=%x", buf[:n], want)
}
payload := []byte("after-standalone-stun")
if _, err := clientRaw.WriteTo(payload, server.LocalAddr()); err != nil {
t.Fatal(err)
}
select {
case got := <-readPayload:
if !bytes.Equal(got, payload) {
t.Fatalf("unexpected payload after stun exchange: %q", got)
}
case err := <-readErr:
t.Fatal(err)
case <-time.After(2 * time.Second):
t.Fatal("payload read timeout")
}
}
func TestUDPcustomStandaloneClientHandshakeSurvivesConcurrentReader(t *testing.T) {
_, serverRaw, clientMask, serverMask := newUDPClientServerPair(t, newStandaloneStunLikeUDPConfig())
go func() {
buf := make([]byte, 2048)
_ = clientMask.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
_, _, _ = clientMask.ReadFrom(buf)
}()
go func() {
buf := make([]byte, 2048)
for {
n, addr, err := serverMask.ReadFrom(buf)
if err != nil {
return
}
if n == len([]byte("dns-payload")) && string(buf[:n]) == "dns-payload" {
return
}
_ = addr
}
}()
writeDone := make(chan error, 1)
go func() {
_, err := clientMask.WriteTo([]byte("dns-payload"), serverRaw.LocalAddr())
writeDone <- err
}()
select {
case err := <-writeDone:
if err != nil {
t.Fatal(err)
}
case <-time.After(2 * time.Second):
t.Fatal("expected handshake to complete even with concurrent reader")
}
}
func TestSudokuBDD(t *testing.T) {
t.Run("GivenSudokuTCPMask_WhenRoundTripWithAsciiPreference_ThenPayloadMatches", func(t *testing.T) {
cfg := &sudoku.Config{
+2 -1
View File
@@ -450,6 +450,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
}
requireDatagram := hyCtx.RequireDatagramFromContext(ctx)
dest.Network = net.Network_UDP
config := streamSettings.ProtocolSettings.(*Config)
initmanager.Do(func() {
@@ -464,8 +465,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
},
}).Start()
})
manager.mutex.Lock()
dest.Network = net.Network_UDP
c, ok := manager.m[dialerConf{Destination: dest, MemoryStreamConfig: streamSettings}]
if !ok {
c = &client{
+3 -3
View File
@@ -175,10 +175,10 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var user *protocol.MemoryUser
var ok bool
if h.validator != nil {
if h.validator != nil && h.validator.GetCount() > 0 {
user = h.validator.Get(auth)
} else if auth == h.config.Auth {
ok = true
} else if h.config.Auth != "" {
ok = auth == h.config.Auth
}
if user != nil || ok {
+15 -10
View File
@@ -5,6 +5,7 @@ import (
"crypto/rand"
"crypto/tls"
"math/big"
"slices"
"time"
utls "github.com/refraction-networking/utls"
@@ -90,18 +91,24 @@ func (c *UConn) HandshakeContextServerName(ctx context.Context) string {
return c.ConnectionState().ServerName
}
// WebsocketHandshake basically calls UConn.Handshake inside it but it will only send
// http/1.1 in its ALPN.
// 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)
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
}
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
// 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
if config.EncryptedClientHelloConfigList != nil {
config.NextProtos = []string{"http/1.1"}
return c.HandshakeContext(ctx)
}
// Iterate over extensions and check for utls.ALPNExtension
@@ -109,12 +116,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 = []string{"http/1.1"}
alpn.AlpnProtocols = ALPN
break
}
}
if !hasALPNExtension { // Append extension if doesn't exists
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}})
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: ALPN})
}
// Rebuild the client hello and do the handshake
if err := c.BuildHandshakeState(); err != nil {
@@ -146,9 +153,7 @@ func copyConfig(c *tls.Config) *utls.Config {
VerifyPeerCertificate: c.VerifyPeerCertificate,
KeyLogWriter: c.KeyLogWriter,
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
}
if config.EncryptedClientHelloConfigList != nil {
config.NextProtos = c.NextProtos
NextProtos: c.NextProtos,
}
return config
}
+4 -1
View File
@@ -2,7 +2,7 @@ package udp
import (
"context"
reflect "reflect"
"reflect"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
@@ -33,6 +33,7 @@ func init() {
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, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c)
if err != nil {
@@ -43,6 +44,7 @@ func init() {
PacketConn: pktConn,
Dest: c.RemoteAddr().(*net.UDPAddr),
}
errors.LogInfo(ctx, "finalmask udp dialer: wrapped UDPConn with ", reflect.TypeOf(pktConn))
case *cnc.Connection:
fakeConn := &internet.FakePacketConn{Conn: c}
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(fakeConn)
@@ -57,6 +59,7 @@ func init() {
Port: 0,
},
}
errors.LogInfo(ctx, "finalmask udp dialer: wrapped cnc.Connection with ", reflect.TypeOf(pktConn))
default:
conn.Close()
return nil, errors.New("unknown conn ", reflect.TypeOf(c))