mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-24 23:57:59 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5edc122b7 | ||
|
|
9dd17d55fb | ||
|
|
310b764811 | ||
|
|
3691741440 | ||
|
|
05e259c8e4 | ||
|
|
175502d807 | ||
|
|
6780045550 | ||
|
|
ff6126463b | ||
|
|
5c3d639c09 | ||
|
|
7c56b7beea | ||
|
|
5b91b152bb | ||
|
|
ef77a42063 | ||
|
|
dab99614dc |
+12
-6
@@ -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
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -20,7 +20,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 26
|
||||
Version_y byte = 4
|
||||
Version_z byte = 13
|
||||
Version_z byte = 15
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
v2net "github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/proxy/freedom"
|
||||
@@ -15,14 +16,15 @@ import (
|
||||
)
|
||||
|
||||
type FreedomConfig struct {
|
||||
TargetStrategy string `json:"targetStrategy"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
Redirect string `json:"redirect"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
Fragment *Fragment `json:"fragment"`
|
||||
Noise *Noise `json:"noise"`
|
||||
Noises []*Noise `json:"noises"`
|
||||
ProxyProtocol uint32 `json:"proxyProtocol"`
|
||||
TargetStrategy string `json:"targetStrategy"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
Redirect string `json:"redirect"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
Fragment *Fragment `json:"fragment"`
|
||||
Noise *Noise `json:"noise"`
|
||||
Noises []*Noise `json:"noises"`
|
||||
ProxyProtocol uint32 `json:"proxyProtocol"`
|
||||
IPsBlocked *StringList `json:"ipsBlocked"`
|
||||
}
|
||||
|
||||
type Fragment struct {
|
||||
@@ -162,6 +164,13 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
|
||||
if c.ProxyProtocol > 0 && c.ProxyProtocol <= 2 {
|
||||
config.ProxyProtocol = c.ProxyProtocol
|
||||
}
|
||||
if c.IPsBlocked != nil {
|
||||
rules, err := geodata.ParseIPRules(*c.IPsBlocked)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.IpsBlocked = &freedom.IPRules{Rules: rules}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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,4 +1,4 @@
|
||||
//go:build !linux && !windows && !android && !darwin
|
||||
//go:build !linux && !windows && !android && !darwin && !freebsd
|
||||
|
||||
package tun
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}),
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}),
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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{},
|
||||
})},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user