mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-22 22:58:03 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4add189c24 | ||
|
|
b4650360d6 | ||
|
|
d52f15060b | ||
|
|
31ab22c33d | ||
|
|
d42c981f9c | ||
|
|
cb1106c2fb | ||
|
|
df4b97097c | ||
|
|
a9cec25b8d |
+4
-4
@@ -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
|
||||
|
||||
+12
-68
@@ -548,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
|
||||
@@ -571,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}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -676,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}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -901,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}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -936,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}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -963,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}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -990,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}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+8
-12
@@ -17,7 +17,7 @@ import (
|
||||
type Holder struct {
|
||||
domainToIP cache.Lru
|
||||
ipRange *net.IPNet
|
||||
mu *sync.Mutex
|
||||
mu sync.Mutex
|
||||
|
||||
config *FakeDnsPool
|
||||
}
|
||||
@@ -49,9 +49,7 @@ func (fkdns *Holder) Start() error {
|
||||
}
|
||||
|
||||
func (fkdns *Holder) Close() error {
|
||||
fkdns.domainToIP = nil
|
||||
fkdns.ipRange = nil
|
||||
fkdns.mu = nil
|
||||
// nothing to do for now, just wait GC
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,7 +68,7 @@ func NewFakeDNSHolder() (*Holder, error) {
|
||||
}
|
||||
|
||||
func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {
|
||||
return &Holder{nil, nil, nil, conf}, nil
|
||||
return &Holder{config: conf}, nil
|
||||
}
|
||||
|
||||
func (fkdns *Holder) initializeFromConfig() error {
|
||||
@@ -92,7 +90,6 @@ func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
|
||||
}
|
||||
fkdns.domainToIP = cache.NewLru(lruSize)
|
||||
fkdns.ipRange = ipRange
|
||||
fkdns.mu = new(sync.Mutex)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -103,7 +100,7 @@ func (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address {
|
||||
if v, ok := fkdns.domainToIP.Get(domain); ok {
|
||||
return []net.Address{v.(net.Address)}
|
||||
}
|
||||
currentTimeMillis := uint64(time.Now().UnixNano() / 1e6)
|
||||
currentTimeMillis := uint64(time.Now().UnixMilli())
|
||||
ones, bits := fkdns.ipRange.Mask.Size()
|
||||
rooms := bits - ones
|
||||
if rooms < 64 {
|
||||
@@ -202,12 +199,11 @@ func (h *HolderMulti) Start() error {
|
||||
}
|
||||
|
||||
func (h *HolderMulti) Close() error {
|
||||
var errs []error
|
||||
for _, v := range h.holders {
|
||||
if err := v.Close(); err != nil {
|
||||
return errors.New("Cannot close all fake dns pools").Base(err)
|
||||
}
|
||||
errs = append(errs, v.Close())
|
||||
}
|
||||
return nil
|
||||
return errors.Combine(errs...)
|
||||
}
|
||||
|
||||
func (h *HolderMulti) createHolderGroups() error {
|
||||
@@ -222,7 +218,7 @@ func (h *HolderMulti) createHolderGroups() error {
|
||||
}
|
||||
|
||||
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
|
||||
holderMulti := &HolderMulti{nil, conf}
|
||||
holderMulti := &HolderMulti{config: conf}
|
||||
if err := holderMulti.createHolderGroups(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -48,3 +48,25 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
||||
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.
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 26
|
||||
Version_y byte = 4
|
||||
Version_z byte = 15
|
||||
Version_z byte = 17
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-26
@@ -290,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)
|
||||
@@ -309,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 {
|
||||
@@ -328,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,
|
||||
@@ -339,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
|
||||
@@ -346,30 +348,38 @@ 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
|
||||
@@ -468,10 +478,8 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
}
|
||||
}
|
||||
if isBlockedAddress(w.BlockedIPMatcher, b.UDP.Address) {
|
||||
blockedAddr := b.UDP.Address
|
||||
b.Release()
|
||||
buf.ReleaseMulti(mb)
|
||||
return errors.New("blocked target IP: ", blockedAddr).AtDebug()
|
||||
continue
|
||||
}
|
||||
destAddr := b.UDP.RawNetAddr()
|
||||
if destAddr == nil {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
)
|
||||
|
||||
type evalValue struct {
|
||||
bytes []byte
|
||||
u64 *uint64
|
||||
bytes []byte
|
||||
u64 *uint64
|
||||
isBytes bool
|
||||
}
|
||||
|
||||
type evalContext struct {
|
||||
@@ -175,7 +176,7 @@ func evaluateExpr(expr *Expr, ctx *evalContext) (evalValue, error) {
|
||||
}
|
||||
out = append(out, bytesValue...)
|
||||
}
|
||||
return evalValue{bytes: out}, nil
|
||||
return evalValue{bytes: out, isBytes: true}, nil
|
||||
case "slice":
|
||||
if len(expr.GetArgs()) != 3 {
|
||||
return evalValue{}, errors.New("slice expects 3 args")
|
||||
@@ -208,52 +209,236 @@ func evaluateExpr(expr *Expr, ctx *evalContext) (evalValue, error) {
|
||||
if end > uint64(len(sourceBytes)) {
|
||||
return evalValue{}, errors.New("slice out of bounds")
|
||||
}
|
||||
return evalValue{bytes: append([]byte(nil), sourceBytes[offsetU64:end]...)}, nil
|
||||
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":
|
||||
if len(expr.GetArgs()) != 1 {
|
||||
return evalValue{}, errors.New("be16 expects 1 arg")
|
||||
}
|
||||
value, err := evaluateExprArg(expr.GetArgs()[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
u64Value, err := value.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if u64Value > 0xFFFF {
|
||||
return evalValue{}, errors.New("be16 overflow")
|
||||
}
|
||||
out := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(out, uint16(u64Value))
|
||||
return evalValue{bytes: out}, nil
|
||||
return evaluatePack(expr.GetArgs(), "be16", 2, binary.BigEndian, ctx)
|
||||
case "be32":
|
||||
if len(expr.GetArgs()) != 1 {
|
||||
return evalValue{}, errors.New("be32 expects 1 arg")
|
||||
}
|
||||
value, err := evaluateExprArg(expr.GetArgs()[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
u64Value, err := value.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if u64Value > 0xFFFFFFFF {
|
||||
return evalValue{}, errors.New("be32 overflow")
|
||||
}
|
||||
out := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(out, uint32(u64Value))
|
||||
return evalValue{bytes: out}, nil
|
||||
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")
|
||||
@@ -309,6 +494,30 @@ func measureExpr(expr *Expr, sizeCtx map[string]int) (int, error) {
|
||||
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())
|
||||
}
|
||||
@@ -317,7 +526,7 @@ func measureExpr(expr *Expr, sizeCtx map[string]int) (int, error) {
|
||||
func evaluateExprArg(arg *ExprArg, ctx *evalContext) (evalValue, error) {
|
||||
switch value := arg.GetValue().(type) {
|
||||
case *ExprArg_Bytes:
|
||||
return evalValue{bytes: append([]byte(nil), value.Bytes...)}, nil
|
||||
return evalValue{bytes: append([]byte(nil), value.Bytes...), isBytes: true}, nil
|
||||
case *ExprArg_U64:
|
||||
return evalValue{u64: &value.U64}, nil
|
||||
case *ExprArg_Var:
|
||||
@@ -325,7 +534,7 @@ func evaluateExprArg(arg *ExprArg, ctx *evalContext) (evalValue, error) {
|
||||
if !ok {
|
||||
return evalValue{}, errors.New("unknown variable: ", value.Var)
|
||||
}
|
||||
return evalValue{bytes: append([]byte(nil), saved...)}, nil
|
||||
return evalValue{bytes: append([]byte(nil), saved...), isBytes: true}, nil
|
||||
case *ExprArg_Metadata:
|
||||
metadata, ok := ctx.metadata[value.Metadata]
|
||||
if !ok {
|
||||
@@ -361,7 +570,7 @@ func measureExprArg(arg *ExprArg, sizeCtx map[string]int) (int, error) {
|
||||
}
|
||||
|
||||
func (v evalValue) asBytes() ([]byte, error) {
|
||||
if v.bytes != nil {
|
||||
if v.isBytes {
|
||||
return append([]byte(nil), v.bytes...), nil
|
||||
}
|
||||
return nil, errors.New("expr value is not bytes")
|
||||
|
||||
@@ -128,3 +128,364 @@ func TestEvaluatorRejectsInvalidArgType(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package custom
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDSLUDPClientSizeTracksEvaluatedItems(t *testing.T) {
|
||||
conn, err := NewConnClientUDP(&UDPConfig{
|
||||
@@ -81,3 +85,104 @@ func TestDSLUDPServerRejectsMalformedVarReference(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user