Compare commits

...
13 Commits
Author SHA1 Message Date
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
Meowand风扇滑翔翼 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 KorzunandLjhAUMEM 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
64 changed files with 3938 additions and 431 deletions
+12 -6
View File
@@ -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
+24 -8
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{},
}),
},
},
}
@@ -587,7 +597,9 @@ func TestIPMatch(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -704,7 +716,9 @@ func TestLocalDomain(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
@@ -994,7 +1008,9 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{
IpsBlocked: &freedom.IPRules{},
}),
},
},
}
+15 -17
View File
@@ -95,48 +95,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 +142,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 +173,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{}
+50
View File
@@ -0,0 +1,50 @@
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)
}
}
+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 = 15
)
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{},
}),
},
},
}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
}
+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
}
+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;
}
+90 -16
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{
@@ -307,7 +373,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
}
// DialDest means the dial target used in the dialer when creating conn
func NewPacketWriter(conn net.Conn, h *Handler, 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 +394,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 +408,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 +467,12 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
}
}
}
if isBlockedAddress(w.BlockedIPMatcher, b.UDP.Address) {
blockedAddr := b.UDP.Address
b.Release()
buf.ReleaseMulti(mb)
return errors.New("blocked target IP: ", blockedAddr).AtDebug()
}
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)
+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{},
}),
},
},
}
+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,416 @@
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
}
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}, 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]...)}, nil
case "xor16":
return evaluateXor(expr.GetArgs(), 0xFFFF, 2, ctx)
case "xor32":
return evaluateXor(expr.GetArgs(), 0xFFFFFFFF, 4, ctx)
case "be16":
if len(expr.GetArgs()) != 1 {
return evalValue{}, errors.New("be16 expects 1 arg")
}
value, err := evaluateExprArg(expr.GetArgs()[0], ctx)
if err != nil {
return evalValue{}, err
}
u64Value, err := value.asU64()
if err != nil {
return evalValue{}, err
}
if u64Value > 0xFFFF {
return evalValue{}, errors.New("be16 overflow")
}
out := make([]byte, 2)
binary.BigEndian.PutUint16(out, uint16(u64Value))
return evalValue{bytes: out}, nil
case "be32":
if len(expr.GetArgs()) != 1 {
return evalValue{}, errors.New("be32 expects 1 arg")
}
value, err := evaluateExprArg(expr.GetArgs()[0], ctx)
if err != nil {
return evalValue{}, err
}
u64Value, err := value.asU64()
if err != nil {
return evalValue{}, err
}
if u64Value > 0xFFFFFFFF {
return evalValue{}, errors.New("be32 overflow")
}
out := make([]byte, 4)
binary.BigEndian.PutUint32(out, uint32(u64Value))
return evalValue{bytes: out}, nil
default:
return evalValue{}, errors.New("unsupported expr op: ", expr.GetOp())
}
}
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
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...)}, 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...)}, 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.bytes != nil {
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,130 @@
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")
}
}
@@ -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,83 @@
package custom
import "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")
}
}
@@ -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 {
+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))