Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 805abcf5f9 | |||
| b4650360d6 | |||
| d52f15060b | |||
| 31ab22c33d | |||
| d42c981f9c | |||
| cb1106c2fb | |||
| df4b97097c | |||
| a9cec25b8d | |||
| c5edc122b7 | |||
| 9dd17d55fb | |||
| 310b764811 | |||
| 3691741440 | |||
| 05e259c8e4 | |||
| 175502d807 | |||
| 6780045550 | |||
| ff6126463b | |||
| 5c3d639c09 | |||
| 7c56b7beea | |||
| 5b91b152bb | |||
| ef77a42063 | |||
| dab99614dc |
+16
-10
@@ -271,11 +271,11 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
|
||||
// Priority domain matching
|
||||
hasMatch := false
|
||||
MatchSlice := s.domainMatcher.Match(strings.ToLower(domain))
|
||||
sort.Slice(MatchSlice, func(i, j int) bool {
|
||||
return MatchSlice[i] < MatchSlice[j]
|
||||
matchSlice := s.domainMatcher.Match(strings.ToLower(domain))
|
||||
sort.Slice(matchSlice, func(i, j int) bool {
|
||||
return matchSlice[i] < matchSlice[j]
|
||||
})
|
||||
for _, match := range MatchSlice {
|
||||
for _, match := range matchSlice {
|
||||
info := s.matcherInfos[match]
|
||||
client := s.clients[info.clientIdx]
|
||||
domainRule := info.domainRule
|
||||
@@ -288,6 +288,7 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
clientNames = append(clientNames, client.Name())
|
||||
hasMatch = true
|
||||
if client.finalQuery {
|
||||
logDecision(s.ctx, domain, domainRules, clientNames)
|
||||
return clients
|
||||
}
|
||||
}
|
||||
@@ -302,17 +303,13 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
clients = append(clients, client)
|
||||
clientNames = append(clientNames, client.Name())
|
||||
if client.finalQuery {
|
||||
logDecision(s.ctx, domain, domainRules, clientNames)
|
||||
return clients
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(domainRules) > 0 {
|
||||
errors.LogDebug(s.ctx, "domain ", domain, " matches following rules: ", domainRules)
|
||||
}
|
||||
if len(clientNames) > 0 {
|
||||
errors.LogDebug(s.ctx, "domain ", domain, " will use DNS in order: ", clientNames)
|
||||
}
|
||||
logDecision(s.ctx, domain, domainRules, clientNames)
|
||||
|
||||
if len(clients) == 0 {
|
||||
if len(s.clients) > 0 {
|
||||
@@ -327,6 +324,15 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
return clients
|
||||
}
|
||||
|
||||
func logDecision(ctx context.Context, domain string, domainRules []string, clientNames []string) {
|
||||
if len(domainRules) > 0 {
|
||||
errors.LogDebug(ctx, "domain ", domain, " matches following rules: ", domainRules)
|
||||
}
|
||||
if len(clientNames) > 0 {
|
||||
errors.LogDebug(ctx, "domain ", domain, " will use DNS in order: ", clientNames)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeQueryErrors(domain string, errs []error) error {
|
||||
if len(errs) == 0 {
|
||||
return dns.ErrEmptyResponse
|
||||
|
||||
+36
-76
@@ -147,7 +147,9 @@ func TestUDPServerSubnet(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -207,7 +209,9 @@ func TestUDPServer(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -345,7 +349,9 @@ func TestPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -414,7 +420,9 @@ func TestUDPServerIPv6(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -481,7 +489,9 @@ func TestStaticHostDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -538,15 +548,8 @@ func TestIPMatch(t *testing.T) {
|
||||
Port: uint32(port),
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
// inner ip, will not match
|
||||
Ip: []byte{192, 168, 11, 1},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
// inner ip, will not match
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{192, 168, 11, 1}, Prefix: 32}}}},
|
||||
},
|
||||
},
|
||||
// second dns, match ip
|
||||
@@ -561,22 +564,8 @@ func TestIPMatch(t *testing.T) {
|
||||
Port: uint32(port),
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 4},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32}}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 4}, Prefix: 32}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -587,7 +576,9 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -664,9 +655,9 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will match localhost, localhost-a and localhost-b,
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 2}, Prefix: 32}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 3}, Prefix: 32}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 4}, Prefix: 32}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 2}, Prefix: 32}}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 3}, Prefix: 32}}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 4}, Prefix: 32}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -704,7 +695,9 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -887,22 +880,8 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.8.8 and 8.8.4.4
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 4, 4},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32}}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 4, 4}, Prefix: 32}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -922,14 +901,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will match 8.8.8.8 and 8.8.8.7, etc
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 7},
|
||||
Prefix: 24,
|
||||
},
|
||||
},
|
||||
},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 7}, Prefix: 24}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -949,14 +921,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.7.7 (api.google.com)
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 7, 7},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 7, 7}, Prefix: 32}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -976,14 +941,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.7.8 (v2.api.google.com)
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 7, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 7, 8}, Prefix: 32}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -994,7 +952,9 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ func TestServiceTestRoute(t *testing.T) {
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
SourceIp: []*geodata.IPRule{{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
|
||||
SourceIp: []*geodata.IPRule{{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}}},
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -92,25 +92,22 @@ func TestRoutingRule(t *testing.T) {
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(),
|
||||
Prefix: 128,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(), Prefix: 128},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -140,9 +137,8 @@ func TestRoutingRule(t *testing.T) {
|
||||
SourceIp: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -159,9 +159,8 @@ func TestIPOnDemand(t *testing.T) {
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -204,9 +203,8 @@ func TestIPIfNonMatchDomain(t *testing.T) {
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -249,9 +247,8 @@ func TestIPIfNonMatchIP(t *testing.T) {
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{127, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,7 +11,11 @@ import (
|
||||
)
|
||||
|
||||
type DomainMatcher interface {
|
||||
// Match returns the indices of all rules that match the input domain.
|
||||
// The returned slice is owned by the caller and may be safely modified.
|
||||
// Note: the slice may contain duplicates and the order is unspecified.
|
||||
Match(input string) []uint32
|
||||
|
||||
MatchAny(input string) bool
|
||||
}
|
||||
|
||||
@@ -95,48 +99,43 @@ func (f *CompactDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainM
|
||||
matchers: make([]strmatcher.MatcherGroup, 0, len(rules)),
|
||||
values: make([]uint32, 0, len(rules)),
|
||||
}
|
||||
custom := strmatcher.NewLinearValueMatcher()
|
||||
var idx uint32
|
||||
for _, r := range rules {
|
||||
for i, r := range rules {
|
||||
switch v := r.Value.(type) {
|
||||
case *DomainRule_Custom:
|
||||
m, err := parseDomain(v.Custom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
custom.Add(m, 0)
|
||||
if compact.custom == nil {
|
||||
compact.custom = strmatcher.NewLinearValueMatcher()
|
||||
}
|
||||
compact.custom.Add(m, uint32(i))
|
||||
case *DomainRule_Geosite:
|
||||
m, err := f.getOrCreateFrom(v.Geosite)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
compact.matchers = append(compact.matchers, m)
|
||||
compact.values = append(compact.values, idx)
|
||||
idx++
|
||||
compact.values = append(compact.values, uint32(i))
|
||||
default:
|
||||
panic("unknown domain rule type")
|
||||
}
|
||||
}
|
||||
if len(compact.matchers) != len(rules) {
|
||||
compact.matchers = append(compact.matchers, custom)
|
||||
compact.values = append(compact.values, idx+1)
|
||||
}
|
||||
return compact, nil
|
||||
}
|
||||
|
||||
type CompactDomainMatcher struct {
|
||||
custom strmatcher.ValueMatcher
|
||||
matchers []strmatcher.MatcherGroup
|
||||
values []uint32
|
||||
}
|
||||
|
||||
func (c *CompactDomainMatcher) Add(matcher strmatcher.MatcherGroup, value uint32) {
|
||||
c.matchers = append(c.matchers, matcher)
|
||||
c.values = append(c.values, value)
|
||||
}
|
||||
|
||||
// Match implements DomainMatcher.
|
||||
func (c *CompactDomainMatcher) Match(input string) []uint32 {
|
||||
result := make([]uint32, 0)
|
||||
var result []uint32
|
||||
if c.custom != nil {
|
||||
result = append(result, c.custom.Match(input)...)
|
||||
}
|
||||
for i, m := range c.matchers {
|
||||
if m.MatchAny(input) {
|
||||
result = append(result, c.values[i])
|
||||
@@ -147,6 +146,9 @@ func (c *CompactDomainMatcher) Match(input string) []uint32 {
|
||||
|
||||
// MatchAny implements DomainMatcher.
|
||||
func (c *CompactDomainMatcher) MatchAny(input string) bool {
|
||||
if c.custom != nil && c.custom.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
for _, m := range c.matchers {
|
||||
if m.MatchAny(input) {
|
||||
return true
|
||||
@@ -175,7 +177,7 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
|
||||
|
||||
func newDomainMatcherFactory() DomainMatcherFactory {
|
||||
switch runtime.GOOS {
|
||||
case "ios":
|
||||
case "ios", "android":
|
||||
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
||||
default:
|
||||
return &MphDomainMatcherFactory{}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
)
|
||||
|
||||
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
||||
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildMatcher() failed: %v", err)
|
||||
}
|
||||
|
||||
got := matcher.Match("example.com")
|
||||
slices.Sort(got)
|
||||
|
||||
want := []uint32{0, 1}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("Match() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildMatcher() failed: %v", err)
|
||||
}
|
||||
|
||||
got := matcher.Match("163.com")
|
||||
slices.Sort(got)
|
||||
|
||||
want := []uint32{0, 1}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("Match() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
|
||||
matcher, err := (&MphDomainMatcherFactory{}).BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildMatcher() failed: %v", err)
|
||||
}
|
||||
|
||||
got := matcher.Match("example.com")
|
||||
if !reflect.DeepEqual(got, []uint32{0, 1}) {
|
||||
t.Fatalf("Match() = %v, want %v", got, []uint32{0, 1})
|
||||
}
|
||||
|
||||
got[0] = 1
|
||||
|
||||
gotAgain := matcher.Match("example.com")
|
||||
if !reflect.DeepEqual(gotAgain, []uint32{0, 1}) {
|
||||
t.Fatalf("Match() after caller mutation = %v, want %v", gotAgain, []uint32{0, 1})
|
||||
}
|
||||
}
|
||||
+96
-39
@@ -433,6 +433,58 @@ func (x *CIDR) GetPrefix() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
type CIDRRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Cidr *CIDR `protobuf:"bytes,1,opt,name=cidr,proto3" json:"cidr,omitempty"`
|
||||
ReverseMatch bool `protobuf:"varint,2,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CIDRRule) Reset() {
|
||||
*x = CIDRRule{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *CIDRRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CIDRRule) ProtoMessage() {}
|
||||
|
||||
func (x *CIDRRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CIDRRule.ProtoReflect.Descriptor instead.
|
||||
func (*CIDRRule) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *CIDRRule) GetCidr() *CIDR {
|
||||
if x != nil {
|
||||
return x.Cidr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CIDRRule) GetReverseMatch() bool {
|
||||
if x != nil {
|
||||
return x.ReverseMatch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type GeoIP struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
|
||||
@@ -444,7 +496,7 @@ type GeoIP struct {
|
||||
|
||||
func (x *GeoIP) Reset() {
|
||||
*x = GeoIP{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[6]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -456,7 +508,7 @@ func (x *GeoIP) String() string {
|
||||
func (*GeoIP) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIP) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[6]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -469,7 +521,7 @@ func (x *GeoIP) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIP) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{6}
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetCode() string {
|
||||
@@ -502,7 +554,7 @@ type GeoIPList struct {
|
||||
|
||||
func (x *GeoIPList) Reset() {
|
||||
*x = GeoIPList{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[7]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -514,7 +566,7 @@ func (x *GeoIPList) String() string {
|
||||
func (*GeoIPList) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIPList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[7]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -527,7 +579,7 @@ func (x *GeoIPList) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIPList) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{7}
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *GeoIPList) GetEntry() []*GeoIP {
|
||||
@@ -548,7 +600,7 @@ type GeoIPRule struct {
|
||||
|
||||
func (x *GeoIPRule) Reset() {
|
||||
*x = GeoIPRule{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[8]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -560,7 +612,7 @@ func (x *GeoIPRule) String() string {
|
||||
func (*GeoIPRule) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIPRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[8]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -573,7 +625,7 @@ func (x *GeoIPRule) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GeoIPRule.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIPRule) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{8}
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *GeoIPRule) GetFile() string {
|
||||
@@ -610,7 +662,7 @@ type IPRule struct {
|
||||
|
||||
func (x *IPRule) Reset() {
|
||||
*x = IPRule{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[9]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -622,7 +674,7 @@ func (x *IPRule) String() string {
|
||||
func (*IPRule) ProtoMessage() {}
|
||||
|
||||
func (x *IPRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[9]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -635,7 +687,7 @@ func (x *IPRule) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use IPRule.ProtoReflect.Descriptor instead.
|
||||
func (*IPRule) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{9}
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *IPRule) GetValue() isIPRule_Value {
|
||||
@@ -654,7 +706,7 @@ func (x *IPRule) GetGeoip() *GeoIPRule {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IPRule) GetCustom() *CIDR {
|
||||
func (x *IPRule) GetCustom() *CIDRRule {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*IPRule_Custom); ok {
|
||||
return x.Custom
|
||||
@@ -672,7 +724,7 @@ type IPRule_Geoip struct {
|
||||
}
|
||||
|
||||
type IPRule_Custom struct {
|
||||
Custom *CIDR `protobuf:"bytes,2,opt,name=custom,proto3,oneof"`
|
||||
Custom *CIDRRule `protobuf:"bytes,2,opt,name=custom,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*IPRule_Geoip) isIPRule_Value() {}
|
||||
@@ -693,7 +745,7 @@ type Domain_Attribute struct {
|
||||
|
||||
func (x *Domain_Attribute) Reset() {
|
||||
*x = Domain_Attribute{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[10]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -705,7 +757,7 @@ func (x *Domain_Attribute) String() string {
|
||||
func (*Domain_Attribute) ProtoMessage() {}
|
||||
|
||||
func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[10]
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -807,7 +859,10 @@ const file_common_geodata_geodat_proto_rawDesc = "" +
|
||||
"\x05value\".\n" +
|
||||
"\x04CIDR\x12\x0e\n" +
|
||||
"\x02ip\x18\x01 \x01(\fR\x02ip\x12\x16\n" +
|
||||
"\x06prefix\x18\x02 \x01(\rR\x06prefix\"o\n" +
|
||||
"\x06prefix\x18\x02 \x01(\rR\x06prefix\"^\n" +
|
||||
"\bCIDRRule\x12-\n" +
|
||||
"\x04cidr\x18\x01 \x01(\v2\x19.xray.common.geodata.CIDRR\x04cidr\x12#\n" +
|
||||
"\rreverse_match\x18\x02 \x01(\bR\freverseMatch\"o\n" +
|
||||
"\x05GeoIP\x12\x12\n" +
|
||||
"\x04code\x18\x01 \x01(\tR\x04code\x12-\n" +
|
||||
"\x04cidr\x18\x02 \x03(\v2\x19.xray.common.geodata.CIDRR\x04cidr\x12#\n" +
|
||||
@@ -817,10 +872,10 @@ const file_common_geodata_geodat_proto_rawDesc = "" +
|
||||
"\tGeoIPRule\x12\x12\n" +
|
||||
"\x04file\x18\x01 \x01(\tR\x04file\x12\x12\n" +
|
||||
"\x04code\x18\x02 \x01(\tR\x04code\x12#\n" +
|
||||
"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"~\n" +
|
||||
"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"\x82\x01\n" +
|
||||
"\x06IPRule\x126\n" +
|
||||
"\x05geoip\x18\x01 \x01(\v2\x1e.xray.common.geodata.GeoIPRuleH\x00R\x05geoip\x123\n" +
|
||||
"\x06custom\x18\x02 \x01(\v2\x19.xray.common.geodata.CIDRH\x00R\x06customB\a\n" +
|
||||
"\x05geoip\x18\x01 \x01(\v2\x1e.xray.common.geodata.GeoIPRuleH\x00R\x05geoip\x127\n" +
|
||||
"\x06custom\x18\x02 \x01(\v2\x1d.xray.common.geodata.CIDRRuleH\x00R\x06customB\a\n" +
|
||||
"\x05valueB[\n" +
|
||||
"\x17com.xray.common.geodataP\x01Z(github.com/xtls/xray-core/common/geodata\xaa\x02\x13Xray.Common.Geodatab\x06proto3"
|
||||
|
||||
@@ -837,7 +892,7 @@ func file_common_geodata_geodat_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_common_geodata_geodat_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_common_geodata_geodat_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||
var file_common_geodata_geodat_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
||||
var file_common_geodata_geodat_proto_goTypes = []any{
|
||||
(Domain_Type)(0), // 0: xray.common.geodata.Domain.Type
|
||||
(*Domain)(nil), // 1: xray.common.geodata.Domain
|
||||
@@ -846,28 +901,30 @@ var file_common_geodata_geodat_proto_goTypes = []any{
|
||||
(*GeoSiteRule)(nil), // 4: xray.common.geodata.GeoSiteRule
|
||||
(*DomainRule)(nil), // 5: xray.common.geodata.DomainRule
|
||||
(*CIDR)(nil), // 6: xray.common.geodata.CIDR
|
||||
(*GeoIP)(nil), // 7: xray.common.geodata.GeoIP
|
||||
(*GeoIPList)(nil), // 8: xray.common.geodata.GeoIPList
|
||||
(*GeoIPRule)(nil), // 9: xray.common.geodata.GeoIPRule
|
||||
(*IPRule)(nil), // 10: xray.common.geodata.IPRule
|
||||
(*Domain_Attribute)(nil), // 11: xray.common.geodata.Domain.Attribute
|
||||
(*CIDRRule)(nil), // 7: xray.common.geodata.CIDRRule
|
||||
(*GeoIP)(nil), // 8: xray.common.geodata.GeoIP
|
||||
(*GeoIPList)(nil), // 9: xray.common.geodata.GeoIPList
|
||||
(*GeoIPRule)(nil), // 10: xray.common.geodata.GeoIPRule
|
||||
(*IPRule)(nil), // 11: xray.common.geodata.IPRule
|
||||
(*Domain_Attribute)(nil), // 12: xray.common.geodata.Domain.Attribute
|
||||
}
|
||||
var file_common_geodata_geodat_proto_depIdxs = []int32{
|
||||
0, // 0: xray.common.geodata.Domain.type:type_name -> xray.common.geodata.Domain.Type
|
||||
11, // 1: xray.common.geodata.Domain.attribute:type_name -> xray.common.geodata.Domain.Attribute
|
||||
12, // 1: xray.common.geodata.Domain.attribute:type_name -> xray.common.geodata.Domain.Attribute
|
||||
1, // 2: xray.common.geodata.GeoSite.domain:type_name -> xray.common.geodata.Domain
|
||||
2, // 3: xray.common.geodata.GeoSiteList.entry:type_name -> xray.common.geodata.GeoSite
|
||||
4, // 4: xray.common.geodata.DomainRule.geosite:type_name -> xray.common.geodata.GeoSiteRule
|
||||
1, // 5: xray.common.geodata.DomainRule.custom:type_name -> xray.common.geodata.Domain
|
||||
6, // 6: xray.common.geodata.GeoIP.cidr:type_name -> xray.common.geodata.CIDR
|
||||
7, // 7: xray.common.geodata.GeoIPList.entry:type_name -> xray.common.geodata.GeoIP
|
||||
9, // 8: xray.common.geodata.IPRule.geoip:type_name -> xray.common.geodata.GeoIPRule
|
||||
6, // 9: xray.common.geodata.IPRule.custom:type_name -> xray.common.geodata.CIDR
|
||||
10, // [10:10] is the sub-list for method output_type
|
||||
10, // [10:10] is the sub-list for method input_type
|
||||
10, // [10:10] is the sub-list for extension type_name
|
||||
10, // [10:10] is the sub-list for extension extendee
|
||||
0, // [0:10] is the sub-list for field type_name
|
||||
6, // 6: xray.common.geodata.CIDRRule.cidr:type_name -> xray.common.geodata.CIDR
|
||||
6, // 7: xray.common.geodata.GeoIP.cidr:type_name -> xray.common.geodata.CIDR
|
||||
8, // 8: xray.common.geodata.GeoIPList.entry:type_name -> xray.common.geodata.GeoIP
|
||||
10, // 9: xray.common.geodata.IPRule.geoip:type_name -> xray.common.geodata.GeoIPRule
|
||||
7, // 10: xray.common.geodata.IPRule.custom:type_name -> xray.common.geodata.CIDRRule
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_common_geodata_geodat_proto_init() }
|
||||
@@ -879,11 +936,11 @@ func file_common_geodata_geodat_proto_init() {
|
||||
(*DomainRule_Geosite)(nil),
|
||||
(*DomainRule_Custom)(nil),
|
||||
}
|
||||
file_common_geodata_geodat_proto_msgTypes[9].OneofWrappers = []any{
|
||||
file_common_geodata_geodat_proto_msgTypes[10].OneofWrappers = []any{
|
||||
(*IPRule_Geoip)(nil),
|
||||
(*IPRule_Custom)(nil),
|
||||
}
|
||||
file_common_geodata_geodat_proto_msgTypes[10].OneofWrappers = []any{
|
||||
file_common_geodata_geodat_proto_msgTypes[11].OneofWrappers = []any{
|
||||
(*Domain_Attribute_BoolValue)(nil),
|
||||
(*Domain_Attribute_IntValue)(nil),
|
||||
}
|
||||
@@ -893,7 +950,7 @@ func file_common_geodata_geodat_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_geodata_geodat_proto_rawDesc), len(file_common_geodata_geodat_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 11,
|
||||
NumMessages: 12,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -66,6 +66,11 @@ message CIDR {
|
||||
uint32 prefix = 2;
|
||||
}
|
||||
|
||||
message CIDRRule {
|
||||
CIDR cidr = 1;
|
||||
bool reverse_match = 2;
|
||||
}
|
||||
|
||||
message GeoIP {
|
||||
string code = 1;
|
||||
repeated CIDR cidr = 2;
|
||||
@@ -85,6 +90,6 @@ message GeoIPRule {
|
||||
message IPRule {
|
||||
oneof value {
|
||||
GeoIPRule geoip = 1;
|
||||
CIDR custom = 2;
|
||||
CIDRRule custom = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,45 +940,58 @@ func (f *IPSetFactory) createFrom(yield func(func(*CIDR)) error) (*IPSet, error)
|
||||
|
||||
func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error) {
|
||||
n := len(rules)
|
||||
custom := make([]*CIDR, 0, n)
|
||||
pos := make([]*GeoIPRule, 0, n)
|
||||
neg := make([]*GeoIPRule, 0, n)
|
||||
posCustom := make([]*CIDR, 0, n)
|
||||
negCustom := make([]*CIDR, 0, n)
|
||||
posGeoip := make([]*GeoIPRule, 0, n)
|
||||
negGeoip := make([]*GeoIPRule, 0, n)
|
||||
|
||||
for _, r := range rules {
|
||||
switch v := r.Value.(type) {
|
||||
case *IPRule_Custom:
|
||||
custom = append(custom, v.Custom)
|
||||
if !v.Custom.ReverseMatch {
|
||||
posCustom = append(posCustom, v.Custom.Cidr)
|
||||
} else {
|
||||
negCustom = append(negCustom, v.Custom.Cidr)
|
||||
}
|
||||
case *IPRule_Geoip:
|
||||
if !v.Geoip.ReverseMatch {
|
||||
pos = append(pos, v.Geoip)
|
||||
posGeoip = append(posGeoip, v.Geoip)
|
||||
} else {
|
||||
neg = append(neg, v.Geoip)
|
||||
negGeoip = append(negGeoip, v.Geoip)
|
||||
}
|
||||
default:
|
||||
panic("unknown ip rule type")
|
||||
}
|
||||
}
|
||||
|
||||
subs := make([]*HeuristicIPMatcher, 0, 3)
|
||||
subs := make([]*HeuristicIPMatcher, 0, 4)
|
||||
|
||||
if len(custom) > 0 {
|
||||
ipset, err := f.CreateFromCIDRs(custom)
|
||||
if len(posCustom) > 0 {
|
||||
ipset, err := f.CreateFromCIDRs(posCustom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: false})
|
||||
}
|
||||
|
||||
if len(pos) > 0 {
|
||||
ipset, err := f.GetOrCreateFromGeoIPRules(pos)
|
||||
if len(negCustom) > 0 {
|
||||
ipset, err := f.CreateFromCIDRs(negCustom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: true})
|
||||
}
|
||||
|
||||
if len(posGeoip) > 0 {
|
||||
ipset, err := f.GetOrCreateFromGeoIPRules(posGeoip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: false})
|
||||
}
|
||||
|
||||
if len(neg) > 0 {
|
||||
ipset, err := f.GetOrCreateFromGeoIPRules(neg)
|
||||
if len(negGeoip) > 0 {
|
||||
ipset, err := f.GetOrCreateFromGeoIPRules(negGeoip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -189,6 +189,34 @@ func TestIPReverseMatcher2(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPCustomReverseMatcher(t *testing.T) {
|
||||
matcher := buildIPMatcher("!8.8.8.8/32")
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "1.1.1.1",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||
t.Error("unexpected output: ", v, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"8.8.8.8/32",
|
||||
|
||||
@@ -17,6 +17,8 @@ func ParseIPRules(rules []string) ([]*IPRule, error) {
|
||||
var ipRules []*IPRule
|
||||
|
||||
for i, r := range rules {
|
||||
r, reverse := cutReversePrefix(r)
|
||||
|
||||
if strings.HasPrefix(r, "geoip:") {
|
||||
r = "ext:" + DefaultGeoIPDat + ":" + r[len("geoip:"):]
|
||||
}
|
||||
@@ -32,9 +34,9 @@ func ParseIPRules(rules []string) ([]*IPRule, error) {
|
||||
var rule isIPRule_Value
|
||||
var err error
|
||||
if prefix > 0 {
|
||||
rule, err = parseGeoIPRule(r[prefix:])
|
||||
rule, err = parseGeoIPRule(r[prefix:], reverse)
|
||||
} else {
|
||||
rule, err = parseCustomIPRule(r)
|
||||
rule, err = parseCustomIPRule(r, reverse)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.New("illegal ip rule: ", rules[i]).Base(err)
|
||||
@@ -45,7 +47,16 @@ func ParseIPRules(rules []string) ([]*IPRule, error) {
|
||||
return ipRules, nil
|
||||
}
|
||||
|
||||
func parseGeoIPRule(rule string) (*IPRule_Geoip, error) {
|
||||
func cutReversePrefix(s string) (string, bool) {
|
||||
reverse := false
|
||||
for strings.HasPrefix(s, "!") {
|
||||
s = s[1:]
|
||||
reverse = !reverse
|
||||
}
|
||||
return s, reverse
|
||||
}
|
||||
|
||||
func parseGeoIPRule(rule string, reverse bool) (*IPRule_Geoip, error) {
|
||||
file, code, ok := strings.Cut(rule, ":")
|
||||
if !ok {
|
||||
return nil, errors.New("syntax error")
|
||||
@@ -55,11 +66,8 @@ func parseGeoIPRule(rule string) (*IPRule_Geoip, error) {
|
||||
return nil, errors.New("empty file")
|
||||
}
|
||||
|
||||
reverse := false
|
||||
if strings.HasPrefix(code, "!") {
|
||||
code = code[1:]
|
||||
reverse = true
|
||||
}
|
||||
code, codeReverse := cutReversePrefix(code)
|
||||
reverse = reverse != codeReverse
|
||||
if code == "" {
|
||||
return nil, errors.New("empty code")
|
||||
}
|
||||
@@ -78,13 +86,16 @@ func parseGeoIPRule(rule string) (*IPRule_Geoip, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCustomIPRule(rule string) (*IPRule_Custom, error) {
|
||||
func parseCustomIPRule(rule string, reverse bool) (*IPRule_Custom, error) {
|
||||
cidr, err := parseCIDR(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IPRule_Custom{
|
||||
Custom: cidr,
|
||||
Custom: &CIDRRule{
|
||||
Cidr: cidr,
|
||||
ReverseMatch: reverse,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,20 @@ func TestParseIPRules(t *testing.T) {
|
||||
rules := []string{
|
||||
"geoip:us",
|
||||
"geoip:cn",
|
||||
"!geoip:cn",
|
||||
"!!geoip:cn",
|
||||
"geoip:!cn",
|
||||
"geoip:!!cn",
|
||||
"!geoip:!cn",
|
||||
"ext:geoip.dat:!cn",
|
||||
"ext:geoip.dat:!!cn",
|
||||
"ext:geoip.dat:ca",
|
||||
"ext-ip:geoip.dat:!cn",
|
||||
"ext-ip:geoip.dat:!ca",
|
||||
"192.168.0.0/24",
|
||||
"!192.168.0.0/24",
|
||||
"!!192.168.0.0/24",
|
||||
"!!!192.168.0.0/24",
|
||||
"192.168.0.1",
|
||||
"fe80::/64",
|
||||
"fe80::",
|
||||
@@ -30,6 +38,53 @@ func TestParseIPRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIPRuleReverse(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
for _, tt := range []struct {
|
||||
rule string
|
||||
reverse bool
|
||||
}{
|
||||
{rule: "!192.168.0.0/24", reverse: true},
|
||||
{rule: "!!192.168.0.0/24", reverse: false},
|
||||
{rule: "!!!192.168.0.0/24", reverse: true},
|
||||
{rule: "!!!!192.168.0.0/24", reverse: false},
|
||||
{rule: "geoip:cn", reverse: false},
|
||||
{rule: "!geoip:cn", reverse: true},
|
||||
{rule: "!!geoip:cn", reverse: false},
|
||||
{rule: "geoip:!cn", reverse: true},
|
||||
{rule: "geoip:!!cn", reverse: false},
|
||||
{rule: "!geoip:!cn", reverse: false},
|
||||
{rule: "!!geoip:!cn", reverse: true},
|
||||
{rule: "!geoip:!!cn", reverse: true},
|
||||
{rule: "ext:geoip.dat:!!!cn", reverse: true},
|
||||
} {
|
||||
t.Run(tt.rule, func(t *testing.T) {
|
||||
rules, err := geodata.ParseIPRules([]string{tt.rule})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse ip rules, got %s", err)
|
||||
}
|
||||
|
||||
if len(rules) != 1 {
|
||||
t.Fatalf("Expected 1 rule, got %d", len(rules))
|
||||
}
|
||||
|
||||
switch rule := rules[0]; {
|
||||
case rule.GetGeoip() != nil:
|
||||
if rule.GetGeoip().GetReverseMatch() != tt.reverse {
|
||||
t.Fatalf("Expected geoip reverse match to be %t", tt.reverse)
|
||||
}
|
||||
case rule.GetCustom() != nil:
|
||||
if rule.GetCustom().GetReverseMatch() != tt.reverse {
|
||||
t.Fatalf("Expected custom reverse match to be %t", tt.reverse)
|
||||
}
|
||||
default:
|
||||
t.Fatal("Expected ip rule")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDomainRules(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package strmatcher
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -253,13 +254,12 @@ func AddMatcherToGroup(g MatcherGroup, matcher Matcher, value uint32) error {
|
||||
}
|
||||
|
||||
// CompositeMatches flattens the matches slice to produce a single matched indices slice.
|
||||
// It is designed to avoid new memory allocation as possible.
|
||||
func CompositeMatches(matches [][]uint32) []uint32 {
|
||||
switch len(matches) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return matches[0]
|
||||
return slices.Clone(matches[0])
|
||||
default:
|
||||
result := make([]uint32, 0, 5)
|
||||
for i := 0; i < len(matches); i++ {
|
||||
|
||||
@@ -62,6 +62,7 @@ type IndexMatcher interface {
|
||||
// Match returns the indices of all matchers that matches the input.
|
||||
// * Empty array is returned if no such matcher exists.
|
||||
// * The order of returned matchers should follow priority specification.
|
||||
// * The returned slice is owned by the caller and may be safely modified.
|
||||
// Priority specification:
|
||||
// 1. Priority between matcher types: full > domain > substr > regex.
|
||||
// 2. Priority of same-priority matchers matching at same position: the early added takes precedence.
|
||||
@@ -89,6 +90,7 @@ type ValueMatcher interface {
|
||||
// * Empty array is returned if no such matcher exists.
|
||||
// * The order of returned values should follow priority specification.
|
||||
// * Same value may appear multiple times if multiple matched matchers were added with that value.
|
||||
// * The returned slice is owned by the caller and may be safely modified.
|
||||
// Priority specification:
|
||||
// 1. Priority between matcher types: full > domain > substr > regex.
|
||||
// 2. Priority of same-priority matchers matching at same position: the early added takes precedence.
|
||||
|
||||
@@ -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 = 17
|
||||
)
|
||||
|
||||
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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pires/go-proxyproto v0.11.0
|
||||
github.com/pires/go-proxyproto v0.12.0
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
@@ -27,7 +27,7 @@ require (
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
golang.zx2c4.com/wireguard/windows v0.6.1
|
||||
google.golang.org/grpc v1.80.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||
|
||||
@@ -45,8 +45,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
|
||||
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
@@ -131,8 +131,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
golang.zx2c4.com/wireguard/windows v0.6.1 h1:XMaKojH1Hs/raMrmnir4n35nTvzvWj7NmSYzHn2F4qU=
|
||||
golang.zx2c4.com/wireguard/windows v0.6.1/go.mod h1:04aqInu5GYuTFvMuDw/rKBAF7mHrltW/3rekpfbbZDM=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
||||
|
||||
+17
-8
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -135,17 +135,15 @@ func TestRouterConfig(t *testing.T) {
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{10, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
Prefix: 128,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Prefix: 128},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -216,17 +214,15 @@ func TestRouterConfig(t *testing.T) {
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{10, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
Prefix: 128,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Prefix: 128},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -99,9 +99,8 @@ func TestXrayConfig(t *testing.T) {
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{10, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -216,8 +215,12 @@ func TestSniffingConfig_Build(t *testing.T) {
|
||||
if rule == nil {
|
||||
t.Fatalf("SniffingConfig.Build() produced a non-custom ip rule at index %d", i)
|
||||
}
|
||||
if !reflect.DeepEqual(rule.Ip, tc.ip) || rule.Prefix != tc.prefix {
|
||||
t.Fatalf("SniffingConfig.Build() produced wrong ip rule at index %d: got (%v, %d), want (%v, %d)", i, rule.Ip, rule.Prefix, tc.ip, tc.prefix)
|
||||
cidr := rule.GetCidr()
|
||||
if cidr == nil {
|
||||
t.Fatalf("SniffingConfig.Build() produced a custom ip rule without cidr at index %d", i)
|
||||
}
|
||||
if !reflect.DeepEqual(cidr.Ip, tc.ip) || cidr.Prefix != tc.prefix {
|
||||
t.Fatalf("SniffingConfig.Build() produced wrong ip rule at index %d: got (%v, %d), want (%v, %d)", i, cidr.Ip, cidr.Prefix, tc.ip, tc.prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+84
-21
@@ -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;
|
||||
}
|
||||
|
||||
+121
-39
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/retry"
|
||||
@@ -30,6 +32,32 @@ import (
|
||||
|
||||
var useSplice bool
|
||||
|
||||
var defaultPrivateBlockIP = []string{
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.0.2.0/24",
|
||||
"192.88.99.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"224.0.0.0/3",
|
||||
"::/127",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
"ff00::/8",
|
||||
}
|
||||
|
||||
var defaultPrivateBlockIPMatcher = func() geodata.IPMatcher {
|
||||
rules := common.Must2(geodata.ParseIPRules(defaultPrivateBlockIP))
|
||||
return common.Must2(geodata.IPReg.BuildIPMatcher(rules))
|
||||
}()
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
h := new(Handler)
|
||||
@@ -50,14 +78,22 @@ func init() {
|
||||
|
||||
// Handler handles Freedom connections.
|
||||
type Handler struct {
|
||||
policyManager policy.Manager
|
||||
config *Config
|
||||
policyManager policy.Manager
|
||||
config *Config
|
||||
blockedIPMatcher geodata.IPMatcher
|
||||
}
|
||||
|
||||
// Init initializes the Handler with necessary parameters.
|
||||
func (h *Handler) Init(config *Config, pm policy.Manager) error {
|
||||
h.config = config
|
||||
h.policyManager = pm
|
||||
if config.IpsBlocked != nil && len(config.IpsBlocked.Rules) > 0 {
|
||||
m, err := geodata.IPReg.BuildIPMatcher(config.IpsBlocked.Rules)
|
||||
if err != nil {
|
||||
return errors.New("failed to build blocked ip matcher").Base(err)
|
||||
}
|
||||
h.blockedIPMatcher = m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -75,6 +111,32 @@ func isValidAddress(addr *net.IPOrDomain) bool {
|
||||
return a != net.AnyIP && a != net.AnyIPv6
|
||||
}
|
||||
|
||||
func (h *Handler) getBlockedIPMatcher(ctx context.Context, inbound *session.Inbound) geodata.IPMatcher {
|
||||
if h.blockedIPMatcher != nil {
|
||||
return h.blockedIPMatcher
|
||||
}
|
||||
if h.config.IpsBlocked != nil && len(h.config.IpsBlocked.Rules) == 0 { // "ipsBlocked": []
|
||||
return nil
|
||||
}
|
||||
if inbound == nil {
|
||||
return nil
|
||||
}
|
||||
switch inbound.Name {
|
||||
case "vmess", "trojan", "hysteria", "wireguard":
|
||||
errors.LogInfo(ctx, "applying default private IP blocking policy for inbound ", inbound.Name)
|
||||
return defaultPrivateBlockIPMatcher
|
||||
}
|
||||
if strings.HasPrefix(inbound.Name, "vless") || strings.HasPrefix(inbound.Name, "shadowsocks") {
|
||||
errors.LogInfo(ctx, "applying default private IP blocking policy for inbound ", inbound.Name)
|
||||
return defaultPrivateBlockIPMatcher
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isBlockedAddress(matcher geodata.IPMatcher, addr net.Address) bool {
|
||||
return matcher != nil && addr != nil && addr.Family().IsIP() && matcher.Match(addr.IP())
|
||||
}
|
||||
|
||||
// Process implements proxy.Outbound.
|
||||
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
@@ -85,6 +147,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
ob.Name = "freedom"
|
||||
ob.CanSpliceCopy = 1
|
||||
inbound := session.InboundFromContext(ctx)
|
||||
blockedIPMatcher := h.getBlockedIPMatcher(ctx, inbound)
|
||||
|
||||
destination := ob.Target
|
||||
origTargetAddr := ob.OriginalTarget.Address
|
||||
@@ -138,23 +201,26 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
return err
|
||||
}
|
||||
|
||||
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
|
||||
version := byte(h.config.ProxyProtocol)
|
||||
srcAddr := inbound.Source.RawNetAddr()
|
||||
dstAddr := rawConn.RemoteAddr()
|
||||
header := proxyproto.HeaderProxyFromAddrs(version, srcAddr, dstAddr)
|
||||
if _, err = header.WriteTo(rawConn); err != nil {
|
||||
rawConn.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
conn = rawConn
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("failed to open connection to ", destination).Base(err)
|
||||
}
|
||||
if remoteAddr := net.DestinationFromAddr(conn.RemoteAddr()).Address; isBlockedAddress(blockedIPMatcher, remoteAddr) {
|
||||
conn.Close()
|
||||
return errors.New("blocked target IP: ", remoteAddr).AtInfo()
|
||||
}
|
||||
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
|
||||
version := byte(h.config.ProxyProtocol)
|
||||
srcAddr := inbound.Source.RawNetAddr()
|
||||
dstAddr := conn.RemoteAddr()
|
||||
header := proxyproto.HeaderProxyFromAddrs(version, srcAddr, dstAddr)
|
||||
if _, err = header.WriteTo(conn); err != nil {
|
||||
conn.Close()
|
||||
return errors.New("failed to set PROXY protocol v", version).Base(err)
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
errors.LogInfo(ctx, "connection opened to ", destination, ", local endpoint ", conn.LocalAddr(), ", remote endpoint ", conn.RemoteAddr())
|
||||
|
||||
@@ -189,7 +255,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
writer = buf.NewWriter(conn)
|
||||
}
|
||||
} else {
|
||||
writer = NewPacketWriter(conn, h, UDPOverride, destination)
|
||||
writer = NewPacketWriter(conn, h, UDPOverride, destination, blockedIPMatcher)
|
||||
if h.config.Noises != nil {
|
||||
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
||||
writer = &NoisePacketWriter{
|
||||
@@ -224,7 +290,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
if destination.Network == net.Network_TCP {
|
||||
reader = buf.NewReader(conn)
|
||||
} else {
|
||||
reader = NewPacketReader(conn, UDPOverride, destination)
|
||||
reader = NewPacketReader(conn, UDPOverride, destination, blockedIPMatcher)
|
||||
}
|
||||
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
|
||||
return errors.New("failed to process response").Base(err)
|
||||
@@ -243,7 +309,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
|
||||
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination, blockedIPMatcher geodata.IPMatcher) buf.Reader {
|
||||
iConn := conn
|
||||
statConn, ok := iConn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
@@ -262,6 +328,7 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.De
|
||||
return &PacketReader{
|
||||
PacketConnWrapper: c,
|
||||
Counter: counter,
|
||||
BlockedIPMatcher: blockedIPMatcher,
|
||||
IsOverridden: isOverridden,
|
||||
InitUnchangedAddr: DialDest.Address,
|
||||
InitChangedAddr: net.DestinationFromAddr(conn.RemoteAddr()).Address,
|
||||
@@ -273,6 +340,7 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.De
|
||||
type PacketReader struct {
|
||||
*internet.PacketConnWrapper
|
||||
stats.Counter
|
||||
BlockedIPMatcher geodata.IPMatcher
|
||||
IsOverridden bool
|
||||
InitUnchangedAddr net.Address
|
||||
InitChangedAddr net.Address
|
||||
@@ -280,34 +348,42 @@ type PacketReader struct {
|
||||
|
||||
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
b := buf.New()
|
||||
b.Resize(0, buf.Size)
|
||||
n, d, err := r.PacketConnWrapper.ReadFrom(b.Bytes())
|
||||
if err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
b.Resize(0, int32(n))
|
||||
// if udp dest addr is changed, we are unable to get the correct src addr
|
||||
// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr
|
||||
if !r.IsOverridden {
|
||||
address := net.IPAddress(d.(*net.UDPAddr).IP)
|
||||
if r.InitChangedAddr == address {
|
||||
address = r.InitUnchangedAddr
|
||||
for {
|
||||
b.Resize(0, buf.Size)
|
||||
n, d, err := r.PacketConnWrapper.ReadFrom(b.Bytes())
|
||||
if err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
b.UDP = &net.Destination{
|
||||
Address: address,
|
||||
Port: net.Port(d.(*net.UDPAddr).Port),
|
||||
Network: net.Network_UDP,
|
||||
b.Resize(0, int32(n))
|
||||
|
||||
udpAddr := d.(*net.UDPAddr)
|
||||
sourceAddr := net.IPAddress(udpAddr.IP)
|
||||
if isBlockedAddress(r.BlockedIPMatcher, sourceAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// if udp dest addr is changed, we are unable to get the correct src addr
|
||||
// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr
|
||||
if !r.IsOverridden {
|
||||
if r.InitChangedAddr == sourceAddr {
|
||||
sourceAddr = r.InitUnchangedAddr
|
||||
}
|
||||
b.UDP = &net.Destination{
|
||||
Address: sourceAddr,
|
||||
Port: net.Port(udpAddr.Port),
|
||||
Network: net.Network_UDP,
|
||||
}
|
||||
}
|
||||
if r.Counter != nil {
|
||||
r.Counter.Add(int64(n))
|
||||
}
|
||||
return buf.MultiBuffer{b}, nil
|
||||
}
|
||||
if r.Counter != nil {
|
||||
r.Counter.Add(int64(n))
|
||||
}
|
||||
return buf.MultiBuffer{b}, nil
|
||||
}
|
||||
|
||||
// DialDest means the dial target used in the dialer when creating conn
|
||||
func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, DialDest net.Destination) buf.Writer {
|
||||
func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, DialDest net.Destination, blockedIPMatcher geodata.IPMatcher) buf.Writer {
|
||||
iConn := conn
|
||||
statConn, ok := iConn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
@@ -328,6 +404,7 @@ func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, Dia
|
||||
PacketConnWrapper: c,
|
||||
Counter: counter,
|
||||
Handler: h,
|
||||
BlockedIPMatcher: blockedIPMatcher,
|
||||
UDPOverride: UDPOverride,
|
||||
ResolvedUDPAddr: resolvedUDPAddr,
|
||||
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
|
||||
@@ -341,7 +418,8 @@ type PacketWriter struct {
|
||||
*internet.PacketConnWrapper
|
||||
stats.Counter
|
||||
*Handler
|
||||
UDPOverride net.Destination
|
||||
BlockedIPMatcher geodata.IPMatcher
|
||||
UDPOverride net.Destination
|
||||
|
||||
// Dest of udp packets might be a domain, we will resolve them to IP
|
||||
// But resolver will return a random one if the domain has many IPs
|
||||
@@ -399,6 +477,10 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if isBlockedAddress(w.BlockedIPMatcher, b.UDP.Address) {
|
||||
b.Release()
|
||||
continue
|
||||
}
|
||||
destAddr := b.UDP.RawNetAddr()
|
||||
if destAddr == nil {
|
||||
b.Release()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -45,9 +45,11 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
|
||||
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
|
||||
inbound := session.InboundFromContext(ctx)
|
||||
if inbound == nil {
|
||||
inbound = &session.Inbound{}
|
||||
inbound := &session.Inbound{}
|
||||
originInbound := session.InboundFromContext(ctx)
|
||||
if originInbound != nil {
|
||||
// get a shallow copy to avoid modifying the inbound tag in upstream context
|
||||
*inbound = *originInbound
|
||||
}
|
||||
|
||||
inbound.Tag = l.config.InboundTag
|
||||
|
||||
@@ -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{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,9 +47,8 @@ func TestResolveIP(t *testing.T) {
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{127, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,625 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
type evalValue struct {
|
||||
bytes []byte
|
||||
u64 *uint64
|
||||
isBytes bool
|
||||
}
|
||||
|
||||
type evalContext struct {
|
||||
vars map[string][]byte
|
||||
metadata map[string]evalValue
|
||||
}
|
||||
|
||||
func newEvalContext() *evalContext {
|
||||
return &evalContext{
|
||||
vars: make(map[string][]byte),
|
||||
metadata: make(map[string]evalValue),
|
||||
}
|
||||
}
|
||||
|
||||
func newEvalContextWithAddrs(local, remote net.Addr) *evalContext {
|
||||
ctx := newEvalContext()
|
||||
loadMetadata(ctx.metadata, "local", local)
|
||||
loadMetadata(ctx.metadata, "remote", remote)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func evaluateUDPItems(items []*UDPItem) ([]byte, error) {
|
||||
return evaluateUDPItemsWithContext(items, newEvalContext())
|
||||
}
|
||||
|
||||
func evaluateUDPItemsWithContext(items []*UDPItem, ctx *evalContext) ([]byte, error) {
|
||||
var out []byte
|
||||
for _, item := range items {
|
||||
value, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, value...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func measureUDPItems(items []*UDPItem) (int, error) {
|
||||
return measureUDPItemsWithFallback(items, nil)
|
||||
}
|
||||
|
||||
func measureUDPItemsWithFallback(items []*UDPItem, fallback map[string]int) (int, error) {
|
||||
sizeCtx := make(map[string]int)
|
||||
for key, value := range fallback {
|
||||
sizeCtx[key] = value
|
||||
}
|
||||
total := 0
|
||||
for _, item := range items {
|
||||
itemSize, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeCtx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total += itemSize
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func collectSavedUDPSizes(items []*UDPItem) map[string]int {
|
||||
sizeCtx := make(map[string]int)
|
||||
for _, item := range items {
|
||||
itemSize, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeCtx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if item.Save != "" {
|
||||
sizeCtx[item.Save] = itemSize
|
||||
}
|
||||
}
|
||||
return sizeCtx
|
||||
}
|
||||
|
||||
func measureItem(randLen int32, packet []byte, save, varName string, expr *Expr, sizeCtx map[string]int) (int, error) {
|
||||
var size int
|
||||
switch {
|
||||
case randLen > 0:
|
||||
size = int(randLen)
|
||||
case len(packet) > 0:
|
||||
size = len(packet)
|
||||
case varName != "":
|
||||
length, ok := sizeCtx[varName]
|
||||
if !ok {
|
||||
return 0, errors.New("unknown variable: ", varName)
|
||||
}
|
||||
size = length
|
||||
case expr != nil:
|
||||
exprSize, err := measureExpr(expr, sizeCtx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
size = exprSize
|
||||
default:
|
||||
size = 0
|
||||
}
|
||||
|
||||
if save != "" {
|
||||
sizeCtx[save] = size
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func evaluateTCPSequence(sequence *TCPSequence) ([]byte, error) {
|
||||
ctx := newEvalContext()
|
||||
var out []byte
|
||||
for _, item := range sequence.Sequence {
|
||||
value, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, value...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func evaluateItem(randLen, randMin, randMax int32, packet []byte, save, varName string, expr *Expr, ctx *evalContext) ([]byte, error) {
|
||||
var value []byte
|
||||
switch {
|
||||
case randLen > 0:
|
||||
value = make([]byte, randLen)
|
||||
crypto.RandBytesBetween(value, byte(randMin), byte(randMax))
|
||||
case len(packet) > 0:
|
||||
value = append([]byte(nil), packet...)
|
||||
case varName != "":
|
||||
saved, ok := ctx.vars[varName]
|
||||
if !ok {
|
||||
return nil, errors.New("unknown variable: ", varName)
|
||||
}
|
||||
value = append([]byte(nil), saved...)
|
||||
case expr != nil:
|
||||
evaluated, err := evaluateExpr(expr, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytesValue, err := evaluated.asBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value = bytesValue
|
||||
default:
|
||||
value = nil
|
||||
}
|
||||
|
||||
if save != "" {
|
||||
ctx.vars[save] = append([]byte(nil), value...)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func evaluateExpr(expr *Expr, ctx *evalContext) (evalValue, error) {
|
||||
switch expr.GetOp() {
|
||||
case "concat":
|
||||
var out []byte
|
||||
for _, arg := range expr.GetArgs() {
|
||||
value, err := evaluateExprArg(arg, ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
bytesValue, err := value.asBytes()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
out = append(out, bytesValue...)
|
||||
}
|
||||
return evalValue{bytes: out, isBytes: true}, nil
|
||||
case "slice":
|
||||
if len(expr.GetArgs()) != 3 {
|
||||
return evalValue{}, errors.New("slice expects 3 args")
|
||||
}
|
||||
source, err := evaluateExprArg(expr.GetArgs()[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
offset, err := evaluateExprArg(expr.GetArgs()[1], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
length, err := evaluateExprArg(expr.GetArgs()[2], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
sourceBytes, err := source.asBytes()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
offsetU64, err := offset.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
lengthU64, err := length.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
end := offsetU64 + lengthU64
|
||||
if end > uint64(len(sourceBytes)) {
|
||||
return evalValue{}, errors.New("slice out of bounds")
|
||||
}
|
||||
return evalValue{bytes: append([]byte(nil), sourceBytes[offsetU64:end]...), isBytes: true}, nil
|
||||
case "xor16":
|
||||
return evaluateXor(expr.GetArgs(), 0xFFFF, 2, ctx)
|
||||
case "xor32":
|
||||
return evaluateXor(expr.GetArgs(), 0xFFFFFFFF, 4, ctx)
|
||||
case "be16":
|
||||
return evaluatePack(expr.GetArgs(), "be16", 2, binary.BigEndian, ctx)
|
||||
case "be32":
|
||||
return evaluatePack(expr.GetArgs(), "be32", 4, binary.BigEndian, ctx)
|
||||
case "le16":
|
||||
return evaluatePack(expr.GetArgs(), "le16", 2, binary.LittleEndian, ctx)
|
||||
case "le32":
|
||||
return evaluatePack(expr.GetArgs(), "le32", 4, binary.LittleEndian, ctx)
|
||||
case "le64":
|
||||
return evaluatePack(expr.GetArgs(), "le64", 8, binary.LittleEndian, ctx)
|
||||
case "pad":
|
||||
return evaluatePad(expr.GetArgs(), ctx)
|
||||
case "truncate":
|
||||
return evaluateTruncate(expr.GetArgs(), ctx)
|
||||
case "add":
|
||||
return evaluateBinaryU64Op(expr.GetArgs(), "add", ctx, func(left, right uint64) (uint64, error) {
|
||||
if left > ^uint64(0)-right {
|
||||
return 0, errors.New("add overflow")
|
||||
}
|
||||
return left + right, nil
|
||||
})
|
||||
case "sub":
|
||||
return evaluateBinaryU64Op(expr.GetArgs(), "sub", ctx, func(left, right uint64) (uint64, error) {
|
||||
if left < right {
|
||||
return 0, errors.New("sub underflow")
|
||||
}
|
||||
return left - right, nil
|
||||
})
|
||||
case "and":
|
||||
return evaluateBinaryU64Op(expr.GetArgs(), "and", ctx, func(left, right uint64) (uint64, error) {
|
||||
return left & right, nil
|
||||
})
|
||||
case "or":
|
||||
return evaluateBinaryU64Op(expr.GetArgs(), "or", ctx, func(left, right uint64) (uint64, error) {
|
||||
return left | right, nil
|
||||
})
|
||||
case "shl":
|
||||
return evaluateShift(expr.GetArgs(), "shl", ctx, func(value uint64, shift uint) (uint64, error) {
|
||||
if shift >= 64 {
|
||||
return 0, errors.New("shift out of range")
|
||||
}
|
||||
if value > (^uint64(0) >> shift) {
|
||||
return 0, errors.New("shl overflow")
|
||||
}
|
||||
return value << shift, nil
|
||||
})
|
||||
case "shr":
|
||||
return evaluateShift(expr.GetArgs(), "shr", ctx, func(value uint64, shift uint) (uint64, error) {
|
||||
if shift >= 64 {
|
||||
return 0, errors.New("shift out of range")
|
||||
}
|
||||
return value >> shift, nil
|
||||
})
|
||||
default:
|
||||
return evalValue{}, errors.New("unsupported expr op: ", expr.GetOp())
|
||||
}
|
||||
}
|
||||
|
||||
func evaluatePack(args []*ExprArg, name string, width int, order binary.ByteOrder, ctx *evalContext) (evalValue, error) {
|
||||
if len(args) != 1 {
|
||||
return evalValue{}, errors.New(name, " expects 1 arg")
|
||||
}
|
||||
value, err := evaluateExprArg(args[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
u64Value, err := value.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
|
||||
switch width {
|
||||
case 2:
|
||||
if u64Value > 0xFFFF {
|
||||
return evalValue{}, errors.New(name, " overflow")
|
||||
}
|
||||
out := make([]byte, 2)
|
||||
order.PutUint16(out, uint16(u64Value))
|
||||
return evalValue{bytes: out, isBytes: true}, nil
|
||||
case 4:
|
||||
if u64Value > 0xFFFFFFFF {
|
||||
return evalValue{}, errors.New(name, " overflow")
|
||||
}
|
||||
out := make([]byte, 4)
|
||||
order.PutUint32(out, uint32(u64Value))
|
||||
return evalValue{bytes: out, isBytes: true}, nil
|
||||
case 8:
|
||||
out := make([]byte, 8)
|
||||
order.PutUint64(out, u64Value)
|
||||
return evalValue{bytes: out, isBytes: true}, nil
|
||||
default:
|
||||
return evalValue{}, errors.New("unsupported pack width")
|
||||
}
|
||||
}
|
||||
|
||||
func evaluatePad(args []*ExprArg, ctx *evalContext) (evalValue, error) {
|
||||
if len(args) != 3 {
|
||||
return evalValue{}, errors.New("pad expects 3 args")
|
||||
}
|
||||
source, err := evaluateExprArg(args[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
target, err := evaluateExprArg(args[1], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
fill, err := evaluateExprArg(args[2], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
sourceBytes, err := source.asBytes()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
targetU64, err := target.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
fillBytes, err := fill.asBytes()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if len(fillBytes) == 0 {
|
||||
return evalValue{}, errors.New("pad fill must not be empty")
|
||||
}
|
||||
if targetU64 < uint64(len(sourceBytes)) {
|
||||
return evalValue{}, errors.New("pad target shorter than source")
|
||||
}
|
||||
|
||||
out := append([]byte(nil), sourceBytes...)
|
||||
for uint64(len(out)) < targetU64 {
|
||||
remaining := int(targetU64) - len(out)
|
||||
if remaining >= len(fillBytes) {
|
||||
out = append(out, fillBytes...)
|
||||
continue
|
||||
}
|
||||
out = append(out, fillBytes[:remaining]...)
|
||||
}
|
||||
return evalValue{bytes: out, isBytes: true}, nil
|
||||
}
|
||||
|
||||
func evaluateTruncate(args []*ExprArg, ctx *evalContext) (evalValue, error) {
|
||||
if len(args) != 2 {
|
||||
return evalValue{}, errors.New("truncate expects 2 args")
|
||||
}
|
||||
source, err := evaluateExprArg(args[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
length, err := evaluateExprArg(args[1], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
sourceBytes, err := source.asBytes()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
lengthU64, err := length.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if lengthU64 > uint64(len(sourceBytes)) {
|
||||
return evalValue{}, errors.New("truncate out of bounds")
|
||||
}
|
||||
return evalValue{bytes: append([]byte(nil), sourceBytes[:lengthU64]...), isBytes: true}, nil
|
||||
}
|
||||
|
||||
func evaluateBinaryU64Op(args []*ExprArg, name string, ctx *evalContext, op func(left, right uint64) (uint64, error)) (evalValue, error) {
|
||||
if len(args) != 2 {
|
||||
return evalValue{}, errors.New(name, " expects 2 args")
|
||||
}
|
||||
left, err := evaluateExprArg(args[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
right, err := evaluateExprArg(args[1], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
leftU64, err := left.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
rightU64, err := right.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
result, err := op(leftU64, rightU64)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
return evalValue{u64: &result}, nil
|
||||
}
|
||||
|
||||
func evaluateShift(args []*ExprArg, name string, ctx *evalContext, op func(value uint64, shift uint) (uint64, error)) (evalValue, error) {
|
||||
if len(args) != 2 {
|
||||
return evalValue{}, errors.New(name, " expects 2 args")
|
||||
}
|
||||
value, err := evaluateExprArg(args[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
shift, err := evaluateExprArg(args[1], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
valueU64, err := value.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
shiftU64, err := shift.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if shiftU64 >= 64 {
|
||||
return evalValue{}, errors.New("shift out of range")
|
||||
}
|
||||
result, err := op(valueU64, uint(shiftU64))
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
return evalValue{u64: &result}, nil
|
||||
}
|
||||
|
||||
func evaluateXor(args []*ExprArg, mask uint64, width int, ctx *evalContext) (evalValue, error) {
|
||||
if len(args) != 2 {
|
||||
return evalValue{}, errors.New("xor expects 2 args")
|
||||
}
|
||||
left, err := evaluateExprArg(args[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
right, err := evaluateExprArg(args[1], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
leftU64, err := left.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
rightU64, err := right.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if width == 2 && (leftU64 > 0xFFFF || rightU64 > 0xFFFF) {
|
||||
return evalValue{}, errors.New("xor16 overflow")
|
||||
}
|
||||
if width == 4 && (leftU64 > 0xFFFFFFFF || rightU64 > 0xFFFFFFFF) {
|
||||
return evalValue{}, errors.New("xor32 overflow")
|
||||
}
|
||||
result := (leftU64 ^ rightU64) & mask
|
||||
return evalValue{u64: &result}, nil
|
||||
}
|
||||
|
||||
func measureExpr(expr *Expr, sizeCtx map[string]int) (int, error) {
|
||||
switch expr.GetOp() {
|
||||
case "concat":
|
||||
total := 0
|
||||
for _, arg := range expr.GetArgs() {
|
||||
size, err := measureExprArg(arg, sizeCtx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total += size
|
||||
}
|
||||
return total, nil
|
||||
case "slice":
|
||||
if len(expr.GetArgs()) != 3 {
|
||||
return 0, errors.New("slice expects 3 args")
|
||||
}
|
||||
lengthArg := expr.GetArgs()[2]
|
||||
if value, ok := lengthArg.GetValue().(*ExprArg_U64); ok {
|
||||
return int(value.U64), nil
|
||||
}
|
||||
return 0, errors.New("slice length must be u64")
|
||||
case "be16":
|
||||
return 2, nil
|
||||
case "be32":
|
||||
return 4, nil
|
||||
case "le16":
|
||||
return 2, nil
|
||||
case "le32":
|
||||
return 4, nil
|
||||
case "le64":
|
||||
return 8, nil
|
||||
case "pad":
|
||||
if len(expr.GetArgs()) != 3 {
|
||||
return 0, errors.New("pad expects 3 args")
|
||||
}
|
||||
lengthArg := expr.GetArgs()[1]
|
||||
if value, ok := lengthArg.GetValue().(*ExprArg_U64); ok {
|
||||
return int(value.U64), nil
|
||||
}
|
||||
return 0, errors.New("pad length must be u64")
|
||||
case "truncate":
|
||||
if len(expr.GetArgs()) != 2 {
|
||||
return 0, errors.New("truncate expects 2 args")
|
||||
}
|
||||
lengthArg := expr.GetArgs()[1]
|
||||
if value, ok := lengthArg.GetValue().(*ExprArg_U64); ok {
|
||||
return int(value.U64), nil
|
||||
}
|
||||
return 0, errors.New("truncate length must be u64")
|
||||
default:
|
||||
return 0, errors.New("expr size is not bytes for op: ", expr.GetOp())
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateExprArg(arg *ExprArg, ctx *evalContext) (evalValue, error) {
|
||||
switch value := arg.GetValue().(type) {
|
||||
case *ExprArg_Bytes:
|
||||
return evalValue{bytes: append([]byte(nil), value.Bytes...), isBytes: true}, nil
|
||||
case *ExprArg_U64:
|
||||
return evalValue{u64: &value.U64}, nil
|
||||
case *ExprArg_Var:
|
||||
saved, ok := ctx.vars[value.Var]
|
||||
if !ok {
|
||||
return evalValue{}, errors.New("unknown variable: ", value.Var)
|
||||
}
|
||||
return evalValue{bytes: append([]byte(nil), saved...), isBytes: true}, nil
|
||||
case *ExprArg_Metadata:
|
||||
metadata, ok := ctx.metadata[value.Metadata]
|
||||
if !ok {
|
||||
return evalValue{}, errors.New("unknown metadata: ", value.Metadata)
|
||||
}
|
||||
return metadata, nil
|
||||
case *ExprArg_Expr:
|
||||
return evaluateExpr(value.Expr, ctx)
|
||||
default:
|
||||
return evalValue{}, errors.New("empty expr arg")
|
||||
}
|
||||
}
|
||||
|
||||
func measureExprArg(arg *ExprArg, sizeCtx map[string]int) (int, error) {
|
||||
switch value := arg.GetValue().(type) {
|
||||
case *ExprArg_Bytes:
|
||||
return len(value.Bytes), nil
|
||||
case *ExprArg_U64:
|
||||
return 0, errors.New("u64 arg has no byte width")
|
||||
case *ExprArg_Var:
|
||||
length, ok := sizeCtx[value.Var]
|
||||
if !ok {
|
||||
return 0, errors.New("unknown variable: ", value.Var)
|
||||
}
|
||||
return length, nil
|
||||
case *ExprArg_Metadata:
|
||||
return 0, errors.New("metadata not implemented: ", value.Metadata)
|
||||
case *ExprArg_Expr:
|
||||
return measureExpr(value.Expr, sizeCtx)
|
||||
default:
|
||||
return 0, errors.New("empty expr arg")
|
||||
}
|
||||
}
|
||||
|
||||
func (v evalValue) asBytes() ([]byte, error) {
|
||||
if v.isBytes {
|
||||
return append([]byte(nil), v.bytes...), nil
|
||||
}
|
||||
return nil, errors.New("expr value is not bytes")
|
||||
}
|
||||
|
||||
func (v evalValue) asU64() (uint64, error) {
|
||||
if v.u64 != nil {
|
||||
return *v.u64, nil
|
||||
}
|
||||
return 0, errors.New("expr value is not u64")
|
||||
}
|
||||
|
||||
func sizeMapFromEvalContext(ctx *evalContext) map[string]int {
|
||||
sizes := make(map[string]int, len(ctx.vars))
|
||||
for key, value := range ctx.vars {
|
||||
sizes[key] = len(value)
|
||||
}
|
||||
return sizes
|
||||
}
|
||||
|
||||
func loadMetadata(dst map[string]evalValue, prefix string, addr net.Addr) {
|
||||
if addr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch value := addr.(type) {
|
||||
case *net.UDPAddr:
|
||||
loadIPPortMetadata(dst, prefix, value.IP, value.Port)
|
||||
case *net.TCPAddr:
|
||||
loadIPPortMetadata(dst, prefix, value.IP, value.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func loadIPPortMetadata(dst map[string]evalValue, prefix string, ip net.IP, port int) {
|
||||
portValue := uint64(port)
|
||||
dst[prefix+"_port"] = evalValue{u64: &portValue}
|
||||
if prefix == "remote" {
|
||||
dst["src_port_u16"] = evalValue{u64: &portValue}
|
||||
} else if prefix == "local" {
|
||||
dst["dst_port_u16"] = evalValue{u64: &portValue}
|
||||
}
|
||||
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
ipValue := uint64(binary.BigEndian.Uint32(ip4))
|
||||
dst[prefix+"_ip4_u32"] = evalValue{u64: &ipValue}
|
||||
if prefix == "remote" {
|
||||
dst["src_ip4_u32"] = evalValue{u64: &ipValue}
|
||||
} else if prefix == "local" {
|
||||
dst["dst_ip4_u32"] = evalValue{u64: &ipValue}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvaluatorSaveAndReuseWithinPacket(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Rand: 4,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := bytes.Repeat([]byte{0x2A}, 8)
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorSliceReturnsWindow(t *testing.T) {
|
||||
sequence := &TCPSequence{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "slice",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
|
||||
{Value: &ExprArg_U64{U64: 1}},
|
||||
{Value: &ExprArg_U64{U64: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateTCPSequence(sequence)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(got, []byte{2, 3}) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorConcatPreservesOrder(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte("ab")}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte("cd")}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte("ef")}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(got) != "abcdef" {
|
||||
t.Fatalf("unexpected output: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorBeXorProducesExpectedBytes(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "xor16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 0x1234}},
|
||||
{Value: &ExprArg_U64{U64: 0xFFFF}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(got, []byte{0xED, 0xCB}) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorRejectsInvalidArgType(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0x01}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := evaluateUDPItems(items)
|
||||
if err == nil {
|
||||
t.Fatal("expected evaluator error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorLittleEndianProducesExpectedBytes(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "le16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 0x1234}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "le32",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 0xA1B2C3D4}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "le64",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 0x0102030405060708}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := []byte{
|
||||
0x34, 0x12,
|
||||
0xD4, 0xC3, 0xB2, 0xA1,
|
||||
0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorPadAndTruncateShapeBytes(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "pad",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xAA, 0xBB}}},
|
||||
{Value: &ExprArg_U64{U64: 5}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xCC, 0xDD}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "truncate",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
|
||||
{Value: &ExprArg_U64{U64: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xCC, 0x01, 0x02}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeasureUDPItemsSupportsPadAndTruncate(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "pad",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xAA}}},
|
||||
{Value: &ExprArg_U64{U64: 4}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0x00}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "truncate",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
|
||||
{Value: &ExprArg_U64{U64: 3}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := measureUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got != 7 {
|
||||
t.Fatalf("unexpected size: %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorArithmeticAndBitwiseProduceExpectedBytes(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "add",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 1}},
|
||||
{Value: &ExprArg_U64{U64: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "sub",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 10}},
|
||||
{Value: &ExprArg_U64{U64: 3}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "and",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 0xF0F0}},
|
||||
{Value: &ExprArg_U64{U64: 0x0FF0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "or",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "shl",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 1}},
|
||||
{Value: &ExprArg_U64{U64: 8}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "shr",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 0x80}},
|
||||
{Value: &ExprArg_U64{U64: 7}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := []byte{
|
||||
0x00, 0x03,
|
||||
0x00, 0x07,
|
||||
0x00, 0xF0,
|
||||
0x01, 0x01,
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorRejectsInvalidShapingAndArithmetic(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
items []*UDPItem
|
||||
match string
|
||||
}{
|
||||
{
|
||||
name: "pad with empty fill",
|
||||
items: []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "pad",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xAA}}},
|
||||
{Value: &ExprArg_U64{U64: 4}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
match: "pad fill",
|
||||
},
|
||||
{
|
||||
name: "truncate beyond source",
|
||||
items: []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "truncate",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2}}},
|
||||
{Value: &ExprArg_U64{U64: 3}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
match: "truncate",
|
||||
},
|
||||
{
|
||||
name: "sub underflow",
|
||||
items: []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "sub",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 1}},
|
||||
{Value: &ExprArg_U64{U64: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
match: "underflow",
|
||||
},
|
||||
{
|
||||
name: "shift too large",
|
||||
items: []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "shl",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 1}},
|
||||
{Value: &ExprArg_U64{U64: 64}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
match: "shift",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := evaluateUDPItems(tt.items)
|
||||
if err == nil {
|
||||
t.Fatal("expected evaluator error")
|
||||
}
|
||||
if !bytes.Contains([]byte(err.Error()), []byte(tt.match)) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||
)
|
||||
|
||||
func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "nope"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := evaluateUDPItemsWithContext(items, newEvalContext())
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown metadata") {
|
||||
t.Fatalf("expected unknown metadata error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataAliasesExposeSrcAndDstNames(t *testing.T) {
|
||||
ctx := newEvalContextWithAddrs(
|
||||
&net.UDPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 3478},
|
||||
&net.UDPAddr{IP: net.IPv4(203, 0, 113, 9), Port: 54321},
|
||||
)
|
||||
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "src_port_u16"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "be32",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "src_ip4_u32"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItemsWithContext(items, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := []byte{0xD4, 0x31, 203, 0, 113, 9}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("unexpected alias output: got=%x want=%x", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataAliasesExposeDstNames(t *testing.T) {
|
||||
ctx := newEvalContextWithAddrs(
|
||||
&net.UDPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 3478},
|
||||
&net.UDPAddr{IP: net.IPv4(203, 0, 113, 9), Port: 54321},
|
||||
)
|
||||
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "dst_port_u16"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "be32",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "dst_ip4_u32"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItemsWithContext(items, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := []byte{0x0D, 0x96, 10, 0, 0, 1}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("unexpected alias output: got=%x want=%x", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataUDPWriteUsesRemotePort(t *testing.T) {
|
||||
cfg := &UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg}).WrapPacketConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
payload := []byte("meta")
|
||||
if _, err := client.WriteTo(payload, serverRaw.LocalAddr()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wire := make([]byte, 64)
|
||||
_ = serverRaw.SetDeadline(time.Now().Add(time.Second))
|
||||
n, _, err := serverRaw.ReadFrom(wire)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(payload)+2 {
|
||||
t.Fatalf("unexpected wire size: %d", n)
|
||||
}
|
||||
wantPort := uint16(serverRaw.LocalAddr().(*net.UDPAddr).Port)
|
||||
if got := binary.BigEndian.Uint16(wire[:2]); got != wantPort {
|
||||
t.Fatalf("unexpected encoded port: got=%d want=%d", got, wantPort)
|
||||
}
|
||||
if !bytes.Equal(wire[2:n], payload) {
|
||||
t.Fatalf("unexpected payload: %q", wire[2:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataTCPHandshakeUsesEndpointPorts(t *testing.T) {
|
||||
clientCfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "local_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
serverCfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "local_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
serverRawCh := make(chan net.Conn, 1)
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
serverRawCh <- conn
|
||||
}()
|
||||
|
||||
clientRaw, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
var serverRaw net.Conn
|
||||
select {
|
||||
case serverRaw = <-serverRawCh:
|
||||
case err := <-errCh:
|
||||
t.Fatal(err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("accept timeout")
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := clientCfg.WrapConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := serverCfg.WrapConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := client.Write([]byte("meta"))
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
buf := make([]byte, 4)
|
||||
if _, err := io.ReadFull(server, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(buf, []byte("meta")) {
|
||||
t.Fatalf("unexpected payload: %q", buf)
|
||||
}
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stateEntry struct {
|
||||
vars map[string][]byte
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
type stateStore struct {
|
||||
mu sync.Mutex
|
||||
ttl time.Duration
|
||||
entries map[string]stateEntry
|
||||
}
|
||||
|
||||
func newStateStore(ttl time.Duration) *stateStore {
|
||||
return &stateStore{
|
||||
ttl: ttl,
|
||||
entries: make(map[string]stateEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stateStore) get(key string) (map[string][]byte, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
entry, ok := s.entries[key]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) {
|
||||
delete(s.entries, key)
|
||||
return nil, false
|
||||
}
|
||||
return cloneVars(entry.vars), true
|
||||
}
|
||||
|
||||
func (s *stateStore) set(key string, vars map[string][]byte) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.entries[key] = stateEntry{
|
||||
vars: cloneVars(vars),
|
||||
expiresAt: time.Now().Add(s.ttl),
|
||||
}
|
||||
}
|
||||
|
||||
func cloneVars(vars map[string][]byte) map[string][]byte {
|
||||
cloned := make(map[string][]byte, len(vars))
|
||||
for key, value := range vars {
|
||||
cloned[key] = append([]byte(nil), value...)
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||
)
|
||||
|
||||
func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) {
|
||||
t.Helper()
|
||||
|
||||
go func() {
|
||||
_, err := from.WriteTo(msg, to.LocalAddr())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, _, err := to.ReadFrom(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(msg) {
|
||||
t.Fatalf("unexpected size: %d", n)
|
||||
}
|
||||
if !bytes.Equal(buf[:n], msg) {
|
||||
t.Fatalf("unexpected payload: %q", buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
|
||||
cfg := &UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
},
|
||||
Server: []*UDPItem{
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
}
|
||||
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
|
||||
|
||||
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := maskManager.WrapPacketConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := maskManager.WrapPacketConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(2 * time.Second))
|
||||
|
||||
mustSendRecvUDP(t, client, server, []byte("client->server"))
|
||||
mustSendRecvUDP(t, server, client, []byte("server->client"))
|
||||
}
|
||||
|
||||
func TestStateStoreIsolatesKeys(t *testing.T) {
|
||||
store := newStateStore(5 * time.Second)
|
||||
store.set("a", map[string][]byte{"txid": {0x01}})
|
||||
store.set("b", map[string][]byte{"txid": {0x02}})
|
||||
|
||||
varsA, ok := store.get("a")
|
||||
if !ok || len(varsA["txid"]) != 1 || varsA["txid"][0] != 0x01 {
|
||||
t.Fatalf("unexpected vars for key a: %v", varsA)
|
||||
}
|
||||
varsB, ok := store.get("b")
|
||||
if !ok || len(varsB["txid"]) != 1 || varsB["txid"][0] != 0x02 {
|
||||
t.Fatalf("unexpected vars for key b: %v", varsB)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateStoreExpiresEntries(t *testing.T) {
|
||||
store := newStateStore(10 * time.Millisecond)
|
||||
store.set("a", map[string][]byte{"txid": {0x01}})
|
||||
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
|
||||
if _, ok := store.get("a"); ok {
|
||||
t.Fatal("expected expired state entry to be removed")
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
type tcpCustomClient struct {
|
||||
clients []*TCPSequence
|
||||
servers []*TCPSequence
|
||||
state *stateStore
|
||||
}
|
||||
|
||||
type tcpCustomClientConn struct {
|
||||
@@ -31,6 +32,7 @@ func NewConnClientTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
|
||||
header: &tcpCustomClient{
|
||||
clients: c.Clients,
|
||||
servers: c.Servers,
|
||||
state: newStateStore(5 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -63,16 +65,20 @@ func (c *tcpCustomClientConn) Read(p []byte) (n int, err error) {
|
||||
|
||||
func (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {
|
||||
c.once.Do(func() {
|
||||
ctx := newEvalContextWithAddrs(c.LocalAddr(), c.RemoteAddr())
|
||||
if vars, ok := c.header.state.get(tcpStateKey(c.LocalAddr(), c.RemoteAddr())); ok {
|
||||
ctx.vars = cloneVars(vars)
|
||||
}
|
||||
i := 0
|
||||
j := 0
|
||||
for i = range c.header.clients {
|
||||
if !writeSequence(c.Conn, c.header.clients[i]) {
|
||||
if !writeSequenceWithContext(c.Conn, c.header.clients[i], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
if j < len(c.header.servers) {
|
||||
if !readSequence(c.Conn, c.header.servers[j]) {
|
||||
if !readSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
@@ -81,13 +87,14 @@ func (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
for j < len(c.header.servers) {
|
||||
if !readSequence(c.Conn, c.header.servers[j]) {
|
||||
if !readSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
c.header.state.set(tcpStateKey(c.LocalAddr(), c.RemoteAddr()), ctx.vars)
|
||||
c.auth = true
|
||||
c.wg.Done()
|
||||
})
|
||||
@@ -105,6 +112,7 @@ type tcpCustomServer struct {
|
||||
clients []*TCPSequence
|
||||
servers []*TCPSequence
|
||||
errors []*TCPSequence
|
||||
state *stateStore
|
||||
}
|
||||
|
||||
type tcpCustomServerConn struct {
|
||||
@@ -123,6 +131,7 @@ func NewConnServerTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
|
||||
clients: c.Clients,
|
||||
servers: c.Servers,
|
||||
errors: c.Errors,
|
||||
state: newStateStore(5 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -145,19 +154,23 @@ func (c *tcpCustomServerConn) Splice() bool {
|
||||
|
||||
func (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {
|
||||
c.once.Do(func() {
|
||||
ctx := newEvalContextWithAddrs(c.LocalAddr(), c.RemoteAddr())
|
||||
if vars, ok := c.header.state.get(tcpStateKey(c.LocalAddr(), c.RemoteAddr())); ok {
|
||||
ctx.vars = cloneVars(vars)
|
||||
}
|
||||
i := 0
|
||||
j := 0
|
||||
for i = range c.header.clients {
|
||||
if !readSequence(c.Conn, c.header.clients[i]) {
|
||||
if !readSequenceWithContext(c.Conn, c.header.clients[i], ctx) {
|
||||
if i < len(c.header.errors) {
|
||||
writeSequence(c.Conn, c.header.errors[i])
|
||||
writeSequenceWithContext(c.Conn, c.header.errors[i], ctx)
|
||||
}
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
if j < len(c.header.servers) {
|
||||
if !writeSequence(c.Conn, c.header.servers[j]) {
|
||||
if !writeSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
@@ -166,13 +179,14 @@ func (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
for j < len(c.header.servers) {
|
||||
if !writeSequence(c.Conn, c.header.servers[j]) {
|
||||
if !writeSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
c.header.state.set(tcpStateKey(c.LocalAddr(), c.RemoteAddr()), ctx.vars)
|
||||
c.auth = true
|
||||
c.wg.Done()
|
||||
})
|
||||
@@ -197,24 +211,56 @@ func (c *tcpCustomServerConn) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func readSequence(r io.Reader, sequence *TCPSequence) bool {
|
||||
return readSequenceWithContext(r, sequence, newEvalContext())
|
||||
}
|
||||
|
||||
func readSequenceWithContext(r io.Reader, sequence *TCPSequence, ctx *evalContext) bool {
|
||||
for _, item := range sequence.Sequence {
|
||||
length := max(int(item.Rand), len(item.Packet))
|
||||
length, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeMapFromEvalContext(ctx))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
n, err := io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if item.Rand > 0 && n != length {
|
||||
if n != length {
|
||||
return false
|
||||
}
|
||||
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, buf[:n]) {
|
||||
return false
|
||||
switch {
|
||||
case item.Rand > 0:
|
||||
case len(item.Packet) > 0:
|
||||
if !bytes.Equal(item.Packet, buf[:n]) {
|
||||
return false
|
||||
}
|
||||
case item.Var != "":
|
||||
saved, ok := ctx.vars[item.Var]
|
||||
if !ok || !bytes.Equal(saved, buf[:n]) {
|
||||
return false
|
||||
}
|
||||
case item.Expr != nil:
|
||||
evaluated, err := evaluateExpr(item.Expr, ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
expected, err := evaluated.asBytes()
|
||||
if err != nil || !bytes.Equal(expected, buf[:n]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if item.Save != "" {
|
||||
ctx.vars[item.Save] = append([]byte(nil), buf[:n]...)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func writeSequence(w io.Writer, sequence *TCPSequence) bool {
|
||||
return writeSequenceWithContext(w, sequence, newEvalContext())
|
||||
}
|
||||
|
||||
func writeSequenceWithContext(w io.Writer, sequence *TCPSequence, ctx *evalContext) bool {
|
||||
var merged []byte
|
||||
for _, item := range sequence.Sequence {
|
||||
if item.DelayMax > 0 {
|
||||
@@ -227,13 +273,11 @@ func writeSequence(w io.Writer, sequence *TCPSequence) bool {
|
||||
}
|
||||
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
|
||||
}
|
||||
if item.Rand > 0 {
|
||||
buf := make([]byte, item.Rand)
|
||||
crypto.RandBytesBetween(buf, byte(item.RandMin), byte(item.RandMax))
|
||||
merged = append(merged, buf...)
|
||||
} else {
|
||||
merged = append(merged, item.Packet...)
|
||||
evaluated, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
merged = append(merged, evaluated...)
|
||||
}
|
||||
if len(merged) > 0 {
|
||||
_, err := w.Write(merged)
|
||||
@@ -244,3 +288,15 @@ func writeSequence(w io.Writer, sequence *TCPSequence) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func tcpStateKey(local, remote net.Addr) string {
|
||||
localKey := ""
|
||||
if local != nil {
|
||||
localKey = local.String()
|
||||
}
|
||||
remoteKey := ""
|
||||
if remote != nil {
|
||||
remoteKey = remote.String()
|
||||
}
|
||||
return localKey + "|" + remoteKey
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDSLTCPHandshakeReusesCapturedValue(t *testing.T) {
|
||||
cfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientRaw, serverRaw := net.Pipe()
|
||||
defer clientRaw.Close()
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := cfg.WrapConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := cfg.WrapConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := client.Write([]byte("payload"))
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
buf := make([]byte, len("payload"))
|
||||
if _, err := io.ReadFull(server, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(buf) != "payload" {
|
||||
t.Fatalf("unexpected payload: %q", buf)
|
||||
}
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSLTCPClientRejectsMismatchedResponseSequence(t *testing.T) {
|
||||
clientCfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
serverCfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
Save: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Packet: []byte{0x01, 0x02},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientRaw, serverRaw := net.Pipe()
|
||||
defer clientRaw.Close()
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := clientCfg.WrapConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := serverCfg.WrapConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := client.Write([]byte("payload"))
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
buf := make([]byte, len("payload"))
|
||||
_, readErr := server.Read(buf)
|
||||
|
||||
if err := <-writeErr; err == nil || !strings.Contains(err.Error(), "header auth failed") {
|
||||
t.Fatalf("expected client auth failure, got %v", err)
|
||||
}
|
||||
if readErr == nil {
|
||||
t.Fatal("expected server read to fail")
|
||||
}
|
||||
if ne, ok := readErr.(net.Error); !ok || !ne.Timeout() {
|
||||
t.Fatalf("expected server timeout after client auth failure, got %v", readErr)
|
||||
}
|
||||
}
|
||||
@@ -3,50 +3,46 @@ package custom
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
const udpStandaloneBufferSize = 4096
|
||||
|
||||
type udpCustomClient struct {
|
||||
client []*UDPItem
|
||||
server []*UDPItem
|
||||
merged []byte
|
||||
read int
|
||||
addr net.Addr
|
||||
state *stateStore
|
||||
vars map[string][]byte
|
||||
}
|
||||
|
||||
func (h *udpCustomClient) Serialize(b []byte) {
|
||||
index := 0
|
||||
for _, item := range h.client {
|
||||
if item.Rand > 0 {
|
||||
crypto.RandBytesBetween(h.merged[index:index+int(item.Rand)], byte(item.RandMin), byte(item.RandMax))
|
||||
index += int(item.Rand)
|
||||
} else {
|
||||
index += len(item.Packet)
|
||||
}
|
||||
evaluated, err := evaluateUDPItems(h.client)
|
||||
if err != nil || len(evaluated) != len(h.merged) {
|
||||
copy(b, h.merged)
|
||||
return
|
||||
}
|
||||
copy(b, h.merged)
|
||||
copy(b, evaluated)
|
||||
}
|
||||
|
||||
func (h *udpCustomClient) Match(b []byte) bool {
|
||||
if len(b) < len(h.merged) {
|
||||
return false
|
||||
var initial map[string][]byte
|
||||
if h.state != nil {
|
||||
initial, _ = h.state.get(udpStateKey(h.addr))
|
||||
}
|
||||
|
||||
data := b
|
||||
match := true
|
||||
|
||||
for _, item := range h.server {
|
||||
length := max(int(item.Rand), len(item.Packet))
|
||||
|
||||
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
|
||||
match = false
|
||||
break
|
||||
vars, ok := matchUDPItems(h.server, b, h.read, initial)
|
||||
if ok {
|
||||
h.vars = vars
|
||||
if h.state != nil {
|
||||
h.state.set(udpStateKey(h.addr), vars)
|
||||
}
|
||||
|
||||
data = data[length:]
|
||||
}
|
||||
|
||||
return match
|
||||
return ok
|
||||
}
|
||||
|
||||
type udpCustomClientConn struct {
|
||||
@@ -60,18 +56,19 @@ func NewConnClientUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error)
|
||||
header: &udpCustomClient{
|
||||
client: c.Client,
|
||||
server: c.Server,
|
||||
state: newStateStore(5 * time.Second),
|
||||
vars: make(map[string][]byte),
|
||||
},
|
||||
}
|
||||
|
||||
index := 0
|
||||
for _, item := range conn.header.client {
|
||||
if item.Rand > 0 {
|
||||
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
|
||||
index += int(item.Rand)
|
||||
} else {
|
||||
conn.header.merged = append(conn.header.merged, item.Packet...)
|
||||
index += len(item.Packet)
|
||||
}
|
||||
clientSavedSizes := collectSavedUDPSizes(conn.header.client)
|
||||
size, err := measureUDPItems(conn.header.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.header.merged = make([]byte, size)
|
||||
conn.header.read, err = measureUDPItemsWithFallback(conn.header.server, clientSavedSizes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
@@ -86,54 +83,69 @@ func (c *udpCustomClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err erro
|
||||
return 0, addr, errors.New("header mismatch")
|
||||
}
|
||||
|
||||
return len(p) - len(c.header.merged), addr, nil
|
||||
return len(p) - c.header.read, addr, nil
|
||||
}
|
||||
|
||||
func (c *udpCustomClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.header.Serialize(p)
|
||||
var localAddr net.Addr
|
||||
if c.PacketConn != nil {
|
||||
localAddr = c.PacketConn.LocalAddr()
|
||||
}
|
||||
ctx := newEvalContextWithAddrs(localAddr, addr)
|
||||
if vars, ok := c.header.state.get(udpStateKey(addr)); ok {
|
||||
ctx.vars = cloneVars(vars)
|
||||
} else if len(c.header.vars) > 0 {
|
||||
ctx.vars = cloneVars(c.header.vars)
|
||||
}
|
||||
evaluated, err := evaluateUDPItemsWithContext(c.header.client, ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(evaluated) != len(c.header.merged) {
|
||||
return 0, errors.New("header size mismatch")
|
||||
}
|
||||
c.header.state.set(udpStateKey(addr), ctx.vars)
|
||||
copy(p, evaluated)
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *udpCustomClientConn) SetReadAddr(addr net.Addr) {
|
||||
c.header.addr = addr
|
||||
}
|
||||
|
||||
type udpCustomServer struct {
|
||||
client []*UDPItem
|
||||
server []*UDPItem
|
||||
merged []byte
|
||||
read int
|
||||
addr net.Addr
|
||||
state *stateStore
|
||||
vars map[string][]byte
|
||||
}
|
||||
|
||||
func (h *udpCustomServer) Serialize(b []byte) {
|
||||
index := 0
|
||||
for _, item := range h.server {
|
||||
if item.Rand > 0 {
|
||||
crypto.RandBytesBetween(h.merged[index:index+int(item.Rand)], byte(item.RandMin), byte(item.RandMax))
|
||||
index += int(item.Rand)
|
||||
} else {
|
||||
index += len(item.Packet)
|
||||
}
|
||||
evaluated, err := evaluateUDPItems(h.server)
|
||||
if err != nil || len(evaluated) != len(h.merged) {
|
||||
copy(b, h.merged)
|
||||
return
|
||||
}
|
||||
copy(b, h.merged)
|
||||
copy(b, evaluated)
|
||||
}
|
||||
|
||||
func (h *udpCustomServer) Match(b []byte) bool {
|
||||
if len(b) < len(h.merged) {
|
||||
return false
|
||||
var initial map[string][]byte
|
||||
if h.state != nil {
|
||||
initial, _ = h.state.get(udpStateKey(h.addr))
|
||||
}
|
||||
|
||||
data := b
|
||||
match := true
|
||||
|
||||
for _, item := range h.client {
|
||||
length := max(int(item.Rand), len(item.Packet))
|
||||
|
||||
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
|
||||
match = false
|
||||
break
|
||||
vars, ok := matchUDPItems(h.client, b, h.read, initial)
|
||||
if ok {
|
||||
h.vars = vars
|
||||
if h.state != nil {
|
||||
h.state.set(udpStateKey(h.addr), vars)
|
||||
}
|
||||
|
||||
data = data[length:]
|
||||
}
|
||||
|
||||
return match
|
||||
return ok
|
||||
}
|
||||
|
||||
type udpCustomServerConn struct {
|
||||
@@ -147,18 +159,19 @@ func NewConnServerUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error)
|
||||
header: &udpCustomServer{
|
||||
client: c.Client,
|
||||
server: c.Server,
|
||||
state: newStateStore(5 * time.Second),
|
||||
vars: make(map[string][]byte),
|
||||
},
|
||||
}
|
||||
|
||||
index := 0
|
||||
for _, item := range conn.header.server {
|
||||
if item.Rand > 0 {
|
||||
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
|
||||
index += int(item.Rand)
|
||||
} else {
|
||||
conn.header.merged = append(conn.header.merged, item.Packet...)
|
||||
index += len(item.Packet)
|
||||
}
|
||||
clientSavedSizes := collectSavedUDPSizes(conn.header.client)
|
||||
size, err := measureUDPItemsWithFallback(conn.header.server, clientSavedSizes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.header.merged = make([]byte, size)
|
||||
conn.header.read, err = measureUDPItems(conn.header.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
@@ -173,11 +186,317 @@ func (c *udpCustomServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err erro
|
||||
return 0, addr, errors.New("header mismatch")
|
||||
}
|
||||
|
||||
return len(p) - len(c.header.merged), addr, nil
|
||||
return len(p) - c.header.read, addr, nil
|
||||
}
|
||||
|
||||
func (c *udpCustomServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.header.Serialize(p)
|
||||
var localAddr net.Addr
|
||||
if c.PacketConn != nil {
|
||||
localAddr = c.PacketConn.LocalAddr()
|
||||
}
|
||||
ctx := newEvalContextWithAddrs(localAddr, addr)
|
||||
if vars, ok := c.header.state.get(udpStateKey(addr)); ok {
|
||||
ctx.vars = cloneVars(vars)
|
||||
} else if len(c.header.vars) > 0 {
|
||||
ctx.vars = cloneVars(c.header.vars)
|
||||
}
|
||||
evaluated, err := evaluateUDPItemsWithContext(c.header.server, ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(evaluated) != len(c.header.merged) {
|
||||
return 0, errors.New("header size mismatch")
|
||||
}
|
||||
c.header.state.set(udpStateKey(addr), ctx.vars)
|
||||
copy(p, evaluated)
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *udpCustomServerConn) SetReadAddr(addr net.Addr) {
|
||||
c.header.addr = addr
|
||||
}
|
||||
|
||||
func matchUDPItems(items []*UDPItem, data []byte, totalSize int, initial map[string][]byte) (map[string][]byte, bool) {
|
||||
if len(data) < totalSize {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ctx := newEvalContext()
|
||||
ctx.vars = cloneVars(initial)
|
||||
offset := 0
|
||||
for _, item := range items {
|
||||
length, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeMapFromEvalContext(ctx))
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(data[offset:]) < length {
|
||||
return nil, false
|
||||
}
|
||||
segment := append([]byte(nil), data[offset:offset+length]...)
|
||||
switch {
|
||||
case item.Rand > 0:
|
||||
case len(item.Packet) > 0:
|
||||
if !bytes.Equal(item.Packet, segment) {
|
||||
return nil, false
|
||||
}
|
||||
case item.Var != "":
|
||||
saved, ok := ctx.vars[item.Var]
|
||||
if !ok || !bytes.Equal(saved, segment) {
|
||||
return nil, false
|
||||
}
|
||||
case item.Expr != nil:
|
||||
evaluated, err := evaluateExpr(item.Expr, ctx)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
expected, err := evaluated.asBytes()
|
||||
if err != nil || !bytes.Equal(expected, segment) {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if item.Save != "" {
|
||||
ctx.vars[item.Save] = segment
|
||||
}
|
||||
offset += length
|
||||
}
|
||||
|
||||
return ctx.vars, true
|
||||
}
|
||||
|
||||
func udpStateKey(addr net.Addr) string {
|
||||
if addr == nil {
|
||||
return ""
|
||||
}
|
||||
return addr.String()
|
||||
}
|
||||
|
||||
type udpCustomStandaloneClientConn struct {
|
||||
net.PacketConn
|
||||
client []*UDPItem
|
||||
server []*UDPItem
|
||||
state *stateStore
|
||||
read int
|
||||
mu sync.Mutex
|
||||
once sync.Once
|
||||
queue chan udpStandalonePacket
|
||||
wait map[string]*udpStandaloneWaiter
|
||||
}
|
||||
|
||||
type udpStandalonePacket struct {
|
||||
data []byte
|
||||
addr net.Addr
|
||||
err error
|
||||
}
|
||||
|
||||
type udpStandaloneWaiter struct {
|
||||
vars map[string][]byte
|
||||
done chan error
|
||||
}
|
||||
|
||||
func NewConnClientUDPStandalone(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
|
||||
clientSavedSizes := collectSavedUDPSizes(c.Client)
|
||||
read, err := measureUDPItemsWithFallback(c.Server, clientSavedSizes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &udpCustomStandaloneClientConn{
|
||||
PacketConn: raw,
|
||||
client: c.Client,
|
||||
server: c.Server,
|
||||
state: newStateStore(5 * time.Second),
|
||||
read: read,
|
||||
queue: make(chan udpStandalonePacket, 16),
|
||||
wait: make(map[string]*udpStandaloneWaiter),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
c.ensureReader()
|
||||
packet, ok := <-c.queue
|
||||
if !ok {
|
||||
return 0, nil, net.ErrClosed
|
||||
}
|
||||
if packet.err != nil {
|
||||
return 0, packet.addr, packet.err
|
||||
}
|
||||
if len(packet.data) > len(p) {
|
||||
copy(p, packet.data[:len(p)])
|
||||
return len(p), packet.addr, nil
|
||||
}
|
||||
copy(p, packet.data)
|
||||
return len(packet.data), packet.addr, nil
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.ensureReader()
|
||||
key := udpStateKey(addr)
|
||||
if _, ok := c.state.get(key); !ok {
|
||||
var localAddr net.Addr
|
||||
if c.PacketConn != nil {
|
||||
localAddr = c.PacketConn.LocalAddr()
|
||||
}
|
||||
|
||||
ctx := newEvalContextWithAddrs(localAddr, addr)
|
||||
request, err := evaluateUDPItemsWithContext(c.client, ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
waiter := c.registerWaiter(key, ctx.vars)
|
||||
if _, err := c.PacketConn.WriteTo(request, addr); err != nil {
|
||||
c.unregisterWaiter(key, waiter)
|
||||
return 0, err
|
||||
}
|
||||
if err := <-waiter.done; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return c.PacketConn.WriteTo(p, addr)
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneClientConn) ensureReader() {
|
||||
c.once.Do(func() {
|
||||
go c.readerLoop(c.queue)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneClientConn) registerWaiter(key string, vars map[string][]byte) *udpStandaloneWaiter {
|
||||
waiter := &udpStandaloneWaiter{
|
||||
vars: cloneVars(vars),
|
||||
done: make(chan error, 1),
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.wait[key] = waiter
|
||||
c.mu.Unlock()
|
||||
return waiter
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneClientConn) unregisterWaiter(key string, waiter *udpStandaloneWaiter) {
|
||||
c.mu.Lock()
|
||||
if c.wait[key] == waiter {
|
||||
delete(c.wait, key)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneClientConn) readerLoop(queue chan udpStandalonePacket) {
|
||||
buf := make([]byte, udpStandaloneBufferSize)
|
||||
for {
|
||||
n, addr, err := c.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
c.failWaiters(err)
|
||||
queue <- udpStandalonePacket{addr: addr, err: err}
|
||||
close(queue)
|
||||
return
|
||||
}
|
||||
data := append([]byte(nil), buf[:n]...)
|
||||
if c.tryCompleteHandshake(addr, data) {
|
||||
continue
|
||||
}
|
||||
queue <- udpStandalonePacket{data: data, addr: addr}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneClientConn) tryCompleteHandshake(addr net.Addr, data []byte) bool {
|
||||
key := udpStateKey(addr)
|
||||
c.mu.Lock()
|
||||
waiter, ok := c.wait[key]
|
||||
c.mu.Unlock()
|
||||
if !ok || len(data) != c.read {
|
||||
return false
|
||||
}
|
||||
|
||||
vars, matched := matchUDPItems(c.server, data, c.read, waiter.vars)
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
|
||||
c.state.set(key, vars)
|
||||
c.mu.Lock()
|
||||
if c.wait[key] == waiter {
|
||||
delete(c.wait, key)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
waiter.done <- nil
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneClientConn) failWaiters(err error) {
|
||||
c.mu.Lock()
|
||||
waiters := c.wait
|
||||
c.wait = make(map[string]*udpStandaloneWaiter)
|
||||
c.mu.Unlock()
|
||||
for _, waiter := range waiters {
|
||||
waiter.done <- err
|
||||
}
|
||||
}
|
||||
|
||||
type udpCustomStandaloneServerConn struct {
|
||||
net.PacketConn
|
||||
client []*UDPItem
|
||||
server []*UDPItem
|
||||
state *stateStore
|
||||
read int
|
||||
}
|
||||
|
||||
func NewConnServerUDPStandalone(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
|
||||
read, err := measureUDPItems(c.Client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &udpCustomStandaloneServerConn{
|
||||
PacketConn: raw,
|
||||
client: c.Client,
|
||||
server: c.Server,
|
||||
state: newStateStore(5 * time.Second),
|
||||
read: read,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
buf := p
|
||||
copyBack := false
|
||||
if len(buf) < udpStandaloneBufferSize {
|
||||
buf = make([]byte, udpStandaloneBufferSize)
|
||||
copyBack = true
|
||||
}
|
||||
|
||||
for {
|
||||
n, addr, err = c.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return 0, addr, err
|
||||
}
|
||||
if n == c.read {
|
||||
vars, ok := matchUDPItems(c.client, buf[:n], c.read, nil)
|
||||
if ok {
|
||||
var localAddr net.Addr
|
||||
if c.PacketConn != nil {
|
||||
localAddr = c.PacketConn.LocalAddr()
|
||||
}
|
||||
ctx := newEvalContextWithAddrs(localAddr, addr)
|
||||
ctx.vars = cloneVars(vars)
|
||||
response, err := evaluateUDPItemsWithContext(c.server, ctx)
|
||||
if err != nil {
|
||||
return 0, addr, err
|
||||
}
|
||||
if _, err := c.PacketConn.WriteTo(response, addr); err != nil {
|
||||
return 0, addr, err
|
||||
}
|
||||
c.state.set(udpStateKey(addr), ctx.vars)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if copyBack {
|
||||
copy(p, buf[:n])
|
||||
}
|
||||
return n, addr, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpCustomStandaloneServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return c.PacketConn.WriteTo(p, addr)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDSLUDPClientSizeTracksEvaluatedItems(t *testing.T) {
|
||||
conn, err := NewConnClientUDP(&UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xAB}}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xCD}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got := conn.(*udpCustomClientConn).Size(); got != 6 {
|
||||
t.Fatalf("unexpected header size: got=%d want=6", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSLUDPServerMatchCapturesSavedValues(t *testing.T) {
|
||||
conn, err := NewConnServerUDP(&UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
Save: "txid",
|
||||
},
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := conn.(*udpCustomServerConn)
|
||||
if !server.header.Match([]byte{0x01, 0x02, 0x01, 0x02}) {
|
||||
t.Fatal("expected packet to match")
|
||||
}
|
||||
|
||||
if got := string(server.header.vars["txid"]); got != string([]byte{0x01, 0x02}) {
|
||||
t.Fatalf("unexpected saved txid: %x", server.header.vars["txid"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSLUDPServerRejectsMalformedVarReference(t *testing.T) {
|
||||
conn, err := NewConnServerUDP(&UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
Save: "txid",
|
||||
},
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := conn.(*udpCustomServerConn)
|
||||
if server.header.Match([]byte{0x01, 0x02, 0x03, 0x04}) {
|
||||
t.Fatal("expected packet mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSLUDPClientWriteSupportsExtendedExprOps(t *testing.T) {
|
||||
conn, err := NewConnClientUDP(&UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "le16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "add",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 1}},
|
||||
{Value: &ExprArg_U64{U64: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "pad",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xAA}}},
|
||||
{Value: &ExprArg_U64{U64: 3}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xBB}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "truncate",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
|
||||
{Value: &ExprArg_U64{U64: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "or",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "shl",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 1}},
|
||||
{Value: &ExprArg_U64{U64: 8}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "shr",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 0x80}},
|
||||
{Value: &ExprArg_U64{U64: 7}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client := conn.(*udpCustomClientConn)
|
||||
buf := make([]byte, client.Size())
|
||||
if _, err := client.WriteTo(buf, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 53}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := []byte{
|
||||
0x03, 0x00,
|
||||
0xAA, 0xBB, 0xBB,
|
||||
0x01, 0x02,
|
||||
0x01, 0x01,
|
||||
}
|
||||
if !bytes.Equal(buf, want) {
|
||||
t.Fatalf("unexpected encoded header: %x", buf)
|
||||
}
|
||||
}
|
||||
@@ -278,7 +278,9 @@ func runVLESSRealityCase(t *testing.T, bin string, mode trafficMode, payloadSize
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},
|
||||
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
})},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -396,7 +398,9 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},
|
||||
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
})},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -512,7 +516,9 @@ func runVLesseEncCase(t *testing.T, bin string, mode trafficMode, payloadSize in
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},
|
||||
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
})},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -610,7 +616,9 @@ func runVLESSXHTTPCase(t *testing.T, bin string, mode trafficMode, payloadSize i
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},
|
||||
{ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
IpsBlocked: &freedom.IPRules{},
|
||||
})},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"math/big"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
@@ -90,18 +91,24 @@ func (c *UConn) HandshakeContextServerName(ctx context.Context) string {
|
||||
return c.ConnectionState().ServerName
|
||||
}
|
||||
|
||||
// WebsocketHandshake basically calls UConn.Handshake inside it but it will only send
|
||||
// http/1.1 in its ALPN.
|
||||
// WebsocketHandshakeContext basically calls UConn.Handshake inside it but it will try
|
||||
// to build outer ALPN to `http/1.1` or `h2 http/1.1` (if manually specified for camouflage)
|
||||
func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
|
||||
config := *utils.AccessField[*utls.Config](c, "config")
|
||||
ALPN := slices.Clone(config.NextProtos)
|
||||
// set other kinds of ALPN to http/1.1
|
||||
if !slices.Equal(ALPN, []string{"h2", "http/1.1"}) {
|
||||
ALPN = []string{"http/1.1"}
|
||||
}
|
||||
// Build the handshake state. This will apply every variable of the TLS of the
|
||||
// fingerprint in the UConn
|
||||
if err := c.BuildHandshakeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
config := *utils.AccessField[*utls.Config](c, "config")
|
||||
// Do not modify outer ALPN to http/1.1 if ECH is used
|
||||
// Outer ALPN will be h2,http/1.1, and real ALPN in config will be hidden in ECH
|
||||
// Do not modify outer ALPN if ECH is used
|
||||
// Outer ALPN will be h2,http/1.1, and real http/1.1 in config will be hidden in ECH
|
||||
if config.EncryptedClientHelloConfigList != nil {
|
||||
config.NextProtos = []string{"http/1.1"}
|
||||
return c.HandshakeContext(ctx)
|
||||
}
|
||||
// Iterate over extensions and check for utls.ALPNExtension
|
||||
@@ -109,12 +116,12 @@ func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
|
||||
for _, extension := range c.Extensions {
|
||||
if alpn, ok := extension.(*utls.ALPNExtension); ok {
|
||||
hasALPNExtension = true
|
||||
alpn.AlpnProtocols = []string{"http/1.1"}
|
||||
alpn.AlpnProtocols = ALPN
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasALPNExtension { // Append extension if doesn't exists
|
||||
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}})
|
||||
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: ALPN})
|
||||
}
|
||||
// Rebuild the client hello and do the handshake
|
||||
if err := c.BuildHandshakeState(); err != nil {
|
||||
@@ -146,9 +153,7 @@ func copyConfig(c *tls.Config) *utls.Config {
|
||||
VerifyPeerCertificate: c.VerifyPeerCertificate,
|
||||
KeyLogWriter: c.KeyLogWriter,
|
||||
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||
}
|
||||
if config.EncryptedClientHelloConfigList != nil {
|
||||
config.NextProtos = c.NextProtos
|
||||
NextProtos: c.NextProtos,
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -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