mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-12-18 21:24:37 +03:00
Compare commits
48 Commits
dev/codeql
...
dns-geo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b85eef0131 | ||
|
|
a3e63e6928 | ||
|
|
ec9f19039e | ||
|
|
ee682b17d1 | ||
|
|
2e56fe11e1 | ||
|
|
7b7084f825 | ||
|
|
a5b2a9be2d | ||
|
|
d5a195458b | ||
|
|
f8b8e8a53a | ||
|
|
7cf30d5101 | ||
|
|
598e15aed2 | ||
|
|
708ce026ca | ||
|
|
b0a66e650c | ||
|
|
217844cc37 | ||
|
|
f20c445974 | ||
|
|
6e902b24ae | ||
|
|
70b63e21a5 | ||
|
|
726a722019 | ||
|
|
16017304b8 | ||
|
|
9721ff7a56 | ||
|
|
eeb40c9ce2 | ||
|
|
095b17da62 | ||
|
|
1c3abb2ec3 | ||
|
|
5d5beb2028 | ||
|
|
44317bd657 | ||
|
|
68201a8898 | ||
|
|
8a7cd65fc2 | ||
|
|
f4a048aa0c | ||
|
|
946a7a2647 | ||
|
|
90c81e8459 | ||
|
|
364086c974 | ||
|
|
7a778d74d0 | ||
|
|
d3533abe3c | ||
|
|
b9c5de96e7 | ||
|
|
b8196a22a0 | ||
|
|
6471cfd290 | ||
|
|
58bca70dfb | ||
|
|
4eb3acb4dd | ||
|
|
fc8b580017 | ||
|
|
06fc82bad1 | ||
|
|
14189eba07 | ||
|
|
b11429eaee | ||
|
|
58daa2f788 | ||
|
|
8382b29922 | ||
|
|
41d3f31447 | ||
|
|
9b93b90fa9 | ||
|
|
b15fffaac6 | ||
|
|
8884e948fe |
@@ -178,8 +178,8 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
|||||||
|
|
||||||
func shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
|
func shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
|
||||||
domain := result.Domain()
|
domain := result.Domain()
|
||||||
for _, d := range request.ExcludeForDomain {
|
if request.ExcludedDomainMatcher != nil {
|
||||||
if domain == d {
|
if request.ExcludedDomainMatcher.ApplyDomain(domain) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,6 +205,16 @@ func shouldOverride(ctx context.Context, result SniffResult, request session.Sni
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func canSniff(ctx context.Context, req session.SniffingRequest, destination net.Destination) bool {
|
||||||
|
if destination.Address.Family().IsIP() && req.ExcludedIPMatcher != nil {
|
||||||
|
if req.ExcludedIPMatcher.MatchIP(destination.Address.IP()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch implements routing.Dispatcher.
|
// Dispatch implements routing.Dispatcher.
|
||||||
func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) {
|
func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) {
|
||||||
if !destination.IsValid() {
|
if !destination.IsValid() {
|
||||||
@@ -222,6 +232,12 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
|||||||
ctx = session.ContextWithContent(ctx, content)
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
}
|
}
|
||||||
sniffingRequest := content.SniffingRequest
|
sniffingRequest := content.SniffingRequest
|
||||||
|
|
||||||
|
if !canSniff(ctx, sniffingRequest, destination) {
|
||||||
|
newError("skip sniffing for ip ", destination.Address.String()).AtDebug().WriteToLog()
|
||||||
|
sniffingRequest.Enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case !sniffingRequest.Enabled:
|
case !sniffingRequest.Enabled:
|
||||||
go d.routedDispatch(ctx, outbound, destination)
|
go d.routedDispatch(ctx, outbound, destination)
|
||||||
@@ -309,15 +325,10 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni
|
|||||||
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
||||||
var handler outbound.Handler
|
var handler outbound.Handler
|
||||||
|
|
||||||
skipRoutePick := false
|
|
||||||
if content := session.ContentFromContext(ctx); content != nil {
|
|
||||||
skipRoutePick = content.SkipRoutePick
|
|
||||||
}
|
|
||||||
|
|
||||||
routingLink := routing_session.AsRoutingContext(ctx)
|
routingLink := routing_session.AsRoutingContext(ctx)
|
||||||
inTag := routingLink.GetInboundTag()
|
inTag := routingLink.GetInboundTag()
|
||||||
isPickRoute := false
|
isPickRoute := false
|
||||||
if d.router != nil && !skipRoutePick {
|
if d.router != nil {
|
||||||
if route, err := d.router.PickRoute(routingLink); err == nil {
|
if route, err := d.router.PickRoute(routingLink); err == nil {
|
||||||
outTag := route.GetOutboundTag()
|
outTag := route.GetOutboundTag()
|
||||||
isPickRoute = true
|
isPickRoute = true
|
||||||
|
|||||||
64
app/dns/config.go
Normal file
64
app/dns/config.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
dm "github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/str"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var typeMap = map[dm.MatchingType]str.Type{
|
||||||
|
dm.MatchingType_Keyword: str.Substr,
|
||||||
|
dm.MatchingType_Regex: str.Regex,
|
||||||
|
dm.MatchingType_Subdomain: str.Domain,
|
||||||
|
dm.MatchingType_Full: str.Full,
|
||||||
|
}
|
||||||
|
|
||||||
|
// References:
|
||||||
|
// https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml
|
||||||
|
// https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan
|
||||||
|
var localTLDsAndDotlessDomains = []*dm.Domain{
|
||||||
|
{Type: dm.MatchingType_Regex, Value: "^[^.]+$"}, // This will only match domains without any dot
|
||||||
|
{Type: dm.MatchingType_Subdomain, Value: "local"},
|
||||||
|
{Type: dm.MatchingType_Subdomain, Value: "localdomain"},
|
||||||
|
{Type: dm.MatchingType_Subdomain, Value: "localhost"},
|
||||||
|
{Type: dm.MatchingType_Subdomain, Value: "lan"},
|
||||||
|
{Type: dm.MatchingType_Subdomain, Value: "home.arpa"},
|
||||||
|
{Type: dm.MatchingType_Subdomain, Value: "example"},
|
||||||
|
{Type: dm.MatchingType_Subdomain, Value: "invalid"},
|
||||||
|
{Type: dm.MatchingType_Subdomain, Value: "test"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{
|
||||||
|
Rule: "geosite:private",
|
||||||
|
Size: uint32(len(localTLDsAndDotlessDomains)),
|
||||||
|
}
|
||||||
|
|
||||||
|
func toStrMatcher(t dm.MatchingType, domain string) (str.Matcher, error) {
|
||||||
|
strMType, f := typeMap[t]
|
||||||
|
if !f {
|
||||||
|
return nil, newError("unknown mapping type", t).AtWarning()
|
||||||
|
}
|
||||||
|
matcher, err := strMType.New(domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create str matcher").Base(err)
|
||||||
|
}
|
||||||
|
return matcher, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNetIP(addrs []net.Address) ([]net.IP, error) {
|
||||||
|
ips := make([]net.IP, 0, len(addrs))
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if addr.Family().IsIP() {
|
||||||
|
ips = append(ips, addr.IP())
|
||||||
|
} else {
|
||||||
|
return nil, newError("Failed to convert address", addr, "to Net IP.").AtWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomTag() string {
|
||||||
|
id := uuid.New()
|
||||||
|
return "xray.system." + id.String()
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.14.0
|
// protoc v3.15.8
|
||||||
// source: app/dns/config.proto
|
// source: app/dns/config.proto
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
domain "github.com/xtls/xray-core/common/matcher/domain"
|
||||||
router "github.com/xtls/xray-core/app/router"
|
geoip "github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
net "github.com/xtls/xray-core/common/net"
|
net "github.com/xtls/xray-core/common/net"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
@@ -23,71 +23,115 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
type QueryStrategy int32
|
||||||
// of the legacy proto package is being used.
|
|
||||||
const _ = proto.ProtoPackageIsVersion4
|
|
||||||
|
|
||||||
type DomainMatchingType int32
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DomainMatchingType_Full DomainMatchingType = 0
|
QueryStrategy_USE_IP QueryStrategy = 0
|
||||||
DomainMatchingType_Subdomain DomainMatchingType = 1
|
QueryStrategy_USE_IP4 QueryStrategy = 1
|
||||||
DomainMatchingType_Keyword DomainMatchingType = 2
|
QueryStrategy_USE_IP6 QueryStrategy = 2
|
||||||
DomainMatchingType_Regex DomainMatchingType = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for DomainMatchingType.
|
// Enum value maps for QueryStrategy.
|
||||||
var (
|
var (
|
||||||
DomainMatchingType_name = map[int32]string{
|
QueryStrategy_name = map[int32]string{
|
||||||
0: "Full",
|
0: "USE_IP",
|
||||||
1: "Subdomain",
|
1: "USE_IP4",
|
||||||
2: "Keyword",
|
2: "USE_IP6",
|
||||||
3: "Regex",
|
|
||||||
}
|
}
|
||||||
DomainMatchingType_value = map[string]int32{
|
QueryStrategy_value = map[string]int32{
|
||||||
"Full": 0,
|
"USE_IP": 0,
|
||||||
"Subdomain": 1,
|
"USE_IP4": 1,
|
||||||
"Keyword": 2,
|
"USE_IP6": 2,
|
||||||
"Regex": 3,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (x DomainMatchingType) Enum() *DomainMatchingType {
|
func (x QueryStrategy) Enum() *QueryStrategy {
|
||||||
p := new(DomainMatchingType)
|
p := new(QueryStrategy)
|
||||||
*p = x
|
*p = x
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x DomainMatchingType) String() string {
|
func (x QueryStrategy) String() string {
|
||||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (DomainMatchingType) Descriptor() protoreflect.EnumDescriptor {
|
func (QueryStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_app_dns_config_proto_enumTypes[0].Descriptor()
|
return file_app_dns_config_proto_enumTypes[0].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (DomainMatchingType) Type() protoreflect.EnumType {
|
func (QueryStrategy) Type() protoreflect.EnumType {
|
||||||
return &file_app_dns_config_proto_enumTypes[0]
|
return &file_app_dns_config_proto_enumTypes[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x DomainMatchingType) Number() protoreflect.EnumNumber {
|
func (x QueryStrategy) Number() protoreflect.EnumNumber {
|
||||||
return protoreflect.EnumNumber(x)
|
return protoreflect.EnumNumber(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use DomainMatchingType.Descriptor instead.
|
// Deprecated: Use QueryStrategy.Descriptor instead.
|
||||||
func (DomainMatchingType) EnumDescriptor() ([]byte, []int) {
|
func (QueryStrategy) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CacheStrategy int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
CacheStrategy_Cache_ALL CacheStrategy = 0
|
||||||
|
CacheStrategy_Cache_NOERROR CacheStrategy = 1
|
||||||
|
CacheStrategy_Cache_DISABLE CacheStrategy = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for CacheStrategy.
|
||||||
|
var (
|
||||||
|
CacheStrategy_name = map[int32]string{
|
||||||
|
0: "Cache_ALL",
|
||||||
|
1: "Cache_NOERROR",
|
||||||
|
2: "Cache_DISABLE",
|
||||||
|
}
|
||||||
|
CacheStrategy_value = map[string]int32{
|
||||||
|
"Cache_ALL": 0,
|
||||||
|
"Cache_NOERROR": 1,
|
||||||
|
"Cache_DISABLE": 2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x CacheStrategy) Enum() *CacheStrategy {
|
||||||
|
p := new(CacheStrategy)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x CacheStrategy) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CacheStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_app_dns_config_proto_enumTypes[1].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CacheStrategy) Type() protoreflect.EnumType {
|
||||||
|
return &file_app_dns_config_proto_enumTypes[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x CacheStrategy) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CacheStrategy.Descriptor instead.
|
||||||
|
func (CacheStrategy) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
type NameServer struct {
|
type NameServer struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||||
PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
|
ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
|
||||||
Geoip []*router.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
|
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
|
||||||
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
PrioritizedDomain []*domain.Domain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
|
||||||
|
Geoip []*geoip.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
|
||||||
|
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServer) Reset() {
|
func (x *NameServer) Reset() {
|
||||||
@@ -129,14 +173,28 @@ func (x *NameServer) GetAddress() *net.Endpoint {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
|
func (x *NameServer) GetClientIp() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.ClientIp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetSkipFallback() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.SkipFallback
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetPrioritizedDomain() []*domain.Domain {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.PrioritizedDomain
|
return x.PrioritizedDomain
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServer) GetGeoip() []*router.GeoIP {
|
func (x *NameServer) GetGeoip() []*geoip.GeoIP {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Geoip
|
return x.Geoip
|
||||||
}
|
}
|
||||||
@@ -174,6 +232,10 @@ type Config struct {
|
|||||||
StaticHosts []*Config_HostMapping `protobuf:"bytes,4,rep,name=static_hosts,json=staticHosts,proto3" json:"static_hosts,omitempty"`
|
StaticHosts []*Config_HostMapping `protobuf:"bytes,4,rep,name=static_hosts,json=staticHosts,proto3" json:"static_hosts,omitempty"`
|
||||||
// Tag is the inbound tag of DNS client.
|
// Tag is the inbound tag of DNS client.
|
||||||
Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
|
Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||||
|
// DisableCache disables DNS cache
|
||||||
|
CacheStrategy CacheStrategy `protobuf:"varint,8,opt,name=cache_strategy,json=cacheStrategy,proto3,enum=xray.app.dns.CacheStrategy" json:"cache_strategy,omitempty"`
|
||||||
|
QueryStrategy QueryStrategy `protobuf:"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||||
|
DisableFallback bool `protobuf:"varint,10,opt,name=disableFallback,proto3" json:"disableFallback,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
@@ -252,59 +314,25 @@ func (x *Config) GetTag() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type NameServer_PriorityDomain struct {
|
func (x *Config) GetCacheStrategy() CacheStrategy {
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Type DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"`
|
|
||||||
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *NameServer_PriorityDomain) Reset() {
|
|
||||||
*x = NameServer_PriorityDomain{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *NameServer_PriorityDomain) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*NameServer_PriorityDomain) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *NameServer_PriorityDomain) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use NameServer_PriorityDomain.ProtoReflect.Descriptor instead.
|
|
||||||
func (*NameServer_PriorityDomain) Descriptor() ([]byte, []int) {
|
|
||||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *NameServer_PriorityDomain) GetType() DomainMatchingType {
|
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Type
|
return x.CacheStrategy
|
||||||
}
|
}
|
||||||
return DomainMatchingType_Full
|
return CacheStrategy_Cache_ALL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServer_PriorityDomain) GetDomain() string {
|
func (x *Config) GetQueryStrategy() QueryStrategy {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Domain
|
return x.QueryStrategy
|
||||||
}
|
}
|
||||||
return ""
|
return QueryStrategy_USE_IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetDisableFallback() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisableFallback
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type NameServer_OriginalRule struct {
|
type NameServer_OriginalRule struct {
|
||||||
@@ -319,7 +347,7 @@ type NameServer_OriginalRule struct {
|
|||||||
func (x *NameServer_OriginalRule) Reset() {
|
func (x *NameServer_OriginalRule) Reset() {
|
||||||
*x = NameServer_OriginalRule{}
|
*x = NameServer_OriginalRule{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_app_dns_config_proto_msgTypes[3]
|
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -332,7 +360,7 @@ func (x *NameServer_OriginalRule) String() string {
|
|||||||
func (*NameServer_OriginalRule) ProtoMessage() {}
|
func (*NameServer_OriginalRule) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
|
func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_app_dns_config_proto_msgTypes[3]
|
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -345,7 +373,7 @@ func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use NameServer_OriginalRule.ProtoReflect.Descriptor instead.
|
// Deprecated: Use NameServer_OriginalRule.ProtoReflect.Descriptor instead.
|
||||||
func (*NameServer_OriginalRule) Descriptor() ([]byte, []int) {
|
func (*NameServer_OriginalRule) Descriptor() ([]byte, []int) {
|
||||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0, 1}
|
return file_app_dns_config_proto_rawDescGZIP(), []int{0, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServer_OriginalRule) GetRule() string {
|
func (x *NameServer_OriginalRule) GetRule() string {
|
||||||
@@ -367,19 +395,18 @@ type Config_HostMapping struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Type DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"`
|
Type domain.MatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.common.matcher.domain.MatchingType" json:"type,omitempty"`
|
||||||
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
Ip [][]byte `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
|
Ip [][]byte `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||||
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
||||||
// domain. Xray will use this domain for IP queries. This field is only
|
// domain. Xray will use this domain for IP queries.
|
||||||
// effective if ip is empty.
|
|
||||||
ProxiedDomain string `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"`
|
ProxiedDomain string `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config_HostMapping) Reset() {
|
func (x *Config_HostMapping) Reset() {
|
||||||
*x = Config_HostMapping{}
|
*x = Config_HostMapping{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_app_dns_config_proto_msgTypes[5]
|
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -392,7 +419,7 @@ func (x *Config_HostMapping) String() string {
|
|||||||
func (*Config_HostMapping) ProtoMessage() {}
|
func (*Config_HostMapping) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Config_HostMapping) ProtoReflect() protoreflect.Message {
|
func (x *Config_HostMapping) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_app_dns_config_proto_msgTypes[5]
|
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -408,11 +435,11 @@ func (*Config_HostMapping) Descriptor() ([]byte, []int) {
|
|||||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1, 1}
|
return file_app_dns_config_proto_rawDescGZIP(), []int{1, 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config_HostMapping) GetType() DomainMatchingType {
|
func (x *Config_HostMapping) GetType() domain.MatchingType {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Type
|
return x.Type
|
||||||
}
|
}
|
||||||
return DomainMatchingType_Full
|
return domain.MatchingType_Full
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config_HostMapping) GetDomain() string {
|
func (x *Config_HostMapping) GetDomain() string {
|
||||||
@@ -444,79 +471,96 @@ var file_app_dns_config_proto_rawDesc = []byte{
|
|||||||
0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
|
0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
|
||||||
0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c,
|
0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c,
|
||||||
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69,
|
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69,
|
||||||
0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70,
|
0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x22, 0x63, 0x6f,
|
||||||
0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
|
0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x64, 0x6f, 0x6d,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xad, 0x03, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
0x61, 0x69, 0x6e, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,
|
0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
0x74, 0x6f, 0x22, 0x93, 0x03, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||||
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x56, 0x0a, 0x12, 0x70, 0x72, 0x69,
|
0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01,
|
||||||
0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||||
0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x07, 0x61,
|
||||||
0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
|
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||||
0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11,
|
0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||||
0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
0x74, 0x49, 0x70, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x61, 0x6c, 0x6c, 0x62,
|
||||||
0x6e, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
|
0x61, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46,
|
||||||
0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x51, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x6f, 0x72,
|
||||||
0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12,
|
0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20,
|
||||||
0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65,
|
0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
|
||||||
0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61,
|
0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
|
0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74,
|
||||||
0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d,
|
0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x36, 0x0a, 0x05, 0x67, 0x65,
|
||||||
0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x5e, 0x0a,
|
0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||||
0x0e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
|
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e,
|
||||||
0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e,
|
0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f,
|
||||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d,
|
0x69, 0x70, 0x12, 0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72,
|
||||||
0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52,
|
0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61,
|
||||||
|
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
||||||
|
0x72, 0x76, 0x65, 0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c,
|
||||||
|
0x65, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73,
|
||||||
|
0x1a, 0x36, 0x0a, 0x0c, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65,
|
||||||
|
0x12, 0x12, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||||
|
0x72, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
|
0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0xdf, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
||||||
|
0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||||
|
0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
|
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f,
|
||||||
|
0x69, 0x6e, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x65, 0x72, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72,
|
||||||
|
0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||||
|
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12,
|
||||||
|
0x39, 0x0a, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,
|
||||||
|
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f,
|
||||||
|
0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42,
|
||||||
|
0x02, 0x18, 0x01, 0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c,
|
||||||
|
0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63,
|
||||||
|
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69,
|
||||||
|
0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e,
|
||||||
|
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e,
|
||||||
|
0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52,
|
||||||
|
0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03,
|
||||||
|
0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x42,
|
||||||
|
0x0a, 0x0e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||||
|
0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||||
|
0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74,
|
||||||
|
0x65, 0x67, 0x79, 0x52, 0x0d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
|
||||||
|
0x67, 0x79, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61,
|
||||||
|
0x74, 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61,
|
||||||
|
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53,
|
||||||
|
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74,
|
||||||
|
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c,
|
||||||
|
0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
|
0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b,
|
||||||
|
0x1a, 0x55, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
|
||||||
|
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||||
|
0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
|
0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65,
|
||||||
|
0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61,
|
||||||
|
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x9a, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74,
|
||||||
|
0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
|
||||||
|
0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
|
0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61,
|
||||||
|
0x69, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52,
|
||||||
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
||||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x36, 0x0a,
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a,
|
||||||
0x0c, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a,
|
0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a,
|
||||||
0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c,
|
0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
||||||
0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f,
|
||||||
0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x9f, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x35, 0x0a, 0x0d, 0x51, 0x75,
|
||||||
0x12, 0x3f, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18,
|
0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x55,
|
||||||
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49,
|
||||||
0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10,
|
||||||
0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
0x02, 0x2a, 0x44, 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
|
||||||
0x73, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
0x67, 0x79, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x41, 0x4c, 0x4c, 0x10,
|
||||||
0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
0x00, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x4e, 0x4f, 0x45, 0x52, 0x52,
|
||||||
0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x44, 0x49,
|
||||||
0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x05,
|
0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x02, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
|
||||||
0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72,
|
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 0x67,
|
||||||
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78,
|
||||||
0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x02, 0x18, 0x01,
|
0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73,
|
||||||
0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62,
|
||||||
0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65,
|
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
0x6e, 0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68,
|
|
||||||
0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
|
|
||||||
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
|
||||||
0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74,
|
|
||||||
0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67,
|
|
||||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x1a, 0x55, 0x0a, 0x0a, 0x48,
|
|
||||||
0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
|
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76,
|
|
||||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61,
|
|
||||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f,
|
|
||||||
0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
|
||||||
0x38, 0x01, 0x1a, 0x92, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69,
|
|
||||||
0x6e, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
|
|
||||||
0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e,
|
|
||||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79,
|
|
||||||
0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61,
|
|
||||||
0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
|
||||||
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70,
|
|
||||||
0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61,
|
|
||||||
0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65,
|
|
||||||
0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
|
||||||
0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a,
|
|
||||||
0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64, 0x6f,
|
|
||||||
0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72,
|
|
||||||
0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x42, 0x46,
|
|
||||||
0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64,
|
|
||||||
0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
|
||||||
0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f,
|
|
||||||
0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41,
|
|
||||||
0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -531,37 +575,40 @@ func file_app_dns_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_app_dns_config_proto_rawDescData
|
return file_app_dns_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_app_dns_config_proto_goTypes = []interface{}{
|
var file_app_dns_config_proto_goTypes = []interface{}{
|
||||||
(DomainMatchingType)(0), // 0: xray.app.dns.DomainMatchingType
|
(QueryStrategy)(0), // 0: xray.app.dns.QueryStrategy
|
||||||
(*NameServer)(nil), // 1: xray.app.dns.NameServer
|
(CacheStrategy)(0), // 1: xray.app.dns.CacheStrategy
|
||||||
(*Config)(nil), // 2: xray.app.dns.Config
|
(*NameServer)(nil), // 2: xray.app.dns.NameServer
|
||||||
(*NameServer_PriorityDomain)(nil), // 3: xray.app.dns.NameServer.PriorityDomain
|
(*Config)(nil), // 3: xray.app.dns.Config
|
||||||
(*NameServer_OriginalRule)(nil), // 4: xray.app.dns.NameServer.OriginalRule
|
(*NameServer_OriginalRule)(nil), // 4: xray.app.dns.NameServer.OriginalRule
|
||||||
nil, // 5: xray.app.dns.Config.HostsEntry
|
nil, // 5: xray.app.dns.Config.HostsEntry
|
||||||
(*Config_HostMapping)(nil), // 6: xray.app.dns.Config.HostMapping
|
(*Config_HostMapping)(nil), // 6: xray.app.dns.Config.HostMapping
|
||||||
(*net.Endpoint)(nil), // 7: xray.common.net.Endpoint
|
(*net.Endpoint)(nil), // 7: xray.common.net.Endpoint
|
||||||
(*router.GeoIP)(nil), // 8: xray.app.router.GeoIP
|
(*domain.Domain)(nil), // 8: xray.common.matcher.domain.Domain
|
||||||
(*net.IPOrDomain)(nil), // 9: xray.common.net.IPOrDomain
|
(*geoip.GeoIP)(nil), // 9: xray.common.matcher.geoip.GeoIP
|
||||||
|
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
||||||
|
(domain.MatchingType)(0), // 11: xray.common.matcher.domain.MatchingType
|
||||||
}
|
}
|
||||||
var file_app_dns_config_proto_depIdxs = []int32{
|
var file_app_dns_config_proto_depIdxs = []int32{
|
||||||
7, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
7, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
||||||
3, // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain
|
8, // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.common.matcher.domain.Domain
|
||||||
8, // 2: xray.app.dns.NameServer.geoip:type_name -> xray.app.router.GeoIP
|
9, // 2: xray.app.dns.NameServer.geoip:type_name -> xray.common.matcher.geoip.GeoIP
|
||||||
4, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
|
4, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
|
||||||
7, // 4: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint
|
7, // 4: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint
|
||||||
1, // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
|
2, // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
|
||||||
5, // 6: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry
|
5, // 6: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry
|
||||||
6, // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
|
6, // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
|
||||||
0, // 8: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType
|
1, // 8: xray.app.dns.Config.cache_strategy:type_name -> xray.app.dns.CacheStrategy
|
||||||
9, // 9: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
|
0, // 9: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||||
0, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
|
10, // 10: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
|
||||||
11, // [11:11] is the sub-list for method output_type
|
11, // 11: xray.app.dns.Config.HostMapping.type:type_name -> xray.common.matcher.domain.MatchingType
|
||||||
11, // [11:11] is the sub-list for method input_type
|
12, // [12:12] is the sub-list for method output_type
|
||||||
11, // [11:11] is the sub-list for extension type_name
|
12, // [12:12] is the sub-list for method input_type
|
||||||
11, // [11:11] is the sub-list for extension extendee
|
12, // [12:12] is the sub-list for extension type_name
|
||||||
0, // [0:11] is the sub-list for field type_name
|
12, // [12:12] is the sub-list for extension extendee
|
||||||
|
0, // [0:12] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_app_dns_config_proto_init() }
|
func init() { file_app_dns_config_proto_init() }
|
||||||
@@ -595,18 +642,6 @@ func file_app_dns_config_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_app_dns_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
file_app_dns_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*NameServer_PriorityDomain); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_app_dns_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*NameServer_OriginalRule); i {
|
switch v := v.(*NameServer_OriginalRule); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -618,7 +653,7 @@ func file_app_dns_config_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_app_dns_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
file_app_dns_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*Config_HostMapping); i {
|
switch v := v.(*Config_HostMapping); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -636,8 +671,8 @@ func file_app_dns_config_proto_init() {
|
|||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_app_dns_config_proto_rawDesc,
|
RawDescriptor: file_app_dns_config_proto_rawDesc,
|
||||||
NumEnums: 1,
|
NumEnums: 2,
|
||||||
NumMessages: 6,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,31 +8,34 @@ option java_multiple_files = true;
|
|||||||
|
|
||||||
import "common/net/address.proto";
|
import "common/net/address.proto";
|
||||||
import "common/net/destination.proto";
|
import "common/net/destination.proto";
|
||||||
import "app/router/config.proto";
|
import "common/matcher/domain/domain.proto";
|
||||||
|
import "common/matcher/geoip/geoip.proto";
|
||||||
|
|
||||||
message NameServer {
|
message NameServer {
|
||||||
xray.common.net.Endpoint address = 1;
|
xray.common.net.Endpoint address = 1;
|
||||||
|
bytes client_ip = 5;
|
||||||
message PriorityDomain {
|
bool skipFallback = 6;
|
||||||
DomainMatchingType type = 1;
|
|
||||||
string domain = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OriginalRule {
|
message OriginalRule {
|
||||||
string rule = 1;
|
string rule = 1;
|
||||||
uint32 size = 2;
|
uint32 size = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
repeated PriorityDomain prioritized_domain = 2;
|
repeated xray.common.matcher.domain.Domain prioritized_domain = 2;
|
||||||
repeated xray.app.router.GeoIP geoip = 3;
|
repeated xray.common.matcher.geoip.GeoIP geoip = 3;
|
||||||
repeated OriginalRule original_rules = 4;
|
repeated OriginalRule original_rules = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DomainMatchingType {
|
enum QueryStrategy {
|
||||||
Full = 0;
|
USE_IP = 0;
|
||||||
Subdomain = 1;
|
USE_IP4 = 1;
|
||||||
Keyword = 2;
|
USE_IP6 = 2;
|
||||||
Regex = 3;
|
}
|
||||||
|
|
||||||
|
enum CacheStrategy {
|
||||||
|
Cache_ALL = 0;
|
||||||
|
Cache_NOERROR = 1;
|
||||||
|
Cache_DISABLE = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
@@ -53,14 +56,13 @@ message Config {
|
|||||||
bytes client_ip = 3;
|
bytes client_ip = 3;
|
||||||
|
|
||||||
message HostMapping {
|
message HostMapping {
|
||||||
DomainMatchingType type = 1;
|
xray.common.matcher.domain.MatchingType type = 1;
|
||||||
string domain = 2;
|
string domain = 2;
|
||||||
|
|
||||||
repeated bytes ip = 3;
|
repeated bytes ip = 3;
|
||||||
|
|
||||||
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
||||||
// domain. Xray will use this domain for IP queries. This field is only
|
// domain. Xray will use this domain for IP queries.
|
||||||
// effective if ip is empty.
|
|
||||||
string proxied_domain = 4;
|
string proxied_domain = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,4 +72,11 @@ message Config {
|
|||||||
string tag = 6;
|
string tag = 6;
|
||||||
|
|
||||||
reserved 7;
|
reserved 7;
|
||||||
|
|
||||||
|
// DisableCache disables DNS cache
|
||||||
|
CacheStrategy cache_strategy = 8;
|
||||||
|
|
||||||
|
QueryStrategy query_strategy = 9;
|
||||||
|
|
||||||
|
bool disableFallback = 10;
|
||||||
}
|
}
|
||||||
|
|||||||
309
app/dns/dns.go
309
app/dns/dns.go
@@ -2,3 +2,312 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/str"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/features"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNS is a DNS rely server.
|
||||||
|
type DNS struct {
|
||||||
|
sync.Mutex
|
||||||
|
tag string
|
||||||
|
cacheStrategy CacheStrategy
|
||||||
|
disableFallback bool
|
||||||
|
ipOption *dns.IPOption
|
||||||
|
hosts *StaticHosts
|
||||||
|
clients []*Client
|
||||||
|
ctx context.Context
|
||||||
|
domainMatcher str.IndexMatcher
|
||||||
|
matcherInfos []DomainMatcherInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
|
||||||
|
type DomainMatcherInfo struct {
|
||||||
|
clientIdx uint16
|
||||||
|
domainRuleIdx uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new DNS server with given configuration.
|
||||||
|
func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||||
|
var tag string
|
||||||
|
if len(config.Tag) > 0 {
|
||||||
|
tag = config.Tag
|
||||||
|
} else {
|
||||||
|
tag = generateRandomTag()
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientIP net.IP
|
||||||
|
switch len(config.ClientIp) {
|
||||||
|
case 0, net.IPv4len, net.IPv6len:
|
||||||
|
clientIP = net.IP(config.ClientIp)
|
||||||
|
default:
|
||||||
|
return nil, newError("unexpected client IP length ", len(config.ClientIp))
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipOption *dns.IPOption
|
||||||
|
switch config.QueryStrategy {
|
||||||
|
case QueryStrategy_USE_IP:
|
||||||
|
ipOption = &dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
case QueryStrategy_USE_IP4:
|
||||||
|
ipOption = &dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
case QueryStrategy_USE_IP6:
|
||||||
|
ipOption = &dns.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create hosts").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clients := []*Client{}
|
||||||
|
domainRuleCount := 0
|
||||||
|
for _, ns := range config.NameServer {
|
||||||
|
domainRuleCount += len(ns.PrioritizedDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
|
||||||
|
matcherInfos := make([]DomainMatcherInfo, domainRuleCount+1)
|
||||||
|
domainMatcher := &str.MatcherGroup{}
|
||||||
|
geoipContainer := geoip.GeoIPMatcherContainer{}
|
||||||
|
|
||||||
|
for _, endpoint := range config.NameServers {
|
||||||
|
features.PrintDeprecatedFeatureWarning("simple DNS server")
|
||||||
|
client, err := NewSimpleClient(ctx, endpoint, clientIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create client").Base(err)
|
||||||
|
}
|
||||||
|
clients = append(clients, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ns := range config.NameServer {
|
||||||
|
clientIdx := len(clients)
|
||||||
|
updateDomain := func(domainRule str.Matcher, originalRuleIdx int, matcherInfos []DomainMatcherInfo) error {
|
||||||
|
midx := domainMatcher.Add(domainRule)
|
||||||
|
matcherInfos[midx] = DomainMatcherInfo{
|
||||||
|
clientIdx: uint16(clientIdx),
|
||||||
|
domainRuleIdx: uint16(originalRuleIdx),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
myClientIP := clientIP
|
||||||
|
switch len(ns.ClientIp) {
|
||||||
|
case net.IPv4len, net.IPv6len:
|
||||||
|
myClientIP = net.IP(ns.ClientIp)
|
||||||
|
}
|
||||||
|
client, err := NewClient(ctx, ns, myClientIP, geoipContainer, &matcherInfos, updateDomain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create client").Base(err)
|
||||||
|
}
|
||||||
|
clients = append(clients, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no DNS client in config, add a `localhost` DNS client
|
||||||
|
if len(clients) == 0 {
|
||||||
|
clients = append(clients, NewLocalDNSClient())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DNS{
|
||||||
|
tag: tag,
|
||||||
|
hosts: hosts,
|
||||||
|
ipOption: ipOption,
|
||||||
|
clients: clients,
|
||||||
|
ctx: ctx,
|
||||||
|
domainMatcher: domainMatcher,
|
||||||
|
matcherInfos: matcherInfos,
|
||||||
|
cacheStrategy: config.CacheStrategy,
|
||||||
|
disableFallback: config.DisableFallback,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements common.HasType.
|
||||||
|
func (*DNS) Type() interface{} {
|
||||||
|
return dns.ClientType()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.
|
||||||
|
func (s *DNS) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.
|
||||||
|
func (s *DNS) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOwnLink implements proxy.dns.ownLinkVerifier
|
||||||
|
func (s *DNS) IsOwnLink(ctx context.Context) bool {
|
||||||
|
inbound := session.InboundFromContext(ctx)
|
||||||
|
return inbound != nil && inbound.Tag == s.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIP implements dns.Client.
|
||||||
|
func (s *DNS) LookupIP(domain string) ([]net.IP, error) {
|
||||||
|
return s.lookupIPInternal(domain, s.ipOption.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupOptions implements dns.Client.
|
||||||
|
func (s *DNS) LookupOptions(domain string, opts ...dns.Option) ([]net.IP, error) {
|
||||||
|
opt := s.ipOption.Copy()
|
||||||
|
for _, o := range opts {
|
||||||
|
if o != nil {
|
||||||
|
o(opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.lookupIPInternal(domain, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIPv4 implements dns.IPv4Lookup.
|
||||||
|
func (s *DNS) LookupIPv4(domain string) ([]net.IP, error) {
|
||||||
|
return s.lookupIPInternal(domain, &dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIPv6 implements dns.IPv6Lookup.
|
||||||
|
func (s *DNS) LookupIPv6(domain string) ([]net.IP, error) {
|
||||||
|
return s.lookupIPInternal(domain, &dns.IPOption{
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNS) lookupIPInternal(domain string, option *dns.IPOption) ([]net.IP, error) {
|
||||||
|
if domain == "" {
|
||||||
|
return nil, newError("empty domain name")
|
||||||
|
}
|
||||||
|
if isQuery(option) {
|
||||||
|
return nil, newError("empty option: Impossible.").AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the FQDN form query
|
||||||
|
if strings.HasSuffix(domain, ".") {
|
||||||
|
domain = domain[:len(domain)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static host lookup
|
||||||
|
switch addrs := s.hosts.Lookup(domain, option); {
|
||||||
|
case addrs == nil: // Domain not recorded in static host
|
||||||
|
break
|
||||||
|
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
|
||||||
|
newError("domain replaced: ", domain, " -> ", addrs[0].Domain()).WriteToLog()
|
||||||
|
domain = addrs[0].Domain()
|
||||||
|
default:
|
||||||
|
// Successfully found ip records in static host.
|
||||||
|
// Skip hosts mapping result in FakeDNS query.
|
||||||
|
if isIPQuery(option) {
|
||||||
|
newError("returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs).WriteToLog()
|
||||||
|
return toNetIP(addrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name servers lookup
|
||||||
|
errs := []error{}
|
||||||
|
ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
|
||||||
|
for _, client := range s.sortClients(domain, option) {
|
||||||
|
ips, err := client.QueryIP(ctx, domain, *option, s.cacheStrategy)
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog()
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, newError("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNS) sortClients(domain string, option *dns.IPOption) []*Client {
|
||||||
|
clients := make([]*Client, 0, len(s.clients))
|
||||||
|
clientUsed := make([]bool, len(s.clients))
|
||||||
|
clientNames := make([]string, 0, len(s.clients))
|
||||||
|
domainRules := []string{}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if len(domainRules) > 0 {
|
||||||
|
newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog()
|
||||||
|
}
|
||||||
|
if len(clientNames) > 0 {
|
||||||
|
newError("domain ", domain, " will use DNS in order: ", clientNames).AtDebug().WriteToLog()
|
||||||
|
}
|
||||||
|
if len(clients) == 0 {
|
||||||
|
clients = append(clients, s.clients[0])
|
||||||
|
clientNames = append(clientNames, s.clients[0].Name())
|
||||||
|
newError("domain ", domain, " will use the first DNS: ", clientNames).AtDebug().WriteToLog()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Priority domain matching
|
||||||
|
for _, match := range s.domainMatcher.Match(domain) {
|
||||||
|
info := s.matcherInfos[match]
|
||||||
|
client := s.clients[info.clientIdx]
|
||||||
|
domainRule := client.domains[info.domainRuleIdx]
|
||||||
|
if !canQueryOnClient(option, client) {
|
||||||
|
newError("skipping the client " + client.Name()).AtDebug().WriteToLog()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
|
||||||
|
if clientUsed[info.clientIdx] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clientUsed[info.clientIdx] = true
|
||||||
|
clients = append(clients, client)
|
||||||
|
clientNames = append(clientNames, client.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.disableFallback {
|
||||||
|
// Default round-robin query
|
||||||
|
for idx, client := range s.clients {
|
||||||
|
if clientUsed[idx] || client.skipFallback {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !canQueryOnClient(option, client) {
|
||||||
|
newError("skipping the client " + client.Name()).AtDebug().WriteToLog()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
clientUsed[idx] = true
|
||||||
|
clients = append(clients, client)
|
||||||
|
clientNames = append(clientNames, client.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return New(ctx, config.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import (
|
|||||||
"github.com/xtls/xray-core/app/policy"
|
"github.com/xtls/xray-core/app/policy"
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||||
"github.com/xtls/xray-core/app/router"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
@@ -101,8 +102,8 @@ func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
rr, _ := dns.NewRR("localhost-b. IN A 127.0.0.4")
|
rr, _ := dns.NewRR("localhost-b. IN A 127.0.0.4")
|
||||||
ans.Answer = append(ans.Answer, rr)
|
ans.Answer = append(ans.Answer, rr)
|
||||||
|
|
||||||
case q.Name == "mijia\\ cloud." && q.Qtype == dns.TypeA:
|
case q.Name == "Mijia\\ Cloud." && q.Qtype == dns.TypeA:
|
||||||
rr, _ := dns.NewRR("mijia\\ cloud. IN A 127.0.0.1")
|
rr, _ := dns.NewRR("Mijia\\ Cloud. IN A 127.0.0.1")
|
||||||
ans.Answer = append(ans.Answer, rr)
|
ans.Answer = append(ans.Answer, rr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,11 +155,7 @@ func TestUDPServerSubnet(t *testing.T) {
|
|||||||
|
|
||||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||||
|
|
||||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -213,11 +210,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -228,11 +221,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := client.LookupIP("facebook.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("facebook.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -243,11 +232,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
_, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
|
_, err := client.LookupIP("notexist.google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("nil error")
|
t.Fatal("nil error")
|
||||||
}
|
}
|
||||||
@@ -257,11 +242,8 @@ func TestUDPServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
|
clientv6 := client.(feature_dns.IPv6Lookup)
|
||||||
IPv4Enable: false,
|
ips, err := clientv6.LookupIPv6("ipv4only.google.com")
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != feature_dns.ErrEmptyResponse {
|
if err != feature_dns.ErrEmptyResponse {
|
||||||
t.Fatal("error: ", err)
|
t.Fatal("error: ", err)
|
||||||
}
|
}
|
||||||
@@ -273,11 +255,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
dnsServer.Shutdown()
|
dnsServer.Shutdown()
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -326,10 +304,10 @@ func TestPrioritizedDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
PrioritizedDomain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Full,
|
Type: domain.MatchingType_Full,
|
||||||
Domain: "google.com",
|
Value: "google.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -354,11 +332,7 @@ func TestPrioritizedDomain(t *testing.T) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -417,12 +391,9 @@ func TestUDPServerIPv6(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||||
|
client6 := client.(feature_dns.IPv6Lookup)
|
||||||
{
|
{
|
||||||
ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
ips, err := client6.LookupIPv6("ipv6.google.com")
|
||||||
IPv4Enable: false,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -462,7 +433,7 @@ func TestStaticHostDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
StaticHosts: []*Config_HostMapping{
|
StaticHosts: []*Config_HostMapping{
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Full,
|
Type: domain.MatchingType_Full,
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
ProxiedDomain: "google.com",
|
ProxiedDomain: "google.com",
|
||||||
},
|
},
|
||||||
@@ -485,11 +456,7 @@ func TestStaticHostDomain(t *testing.T) {
|
|||||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := client.LookupIP("example.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("example.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -530,10 +497,10 @@ func TestIPMatch(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{
|
{
|
||||||
CountryCode: "local",
|
CountryCode: "local",
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
// inner ip, will not match
|
// inner ip, will not match
|
||||||
Ip: []byte{192, 168, 11, 1},
|
Ip: []byte{192, 168, 11, 1},
|
||||||
@@ -554,10 +521,10 @@ func TestIPMatch(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{
|
{
|
||||||
CountryCode: "test",
|
CountryCode: "test",
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{8, 8, 8, 8},
|
Ip: []byte{8, 8, 8, 8},
|
||||||
Prefix: 32,
|
Prefix: 32,
|
||||||
@@ -566,7 +533,7 @@ func TestIPMatch(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
CountryCode: "test",
|
CountryCode: "test",
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{8, 8, 8, 4},
|
Ip: []byte{8, 8, 8, 4},
|
||||||
Prefix: 32,
|
Prefix: 32,
|
||||||
@@ -596,11 +563,7 @@ func TestIPMatch(t *testing.T) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -654,14 +617,14 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
PrioritizedDomain: []*domain.Domain{
|
||||||
// Equivalent of dotless:localhost
|
// Equivalent of dotless:localhost
|
||||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
|
{Type: domain.MatchingType_Regex, Value: "^[^.]*localhost[^.]*$"},
|
||||||
},
|
},
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{ // Will match localhost, localhost-a and localhost-b,
|
{ // Will match localhost, localhost-a and localhost-b,
|
||||||
CountryCode: "local",
|
CountryCode: "local",
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
|
{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
|
||||||
{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
|
{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
|
||||||
{Ip: []byte{127, 0, 0, 4}, Prefix: 32},
|
{Ip: []byte{127, 0, 0, 4}, Prefix: 32},
|
||||||
@@ -679,22 +642,22 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
PrioritizedDomain: []*domain.Domain{
|
||||||
// Equivalent of dotless: and domain:local
|
// Equivalent of dotless: and domain:local
|
||||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*$"},
|
{Type: domain.MatchingType_Regex, Value: "^[^.]*$"},
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
{Type: domain.MatchingType_Subdomain, Value: "local"},
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
{Type: domain.MatchingType_Subdomain, Value: "localdomain"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StaticHosts: []*Config_HostMapping{
|
StaticHosts: []*Config_HostMapping{
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Full,
|
Type: domain.MatchingType_Full,
|
||||||
Domain: "hostnamestatic",
|
Domain: "hostnamestatic",
|
||||||
Ip: [][]byte{{127, 0, 0, 53}},
|
Ip: [][]byte{{127, 0, 0, 53}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Full,
|
Type: domain.MatchingType_Full,
|
||||||
Domain: "hostnamealias",
|
Domain: "hostnamealias",
|
||||||
ProxiedDomain: "hostname.localdomain",
|
ProxiedDomain: "hostname.localdomain",
|
||||||
},
|
},
|
||||||
@@ -719,11 +682,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
{ // Will match dotless:
|
{ // Will match dotless:
|
||||||
ips, err := client.LookupIP("hostname", feature_dns.IPOption{
|
ips, err := client.LookupIP("hostname")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -734,11 +693,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match domain:local
|
{ // Will match domain:local
|
||||||
ips, err := client.LookupIP("hostname.local", feature_dns.IPOption{
|
ips, err := client.LookupIP("hostname.local")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -749,11 +704,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match static ip
|
{ // Will match static ip
|
||||||
ips, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
|
ips, err := client.LookupIP("hostnamestatic")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -764,11 +715,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match domain replacing
|
{ // Will match domain replacing
|
||||||
ips, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
|
ips, err := client.LookupIP("hostnamealias")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -779,11 +726,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
|
{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
|
||||||
ips, err := client.LookupIP("localhost", feature_dns.IPOption{
|
ips, err := client.LookupIP("localhost")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -794,11 +737,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
||||||
ips, err := client.LookupIP("localhost-a", feature_dns.IPOption{
|
ips, err := client.LookupIP("localhost-a")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -809,11 +748,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
||||||
ips, err := client.LookupIP("localhost-b", feature_dns.IPOption{
|
ips, err := client.LookupIP("localhost-b")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -824,11 +759,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match dotless:
|
{ // Will match dotless:
|
||||||
ips, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
|
ips, err := client.LookupIP("Mijia Cloud")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -882,15 +813,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
PrioritizedDomain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Subdomain,
|
Type: domain.MatchingType_Subdomain,
|
||||||
Domain: "google.com",
|
Value: "google.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{ // Will only match 8.8.8.8 and 8.8.4.4
|
{ // Will only match 8.8.8.8 and 8.8.4.4
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||||
{Ip: []byte{8, 8, 4, 4}, Prefix: 32},
|
{Ip: []byte{8, 8, 4, 4}, Prefix: 32},
|
||||||
},
|
},
|
||||||
@@ -907,15 +838,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
PrioritizedDomain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Subdomain,
|
Type: domain.MatchingType_Subdomain,
|
||||||
Domain: "google.com",
|
Value: "google.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{ // Will match 8.8.8.8 and 8.8.8.7, etc
|
{ // Will match 8.8.8.8 and 8.8.8.7, etc
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{Ip: []byte{8, 8, 8, 7}, Prefix: 24},
|
{Ip: []byte{8, 8, 8, 7}, Prefix: 24},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -931,15 +862,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
PrioritizedDomain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Subdomain,
|
Type: domain.MatchingType_Subdomain,
|
||||||
Domain: "api.google.com",
|
Value: "api.google.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{ // Will only match 8.8.7.7 (api.google.com)
|
{ // Will only match 8.8.7.7 (api.google.com)
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{Ip: []byte{8, 8, 7, 7}, Prefix: 32},
|
{Ip: []byte{8, 8, 7, 7}, Prefix: 32},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -955,15 +886,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Port: uint32(port),
|
Port: uint32(port),
|
||||||
},
|
},
|
||||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
PrioritizedDomain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Full,
|
Type: domain.MatchingType_Full,
|
||||||
Domain: "v2.api.google.com",
|
Value: "v2.api.google.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{ // Will only match 8.8.7.8 (v2.api.google.com)
|
{ // Will only match 8.8.7.8 (v2.api.google.com)
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{Ip: []byte{8, 8, 7, 8}, Prefix: 32},
|
{Ip: []byte{8, 8, 7, 8}, Prefix: 32},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -990,11 +921,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
{ // Will match server 1,2 and server 1 returns expected ip
|
{ // Will match server 1,2 and server 1 returns expected ip
|
||||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -1005,11 +932,8 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
|
{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
|
||||||
ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
clientv4 := client.(feature_dns.IPv4Lookup)
|
||||||
IPv4Enable: true,
|
ips, err := clientv4.LookupIPv4("ipv6.google.com")
|
||||||
IPv6Enable: false,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -1020,11 +944,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match server 3,1,2 and server 3 returns expected one
|
{ // Will match server 3,1,2 and server 3 returns expected one
|
||||||
ips, err := client.LookupIP("api.google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("api.google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -1035,11 +955,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match server 4,3,1,2 and server 4 returns expected one
|
{ // Will match server 4,3,1,2 and server 4 returns expected one
|
||||||
ips, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("v2.api.google.com")
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected error: ", err)
|
t.Fatal("unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/str"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
|
||||||
"github.com/xtls/xray-core/features"
|
"github.com/xtls/xray-core/features"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
)
|
)
|
||||||
@@ -11,31 +11,12 @@ import (
|
|||||||
// StaticHosts represents static domain-ip mapping in DNS server.
|
// StaticHosts represents static domain-ip mapping in DNS server.
|
||||||
type StaticHosts struct {
|
type StaticHosts struct {
|
||||||
ips [][]net.Address
|
ips [][]net.Address
|
||||||
matchers *strmatcher.MatcherGroup
|
matchers *str.MatcherGroup
|
||||||
}
|
|
||||||
|
|
||||||
var typeMap = map[DomainMatchingType]strmatcher.Type{
|
|
||||||
DomainMatchingType_Full: strmatcher.Full,
|
|
||||||
DomainMatchingType_Subdomain: strmatcher.Domain,
|
|
||||||
DomainMatchingType_Keyword: strmatcher.Substr,
|
|
||||||
DomainMatchingType_Regex: strmatcher.Regex,
|
|
||||||
}
|
|
||||||
|
|
||||||
func toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) {
|
|
||||||
strMType, f := typeMap[t]
|
|
||||||
if !f {
|
|
||||||
return nil, newError("unknown mapping type", t).AtWarning()
|
|
||||||
}
|
|
||||||
matcher, err := strMType.New(domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to create str matcher").Base(err)
|
|
||||||
}
|
|
||||||
return matcher, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStaticHosts creates a new StaticHosts instance.
|
// NewStaticHosts creates a new StaticHosts instance.
|
||||||
func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) {
|
func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) {
|
||||||
g := new(strmatcher.MatcherGroup)
|
g := new(str.MatcherGroup)
|
||||||
sh := &StaticHosts{
|
sh := &StaticHosts{
|
||||||
ips: make([][]net.Address, len(hosts)+len(legacy)+16),
|
ips: make([][]net.Address, len(hosts)+len(legacy)+16),
|
||||||
matchers: g,
|
matchers: g,
|
||||||
@@ -45,7 +26,7 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
|
|||||||
features.PrintDeprecatedFeatureWarning("simple host mapping")
|
features.PrintDeprecatedFeatureWarning("simple host mapping")
|
||||||
|
|
||||||
for domain, ip := range legacy {
|
for domain, ip := range legacy {
|
||||||
matcher, err := strmatcher.Full.New(domain)
|
matcher, err := str.Full.New(domain)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
id := g.Add(matcher)
|
id := g.Add(matcher)
|
||||||
|
|
||||||
@@ -66,6 +47,9 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
|
|||||||
id := g.Add(matcher)
|
id := g.Add(matcher)
|
||||||
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
||||||
switch {
|
switch {
|
||||||
|
case len(mapping.ProxiedDomain) > 0:
|
||||||
|
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||||
|
|
||||||
case len(mapping.Ip) > 0:
|
case len(mapping.Ip) > 0:
|
||||||
for _, ip := range mapping.Ip {
|
for _, ip := range mapping.Ip {
|
||||||
addr := net.IPAddress(ip)
|
addr := net.IPAddress(ip)
|
||||||
@@ -75,49 +59,56 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
|
|||||||
ips = append(ips, addr)
|
ips = append(ips, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
case len(mapping.ProxiedDomain) > 0:
|
|
||||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, newError("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning()
|
return nil, newError("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for localhost IPv6. This is a dirty workaround as JSON config supports only single IP mapping.
|
|
||||||
if len(ips) == 1 && ips[0] == net.LocalHostIP {
|
|
||||||
ips = append(ips, net.LocalHostIPv6)
|
|
||||||
}
|
|
||||||
|
|
||||||
sh.ips[id] = ips
|
sh.ips[id] = ips
|
||||||
}
|
}
|
||||||
|
|
||||||
return sh, nil
|
return sh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
func filterIP(ips []net.Address, option *dns.IPOption) []net.Address {
|
||||||
filtered := make([]net.Address, 0, len(ips))
|
filtered := make([]net.Address, 0, len(ips))
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {
|
if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {
|
||||||
filtered = append(filtered, ip)
|
filtered = append(filtered, ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(filtered) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIP returns IP address for the given domain, if exists in this StaticHosts.
|
func (h *StaticHosts) lookupInternal(domain string) []net.Address {
|
||||||
func (h *StaticHosts) LookupIP(domain string, option dns.IPOption) []net.Address {
|
var ips []net.Address
|
||||||
indices := h.matchers.Match(domain)
|
for _, id := range h.matchers.Match(domain) {
|
||||||
if len(indices) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ips := []net.Address{}
|
|
||||||
for _, id := range indices {
|
|
||||||
ips = append(ips, h.ips[id]...)
|
ips = append(ips, h.ips[id]...)
|
||||||
}
|
}
|
||||||
if len(ips) == 1 && ips[0].Family().IsDomain() {
|
if len(ips) == 1 && ips[0].Family().IsDomain() {
|
||||||
return ips
|
return ips
|
||||||
}
|
}
|
||||||
return filterIP(ips, option)
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StaticHosts) lookup(domain string, option *dns.IPOption, maxDepth int) []net.Address {
|
||||||
|
switch addrs := h.lookupInternal(domain); {
|
||||||
|
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||||
|
return nil
|
||||||
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||||
|
newError("found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it").AtDebug().WriteToLog()
|
||||||
|
if maxDepth > 0 {
|
||||||
|
unwrapped := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
||||||
|
if unwrapped != nil {
|
||||||
|
return unwrapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
default: // IP record found, return a non-nil IP array
|
||||||
|
return filterIP(addrs, option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
||||||
|
func (h *StaticHosts) Lookup(domain string, option *dns.IPOption) []net.Address {
|
||||||
|
return h.lookup(domain, option, 5)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
. "github.com/xtls/xray-core/app/dns"
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
)
|
)
|
||||||
@@ -14,24 +15,39 @@ import (
|
|||||||
func TestStaticHosts(t *testing.T) {
|
func TestStaticHosts(t *testing.T) {
|
||||||
pb := []*Config_HostMapping{
|
pb := []*Config_HostMapping{
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Full,
|
Type: domain.MatchingType_Full,
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Ip: [][]byte{
|
Ip: [][]byte{
|
||||||
{1, 1, 1, 1},
|
{1, 1, 1, 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Subdomain,
|
Type: domain.MatchingType_Full,
|
||||||
|
Domain: "proxy.example.com",
|
||||||
|
Ip: [][]byte{
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
},
|
||||||
|
ProxiedDomain: "another-proxy.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: domain.MatchingType_Full,
|
||||||
|
Domain: "proxy2.example.com",
|
||||||
|
ProxiedDomain: "proxy.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: domain.MatchingType_Subdomain,
|
||||||
Domain: "example.cn",
|
Domain: "example.cn",
|
||||||
Ip: [][]byte{
|
Ip: [][]byte{
|
||||||
{2, 2, 2, 2},
|
{2, 2, 2, 2},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Subdomain,
|
Type: domain.MatchingType_Subdomain,
|
||||||
Domain: "baidu.com",
|
Domain: "baidu.com",
|
||||||
Ip: [][]byte{
|
Ip: [][]byte{
|
||||||
{127, 0, 0, 1},
|
{127, 0, 0, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -40,7 +56,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.LookupIP("example.com", dns.IPOption{
|
ips := hosts.Lookup("example.com", &dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
@@ -53,7 +69,33 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.LookupIP("www.example.cn", dns.IPOption{
|
domain := hosts.Lookup("proxy.example.com", &dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
if len(domain) != 1 {
|
||||||
|
t.Error("expect 1 domain, but got ", len(domain))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(domain[0].Domain(), "another-proxy.example.com"); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
domain := hosts.Lookup("proxy2.example.com", &dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
if len(domain) != 1 {
|
||||||
|
t.Error("expect 1 domain, but got ", len(domain))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(domain[0].Domain(), "another-proxy.example.com"); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ips := hosts.Lookup("www.example.cn", &dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
@@ -66,7 +108,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.LookupIP("baidu.com", dns.IPOption{
|
ips := hosts.Lookup("baidu.com", &dns.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: false,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,40 +2,211 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/str"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
core "github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/dns/localdns"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is the interface for DNS client.
|
// Server is the interface for Name Server.
|
||||||
type Client interface {
|
type Server interface {
|
||||||
// Name of the Client.
|
// Name of the Client.
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
// QueryIP sends IP queries to its configured server.
|
// QueryIP sends IP queries to its configured server.
|
||||||
QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, error)
|
QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, cs CacheStrategy) ([]net.IP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocalNameServer struct {
|
// Client is the interface for DNS client.
|
||||||
client *localdns.Client
|
type Client struct {
|
||||||
|
server Server
|
||||||
|
clientIP net.IP
|
||||||
|
skipFallback bool
|
||||||
|
domains []string
|
||||||
|
expectIPs []*geoip.GeoIPMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalNameServer) QueryIP(_ context.Context, domain string, option dns.IPOption) ([]net.IP, error) {
|
var errExpectedIPNonMatch = errors.New("expectIPs not match")
|
||||||
if option.IPv4Enable || option.IPv6Enable {
|
|
||||||
return s.client.LookupIP(domain, option)
|
// NewServer creates a name server object according to the network destination url.
|
||||||
|
func NewServer(dest net.Destination, dispatcher routing.Dispatcher) (Server, error) {
|
||||||
|
if address := dest.Address; address.Family().IsDomain() {
|
||||||
|
u, err := url.Parse(address.Domain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(u.String(), "localhost"):
|
||||||
|
return NewLocalNameServer(), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "https"): // DOH Remote mode
|
||||||
|
return NewDoHNameServer(u, dispatcher)
|
||||||
|
case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode
|
||||||
|
return NewDoHLocalNameServer(u), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
|
||||||
|
return NewQUICNameServer(u)
|
||||||
|
case strings.EqualFold(u.String(), "fakedns"):
|
||||||
|
return NewFakeDNSServer(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dest.Network == net.Network_Unknown {
|
||||||
|
dest.Network = net.Network_UDP
|
||||||
|
}
|
||||||
|
if dest.Network == net.Network_UDP { // UDP classic DNS mode
|
||||||
|
return NewClassicNameServer(dest, dispatcher), nil
|
||||||
|
}
|
||||||
|
return nil, newError("No available name server could be created from ", dest).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
|
||||||
|
func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container geoip.GeoIPMatcherContainer, matcherInfos *[]DomainMatcherInfo, updateDomainRule func(str.Matcher, int, []DomainMatcherInfo) error) (*Client, error) {
|
||||||
|
client := &Client{}
|
||||||
|
|
||||||
|
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||||
|
// Create a new server for each client for now
|
||||||
|
server, err := NewServer(ns.Address.AsDestination(), dispatcher)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create nameserver").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priotize local domains with specific TLDs or without any dot to local DNS
|
||||||
|
if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
|
||||||
|
ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
|
||||||
|
ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
|
||||||
|
// The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config.
|
||||||
|
// Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
|
||||||
|
// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
|
||||||
|
// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.
|
||||||
|
for i := 0; i < len(localTLDsAndDotlessDomains); i++ {
|
||||||
|
*matcherInfos = append(*matcherInfos, DomainMatcherInfo{
|
||||||
|
clientIdx: uint16(0),
|
||||||
|
domainRuleIdx: uint16(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish domain rules
|
||||||
|
var rules []string
|
||||||
|
ruleCurr := 0
|
||||||
|
ruleIter := 0
|
||||||
|
for _, domain := range ns.PrioritizedDomain {
|
||||||
|
domainRule, err := toStrMatcher(domain.Type, domain.Value)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create prioritized domain").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
originalRuleIdx := ruleCurr
|
||||||
|
if ruleCurr < len(ns.OriginalRules) {
|
||||||
|
rule := ns.OriginalRules[ruleCurr]
|
||||||
|
if ruleCurr >= len(rules) {
|
||||||
|
rules = append(rules, rule.Rule)
|
||||||
|
}
|
||||||
|
ruleIter++
|
||||||
|
if ruleIter >= int(rule.Size) {
|
||||||
|
ruleIter = 0
|
||||||
|
ruleCurr++
|
||||||
|
}
|
||||||
|
} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)
|
||||||
|
rules = append(rules, domainRule.String())
|
||||||
|
ruleCurr++
|
||||||
|
}
|
||||||
|
err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create prioritized domain").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish expected IPs
|
||||||
|
var matchers []*geoip.GeoIPMatcher
|
||||||
|
for _, geoip := range ns.Geoip {
|
||||||
|
matcher, err := container.Add(geoip)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create ip matcher").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
matchers = append(matchers, matcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientIP) > 0 {
|
||||||
|
switch ns.Address.Address.GetAddress().(type) {
|
||||||
|
case *net.IPOrDomain_Domain:
|
||||||
|
newError("DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||||
|
case *net.IPOrDomain_Ip:
|
||||||
|
newError("DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.server = server
|
||||||
|
client.clientIP = clientIP
|
||||||
|
client.domains = rules
|
||||||
|
client.expectIPs = matchers
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return client, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSimpleClient creates a DNS client with a simple destination.
|
||||||
|
func NewSimpleClient(ctx context.Context, endpoint *net.Endpoint, clientIP net.IP) (*Client, error) {
|
||||||
|
client := &Client{}
|
||||||
|
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||||
|
server, err := NewServer(endpoint.AsDestination(), dispatcher)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create nameserver").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
client.server = server
|
||||||
|
client.clientIP = clientIP
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(clientIP) > 0 {
|
||||||
|
switch endpoint.Address.GetAddress().(type) {
|
||||||
|
case *net.IPOrDomain_Domain:
|
||||||
|
newError("DNS: client ", endpoint.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||||
|
case *net.IPOrDomain_Ip:
|
||||||
|
newError("DNS: client ", endpoint.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, newError("neither IPv4 nor IPv6 is enabled")
|
return client, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalNameServer) Name() string {
|
// Name returns the server name the client manages.
|
||||||
return "localhost"
|
func (c *Client) Name() string {
|
||||||
|
return c.server.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalNameServer() *LocalNameServer {
|
// QueryIP send DNS query to the name server with the client's IP.
|
||||||
newError("DNS: created localhost client").AtInfo().WriteToLog()
|
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, cs CacheStrategy) ([]net.IP, error) {
|
||||||
return &LocalNameServer{
|
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
||||||
client: localdns.New(),
|
ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, cs)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ips, err
|
||||||
}
|
}
|
||||||
|
return c.MatchExpectedIPs(domain, ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
|
||||||
|
func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) {
|
||||||
|
if len(c.expectIPs) == 0 {
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
newIps := []net.IP{}
|
||||||
|
for _, ip := range ips {
|
||||||
|
for _, matcher := range c.expectIPs {
|
||||||
|
if matcher.Match(ip) {
|
||||||
|
newIps = append(newIps, ip)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newIps) == 0 {
|
||||||
|
return nil, errExpectedIPNonMatch
|
||||||
|
}
|
||||||
|
newError("domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name()).AtDebug().WriteToLog()
|
||||||
|
return newIps, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ type DoHNameServer struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDoHNameServer creates DOH client object for remote resolving
|
// NewDoHNameServer creates DOH server object for remote resolving
|
||||||
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.IP) (*DoHNameServer, error) {
|
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher) (*DoHNameServer, error) {
|
||||||
newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog()
|
newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog()
|
||||||
s := baseDOHNameServer(url, "DOH", clientIP)
|
s := baseDOHNameServer(url, "DOH")
|
||||||
|
|
||||||
s.dispatcher = dispatcher
|
s.dispatcher = dispatcher
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
@@ -80,6 +80,12 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Record(&log.AccessMessage{
|
||||||
|
From: "DoH",
|
||||||
|
To: s.dohURL,
|
||||||
|
Status: log.AccessAccepted,
|
||||||
|
Detour: "local",
|
||||||
|
})
|
||||||
|
|
||||||
cc := common.ChainedClosable{}
|
cc := common.ChainedClosable{}
|
||||||
if cw, ok := link.Writer.(common.Closable); ok {
|
if cw, ok := link.Writer.(common.Closable); ok {
|
||||||
@@ -104,9 +110,9 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDoHLocalNameServer creates DOH client object for local resolving
|
// NewDoHLocalNameServer creates DOH client object for local resolving
|
||||||
func NewDoHLocalNameServer(url *url.URL, clientIP net.IP) *DoHNameServer {
|
func NewDoHLocalNameServer(url *url.URL) *DoHNameServer {
|
||||||
url.Scheme = "https"
|
url.Scheme = "https"
|
||||||
s := baseDOHNameServer(url, "DOHL", clientIP)
|
s := baseDOHNameServer(url, "DOHL")
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 90 * time.Second,
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
@@ -136,13 +142,12 @@ func NewDoHLocalNameServer(url *url.URL, clientIP net.IP) *DoHNameServer {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func baseDOHNameServer(url *url.URL, prefix string, clientIP net.IP) *DoHNameServer {
|
func baseDOHNameServer(url *url.URL, prefix string) *DoHNameServer {
|
||||||
s := &DoHNameServer{
|
s := &DoHNameServer{
|
||||||
ips: make(map[string]record),
|
ips: make(map[string]record),
|
||||||
clientIP: clientIP,
|
pub: pubsub.NewService(),
|
||||||
pub: pubsub.NewService(),
|
name: prefix + "//" + url.Host,
|
||||||
name: prefix + "//" + url.Host,
|
dohURL: url.String(),
|
||||||
dohURL: url.String(),
|
|
||||||
}
|
}
|
||||||
s.cleanup = &task.Periodic{
|
s.cleanup = &task.Periodic{
|
||||||
Interval: time.Minute,
|
Interval: time.Minute,
|
||||||
@@ -152,7 +157,7 @@ func baseDOHNameServer(url *url.URL, prefix string, clientIP net.IP) *DoHNameSer
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns client name
|
// Name implements Server.
|
||||||
func (s *DoHNameServer) Name() string {
|
func (s *DoHNameServer) Name() string {
|
||||||
return s.name
|
return s.name
|
||||||
}
|
}
|
||||||
@@ -235,7 +240,7 @@ func (s *DoHNameServer) newReqID() uint16 {
|
|||||||
return uint16(atomic.AddUint32(&s.reqID, 1))
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns_feature.IPOption) {
|
func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
|
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
|
||||||
if s.name+"." == "DOH//"+domain {
|
if s.name+"." == "DOH//"+domain {
|
||||||
@@ -243,7 +248,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP))
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
if d, ok := ctx.Deadline(); ok {
|
if d, ok := ctx.Deadline(); ok {
|
||||||
@@ -264,8 +269,8 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns
|
|||||||
}
|
}
|
||||||
|
|
||||||
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||||
Protocol: "https",
|
Protocol: "https",
|
||||||
//SkipRoutePick: true,
|
SkipDNSResolve: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// forced to use mux for DOH
|
// forced to use mux for DOH
|
||||||
@@ -349,7 +354,7 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(ips) > 0 {
|
if len(ips) > 0 {
|
||||||
return toNetIP(ips), nil
|
return toNetIP(ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastErr != nil {
|
if lastErr != nil {
|
||||||
@@ -363,15 +368,21 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt
|
|||||||
return nil, errRecordNotFound
|
return nil, errRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryIP is called from dns.Server->queryIPTimeout
|
// QueryIP implements Server.
|
||||||
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, error) { // nolint: dupl
|
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, cs CacheStrategy) ([]net.IP, error) { // nolint: dupl
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
ips, err := s.findIPsForDomain(fqdn, option)
|
if cs == CacheStrategy_Cache_DISABLE {
|
||||||
if err != errRecordNotFound {
|
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||||
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
} else {
|
||||||
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
return ips, err
|
if err != errRecordNotFound {
|
||||||
|
if cs == CacheStrategy_Cache_NOERROR && err == nil {
|
||||||
|
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||||
|
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipv4 and ipv6 belong to different subscription groups
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
@@ -400,7 +411,7 @@ func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_f
|
|||||||
}
|
}
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
s.sendQuery(ctx, fqdn, option)
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
60
app/dns/nameserver_doh_test.go
Normal file
60
app/dns/nameserver_doh_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDOHNameServer(t *testing.T) {
|
||||||
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
s := NewDoHLocalNameServer(url)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, CacheStrategy_Cache_ALL)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDOHNameServerWithCache(t *testing.T) {
|
||||||
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
s := NewDoHLocalNameServer(url)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, CacheStrategy_Cache_ALL)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
|
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, CacheStrategy_Cache_ALL)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,11 +16,13 @@ func NewFakeDNSServer() *FakeDNSServer {
|
|||||||
return &FakeDNSServer{}
|
return &FakeDNSServer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FakeDNSName = "FakeDNS"
|
||||||
|
|
||||||
func (FakeDNSServer) Name() string {
|
func (FakeDNSServer) Name() string {
|
||||||
return "FakeDNS"
|
return FakeDNSName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ dns.IPOption) ([]net.IP, error) {
|
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, _ dns.IPOption, _ CacheStrategy) ([]net.IP, error) {
|
||||||
if f.fakeDNSEngine == nil {
|
if f.fakeDNSEngine == nil {
|
||||||
if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) {
|
if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) {
|
||||||
f.fakeDNSEngine = fd
|
f.fakeDNSEngine = fd
|
||||||
@@ -30,9 +32,9 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ dns.IPOpti
|
|||||||
}
|
}
|
||||||
ips := f.fakeDNSEngine.GetFakeIPForDomain(domain)
|
ips := f.fakeDNSEngine.GetFakeIPForDomain(domain)
|
||||||
|
|
||||||
netIP := toNetIP(ips)
|
netIP, err := toNetIP(ips)
|
||||||
if netIP == nil {
|
if err != nil {
|
||||||
return nil, newError("Unable to convert IP to net ip").AtError()
|
return nil, newError("Unable to convert IP to net ip").Base(err).AtError()
|
||||||
}
|
}
|
||||||
|
|
||||||
newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
|
newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
|
||||||
|
|||||||
53
app/dns/nameserver_local.go
Normal file
53
app/dns/nameserver_local.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/dns/localdns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LocalNameServer is an wrapper over local DNS feature.
|
||||||
|
type LocalNameServer struct {
|
||||||
|
client *localdns.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP implements Server.
|
||||||
|
func (s *LocalNameServer) QueryIP(_ context.Context, domain string, _ net.IP, option dns.IPOption, _ CacheStrategy) ([]net.IP, error) {
|
||||||
|
var ips []net.IP
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case option.IPv4Enable && option.IPv6Enable:
|
||||||
|
ips, err = s.client.LookupIP(domain)
|
||||||
|
case option.IPv4Enable:
|
||||||
|
ips, err = s.client.LookupIPv4(domain)
|
||||||
|
case option.IPv6Enable:
|
||||||
|
ips, err = s.client.LookupIPv6(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
newError("Localhost got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
|
func (s *LocalNameServer) Name() string {
|
||||||
|
return "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalNameServer creates localdns server object for directly lookup in system DNS.
|
||||||
|
func NewLocalNameServer() *LocalNameServer {
|
||||||
|
newError("DNS: created localhost client").AtInfo().WriteToLog()
|
||||||
|
return &LocalNameServer{
|
||||||
|
client: localdns.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalDNSClient creates localdns client object for directly lookup in system DNS.
|
||||||
|
func NewLocalDNSClient() *Client {
|
||||||
|
return &Client{server: NewLocalNameServer()}
|
||||||
|
}
|
||||||
@@ -7,17 +7,17 @@ import (
|
|||||||
|
|
||||||
. "github.com/xtls/xray-core/app/dns"
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocalNameServer(t *testing.T) {
|
func TestLocalNameServer(t *testing.T) {
|
||||||
s := NewLocalNameServer()
|
s := NewLocalNameServer()
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
}, CacheStrategy_Cache_ALL)
|
||||||
})
|
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
394
app/dns/nameserver_quic.go
Normal file
394
app/dns/nameserver_quic.go
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lucas-clemente/quic-go"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NextProtoDQ - During connection establishment, DNS/QUIC support is indicated
|
||||||
|
// by selecting the ALPN token "dq" in the crypto handshake.
|
||||||
|
const NextProtoDQ = "doq-i00"
|
||||||
|
|
||||||
|
// QUICNameServer implemented DNS over QUIC
|
||||||
|
type QUICNameServer struct {
|
||||||
|
sync.RWMutex
|
||||||
|
ips map[string]record
|
||||||
|
pub *pubsub.Service
|
||||||
|
cleanup *task.Periodic
|
||||||
|
reqID uint32
|
||||||
|
name string
|
||||||
|
destination net.Destination
|
||||||
|
session quic.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQUICNameServer creates DNS-over-QUIC client object for local resolving
|
||||||
|
func NewQUICNameServer(url *url.URL) (*QUICNameServer, error) {
|
||||||
|
newError("DNS: created Local DNS-over-QUIC client for ", url.String()).AtInfo().WriteToLog()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
port := net.Port(784)
|
||||||
|
if url.Port() != "" {
|
||||||
|
port, err = net.PortFromString(url.Port())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dest := net.UDPDestination(net.DomainAddress(url.Hostname()), port)
|
||||||
|
|
||||||
|
s := &QUICNameServer{
|
||||||
|
ips: make(map[string]record),
|
||||||
|
pub: pubsub.NewService(),
|
||||||
|
name: url.String(),
|
||||||
|
destination: dest,
|
||||||
|
}
|
||||||
|
s.cleanup = &task.Periodic{
|
||||||
|
Interval: time.Minute,
|
||||||
|
Execute: s.Cleanup,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns client name
|
||||||
|
func (s *QUICNameServer) Name() string {
|
||||||
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup clears expired items from cache
|
||||||
|
func (s *QUICNameServer) Cleanup() error {
|
||||||
|
now := time.Now()
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
return newError("nothing to do. stopping...")
|
||||||
|
}
|
||||||
|
|
||||||
|
for domain, record := range s.ips {
|
||||||
|
if record.A != nil && record.A.Expire.Before(now) {
|
||||||
|
record.A = nil
|
||||||
|
}
|
||||||
|
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||||
|
record.AAAA = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.A == nil && record.AAAA == nil {
|
||||||
|
newError(s.name, " cleanup ", domain).AtDebug().WriteToLog()
|
||||||
|
delete(s.ips, domain)
|
||||||
|
} else {
|
||||||
|
s.ips[domain] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
s.ips = make(map[string]record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||||
|
elapsed := time.Since(req.start)
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
rec := s.ips[req.domain]
|
||||||
|
updated := false
|
||||||
|
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
if isNewer(rec.A, ipRec) {
|
||||||
|
rec.A = ipRec
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
addr := make([]net.Address, 0)
|
||||||
|
for _, ip := range ipRec.IP {
|
||||||
|
if len(ip.IP()) == net.IPv6len {
|
||||||
|
addr = append(addr, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipRec.IP = addr
|
||||||
|
if isNewer(rec.AAAA, ipRec) {
|
||||||
|
rec.AAAA = ipRec
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
s.ips[req.domain] = rec
|
||||||
|
}
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
s.pub.Publish(req.domain+"4", nil)
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
s.pub.Publish(req.domain+"6", nil)
|
||||||
|
}
|
||||||
|
s.Unlock()
|
||||||
|
common.Must(s.cleanup.Start())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) newReqID() uint16 {
|
||||||
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
|
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
|
||||||
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
|
|
||||||
|
var deadline time.Time
|
||||||
|
if d, ok := ctx.Deadline(); ok {
|
||||||
|
deadline = d
|
||||||
|
} else {
|
||||||
|
deadline = time.Now().Add(time.Second * 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, req := range reqs {
|
||||||
|
go func(r *dnsRequest) {
|
||||||
|
// generate new context for each req, using same context
|
||||||
|
// may cause reqs all aborted if any one encounter an error
|
||||||
|
dnsCtx := context.Background()
|
||||||
|
|
||||||
|
// reserve internal dns server requested Inbound
|
||||||
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
|
dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
|
||||||
|
}
|
||||||
|
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||||
|
Protocol: "quic",
|
||||||
|
SkipDNSResolve: true,
|
||||||
|
})
|
||||||
|
dnsCtx = log.ContextWithAccessMessage(dnsCtx, &log.AccessMessage{
|
||||||
|
From: "DoQ",
|
||||||
|
To: s.name,
|
||||||
|
Status: log.AccessAccepted,
|
||||||
|
Reason: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
b, err := dns.PackMessage(r.msg)
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to pack dns query").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.openStream(dnsCtx)
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to open quic session").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to send query").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = conn.Close()
|
||||||
|
|
||||||
|
respBuf := buf.New()
|
||||||
|
defer respBuf.Release()
|
||||||
|
n, err := respBuf.ReadFrom(conn)
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
newError("failed to read response").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rec, err := parseResponse(respBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to handle response").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.updateIP(r, rec)
|
||||||
|
}(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||||
|
s.RLock()
|
||||||
|
record, found := s.ips[domain]
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var ips []net.Address
|
||||||
|
var lastErr error
|
||||||
|
if option.IPv6Enable && record.AAAA != nil && record.AAAA.RCode == dnsmessage.RCodeSuccess {
|
||||||
|
aaaa, err := record.AAAA.getIPs()
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
ips = append(ips, aaaa...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv4Enable && record.A != nil && record.A.RCode == dnsmessage.RCodeSuccess {
|
||||||
|
a, err := record.A.getIPs()
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
ips = append(ips, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return toNetIP(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastErr != nil {
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP is called from dns.Server->queryIPTimeout
|
||||||
|
func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, cs CacheStrategy) ([]net.IP, error) {
|
||||||
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
|
if cs == CacheStrategy_Cache_DISABLE {
|
||||||
|
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||||
|
} else {
|
||||||
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
|
if err != errRecordNotFound {
|
||||||
|
if cs == CacheStrategy_Cache_NOERROR && err == nil {
|
||||||
|
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||||
|
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
|
var sub4, sub6 *pubsub.Subscriber
|
||||||
|
if option.IPv4Enable {
|
||||||
|
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||||
|
defer sub4.Close()
|
||||||
|
}
|
||||||
|
if option.IPv6Enable {
|
||||||
|
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||||
|
defer sub6.Close()
|
||||||
|
}
|
||||||
|
done := make(chan interface{})
|
||||||
|
go func() {
|
||||||
|
if sub4 != nil {
|
||||||
|
select {
|
||||||
|
case <-sub4.Wait():
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sub6 != nil {
|
||||||
|
select {
|
||||||
|
case <-sub6.Wait():
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
|
if err != errRecordNotFound {
|
||||||
|
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSQueried, time.Since(start), err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isActive(s quic.Session) bool {
|
||||||
|
select {
|
||||||
|
case <-s.Context().Done():
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) getSession() (quic.Session, error) {
|
||||||
|
var session quic.Session
|
||||||
|
s.RLock()
|
||||||
|
session = s.session
|
||||||
|
if session != nil && isActive(session) {
|
||||||
|
s.RUnlock()
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
if session != nil {
|
||||||
|
// we're recreating the session, let's create a new one
|
||||||
|
_ = session.CloseWithError(0, "")
|
||||||
|
}
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
session, err = s.openSession()
|
||||||
|
if err != nil {
|
||||||
|
// This does not look too nice, but QUIC (or maybe quic-go)
|
||||||
|
// doesn't seem stable enough.
|
||||||
|
// Maybe retransmissions aren't fully implemented in quic-go?
|
||||||
|
// Anyways, the simple solution is to make a second try when
|
||||||
|
// it fails to open the QUIC session.
|
||||||
|
session, err = s.openSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.session = session
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) openSession() (quic.Session, error) {
|
||||||
|
tlsConfig := tls.Config{}
|
||||||
|
quicConfig := &quic.Config{}
|
||||||
|
|
||||||
|
session, err := quic.DialAddrContext(context.Background(), s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto("http/1.1", http2.NextProtoTLS, NextProtoDQ)), quicConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) openStream(ctx context.Context) (quic.Stream, error) {
|
||||||
|
session, err := s.getSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// open a new stream
|
||||||
|
return session.OpenStreamSync(ctx)
|
||||||
|
}
|
||||||
60
app/dns/nameserver_quic_test.go
Normal file
60
app/dns/nameserver_quic_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQUICNameServer(t *testing.T) {
|
||||||
|
url, err := url.Parse("quic://dns.adguard.com")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewQUICNameServer(url)
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, CacheStrategy_Cache_ALL)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQUICNameServerWithCache(t *testing.T) {
|
||||||
|
url, err := url.Parse("quic://dns.adguard.com")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewQUICNameServer(url)
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, CacheStrategy_Cache_ALL)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, CacheStrategy_Cache_ALL)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -32,10 +31,10 @@ type ClassicNameServer struct {
|
|||||||
udpServer *udp.Dispatcher
|
udpServer *udp.Dispatcher
|
||||||
cleanup *task.Periodic
|
cleanup *task.Periodic
|
||||||
reqID uint32
|
reqID uint32
|
||||||
clientIP net.IP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, clientIP net.IP) *ClassicNameServer {
|
// NewClassicNameServer creates udp server object for remote resolving.
|
||||||
|
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher) *ClassicNameServer {
|
||||||
// default to 53 if unspecific
|
// default to 53 if unspecific
|
||||||
if address.Port == 0 {
|
if address.Port == 0 {
|
||||||
address.Port = net.Port(53)
|
address.Port = net.Port(53)
|
||||||
@@ -45,7 +44,6 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher
|
|||||||
address: address,
|
address: address,
|
||||||
ips: make(map[string]record),
|
ips: make(map[string]record),
|
||||||
requests: make(map[uint16]dnsRequest),
|
requests: make(map[uint16]dnsRequest),
|
||||||
clientIP: clientIP,
|
|
||||||
pub: pubsub.NewService(),
|
pub: pubsub.NewService(),
|
||||||
name: strings.ToUpper(address.String()),
|
name: strings.ToUpper(address.String()),
|
||||||
}
|
}
|
||||||
@@ -58,10 +56,12 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
func (s *ClassicNameServer) Name() string {
|
func (s *ClassicNameServer) Name() string {
|
||||||
return s.name
|
return s.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup clears expired items from cache
|
||||||
func (s *ClassicNameServer) Cleanup() error {
|
func (s *ClassicNameServer) Cleanup() error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
s.Lock()
|
s.Lock()
|
||||||
@@ -103,6 +103,7 @@ func (s *ClassicNameServer) Cleanup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleResponse handles udp response packet from remote DNS server.
|
||||||
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
||||||
ipRec, err := parseResponse(packet.Payload.Bytes())
|
ipRec, err := parseResponse(packet.Payload.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -180,10 +181,10 @@ func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) {
|
|||||||
s.requests[id] = *req
|
s.requests[id] = *req
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option dns_feature.IPOption) {
|
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
|
||||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP))
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
|
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
s.addPendingRequest(req)
|
s.addPendingRequest(req)
|
||||||
@@ -192,7 +193,6 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option
|
|||||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
udpCtx = session.ContextWithInbound(udpCtx, inbound)
|
udpCtx = session.ContextWithInbound(udpCtx, inbound)
|
||||||
}
|
}
|
||||||
udpCtx = internet.ContextWithLookupDomain(udpCtx, internet.LookupDomainFromContext(ctx))
|
|
||||||
udpCtx = session.ContextWithContent(udpCtx, &session.Content{
|
udpCtx = session.ContextWithContent(udpCtx, &session.Content{
|
||||||
Protocol: "dns",
|
Protocol: "dns",
|
||||||
})
|
})
|
||||||
@@ -234,7 +234,7 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(ips) > 0 {
|
if len(ips) > 0 {
|
||||||
return toNetIP(ips), nil
|
return toNetIP(ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastErr != nil {
|
if lastErr != nil {
|
||||||
@@ -245,14 +245,20 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
// QueryIP implements Server.
|
// QueryIP implements Server.
|
||||||
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, cs CacheStrategy) ([]net.IP, error) {
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
ips, err := s.findIPsForDomain(fqdn, option)
|
if cs == CacheStrategy_Cache_DISABLE {
|
||||||
if err != errRecordNotFound {
|
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||||
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
} else {
|
||||||
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
return ips, err
|
if err != errRecordNotFound {
|
||||||
|
if cs == CacheStrategy_Cache_NOERROR && err == nil {
|
||||||
|
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||||
|
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipv4 and ipv6 belong to different subscription groups
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
@@ -281,7 +287,7 @@ func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option d
|
|||||||
}
|
}
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
s.sendQuery(ctx, fqdn, option)
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
16
app/dns/options.go
Normal file
16
app/dns/options.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/features/dns"
|
||||||
|
|
||||||
|
func isIPQuery(o *dns.IPOption) bool {
|
||||||
|
return o.IPv4Enable || o.IPv6Enable
|
||||||
|
}
|
||||||
|
|
||||||
|
func canQueryOnClient(o *dns.IPOption, c *Client) bool {
|
||||||
|
isIPClient := !(c.Name() == FakeDNSName)
|
||||||
|
return isIPClient && isIPQuery(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isQuery(o *dns.IPOption) bool {
|
||||||
|
return !(o.IPv4Enable || o.IPv6Enable || o.FakeEnable)
|
||||||
|
}
|
||||||
@@ -1,439 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/router"
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/session"
|
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
|
||||||
"github.com/xtls/xray-core/common/uuid"
|
|
||||||
core "github.com/xtls/xray-core/core"
|
|
||||||
"github.com/xtls/xray-core/features"
|
|
||||||
"github.com/xtls/xray-core/features/dns"
|
|
||||||
"github.com/xtls/xray-core/features/routing"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Server is a DNS rely server.
|
|
||||||
type Server struct {
|
|
||||||
sync.Mutex
|
|
||||||
hosts *StaticHosts
|
|
||||||
clientIP net.IP
|
|
||||||
clients []Client // clientIdx -> Client
|
|
||||||
ctx context.Context
|
|
||||||
ipIndexMap []*MultiGeoIPMatcher // clientIdx -> *MultiGeoIPMatcher
|
|
||||||
domainRules [][]string // clientIdx -> domainRuleIdx -> DomainRule
|
|
||||||
domainMatcher strmatcher.IndexMatcher
|
|
||||||
matcherInfos []DomainMatcherInfo // matcherIdx -> DomainMatcherInfo
|
|
||||||
tag string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
|
|
||||||
type DomainMatcherInfo struct {
|
|
||||||
clientIdx uint16
|
|
||||||
domainRuleIdx uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiGeoIPMatcher for match
|
|
||||||
type MultiGeoIPMatcher struct {
|
|
||||||
matchers []*router.GeoIPMatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
var errExpectedIPNonMatch = errors.New("expectIPs not match")
|
|
||||||
|
|
||||||
// Match check ip match
|
|
||||||
func (c *MultiGeoIPMatcher) Match(ip net.IP) bool {
|
|
||||||
for _, matcher := range c.matchers {
|
|
||||||
if matcher.Match(ip) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasMatcher check has matcher
|
|
||||||
func (c *MultiGeoIPMatcher) HasMatcher() bool {
|
|
||||||
return len(c.matchers) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateRandomTag() string {
|
|
||||||
id := uuid.New()
|
|
||||||
return "xray.system." + id.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new DNS server with given configuration.
|
|
||||||
func New(ctx context.Context, config *Config) (*Server, error) {
|
|
||||||
server := &Server{
|
|
||||||
clients: make([]Client, 0, len(config.NameServers)+len(config.NameServer)),
|
|
||||||
ctx: ctx,
|
|
||||||
tag: config.Tag,
|
|
||||||
}
|
|
||||||
if server.tag == "" {
|
|
||||||
server.tag = generateRandomTag()
|
|
||||||
}
|
|
||||||
if len(config.ClientIp) > 0 {
|
|
||||||
if len(config.ClientIp) != net.IPv4len && len(config.ClientIp) != net.IPv6len {
|
|
||||||
return nil, newError("unexpected IP length", len(config.ClientIp))
|
|
||||||
}
|
|
||||||
server.clientIP = net.IP(config.ClientIp)
|
|
||||||
}
|
|
||||||
|
|
||||||
hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to create hosts").Base(err)
|
|
||||||
}
|
|
||||||
server.hosts = hosts
|
|
||||||
|
|
||||||
addNameServer := func(ns *NameServer) int {
|
|
||||||
endpoint := ns.Address
|
|
||||||
address := endpoint.Address.AsAddress()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case address.Family().IsDomain() && address.Domain() == "localhost":
|
|
||||||
server.clients = append(server.clients, NewLocalNameServer())
|
|
||||||
// Priotize local domains with specific TLDs or without any dot to local DNS
|
|
||||||
// References:
|
|
||||||
// https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml
|
|
||||||
// https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan
|
|
||||||
localTLDsAndDotlessDomains := []*NameServer_PriorityDomain{
|
|
||||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]+$"}, // This will only match domains without any dot
|
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "localhost"},
|
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "lan"},
|
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "home.arpa"},
|
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "example"},
|
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "invalid"},
|
|
||||||
{Type: DomainMatchingType_Subdomain, Domain: "test"},
|
|
||||||
}
|
|
||||||
ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
|
|
||||||
|
|
||||||
case address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https+local://"):
|
|
||||||
// URI schemed string treated as domain
|
|
||||||
// DOH Local mode
|
|
||||||
u, err := url.Parse(address.Domain())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(newError("DNS config error").Base(err))
|
|
||||||
}
|
|
||||||
server.clients = append(server.clients, NewDoHLocalNameServer(u, server.clientIP))
|
|
||||||
|
|
||||||
case address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https://"):
|
|
||||||
// DOH Remote mode
|
|
||||||
u, err := url.Parse(address.Domain())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(newError("DNS config error").Base(err))
|
|
||||||
}
|
|
||||||
idx := len(server.clients)
|
|
||||||
server.clients = append(server.clients, nil)
|
|
||||||
|
|
||||||
// need the core dispatcher, register DOHClient at callback
|
|
||||||
common.Must(core.RequireFeatures(ctx, func(d routing.Dispatcher) {
|
|
||||||
c, err := NewDoHNameServer(u, d, server.clientIP)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(newError("DNS config error").Base(err))
|
|
||||||
}
|
|
||||||
server.clients[idx] = c
|
|
||||||
}))
|
|
||||||
|
|
||||||
case address.Family().IsDomain() && address.Domain() == "fakedns":
|
|
||||||
server.clients = append(server.clients, NewFakeDNSServer())
|
|
||||||
|
|
||||||
default:
|
|
||||||
// UDP classic DNS mode
|
|
||||||
dest := endpoint.AsDestination()
|
|
||||||
if dest.Network == net.Network_Unknown {
|
|
||||||
dest.Network = net.Network_UDP
|
|
||||||
}
|
|
||||||
if dest.Network == net.Network_UDP {
|
|
||||||
idx := len(server.clients)
|
|
||||||
server.clients = append(server.clients, nil)
|
|
||||||
|
|
||||||
common.Must(core.RequireFeatures(ctx, func(d routing.Dispatcher) {
|
|
||||||
server.clients[idx] = NewClassicNameServer(dest, d, server.clientIP)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
server.ipIndexMap = append(server.ipIndexMap, nil)
|
|
||||||
return len(server.clients) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.NameServers) > 0 {
|
|
||||||
features.PrintDeprecatedFeatureWarning("simple DNS server")
|
|
||||||
for _, destPB := range config.NameServers {
|
|
||||||
addNameServer(&NameServer{Address: destPB})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.NameServer) > 0 {
|
|
||||||
clientIndices := []int{}
|
|
||||||
domainRuleCount := 0
|
|
||||||
for _, ns := range config.NameServer {
|
|
||||||
idx := addNameServer(ns)
|
|
||||||
clientIndices = append(clientIndices, idx)
|
|
||||||
domainRuleCount += len(ns.PrioritizedDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
domainRules := make([][]string, len(server.clients))
|
|
||||||
domainMatcher := &strmatcher.MatcherGroup{}
|
|
||||||
matcherInfos := make([]DomainMatcherInfo, domainRuleCount+1) // matcher index starts from 1
|
|
||||||
var geoIPMatcherContainer router.GeoIPMatcherContainer
|
|
||||||
for nidx, ns := range config.NameServer {
|
|
||||||
idx := clientIndices[nidx]
|
|
||||||
|
|
||||||
// Establish domain rule matcher
|
|
||||||
rules := []string{}
|
|
||||||
ruleCurr := 0
|
|
||||||
ruleIter := 0
|
|
||||||
for _, domain := range ns.PrioritizedDomain {
|
|
||||||
matcher, err := toStrMatcher(domain.Type, domain.Domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to create prioritized domain").Base(err).AtWarning()
|
|
||||||
}
|
|
||||||
midx := domainMatcher.Add(matcher)
|
|
||||||
if midx >= uint32(len(matcherInfos)) { // This rarely happens according to current matcher's implementation
|
|
||||||
newError("expanding domain matcher info array to size ", midx, " when adding ", matcher).AtDebug().WriteToLog()
|
|
||||||
matcherInfos = append(matcherInfos, make([]DomainMatcherInfo, midx-uint32(len(matcherInfos))+1)...)
|
|
||||||
}
|
|
||||||
info := &matcherInfos[midx]
|
|
||||||
info.clientIdx = uint16(idx)
|
|
||||||
if ruleCurr < len(ns.OriginalRules) {
|
|
||||||
info.domainRuleIdx = uint16(ruleCurr)
|
|
||||||
rule := ns.OriginalRules[ruleCurr]
|
|
||||||
if ruleCurr >= len(rules) {
|
|
||||||
rules = append(rules, rule.Rule)
|
|
||||||
}
|
|
||||||
ruleIter++
|
|
||||||
if ruleIter >= int(rule.Size) {
|
|
||||||
ruleIter = 0
|
|
||||||
ruleCurr++
|
|
||||||
}
|
|
||||||
} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)
|
|
||||||
info.domainRuleIdx = uint16(len(rules))
|
|
||||||
rules = append(rules, matcher.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
domainRules[idx] = rules
|
|
||||||
|
|
||||||
// only add to ipIndexMap if GeoIP is configured
|
|
||||||
if len(ns.Geoip) > 0 {
|
|
||||||
var matchers []*router.GeoIPMatcher
|
|
||||||
for _, geoip := range ns.Geoip {
|
|
||||||
matcher, err := geoIPMatcherContainer.Add(geoip)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to create ip matcher").Base(err).AtWarning()
|
|
||||||
}
|
|
||||||
matchers = append(matchers, matcher)
|
|
||||||
}
|
|
||||||
matcher := &MultiGeoIPMatcher{matchers: matchers}
|
|
||||||
server.ipIndexMap[idx] = matcher
|
|
||||||
}
|
|
||||||
}
|
|
||||||
server.domainRules = domainRules
|
|
||||||
server.domainMatcher = domainMatcher
|
|
||||||
server.matcherInfos = matcherInfos
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(server.clients) == 0 {
|
|
||||||
server.clients = append(server.clients, NewLocalNameServer())
|
|
||||||
server.ipIndexMap = append(server.ipIndexMap, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return server, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type implements common.HasType.
|
|
||||||
func (*Server) Type() interface{} {
|
|
||||||
return dns.ClientType()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start implements common.Runnable.
|
|
||||||
func (s *Server) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements common.Closable.
|
|
||||||
func (s *Server) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) IsOwnLink(ctx context.Context) bool {
|
|
||||||
inbound := session.InboundFromContext(ctx)
|
|
||||||
return inbound != nil && inbound.Tag == s.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match check dns ip match geoip
|
|
||||||
func (s *Server) Match(idx int, client Client, domain string, ips []net.IP) ([]net.IP, error) {
|
|
||||||
var matcher *MultiGeoIPMatcher
|
|
||||||
if idx < len(s.ipIndexMap) {
|
|
||||||
matcher = s.ipIndexMap[idx]
|
|
||||||
}
|
|
||||||
if matcher == nil {
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matcher.HasMatcher() {
|
|
||||||
newError("domain ", domain, " server has no valid matcher: ", client.Name(), " idx:", idx).AtDebug().WriteToLog()
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newIps := []net.IP{}
|
|
||||||
for _, ip := range ips {
|
|
||||||
if matcher.Match(ip) {
|
|
||||||
newIps = append(newIps, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(newIps) == 0 {
|
|
||||||
return nil, errExpectedIPNonMatch
|
|
||||||
}
|
|
||||||
newError("domain ", domain, " expectIPs ", newIps, " matched at server ", client.Name(), " idx:", idx).AtDebug().WriteToLog()
|
|
||||||
return newIps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) queryIPTimeout(idx int, client Client, domain string, option dns.IPOption) ([]net.IP, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(s.ctx, time.Second*4)
|
|
||||||
if len(s.tag) > 0 {
|
|
||||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
|
||||||
Tag: s.tag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
ctx = internet.ContextWithLookupDomain(ctx, domain)
|
|
||||||
ips, err := client.QueryIP(ctx, domain, option)
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ips, err = s.Match(idx, client, domain, ips)
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) lookupStatic(domain string, option dns.IPOption, depth int32) []net.Address {
|
|
||||||
ips := s.hosts.LookupIP(domain, option)
|
|
||||||
if ips == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if ips[0].Family().IsDomain() && depth < 5 {
|
|
||||||
if newIPs := s.lookupStatic(ips[0].Domain(), option, depth+1); newIPs != nil {
|
|
||||||
return newIPs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ips
|
|
||||||
}
|
|
||||||
|
|
||||||
func toNetIP(ips []net.Address) []net.IP {
|
|
||||||
if len(ips) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
netips := make([]net.IP, 0, len(ips))
|
|
||||||
for _, ip := range ips {
|
|
||||||
netips = append(netips, ip.IP())
|
|
||||||
}
|
|
||||||
return netips
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupIP implements dns.Client.
|
|
||||||
func (s *Server) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
|
|
||||||
if domain == "" {
|
|
||||||
return nil, newError("empty domain name")
|
|
||||||
}
|
|
||||||
domain = strings.ToLower(domain)
|
|
||||||
|
|
||||||
// normalize the FQDN form query
|
|
||||||
if strings.HasSuffix(domain, ".") {
|
|
||||||
domain = domain[:len(domain)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
ips := s.lookupStatic(domain, option, 0)
|
|
||||||
if ips != nil && ips[0].Family().IsIP() {
|
|
||||||
newError("returning ", len(ips), " IPs for domain ", domain).WriteToLog()
|
|
||||||
return toNetIP(ips), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ips != nil && ips[0].Family().IsDomain() {
|
|
||||||
newdomain := ips[0].Domain()
|
|
||||||
newError("domain replaced: ", domain, " -> ", newdomain).WriteToLog()
|
|
||||||
domain = newdomain
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastErr error
|
|
||||||
var matchedClient Client
|
|
||||||
if s.domainMatcher != nil {
|
|
||||||
indices := s.domainMatcher.Match(domain)
|
|
||||||
domainRules := []string{}
|
|
||||||
matchingDNS := []string{}
|
|
||||||
for _, idx := range indices {
|
|
||||||
info := s.matcherInfos[idx]
|
|
||||||
rule := s.domainRules[info.clientIdx][info.domainRuleIdx]
|
|
||||||
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", rule, info.clientIdx))
|
|
||||||
matchingDNS = append(matchingDNS, s.clients[info.clientIdx].Name())
|
|
||||||
}
|
|
||||||
if len(domainRules) > 0 {
|
|
||||||
newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog()
|
|
||||||
}
|
|
||||||
if len(matchingDNS) > 0 {
|
|
||||||
newError("domain ", domain, " uses following DNS first: ", matchingDNS).AtDebug().WriteToLog()
|
|
||||||
}
|
|
||||||
for _, idx := range indices {
|
|
||||||
clientIdx := int(s.matcherInfos[idx].clientIdx)
|
|
||||||
matchedClient = s.clients[clientIdx]
|
|
||||||
if !option.FakeEnable && strings.EqualFold(matchedClient.Name(), "FakeDNS") {
|
|
||||||
newError("skip DNS resolution for domain ", domain, " at server ", matchedClient.Name()).AtDebug().WriteToLog()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ips, err := s.queryIPTimeout(clientIdx, matchedClient, domain, option)
|
|
||||||
if len(ips) > 0 {
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
if err == dns.ErrEmptyResponse {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
newError("failed to lookup ip for domain ", domain, " at server ", matchedClient.Name()).Base(err).WriteToLog()
|
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, client := range s.clients {
|
|
||||||
if client == matchedClient {
|
|
||||||
newError("domain ", domain, " at server ", client.Name(), " idx:", idx, " already lookup failed, just ignore").AtDebug().WriteToLog()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
|
||||||
newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).AtDebug().WriteToLog()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ips, err := s.queryIPTimeout(idx, client, domain, option)
|
|
||||||
if len(ips) > 0 {
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog()
|
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, newError("returning nil for domain ", domain).Base(lastErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
|
||||||
return New(ctx, config.(*Config))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
package proxyman
|
package proxyman
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
|
|
||||||
func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
|
func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
|
||||||
if s == nil || s.Concurrency == nil {
|
if s == nil || s.Concurrency == nil {
|
||||||
return 3
|
return 3
|
||||||
@@ -37,3 +44,32 @@ func (c *ReceiverConfig) GetEffectiveSniffingSettings() *SniffingConfig {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SniffingMatcher struct {
|
||||||
|
ExDomain *domain.DomainMatcher
|
||||||
|
ExIP *geoip.MultiGeoIPMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSniffingMatcher(sc *SniffingConfig) (*SniffingMatcher, error) {
|
||||||
|
m := new(SniffingMatcher)
|
||||||
|
|
||||||
|
if sc == nil {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc.DomainsExcluded != nil {
|
||||||
|
exDomain, err := domain.NewDomainMatcher(sc.DomainsExcluded)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to parse domain").Base(err)
|
||||||
|
}
|
||||||
|
m.ExDomain = exDomain
|
||||||
|
}
|
||||||
|
if sc.IpsExcluded != nil {
|
||||||
|
exIP, err := geoip.NewMultiGeoIPMatcher(sc.IpsExcluded, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to parse ip").Base(err)
|
||||||
|
}
|
||||||
|
m.ExIP = exIP
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.25.0
|
||||||
// protoc (unknown)
|
// protoc v3.15.6
|
||||||
// source: app/proxyman/config.proto
|
// source: app/proxyman/config.proto
|
||||||
|
|
||||||
package proxyman
|
package proxyman
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
proto "github.com/golang/protobuf/proto"
|
||||||
|
domain "github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
geoip "github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
net "github.com/xtls/xray-core/common/net"
|
net "github.com/xtls/xray-core/common/net"
|
||||||
serial "github.com/xtls/xray-core/common/serial"
|
serial "github.com/xtls/xray-core/common/serial"
|
||||||
internet "github.com/xtls/xray-core/transport/internet"
|
internet "github.com/xtls/xray-core/transport/internet"
|
||||||
@@ -240,8 +242,9 @@ type SniffingConfig struct {
|
|||||||
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
||||||
// Override target destination if sniff'ed protocol is in the given list.
|
// Override target destination if sniff'ed protocol is in the given list.
|
||||||
// Supported values are "http", "tls", "fakedns".
|
// Supported values are "http", "tls", "fakedns".
|
||||||
DestinationOverride []string `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"`
|
DestinationOverride []string `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"`
|
||||||
DomainsExcluded []string `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,omitempty"`
|
DomainsExcluded []*domain.Domain `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,omitempty"`
|
||||||
|
IpsExcluded []*geoip.GeoIP `protobuf:"bytes,5,rep,name=ips_excluded,json=ipsExcluded,proto3" json:"ips_excluded,omitempty"`
|
||||||
// Whether should only try to sniff metadata without waiting for client input.
|
// Whether should only try to sniff metadata without waiting for client input.
|
||||||
// Can be used to support SMTP like protocol where server send the first message.
|
// Can be used to support SMTP like protocol where server send the first message.
|
||||||
MetadataOnly bool `protobuf:"varint,4,opt,name=metadata_only,json=metadataOnly,proto3" json:"metadata_only,omitempty"`
|
MetadataOnly bool `protobuf:"varint,4,opt,name=metadata_only,json=metadataOnly,proto3" json:"metadata_only,omitempty"`
|
||||||
@@ -293,13 +296,20 @@ func (x *SniffingConfig) GetDestinationOverride() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SniffingConfig) GetDomainsExcluded() []string {
|
func (x *SniffingConfig) GetDomainsExcluded() []*domain.Domain {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.DomainsExcluded
|
return x.DomainsExcluded
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SniffingConfig) GetIpsExcluded() []*geoip.GeoIP {
|
||||||
|
if x != nil {
|
||||||
|
return x.IpsExcluded
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *SniffingConfig) GetMetadataOnly() bool {
|
func (x *SniffingConfig) GetMetadataOnly() bool {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.MetadataOnly
|
return x.MetadataOnly
|
||||||
@@ -738,133 +748,144 @@ var File_app_proxyman_config_proto protoreflect.FileDescriptor
|
|||||||
var file_app_proxyman_config_proto_rawDesc = []byte{
|
var file_app_proxyman_config_proto_rawDesc = []byte{
|
||||||
0x0a, 0x19, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2f, 0x63,
|
0x0a, 0x19, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2f, 0x63,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x78, 0x72, 0x61,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x78, 0x72, 0x61,
|
||||||
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x1a, 0x18,
|
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x1a, 0x22,
|
||||||
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65,
|
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x64,
|
||||||
0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
|
0x74, 0x6f, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68,
|
||||||
0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
0x65, 0x72, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x70,
|
||||||
0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
|
||||||
0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f,
|
0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15,
|
||||||
0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72,
|
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e,
|
||||||
0x6f, 0x74, 0x6f, 0x22, 0x0f, 0x0a, 0x0d, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f,
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
|
||||||
0x6e, 0x66, 0x69, 0x67, 0x22, 0xae, 0x03, 0x0a, 0x12, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74,
|
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x3e, 0x0a, 0x04, 0x74,
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73,
|
||||||
0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73,
|
||||||
|
0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0f, 0x0a, 0x0d, 0x49, 0x6e, 0x62,
|
||||||
|
0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xae, 0x03, 0x0a, 0x12, 0x41,
|
||||||
|
0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
|
||||||
|
0x79, 0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||||
|
0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
|
||||||
|
0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74,
|
||||||
|
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70,
|
||||||
|
0x65, 0x12, 0x65, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79,
|
||||||
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||||
|
0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63,
|
||||||
|
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c,
|
||||||
|
0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||||
|
0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e,
|
||||||
|
0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x59, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72,
|
||||||
|
0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||||
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c,
|
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c,
|
||||||
0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||||
0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x65, 0x0a, 0x0b, 0x63,
|
0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
|
||||||
0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
0x65, 0x67, 0x79, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x07, 0x72, 0x65, 0x66, 0x72,
|
||||||
0x32, 0x43, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78,
|
0x65, 0x73, 0x68, 0x1a, 0x35, 0x0a, 0x1d, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53,
|
0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72,
|
||||||
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
|
0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20,
|
||||||
0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72,
|
0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x31, 0x0a, 0x19, 0x41, 0x6c,
|
||||||
0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
|
||||||
0x63, 0x79, 0x12, 0x59, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20,
|
|
||||||
0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70,
|
|
||||||
0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
|
|
||||||
0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63,
|
|
||||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x66,
|
|
||||||
0x72, 0x65, 0x73, 0x68, 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x1a, 0x35, 0x0a,
|
|
||||||
0x1d, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
|
|
||||||
0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14,
|
|
||||||
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76,
|
|
||||||
0x61, 0x6c, 0x75, 0x65, 0x1a, 0x31, 0x0a, 0x19, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
|
|
||||||
0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73,
|
|
||||||
0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
|
|
||||||
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
|
|
||||||
0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52,
|
|
||||||
0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x78, 0x74, 0x65, 0x72,
|
|
||||||
0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0xad, 0x01, 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69,
|
|
||||||
0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62,
|
|
||||||
0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c,
|
|
||||||
0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f,
|
|
||||||
0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
|
|
||||||
0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65,
|
|
||||||
0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73,
|
|
||||||
0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52,
|
|
||||||
0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64,
|
|
||||||
0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x6c,
|
|
||||||
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
|
||||||
0x61, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x90, 0x04, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76,
|
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74,
|
|
||||||
0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78,
|
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50,
|
|
||||||
0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61,
|
|
||||||
0x6e, 0x67, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20,
|
|
||||||
0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
|
|
||||||
0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
|
||||||
0x52, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x12, 0x56, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f,
|
|
||||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
|
|
||||||
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61,
|
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c,
|
|
||||||
0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||||
0x12, 0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69,
|
0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||||
0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x0a,
|
||||||
0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10,
|
||||||
0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x10, 0x01, 0x12, 0x0c, 0x0a,
|
||||||
0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
|
0x08, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0x96, 0x02, 0x0a, 0x0e,
|
||||||
0x12, 0x40, 0x0a, 0x1c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67,
|
0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18,
|
||||||
0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f,
|
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74,
|
||||||
0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69,
|
0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65,
|
||||||
0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x76, 0x65,
|
0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
|
||||||
0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x78, 0x72,
|
0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x4d, 0x0a, 0x10, 0x64,
|
||||||
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e,
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18,
|
||||||
0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x42, 0x02,
|
0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
0x18, 0x01, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69,
|
0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61,
|
||||||
0x64, 0x65, 0x12, 0x4e, 0x0a, 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x5f, 0x73,
|
0x69, 0x6e, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e,
|
0x6e, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0c, 0x69, 0x70,
|
||||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61,
|
0x73, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b,
|
||||||
0x6e, 0x2e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d,
|
||||||
0x52, 0x10, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
|
0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f,
|
||||||
0x67, 0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xc0, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x62,
|
0x49, 0x50, 0x52, 0x0b, 0x69, 0x70, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x12,
|
||||||
0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x6c, 0x79,
|
||||||
0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||||
0x74, 0x61, 0x67, 0x12, 0x4d, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f,
|
0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x90, 0x04, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
|
||||||
0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20,
|
0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f,
|
||||||
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72,
|
0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72,
|
||||||
0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f,
|
||||||
0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
|
0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e,
|
||||||
0x67, 0x73, 0x12, 0x47, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74,
|
0x67, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
|
0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e,
|
0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52,
|
||||||
0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72,
|
0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x12, 0x56, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x63,
|
||||||
0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f,
|
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03,
|
||||||
0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xb0, 0x02,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
|
||||||
0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d,
|
0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74,
|
||||||
0x0a, 0x03, 0x76, 0x69, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72,
|
0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c, 0x6c,
|
||||||
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50,
|
0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12,
|
||||||
0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61, 0x12, 0x4e, 0x0a,
|
0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e,
|
||||||
0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
|
0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72,
|
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||||
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
|
0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
||||||
0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73,
|
0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12,
|
||||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4b, 0x0a,
|
0x40, 0x0a, 0x1c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69,
|
||||||
0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
|
0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
||||||
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
|
0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72,
|
||||||
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
|
0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f,
|
0x6e, 0x12, 0x4e, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72,
|
||||||
0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x6d, 0x75,
|
0x72, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61,
|
||||||
0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
|
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4b,
|
||||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x42, 0x02, 0x18,
|
||||||
0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69,
|
0x01, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
|
||||||
0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d,
|
0x65, 0x12, 0x4e, 0x0a, 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65,
|
||||||
0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
|
0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78,
|
||||||
0x22, 0x50, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67,
|
|
||||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
|
||||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
|
||||||
0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18,
|
|
||||||
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
|
||||||
0x63, 0x79, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x63, 0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07,
|
|
||||||
0x0a, 0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
|
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e,
|
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e,
|
||||||
0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
|
0x2e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
||||||
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70,
|
0x10, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
|
||||||
0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61,
|
0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xc0, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x62, 0x6f,
|
||||||
0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06,
|
0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74,
|
||||||
|
0x61, 0x67, 0x12, 0x4d, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73,
|
||||||
|
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
|
||||||
|
0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69,
|
||||||
|
0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52,
|
||||||
|
0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
|
||||||
|
0x73, 0x12, 0x47, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69,
|
||||||
|
0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||||
|
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54,
|
||||||
|
0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f,
|
||||||
|
0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, 0x75,
|
||||||
|
0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xb0, 0x02, 0x0a,
|
||||||
|
0x0c, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a,
|
||||||
|
0x03, 0x76, 0x69, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61,
|
||||||
|
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f,
|
||||||
|
0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61, 0x12, 0x4e, 0x0a, 0x0f,
|
||||||
|
0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
|
||||||
|
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
|
||||||
|
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
|
||||||
|
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74,
|
||||||
|
0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4b, 0x0a, 0x0e,
|
||||||
|
0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03,
|
||||||
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
|
||||||
|
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x50,
|
||||||
|
0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78,
|
||||||
|
0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x6d, 0x75, 0x6c,
|
||||||
|
0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
|
||||||
|
0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
||||||
|
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70,
|
||||||
|
0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75,
|
||||||
|
0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22,
|
||||||
|
0x50, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43,
|
||||||
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12,
|
||||||
|
0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02,
|
||||||
|
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63,
|
||||||
|
0x79, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
||||||
|
0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a,
|
||||||
|
0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
|
||||||
|
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50,
|
||||||
|
0x01, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74,
|
||||||
|
0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70,
|
||||||
|
0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79,
|
||||||
|
0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -894,33 +915,37 @@ var file_app_proxyman_config_proto_goTypes = []interface{}{
|
|||||||
(*MultiplexingConfig)(nil), // 9: xray.app.proxyman.MultiplexingConfig
|
(*MultiplexingConfig)(nil), // 9: xray.app.proxyman.MultiplexingConfig
|
||||||
(*AllocationStrategy_AllocationStrategyConcurrency)(nil), // 10: xray.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency
|
(*AllocationStrategy_AllocationStrategyConcurrency)(nil), // 10: xray.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency
|
||||||
(*AllocationStrategy_AllocationStrategyRefresh)(nil), // 11: xray.app.proxyman.AllocationStrategy.AllocationStrategyRefresh
|
(*AllocationStrategy_AllocationStrategyRefresh)(nil), // 11: xray.app.proxyman.AllocationStrategy.AllocationStrategyRefresh
|
||||||
(*net.PortRange)(nil), // 12: xray.common.net.PortRange
|
(*domain.Domain)(nil), // 12: xray.common.matcher.domain.Domain
|
||||||
(*net.IPOrDomain)(nil), // 13: xray.common.net.IPOrDomain
|
(*geoip.GeoIP)(nil), // 13: xray.common.matcher.geoip.GeoIP
|
||||||
(*internet.StreamConfig)(nil), // 14: xray.transport.internet.StreamConfig
|
(*net.PortRange)(nil), // 14: xray.common.net.PortRange
|
||||||
(*serial.TypedMessage)(nil), // 15: xray.common.serial.TypedMessage
|
(*net.IPOrDomain)(nil), // 15: xray.common.net.IPOrDomain
|
||||||
(*internet.ProxyConfig)(nil), // 16: xray.transport.internet.ProxyConfig
|
(*internet.StreamConfig)(nil), // 16: xray.transport.internet.StreamConfig
|
||||||
|
(*serial.TypedMessage)(nil), // 17: xray.common.serial.TypedMessage
|
||||||
|
(*internet.ProxyConfig)(nil), // 18: xray.transport.internet.ProxyConfig
|
||||||
}
|
}
|
||||||
var file_app_proxyman_config_proto_depIdxs = []int32{
|
var file_app_proxyman_config_proto_depIdxs = []int32{
|
||||||
1, // 0: xray.app.proxyman.AllocationStrategy.type:type_name -> xray.app.proxyman.AllocationStrategy.Type
|
1, // 0: xray.app.proxyman.AllocationStrategy.type:type_name -> xray.app.proxyman.AllocationStrategy.Type
|
||||||
10, // 1: xray.app.proxyman.AllocationStrategy.concurrency:type_name -> xray.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency
|
10, // 1: xray.app.proxyman.AllocationStrategy.concurrency:type_name -> xray.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency
|
||||||
11, // 2: xray.app.proxyman.AllocationStrategy.refresh:type_name -> xray.app.proxyman.AllocationStrategy.AllocationStrategyRefresh
|
11, // 2: xray.app.proxyman.AllocationStrategy.refresh:type_name -> xray.app.proxyman.AllocationStrategy.AllocationStrategyRefresh
|
||||||
12, // 3: xray.app.proxyman.ReceiverConfig.port_range:type_name -> xray.common.net.PortRange
|
12, // 3: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.matcher.domain.Domain
|
||||||
13, // 4: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain
|
13, // 4: xray.app.proxyman.SniffingConfig.ips_excluded:type_name -> xray.common.matcher.geoip.GeoIP
|
||||||
3, // 5: xray.app.proxyman.ReceiverConfig.allocation_strategy:type_name -> xray.app.proxyman.AllocationStrategy
|
14, // 5: xray.app.proxyman.ReceiverConfig.port_range:type_name -> xray.common.net.PortRange
|
||||||
14, // 6: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
15, // 6: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain
|
||||||
0, // 7: xray.app.proxyman.ReceiverConfig.domain_override:type_name -> xray.app.proxyman.KnownProtocols
|
3, // 7: xray.app.proxyman.ReceiverConfig.allocation_strategy:type_name -> xray.app.proxyman.AllocationStrategy
|
||||||
4, // 8: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig
|
16, // 8: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||||
15, // 9: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage
|
0, // 9: xray.app.proxyman.ReceiverConfig.domain_override:type_name -> xray.app.proxyman.KnownProtocols
|
||||||
15, // 10: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
4, // 10: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig
|
||||||
13, // 11: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
17, // 11: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage
|
||||||
14, // 12: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
17, // 12: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
||||||
16, // 13: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
|
15, // 13: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
||||||
9, // 14: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
16, // 14: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||||
15, // [15:15] is the sub-list for method output_type
|
18, // 15: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
|
||||||
15, // [15:15] is the sub-list for method input_type
|
9, // 16: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
||||||
15, // [15:15] is the sub-list for extension type_name
|
17, // [17:17] is the sub-list for method output_type
|
||||||
15, // [15:15] is the sub-list for extension extendee
|
17, // [17:17] is the sub-list for method input_type
|
||||||
0, // [0:15] is the sub-list for field type_name
|
17, // [17:17] is the sub-list for extension type_name
|
||||||
|
17, // [17:17] is the sub-list for extension extendee
|
||||||
|
0, // [0:17] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_app_proxyman_config_proto_init() }
|
func init() { file_app_proxyman_config_proto_init() }
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ option go_package = "github.com/xtls/xray-core/app/proxyman";
|
|||||||
option java_package = "com.xray.app.proxyman";
|
option java_package = "com.xray.app.proxyman";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
import "common/matcher/domain/domain.proto";
|
||||||
|
import "common/matcher/geoip/geoip.proto";
|
||||||
import "common/net/address.proto";
|
import "common/net/address.proto";
|
||||||
import "common/net/port.proto";
|
import "common/net/port.proto";
|
||||||
import "transport/internet/config.proto";
|
import "transport/internet/config.proto";
|
||||||
@@ -56,7 +58,9 @@ message SniffingConfig {
|
|||||||
// Override target destination if sniff'ed protocol is in the given list.
|
// Override target destination if sniff'ed protocol is in the given list.
|
||||||
// Supported values are "http", "tls", "fakedns".
|
// Supported values are "http", "tls", "fakedns".
|
||||||
repeated string destination_override = 2;
|
repeated string destination_override = 2;
|
||||||
repeated string domains_excluded = 3;
|
|
||||||
|
repeated xray.common.matcher.domain.Domain domains_excluded = 3;
|
||||||
|
repeated xray.common.matcher.geoip.GeoIP ips_excluded = 5;
|
||||||
|
|
||||||
// Whether should only try to sniff metadata without waiting for client input.
|
// Whether should only try to sniff metadata without waiting for client input.
|
||||||
// Can be used to support SMTP like protocol where server send the first message.
|
// Can be used to support SMTP like protocol where server send the first message.
|
||||||
|
|||||||
9
app/proxyman/errors.generated.go
Normal file
9
app/proxyman/errors.generated.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package proxyman
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
|
type errPathObjHolder struct{}
|
||||||
|
|
||||||
|
func newError(values ...interface{}) *errors.Error {
|
||||||
|
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||||
|
}
|
||||||
@@ -91,13 +91,20 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
|||||||
if net.HasNetwork(nl, net.Network_UNIX) {
|
if net.HasNetwork(nl, net.Network_UNIX) {
|
||||||
newError("creating unix domain socket worker on ", address).AtDebug().WriteToLog()
|
newError("creating unix domain socket worker on ", address).AtDebug().WriteToLog()
|
||||||
|
|
||||||
|
sc := receiverConfig.GetEffectiveSniffingSettings()
|
||||||
|
sm, err := proxyman.NewSniffingMatcher(sc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
worker := &dsWorker{
|
worker := &dsWorker{
|
||||||
address: address,
|
address: address,
|
||||||
proxy: p,
|
proxy: p,
|
||||||
stream: mss,
|
stream: mss,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dispatcher: h.mux,
|
dispatcher: h.mux,
|
||||||
sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(),
|
sniffingConfig: sc,
|
||||||
|
sniffingMatcher: sm,
|
||||||
uplinkCounter: uplinkCounter,
|
uplinkCounter: uplinkCounter,
|
||||||
downlinkCounter: downlinkCounter,
|
downlinkCounter: downlinkCounter,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -110,6 +117,12 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
|||||||
if net.HasNetwork(nl, net.Network_TCP) {
|
if net.HasNetwork(nl, net.Network_TCP) {
|
||||||
newError("creating stream worker on ", address, ":", port).AtDebug().WriteToLog()
|
newError("creating stream worker on ", address, ":", port).AtDebug().WriteToLog()
|
||||||
|
|
||||||
|
sc := receiverConfig.GetEffectiveSniffingSettings()
|
||||||
|
sm, err := proxyman.NewSniffingMatcher(sc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
worker := &tcpWorker{
|
worker := &tcpWorker{
|
||||||
address: address,
|
address: address,
|
||||||
port: net.Port(port),
|
port: net.Port(port),
|
||||||
@@ -118,7 +131,8 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
|||||||
recvOrigDest: receiverConfig.ReceiveOriginalDestination,
|
recvOrigDest: receiverConfig.ReceiveOriginalDestination,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dispatcher: h.mux,
|
dispatcher: h.mux,
|
||||||
sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(),
|
sniffingConfig: sc,
|
||||||
|
sniffingMatcher: sm,
|
||||||
uplinkCounter: uplinkCounter,
|
uplinkCounter: uplinkCounter,
|
||||||
downlinkCounter: downlinkCounter,
|
downlinkCounter: downlinkCounter,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type tcpWorker struct {
|
|||||||
tag string
|
tag string
|
||||||
dispatcher routing.Dispatcher
|
dispatcher routing.Dispatcher
|
||||||
sniffingConfig *proxyman.SniffingConfig
|
sniffingConfig *proxyman.SniffingConfig
|
||||||
|
sniffingMatcher *proxyman.SniffingMatcher
|
||||||
uplinkCounter stats.Counter
|
uplinkCounter stats.Counter
|
||||||
downlinkCounter stats.Counter
|
downlinkCounter stats.Counter
|
||||||
|
|
||||||
@@ -97,7 +98,8 @@ func (w *tcpWorker) callback(conn internet.Connection) {
|
|||||||
if w.sniffingConfig != nil {
|
if w.sniffingConfig != nil {
|
||||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||||
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
|
content.SniffingRequest.ExcludedDomainMatcher = w.sniffingMatcher.ExDomain
|
||||||
|
content.SniffingRequest.ExcludedIPMatcher = w.sniffingMatcher.ExIP
|
||||||
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
||||||
}
|
}
|
||||||
ctx = session.ContextWithContent(ctx, content)
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
@@ -428,6 +430,7 @@ type dsWorker struct {
|
|||||||
tag string
|
tag string
|
||||||
dispatcher routing.Dispatcher
|
dispatcher routing.Dispatcher
|
||||||
sniffingConfig *proxyman.SniffingConfig
|
sniffingConfig *proxyman.SniffingConfig
|
||||||
|
sniffingMatcher *proxyman.SniffingMatcher
|
||||||
uplinkCounter stats.Counter
|
uplinkCounter stats.Counter
|
||||||
downlinkCounter stats.Counter
|
downlinkCounter stats.Counter
|
||||||
|
|
||||||
@@ -459,7 +462,8 @@ func (w *dsWorker) callback(conn internet.Connection) {
|
|||||||
if w.sniffingConfig != nil {
|
if w.sniffingConfig != nil {
|
||||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||||
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
|
content.SniffingRequest.ExcludedDomainMatcher = w.sniffingMatcher.ExDomain
|
||||||
|
content.SniffingRequest.ExcludedIPMatcher = w.sniffingMatcher.ExIP
|
||||||
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
||||||
}
|
}
|
||||||
ctx = session.ContextWithContent(ctx, content)
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
. "github.com/xtls/xray-core/app/router/command"
|
. "github.com/xtls/xray-core/app/router/command"
|
||||||
"github.com/xtls/xray-core/app/stats"
|
"github.com/xtls/xray-core/app/stats"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/testing/mocks"
|
"github.com/xtls/xray-core/testing/mocks"
|
||||||
@@ -231,11 +233,11 @@ func TestSerivceTestRoute(t *testing.T) {
|
|||||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Domain: []*router.Domain{{Type: router.Domain_Domain, Value: "com"}},
|
Domain: []*domain.Domain{{Type: domain.MatchingType_Subdomain, Value: "com"}},
|
||||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceGeoip: []*router.GeoIP{{CountryCode: "private", Cidr: []*router.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
|
SourceGeoip: []*geoip.GeoIP{{CountryCode: "private", Cidr: []*geoip.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
|
||||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ func (c routingContext) GetTargetPort() net.Port {
|
|||||||
return net.Port(c.RoutingContext.GetTargetPort())
|
return net.Port(c.RoutingContext.GetTargetPort())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSkipDNSResolve is a mock implementation here to match the interface,
|
||||||
|
// SkipDNSResolve is set from dns module, no use if coming from a protobuf object?
|
||||||
|
func (c routingContext) GetSkipDNSResolve() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// AsRoutingContext converts a protobuf RoutingContext into an implementation of routing.Context.
|
// AsRoutingContext converts a protobuf RoutingContext into an implementation of routing.Context.
|
||||||
func AsRoutingContext(r *RoutingContext) routing.Context {
|
func AsRoutingContext(r *RoutingContext) routing.Context {
|
||||||
return routingContext{r}
|
return routingContext{r}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"go.starlark.net/syntax"
|
"go.starlark.net/syntax"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,100 +40,6 @@ func (v *ConditionChan) Len() int {
|
|||||||
return len(*v)
|
return len(*v)
|
||||||
}
|
}
|
||||||
|
|
||||||
var matcherTypeMap = map[Domain_Type]strmatcher.Type{
|
|
||||||
Domain_Plain: strmatcher.Substr,
|
|
||||||
Domain_Regex: strmatcher.Regex,
|
|
||||||
Domain_Domain: strmatcher.Domain,
|
|
||||||
Domain_Full: strmatcher.Full,
|
|
||||||
}
|
|
||||||
|
|
||||||
func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
|
|
||||||
matcherType, f := matcherTypeMap[domain.Type]
|
|
||||||
if !f {
|
|
||||||
return nil, newError("unsupported domain type", domain.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
matcher, err := matcherType.New(domain.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to create domain matcher").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DomainMatcher struct {
|
|
||||||
matchers strmatcher.IndexMatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
|
|
||||||
g := new(strmatcher.MatcherGroup)
|
|
||||||
for _, d := range domains {
|
|
||||||
m, err := domainToMatcher(d)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
g.Add(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DomainMatcher{
|
|
||||||
matchers: g,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DomainMatcher) ApplyDomain(domain string) bool {
|
|
||||||
return len(m.matchers.Match(domain)) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply implements Condition.
|
|
||||||
func (m *DomainMatcher) Apply(ctx routing.Context) bool {
|
|
||||||
domain := ctx.GetTargetDomain()
|
|
||||||
if len(domain) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return m.ApplyDomain(strings.ToLower(domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
type MultiGeoIPMatcher struct {
|
|
||||||
matchers []*GeoIPMatcher
|
|
||||||
onSource bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
|
|
||||||
var matchers []*GeoIPMatcher
|
|
||||||
for _, geoip := range geoips {
|
|
||||||
matcher, err := globalGeoIPContainer.Add(geoip)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
matchers = append(matchers, matcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
matcher := &MultiGeoIPMatcher{
|
|
||||||
matchers: matchers,
|
|
||||||
onSource: onSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply implements Condition.
|
|
||||||
func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
|
|
||||||
var ips []net.IP
|
|
||||||
if m.onSource {
|
|
||||||
ips = ctx.GetSourceIPs()
|
|
||||||
} else {
|
|
||||||
ips = ctx.GetTargetIPs()
|
|
||||||
}
|
|
||||||
for _, ip := range ips {
|
|
||||||
for _, matcher := range m.matchers {
|
|
||||||
if matcher.Match(ip) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type PortMatcher struct {
|
type PortMatcher struct {
|
||||||
port net.MemoryPortList
|
port net.MemoryPortList
|
||||||
onSource bool
|
onSource bool
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import (
|
|||||||
. "github.com/xtls/xray-core/app/router"
|
. "github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geosite"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
@@ -26,10 +29,10 @@ func init() {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
|
||||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
|
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "resources", "geoip.dat")))
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
|
||||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat")))
|
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "resources", "geosite.dat")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,18 +64,18 @@ func TestRoutingRule(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
rule: &RoutingRule{
|
rule: &RoutingRule{
|
||||||
Domain: []*Domain{
|
Domain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Value: "example.com",
|
Value: "example.com",
|
||||||
Type: Domain_Plain,
|
Type: domain.MatchingType_Keyword,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Value: "google.com",
|
Value: "google.com",
|
||||||
Type: Domain_Domain,
|
Type: domain.MatchingType_Subdomain,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Value: "^facebook\\.com$",
|
Value: "^facebook\\.com$",
|
||||||
Type: Domain_Regex,
|
Type: domain.MatchingType_Regex,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -109,7 +112,7 @@ func TestRoutingRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
rule: &RoutingRule{
|
rule: &RoutingRule{
|
||||||
Cidr: []*CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{8, 8, 8, 8},
|
Ip: []byte{8, 8, 8, 8},
|
||||||
Prefix: 32,
|
Prefix: 32,
|
||||||
@@ -145,9 +148,9 @@ func TestRoutingRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
rule: &RoutingRule{
|
rule: &RoutingRule{
|
||||||
Geoip: []*GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{
|
{
|
||||||
Cidr: []*CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{8, 8, 8, 8},
|
Ip: []byte{8, 8, 8, 8},
|
||||||
Prefix: 32,
|
Prefix: 32,
|
||||||
@@ -185,7 +188,7 @@ func TestRoutingRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
rule: &RoutingRule{
|
rule: &RoutingRule{
|
||||||
SourceCidr: []*CIDR{
|
SourceCidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{192, 168, 0, 0},
|
Ip: []byte{192, 168, 0, 0},
|
||||||
Prefix: 16,
|
Prefix: 16,
|
||||||
@@ -333,19 +336,19 @@ func TestRoutingRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGeoSite(country string) ([]*Domain, error) {
|
func loadGeoSite(country string) ([]*domain.Domain, error) {
|
||||||
geositeBytes, err := filesystem.ReadAsset("geosite.dat")
|
geositeBytes, err := filesystem.ReadAsset("geosite.dat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var geositeList GeoSiteList
|
var geositeList geosite.GeoSiteList
|
||||||
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, site := range geositeList.Entry {
|
for _, site := range geositeList.Entry {
|
||||||
if site.CountryCode == country {
|
if site.CountryCode == country {
|
||||||
return site.Domain, nil
|
return geosite.ToDomains(site.Domain), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +359,10 @@ func TestChinaSites(t *testing.T) {
|
|||||||
domains, err := loadGeoSite("CN")
|
domains, err := loadGeoSite("CN")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher, err := NewDomainMatcher(domains)
|
matcher, err := domain.NewDomainMatcher(domains)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
acMatcher, err := domain.NewMphMatcherGroup(domains)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
type TestCase struct {
|
type TestCase struct {
|
||||||
@@ -387,20 +393,126 @@ func TestChinaSites(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
r := matcher.ApplyDomain(testCase.Domain)
|
r1 := matcher.ApplyDomain(testCase.Domain)
|
||||||
if r != testCase.Output {
|
r2 := acMatcher.ApplyDomain(testCase.Domain)
|
||||||
t.Error("expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
|
if r1 != testCase.Output {
|
||||||
|
t.Error("DomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r1)
|
||||||
|
} else if r2 != testCase.Output {
|
||||||
|
t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||||
|
domains, err := loadGeoSite("CN")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
matcher, err := domain.NewMphMatcherGroup(domains)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
Domain string
|
||||||
|
Output bool
|
||||||
|
}
|
||||||
|
testCases := []TestCase{
|
||||||
|
{
|
||||||
|
Domain: "163.com",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "163.com",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "164.com",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "164.com",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
_ = matcher.ApplyDomain(testCase.Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDomainMatcher(b *testing.B) {
|
||||||
|
domains, err := loadGeoSite("CN")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
matcher, err := domain.NewDomainMatcher(domains)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
Domain string
|
||||||
|
Output bool
|
||||||
|
}
|
||||||
|
testCases := []TestCase{
|
||||||
|
{
|
||||||
|
Domain: "163.com",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "163.com",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "164.com",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "164.com",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
_ = matcher.ApplyDomain(testCase.Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadGeoIP(country string) ([]*geoip.CIDR, error) {
|
||||||
|
geoipBytes, err := filesystem.ReadAsset("dat")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var geoipList geoip.GeoIPList
|
||||||
|
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, geoip := range geoipList.Entry {
|
||||||
|
if geoip.CountryCode == country {
|
||||||
|
return geoip.Cidr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("country not found: " + country)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
||||||
var geoips []*GeoIP
|
var geoips []*geoip.GeoIP
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := loadGeoIP("CN")
|
ips, err := loadGeoIP("CN")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
geoips = append(geoips, &GeoIP{
|
geoips = append(geoips, &geoip.GeoIP{
|
||||||
CountryCode: "CN",
|
CountryCode: "CN",
|
||||||
Cidr: ips,
|
Cidr: ips,
|
||||||
})
|
})
|
||||||
@@ -409,7 +521,7 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
|||||||
{
|
{
|
||||||
ips, err := loadGeoIP("JP")
|
ips, err := loadGeoIP("JP")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
geoips = append(geoips, &GeoIP{
|
geoips = append(geoips, &geoip.GeoIP{
|
||||||
CountryCode: "JP",
|
CountryCode: "JP",
|
||||||
Cidr: ips,
|
Cidr: ips,
|
||||||
})
|
})
|
||||||
@@ -418,7 +530,7 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
|||||||
{
|
{
|
||||||
ips, err := loadGeoIP("CA")
|
ips, err := loadGeoIP("CA")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
geoips = append(geoips, &GeoIP{
|
geoips = append(geoips, &geoip.GeoIP{
|
||||||
CountryCode: "CA",
|
CountryCode: "CA",
|
||||||
Cidr: ips,
|
Cidr: ips,
|
||||||
})
|
})
|
||||||
@@ -427,13 +539,13 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
|||||||
{
|
{
|
||||||
ips, err := loadGeoIP("US")
|
ips, err := loadGeoIP("US")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
geoips = append(geoips, &GeoIP{
|
geoips = append(geoips, &geoip.GeoIP{
|
||||||
CountryCode: "US",
|
CountryCode: "US",
|
||||||
Cidr: ips,
|
Cidr: ips,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher, err := NewMultiGeoIPMatcher(geoips, false)
|
matcher, err := geoip.NewMultiGeoIPMatcher(geoips, false)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
|
ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
|
||||||
|
|||||||
@@ -1,50 +1,13 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dm "github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/features/outbound"
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CIDRList is an alias of []*CIDR to provide sort.Interface.
|
|
||||||
type CIDRList []*CIDR
|
|
||||||
|
|
||||||
// Len implements sort.Interface.
|
|
||||||
func (l *CIDRList) Len() int {
|
|
||||||
return len(*l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less implements sort.Interface.
|
|
||||||
func (l *CIDRList) Less(i int, j int) bool {
|
|
||||||
ci := (*l)[i]
|
|
||||||
cj := (*l)[j]
|
|
||||||
|
|
||||||
if len(ci.Ip) < len(cj.Ip) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ci.Ip) > len(cj.Ip) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := 0; k < len(ci.Ip); k++ {
|
|
||||||
if ci.Ip[k] < cj.Ip[k] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ci.Ip[k] > cj.Ip[k] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ci.Prefix < cj.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap implements sort.Interface.
|
|
||||||
func (l *CIDRList) Swap(i int, j int) {
|
|
||||||
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Tag string
|
Tag string
|
||||||
Balancer *Balancer
|
Balancer *Balancer
|
||||||
@@ -67,11 +30,24 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
|||||||
conds := NewConditionChan()
|
conds := NewConditionChan()
|
||||||
|
|
||||||
if len(rr.Domain) > 0 {
|
if len(rr.Domain) > 0 {
|
||||||
matcher, err := NewDomainMatcher(rr.Domain)
|
switch rr.DomainMatcher {
|
||||||
if err != nil {
|
case "linear":
|
||||||
return nil, newError("failed to build domain condition").Base(err)
|
matcher, err := dm.NewDomainMatcher(rr.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to build domain condition").Base(err)
|
||||||
|
}
|
||||||
|
conds.Add(matcher)
|
||||||
|
case "mph", "hybrid":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
matcher, err := dm.NewMphMatcherGroup(rr.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to build domain condition with MphDomainMatcher").Base(err)
|
||||||
|
}
|
||||||
|
newError("MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)").AtDebug().WriteToLog()
|
||||||
|
conds.Add(matcher)
|
||||||
}
|
}
|
||||||
conds.Add(matcher)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rr.UserEmail) > 0 {
|
if len(rr.UserEmail) > 0 {
|
||||||
@@ -99,13 +75,13 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(rr.Geoip) > 0 {
|
if len(rr.Geoip) > 0 {
|
||||||
cond, err := NewMultiGeoIPMatcher(rr.Geoip, false)
|
cond, err := geoip.NewMultiGeoIPMatcher(rr.Geoip, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conds.Add(cond)
|
conds.Add(cond)
|
||||||
} else if len(rr.Cidr) > 0 {
|
} else if len(rr.Cidr) > 0 {
|
||||||
cond, err := NewMultiGeoIPMatcher([]*GeoIP{{Cidr: rr.Cidr}}, false)
|
cond, err := geoip.NewMultiGeoIPMatcher([]*geoip.GeoIP{{Cidr: rr.Cidr}}, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -113,13 +89,13 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(rr.SourceGeoip) > 0 {
|
if len(rr.SourceGeoip) > 0 {
|
||||||
cond, err := NewMultiGeoIPMatcher(rr.SourceGeoip, true)
|
cond, err := geoip.NewMultiGeoIPMatcher(rr.SourceGeoip, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conds.Add(cond)
|
conds.Add(cond)
|
||||||
} else if len(rr.SourceCidr) > 0 {
|
} else if len(rr.SourceCidr) > 0 {
|
||||||
cond, err := NewMultiGeoIPMatcher([]*GeoIP{{Cidr: rr.SourceCidr}}, true)
|
cond, err := geoip.NewMultiGeoIPMatcher([]*geoip.GeoIP{{Cidr: rr.SourceCidr}}, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,66 +8,8 @@ option java_multiple_files = true;
|
|||||||
|
|
||||||
import "common/net/port.proto";
|
import "common/net/port.proto";
|
||||||
import "common/net/network.proto";
|
import "common/net/network.proto";
|
||||||
|
import "common/matcher/domain/domain.proto";
|
||||||
// Domain for routing decision.
|
import "common/matcher/geoip/geoip.proto";
|
||||||
message Domain {
|
|
||||||
// Type of domain value.
|
|
||||||
enum Type {
|
|
||||||
// The value is used as is.
|
|
||||||
Plain = 0;
|
|
||||||
// The value is used as a regular expression.
|
|
||||||
Regex = 1;
|
|
||||||
// The value is a root domain.
|
|
||||||
Domain = 2;
|
|
||||||
// The value is a domain.
|
|
||||||
Full = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domain matching type.
|
|
||||||
Type type = 1;
|
|
||||||
|
|
||||||
// Domain value.
|
|
||||||
string value = 2;
|
|
||||||
|
|
||||||
message Attribute {
|
|
||||||
string key = 1;
|
|
||||||
|
|
||||||
oneof typed_value {
|
|
||||||
bool bool_value = 2;
|
|
||||||
int64 int_value = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attributes of this domain. May be used for filtering.
|
|
||||||
repeated Attribute attribute = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP for routing decision, in CIDR form.
|
|
||||||
message CIDR {
|
|
||||||
// IP address, should be either 4 or 16 bytes.
|
|
||||||
bytes ip = 1;
|
|
||||||
|
|
||||||
// Number of leading ones in the network mask.
|
|
||||||
uint32 prefix = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GeoIP {
|
|
||||||
string country_code = 1;
|
|
||||||
repeated CIDR cidr = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GeoIPList {
|
|
||||||
repeated GeoIP entry = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GeoSite {
|
|
||||||
string country_code = 1;
|
|
||||||
repeated Domain domain = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GeoSiteList {
|
|
||||||
repeated GeoSite entry = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RoutingRule {
|
message RoutingRule {
|
||||||
oneof target_tag {
|
oneof target_tag {
|
||||||
@@ -79,17 +21,17 @@ message RoutingRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List of domains for target domain matching.
|
// List of domains for target domain matching.
|
||||||
repeated Domain domain = 2;
|
repeated xray.common.matcher.domain.Domain domain = 2;
|
||||||
|
|
||||||
// List of CIDRs for target IP address matching.
|
// List of CIDRs for target IP address matching.
|
||||||
// Deprecated. Use geoip below.
|
// Deprecated. Use geoip below.
|
||||||
repeated CIDR cidr = 3 [deprecated = true];
|
repeated xray.common.matcher.geoip.CIDR cidr = 3 [deprecated = true];
|
||||||
|
|
||||||
// List of GeoIPs for target IP address matching. If this entry exists, the
|
// List of GeoIPs for target IP address matching. If this entry exists, the
|
||||||
// cidr above will have no effect. GeoIP fields with the same country code are
|
// cidr above will have no effect. GeoIP fields with the same country code are
|
||||||
// supposed to contain exactly same content. They will be merged during
|
// supposed to contain exactly same content. They will be merged during
|
||||||
// runtime. For customized GeoIPs, please leave country code empty.
|
// runtime. For customized GeoIPs, please leave country code empty.
|
||||||
repeated GeoIP geoip = 10;
|
repeated xray.common.matcher.geoip.GeoIP geoip = 10;
|
||||||
|
|
||||||
// A range of port [from, to]. If the destination port is in this range, this
|
// A range of port [from, to]. If the destination port is in this range, this
|
||||||
// rule takes effect. Deprecated. Use port_list.
|
// rule takes effect. Deprecated. Use port_list.
|
||||||
@@ -105,11 +47,11 @@ message RoutingRule {
|
|||||||
repeated xray.common.net.Network networks = 13;
|
repeated xray.common.net.Network networks = 13;
|
||||||
|
|
||||||
// List of CIDRs for source IP address matching.
|
// List of CIDRs for source IP address matching.
|
||||||
repeated CIDR source_cidr = 6 [deprecated = true];
|
repeated xray.common.matcher.geoip.CIDR source_cidr = 6 [deprecated = true];
|
||||||
|
|
||||||
// List of GeoIPs for source IP address matching. If this entry exists, the
|
// List of GeoIPs for source IP address matching. If this entry exists, the
|
||||||
// source_cidr above will have no effect.
|
// source_cidr above will have no effect.
|
||||||
repeated GeoIP source_geoip = 11;
|
repeated xray.common.matcher.geoip.GeoIP source_geoip = 11;
|
||||||
|
|
||||||
// List of ports for source port matching.
|
// List of ports for source port matching.
|
||||||
xray.common.net.PortList source_port_list = 16;
|
xray.common.net.PortList source_port_list = 16;
|
||||||
@@ -119,6 +61,8 @@ message RoutingRule {
|
|||||||
repeated string protocol = 9;
|
repeated string protocol = 9;
|
||||||
|
|
||||||
string attributes = 15;
|
string attributes = 15;
|
||||||
|
|
||||||
|
string domain_matcher = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BalancingRule {
|
message BalancingRule {
|
||||||
|
|||||||
@@ -80,7 +80,13 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {
|
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {
|
||||||
if r.domainStrategy == Config_IpOnDemand {
|
|
||||||
|
// SkipDNSResolve is set from DNS module.
|
||||||
|
// the DOH remote server maybe a domain name,
|
||||||
|
// this prevents cycle resolving dead loop
|
||||||
|
skipDNSResolve := ctx.GetSkipDNSResolve()
|
||||||
|
|
||||||
|
if r.domainStrategy == Config_IpOnDemand && !skipDNSResolve {
|
||||||
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +96,7 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 {
|
if r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 || skipDNSResolve {
|
||||||
return nil, ctx, common.ErrNoClue
|
return nil, ctx, common.ErrNoClue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
. "github.com/xtls/xray-core/app/router"
|
. "github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
|
||||||
"github.com/xtls/xray-core/features/outbound"
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||||
"github.com/xtls/xray-core/testing/mocks"
|
"github.com/xtls/xray-core/testing/mocks"
|
||||||
@@ -102,7 +102,7 @@ func TestIPOnDemand(t *testing.T) {
|
|||||||
TargetTag: &RoutingRule_Tag{
|
TargetTag: &RoutingRule_Tag{
|
||||||
Tag: "test",
|
Tag: "test",
|
||||||
},
|
},
|
||||||
Cidr: []*CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{192, 168, 0, 0},
|
Ip: []byte{192, 168, 0, 0},
|
||||||
Prefix: 16,
|
Prefix: 16,
|
||||||
@@ -116,11 +116,7 @@ func TestIPOnDemand(t *testing.T) {
|
|||||||
defer mockCtl.Finish()
|
defer mockCtl.Finish()
|
||||||
|
|
||||||
mockDNS := mocks.NewDNSClient(mockCtl)
|
mockDNS := mocks.NewDNSClient(mockCtl)
|
||||||
mockDNS.EXPECT().LookupIP(gomock.Eq("example.com"), dns.IPOption{
|
mockDNS.EXPECT().LookupIP(gomock.Eq("example.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
|
|
||||||
|
|
||||||
r := new(Router)
|
r := new(Router)
|
||||||
common.Must(r.Init(config, mockDNS, nil))
|
common.Must(r.Init(config, mockDNS, nil))
|
||||||
@@ -141,7 +137,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
|
|||||||
TargetTag: &RoutingRule_Tag{
|
TargetTag: &RoutingRule_Tag{
|
||||||
Tag: "test",
|
Tag: "test",
|
||||||
},
|
},
|
||||||
Cidr: []*CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{192, 168, 0, 0},
|
Ip: []byte{192, 168, 0, 0},
|
||||||
Prefix: 16,
|
Prefix: 16,
|
||||||
@@ -155,11 +151,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
|
|||||||
defer mockCtl.Finish()
|
defer mockCtl.Finish()
|
||||||
|
|
||||||
mockDNS := mocks.NewDNSClient(mockCtl)
|
mockDNS := mocks.NewDNSClient(mockCtl)
|
||||||
mockDNS.EXPECT().LookupIP(gomock.Eq("example.com"), dns.IPOption{
|
mockDNS.EXPECT().LookupIP(gomock.Eq("example.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
|
|
||||||
|
|
||||||
r := new(Router)
|
r := new(Router)
|
||||||
common.Must(r.Init(config, mockDNS, nil))
|
common.Must(r.Init(config, mockDNS, nil))
|
||||||
@@ -180,7 +172,7 @@ func TestIPIfNonMatchIP(t *testing.T) {
|
|||||||
TargetTag: &RoutingRule_Tag{
|
TargetTag: &RoutingRule_Tag{
|
||||||
Tag: "test",
|
Tag: "test",
|
||||||
},
|
},
|
||||||
Cidr: []*CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{127, 0, 0, 0},
|
Ip: []byte{127, 0, 0, 0},
|
||||||
Prefix: 8,
|
Prefix: 8,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/xtls/xray-core/app/stats"
|
"github.com/xtls/xray-core/app/stats"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
"github.com/xtls/xray-core/common/matcher/str"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
feature_stats "github.com/xtls/xray-core/features/stats"
|
feature_stats "github.com/xtls/xray-core/features/stats"
|
||||||
)
|
)
|
||||||
@@ -49,7 +49,7 @@ func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
|
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
|
||||||
matcher, err := strmatcher.Substr.New(request.Pattern)
|
matcher, err := str.Substr.New(request.Pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
3
common/matcher/domain/conf/conf.go
Normal file
3
common/matcher/domain/conf/conf.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
94
common/matcher/domain/conf/domain.go
Normal file
94
common/matcher/domain/conf/domain.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
dm "github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geosite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseDomainRule(domain string) ([]*dm.Domain, error) {
|
||||||
|
if strings.HasPrefix(domain, "geosite:") {
|
||||||
|
country := strings.ToUpper(domain[8:])
|
||||||
|
domains, err := geosite.LoadGeositeWithAttr("geosite.dat", country)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to load geosite: ", country).Base(err)
|
||||||
|
}
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
var isExtDatFile = 0
|
||||||
|
{
|
||||||
|
const prefix = "ext:"
|
||||||
|
if strings.HasPrefix(domain, prefix) {
|
||||||
|
isExtDatFile = len(prefix)
|
||||||
|
}
|
||||||
|
const prefixQualified = "ext-domain:"
|
||||||
|
if strings.HasPrefix(domain, prefixQualified) {
|
||||||
|
isExtDatFile = len(prefixQualified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isExtDatFile != 0 {
|
||||||
|
kv := strings.Split(domain[isExtDatFile:], ":")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return nil, newError("invalid external resource: ", domain)
|
||||||
|
}
|
||||||
|
filename := kv[0]
|
||||||
|
country := kv[1]
|
||||||
|
domains, err := geosite.LoadGeositeWithAttr(filename, country)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err)
|
||||||
|
}
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
domainRule := new(dm.Domain)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(domain, "regexp:"):
|
||||||
|
regexpVal := domain[7:]
|
||||||
|
if len(regexpVal) == 0 {
|
||||||
|
return nil, newError("empty regexp type of rule: ", domain)
|
||||||
|
}
|
||||||
|
domainRule.Type = dm.MatchingType_Regex
|
||||||
|
domainRule.Value = regexpVal
|
||||||
|
|
||||||
|
case strings.HasPrefix(domain, "domain:"):
|
||||||
|
domainName := domain[7:]
|
||||||
|
if len(domainName) == 0 {
|
||||||
|
return nil, newError("empty domain type of rule: ", domain)
|
||||||
|
}
|
||||||
|
domainRule.Type = dm.MatchingType_Subdomain
|
||||||
|
domainRule.Value = domainName
|
||||||
|
|
||||||
|
case strings.HasPrefix(domain, "full:"):
|
||||||
|
fullVal := domain[5:]
|
||||||
|
if len(fullVal) == 0 {
|
||||||
|
return nil, newError("empty full domain type of rule: ", domain)
|
||||||
|
}
|
||||||
|
domainRule.Type = dm.MatchingType_Full
|
||||||
|
domainRule.Value = fullVal
|
||||||
|
|
||||||
|
case strings.HasPrefix(domain, "keyword:"):
|
||||||
|
keywordVal := domain[8:]
|
||||||
|
if len(keywordVal) == 0 {
|
||||||
|
return nil, newError("empty keyword type of rule: ", domain)
|
||||||
|
}
|
||||||
|
domainRule.Type = dm.MatchingType_Keyword
|
||||||
|
domainRule.Value = keywordVal
|
||||||
|
|
||||||
|
case strings.HasPrefix(domain, "dotless:"):
|
||||||
|
domainRule.Type = dm.MatchingType_Regex
|
||||||
|
switch substr := domain[8:]; {
|
||||||
|
case substr == "":
|
||||||
|
domainRule.Value = "^[^.]*$"
|
||||||
|
case !strings.Contains(substr, "."):
|
||||||
|
domainRule.Value = "^[^.]*" + substr + "[^.]*$"
|
||||||
|
default:
|
||||||
|
return nil, newError("substr in dotless rule should not contain a dot: ", substr)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
domainRule.Type = dm.MatchingType_Keyword
|
||||||
|
domainRule.Value = domain
|
||||||
|
}
|
||||||
|
return []*dm.Domain{domainRule}, nil
|
||||||
|
}
|
||||||
9
common/matcher/domain/conf/errors.generated.go
Normal file
9
common/matcher/domain/conf/errors.generated.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
|
type errPathObjHolder struct{}
|
||||||
|
|
||||||
|
func newError(values ...interface{}) *errors.Error {
|
||||||
|
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||||
|
}
|
||||||
3
common/matcher/domain/domain.go
Normal file
3
common/matcher/domain/domain.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
229
common/matcher/domain/domain.pb.go
Normal file
229
common/matcher/domain/domain.pb.go
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.25.0
|
||||||
|
// protoc v3.15.6
|
||||||
|
// source: common/matcher/domain/domain.proto
|
||||||
|
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
proto "github.com/golang/protobuf/proto"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||||
|
// of the legacy proto package is being used.
|
||||||
|
const _ = proto.ProtoPackageIsVersion4
|
||||||
|
|
||||||
|
type MatchingType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
MatchingType_Full MatchingType = 0
|
||||||
|
MatchingType_Subdomain MatchingType = 1
|
||||||
|
MatchingType_Keyword MatchingType = 2
|
||||||
|
MatchingType_Regex MatchingType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for MatchingType.
|
||||||
|
var (
|
||||||
|
MatchingType_name = map[int32]string{
|
||||||
|
0: "Full",
|
||||||
|
1: "Subdomain",
|
||||||
|
2: "Keyword",
|
||||||
|
3: "Regex",
|
||||||
|
}
|
||||||
|
MatchingType_value = map[string]int32{
|
||||||
|
"Full": 0,
|
||||||
|
"Subdomain": 1,
|
||||||
|
"Keyword": 2,
|
||||||
|
"Regex": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x MatchingType) Enum() *MatchingType {
|
||||||
|
p := new(MatchingType)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x MatchingType) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MatchingType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_common_matcher_domain_domain_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MatchingType) Type() protoreflect.EnumType {
|
||||||
|
return &file_common_matcher_domain_domain_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x MatchingType) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use MatchingType.Descriptor instead.
|
||||||
|
func (MatchingType) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_domain_domain_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Domain struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Domain matching type.
|
||||||
|
Type MatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.common.matcher.domain.MatchingType" json:"type,omitempty"`
|
||||||
|
// Domain value.
|
||||||
|
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) Reset() {
|
||||||
|
*x = Domain{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_common_matcher_domain_domain_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Domain) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Domain) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_common_matcher_domain_domain_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Domain) Descriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_domain_domain_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) GetType() MatchingType {
|
||||||
|
if x != nil {
|
||||||
|
return x.Type
|
||||||
|
}
|
||||||
|
return MatchingType_Full
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) GetValue() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_common_matcher_domain_domain_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_common_matcher_domain_domain_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x22, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
|
||||||
|
0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
|
||||||
|
0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
|
0x22, 0x5c, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3c, 0x0a, 0x04, 0x74, 0x79,
|
||||||
|
0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
|
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64,
|
||||||
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79,
|
||||||
|
0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||||
|
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x3f,
|
||||||
|
0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08,
|
||||||
|
0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64,
|
||||||
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f,
|
||||||
|
0x72, 0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x42,
|
||||||
|
0x70, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
|
||||||
|
0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
|
0x6e, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
||||||
|
0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63,
|
||||||
|
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x64, 0x6f,
|
||||||
|
0x6d, 0x61, 0x69, 0x6e, 0xaa, 0x02, 0x1a, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d,
|
||||||
|
0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
|
0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_common_matcher_domain_domain_proto_rawDescOnce sync.Once
|
||||||
|
file_common_matcher_domain_domain_proto_rawDescData = file_common_matcher_domain_domain_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_common_matcher_domain_domain_proto_rawDescGZIP() []byte {
|
||||||
|
file_common_matcher_domain_domain_proto_rawDescOnce.Do(func() {
|
||||||
|
file_common_matcher_domain_domain_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_matcher_domain_domain_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_common_matcher_domain_domain_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_common_matcher_domain_domain_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_common_matcher_domain_domain_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_common_matcher_domain_domain_proto_goTypes = []interface{}{
|
||||||
|
(MatchingType)(0), // 0: xray.common.matcher.domain.MatchingType
|
||||||
|
(*Domain)(nil), // 1: xray.common.matcher.domain.Domain
|
||||||
|
}
|
||||||
|
var file_common_matcher_domain_domain_proto_depIdxs = []int32{
|
||||||
|
0, // 0: xray.common.matcher.domain.Domain.type:type_name -> xray.common.matcher.domain.MatchingType
|
||||||
|
1, // [1:1] is the sub-list for method output_type
|
||||||
|
1, // [1:1] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_common_matcher_domain_domain_proto_init() }
|
||||||
|
func file_common_matcher_domain_domain_proto_init() {
|
||||||
|
if File_common_matcher_domain_domain_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_common_matcher_domain_domain_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Domain); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_common_matcher_domain_domain_proto_rawDesc,
|
||||||
|
NumEnums: 1,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_common_matcher_domain_domain_proto_goTypes,
|
||||||
|
DependencyIndexes: file_common_matcher_domain_domain_proto_depIdxs,
|
||||||
|
EnumInfos: file_common_matcher_domain_domain_proto_enumTypes,
|
||||||
|
MessageInfos: file_common_matcher_domain_domain_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_common_matcher_domain_domain_proto = out.File
|
||||||
|
file_common_matcher_domain_domain_proto_rawDesc = nil
|
||||||
|
file_common_matcher_domain_domain_proto_goTypes = nil
|
||||||
|
file_common_matcher_domain_domain_proto_depIdxs = nil
|
||||||
|
}
|
||||||
39
common/matcher/domain/domain.proto
Normal file
39
common/matcher/domain/domain.proto
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.common.matcher.domain;
|
||||||
|
option csharp_namespace = "Xray.Common.Matcher.Domain";
|
||||||
|
option go_package = "github.com/xtls/xray-core/common/matcher/domain";
|
||||||
|
option java_package = "com.xray.common.matcher.domain";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
enum MatchingType {
|
||||||
|
Full = 0;
|
||||||
|
Subdomain = 1;
|
||||||
|
Keyword = 2;
|
||||||
|
Regex = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Domain {
|
||||||
|
// Domain matching type.
|
||||||
|
MatchingType type = 1;
|
||||||
|
|
||||||
|
// Domain value.
|
||||||
|
string value = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
|
||||||
|
switch t {
|
||||||
|
case router.Domain_Domain:
|
||||||
|
return dns.DomainMatchingType_Subdomain
|
||||||
|
case router.Domain_Full:
|
||||||
|
return dns.DomainMatchingType_Full
|
||||||
|
case router.Domain_Plain:
|
||||||
|
return dns.DomainMatchingType_Keyword
|
||||||
|
case router.Domain_Regex:
|
||||||
|
return dns.DomainMatchingType_Regex
|
||||||
|
default:
|
||||||
|
panic("unknown domain type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
9
common/matcher/domain/errors.generated.go
Normal file
9
common/matcher/domain/errors.generated.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
|
type errPathObjHolder struct{}
|
||||||
|
|
||||||
|
func newError(values ...interface{}) *errors.Error {
|
||||||
|
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||||
|
}
|
||||||
79
common/matcher/domain/matcher.go
Normal file
79
common/matcher/domain/matcher.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/matcher/str"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var matcherTypeMap = map[MatchingType]str.Type{
|
||||||
|
MatchingType_Keyword: str.Substr,
|
||||||
|
MatchingType_Regex: str.Regex,
|
||||||
|
MatchingType_Subdomain: str.Domain,
|
||||||
|
MatchingType_Full: str.Full,
|
||||||
|
}
|
||||||
|
|
||||||
|
func domainToMatcher(domain *Domain) (str.Matcher, error) {
|
||||||
|
matcherType, f := matcherTypeMap[domain.Type]
|
||||||
|
if !f {
|
||||||
|
return nil, newError("unsupported domain type", domain.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher, err := matcherType.New(domain.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create domain matcher").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return matcher, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainMatcher struct {
|
||||||
|
matchers str.IndexMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
|
||||||
|
g := str.NewMphMatcherGroup()
|
||||||
|
for _, d := range domains {
|
||||||
|
matcherType, f := matcherTypeMap[d.Type]
|
||||||
|
if !f {
|
||||||
|
return nil, newError("unsupported domain type", d.Type)
|
||||||
|
}
|
||||||
|
_, err := g.AddPattern(d.Value, matcherType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Build()
|
||||||
|
return &DomainMatcher{
|
||||||
|
matchers: g,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
|
||||||
|
g := new(str.MatcherGroup)
|
||||||
|
for _, d := range domains {
|
||||||
|
m, err := domainToMatcher(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
g.Add(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DomainMatcher{
|
||||||
|
matchers: g,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DomainMatcher) ApplyDomain(domain string) bool {
|
||||||
|
return len(m.matchers.Match(strings.ToLower(domain))) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Condition.
|
||||||
|
func (m *DomainMatcher) Apply(ctx routing.Context) bool {
|
||||||
|
domain := ctx.GetTargetDomain()
|
||||||
|
if len(domain) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return m.ApplyDomain(domain)
|
||||||
|
}
|
||||||
224
common/matcher/geoip/conf.go
Normal file
224
common/matcher/geoip/conf.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package geoip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
FileCache = make(map[string][]byte)
|
||||||
|
IPCache = make(map[string]*GeoIP)
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadGeoIP(code string) ([]*CIDR, error) {
|
||||||
|
return LoadIPFile("geoip.dat", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadIPFile(file, code string) ([]*CIDR, error) {
|
||||||
|
index := file + ":" + code
|
||||||
|
if IPCache[index] == nil {
|
||||||
|
bs, err := loadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to load file: ", file).Base(err)
|
||||||
|
}
|
||||||
|
bs = find(bs, []byte(code))
|
||||||
|
if bs == nil {
|
||||||
|
return nil, newError("code not found in ", file, ": ", code)
|
||||||
|
}
|
||||||
|
var geoipdat GeoIP
|
||||||
|
if err := proto.Unmarshal(bs, &geoipdat); err != nil {
|
||||||
|
return nil, newError("error unmarshal IP in ", file, ": ", code).Base(err)
|
||||||
|
}
|
||||||
|
defer runtime.GC() // or debug.FreeOSMemory()
|
||||||
|
return geoipdat.Cidr, nil // do not cache geoip
|
||||||
|
// IPCache[index] = &geoipdat
|
||||||
|
}
|
||||||
|
return IPCache[index].Cidr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFile(file string) ([]byte, error) {
|
||||||
|
if FileCache[file] == nil {
|
||||||
|
bs, err := filesystem.ReadAsset(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to open file: ", file).Base(err)
|
||||||
|
}
|
||||||
|
if len(bs) == 0 {
|
||||||
|
return nil, newError("empty file: ", file)
|
||||||
|
}
|
||||||
|
// Do not cache file, may save RAM when there
|
||||||
|
// are many files, but consume CPU each time.
|
||||||
|
return bs, nil
|
||||||
|
// FileCache[file] = bs
|
||||||
|
}
|
||||||
|
return FileCache[file], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func find(data, code []byte) []byte {
|
||||||
|
codeL := len(code)
|
||||||
|
if codeL == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
dataL := len(data)
|
||||||
|
if dataL < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
x, y := proto.DecodeVarint(data[1:])
|
||||||
|
if x == 0 && y == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
headL, bodyL := 1+y, int(x)
|
||||||
|
dataL -= headL
|
||||||
|
if dataL < bodyL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data = data[headL:]
|
||||||
|
if int(data[1]) == codeL {
|
||||||
|
for i := 0; i < codeL && data[2+i] == code[i]; i++ {
|
||||||
|
if i+1 == codeL {
|
||||||
|
return data[:bodyL]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dataL == bodyL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data = data[bodyL:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseIPList(ips []string) ([]*GeoIP, error) {
|
||||||
|
var geoipList []*GeoIP
|
||||||
|
var customCidrs []*CIDR
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if strings.HasPrefix(ip, "geoip:") {
|
||||||
|
country := ip[6:]
|
||||||
|
isReverseMatch := false
|
||||||
|
if strings.HasPrefix(country, "!") {
|
||||||
|
country = country[1:]
|
||||||
|
isReverseMatch = true
|
||||||
|
}
|
||||||
|
|
||||||
|
geoipc, err := LoadGeoIP(strings.ToUpper(country))
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to load GeoIP: ", country).Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
geoipList = append(geoipList, &GeoIP{
|
||||||
|
CountryCode: strings.ToUpper(country),
|
||||||
|
Cidr: geoipc,
|
||||||
|
ReverseMatch: isReverseMatch,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var isExtDatFile = 0
|
||||||
|
{
|
||||||
|
const prefix = "ext:"
|
||||||
|
if strings.HasPrefix(ip, prefix) {
|
||||||
|
isExtDatFile = len(prefix)
|
||||||
|
}
|
||||||
|
const prefixQualified = "ext-ip:"
|
||||||
|
if strings.HasPrefix(ip, prefixQualified) {
|
||||||
|
isExtDatFile = len(prefixQualified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isExtDatFile != 0 {
|
||||||
|
kv := strings.Split(ip[isExtDatFile:], ":")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return nil, newError("invalid external resource: ", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := kv[0]
|
||||||
|
country := kv[1]
|
||||||
|
if len(filename) == 0 || len(country) == 0 {
|
||||||
|
return nil, newError("empty filename or empty country in rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
isReverseMatch := false
|
||||||
|
if strings.HasPrefix(country, "!") {
|
||||||
|
country = country[1:]
|
||||||
|
isReverseMatch = true
|
||||||
|
}
|
||||||
|
geoipc, err := LoadIPFile(filename, strings.ToUpper(country))
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
geoipList = append(geoipList, &GeoIP{
|
||||||
|
CountryCode: strings.ToUpper(filename + "_" + country),
|
||||||
|
Cidr: geoipc,
|
||||||
|
ReverseMatch: isReverseMatch,
|
||||||
|
})
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ipRule, err := ParseIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("invalid IP: ", ip).Base(err)
|
||||||
|
}
|
||||||
|
customCidrs = append(customCidrs, ipRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(customCidrs) > 0 {
|
||||||
|
geoipList = append(geoipList, &GeoIP{
|
||||||
|
Cidr: customCidrs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return geoipList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseIP(s string) (*CIDR, error) {
|
||||||
|
var addr, mask string
|
||||||
|
i := strings.Index(s, "/")
|
||||||
|
if i < 0 {
|
||||||
|
addr = s
|
||||||
|
} else {
|
||||||
|
addr = s[:i]
|
||||||
|
mask = s[i+1:]
|
||||||
|
}
|
||||||
|
ip := net.ParseAddress(addr)
|
||||||
|
switch ip.Family() {
|
||||||
|
case net.AddressFamilyIPv4:
|
||||||
|
bits := uint32(32)
|
||||||
|
if len(mask) > 0 {
|
||||||
|
bits64, err := strconv.ParseUint(mask, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("invalid network mask for router: ", mask).Base(err)
|
||||||
|
}
|
||||||
|
bits = uint32(bits64)
|
||||||
|
}
|
||||||
|
if bits > 32 {
|
||||||
|
return nil, newError("invalid network mask for router: ", bits)
|
||||||
|
}
|
||||||
|
return &CIDR{
|
||||||
|
Ip: ip.IP(),
|
||||||
|
Prefix: bits,
|
||||||
|
}, nil
|
||||||
|
case net.AddressFamilyIPv6:
|
||||||
|
bits := uint32(128)
|
||||||
|
if len(mask) > 0 {
|
||||||
|
bits64, err := strconv.ParseUint(mask, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("invalid network mask for router: ", mask).Base(err)
|
||||||
|
}
|
||||||
|
bits = uint32(bits64)
|
||||||
|
}
|
||||||
|
if bits > 128 {
|
||||||
|
return nil, newError("invalid network mask for router: ", bits)
|
||||||
|
}
|
||||||
|
return &CIDR{
|
||||||
|
Ip: ip.IP(),
|
||||||
|
Prefix: bits,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, newError("unsupported address for router: ", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
common/matcher/geoip/crid.go
Normal file
40
common/matcher/geoip/crid.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package geoip
|
||||||
|
|
||||||
|
// CIDRList is an alias of []*CIDR to provide sort.Interface.
|
||||||
|
type CIDRList []*CIDR
|
||||||
|
|
||||||
|
// Len implements sort.Interface.
|
||||||
|
func (l *CIDRList) Len() int {
|
||||||
|
return len(*l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less implements sort.Interface.
|
||||||
|
func (l *CIDRList) Less(i int, j int) bool {
|
||||||
|
ci := (*l)[i]
|
||||||
|
cj := (*l)[j]
|
||||||
|
|
||||||
|
if len(ci.Ip) < len(cj.Ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ci.Ip) > len(cj.Ip) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := 0; k < len(ci.Ip); k++ {
|
||||||
|
if ci.Ip[k] < cj.Ip[k] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.Ip[k] > cj.Ip[k] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ci.Prefix < cj.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap implements sort.Interface.
|
||||||
|
func (l *CIDRList) Swap(i int, j int) {
|
||||||
|
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
|
||||||
|
}
|
||||||
9
common/matcher/geoip/errors.generated.go
Normal file
9
common/matcher/geoip/errors.generated.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package geoip
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
|
type errPathObjHolder struct{}
|
||||||
|
|
||||||
|
func newError(values ...interface{}) *errors.Error {
|
||||||
|
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package router
|
package geoip
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
@@ -7,17 +7,24 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
|
|
||||||
type ipv6 struct {
|
type ipv6 struct {
|
||||||
a uint64
|
a uint64
|
||||||
b uint64
|
b uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeoIPMatcher struct {
|
type GeoIPMatcher struct {
|
||||||
countryCode string
|
countryCode string
|
||||||
ip4 []uint32
|
reverseMatch bool
|
||||||
prefix4 []uint8
|
ip4 []uint32
|
||||||
ip6 []ipv6
|
prefix4 []uint8
|
||||||
prefix6 []uint8
|
ip6 []ipv6
|
||||||
|
prefix6 []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
|
||||||
|
m.reverseMatch = isReverseMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalize4(ip uint32, prefix uint8) uint32 {
|
func normalize4(ip uint32, prefix uint8) uint32 {
|
||||||
@@ -147,8 +154,17 @@ func (m *GeoIPMatcher) match6(ip ipv6) bool {
|
|||||||
func (m *GeoIPMatcher) Match(ip net.IP) bool {
|
func (m *GeoIPMatcher) Match(ip net.IP) bool {
|
||||||
switch len(ip) {
|
switch len(ip) {
|
||||||
case 4:
|
case 4:
|
||||||
|
if m.reverseMatch {
|
||||||
|
return !m.match4(binary.BigEndian.Uint32(ip))
|
||||||
|
}
|
||||||
return m.match4(binary.BigEndian.Uint32(ip))
|
return m.match4(binary.BigEndian.Uint32(ip))
|
||||||
case 16:
|
case 16:
|
||||||
|
if m.reverseMatch {
|
||||||
|
return !m.match6(ipv6{
|
||||||
|
a: binary.BigEndian.Uint64(ip[0:8]),
|
||||||
|
b: binary.BigEndian.Uint64(ip[8:16]),
|
||||||
|
})
|
||||||
|
}
|
||||||
return m.match6(ipv6{
|
return m.match6(ipv6{
|
||||||
a: binary.BigEndian.Uint64(ip[0:8]),
|
a: binary.BigEndian.Uint64(ip[0:8]),
|
||||||
b: binary.BigEndian.Uint64(ip[8:16]),
|
b: binary.BigEndian.Uint64(ip[8:16]),
|
||||||
@@ -168,14 +184,15 @@ type GeoIPMatcherContainer struct {
|
|||||||
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
||||||
if len(geoip.CountryCode) > 0 {
|
if len(geoip.CountryCode) > 0 {
|
||||||
for _, m := range c.matchers {
|
for _, m := range c.matchers {
|
||||||
if m.countryCode == geoip.CountryCode {
|
if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &GeoIPMatcher{
|
m := &GeoIPMatcher{
|
||||||
countryCode: geoip.CountryCode,
|
countryCode: geoip.CountryCode,
|
||||||
|
reverseMatch: geoip.ReverseMatch,
|
||||||
}
|
}
|
||||||
if err := m.Init(geoip.Cidr); err != nil {
|
if err := m.Init(geoip.Cidr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -187,5 +204,5 @@ func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
globalGeoIPContainer GeoIPMatcherContainer
|
GlobalGeoIPContainer GeoIPMatcherContainer
|
||||||
)
|
)
|
||||||
317
common/matcher/geoip/geoip.pb.go
Normal file
317
common/matcher/geoip/geoip.pb.go
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.25.0
|
||||||
|
// protoc v3.15.7
|
||||||
|
// source: common/matcher/geoip/geoip.proto
|
||||||
|
|
||||||
|
package geoip
|
||||||
|
|
||||||
|
import (
|
||||||
|
proto "github.com/golang/protobuf/proto"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||||
|
// of the legacy proto package is being used.
|
||||||
|
const _ = proto.ProtoPackageIsVersion4
|
||||||
|
|
||||||
|
// IP for routing decision, in CIDR form.
|
||||||
|
type CIDR struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// IP address, should be either 4 or 16 bytes.
|
||||||
|
Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||||
|
// Number of leading ones in the network mask.
|
||||||
|
Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CIDR) Reset() {
|
||||||
|
*x = CIDR{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CIDR) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CIDR) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CIDR) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CIDR.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CIDR) Descriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CIDR) GetIp() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CIDR) GetPrefix() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Prefix
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoIP struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
|
||||||
|
Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"`
|
||||||
|
ReverseMatch bool `protobuf:"varint,3,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoIP) Reset() {
|
||||||
|
*x = GeoIP{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoIP) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GeoIP) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GeoIP) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GeoIP) Descriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoIP) GetCountryCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.CountryCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoIP) GetCidr() []*CIDR {
|
||||||
|
if x != nil {
|
||||||
|
return x.Cidr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoIP) GetReverseMatch() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.ReverseMatch
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoIPList struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoIPList) Reset() {
|
||||||
|
*x = GeoIPList{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoIPList) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GeoIPList) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GeoIPList) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_common_matcher_geoip_geoip_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GeoIPList) Descriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoIPList) GetEntry() []*GeoIP {
|
||||||
|
if x != nil {
|
||||||
|
return x.Entry
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_common_matcher_geoip_geoip_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_common_matcher_geoip_geoip_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
|
||||||
|
0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x12, 0x19, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
|
0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x22, 0x2e, 0x0a,
|
||||||
|
0x04, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
|
0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18,
|
||||||
|
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x84, 0x01,
|
||||||
|
0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||||
|
0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63,
|
||||||
|
0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x63, 0x69,
|
||||||
|
0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
|
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67,
|
||||||
|
0x65, 0x6f, 0x69, 0x70, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x12,
|
||||||
|
0x23, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68,
|
||||||
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4d,
|
||||||
|
0x61, 0x74, 0x63, 0x68, 0x22, 0x43, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73,
|
||||||
|
0x74, 0x12, 0x36, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
||||||
|
0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d,
|
||||||
|
0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f,
|
||||||
|
0x49, 0x50, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d,
|
||||||
|
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74,
|
||||||
|
0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69,
|
||||||
|
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72,
|
||||||
|
0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d,
|
||||||
|
0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0xaa, 0x02, 0x19, 0x58,
|
||||||
|
0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68,
|
||||||
|
0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_common_matcher_geoip_geoip_proto_rawDescOnce sync.Once
|
||||||
|
file_common_matcher_geoip_geoip_proto_rawDescData = file_common_matcher_geoip_geoip_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_common_matcher_geoip_geoip_proto_rawDescGZIP() []byte {
|
||||||
|
file_common_matcher_geoip_geoip_proto_rawDescOnce.Do(func() {
|
||||||
|
file_common_matcher_geoip_geoip_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_matcher_geoip_geoip_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_common_matcher_geoip_geoip_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_common_matcher_geoip_geoip_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||||
|
var file_common_matcher_geoip_geoip_proto_goTypes = []interface{}{
|
||||||
|
(*CIDR)(nil), // 0: xray.common.matcher.geoip.CIDR
|
||||||
|
(*GeoIP)(nil), // 1: xray.common.matcher.geoip.GeoIP
|
||||||
|
(*GeoIPList)(nil), // 2: xray.common.matcher.geoip.GeoIPList
|
||||||
|
}
|
||||||
|
var file_common_matcher_geoip_geoip_proto_depIdxs = []int32{
|
||||||
|
0, // 0: xray.common.matcher.geoip.GeoIP.cidr:type_name -> xray.common.matcher.geoip.CIDR
|
||||||
|
1, // 1: xray.common.matcher.geoip.GeoIPList.entry:type_name -> xray.common.matcher.geoip.GeoIP
|
||||||
|
2, // [2:2] is the sub-list for method output_type
|
||||||
|
2, // [2:2] is the sub-list for method input_type
|
||||||
|
2, // [2:2] is the sub-list for extension type_name
|
||||||
|
2, // [2:2] is the sub-list for extension extendee
|
||||||
|
0, // [0:2] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_common_matcher_geoip_geoip_proto_init() }
|
||||||
|
func file_common_matcher_geoip_geoip_proto_init() {
|
||||||
|
if File_common_matcher_geoip_geoip_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_common_matcher_geoip_geoip_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CIDR); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_common_matcher_geoip_geoip_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GeoIP); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_common_matcher_geoip_geoip_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GeoIPList); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_common_matcher_geoip_geoip_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 3,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_common_matcher_geoip_geoip_proto_goTypes,
|
||||||
|
DependencyIndexes: file_common_matcher_geoip_geoip_proto_depIdxs,
|
||||||
|
MessageInfos: file_common_matcher_geoip_geoip_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_common_matcher_geoip_geoip_proto = out.File
|
||||||
|
file_common_matcher_geoip_geoip_proto_rawDesc = nil
|
||||||
|
file_common_matcher_geoip_geoip_proto_goTypes = nil
|
||||||
|
file_common_matcher_geoip_geoip_proto_depIdxs = nil
|
||||||
|
}
|
||||||
26
common/matcher/geoip/geoip.proto
Normal file
26
common/matcher/geoip/geoip.proto
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.common.matcher.geoip;
|
||||||
|
option csharp_namespace = "Xray.Common.Matcher.GeoIP";
|
||||||
|
option go_package = "github.com/xtls/xray-core/common/matcher/geoip";
|
||||||
|
option java_package = "com.xray.common.matcher.geoip";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
// IP for routing decision, in CIDR form.
|
||||||
|
message CIDR {
|
||||||
|
// IP address, should be either 4 or 16 bytes.
|
||||||
|
bytes ip = 1;
|
||||||
|
|
||||||
|
// Number of leading ones in the network mask.
|
||||||
|
uint32 prefix = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GeoIP {
|
||||||
|
string country_code = 1;
|
||||||
|
repeated CIDR cidr = 2;
|
||||||
|
bool reverse_match =3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GeoIPList {
|
||||||
|
repeated GeoIP entry = 1;
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package router_test
|
package geoip_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/xtls/xray-core/app/router"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
. "github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -18,27 +18,47 @@ func init() {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
|
||||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "resources", "geoip.dat")))
|
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geoip.dat")))
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(platform.GetAssetLocation("geoiptestrouter.dat")); err != nil && os.IsNotExist(err) {
|
||||||
|
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoiptestrouter.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geoip.dat")))
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
|
||||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "resources", "geosite.dat")))
|
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geosite.dat")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseIPList(t *testing.T) {
|
||||||
|
ips := []string{
|
||||||
|
"geoip:us",
|
||||||
|
"geoip:cn",
|
||||||
|
"geoip:!cn",
|
||||||
|
"ext:geoiptestrouter.dat:!cn",
|
||||||
|
"ext:geoiptestrouter.dat:ca",
|
||||||
|
"ext-ip:geoiptestrouter.dat:!cn",
|
||||||
|
"ext-ip:geoiptestrouter.dat:!ca",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ParseIPList(ips)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse geoip list, got %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGeoIPMatcherContainer(t *testing.T) {
|
func TestGeoIPMatcherContainer(t *testing.T) {
|
||||||
container := &router.GeoIPMatcherContainer{}
|
container := &GeoIPMatcherContainer{}
|
||||||
|
|
||||||
m1, err := container.Add(&router.GeoIP{
|
m1, err := container.Add(&GeoIP{
|
||||||
CountryCode: "CN",
|
CountryCode: "CN",
|
||||||
})
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
m2, err := container.Add(&router.GeoIP{
|
m2, err := container.Add(&GeoIP{
|
||||||
CountryCode: "US",
|
CountryCode: "US",
|
||||||
})
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
m3, err := container.Add(&router.GeoIP{
|
m3, err := container.Add(&GeoIP{
|
||||||
CountryCode: "CN",
|
CountryCode: "CN",
|
||||||
})
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
@@ -53,7 +73,7 @@ func TestGeoIPMatcherContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGeoIPMatcher(t *testing.T) {
|
func TestGeoIPMatcher(t *testing.T) {
|
||||||
cidrList := router.CIDRList{
|
cidrList := CIDRList{
|
||||||
{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
|
{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
|
||||||
{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
|
{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
|
||||||
{Ip: []byte{100, 64, 0, 0}, Prefix: 10},
|
{Ip: []byte{100, 64, 0, 0}, Prefix: 10},
|
||||||
@@ -70,7 +90,7 @@ func TestGeoIPMatcher(t *testing.T) {
|
|||||||
{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
|
{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher := &router.GeoIPMatcher{}
|
matcher := &GeoIPMatcher{}
|
||||||
common.Must(matcher.Init(cidrList))
|
common.Must(matcher.Init(cidrList))
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -123,11 +143,47 @@ func TestGeoIPMatcher(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGeoIPReverseMatcher(t *testing.T) {
|
||||||
|
cidrList := CIDRList{
|
||||||
|
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||||
|
{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
|
||||||
|
}
|
||||||
|
matcher := &GeoIPMatcher{}
|
||||||
|
matcher.SetReverseMatch(true) // Reverse match
|
||||||
|
common.Must(matcher.Init(cidrList))
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
Input string
|
||||||
|
Output bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: "8.8.8.8",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "2001:cdba::3257:9652",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "91.108.255.254",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
ip := net.ParseAddress(testCase.Input).IP()
|
||||||
|
actual := matcher.Match(ip)
|
||||||
|
if actual != testCase.Output {
|
||||||
|
t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGeoIPMatcher4CN(t *testing.T) {
|
func TestGeoIPMatcher4CN(t *testing.T) {
|
||||||
ips, err := loadGeoIP("CN")
|
ips, err := loadGeoIP("CN")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher := &router.GeoIPMatcher{}
|
matcher := &GeoIPMatcher{}
|
||||||
common.Must(matcher.Init(ips))
|
common.Must(matcher.Init(ips))
|
||||||
|
|
||||||
if matcher.Match([]byte{8, 8, 8, 8}) {
|
if matcher.Match([]byte{8, 8, 8, 8}) {
|
||||||
@@ -139,7 +195,7 @@ func TestGeoIPMatcher6US(t *testing.T) {
|
|||||||
ips, err := loadGeoIP("US")
|
ips, err := loadGeoIP("US")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher := &router.GeoIPMatcher{}
|
matcher := &GeoIPMatcher{}
|
||||||
common.Must(matcher.Init(ips))
|
common.Must(matcher.Init(ips))
|
||||||
|
|
||||||
if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
|
if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
|
||||||
@@ -147,12 +203,12 @@ func TestGeoIPMatcher6US(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGeoIP(country string) ([]*router.CIDR, error) {
|
func loadGeoIP(country string) ([]*CIDR, error) {
|
||||||
geoipBytes, err := filesystem.ReadAsset("geoip.dat")
|
geoipBytes, err := filesystem.ReadAsset("geoip.dat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var geoipList router.GeoIPList
|
var geoipList GeoIPList
|
||||||
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -170,7 +226,7 @@ func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
|||||||
ips, err := loadGeoIP("CN")
|
ips, err := loadGeoIP("CN")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher := &router.GeoIPMatcher{}
|
matcher := &GeoIPMatcher{}
|
||||||
common.Must(matcher.Init(ips))
|
common.Must(matcher.Init(ips))
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
@@ -184,7 +240,7 @@ func BenchmarkGeoIPMatcher6US(b *testing.B) {
|
|||||||
ips, err := loadGeoIP("US")
|
ips, err := loadGeoIP("US")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher := &router.GeoIPMatcher{}
|
matcher := &GeoIPMatcher{}
|
||||||
common.Must(matcher.Init(ips))
|
common.Must(matcher.Init(ips))
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
57
common/matcher/geoip/matcher.go
Normal file
57
common/matcher/geoip/matcher.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package geoip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MultiGeoIPMatcher struct {
|
||||||
|
matchers []*GeoIPMatcher
|
||||||
|
onSource bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
|
||||||
|
var matchers []*GeoIPMatcher
|
||||||
|
for _, geoip := range geoips {
|
||||||
|
matcher, err := GlobalGeoIPContainer.Add(geoip)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
matchers = append(matchers, matcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher := &MultiGeoIPMatcher{
|
||||||
|
matchers: matchers,
|
||||||
|
onSource: onSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
return matcher, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Condition.
|
||||||
|
func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
|
||||||
|
var ips []net.IP
|
||||||
|
if m.onSource {
|
||||||
|
ips = ctx.GetSourceIPs()
|
||||||
|
} else {
|
||||||
|
ips = ctx.GetTargetIPs()
|
||||||
|
}
|
||||||
|
for _, ip := range ips {
|
||||||
|
for _, matcher := range m.matchers {
|
||||||
|
if matcher.Match(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchIP match given ip.
|
||||||
|
func (m *MultiGeoIPMatcher) MatchIP(ip net.IP) bool {
|
||||||
|
for _, matcher := range m.matchers {
|
||||||
|
if matcher.Match(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
35
common/matcher/geosite/attribute.go
Normal file
35
common/matcher/geosite/attribute.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package geosite
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type AttributeList struct {
|
||||||
|
matcher []AttributeMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (al *AttributeList) Match(domain *Domain) bool {
|
||||||
|
for _, matcher := range al.matcher {
|
||||||
|
if !matcher.Match(domain) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (al *AttributeList) IsEmpty() bool {
|
||||||
|
return len(al.matcher) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttributeMatcher interface {
|
||||||
|
Match(*Domain) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type BooleanMatcher string
|
||||||
|
|
||||||
|
func (m BooleanMatcher) Match(domain *Domain) bool {
|
||||||
|
for _, attr := range domain.Attribute {
|
||||||
|
if strings.EqualFold(attr.GetKey(), string(m)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
42
common/matcher/geosite/conf.go
Normal file
42
common/matcher/geosite/conf.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package geosite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
dm "github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadGeositeWithAttr(file string, siteWithAttr string) ([]*dm.Domain, error) {
|
||||||
|
parts := strings.Split(siteWithAttr, "@")
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return nil, newError("empty site")
|
||||||
|
}
|
||||||
|
country := strings.ToUpper(parts[0])
|
||||||
|
attrs := parseAttrs(parts[1:])
|
||||||
|
domains, err := loadSite(file, country)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if attrs.IsEmpty() {
|
||||||
|
return ToDomains(domains), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredDomains := make([]*dm.Domain, 0, len(domains))
|
||||||
|
for _, domain := range domains {
|
||||||
|
if attrs.Match(domain) {
|
||||||
|
filteredDomains = append(filteredDomains, domain.ToDomain())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredDomains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAttrs(attrs []string) *AttributeList {
|
||||||
|
al := new(AttributeList)
|
||||||
|
for _, attr := range attrs {
|
||||||
|
lc := strings.ToLower(attr)
|
||||||
|
al.matcher = append(al.matcher, BooleanMatcher(lc))
|
||||||
|
}
|
||||||
|
return al
|
||||||
|
}
|
||||||
9
common/matcher/geosite/errors.generated.go
Normal file
9
common/matcher/geosite/errors.generated.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package geosite
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
|
type errPathObjHolder struct{}
|
||||||
|
|
||||||
|
func newError(values ...interface{}) *errors.Error {
|
||||||
|
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||||
|
}
|
||||||
86
common/matcher/geosite/file.go
Normal file
86
common/matcher/geosite/file.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package geosite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SiteCache = make(map[string]*GeoSite)
|
||||||
|
FileCache = make(map[string][]byte)
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadFile(file string) ([]byte, error) {
|
||||||
|
if FileCache[file] == nil {
|
||||||
|
bs, err := filesystem.ReadAsset(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to open file: ", file).Base(err)
|
||||||
|
}
|
||||||
|
if len(bs) == 0 {
|
||||||
|
return nil, newError("empty file: ", file)
|
||||||
|
}
|
||||||
|
// Do not cache file, may save RAM when there
|
||||||
|
// are many files, but consume CPU each time.
|
||||||
|
return bs, nil
|
||||||
|
FileCache[file] = bs
|
||||||
|
}
|
||||||
|
return FileCache[file], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSite(file, code string) ([]*Domain, error) {
|
||||||
|
index := file + ":" + code
|
||||||
|
if SiteCache[index] == nil {
|
||||||
|
bs, err := loadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to load file: ", file).Base(err)
|
||||||
|
}
|
||||||
|
bs = find(bs, []byte(code))
|
||||||
|
if bs == nil {
|
||||||
|
return nil, newError("list not found in ", file, ": ", code)
|
||||||
|
}
|
||||||
|
var ges GeoSite
|
||||||
|
if err := proto.Unmarshal(bs, &ges); err != nil {
|
||||||
|
return nil, newError("error unmarshal Site in ", file, ": ", code).Base(err)
|
||||||
|
}
|
||||||
|
defer runtime.GC() // or debug.FreeOSMemory()
|
||||||
|
return ges.Domain, nil // do not cache geosite
|
||||||
|
SiteCache[index] = &ges
|
||||||
|
}
|
||||||
|
return SiteCache[index].Domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func find(data, code []byte) []byte {
|
||||||
|
codeL := len(code)
|
||||||
|
if codeL == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
dataL := len(data)
|
||||||
|
if dataL < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
x, y := proto.DecodeVarint(data[1:])
|
||||||
|
if x == 0 && y == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
headL, bodyL := 1+y, int(x)
|
||||||
|
dataL -= headL
|
||||||
|
if dataL < bodyL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data = data[headL:]
|
||||||
|
if int(data[1]) == codeL {
|
||||||
|
for i := 0; i < codeL && data[2+i] == code[i]; i++ {
|
||||||
|
if i+1 == codeL {
|
||||||
|
return data[:bodyL]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dataL == bodyL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data = data[bodyL:]
|
||||||
|
}
|
||||||
|
}
|
||||||
33
common/matcher/geosite/geosite.go
Normal file
33
common/matcher/geosite/geosite.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package geosite
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
|
||||||
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
|
|
||||||
|
func ToDomains(dms []*Domain) []*domain.Domain {
|
||||||
|
dm := make([]*domain.Domain, len(dms))
|
||||||
|
|
||||||
|
for idx, entry := range dms {
|
||||||
|
dm[idx] = entry.ToDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Domain) ToDomain() *domain.Domain {
|
||||||
|
return &domain.Domain{Type: d.Type.ToMatchingType(), Value: d.Value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Domain_Type) ToMatchingType() domain.MatchingType {
|
||||||
|
switch t {
|
||||||
|
case Domain_Plain:
|
||||||
|
return domain.MatchingType_Keyword
|
||||||
|
case Domain_Regex:
|
||||||
|
return domain.MatchingType_Regex
|
||||||
|
case Domain_Domain:
|
||||||
|
return domain.MatchingType_Subdomain
|
||||||
|
case Domain_Full:
|
||||||
|
return domain.MatchingType_Full
|
||||||
|
}
|
||||||
|
panic("impossible")
|
||||||
|
}
|
||||||
501
common/matcher/geosite/geosite.pb.go
Normal file
501
common/matcher/geosite/geosite.pb.go
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.25.0
|
||||||
|
// protoc v3.15.6
|
||||||
|
// source: common/matcher/geosite/geosite.proto
|
||||||
|
|
||||||
|
package geosite
|
||||||
|
|
||||||
|
import (
|
||||||
|
proto "github.com/golang/protobuf/proto"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||||
|
// of the legacy proto package is being used.
|
||||||
|
const _ = proto.ProtoPackageIsVersion4
|
||||||
|
|
||||||
|
type Domain_Type int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The value is used as is.
|
||||||
|
Domain_Plain Domain_Type = 0
|
||||||
|
// The value is used as a regular expression.
|
||||||
|
Domain_Regex Domain_Type = 1
|
||||||
|
// The value is a root domain.
|
||||||
|
Domain_Domain Domain_Type = 2
|
||||||
|
// The value is a domain.
|
||||||
|
Domain_Full Domain_Type = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for Domain_Type.
|
||||||
|
var (
|
||||||
|
Domain_Type_name = map[int32]string{
|
||||||
|
0: "Plain",
|
||||||
|
1: "Regex",
|
||||||
|
2: "Domain",
|
||||||
|
3: "Full",
|
||||||
|
}
|
||||||
|
Domain_Type_value = map[string]int32{
|
||||||
|
"Plain": 0,
|
||||||
|
"Regex": 1,
|
||||||
|
"Domain": 2,
|
||||||
|
"Full": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x Domain_Type) Enum() *Domain_Type {
|
||||||
|
p := new(Domain_Type)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x Domain_Type) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Domain_Type) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_common_matcher_geosite_geosite_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Domain_Type) Type() protoreflect.EnumType {
|
||||||
|
return &file_common_matcher_geosite_geosite_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x Domain_Type) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Domain_Type.Descriptor instead.
|
||||||
|
func (Domain_Type) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Domain struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Domain matching type.
|
||||||
|
Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=xray.common.matcher.geosite.Domain_Type" json:"type,omitempty"`
|
||||||
|
// Domain value.
|
||||||
|
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||||
|
// Attributes of this domain. May be used for filtering.
|
||||||
|
Attribute []*Domain_Attribute `protobuf:"bytes,3,rep,name=attribute,proto3" json:"attribute,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) Reset() {
|
||||||
|
*x = Domain{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_common_matcher_geosite_geosite_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Domain) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Domain) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_common_matcher_geosite_geosite_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Domain) Descriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) GetType() Domain_Type {
|
||||||
|
if x != nil {
|
||||||
|
return x.Type
|
||||||
|
}
|
||||||
|
return Domain_Plain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) GetValue() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain) GetAttribute() []*Domain_Attribute {
|
||||||
|
if x != nil {
|
||||||
|
return x.Attribute
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoSite struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
|
||||||
|
Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoSite) Reset() {
|
||||||
|
*x = GeoSite{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_common_matcher_geosite_geosite_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoSite) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GeoSite) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GeoSite) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_common_matcher_geosite_geosite_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GeoSite.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GeoSite) Descriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoSite) GetCountryCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.CountryCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoSite) GetDomain() []*Domain {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoSiteList struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Entry []*GeoSite `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoSiteList) Reset() {
|
||||||
|
*x = GeoSiteList{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_common_matcher_geosite_geosite_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoSiteList) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GeoSiteList) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GeoSiteList) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_common_matcher_geosite_geosite_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GeoSiteList) Descriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GeoSiteList) GetEntry() []*GeoSite {
|
||||||
|
if x != nil {
|
||||||
|
return x.Entry
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Domain_Attribute struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
// Types that are assignable to TypedValue:
|
||||||
|
// *Domain_Attribute_BoolValue
|
||||||
|
// *Domain_Attribute_IntValue
|
||||||
|
TypedValue isDomain_Attribute_TypedValue `protobuf_oneof:"typed_value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain_Attribute) Reset() {
|
||||||
|
*x = Domain_Attribute{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_common_matcher_geosite_geosite_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain_Attribute) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Domain_Attribute) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_common_matcher_geosite_geosite_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Domain_Attribute.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Domain_Attribute) Descriptor() ([]byte, []int) {
|
||||||
|
return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain_Attribute) GetKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue {
|
||||||
|
if m != nil {
|
||||||
|
return m.TypedValue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain_Attribute) GetBoolValue() bool {
|
||||||
|
if x, ok := x.GetTypedValue().(*Domain_Attribute_BoolValue); ok {
|
||||||
|
return x.BoolValue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Domain_Attribute) GetIntValue() int64 {
|
||||||
|
if x, ok := x.GetTypedValue().(*Domain_Attribute_IntValue); ok {
|
||||||
|
return x.IntValue
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type isDomain_Attribute_TypedValue interface {
|
||||||
|
isDomain_Attribute_TypedValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Domain_Attribute_BoolValue struct {
|
||||||
|
BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Domain_Attribute_IntValue struct {
|
||||||
|
IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Domain_Attribute_BoolValue) isDomain_Attribute_TypedValue() {}
|
||||||
|
|
||||||
|
func (*Domain_Attribute_IntValue) isDomain_Attribute_TypedValue() {}
|
||||||
|
|
||||||
|
var File_common_matcher_geosite_geosite_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_common_matcher_geosite_geosite_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x24, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
|
||||||
|
0x2f, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2f, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65,
|
||||||
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
|
0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x73,
|
||||||
|
0x69, 0x74, 0x65, 0x22, 0xcb, 0x02, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3c,
|
||||||
|
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x78,
|
||||||
|
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68,
|
||||||
|
0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
|
0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||||
|
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
||||||
|
0x75, 0x65, 0x12, 0x4b, 0x0a, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18,
|
||||||
|
0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
|
0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x73,
|
||||||
|
0x69, 0x74, 0x65, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69,
|
||||||
|
0x62, 0x75, 0x74, 0x65, 0x52, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x1a,
|
||||||
|
0x6c, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03,
|
||||||
|
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1f,
|
||||||
|
0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
|
0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,
|
||||||
|
0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||||
|
0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0d,
|
||||||
|
0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x32, 0x0a,
|
||||||
|
0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x10, 0x00,
|
||||||
|
0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44,
|
||||||
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10,
|
||||||
|
0x03, 0x22, 0x69, 0x0a, 0x07, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c,
|
||||||
|
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||||
|
0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12,
|
||||||
|
0x3b, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||||
|
0x23, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61,
|
||||||
|
0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x44, 0x6f,
|
||||||
|
0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x49, 0x0a, 0x0b,
|
||||||
|
0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x05, 0x65,
|
||||||
|
0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61,
|
||||||
|
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
|
||||||
|
0x2e, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65,
|
||||||
|
0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
|
||||||
|
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68,
|
||||||
|
0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69,
|
||||||
|
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72,
|
||||||
|
0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d,
|
||||||
|
0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0xaa, 0x02,
|
||||||
|
0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74,
|
||||||
|
0x63, 0x68, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x62, 0x06, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_common_matcher_geosite_geosite_proto_rawDescOnce sync.Once
|
||||||
|
file_common_matcher_geosite_geosite_proto_rawDescData = file_common_matcher_geosite_geosite_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_common_matcher_geosite_geosite_proto_rawDescGZIP() []byte {
|
||||||
|
file_common_matcher_geosite_geosite_proto_rawDescOnce.Do(func() {
|
||||||
|
file_common_matcher_geosite_geosite_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_matcher_geosite_geosite_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_common_matcher_geosite_geosite_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_common_matcher_geosite_geosite_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_common_matcher_geosite_geosite_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||||
|
var file_common_matcher_geosite_geosite_proto_goTypes = []interface{}{
|
||||||
|
(Domain_Type)(0), // 0: xray.common.matcher.geosite.Domain.Type
|
||||||
|
(*Domain)(nil), // 1: xray.common.matcher.geosite.Domain
|
||||||
|
(*GeoSite)(nil), // 2: xray.common.matcher.geosite.GeoSite
|
||||||
|
(*GeoSiteList)(nil), // 3: xray.common.matcher.geosite.GeoSiteList
|
||||||
|
(*Domain_Attribute)(nil), // 4: xray.common.matcher.geosite.Domain.Attribute
|
||||||
|
}
|
||||||
|
var file_common_matcher_geosite_geosite_proto_depIdxs = []int32{
|
||||||
|
0, // 0: xray.common.matcher.geosite.Domain.type:type_name -> xray.common.matcher.geosite.Domain.Type
|
||||||
|
4, // 1: xray.common.matcher.geosite.Domain.attribute:type_name -> xray.common.matcher.geosite.Domain.Attribute
|
||||||
|
1, // 2: xray.common.matcher.geosite.GeoSite.domain:type_name -> xray.common.matcher.geosite.Domain
|
||||||
|
2, // 3: xray.common.matcher.geosite.GeoSiteList.entry:type_name -> xray.common.matcher.geosite.GeoSite
|
||||||
|
4, // [4:4] is the sub-list for method output_type
|
||||||
|
4, // [4:4] is the sub-list for method input_type
|
||||||
|
4, // [4:4] is the sub-list for extension type_name
|
||||||
|
4, // [4:4] is the sub-list for extension extendee
|
||||||
|
0, // [0:4] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_common_matcher_geosite_geosite_proto_init() }
|
||||||
|
func file_common_matcher_geosite_geosite_proto_init() {
|
||||||
|
if File_common_matcher_geosite_geosite_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_common_matcher_geosite_geosite_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Domain); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_common_matcher_geosite_geosite_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GeoSite); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_common_matcher_geosite_geosite_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GeoSiteList); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_common_matcher_geosite_geosite_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Domain_Attribute); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_common_matcher_geosite_geosite_proto_msgTypes[3].OneofWrappers = []interface{}{
|
||||||
|
(*Domain_Attribute_BoolValue)(nil),
|
||||||
|
(*Domain_Attribute_IntValue)(nil),
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_common_matcher_geosite_geosite_proto_rawDesc,
|
||||||
|
NumEnums: 1,
|
||||||
|
NumMessages: 4,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_common_matcher_geosite_geosite_proto_goTypes,
|
||||||
|
DependencyIndexes: file_common_matcher_geosite_geosite_proto_depIdxs,
|
||||||
|
EnumInfos: file_common_matcher_geosite_geosite_proto_enumTypes,
|
||||||
|
MessageInfos: file_common_matcher_geosite_geosite_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_common_matcher_geosite_geosite_proto = out.File
|
||||||
|
file_common_matcher_geosite_geosite_proto_rawDesc = nil
|
||||||
|
file_common_matcher_geosite_geosite_proto_goTypes = nil
|
||||||
|
file_common_matcher_geosite_geosite_proto_depIdxs = nil
|
||||||
|
}
|
||||||
46
common/matcher/geosite/geosite.proto
Normal file
46
common/matcher/geosite/geosite.proto
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.common.matcher.geosite;
|
||||||
|
option csharp_namespace = "Xray.Common.Matcher.GeoSite";
|
||||||
|
option go_package = "github.com/xtls/xray-core/common/matcher/geosite";
|
||||||
|
option java_package = "com.xray.common.matcher.geosite";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message Domain {
|
||||||
|
enum Type {
|
||||||
|
// The value is used as is.
|
||||||
|
Plain = 0;
|
||||||
|
// The value is used as a regular expression.
|
||||||
|
Regex = 1;
|
||||||
|
// The value is a root domain.
|
||||||
|
Domain = 2;
|
||||||
|
// The value is a domain.
|
||||||
|
Full = 3;
|
||||||
|
}
|
||||||
|
// Domain matching type.
|
||||||
|
Type type = 1;
|
||||||
|
|
||||||
|
// Domain value.
|
||||||
|
string value = 2;
|
||||||
|
|
||||||
|
message Attribute {
|
||||||
|
string key = 1;
|
||||||
|
|
||||||
|
oneof typed_value {
|
||||||
|
bool bool_value = 2;
|
||||||
|
int64 int_value = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes of this domain. May be used for filtering.
|
||||||
|
repeated Attribute attribute = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GeoSite {
|
||||||
|
string country_code = 1;
|
||||||
|
repeated Domain domain = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GeoSiteList {
|
||||||
|
repeated GeoSite entry = 1;
|
||||||
|
}
|
||||||
243
common/matcher/str/ac_automaton_matcher.go
Normal file
243
common/matcher/str/ac_automaton_matcher.go
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
package str
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
const validCharCount = 53
|
||||||
|
|
||||||
|
type MatchType struct {
|
||||||
|
matchType Type
|
||||||
|
exist bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TrieEdge bool = true
|
||||||
|
FailEdge bool = false
|
||||||
|
)
|
||||||
|
|
||||||
|
type Edge struct {
|
||||||
|
edgeType bool
|
||||||
|
nextNode int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ACAutomaton struct {
|
||||||
|
trie [][validCharCount]Edge
|
||||||
|
fail []int
|
||||||
|
exists []MatchType
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNode() [validCharCount]Edge {
|
||||||
|
var s [validCharCount]Edge
|
||||||
|
for i := range s {
|
||||||
|
s[i] = Edge{
|
||||||
|
edgeType: FailEdge,
|
||||||
|
nextNode: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var char2Index = []int{
|
||||||
|
'A': 0,
|
||||||
|
'a': 0,
|
||||||
|
'B': 1,
|
||||||
|
'b': 1,
|
||||||
|
'C': 2,
|
||||||
|
'c': 2,
|
||||||
|
'D': 3,
|
||||||
|
'd': 3,
|
||||||
|
'E': 4,
|
||||||
|
'e': 4,
|
||||||
|
'F': 5,
|
||||||
|
'f': 5,
|
||||||
|
'G': 6,
|
||||||
|
'g': 6,
|
||||||
|
'H': 7,
|
||||||
|
'h': 7,
|
||||||
|
'I': 8,
|
||||||
|
'i': 8,
|
||||||
|
'J': 9,
|
||||||
|
'j': 9,
|
||||||
|
'K': 10,
|
||||||
|
'k': 10,
|
||||||
|
'L': 11,
|
||||||
|
'l': 11,
|
||||||
|
'M': 12,
|
||||||
|
'm': 12,
|
||||||
|
'N': 13,
|
||||||
|
'n': 13,
|
||||||
|
'O': 14,
|
||||||
|
'o': 14,
|
||||||
|
'P': 15,
|
||||||
|
'p': 15,
|
||||||
|
'Q': 16,
|
||||||
|
'q': 16,
|
||||||
|
'R': 17,
|
||||||
|
'r': 17,
|
||||||
|
'S': 18,
|
||||||
|
's': 18,
|
||||||
|
'T': 19,
|
||||||
|
't': 19,
|
||||||
|
'U': 20,
|
||||||
|
'u': 20,
|
||||||
|
'V': 21,
|
||||||
|
'v': 21,
|
||||||
|
'W': 22,
|
||||||
|
'w': 22,
|
||||||
|
'X': 23,
|
||||||
|
'x': 23,
|
||||||
|
'Y': 24,
|
||||||
|
'y': 24,
|
||||||
|
'Z': 25,
|
||||||
|
'z': 25,
|
||||||
|
'!': 26,
|
||||||
|
'$': 27,
|
||||||
|
'&': 28,
|
||||||
|
'\'': 29,
|
||||||
|
'(': 30,
|
||||||
|
')': 31,
|
||||||
|
'*': 32,
|
||||||
|
'+': 33,
|
||||||
|
',': 34,
|
||||||
|
';': 35,
|
||||||
|
'=': 36,
|
||||||
|
':': 37,
|
||||||
|
'%': 38,
|
||||||
|
'-': 39,
|
||||||
|
'.': 40,
|
||||||
|
'_': 41,
|
||||||
|
'~': 42,
|
||||||
|
'0': 43,
|
||||||
|
'1': 44,
|
||||||
|
'2': 45,
|
||||||
|
'3': 46,
|
||||||
|
'4': 47,
|
||||||
|
'5': 48,
|
||||||
|
'6': 49,
|
||||||
|
'7': 50,
|
||||||
|
'8': 51,
|
||||||
|
'9': 52,
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewACAutomaton() *ACAutomaton {
|
||||||
|
var ac = new(ACAutomaton)
|
||||||
|
ac.trie = append(ac.trie, newNode())
|
||||||
|
ac.fail = append(ac.fail, 0)
|
||||||
|
ac.exists = append(ac.exists, MatchType{
|
||||||
|
matchType: Full,
|
||||||
|
exist: false,
|
||||||
|
})
|
||||||
|
return ac
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ACAutomaton) Add(domain string, t Type) {
|
||||||
|
var node = 0
|
||||||
|
for i := len(domain) - 1; i >= 0; i-- {
|
||||||
|
var idx = char2Index[domain[i]]
|
||||||
|
if ac.trie[node][idx].nextNode == 0 {
|
||||||
|
ac.count++
|
||||||
|
if len(ac.trie) < ac.count+1 {
|
||||||
|
ac.trie = append(ac.trie, newNode())
|
||||||
|
ac.fail = append(ac.fail, 0)
|
||||||
|
ac.exists = append(ac.exists, MatchType{
|
||||||
|
matchType: Full,
|
||||||
|
exist: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ac.trie[node][idx] = Edge{
|
||||||
|
edgeType: TrieEdge,
|
||||||
|
nextNode: ac.count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node = ac.trie[node][idx].nextNode
|
||||||
|
}
|
||||||
|
ac.exists[node] = MatchType{
|
||||||
|
matchType: t,
|
||||||
|
exist: true,
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case Domain:
|
||||||
|
ac.exists[node] = MatchType{
|
||||||
|
matchType: Full,
|
||||||
|
exist: true,
|
||||||
|
}
|
||||||
|
var idx = char2Index['.']
|
||||||
|
if ac.trie[node][idx].nextNode == 0 {
|
||||||
|
ac.count++
|
||||||
|
if len(ac.trie) < ac.count+1 {
|
||||||
|
ac.trie = append(ac.trie, newNode())
|
||||||
|
ac.fail = append(ac.fail, 0)
|
||||||
|
ac.exists = append(ac.exists, MatchType{
|
||||||
|
matchType: Full,
|
||||||
|
exist: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ac.trie[node][idx] = Edge{
|
||||||
|
edgeType: TrieEdge,
|
||||||
|
nextNode: ac.count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node = ac.trie[node][idx].nextNode
|
||||||
|
ac.exists[node] = MatchType{
|
||||||
|
matchType: t,
|
||||||
|
exist: true,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ACAutomaton) Build() {
|
||||||
|
var queue = list.New()
|
||||||
|
for i := 0; i < validCharCount; i++ {
|
||||||
|
if ac.trie[0][i].nextNode != 0 {
|
||||||
|
queue.PushBack(ac.trie[0][i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
var front = queue.Front()
|
||||||
|
if front == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
var node = front.Value.(Edge).nextNode
|
||||||
|
queue.Remove(front)
|
||||||
|
for i := 0; i < validCharCount; i++ {
|
||||||
|
if ac.trie[node][i].nextNode != 0 {
|
||||||
|
ac.fail[ac.trie[node][i].nextNode] = ac.trie[ac.fail[node]][i].nextNode
|
||||||
|
queue.PushBack(ac.trie[node][i])
|
||||||
|
} else {
|
||||||
|
ac.trie[node][i] = Edge{
|
||||||
|
edgeType: FailEdge,
|
||||||
|
nextNode: ac.trie[ac.fail[node]][i].nextNode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ACAutomaton) Match(s string) bool {
|
||||||
|
var node = 0
|
||||||
|
var fullMatch = true
|
||||||
|
// 1. the match string is all through trie edge. FULL MATCH or DOMAIN
|
||||||
|
// 2. the match string is through a fail edge. NOT FULL MATCH
|
||||||
|
// 2.1 Through a fail edge, but there exists a valid node. SUBSTR
|
||||||
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
var idx = char2Index[s[i]]
|
||||||
|
fullMatch = fullMatch && ac.trie[node][idx].edgeType
|
||||||
|
node = ac.trie[node][idx].nextNode
|
||||||
|
switch ac.exists[node].matchType {
|
||||||
|
case Substr:
|
||||||
|
return true
|
||||||
|
case Domain:
|
||||||
|
if fullMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fullMatch && ac.exists[node].exist
|
||||||
|
}
|
||||||
@@ -1,13 +1,26 @@
|
|||||||
package strmatcher_test
|
package str_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
. "github.com/xtls/xray-core/common/strmatcher"
|
. "github.com/xtls/xray-core/common/matcher/str"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func BenchmarkACAutomaton(b *testing.B) {
|
||||||
|
ac := NewACAutomaton()
|
||||||
|
for i := 1; i <= 1024; i++ {
|
||||||
|
ac.Add(strconv.Itoa(i)+".xray.com", Domain)
|
||||||
|
}
|
||||||
|
ac.Build()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = ac.Match("0.xray.com")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkDomainMatcherGroup(b *testing.B) {
|
func BenchmarkDomainMatcherGroup(b *testing.B) {
|
||||||
g := new(DomainMatcherGroup)
|
g := new(DomainMatcherGroup)
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package strmatcher
|
package str
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package strmatcher_test
|
package str_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/common/strmatcher"
|
. "github.com/xtls/xray-core/common/matcher/str"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDomainMatcherGroup(t *testing.T) {
|
func TestDomainMatcherGroup(t *testing.T) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package strmatcher
|
package str
|
||||||
|
|
||||||
type FullMatcherGroup struct {
|
type FullMatcherGroup struct {
|
||||||
matchers map[string][]uint32
|
matchers map[string][]uint32
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package strmatcher_test
|
package str_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/common/strmatcher"
|
. "github.com/xtls/xray-core/common/matcher/str"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFullMatcherGroup(t *testing.T) {
|
func TestFullMatcherGroup(t *testing.T) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package strmatcher
|
package str
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package strmatcher_test
|
package str_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
. "github.com/xtls/xray-core/common/strmatcher"
|
. "github.com/xtls/xray-core/common/matcher/str"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMatcher(t *testing.T) {
|
func TestMatcher(t *testing.T) {
|
||||||
301
common/matcher/str/mph_matcher.go
Normal file
301
common/matcher/str/mph_matcher.go
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
package str
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/bits"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrimeRK is the prime base used in Rabin-Karp algorithm.
|
||||||
|
const PrimeRK = 16777619
|
||||||
|
|
||||||
|
// calculate the rolling murmurHash of given string
|
||||||
|
func RollingHash(s string) uint32 {
|
||||||
|
h := uint32(0)
|
||||||
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
h = h*PrimeRK + uint32(s[i])
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// A MphMatcherGroup is divided into three parts:
|
||||||
|
// 1. `full` and `domain` patterns are matched by Rabin-Karp algorithm and minimal perfect hash table;
|
||||||
|
// 2. `substr` patterns are matched by ac automaton;
|
||||||
|
// 3. `regex` patterns are matched with the regex library.
|
||||||
|
type MphMatcherGroup struct {
|
||||||
|
ac *ACAutomaton
|
||||||
|
otherMatchers []matcherEntry
|
||||||
|
rules []string
|
||||||
|
level0 []uint32
|
||||||
|
level0Mask int
|
||||||
|
level1 []uint32
|
||||||
|
level1Mask int
|
||||||
|
count uint32
|
||||||
|
ruleMap *map[string]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *MphMatcherGroup) AddFullOrDomainPattern(pattern string, t Type) {
|
||||||
|
h := RollingHash(pattern)
|
||||||
|
switch t {
|
||||||
|
case Domain:
|
||||||
|
(*g.ruleMap)["."+pattern] = h*PrimeRK + uint32('.')
|
||||||
|
fallthrough
|
||||||
|
case Full:
|
||||||
|
(*g.ruleMap)[pattern] = h
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMphMatcherGroup() *MphMatcherGroup {
|
||||||
|
return &MphMatcherGroup{
|
||||||
|
ac: nil,
|
||||||
|
otherMatchers: nil,
|
||||||
|
rules: nil,
|
||||||
|
level0: nil,
|
||||||
|
level0Mask: 0,
|
||||||
|
level1: nil,
|
||||||
|
level1Mask: 0,
|
||||||
|
count: 1,
|
||||||
|
ruleMap: &map[string]uint32{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPattern adds a pattern to MphMatcherGroup
|
||||||
|
func (g *MphMatcherGroup) AddPattern(pattern string, t Type) (uint32, error) {
|
||||||
|
switch t {
|
||||||
|
case Substr:
|
||||||
|
if g.ac == nil {
|
||||||
|
g.ac = NewACAutomaton()
|
||||||
|
}
|
||||||
|
g.ac.Add(pattern, t)
|
||||||
|
case Full, Domain:
|
||||||
|
pattern = strings.ToLower(pattern)
|
||||||
|
g.AddFullOrDomainPattern(pattern, t)
|
||||||
|
case Regex:
|
||||||
|
r, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
g.otherMatchers = append(g.otherMatchers, matcherEntry{
|
||||||
|
m: ®exMatcher{pattern: r},
|
||||||
|
id: g.count,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
panic("Unknown type")
|
||||||
|
}
|
||||||
|
return g.count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build builds a minimal perfect hash table and ac automaton from insert rules
|
||||||
|
func (g *MphMatcherGroup) Build() {
|
||||||
|
if g.ac != nil {
|
||||||
|
g.ac.Build()
|
||||||
|
}
|
||||||
|
keyLen := len(*g.ruleMap)
|
||||||
|
if keyLen == 0 {
|
||||||
|
keyLen = 1
|
||||||
|
(*g.ruleMap)["empty___"] = RollingHash("empty___")
|
||||||
|
}
|
||||||
|
g.level0 = make([]uint32, nextPow2(keyLen/4))
|
||||||
|
g.level0Mask = len(g.level0) - 1
|
||||||
|
g.level1 = make([]uint32, nextPow2(keyLen))
|
||||||
|
g.level1Mask = len(g.level1) - 1
|
||||||
|
var sparseBuckets = make([][]int, len(g.level0))
|
||||||
|
var ruleIdx int
|
||||||
|
for rule, hash := range *g.ruleMap {
|
||||||
|
n := int(hash) & g.level0Mask
|
||||||
|
g.rules = append(g.rules, rule)
|
||||||
|
sparseBuckets[n] = append(sparseBuckets[n], ruleIdx)
|
||||||
|
ruleIdx++
|
||||||
|
}
|
||||||
|
g.ruleMap = nil
|
||||||
|
var buckets []indexBucket
|
||||||
|
for n, vals := range sparseBuckets {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
buckets = append(buckets, indexBucket{n, vals})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(bySize(buckets))
|
||||||
|
|
||||||
|
occ := make([]bool, len(g.level1))
|
||||||
|
var tmpOcc []int
|
||||||
|
for _, bucket := range buckets {
|
||||||
|
var seed = uint32(0)
|
||||||
|
for {
|
||||||
|
findSeed := true
|
||||||
|
tmpOcc = tmpOcc[:0]
|
||||||
|
for _, i := range bucket.vals {
|
||||||
|
n := int(strhashFallback(unsafe.Pointer(&g.rules[i]), uintptr(seed))) & g.level1Mask
|
||||||
|
if occ[n] {
|
||||||
|
for _, n := range tmpOcc {
|
||||||
|
occ[n] = false
|
||||||
|
}
|
||||||
|
seed++
|
||||||
|
findSeed = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
occ[n] = true
|
||||||
|
tmpOcc = append(tmpOcc, n)
|
||||||
|
g.level1[n] = uint32(i)
|
||||||
|
}
|
||||||
|
if findSeed {
|
||||||
|
g.level0[bucket.n] = seed
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextPow2(v int) int {
|
||||||
|
if v <= 1 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
const MaxUInt = ^uint(0)
|
||||||
|
n := (MaxUInt >> bits.LeadingZeros(uint(v))) + 1
|
||||||
|
return int(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup searches for s in t and returns its index and whether it was found.
|
||||||
|
func (g *MphMatcherGroup) Lookup(h uint32, s string) bool {
|
||||||
|
i0 := int(h) & g.level0Mask
|
||||||
|
seed := g.level0[i0]
|
||||||
|
i1 := int(strhashFallback(unsafe.Pointer(&s), uintptr(seed))) & g.level1Mask
|
||||||
|
n := g.level1[i1]
|
||||||
|
return s == g.rules[int(n)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match implements IndexMatcher.Match.
|
||||||
|
func (g *MphMatcherGroup) Match(pattern string) []uint32 {
|
||||||
|
result := []uint32{}
|
||||||
|
hash := uint32(0)
|
||||||
|
for i := len(pattern) - 1; i >= 0; i-- {
|
||||||
|
hash = hash*PrimeRK + uint32(pattern[i])
|
||||||
|
if pattern[i] == '.' {
|
||||||
|
if g.Lookup(hash, pattern[i:]) {
|
||||||
|
result = append(result, 1)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if g.Lookup(hash, pattern) {
|
||||||
|
result = append(result, 1)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if g.ac != nil && g.ac.Match(pattern) {
|
||||||
|
result = append(result, 1)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
for _, e := range g.otherMatchers {
|
||||||
|
if e.m.Match(pattern) {
|
||||||
|
result = append(result, e.id)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexBucket struct {
|
||||||
|
n int
|
||||||
|
vals []int
|
||||||
|
}
|
||||||
|
|
||||||
|
type bySize []indexBucket
|
||||||
|
|
||||||
|
func (s bySize) Len() int { return len(s) }
|
||||||
|
func (s bySize) Less(i, j int) bool { return len(s[i].vals) > len(s[j].vals) }
|
||||||
|
func (s bySize) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
|
type stringStruct struct {
|
||||||
|
str unsafe.Pointer
|
||||||
|
len int
|
||||||
|
}
|
||||||
|
|
||||||
|
func strhashFallback(a unsafe.Pointer, h uintptr) uintptr {
|
||||||
|
x := (*stringStruct)(a)
|
||||||
|
return memhashFallback(x.str, h, uintptr(x.len))
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Constants for multiplication: four random odd 64-bit numbers.
|
||||||
|
m1 = 16877499708836156737
|
||||||
|
m2 = 2820277070424839065
|
||||||
|
m3 = 9497967016996688599
|
||||||
|
m4 = 15839092249703872147
|
||||||
|
)
|
||||||
|
|
||||||
|
var hashkey = [4]uintptr{1, 1, 1, 1}
|
||||||
|
|
||||||
|
func memhashFallback(p unsafe.Pointer, seed, s uintptr) uintptr {
|
||||||
|
h := uint64(seed + s*hashkey[0])
|
||||||
|
tail:
|
||||||
|
switch {
|
||||||
|
case s == 0:
|
||||||
|
case s < 4:
|
||||||
|
h ^= uint64(*(*byte)(p))
|
||||||
|
h ^= uint64(*(*byte)(add(p, s>>1))) << 8
|
||||||
|
h ^= uint64(*(*byte)(add(p, s-1))) << 16
|
||||||
|
h = rotl31(h*m1) * m2
|
||||||
|
case s <= 8:
|
||||||
|
h ^= uint64(readUnaligned32(p))
|
||||||
|
h ^= uint64(readUnaligned32(add(p, s-4))) << 32
|
||||||
|
h = rotl31(h*m1) * m2
|
||||||
|
case s <= 16:
|
||||||
|
h ^= readUnaligned64(p)
|
||||||
|
h = rotl31(h*m1) * m2
|
||||||
|
h ^= readUnaligned64(add(p, s-8))
|
||||||
|
h = rotl31(h*m1) * m2
|
||||||
|
case s <= 32:
|
||||||
|
h ^= readUnaligned64(p)
|
||||||
|
h = rotl31(h*m1) * m2
|
||||||
|
h ^= readUnaligned64(add(p, 8))
|
||||||
|
h = rotl31(h*m1) * m2
|
||||||
|
h ^= readUnaligned64(add(p, s-16))
|
||||||
|
h = rotl31(h*m1) * m2
|
||||||
|
h ^= readUnaligned64(add(p, s-8))
|
||||||
|
h = rotl31(h*m1) * m2
|
||||||
|
default:
|
||||||
|
v1 := h
|
||||||
|
v2 := uint64(seed * hashkey[1])
|
||||||
|
v3 := uint64(seed * hashkey[2])
|
||||||
|
v4 := uint64(seed * hashkey[3])
|
||||||
|
for s >= 32 {
|
||||||
|
v1 ^= readUnaligned64(p)
|
||||||
|
v1 = rotl31(v1*m1) * m2
|
||||||
|
p = add(p, 8)
|
||||||
|
v2 ^= readUnaligned64(p)
|
||||||
|
v2 = rotl31(v2*m2) * m3
|
||||||
|
p = add(p, 8)
|
||||||
|
v3 ^= readUnaligned64(p)
|
||||||
|
v3 = rotl31(v3*m3) * m4
|
||||||
|
p = add(p, 8)
|
||||||
|
v4 ^= readUnaligned64(p)
|
||||||
|
v4 = rotl31(v4*m4) * m1
|
||||||
|
p = add(p, 8)
|
||||||
|
s -= 32
|
||||||
|
}
|
||||||
|
h = v1 ^ v2 ^ v3 ^ v4
|
||||||
|
goto tail
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= m3
|
||||||
|
h ^= h >> 32
|
||||||
|
return uintptr(h)
|
||||||
|
}
|
||||||
|
func add(p unsafe.Pointer, x uintptr) unsafe.Pointer {
|
||||||
|
return unsafe.Pointer(uintptr(p) + x)
|
||||||
|
}
|
||||||
|
func readUnaligned32(p unsafe.Pointer) uint32 {
|
||||||
|
q := (*[4]byte)(p)
|
||||||
|
return uint32(q[0]) | uint32(q[1])<<8 | uint32(q[2])<<16 | uint32(q[3])<<24
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotl31(x uint64) uint64 {
|
||||||
|
return (x << 31) | (x >> (64 - 31))
|
||||||
|
}
|
||||||
|
func readUnaligned64(p unsafe.Pointer) uint64 {
|
||||||
|
q := (*[8]byte)(p)
|
||||||
|
return uint64(q[0]) | uint64(q[1])<<8 | uint64(q[2])<<16 | uint64(q[3])<<24 | uint64(q[4])<<32 | uint64(q[5])<<40 | uint64(q[6])<<48 | uint64(q[7])<<56
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package strmatcher
|
package str
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -27,6 +27,7 @@ const (
|
|||||||
|
|
||||||
// New creates a new Matcher based on the given pattern.
|
// New creates a new Matcher based on the given pattern.
|
||||||
func (t Type) New(pattern string) (Matcher, error) {
|
func (t Type) New(pattern string) (Matcher, error) {
|
||||||
|
// 1. regex matching is case-sensitive
|
||||||
switch t {
|
switch t {
|
||||||
case Full:
|
case Full:
|
||||||
return fullMatcher(pattern), nil
|
return fullMatcher(pattern), nil
|
||||||
262
common/matcher/str/strmatcher_test.go
Normal file
262
common/matcher/str/strmatcher_test.go
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
package str_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
. "github.com/xtls/xray-core/common/matcher/str"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatcherGroup(t *testing.T) {
|
||||||
|
rules := []struct {
|
||||||
|
Type Type
|
||||||
|
Domain string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Type: Regex,
|
||||||
|
Domain: "apis\\.us$",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Substr,
|
||||||
|
Domain: "apis",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Domain,
|
||||||
|
Domain: "googleapis.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Domain,
|
||||||
|
Domain: "com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Full,
|
||||||
|
Domain: "www.baidu.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Substr,
|
||||||
|
Domain: "apis",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Domain,
|
||||||
|
Domain: "googleapis.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Full,
|
||||||
|
Domain: "fonts.googleapis.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Full,
|
||||||
|
Domain: "www.baidu.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: Domain,
|
||||||
|
Domain: "example.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Output []uint32
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: "www.baidu.com",
|
||||||
|
Output: []uint32{5, 9, 4},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "fonts.googleapis.com",
|
||||||
|
Output: []uint32{8, 3, 7, 4, 2, 6},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "example.googleapis.com",
|
||||||
|
Output: []uint32{3, 7, 4, 2, 6},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "testapis.us",
|
||||||
|
Output: []uint32{1, 2, 6},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "example.com",
|
||||||
|
Output: []uint32{10, 4},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
matcherGroup := &MatcherGroup{}
|
||||||
|
for _, rule := range rules {
|
||||||
|
matcher, err := rule.Type.New(rule.Domain)
|
||||||
|
common.Must(err)
|
||||||
|
matcherGroup.Add(matcher)
|
||||||
|
}
|
||||||
|
for _, test := range cases {
|
||||||
|
if m := matcherGroup.Match(test.Input); !reflect.DeepEqual(m, test.Output) {
|
||||||
|
t.Error("unexpected output: ", m, " for test case ", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestACAutomaton(t *testing.T) {
|
||||||
|
cases1 := []struct {
|
||||||
|
pattern string
|
||||||
|
mType Type
|
||||||
|
input string
|
||||||
|
output bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
pattern: "xtls.github.io",
|
||||||
|
mType: Domain,
|
||||||
|
input: "www.xtls.github.io",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "xtls.github.io",
|
||||||
|
mType: Domain,
|
||||||
|
input: "xtls.github.io",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "xtls.github.io",
|
||||||
|
mType: Domain,
|
||||||
|
input: "www.xtis.github.io",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "xtls.github.io",
|
||||||
|
mType: Domain,
|
||||||
|
input: "tls.github.io",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "xtls.github.io",
|
||||||
|
mType: Domain,
|
||||||
|
input: "xxtls.github.io",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "xtls.github.io",
|
||||||
|
mType: Full,
|
||||||
|
input: "xtls.github.io",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "xtls.github.io",
|
||||||
|
mType: Full,
|
||||||
|
input: "xxtls.github.io",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range cases1 {
|
||||||
|
var ac = NewACAutomaton()
|
||||||
|
ac.Add(test.pattern, test.mType)
|
||||||
|
ac.Build()
|
||||||
|
if m := ac.Match(test.input); m != test.output {
|
||||||
|
t.Error("unexpected output: ", m, " for test case ", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cases2Input := []struct {
|
||||||
|
pattern string
|
||||||
|
mType Type
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
pattern: "163.com",
|
||||||
|
mType: Domain,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "m.126.com",
|
||||||
|
mType: Full,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "3.com",
|
||||||
|
mType: Full,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "google.com",
|
||||||
|
mType: Substr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "vgoogle.com",
|
||||||
|
mType: Substr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var ac = NewACAutomaton()
|
||||||
|
for _, test := range cases2Input {
|
||||||
|
ac.Add(test.pattern, test.mType)
|
||||||
|
}
|
||||||
|
ac.Build()
|
||||||
|
cases2Output := []struct {
|
||||||
|
pattern string
|
||||||
|
res bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
pattern: "126.com",
|
||||||
|
res: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "m.163.com",
|
||||||
|
res: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "mm163.com",
|
||||||
|
res: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "m.126.com",
|
||||||
|
res: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "163.com",
|
||||||
|
res: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "63.com",
|
||||||
|
res: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "oogle.com",
|
||||||
|
res: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "vvgoogle.com",
|
||||||
|
res: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range cases2Output {
|
||||||
|
if m := ac.Match(test.pattern); m != test.res {
|
||||||
|
t.Error("unexpected output: ", m, " for test case ", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cases3Input := []struct {
|
||||||
|
pattern string
|
||||||
|
mType Type
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
pattern: "video.google.com",
|
||||||
|
mType: Domain,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "gle.com",
|
||||||
|
mType: Domain,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var ac = NewACAutomaton()
|
||||||
|
for _, test := range cases3Input {
|
||||||
|
ac.Add(test.pattern, test.mType)
|
||||||
|
}
|
||||||
|
ac.Build()
|
||||||
|
cases3Output := []struct {
|
||||||
|
pattern string
|
||||||
|
res bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
pattern: "google.com",
|
||||||
|
res: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range cases3Output {
|
||||||
|
if m := ac.Match(test.pattern); m != test.res {
|
||||||
|
t.Error("unexpected output: ", m, " for test case ", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,7 +102,7 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
|
|||||||
return errNotClientHello
|
return errNotClientHello
|
||||||
}
|
}
|
||||||
if nameType == 0 {
|
if nameType == 0 {
|
||||||
serverName := strings.ToLower(string(d[:nameLen]))
|
serverName := string(d[:nameLen])
|
||||||
// An SNI value may not include a
|
// An SNI value may not include a
|
||||||
// trailing dot. See
|
// trailing dot. See
|
||||||
// https://tools.ietf.org/html/rfc6066#section-3.
|
// https://tools.ietf.org/html/rfc6066#section-3.
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/signal"
|
"github.com/xtls/xray-core/common/signal"
|
||||||
@@ -60,7 +62,8 @@ type Outbound struct {
|
|||||||
|
|
||||||
// SniffingRequest controls the behavior of content sniffing.
|
// SniffingRequest controls the behavior of content sniffing.
|
||||||
type SniffingRequest struct {
|
type SniffingRequest struct {
|
||||||
ExcludeForDomain []string
|
ExcludedDomainMatcher *domain.DomainMatcher
|
||||||
|
ExcludedIPMatcher *geoip.MultiGeoIPMatcher
|
||||||
OverrideDestinationForProtocol []string
|
OverrideDestinationForProtocol []string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
MetadataOnly bool
|
MetadataOnly bool
|
||||||
@@ -75,7 +78,7 @@ type Content struct {
|
|||||||
|
|
||||||
Attributes map[string]string
|
Attributes map[string]string
|
||||||
|
|
||||||
SkipRoutePick bool
|
SkipDNSResolve bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sockopt is the settings for socket connection.
|
// Sockopt is the settings for socket connection.
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
package strmatcher_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
. "github.com/xtls/xray-core/common/strmatcher"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMatcherGroup(t *testing.T) {
|
|
||||||
rules := []struct {
|
|
||||||
Type Type
|
|
||||||
Domain string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Type: Regex,
|
|
||||||
Domain: "apis\\.us$",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Substr,
|
|
||||||
Domain: "apis",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Domain,
|
|
||||||
Domain: "googleapis.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Domain,
|
|
||||||
Domain: "com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Full,
|
|
||||||
Domain: "www.baidu.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Substr,
|
|
||||||
Domain: "apis",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Domain,
|
|
||||||
Domain: "googleapis.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Full,
|
|
||||||
Domain: "fonts.googleapis.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Full,
|
|
||||||
Domain: "www.baidu.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: Domain,
|
|
||||||
Domain: "example.com",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cases := []struct {
|
|
||||||
Input string
|
|
||||||
Output []uint32
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Input: "www.baidu.com",
|
|
||||||
Output: []uint32{5, 9, 4},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Input: "fonts.googleapis.com",
|
|
||||||
Output: []uint32{8, 3, 7, 4, 2, 6},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Input: "example.googleapis.com",
|
|
||||||
Output: []uint32{3, 7, 4, 2, 6},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Input: "testapis.us",
|
|
||||||
Output: []uint32{1, 2, 6},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Input: "example.com",
|
|
||||||
Output: []uint32{10, 4},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
matcherGroup := &MatcherGroup{}
|
|
||||||
for _, rule := range rules {
|
|
||||||
matcher, err := rule.Type.New(rule.Domain)
|
|
||||||
common.Must(err)
|
|
||||||
matcherGroup.Add(matcher)
|
|
||||||
}
|
|
||||||
for _, test := range cases {
|
|
||||||
if m := matcherGroup.Match(test.Input); !reflect.DeepEqual(m, test.Output) {
|
|
||||||
t.Error("unexpected output: ", m, " for test case ", test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,12 @@ type IPOption struct {
|
|||||||
FakeEnable bool
|
FakeEnable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *IPOption) Copy() *IPOption {
|
||||||
|
return &IPOption{p.IPv4Enable, p.IPv6Enable, p.FakeEnable}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(dopt *IPOption) *IPOption
|
||||||
|
|
||||||
// Client is a Xray feature for querying DNS information.
|
// Client is a Xray feature for querying DNS information.
|
||||||
//
|
//
|
||||||
// xray:api:stable
|
// xray:api:stable
|
||||||
@@ -21,7 +27,24 @@ type Client interface {
|
|||||||
features.Feature
|
features.Feature
|
||||||
|
|
||||||
// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.
|
// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.
|
||||||
LookupIP(domain string, option IPOption) ([]net.IP, error)
|
LookupIP(domain string) ([]net.IP, error)
|
||||||
|
|
||||||
|
// LookupOptions query IP address for domain with *IPOption.
|
||||||
|
LookupOptions(domain string, opt ...Option) ([]net.IP, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4Lookup is an optional feature for querying IPv4 addresses only.
|
||||||
|
//
|
||||||
|
// xray:api:beta
|
||||||
|
type IPv4Lookup interface {
|
||||||
|
LookupIPv4(domain string) ([]net.IP, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv6Lookup is an optional feature for querying IPv6 addresses only.
|
||||||
|
//
|
||||||
|
// xray:api:beta
|
||||||
|
type IPv6Lookup interface {
|
||||||
|
LookupIPv6(domain string) ([]net.IP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientType returns the type of Client interface. Can be used for implementing common.HasType.
|
// ClientType returns the type of Client interface. Can be used for implementing common.HasType.
|
||||||
@@ -50,3 +73,35 @@ func RCodeFromError(err error) uint16 {
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
LookupIPv4Only = func(d *IPOption) *IPOption {
|
||||||
|
d.IPv4Enable = true
|
||||||
|
d.IPv6Enable = false
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
LookupIPv6Only = func(d *IPOption) *IPOption {
|
||||||
|
d.IPv4Enable = false
|
||||||
|
d.IPv6Enable = true
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
LookupIP = func(d *IPOption) *IPOption {
|
||||||
|
d.IPv4Enable = true
|
||||||
|
d.IPv6Enable = true
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
LookupFake = func(d *IPOption) *IPOption {
|
||||||
|
d.FakeEnable = true
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
LookupNoFake = func(d *IPOption) *IPOption {
|
||||||
|
d.FakeEnable = false
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
LookupAll = func(d *IPOption) *IPOption {
|
||||||
|
LookupIP(d)
|
||||||
|
LookupFake(d)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -20,41 +20,64 @@ func (*Client) Start() error { return nil }
|
|||||||
func (*Client) Close() error { return nil }
|
func (*Client) Close() error { return nil }
|
||||||
|
|
||||||
// LookupIP implements Client.
|
// LookupIP implements Client.
|
||||||
func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, error) {
|
func (*Client) LookupIP(host string) ([]net.IP, error) {
|
||||||
ips, err := net.LookupIP(host)
|
ips, err := net.LookupIP(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
parsedIPs := make([]net.IP, 0, len(ips))
|
parsedIPs := make([]net.IP, 0, len(ips))
|
||||||
ipv4 := make([]net.IP, 0, len(ips))
|
|
||||||
ipv6 := make([]net.IP, 0, len(ips))
|
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
parsed := net.IPAddress(ip)
|
parsed := net.IPAddress(ip)
|
||||||
if parsed != nil {
|
if parsed != nil {
|
||||||
parsedIPs = append(parsedIPs, parsed.IP())
|
parsedIPs = append(parsedIPs, parsed.IP())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if len(parsedIPs) == 0 {
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
return parsedIPs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupOptions implements Client.
|
||||||
|
func (c *Client) LookupOptions(host string, _ ...dns.Option) ([]net.IP, error) {
|
||||||
|
return c.LookupIP(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIPv4 implements IPv4Lookup.
|
||||||
|
func (c *Client) LookupIPv4(host string) ([]net.IP, error) {
|
||||||
|
ips, err := c.LookupIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ipv4 := make([]net.IP, 0, len(ips))
|
||||||
|
for _, ip := range ips {
|
||||||
if len(ip) == net.IPv4len {
|
if len(ip) == net.IPv4len {
|
||||||
ipv4 = append(ipv4, ip)
|
ipv4 = append(ipv4, ip)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if len(ipv4) == 0 {
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
return ipv4, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIPv6 implements IPv6Lookup.
|
||||||
|
func (c *Client) LookupIPv6(host string) ([]net.IP, error) {
|
||||||
|
ips, err := c.LookupIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ipv6 := make([]net.IP, 0, len(ips))
|
||||||
|
for _, ip := range ips {
|
||||||
if len(ip) == net.IPv6len {
|
if len(ip) == net.IPv6len {
|
||||||
ipv6 = append(ipv6, ip)
|
ipv6 = append(ipv6, ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch {
|
if len(ipv6) == 0 {
|
||||||
case option.IPv4Enable && option.IPv6Enable:
|
return nil, dns.ErrEmptyResponse
|
||||||
if len(parsedIPs) > 0 {
|
|
||||||
return parsedIPs, nil
|
|
||||||
}
|
|
||||||
case option.IPv4Enable:
|
|
||||||
if len(ipv4) > 0 {
|
|
||||||
return ipv4, nil
|
|
||||||
}
|
|
||||||
case option.IPv6Enable:
|
|
||||||
if len(ipv6) > 0 {
|
|
||||||
return ipv6, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, dns.ErrEmptyResponse
|
|
||||||
|
return ipv6, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create a new dns.Client that queries localhost for DNS.
|
// New create a new dns.Client that queries localhost for DNS.
|
||||||
|
|||||||
@@ -37,4 +37,7 @@ type Context interface {
|
|||||||
|
|
||||||
// GetAttributes returns extra attributes from the conneciont content.
|
// GetAttributes returns extra attributes from the conneciont content.
|
||||||
GetAttributes() map[string]string
|
GetAttributes() map[string]string
|
||||||
|
|
||||||
|
// GetSkipDNSResolve returns a flag switch for weather skip dns resolve during route pick.
|
||||||
|
GetSkipDNSResolve() bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,16 +26,12 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if domain := ctx.GetTargetDomain(); len(domain) != 0 {
|
if domain := ctx.GetTargetDomain(); len(domain) != 0 {
|
||||||
ips, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{
|
ips, err := ctx.dnsClient.LookupIP(domain)
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ctx.resolvedIPs = ips
|
ctx.resolvedIPs = ips
|
||||||
return ips
|
return ips
|
||||||
}
|
}
|
||||||
newError("resolve ip for ", domain).Base(err).WriteToLog()
|
newError("failed to resolve ip for ", domain).Base(err).WriteToLog()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -109,6 +109,14 @@ func (ctx *Context) GetAttributes() map[string]string {
|
|||||||
return ctx.Content.Attributes
|
return ctx.Content.Attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSkipDNSResolve implements routing.Context.
|
||||||
|
func (ctx *Context) GetSkipDNSResolve() bool {
|
||||||
|
if ctx.Content == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ctx.Content.SkipDNSResolve
|
||||||
|
}
|
||||||
|
|
||||||
// AsRoutingContext creates a context from context.context with session info.
|
// AsRoutingContext creates a context from context.context with session info.
|
||||||
func AsRoutingContext(ctx context.Context) routing.Context {
|
func AsRoutingContext(ctx context.Context) routing.Context {
|
||||||
return &Context{
|
return &Context{
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
|
|
||||||
type StringList []string
|
type StringList []string
|
||||||
|
|
||||||
func NewStringList(raw []string) *StringList {
|
func NewStringList(raw []string) *StringList {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package conf_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -11,12 +12,11 @@ import (
|
|||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStringListUnmarshalError(t *testing.T) {
|
func TestStringListUnmarshalError(t *testing.T) {
|
||||||
rawJSON := `1234`
|
rawJSON := `1234`
|
||||||
list := new(StringList)
|
list := new(conf.StringList)
|
||||||
err := json.Unmarshal([]byte(rawJSON), list)
|
err := json.Unmarshal([]byte(rawJSON), list)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error, but got nil")
|
t.Error("expected error, but got nil")
|
||||||
@@ -25,7 +25,7 @@ func TestStringListUnmarshalError(t *testing.T) {
|
|||||||
|
|
||||||
func TestStringListLen(t *testing.T) {
|
func TestStringListLen(t *testing.T) {
|
||||||
rawJSON := `"a, b, c, d"`
|
rawJSON := `"a, b, c, d"`
|
||||||
var list StringList
|
var list conf.StringList
|
||||||
err := json.Unmarshal([]byte(rawJSON), &list)
|
err := json.Unmarshal([]byte(rawJSON), &list)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if r := cmp.Diff([]string(list), []string{"a", " b", " c", " d"}); r != "" {
|
if r := cmp.Diff([]string(list), []string{"a", " b", " c", " d"}); r != "" {
|
||||||
@@ -35,7 +35,7 @@ func TestStringListLen(t *testing.T) {
|
|||||||
|
|
||||||
func TestIPParsing(t *testing.T) {
|
func TestIPParsing(t *testing.T) {
|
||||||
rawJSON := "\"8.8.8.8\""
|
rawJSON := "\"8.8.8.8\""
|
||||||
var address Address
|
var address conf.Address
|
||||||
err := json.Unmarshal([]byte(rawJSON), &address)
|
err := json.Unmarshal([]byte(rawJSON), &address)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != "" {
|
if r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != "" {
|
||||||
@@ -45,7 +45,7 @@ func TestIPParsing(t *testing.T) {
|
|||||||
|
|
||||||
func TestDomainParsing(t *testing.T) {
|
func TestDomainParsing(t *testing.T) {
|
||||||
rawJSON := "\"example.com\""
|
rawJSON := "\"example.com\""
|
||||||
var address Address
|
var address conf.Address
|
||||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||||
if address.Domain() != "example.com" {
|
if address.Domain() != "example.com" {
|
||||||
t.Error("domain: ", address.Domain())
|
t.Error("domain: ", address.Domain())
|
||||||
@@ -55,7 +55,7 @@ func TestDomainParsing(t *testing.T) {
|
|||||||
func TestURLParsing(t *testing.T) {
|
func TestURLParsing(t *testing.T) {
|
||||||
{
|
{
|
||||||
rawJSON := "\"https://dns.google/dns-query\""
|
rawJSON := "\"https://dns.google/dns-query\""
|
||||||
var address Address
|
var address conf.Address
|
||||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||||
if address.Domain() != "https://dns.google/dns-query" {
|
if address.Domain() != "https://dns.google/dns-query" {
|
||||||
t.Error("URL: ", address.Domain())
|
t.Error("URL: ", address.Domain())
|
||||||
@@ -63,7 +63,7 @@ func TestURLParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
rawJSON := "\"https+local://dns.google/dns-query\""
|
rawJSON := "\"https+local://dns.google/dns-query\""
|
||||||
var address Address
|
var address conf.Address
|
||||||
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
common.Must(json.Unmarshal([]byte(rawJSON), &address))
|
||||||
if address.Domain() != "https+local://dns.google/dns-query" {
|
if address.Domain() != "https+local://dns.google/dns-query" {
|
||||||
t.Error("URL: ", address.Domain())
|
t.Error("URL: ", address.Domain())
|
||||||
@@ -73,7 +73,7 @@ func TestURLParsing(t *testing.T) {
|
|||||||
|
|
||||||
func TestInvalidAddressJson(t *testing.T) {
|
func TestInvalidAddressJson(t *testing.T) {
|
||||||
rawJSON := "1234"
|
rawJSON := "1234"
|
||||||
var address Address
|
var address conf.Address
|
||||||
err := json.Unmarshal([]byte(rawJSON), &address)
|
err := json.Unmarshal([]byte(rawJSON), &address)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
@@ -81,7 +81,7 @@ func TestInvalidAddressJson(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStringNetwork(t *testing.T) {
|
func TestStringNetwork(t *testing.T) {
|
||||||
var network Network
|
var network conf.Network
|
||||||
common.Must(json.Unmarshal([]byte(`"tcp"`), &network))
|
common.Must(json.Unmarshal([]byte(`"tcp"`), &network))
|
||||||
if v := network.Build(); v != net.Network_TCP {
|
if v := network.Build(); v != net.Network_TCP {
|
||||||
t.Error("network: ", v)
|
t.Error("network: ", v)
|
||||||
@@ -89,7 +89,7 @@ func TestStringNetwork(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestArrayNetworkList(t *testing.T) {
|
func TestArrayNetworkList(t *testing.T) {
|
||||||
var list NetworkList
|
var list conf.NetworkList
|
||||||
common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list))
|
common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list))
|
||||||
|
|
||||||
nlist := list.Build()
|
nlist := list.Build()
|
||||||
@@ -102,7 +102,7 @@ func TestArrayNetworkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStringNetworkList(t *testing.T) {
|
func TestStringNetworkList(t *testing.T) {
|
||||||
var list NetworkList
|
var list conf.NetworkList
|
||||||
common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list))
|
common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list))
|
||||||
|
|
||||||
nlist := list.Build()
|
nlist := list.Build()
|
||||||
@@ -115,7 +115,7 @@ func TestStringNetworkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidNetworkJson(t *testing.T) {
|
func TestInvalidNetworkJson(t *testing.T) {
|
||||||
var list NetworkList
|
var list conf.NetworkList
|
||||||
err := json.Unmarshal([]byte("0"), &list)
|
err := json.Unmarshal([]byte("0"), &list)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
@@ -123,10 +123,10 @@ func TestInvalidNetworkJson(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIntPort(t *testing.T) {
|
func TestIntPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange conf.PortRange
|
||||||
common.Must(json.Unmarshal([]byte("1234"), &portRange))
|
common.Must(json.Unmarshal([]byte("1234"), &portRange))
|
||||||
|
|
||||||
if r := cmp.Diff(portRange, PortRange{
|
if r := cmp.Diff(portRange, conf.PortRange{
|
||||||
From: 1234, To: 1234,
|
From: 1234, To: 1234,
|
||||||
}); r != "" {
|
}); r != "" {
|
||||||
t.Error(r)
|
t.Error(r)
|
||||||
@@ -134,7 +134,7 @@ func TestIntPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOverRangeIntPort(t *testing.T) {
|
func TestOverRangeIntPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange conf.PortRange
|
||||||
err := json.Unmarshal([]byte("70000"), &portRange)
|
err := json.Unmarshal([]byte("70000"), &portRange)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
@@ -149,10 +149,10 @@ func TestOverRangeIntPort(t *testing.T) {
|
|||||||
func TestEnvPort(t *testing.T) {
|
func TestEnvPort(t *testing.T) {
|
||||||
common.Must(os.Setenv("PORT", "1234"))
|
common.Must(os.Setenv("PORT", "1234"))
|
||||||
|
|
||||||
var portRange PortRange
|
var portRange conf.PortRange
|
||||||
common.Must(json.Unmarshal([]byte("\"env:PORT\""), &portRange))
|
common.Must(json.Unmarshal([]byte("\"env:PORT\""), &portRange))
|
||||||
|
|
||||||
if r := cmp.Diff(portRange, PortRange{
|
if r := cmp.Diff(portRange, conf.PortRange{
|
||||||
From: 1234, To: 1234,
|
From: 1234, To: 1234,
|
||||||
}); r != "" {
|
}); r != "" {
|
||||||
t.Error(r)
|
t.Error(r)
|
||||||
@@ -160,10 +160,10 @@ func TestEnvPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSingleStringPort(t *testing.T) {
|
func TestSingleStringPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange conf.PortRange
|
||||||
common.Must(json.Unmarshal([]byte("\"1234\""), &portRange))
|
common.Must(json.Unmarshal([]byte("\"1234\""), &portRange))
|
||||||
|
|
||||||
if r := cmp.Diff(portRange, PortRange{
|
if r := cmp.Diff(portRange, conf.PortRange{
|
||||||
From: 1234, To: 1234,
|
From: 1234, To: 1234,
|
||||||
}); r != "" {
|
}); r != "" {
|
||||||
t.Error(r)
|
t.Error(r)
|
||||||
@@ -171,10 +171,10 @@ func TestSingleStringPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStringPairPort(t *testing.T) {
|
func TestStringPairPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange conf.PortRange
|
||||||
common.Must(json.Unmarshal([]byte("\"1234-5678\""), &portRange))
|
common.Must(json.Unmarshal([]byte("\"1234-5678\""), &portRange))
|
||||||
|
|
||||||
if r := cmp.Diff(portRange, PortRange{
|
if r := cmp.Diff(portRange, conf.PortRange{
|
||||||
From: 1234, To: 5678,
|
From: 1234, To: 5678,
|
||||||
}); r != "" {
|
}); r != "" {
|
||||||
t.Error(r)
|
t.Error(r)
|
||||||
@@ -182,7 +182,7 @@ func TestStringPairPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOverRangeStringPort(t *testing.T) {
|
func TestOverRangeStringPort(t *testing.T) {
|
||||||
var portRange PortRange
|
var portRange conf.PortRange
|
||||||
err := json.Unmarshal([]byte("\"65536\""), &portRange)
|
err := json.Unmarshal([]byte("\"65536\""), &portRange)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
@@ -205,7 +205,7 @@ func TestOverRangeStringPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserParsing(t *testing.T) {
|
func TestUserParsing(t *testing.T) {
|
||||||
user := new(User)
|
user := new(conf.User)
|
||||||
common.Must(json.Unmarshal([]byte(`{
|
common.Must(json.Unmarshal([]byte(`{
|
||||||
"id": "96edb838-6d68-42ef-a933-25f7ac3a9d09",
|
"id": "96edb838-6d68-42ef-a933-25f7ac3a9d09",
|
||||||
"email": "love@example.com",
|
"email": "love@example.com",
|
||||||
@@ -223,7 +223,7 @@ func TestUserParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidUserJson(t *testing.T) {
|
func TestInvalidUserJson(t *testing.T) {
|
||||||
user := new(User)
|
user := new(conf.User)
|
||||||
err := json.Unmarshal([]byte(`{"email": 1234}`), user)
|
err := json.Unmarshal([]byte(`{"email": 1234}`), user)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("nil error")
|
t.Error("nil error")
|
||||||
|
|||||||
@@ -2,19 +2,25 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/dns"
|
"github.com/xtls/xray-core/app/dns"
|
||||||
"github.com/xtls/xray-core/app/router"
|
dm "github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain/conf"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geosite"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NameServerConfig struct {
|
type NameServerConfig struct {
|
||||||
Address *Address
|
Address *Address
|
||||||
Port uint16
|
ClientIP *Address
|
||||||
Domains []string
|
Port uint16
|
||||||
ExpectIPs StringList
|
SkipFallback bool
|
||||||
|
Domains []string
|
||||||
|
ExpectIPs StringList
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
||||||
@@ -25,14 +31,18 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var advanced struct {
|
var advanced struct {
|
||||||
Address *Address `json:"address"`
|
Address *Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
ClientIP *Address `json:"clientIp"`
|
||||||
Domains []string `json:"domains"`
|
Port uint16 `json:"port"`
|
||||||
ExpectIPs StringList `json:"expectIps"`
|
SkipFallback bool `json:"skipFallback"`
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
ExpectIPs StringList `json:"expectIps"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &advanced); err == nil {
|
if err := json.Unmarshal(data, &advanced); err == nil {
|
||||||
c.Address = advanced.Address
|
c.Address = advanced.Address
|
||||||
|
c.ClientIP = advanced.ClientIP
|
||||||
c.Port = advanced.Port
|
c.Port = advanced.Port
|
||||||
|
c.SkipFallback = advanced.SkipFallback
|
||||||
c.Domains = advanced.Domains
|
c.Domains = advanced.Domains
|
||||||
c.ExpectIPs = advanced.ExpectIPs
|
c.ExpectIPs = advanced.ExpectIPs
|
||||||
return nil
|
return nil
|
||||||
@@ -41,39 +51,24 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
|||||||
return newError("failed to parse name server: ", string(data))
|
return newError("failed to parse name server: ", string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
|
|
||||||
switch t {
|
|
||||||
case router.Domain_Domain:
|
|
||||||
return dns.DomainMatchingType_Subdomain
|
|
||||||
case router.Domain_Full:
|
|
||||||
return dns.DomainMatchingType_Full
|
|
||||||
case router.Domain_Plain:
|
|
||||||
return dns.DomainMatchingType_Keyword
|
|
||||||
case router.Domain_Regex:
|
|
||||||
return dns.DomainMatchingType_Regex
|
|
||||||
default:
|
|
||||||
panic("unknown domain type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||||
if c.Address == nil {
|
if c.Address == nil {
|
||||||
return nil, newError("NameServer address is not specified.")
|
return nil, newError("NameServer address is not specified.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var domains []*dns.NameServer_PriorityDomain
|
var domains []*dm.Domain
|
||||||
var originalRules []*dns.NameServer_OriginalRule
|
var originalRules []*dns.NameServer_OriginalRule
|
||||||
|
|
||||||
for _, rule := range c.Domains {
|
for _, rule := range c.Domains {
|
||||||
parsedDomain, err := parseDomainRule(rule)
|
parsedDomain, err := conf.ParseDomainRule(rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid domain rule: ", rule).Base(err)
|
return nil, newError("invalid domain rule: ", rule).Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pd := range parsedDomain {
|
for _, pd := range parsedDomain {
|
||||||
domains = append(domains, &dns.NameServer_PriorityDomain{
|
domains = append(domains, &dm.Domain{
|
||||||
Type: toDomainMatchingType(pd.Type),
|
Type: pd.Type,
|
||||||
Domain: pd.Value,
|
Value: pd.Value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
originalRules = append(originalRules, &dns.NameServer_OriginalRule{
|
originalRules = append(originalRules, &dns.NameServer_OriginalRule{
|
||||||
@@ -82,54 +77,100 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
geoipList, err := toCidrList(c.ExpectIPs)
|
geoipList, err := geoip.ParseIPList(c.ExpectIPs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
|
return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var myClientIP []byte
|
||||||
|
if c.ClientIP != nil {
|
||||||
|
if !c.ClientIP.Family().IsIP() {
|
||||||
|
return nil, newError("not an IP address:", c.ClientIP.String())
|
||||||
|
}
|
||||||
|
myClientIP = []byte(c.ClientIP.IP())
|
||||||
|
}
|
||||||
return &dns.NameServer{
|
return &dns.NameServer{
|
||||||
Address: &net.Endpoint{
|
Address: &net.Endpoint{
|
||||||
Network: net.Network_UDP,
|
Network: net.Network_UDP,
|
||||||
Address: c.Address.Build(),
|
Address: c.Address.Build(),
|
||||||
Port: uint32(c.Port),
|
Port: uint32(c.Port),
|
||||||
},
|
},
|
||||||
|
ClientIp: myClientIP,
|
||||||
|
SkipFallback: c.SkipFallback,
|
||||||
PrioritizedDomain: domains,
|
PrioritizedDomain: domains,
|
||||||
Geoip: geoipList,
|
Geoip: geoipList,
|
||||||
OriginalRules: originalRules,
|
OriginalRules: originalRules,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeMap = map[router.Domain_Type]dns.DomainMatchingType{
|
|
||||||
router.Domain_Full: dns.DomainMatchingType_Full,
|
|
||||||
router.Domain_Domain: dns.DomainMatchingType_Subdomain,
|
|
||||||
router.Domain_Plain: dns.DomainMatchingType_Keyword,
|
|
||||||
router.Domain_Regex: dns.DomainMatchingType_Regex,
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSConfig is a JSON serializable object for dns.Config.
|
// DNSConfig is a JSON serializable object for dns.Config.
|
||||||
type DNSConfig struct {
|
type DNSConfig struct {
|
||||||
Servers []*NameServerConfig `json:"servers"`
|
Servers []*NameServerConfig `json:"servers"`
|
||||||
Hosts map[string]*Address `json:"hosts"`
|
Hosts map[string]*HostAddress `json:"hosts"`
|
||||||
ClientIP *Address `json:"clientIp"`
|
ClientIP *Address `json:"clientIp"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
|
QueryStrategy string `json:"queryStrategy"`
|
||||||
|
CacheStrategy string `json:"cacheStrategy"`
|
||||||
|
DisableCache bool `json:"disableCache"`
|
||||||
|
DisableFallback bool `json:"disableFallback"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHostMapping(addr *Address) *dns.Config_HostMapping {
|
type HostAddress struct {
|
||||||
if addr.Family().IsIP() {
|
addr *Address
|
||||||
return &dns.Config_HostMapping{
|
addrs []*Address
|
||||||
Ip: [][]byte{[]byte(addr.IP())},
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||||
|
func (h *HostAddress) UnmarshalJSON(data []byte) error {
|
||||||
|
addr := new(Address)
|
||||||
|
var addrs []*Address
|
||||||
|
switch {
|
||||||
|
case json.Unmarshal(data, &addr) == nil:
|
||||||
|
h.addr = addr
|
||||||
|
case json.Unmarshal(data, &addrs) == nil:
|
||||||
|
h.addrs = addrs
|
||||||
|
default:
|
||||||
|
return newError("invalid address")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
|
||||||
|
if ha.addr != nil {
|
||||||
|
if ha.addr.Family().IsDomain() {
|
||||||
|
return &dns.Config_HostMapping{
|
||||||
|
ProxiedDomain: ha.addr.Domain(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return &dns.Config_HostMapping{
|
return &dns.Config_HostMapping{
|
||||||
ProxiedDomain: addr.Domain(),
|
Ip: [][]byte{ha.addr.IP()},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ips := make([][]byte, 0, len(ha.addrs))
|
||||||
|
for _, addr := range ha.addrs {
|
||||||
|
if addr.Family().IsDomain() {
|
||||||
|
return &dns.Config_HostMapping{
|
||||||
|
ProxiedDomain: addr.Domain(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ips = append(ips, []byte(addr.IP()))
|
||||||
|
}
|
||||||
|
return &dns.Config_HostMapping{
|
||||||
|
Ip: ips,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable
|
// Build implements Buildable
|
||||||
func (c *DNSConfig) Build() (*dns.Config, error) {
|
func (c *DNSConfig) Build() (*dns.Config, error) {
|
||||||
config := &dns.Config{
|
config := &dns.Config{
|
||||||
Tag: c.Tag,
|
Tag: c.Tag,
|
||||||
|
CacheStrategy: dns.CacheStrategy_Cache_ALL,
|
||||||
|
DisableFallback: c.DisableFallback,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DisableCache {
|
||||||
|
config.CacheStrategy = dns.CacheStrategy_Cache_DISABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ClientIP != nil {
|
if c.ClientIP != nil {
|
||||||
@@ -139,6 +180,25 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
config.ClientIp = []byte(c.ClientIP.IP())
|
config.ClientIp = []byte(c.ClientIP.IP())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.QueryStrategy = dns.QueryStrategy_USE_IP
|
||||||
|
switch strings.ToLower(c.QueryStrategy) {
|
||||||
|
case "useip", "use_ip", "use-ip":
|
||||||
|
config.QueryStrategy = dns.QueryStrategy_USE_IP
|
||||||
|
case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4":
|
||||||
|
config.QueryStrategy = dns.QueryStrategy_USE_IP4
|
||||||
|
case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
|
||||||
|
config.QueryStrategy = dns.QueryStrategy_USE_IP6
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(c.CacheStrategy) {
|
||||||
|
case "noerror":
|
||||||
|
config.CacheStrategy = dns.CacheStrategy_Cache_NOERROR
|
||||||
|
case "all":
|
||||||
|
config.CacheStrategy = dns.CacheStrategy_Cache_ALL
|
||||||
|
case "disable", "none":
|
||||||
|
config.CacheStrategy = dns.CacheStrategy_Cache_DISABLE
|
||||||
|
}
|
||||||
|
|
||||||
for _, server := range c.Servers {
|
for _, server := range c.Servers {
|
||||||
ns, err := server.Build()
|
ns, err := server.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -164,7 +224,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
return nil, newError("empty domain type of rule: ", domain)
|
return nil, newError("empty domain type of rule: ", domain)
|
||||||
}
|
}
|
||||||
mapping := getHostMapping(addr)
|
mapping := getHostMapping(addr)
|
||||||
mapping.Type = dns.DomainMatchingType_Subdomain
|
mapping.Type = dm.MatchingType_Subdomain
|
||||||
mapping.Domain = domainName
|
mapping.Domain = domainName
|
||||||
mappings = append(mappings, mapping)
|
mappings = append(mappings, mapping)
|
||||||
|
|
||||||
@@ -173,13 +233,13 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
return nil, newError("empty geosite rule: ", domain)
|
return nil, newError("empty geosite rule: ", domain)
|
||||||
}
|
}
|
||||||
domains, err := loadGeositeWithAttr("geosite.dat", listName)
|
domains, err := geosite.LoadGeositeWithAttr("geosite.dat", listName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to load geosite: ", listName).Base(err)
|
return nil, newError("failed to load geosite: ", listName).Base(err)
|
||||||
}
|
}
|
||||||
for _, d := range domains {
|
for _, d := range domains {
|
||||||
mapping := getHostMapping(addr)
|
mapping := getHostMapping(addr)
|
||||||
mapping.Type = typeMap[d.Type]
|
mapping.Type = d.Type
|
||||||
mapping.Domain = d.Value
|
mapping.Domain = d.Value
|
||||||
mappings = append(mappings, mapping)
|
mappings = append(mappings, mapping)
|
||||||
}
|
}
|
||||||
@@ -190,7 +250,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
return nil, newError("empty regexp type of rule: ", domain)
|
return nil, newError("empty regexp type of rule: ", domain)
|
||||||
}
|
}
|
||||||
mapping := getHostMapping(addr)
|
mapping := getHostMapping(addr)
|
||||||
mapping.Type = dns.DomainMatchingType_Regex
|
mapping.Type = dm.MatchingType_Regex
|
||||||
mapping.Domain = regexpVal
|
mapping.Domain = regexpVal
|
||||||
mappings = append(mappings, mapping)
|
mappings = append(mappings, mapping)
|
||||||
|
|
||||||
@@ -200,7 +260,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
return nil, newError("empty keyword type of rule: ", domain)
|
return nil, newError("empty keyword type of rule: ", domain)
|
||||||
}
|
}
|
||||||
mapping := getHostMapping(addr)
|
mapping := getHostMapping(addr)
|
||||||
mapping.Type = dns.DomainMatchingType_Keyword
|
mapping.Type = dm.MatchingType_Keyword
|
||||||
mapping.Domain = keywordVal
|
mapping.Domain = keywordVal
|
||||||
mappings = append(mappings, mapping)
|
mappings = append(mappings, mapping)
|
||||||
|
|
||||||
@@ -210,13 +270,13 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
return nil, newError("empty full domain type of rule: ", domain)
|
return nil, newError("empty full domain type of rule: ", domain)
|
||||||
}
|
}
|
||||||
mapping := getHostMapping(addr)
|
mapping := getHostMapping(addr)
|
||||||
mapping.Type = dns.DomainMatchingType_Full
|
mapping.Type = dm.MatchingType_Full
|
||||||
mapping.Domain = fullVal
|
mapping.Domain = fullVal
|
||||||
mappings = append(mappings, mapping)
|
mappings = append(mappings, mapping)
|
||||||
|
|
||||||
case strings.HasPrefix(domain, "dotless:"):
|
case strings.HasPrefix(domain, "dotless:"):
|
||||||
mapping := getHostMapping(addr)
|
mapping := getHostMapping(addr)
|
||||||
mapping.Type = dns.DomainMatchingType_Regex
|
mapping.Type = dm.MatchingType_Regex
|
||||||
switch substr := domain[8:]; {
|
switch substr := domain[8:]; {
|
||||||
case substr == "":
|
case substr == "":
|
||||||
mapping.Domain = "^[^.]*$"
|
mapping.Domain = "^[^.]*$"
|
||||||
@@ -234,20 +294,20 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||||||
}
|
}
|
||||||
filename := kv[0]
|
filename := kv[0]
|
||||||
list := kv[1]
|
list := kv[1]
|
||||||
domains, err := loadGeositeWithAttr(filename, list)
|
domains, err := geosite.LoadGeositeWithAttr(filename, list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
|
return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
|
||||||
}
|
}
|
||||||
for _, d := range domains {
|
for _, d := range domains {
|
||||||
mapping := getHostMapping(addr)
|
mapping := getHostMapping(addr)
|
||||||
mapping.Type = typeMap[d.Type]
|
mapping.Type = d.Type
|
||||||
mapping.Domain = d.Value
|
mapping.Domain = d.Value
|
||||||
mappings = append(mappings, mapping)
|
mappings = append(mappings, mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
mapping := getHostMapping(addr)
|
mapping := getHostMapping(addr)
|
||||||
mapping.Type = dns.DomainMatchingType_Full
|
mapping.Type = dm.MatchingType_Full
|
||||||
mapping.Domain = domain
|
mapping.Domain = domain
|
||||||
mappings = append(mappings, mapping)
|
mappings = append(mappings, mapping)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import (
|
|||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/xtls/xray-core/app/dns"
|
"github.com/xtls/xray-core/app/dns"
|
||||||
"github.com/xtls/xray-core/app/router"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geosite"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
@@ -30,12 +31,12 @@ func init() {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer geositeFile.Close()
|
defer geositeFile.Close()
|
||||||
|
|
||||||
list := &router.GeoSiteList{
|
list := &geosite.GeoSiteList{
|
||||||
Entry: []*router.GeoSite{
|
Entry: []*geosite.GeoSite{
|
||||||
{
|
{
|
||||||
CountryCode: "TEST",
|
CountryCode: "TEST",
|
||||||
Domain: []*router.Domain{
|
Domain: []*geosite.Domain{
|
||||||
{Type: router.Domain_Full, Value: "example.com"},
|
{Type: geosite.Domain_Full, Value: "example.com"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -67,17 +68,23 @@ func TestDNSConfigParsing(t *testing.T) {
|
|||||||
Input: `{
|
Input: `{
|
||||||
"servers": [{
|
"servers": [{
|
||||||
"address": "8.8.8.8",
|
"address": "8.8.8.8",
|
||||||
|
"clientIp": "10.0.0.1",
|
||||||
"port": 5353,
|
"port": 5353,
|
||||||
|
"skipFallback": true,
|
||||||
"domains": ["domain:example.com"]
|
"domains": ["domain:example.com"]
|
||||||
}],
|
}],
|
||||||
"hosts": {
|
"hosts": {
|
||||||
"example.com": "127.0.0.1",
|
"example.com": "127.0.0.1",
|
||||||
|
"xtls.github.io": ["1.2.3.4", "5.6.7.8"],
|
||||||
"domain:example.com": "google.com",
|
"domain:example.com": "google.com",
|
||||||
"geosite:test": "10.0.0.1",
|
"geosite:test": ["127.0.0.1", "127.0.0.2"],
|
||||||
"keyword:google": "8.8.8.8",
|
"keyword:google": ["8.8.8.8", "8.8.4.4"],
|
||||||
"regexp:.*\\.com": "8.8.4.4"
|
"regexp:.*\\.com": "8.8.4.4"
|
||||||
},
|
},
|
||||||
"clientIp": "10.0.0.1"
|
"clientIp": "10.0.0.1",
|
||||||
|
"queryStrategy": "UseIPv4",
|
||||||
|
"cacheStrategy": "disable",
|
||||||
|
"disableFallback": true
|
||||||
}`,
|
}`,
|
||||||
Parser: parserCreator(),
|
Parser: parserCreator(),
|
||||||
Output: &dns.Config{
|
Output: &dns.Config{
|
||||||
@@ -92,10 +99,12 @@ func TestDNSConfigParsing(t *testing.T) {
|
|||||||
Network: net.Network_UDP,
|
Network: net.Network_UDP,
|
||||||
Port: 5353,
|
Port: 5353,
|
||||||
},
|
},
|
||||||
PrioritizedDomain: []*dns.NameServer_PriorityDomain{
|
ClientIp: []byte{10, 0, 0, 1},
|
||||||
|
SkipFallback: true,
|
||||||
|
PrioritizedDomain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: dns.DomainMatchingType_Subdomain,
|
Type: domain.MatchingType_Subdomain,
|
||||||
Domain: "example.com",
|
Value: "example.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OriginalRules: []*dns.NameServer_OriginalRule{
|
OriginalRules: []*dns.NameServer_OriginalRule{
|
||||||
@@ -108,32 +117,40 @@ func TestDNSConfigParsing(t *testing.T) {
|
|||||||
},
|
},
|
||||||
StaticHosts: []*dns.Config_HostMapping{
|
StaticHosts: []*dns.Config_HostMapping{
|
||||||
{
|
{
|
||||||
Type: dns.DomainMatchingType_Subdomain,
|
Type: domain.MatchingType_Subdomain,
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
ProxiedDomain: "google.com",
|
ProxiedDomain: "google.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: dns.DomainMatchingType_Full,
|
Type: domain.MatchingType_Full,
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Ip: [][]byte{{127, 0, 0, 1}},
|
Ip: [][]byte{{127, 0, 0, 1}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: dns.DomainMatchingType_Full,
|
Type: domain.MatchingType_Full,
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Ip: [][]byte{{10, 0, 0, 1}},
|
Ip: [][]byte{{127, 0, 0, 1}, {127, 0, 0, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: dns.DomainMatchingType_Keyword,
|
Type: domain.MatchingType_Keyword,
|
||||||
Domain: "google",
|
Domain: "google",
|
||||||
Ip: [][]byte{{8, 8, 8, 8}},
|
Ip: [][]byte{{8, 8, 8, 8}, {8, 8, 4, 4}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: dns.DomainMatchingType_Regex,
|
Type: domain.MatchingType_Regex,
|
||||||
Domain: ".*\\.com",
|
Domain: ".*\\.com",
|
||||||
Ip: [][]byte{{8, 8, 4, 4}},
|
Ip: [][]byte{{8, 8, 4, 4}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Type: domain.MatchingType_Full,
|
||||||
|
Domain: "xtls.github.io",
|
||||||
|
Ip: [][]byte{{1, 2, 3, 4}, {5, 6, 7, 8}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ClientIp: []byte{10, 0, 0, 1},
|
ClientIp: []byte{10, 0, 0, 1},
|
||||||
|
QueryStrategy: dns.QueryStrategy_USE_IP4,
|
||||||
|
CacheStrategy: dns.CacheStrategy_Cache_DISABLE,
|
||||||
|
DisableFallback: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
|
|||||||
config := new(freedom.Config)
|
config := new(freedom.Config)
|
||||||
config.DomainStrategy = freedom.Config_AS_IS
|
config.DomainStrategy = freedom.Config_AS_IS
|
||||||
switch strings.ToLower(c.DomainStrategy) {
|
switch strings.ToLower(c.DomainStrategy) {
|
||||||
case "useip", "use_ip":
|
case "useip", "use_ip", "use-ip":
|
||||||
config.DomainStrategy = freedom.Config_USE_IP
|
config.DomainStrategy = freedom.Config_USE_IP
|
||||||
case "useip4", "useipv4", "use_ipv4", "use_ip_v4", "use_ip4":
|
case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4":
|
||||||
config.DomainStrategy = freedom.Config_USE_IP4
|
config.DomainStrategy = freedom.Config_USE_IP4
|
||||||
case "useip6", "useipv6", "use_ipv6", "use_ip_v6", "use_ip6":
|
case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
|
||||||
config.DomainStrategy = freedom.Config_USE_IP6
|
config.DomainStrategy = freedom.Config_USE_IP6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/router"
|
"github.com/xtls/xray-core/app/router"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain/conf"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geosite"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RouterRulesConfig struct {
|
type RouterRulesConfig struct {
|
||||||
@@ -53,11 +52,11 @@ func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch strings.ToLower(ds) {
|
switch strings.ToLower(ds) {
|
||||||
case "alwaysip":
|
case "alwaysip", "always_ip", "always-ip":
|
||||||
return router.Config_UseIp
|
return router.Config_UseIp
|
||||||
case "ipifnonmatch":
|
case "ipifnonmatch", "ip_if_non_match", "ip-if-non-match":
|
||||||
return router.Config_IpIfNonMatch
|
return router.Config_IpIfNonMatch
|
||||||
case "ipondemand":
|
case "ipondemand", "ip_on_demand", "ip-on-demand":
|
||||||
return router.Config_IpOnDemand
|
return router.Config_IpOnDemand
|
||||||
default:
|
default:
|
||||||
return router.Config_AsIs
|
return router.Config_AsIs
|
||||||
@@ -95,12 +94,13 @@ func (c *RouterConfig) Build() (*router.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RouterRule struct {
|
type RouterRule struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
OutboundTag string `json:"outboundTag"`
|
OutboundTag string `json:"outboundTag"`
|
||||||
BalancerTag string `json:"balancerTag"`
|
BalancerTag string `json:"balancerTag"`
|
||||||
|
DomainMatcher string `json:"domainMatcher"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseIP(s string) (*router.CIDR, error) {
|
func ParseIP(s string) (*geoip.CIDR, error) {
|
||||||
var addr, mask string
|
var addr, mask string
|
||||||
i := strings.Index(s, "/")
|
i := strings.Index(s, "/")
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
@@ -123,7 +123,7 @@ func ParseIP(s string) (*router.CIDR, error) {
|
|||||||
if bits > 32 {
|
if bits > 32 {
|
||||||
return nil, newError("invalid network mask for router: ", bits)
|
return nil, newError("invalid network mask for router: ", bits)
|
||||||
}
|
}
|
||||||
return &router.CIDR{
|
return &geoip.CIDR{
|
||||||
Ip: []byte(ip.IP()),
|
Ip: []byte(ip.IP()),
|
||||||
Prefix: bits,
|
Prefix: bits,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -139,8 +139,8 @@ func ParseIP(s string) (*router.CIDR, error) {
|
|||||||
if bits > 128 {
|
if bits > 128 {
|
||||||
return nil, newError("invalid network mask for router: ", bits)
|
return nil, newError("invalid network mask for router: ", bits)
|
||||||
}
|
}
|
||||||
return &router.CIDR{
|
return &geoip.CIDR{
|
||||||
Ip: []byte(ip.IP()),
|
Ip: ip.IP(),
|
||||||
Prefix: bits,
|
Prefix: bits,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
@@ -148,314 +148,6 @@ func ParseIP(s string) (*router.CIDR, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGeoIP(code string) ([]*router.CIDR, error) {
|
|
||||||
return loadIP("geoip.dat", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
FileCache = make(map[string][]byte)
|
|
||||||
IPCache = make(map[string]*router.GeoIP)
|
|
||||||
SiteCache = make(map[string]*router.GeoSite)
|
|
||||||
)
|
|
||||||
|
|
||||||
func loadFile(file string) ([]byte, error) {
|
|
||||||
if FileCache[file] == nil {
|
|
||||||
bs, err := filesystem.ReadAsset(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to open file: ", file).Base(err)
|
|
||||||
}
|
|
||||||
if len(bs) == 0 {
|
|
||||||
return nil, newError("empty file: ", file)
|
|
||||||
}
|
|
||||||
// Do not cache file, may save RAM when there
|
|
||||||
// are many files, but consume CPU each time.
|
|
||||||
return bs, nil
|
|
||||||
FileCache[file] = bs
|
|
||||||
}
|
|
||||||
return FileCache[file], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadIP(file, code string) ([]*router.CIDR, error) {
|
|
||||||
index := file + ":" + code
|
|
||||||
if IPCache[index] == nil {
|
|
||||||
bs, err := loadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to load file: ", file).Base(err)
|
|
||||||
}
|
|
||||||
bs = find(bs, []byte(code))
|
|
||||||
if bs == nil {
|
|
||||||
return nil, newError("code not found in ", file, ": ", code)
|
|
||||||
}
|
|
||||||
var geoip router.GeoIP
|
|
||||||
if err := proto.Unmarshal(bs, &geoip); err != nil {
|
|
||||||
return nil, newError("error unmarshal IP in ", file, ": ", code).Base(err)
|
|
||||||
}
|
|
||||||
defer runtime.GC() // or debug.FreeOSMemory()
|
|
||||||
return geoip.Cidr, nil // do not cache geoip
|
|
||||||
IPCache[index] = &geoip
|
|
||||||
}
|
|
||||||
return IPCache[index].Cidr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadSite(file, code string) ([]*router.Domain, error) {
|
|
||||||
index := file + ":" + code
|
|
||||||
if SiteCache[index] == nil {
|
|
||||||
bs, err := loadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to load file: ", file).Base(err)
|
|
||||||
}
|
|
||||||
bs = find(bs, []byte(code))
|
|
||||||
if bs == nil {
|
|
||||||
return nil, newError("list not found in ", file, ": ", code)
|
|
||||||
}
|
|
||||||
var geosite router.GeoSite
|
|
||||||
if err := proto.Unmarshal(bs, &geosite); err != nil {
|
|
||||||
return nil, newError("error unmarshal Site in ", file, ": ", code).Base(err)
|
|
||||||
}
|
|
||||||
defer runtime.GC() // or debug.FreeOSMemory()
|
|
||||||
return geosite.Domain, nil // do not cache geosite
|
|
||||||
SiteCache[index] = &geosite
|
|
||||||
}
|
|
||||||
return SiteCache[index].Domain, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func find(data, code []byte) []byte {
|
|
||||||
codeL := len(code)
|
|
||||||
if codeL == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
dataL := len(data)
|
|
||||||
if dataL < 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
x, y := proto.DecodeVarint(data[1:])
|
|
||||||
if x == 0 && y == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
headL, bodyL := 1+y, int(x)
|
|
||||||
dataL -= headL
|
|
||||||
if dataL < bodyL {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data = data[headL:]
|
|
||||||
if int(data[1]) == codeL {
|
|
||||||
for i := 0; i < codeL && data[2+i] == code[i]; i++ {
|
|
||||||
if i+1 == codeL {
|
|
||||||
return data[:bodyL]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dataL == bodyL {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data = data[bodyL:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AttributeMatcher interface {
|
|
||||||
Match(*router.Domain) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type BooleanMatcher string
|
|
||||||
|
|
||||||
func (m BooleanMatcher) Match(domain *router.Domain) bool {
|
|
||||||
for _, attr := range domain.Attribute {
|
|
||||||
if attr.Key == string(m) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type AttributeList struct {
|
|
||||||
matcher []AttributeMatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (al *AttributeList) Match(domain *router.Domain) bool {
|
|
||||||
for _, matcher := range al.matcher {
|
|
||||||
if !matcher.Match(domain) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (al *AttributeList) IsEmpty() bool {
|
|
||||||
return len(al.matcher) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAttrs(attrs []string) *AttributeList {
|
|
||||||
al := new(AttributeList)
|
|
||||||
for _, attr := range attrs {
|
|
||||||
lc := strings.ToLower(attr)
|
|
||||||
al.matcher = append(al.matcher, BooleanMatcher(lc))
|
|
||||||
}
|
|
||||||
return al
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
|
|
||||||
parts := strings.Split(siteWithAttr, "@")
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return nil, newError("empty site")
|
|
||||||
}
|
|
||||||
country := strings.ToUpper(parts[0])
|
|
||||||
attrs := parseAttrs(parts[1:])
|
|
||||||
domains, err := loadSite(file, country)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if attrs.IsEmpty() {
|
|
||||||
return domains, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
|
||||||
for _, domain := range domains {
|
|
||||||
if attrs.Match(domain) {
|
|
||||||
filteredDomains = append(filteredDomains, domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredDomains, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDomainRule(domain string) ([]*router.Domain, error) {
|
|
||||||
if strings.HasPrefix(domain, "geosite:") {
|
|
||||||
country := strings.ToUpper(domain[8:])
|
|
||||||
domains, err := loadGeositeWithAttr("geosite.dat", country)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to load geosite: ", country).Base(err)
|
|
||||||
}
|
|
||||||
return domains, nil
|
|
||||||
}
|
|
||||||
var isExtDatFile = 0
|
|
||||||
{
|
|
||||||
const prefix = "ext:"
|
|
||||||
if strings.HasPrefix(domain, prefix) {
|
|
||||||
isExtDatFile = len(prefix)
|
|
||||||
}
|
|
||||||
const prefixQualified = "ext-domain:"
|
|
||||||
if strings.HasPrefix(domain, prefixQualified) {
|
|
||||||
isExtDatFile = len(prefixQualified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isExtDatFile != 0 {
|
|
||||||
kv := strings.Split(domain[isExtDatFile:], ":")
|
|
||||||
if len(kv) != 2 {
|
|
||||||
return nil, newError("invalid external resource: ", domain)
|
|
||||||
}
|
|
||||||
filename := kv[0]
|
|
||||||
country := kv[1]
|
|
||||||
domains, err := loadGeositeWithAttr(filename, country)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err)
|
|
||||||
}
|
|
||||||
return domains, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
domainRule := new(router.Domain)
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(domain, "regexp:"):
|
|
||||||
domainRule.Type = router.Domain_Regex
|
|
||||||
domainRule.Value = domain[7:]
|
|
||||||
|
|
||||||
case strings.HasPrefix(domain, "domain:"):
|
|
||||||
domainRule.Type = router.Domain_Domain
|
|
||||||
domainRule.Value = domain[7:]
|
|
||||||
|
|
||||||
case strings.HasPrefix(domain, "full:"):
|
|
||||||
domainRule.Type = router.Domain_Full
|
|
||||||
domainRule.Value = domain[5:]
|
|
||||||
|
|
||||||
case strings.HasPrefix(domain, "keyword:"):
|
|
||||||
domainRule.Type = router.Domain_Plain
|
|
||||||
domainRule.Value = domain[8:]
|
|
||||||
|
|
||||||
case strings.HasPrefix(domain, "dotless:"):
|
|
||||||
domainRule.Type = router.Domain_Regex
|
|
||||||
switch substr := domain[8:]; {
|
|
||||||
case substr == "":
|
|
||||||
domainRule.Value = "^[^.]*$"
|
|
||||||
case !strings.Contains(substr, "."):
|
|
||||||
domainRule.Value = "^[^.]*" + substr + "[^.]*$"
|
|
||||||
default:
|
|
||||||
return nil, newError("substr in dotless rule should not contain a dot: ", substr)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
domainRule.Type = router.Domain_Plain
|
|
||||||
domainRule.Value = domain
|
|
||||||
}
|
|
||||||
return []*router.Domain{domainRule}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toCidrList(ips StringList) ([]*router.GeoIP, error) {
|
|
||||||
var geoipList []*router.GeoIP
|
|
||||||
var customCidrs []*router.CIDR
|
|
||||||
|
|
||||||
for _, ip := range ips {
|
|
||||||
if strings.HasPrefix(ip, "geoip:") {
|
|
||||||
country := ip[6:]
|
|
||||||
geoip, err := loadGeoIP(strings.ToUpper(country))
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to load GeoIP: ", country).Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
geoipList = append(geoipList, &router.GeoIP{
|
|
||||||
CountryCode: strings.ToUpper(country),
|
|
||||||
Cidr: geoip,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var isExtDatFile = 0
|
|
||||||
{
|
|
||||||
const prefix = "ext:"
|
|
||||||
if strings.HasPrefix(ip, prefix) {
|
|
||||||
isExtDatFile = len(prefix)
|
|
||||||
}
|
|
||||||
const prefixQualified = "ext-ip:"
|
|
||||||
if strings.HasPrefix(ip, prefixQualified) {
|
|
||||||
isExtDatFile = len(prefixQualified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isExtDatFile != 0 {
|
|
||||||
kv := strings.Split(ip[isExtDatFile:], ":")
|
|
||||||
if len(kv) != 2 {
|
|
||||||
return nil, newError("invalid external resource: ", ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := kv[0]
|
|
||||||
country := kv[1]
|
|
||||||
geoip, err := loadIP(filename, strings.ToUpper(country))
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
geoipList = append(geoipList, &router.GeoIP{
|
|
||||||
CountryCode: strings.ToUpper(filename + "_" + country),
|
|
||||||
Cidr: geoip,
|
|
||||||
})
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ipRule, err := ParseIP(ip)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("invalid IP: ", ip).Base(err)
|
|
||||||
}
|
|
||||||
customCidrs = append(customCidrs, ipRule)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(customCidrs) > 0 {
|
|
||||||
geoipList = append(geoipList, &router.GeoIP{
|
|
||||||
Cidr: customCidrs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return geoipList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||||
type RawFieldRule struct {
|
type RawFieldRule struct {
|
||||||
RouterRule
|
RouterRule
|
||||||
@@ -491,9 +183,13 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
return nil, newError("neither outboundTag nor balancerTag is specified in routing rule")
|
return nil, newError("neither outboundTag nor balancerTag is specified in routing rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rawFieldRule.DomainMatcher != "" {
|
||||||
|
rule.DomainMatcher = rawFieldRule.DomainMatcher
|
||||||
|
}
|
||||||
|
|
||||||
if rawFieldRule.Domain != nil {
|
if rawFieldRule.Domain != nil {
|
||||||
for _, domain := range *rawFieldRule.Domain {
|
for _, domain := range *rawFieldRule.Domain {
|
||||||
rules, err := parseDomainRule(domain)
|
rules, err := conf.ParseDomainRule(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
||||||
}
|
}
|
||||||
@@ -503,7 +199,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
|
|
||||||
if rawFieldRule.Domains != nil {
|
if rawFieldRule.Domains != nil {
|
||||||
for _, domain := range *rawFieldRule.Domains {
|
for _, domain := range *rawFieldRule.Domains {
|
||||||
rules, err := parseDomainRule(domain)
|
rules, err := conf.ParseDomainRule(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
||||||
}
|
}
|
||||||
@@ -512,7 +208,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rawFieldRule.IP != nil {
|
if rawFieldRule.IP != nil {
|
||||||
geoipList, err := toCidrList(*rawFieldRule.IP)
|
geoipList, err := geoip.ParseIPList(*rawFieldRule.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -528,7 +224,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rawFieldRule.SourceIP != nil {
|
if rawFieldRule.SourceIP != nil {
|
||||||
geoipList, err := toCidrList(*rawFieldRule.SourceIP)
|
geoipList, err := geoip.ParseIPList(*rawFieldRule.SourceIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -570,21 +266,21 @@ func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid router rule").Base(err)
|
return nil, newError("invalid router rule").Base(err)
|
||||||
}
|
}
|
||||||
if rawRule.Type == "field" {
|
if strings.EqualFold(rawRule.Type, "field") {
|
||||||
fieldrule, err := parseFieldRule(msg)
|
fieldrule, err := parseFieldRule(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid field rule").Base(err)
|
return nil, newError("invalid field rule").Base(err)
|
||||||
}
|
}
|
||||||
return fieldrule, nil
|
return fieldrule, nil
|
||||||
}
|
}
|
||||||
if rawRule.Type == "chinaip" {
|
if strings.EqualFold(rawRule.Type, "chinaip") {
|
||||||
chinaiprule, err := parseChinaIPRule(msg)
|
chinaiprule, err := parseChinaIPRule(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid chinaip rule").Base(err)
|
return nil, newError("invalid chinaip rule").Base(err)
|
||||||
}
|
}
|
||||||
return chinaiprule, nil
|
return chinaiprule, nil
|
||||||
}
|
}
|
||||||
if rawRule.Type == "chinasites" {
|
if strings.EqualFold(rawRule.Type, "chinasites") {
|
||||||
chinasitesrule, err := parseChinaSitesRule(msg)
|
chinasitesrule, err := parseChinaSitesRule(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid chinasites rule").Base(err)
|
return nil, newError("invalid chinasites rule").Base(err)
|
||||||
@@ -600,7 +296,7 @@ func parseChinaIPRule(data []byte) (*router.RoutingRule, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid router rule").Base(err)
|
return nil, newError("invalid router rule").Base(err)
|
||||||
}
|
}
|
||||||
chinaIPs, err := loadGeoIP("CN")
|
chinaIPs, err := geoip.LoadGeoIP("CN")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to load geoip:cn").Base(err)
|
return nil, newError("failed to load geoip:cn").Base(err)
|
||||||
}
|
}
|
||||||
@@ -618,7 +314,7 @@ func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid router rule").Base(err).AtError()
|
return nil, newError("invalid router rule").Base(err).AtError()
|
||||||
}
|
}
|
||||||
domains, err := loadGeositeWithAttr("geosite.dat", "CN")
|
domains, err := geosite.LoadGeositeWithAttr("geosite.dat", "CN")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to load geosite:cn.").Base(err)
|
return nil, newError("failed to load geosite:cn.").Base(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/router"
|
"github.com/xtls/xray-core/app/router"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
)
|
)
|
||||||
@@ -73,13 +75,13 @@ func TestRouterConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Domain: []*router.Domain{
|
Domain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: router.Domain_Plain,
|
Type: domain.MatchingType_Keyword,
|
||||||
Value: "baidu.com",
|
Value: "baidu.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: router.Domain_Plain,
|
Type: domain.MatchingType_Keyword,
|
||||||
Value: "qq.com",
|
Value: "qq.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -88,9 +90,9 @@ func TestRouterConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{
|
{
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{10, 0, 0, 0},
|
Ip: []byte{10, 0, 0, 0},
|
||||||
Prefix: 8,
|
Prefix: 8,
|
||||||
@@ -161,13 +163,13 @@ func TestRouterConfig(t *testing.T) {
|
|||||||
DomainStrategy: router.Config_IpIfNonMatch,
|
DomainStrategy: router.Config_IpIfNonMatch,
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Domain: []*router.Domain{
|
Domain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: router.Domain_Plain,
|
Type: domain.MatchingType_Keyword,
|
||||||
Value: "baidu.com",
|
Value: "baidu.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: router.Domain_Plain,
|
Type: domain.MatchingType_Keyword,
|
||||||
Value: "qq.com",
|
Value: "qq.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -176,9 +178,9 @@ func TestRouterConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{
|
{
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{10, 0, 0, 0},
|
Ip: []byte{10, 0, 0, 0},
|
||||||
Prefix: 8,
|
Prefix: 8,
|
||||||
@@ -224,13 +226,13 @@ func TestRouterConfig(t *testing.T) {
|
|||||||
DomainStrategy: router.Config_AsIs,
|
DomainStrategy: router.Config_AsIs,
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Domain: []*router.Domain{
|
Domain: []*domain.Domain{
|
||||||
{
|
{
|
||||||
Type: router.Domain_Plain,
|
Type: domain.MatchingType_Keyword,
|
||||||
Value: "baidu.com",
|
Value: "baidu.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: router.Domain_Plain,
|
Type: domain.MatchingType_Keyword,
|
||||||
Value: "qq.com",
|
Value: "qq.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -239,9 +241,9 @@ func TestRouterConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{
|
{
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{10, 0, 0, 0},
|
Ip: []byte{10, 0, 0, 0},
|
||||||
Prefix: 8,
|
Prefix: 8,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -10,8 +10,12 @@ import (
|
|||||||
"github.com/xtls/xray-core/app/dispatcher"
|
"github.com/xtls/xray-core/app/dispatcher"
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
"github.com/xtls/xray-core/app/stats"
|
"github.com/xtls/xray-core/app/stats"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain/conf"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/xtls"
|
"github.com/xtls/xray-core/transport/internet/xtls"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,6 +66,7 @@ type SniffingConfig struct {
|
|||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
DestOverride *StringList `json:"destOverride"`
|
DestOverride *StringList `json:"destOverride"`
|
||||||
DomainsExcluded *StringList `json:"domainsExcluded"`
|
DomainsExcluded *StringList `json:"domainsExcluded"`
|
||||||
|
IPsExcluded *StringList `json:"ipsExcluded"`
|
||||||
MetadataOnly bool `json:"metadataOnly"`
|
MetadataOnly bool `json:"metadataOnly"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,17 +88,31 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var d []string
|
var exDomain []*domain.Domain
|
||||||
if c.DomainsExcluded != nil {
|
if c.DomainsExcluded != nil {
|
||||||
for _, domain := range *c.DomainsExcluded {
|
for _, dmr := range *c.DomainsExcluded {
|
||||||
d = append(d, strings.ToLower(domain))
|
if dm, err := conf.ParseDomainRule(dmr); err == nil {
|
||||||
|
exDomain = append(exDomain, dm...)
|
||||||
|
} else {
|
||||||
|
return nil, newError("failed to parse excluded domain").Base(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var exIP []*geoip.GeoIP
|
||||||
|
if c.IPsExcluded != nil {
|
||||||
|
exip, err := geoip.ParseIPList(*c.IPsExcluded)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to parse excluded ip").Base(err)
|
||||||
|
}
|
||||||
|
exIP = exip
|
||||||
|
}
|
||||||
|
|
||||||
return &proxyman.SniffingConfig{
|
return &proxyman.SniffingConfig{
|
||||||
Enabled: c.Enabled,
|
Enabled: c.Enabled,
|
||||||
DestinationOverride: p,
|
DestinationOverride: p,
|
||||||
DomainsExcluded: d,
|
DomainsExcluded: exDomain,
|
||||||
|
IpsExcluded: exIP,
|
||||||
MetadataOnly: c.MetadataOnly,
|
MetadataOnly: c.MetadataOnly,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package conf_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/app/router"
|
"github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
clog "github.com/xtls/xray-core/common/log"
|
clog "github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
@@ -154,9 +156,9 @@ func TestXrayConfig(t *testing.T) {
|
|||||||
DomainStrategy: router.Config_AsIs,
|
DomainStrategy: router.Config_AsIs,
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Geoip: []*router.GeoIP{
|
Geoip: []*geoip.GeoIP{
|
||||||
{
|
{
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{10, 0, 0, 0},
|
Ip: []byte{10, 0, 0, 0},
|
||||||
Prefix: 8,
|
Prefix: 8,
|
||||||
@@ -371,6 +373,52 @@ func TestMuxConfig_Build(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSniffingConfig_Build(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields string
|
||||||
|
want *proxyman.SniffingConfig
|
||||||
|
}{
|
||||||
|
{"default", `
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"destOverride": ["tls"],
|
||||||
|
"domainsExcluded": ["domain:google.com"],
|
||||||
|
"ipsExcluded": ["8.8.8.8"]
|
||||||
|
}`, &proxyman.SniffingConfig{
|
||||||
|
Enabled: true,
|
||||||
|
DestinationOverride: []string{"tls"},
|
||||||
|
DomainsExcluded: []*domain.Domain{
|
||||||
|
{
|
||||||
|
Type: domain.MatchingType_Subdomain,
|
||||||
|
Value: "google.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IpsExcluded: []*geoip.GeoIP{
|
||||||
|
{
|
||||||
|
Cidr: []*geoip.CIDR{{Ip: []byte{8, 8, 8, 8}, Prefix: 32}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
{"empty def", `{}`, &proxyman.SniffingConfig{
|
||||||
|
Enabled: false,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m := &SniffingConfig{}
|
||||||
|
common.Must(json.Unmarshal([]byte(tt.fields), m))
|
||||||
|
got, err := m.Build()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("SniffingConfig.Build() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfig_Override(t *testing.T) {
|
func TestConfig_Override(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ type Handler struct {
|
|||||||
|
|
||||||
func (h *Handler) Init(config *Config, dnsClient dns.Client) error {
|
func (h *Handler) Init(config *Config, dnsClient dns.Client) error {
|
||||||
h.client = dnsClient
|
h.client = dnsClient
|
||||||
|
|
||||||
if v, ok := dnsClient.(ownLinkVerifier); ok {
|
if v, ok := dnsClient.(ownLinkVerifier); ok {
|
||||||
h.ownLinkVerifier = v
|
h.ownLinkVerifier = v
|
||||||
}
|
}
|
||||||
@@ -198,22 +199,16 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
var ttl uint32 = 600
|
var ttl uint32 = 600
|
||||||
|
var opt dns.Option
|
||||||
|
|
||||||
switch qType {
|
switch qType {
|
||||||
case dnsmessage.TypeA:
|
case dnsmessage.TypeA:
|
||||||
ips, err = h.client.LookupIP(domain, dns.IPOption{
|
opt = dns.LookupIPv4Only
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: false,
|
|
||||||
FakeEnable: true,
|
|
||||||
})
|
|
||||||
case dnsmessage.TypeAAAA:
|
case dnsmessage.TypeAAAA:
|
||||||
ips, err = h.client.LookupIP(domain, dns.IPOption{
|
opt = dns.LookupIPv6Only
|
||||||
IPv4Enable: false,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ips, err = h.client.LookupOptions(domain, opt, dns.LookupFake)
|
||||||
rcode := dns.RCodeFromError(err)
|
rcode := dns.RCodeFromError(err)
|
||||||
if rcode == 0 && len(ips) == 0 && err != dns.ErrEmptyResponse {
|
if rcode == 0 && len(ips) == 0 && err != dns.ErrEmptyResponse {
|
||||||
newError("ip query").Base(err).WriteToLog()
|
newError("ip query").Base(err).WriteToLog()
|
||||||
@@ -228,7 +223,6 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
|
|||||||
RecursionAvailable: true,
|
RecursionAvailable: true,
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
Response: true,
|
Response: true,
|
||||||
Authoritative: true,
|
|
||||||
})
|
})
|
||||||
builder.EnableCompression()
|
builder.EnableCompression()
|
||||||
common.Must(builder.StartQuestions())
|
common.Must(builder.StartQuestions())
|
||||||
|
|||||||
@@ -59,26 +59,14 @@ func (h *Handler) policy() policy.Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
|
func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
|
||||||
var option dns.IPOption = dns.IPOption{
|
var opt dns.Option
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
}
|
|
||||||
if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) {
|
if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) {
|
||||||
option = dns.IPOption{
|
opt = dns.LookupIPv4Only
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: false,
|
|
||||||
FakeEnable: false,
|
|
||||||
}
|
|
||||||
} else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) {
|
} else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) {
|
||||||
option = dns.IPOption{
|
opt = dns.LookupIPv6Only
|
||||||
IPv4Enable: false,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := h.dns.LookupIP(domain, option)
|
ips, err := h.dns.LookupOptions(domain, opt, dns.LookupNoFake)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx))
|
newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,36 +5,37 @@
|
|||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
dns "github.com/xtls/xray-core/features/dns"
|
|
||||||
net "net"
|
net "net"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
dns "github.com/xtls/xray-core/features/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSClient is a mock of Client interface
|
// DNSClient is a mock of Client interface.
|
||||||
type DNSClient struct {
|
type DNSClient struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *DNSClientMockRecorder
|
recorder *DNSClientMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSClientMockRecorder is the mock recorder for DNSClient
|
// DNSClientMockRecorder is the mock recorder for DNSClient.
|
||||||
type DNSClientMockRecorder struct {
|
type DNSClientMockRecorder struct {
|
||||||
mock *DNSClient
|
mock *DNSClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDNSClient creates a new mock instance
|
// NewDNSClient creates a new mock instance.
|
||||||
func NewDNSClient(ctrl *gomock.Controller) *DNSClient {
|
func NewDNSClient(ctrl *gomock.Controller) *DNSClient {
|
||||||
mock := &DNSClient{ctrl: ctrl}
|
mock := &DNSClient{ctrl: ctrl}
|
||||||
mock.recorder = &DNSClientMockRecorder{mock}
|
mock.recorder = &DNSClientMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *DNSClient) EXPECT() *DNSClientMockRecorder {
|
func (m *DNSClient) EXPECT() *DNSClientMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close mocks base method
|
// Close mocks base method.
|
||||||
func (m *DNSClient) Close() error {
|
func (m *DNSClient) Close() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Close")
|
ret := m.ctrl.Call(m, "Close")
|
||||||
@@ -42,28 +43,48 @@ func (m *DNSClient) Close() error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close indicates an expected call of Close
|
// Close indicates an expected call of Close.
|
||||||
func (mr *DNSClientMockRecorder) Close() *gomock.Call {
|
func (mr *DNSClientMockRecorder) Close() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*DNSClient)(nil).Close))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*DNSClient)(nil).Close))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIP mocks base method
|
// LookupIP mocks base method.
|
||||||
func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, error) {
|
func (m *DNSClient) LookupIP(arg0 string) ([]net.IP, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "LookupIP", arg0, arg1)
|
ret := m.ctrl.Call(m, "LookupIP", arg0)
|
||||||
ret0, _ := ret[0].([]net.IP)
|
ret0, _ := ret[0].([]net.IP)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIP indicates an expected call of LookupIP
|
// LookupIP indicates an expected call of LookupIP.
|
||||||
func (mr *DNSClientMockRecorder) LookupIP(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *DNSClientMockRecorder) LookupIP(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupIP", reflect.TypeOf((*DNSClient)(nil).LookupIP), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupIP", reflect.TypeOf((*DNSClient)(nil).LookupIP), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start mocks base method
|
// LookupOptions mocks base method.
|
||||||
|
func (m *DNSClient) LookupOptions(arg0 string, arg1 ...dns.Option) ([]net.IP, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
varargs := []interface{}{arg0}
|
||||||
|
for _, a := range arg1 {
|
||||||
|
varargs = append(varargs, a)
|
||||||
|
}
|
||||||
|
ret := m.ctrl.Call(m, "LookupOptions", varargs...)
|
||||||
|
ret0, _ := ret[0].([]net.IP)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupOptions indicates an expected call of LookupOptions.
|
||||||
|
func (mr *DNSClientMockRecorder) LookupOptions(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
varargs := append([]interface{}{arg0}, arg1...)
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupOptions", reflect.TypeOf((*DNSClient)(nil).LookupOptions), varargs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start mocks base method.
|
||||||
func (m *DNSClient) Start() error {
|
func (m *DNSClient) Start() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Start")
|
ret := m.ctrl.Call(m, "Start")
|
||||||
@@ -71,13 +92,13 @@ func (m *DNSClient) Start() error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start indicates an expected call of Start
|
// Start indicates an expected call of Start.
|
||||||
func (mr *DNSClientMockRecorder) Start() *gomock.Call {
|
func (mr *DNSClientMockRecorder) Start() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*DNSClient)(nil).Start))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*DNSClient)(nil).Start))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type mocks base method
|
// Type mocks base method.
|
||||||
func (m *DNSClient) Type() interface{} {
|
func (m *DNSClient) Type() interface{} {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Type")
|
ret := m.ctrl.Call(m, "Type")
|
||||||
@@ -85,7 +106,7 @@ func (m *DNSClient) Type() interface{} {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type indicates an expected call of Type
|
// Type indicates an expected call of Type.
|
||||||
func (mr *DNSClientMockRecorder) Type() *gomock.Call {
|
func (mr *DNSClientMockRecorder) Type() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*DNSClient)(nil).Type))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*DNSClient)(nil).Type))
|
||||||
|
|||||||
@@ -5,34 +5,35 @@
|
|||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reader is a mock of Reader interface
|
// Reader is a mock of Reader interface.
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *ReaderMockRecorder
|
recorder *ReaderMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReaderMockRecorder is the mock recorder for Reader
|
// ReaderMockRecorder is the mock recorder for Reader.
|
||||||
type ReaderMockRecorder struct {
|
type ReaderMockRecorder struct {
|
||||||
mock *Reader
|
mock *Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader creates a new mock instance
|
// NewReader creates a new mock instance.
|
||||||
func NewReader(ctrl *gomock.Controller) *Reader {
|
func NewReader(ctrl *gomock.Controller) *Reader {
|
||||||
mock := &Reader{ctrl: ctrl}
|
mock := &Reader{ctrl: ctrl}
|
||||||
mock.recorder = &ReaderMockRecorder{mock}
|
mock.recorder = &ReaderMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *Reader) EXPECT() *ReaderMockRecorder {
|
func (m *Reader) EXPECT() *ReaderMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read mocks base method
|
// Read mocks base method.
|
||||||
func (m *Reader) Read(arg0 []byte) (int, error) {
|
func (m *Reader) Read(arg0 []byte) (int, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Read", arg0)
|
ret := m.ctrl.Call(m, "Read", arg0)
|
||||||
@@ -41,36 +42,36 @@ func (m *Reader) Read(arg0 []byte) (int, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read indicates an expected call of Read
|
// Read indicates an expected call of Read.
|
||||||
func (mr *ReaderMockRecorder) Read(arg0 interface{}) *gomock.Call {
|
func (mr *ReaderMockRecorder) Read(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*Reader)(nil).Read), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*Reader)(nil).Read), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writer is a mock of Writer interface
|
// Writer is a mock of Writer interface.
|
||||||
type Writer struct {
|
type Writer struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *WriterMockRecorder
|
recorder *WriterMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriterMockRecorder is the mock recorder for Writer
|
// WriterMockRecorder is the mock recorder for Writer.
|
||||||
type WriterMockRecorder struct {
|
type WriterMockRecorder struct {
|
||||||
mock *Writer
|
mock *Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWriter creates a new mock instance
|
// NewWriter creates a new mock instance.
|
||||||
func NewWriter(ctrl *gomock.Controller) *Writer {
|
func NewWriter(ctrl *gomock.Controller) *Writer {
|
||||||
mock := &Writer{ctrl: ctrl}
|
mock := &Writer{ctrl: ctrl}
|
||||||
mock.recorder = &WriterMockRecorder{mock}
|
mock.recorder = &WriterMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *Writer) EXPECT() *WriterMockRecorder {
|
func (m *Writer) EXPECT() *WriterMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write mocks base method
|
// Write mocks base method.
|
||||||
func (m *Writer) Write(arg0 []byte) (int, error) {
|
func (m *Writer) Write(arg0 []byte) (int, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Write", arg0)
|
ret := m.ctrl.Call(m, "Write", arg0)
|
||||||
@@ -79,7 +80,7 @@ func (m *Writer) Write(arg0 []byte) (int, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write indicates an expected call of Write
|
// Write indicates an expected call of Write.
|
||||||
func (mr *WriterMockRecorder) Write(arg0 interface{}) *gomock.Call {
|
func (mr *WriterMockRecorder) Write(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*Writer)(nil).Write), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*Writer)(nil).Write), arg0)
|
||||||
|
|||||||
@@ -5,41 +5,42 @@
|
|||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
log "github.com/xtls/xray-core/common/log"
|
log "github.com/xtls/xray-core/common/log"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogHandler is a mock of Handler interface
|
// LogHandler is a mock of Handler interface.
|
||||||
type LogHandler struct {
|
type LogHandler struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *LogHandlerMockRecorder
|
recorder *LogHandlerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogHandlerMockRecorder is the mock recorder for LogHandler
|
// LogHandlerMockRecorder is the mock recorder for LogHandler.
|
||||||
type LogHandlerMockRecorder struct {
|
type LogHandlerMockRecorder struct {
|
||||||
mock *LogHandler
|
mock *LogHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLogHandler creates a new mock instance
|
// NewLogHandler creates a new mock instance.
|
||||||
func NewLogHandler(ctrl *gomock.Controller) *LogHandler {
|
func NewLogHandler(ctrl *gomock.Controller) *LogHandler {
|
||||||
mock := &LogHandler{ctrl: ctrl}
|
mock := &LogHandler{ctrl: ctrl}
|
||||||
mock.recorder = &LogHandlerMockRecorder{mock}
|
mock.recorder = &LogHandlerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *LogHandler) EXPECT() *LogHandlerMockRecorder {
|
func (m *LogHandler) EXPECT() *LogHandlerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle mocks base method
|
// Handle mocks base method.
|
||||||
func (m *LogHandler) Handle(arg0 log.Message) {
|
func (m *LogHandler) Handle(arg0 log.Message) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Handle", arg0)
|
m.ctrl.Call(m, "Handle", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle indicates an expected call of Handle
|
// Handle indicates an expected call of Handle.
|
||||||
func (mr *LogHandlerMockRecorder) Handle(arg0 interface{}) *gomock.Call {
|
func (mr *LogHandlerMockRecorder) Handle(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handle", reflect.TypeOf((*LogHandler)(nil).Handle), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handle", reflect.TypeOf((*LogHandler)(nil).Handle), arg0)
|
||||||
|
|||||||
@@ -5,35 +5,36 @@
|
|||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
mux "github.com/xtls/xray-core/common/mux"
|
mux "github.com/xtls/xray-core/common/mux"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MuxClientWorkerFactory is a mock of ClientWorkerFactory interface
|
// MuxClientWorkerFactory is a mock of ClientWorkerFactory interface.
|
||||||
type MuxClientWorkerFactory struct {
|
type MuxClientWorkerFactory struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MuxClientWorkerFactoryMockRecorder
|
recorder *MuxClientWorkerFactoryMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MuxClientWorkerFactoryMockRecorder is the mock recorder for MuxClientWorkerFactory
|
// MuxClientWorkerFactoryMockRecorder is the mock recorder for MuxClientWorkerFactory.
|
||||||
type MuxClientWorkerFactoryMockRecorder struct {
|
type MuxClientWorkerFactoryMockRecorder struct {
|
||||||
mock *MuxClientWorkerFactory
|
mock *MuxClientWorkerFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMuxClientWorkerFactory creates a new mock instance
|
// NewMuxClientWorkerFactory creates a new mock instance.
|
||||||
func NewMuxClientWorkerFactory(ctrl *gomock.Controller) *MuxClientWorkerFactory {
|
func NewMuxClientWorkerFactory(ctrl *gomock.Controller) *MuxClientWorkerFactory {
|
||||||
mock := &MuxClientWorkerFactory{ctrl: ctrl}
|
mock := &MuxClientWorkerFactory{ctrl: ctrl}
|
||||||
mock.recorder = &MuxClientWorkerFactoryMockRecorder{mock}
|
mock.recorder = &MuxClientWorkerFactoryMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MuxClientWorkerFactory) EXPECT() *MuxClientWorkerFactoryMockRecorder {
|
func (m *MuxClientWorkerFactory) EXPECT() *MuxClientWorkerFactoryMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mocks base method
|
// Create mocks base method.
|
||||||
func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) {
|
func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Create")
|
ret := m.ctrl.Call(m, "Create")
|
||||||
@@ -42,7 +43,7 @@ func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create indicates an expected call of Create
|
// Create indicates an expected call of Create.
|
||||||
func (mr *MuxClientWorkerFactoryMockRecorder) Create() *gomock.Call {
|
func (mr *MuxClientWorkerFactoryMockRecorder) Create() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MuxClientWorkerFactory)(nil).Create))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MuxClientWorkerFactory)(nil).Create))
|
||||||
|
|||||||
@@ -6,35 +6,36 @@ package mocks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
outbound "github.com/xtls/xray-core/features/outbound"
|
outbound "github.com/xtls/xray-core/features/outbound"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OutboundManager is a mock of Manager interface
|
// OutboundManager is a mock of Manager interface.
|
||||||
type OutboundManager struct {
|
type OutboundManager struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *OutboundManagerMockRecorder
|
recorder *OutboundManagerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutboundManagerMockRecorder is the mock recorder for OutboundManager
|
// OutboundManagerMockRecorder is the mock recorder for OutboundManager.
|
||||||
type OutboundManagerMockRecorder struct {
|
type OutboundManagerMockRecorder struct {
|
||||||
mock *OutboundManager
|
mock *OutboundManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutboundManager creates a new mock instance
|
// NewOutboundManager creates a new mock instance.
|
||||||
func NewOutboundManager(ctrl *gomock.Controller) *OutboundManager {
|
func NewOutboundManager(ctrl *gomock.Controller) *OutboundManager {
|
||||||
mock := &OutboundManager{ctrl: ctrl}
|
mock := &OutboundManager{ctrl: ctrl}
|
||||||
mock.recorder = &OutboundManagerMockRecorder{mock}
|
mock.recorder = &OutboundManagerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *OutboundManager) EXPECT() *OutboundManagerMockRecorder {
|
func (m *OutboundManager) EXPECT() *OutboundManagerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHandler mocks base method
|
// AddHandler mocks base method.
|
||||||
func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler) error {
|
func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AddHandler", arg0, arg1)
|
ret := m.ctrl.Call(m, "AddHandler", arg0, arg1)
|
||||||
@@ -42,13 +43,13 @@ func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHandler indicates an expected call of AddHandler
|
// AddHandler indicates an expected call of AddHandler.
|
||||||
func (mr *OutboundManagerMockRecorder) AddHandler(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *OutboundManagerMockRecorder) AddHandler(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHandler", reflect.TypeOf((*OutboundManager)(nil).AddHandler), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHandler", reflect.TypeOf((*OutboundManager)(nil).AddHandler), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close mocks base method
|
// Close mocks base method.
|
||||||
func (m *OutboundManager) Close() error {
|
func (m *OutboundManager) Close() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Close")
|
ret := m.ctrl.Call(m, "Close")
|
||||||
@@ -56,13 +57,13 @@ func (m *OutboundManager) Close() error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close indicates an expected call of Close
|
// Close indicates an expected call of Close.
|
||||||
func (mr *OutboundManagerMockRecorder) Close() *gomock.Call {
|
func (mr *OutboundManagerMockRecorder) Close() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*OutboundManager)(nil).Close))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*OutboundManager)(nil).Close))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultHandler mocks base method
|
// GetDefaultHandler mocks base method.
|
||||||
func (m *OutboundManager) GetDefaultHandler() outbound.Handler {
|
func (m *OutboundManager) GetDefaultHandler() outbound.Handler {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetDefaultHandler")
|
ret := m.ctrl.Call(m, "GetDefaultHandler")
|
||||||
@@ -70,13 +71,13 @@ func (m *OutboundManager) GetDefaultHandler() outbound.Handler {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultHandler indicates an expected call of GetDefaultHandler
|
// GetDefaultHandler indicates an expected call of GetDefaultHandler.
|
||||||
func (mr *OutboundManagerMockRecorder) GetDefaultHandler() *gomock.Call {
|
func (mr *OutboundManagerMockRecorder) GetDefaultHandler() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultHandler", reflect.TypeOf((*OutboundManager)(nil).GetDefaultHandler))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultHandler", reflect.TypeOf((*OutboundManager)(nil).GetDefaultHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHandler mocks base method
|
// GetHandler mocks base method.
|
||||||
func (m *OutboundManager) GetHandler(arg0 string) outbound.Handler {
|
func (m *OutboundManager) GetHandler(arg0 string) outbound.Handler {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetHandler", arg0)
|
ret := m.ctrl.Call(m, "GetHandler", arg0)
|
||||||
@@ -84,13 +85,13 @@ func (m *OutboundManager) GetHandler(arg0 string) outbound.Handler {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHandler indicates an expected call of GetHandler
|
// GetHandler indicates an expected call of GetHandler.
|
||||||
func (mr *OutboundManagerMockRecorder) GetHandler(arg0 interface{}) *gomock.Call {
|
func (mr *OutboundManagerMockRecorder) GetHandler(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHandler", reflect.TypeOf((*OutboundManager)(nil).GetHandler), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHandler", reflect.TypeOf((*OutboundManager)(nil).GetHandler), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveHandler mocks base method
|
// RemoveHandler mocks base method.
|
||||||
func (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error {
|
func (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "RemoveHandler", arg0, arg1)
|
ret := m.ctrl.Call(m, "RemoveHandler", arg0, arg1)
|
||||||
@@ -98,13 +99,13 @@ func (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveHandler indicates an expected call of RemoveHandler
|
// RemoveHandler indicates an expected call of RemoveHandler.
|
||||||
func (mr *OutboundManagerMockRecorder) RemoveHandler(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *OutboundManagerMockRecorder) RemoveHandler(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHandler", reflect.TypeOf((*OutboundManager)(nil).RemoveHandler), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHandler", reflect.TypeOf((*OutboundManager)(nil).RemoveHandler), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start mocks base method
|
// Start mocks base method.
|
||||||
func (m *OutboundManager) Start() error {
|
func (m *OutboundManager) Start() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Start")
|
ret := m.ctrl.Call(m, "Start")
|
||||||
@@ -112,13 +113,13 @@ func (m *OutboundManager) Start() error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start indicates an expected call of Start
|
// Start indicates an expected call of Start.
|
||||||
func (mr *OutboundManagerMockRecorder) Start() *gomock.Call {
|
func (mr *OutboundManagerMockRecorder) Start() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*OutboundManager)(nil).Start))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*OutboundManager)(nil).Start))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type mocks base method
|
// Type mocks base method.
|
||||||
func (m *OutboundManager) Type() interface{} {
|
func (m *OutboundManager) Type() interface{} {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Type")
|
ret := m.ctrl.Call(m, "Type")
|
||||||
@@ -126,36 +127,36 @@ func (m *OutboundManager) Type() interface{} {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type indicates an expected call of Type
|
// Type indicates an expected call of Type.
|
||||||
func (mr *OutboundManagerMockRecorder) Type() *gomock.Call {
|
func (mr *OutboundManagerMockRecorder) Type() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*OutboundManager)(nil).Type))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*OutboundManager)(nil).Type))
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutboundHandlerSelector is a mock of HandlerSelector interface
|
// OutboundHandlerSelector is a mock of HandlerSelector interface.
|
||||||
type OutboundHandlerSelector struct {
|
type OutboundHandlerSelector struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *OutboundHandlerSelectorMockRecorder
|
recorder *OutboundHandlerSelectorMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutboundHandlerSelectorMockRecorder is the mock recorder for OutboundHandlerSelector
|
// OutboundHandlerSelectorMockRecorder is the mock recorder for OutboundHandlerSelector.
|
||||||
type OutboundHandlerSelectorMockRecorder struct {
|
type OutboundHandlerSelectorMockRecorder struct {
|
||||||
mock *OutboundHandlerSelector
|
mock *OutboundHandlerSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutboundHandlerSelector creates a new mock instance
|
// NewOutboundHandlerSelector creates a new mock instance.
|
||||||
func NewOutboundHandlerSelector(ctrl *gomock.Controller) *OutboundHandlerSelector {
|
func NewOutboundHandlerSelector(ctrl *gomock.Controller) *OutboundHandlerSelector {
|
||||||
mock := &OutboundHandlerSelector{ctrl: ctrl}
|
mock := &OutboundHandlerSelector{ctrl: ctrl}
|
||||||
mock.recorder = &OutboundHandlerSelectorMockRecorder{mock}
|
mock.recorder = &OutboundHandlerSelectorMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *OutboundHandlerSelector) EXPECT() *OutboundHandlerSelectorMockRecorder {
|
func (m *OutboundHandlerSelector) EXPECT() *OutboundHandlerSelectorMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select mocks base method
|
// Select mocks base method.
|
||||||
func (m *OutboundHandlerSelector) Select(arg0 []string) []string {
|
func (m *OutboundHandlerSelector) Select(arg0 []string) []string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Select", arg0)
|
ret := m.ctrl.Call(m, "Select", arg0)
|
||||||
@@ -163,7 +164,7 @@ func (m *OutboundHandlerSelector) Select(arg0 []string) []string {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select indicates an expected call of Select
|
// Select indicates an expected call of Select.
|
||||||
func (mr *OutboundHandlerSelectorMockRecorder) Select(arg0 interface{}) *gomock.Call {
|
func (mr *OutboundHandlerSelectorMockRecorder) Select(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*OutboundHandlerSelector)(nil).Select), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*OutboundHandlerSelector)(nil).Select), arg0)
|
||||||
|
|||||||
@@ -6,38 +6,39 @@ package mocks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
net "github.com/xtls/xray-core/common/net"
|
net "github.com/xtls/xray-core/common/net"
|
||||||
routing "github.com/xtls/xray-core/features/routing"
|
routing "github.com/xtls/xray-core/features/routing"
|
||||||
transport "github.com/xtls/xray-core/transport"
|
transport "github.com/xtls/xray-core/transport"
|
||||||
internet "github.com/xtls/xray-core/transport/internet"
|
internet "github.com/xtls/xray-core/transport/internet"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProxyInbound is a mock of Inbound interface
|
// ProxyInbound is a mock of Inbound interface.
|
||||||
type ProxyInbound struct {
|
type ProxyInbound struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *ProxyInboundMockRecorder
|
recorder *ProxyInboundMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyInboundMockRecorder is the mock recorder for ProxyInbound
|
// ProxyInboundMockRecorder is the mock recorder for ProxyInbound.
|
||||||
type ProxyInboundMockRecorder struct {
|
type ProxyInboundMockRecorder struct {
|
||||||
mock *ProxyInbound
|
mock *ProxyInbound
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProxyInbound creates a new mock instance
|
// NewProxyInbound creates a new mock instance.
|
||||||
func NewProxyInbound(ctrl *gomock.Controller) *ProxyInbound {
|
func NewProxyInbound(ctrl *gomock.Controller) *ProxyInbound {
|
||||||
mock := &ProxyInbound{ctrl: ctrl}
|
mock := &ProxyInbound{ctrl: ctrl}
|
||||||
mock.recorder = &ProxyInboundMockRecorder{mock}
|
mock.recorder = &ProxyInboundMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *ProxyInbound) EXPECT() *ProxyInboundMockRecorder {
|
func (m *ProxyInbound) EXPECT() *ProxyInboundMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network mocks base method
|
// Network mocks base method.
|
||||||
func (m *ProxyInbound) Network() []net.Network {
|
func (m *ProxyInbound) Network() []net.Network {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Network")
|
ret := m.ctrl.Call(m, "Network")
|
||||||
@@ -45,13 +46,13 @@ func (m *ProxyInbound) Network() []net.Network {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network indicates an expected call of Network
|
// Network indicates an expected call of Network.
|
||||||
func (mr *ProxyInboundMockRecorder) Network() *gomock.Call {
|
func (mr *ProxyInboundMockRecorder) Network() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*ProxyInbound)(nil).Network))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*ProxyInbound)(nil).Network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process mocks base method
|
// Process mocks base method.
|
||||||
func (m *ProxyInbound) Process(arg0 context.Context, arg1 net.Network, arg2 internet.Connection, arg3 routing.Dispatcher) error {
|
func (m *ProxyInbound) Process(arg0 context.Context, arg1 net.Network, arg2 internet.Connection, arg3 routing.Dispatcher) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2, arg3)
|
ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2, arg3)
|
||||||
@@ -59,36 +60,36 @@ func (m *ProxyInbound) Process(arg0 context.Context, arg1 net.Network, arg2 inte
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process indicates an expected call of Process
|
// Process indicates an expected call of Process.
|
||||||
func (mr *ProxyInboundMockRecorder) Process(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
func (mr *ProxyInboundMockRecorder) Process(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyInbound)(nil).Process), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyInbound)(nil).Process), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyOutbound is a mock of Outbound interface
|
// ProxyOutbound is a mock of Outbound interface.
|
||||||
type ProxyOutbound struct {
|
type ProxyOutbound struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *ProxyOutboundMockRecorder
|
recorder *ProxyOutboundMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyOutboundMockRecorder is the mock recorder for ProxyOutbound
|
// ProxyOutboundMockRecorder is the mock recorder for ProxyOutbound.
|
||||||
type ProxyOutboundMockRecorder struct {
|
type ProxyOutboundMockRecorder struct {
|
||||||
mock *ProxyOutbound
|
mock *ProxyOutbound
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProxyOutbound creates a new mock instance
|
// NewProxyOutbound creates a new mock instance.
|
||||||
func NewProxyOutbound(ctrl *gomock.Controller) *ProxyOutbound {
|
func NewProxyOutbound(ctrl *gomock.Controller) *ProxyOutbound {
|
||||||
mock := &ProxyOutbound{ctrl: ctrl}
|
mock := &ProxyOutbound{ctrl: ctrl}
|
||||||
mock.recorder = &ProxyOutboundMockRecorder{mock}
|
mock.recorder = &ProxyOutboundMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *ProxyOutbound) EXPECT() *ProxyOutboundMockRecorder {
|
func (m *ProxyOutbound) EXPECT() *ProxyOutboundMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process mocks base method
|
// Process mocks base method.
|
||||||
func (m *ProxyOutbound) Process(arg0 context.Context, arg1 *transport.Link, arg2 internet.Dialer) error {
|
func (m *ProxyOutbound) Process(arg0 context.Context, arg1 *transport.Link, arg2 internet.Dialer) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2)
|
||||||
@@ -96,7 +97,7 @@ func (m *ProxyOutbound) Process(arg0 context.Context, arg1 *transport.Link, arg2
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process indicates an expected call of Process
|
// Process indicates an expected call of Process.
|
||||||
func (mr *ProxyOutboundMockRecorder) Process(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *ProxyOutboundMockRecorder) Process(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyOutbound)(nil).Process), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyOutbound)(nil).Process), arg0, arg1, arg2)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
"github.com/xtls/xray-core/app/router"
|
"github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/geoip"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
@@ -39,7 +40,7 @@ func TestResolveIP(t *testing.T) {
|
|||||||
DomainStrategy: router.Config_IpIfNonMatch,
|
DomainStrategy: router.Config_IpIfNonMatch,
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*geoip.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{127, 0, 0, 0},
|
Ip: []byte{127, 0, 0, 0},
|
||||||
Prefix: 8,
|
Prefix: 8,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/app/router"
|
"github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
clog "github.com/xtls/xray-core/common/log"
|
clog "github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/matcher/domain"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
@@ -53,8 +54,8 @@ func TestReverseProxy(t *testing.T) {
|
|||||||
serial.ToTypedMessage(&router.Config{
|
serial.ToTypedMessage(&router.Config{
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Domain: []*router.Domain{
|
Domain: []*domain.Domain{
|
||||||
{Type: router.Domain_Full, Value: "test.example.com"},
|
{Type: domain.MatchingType_Full, Value: "test.example.com"},
|
||||||
},
|
},
|
||||||
TargetTag: &router.RoutingRule_Tag{
|
TargetTag: &router.RoutingRule_Tag{
|
||||||
Tag: "portal",
|
Tag: "portal",
|
||||||
@@ -122,8 +123,8 @@ func TestReverseProxy(t *testing.T) {
|
|||||||
serial.ToTypedMessage(&router.Config{
|
serial.ToTypedMessage(&router.Config{
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Domain: []*router.Domain{
|
Domain: []*domain.Domain{
|
||||||
{Type: router.Domain_Full, Value: "test.example.com"},
|
{Type: domain.MatchingType_Full, Value: "test.example.com"},
|
||||||
},
|
},
|
||||||
TargetTag: &router.RoutingRule_Tag{
|
TargetTag: &router.RoutingRule_Tag{
|
||||||
Tag: "reverse",
|
Tag: "reverse",
|
||||||
@@ -238,8 +239,8 @@ func TestReverseProxyLongRunning(t *testing.T) {
|
|||||||
serial.ToTypedMessage(&router.Config{
|
serial.ToTypedMessage(&router.Config{
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Domain: []*router.Domain{
|
Domain: []*domain.Domain{
|
||||||
{Type: router.Domain_Full, Value: "test.example.com"},
|
{Type: domain.MatchingType_Full, Value: "test.example.com"},
|
||||||
},
|
},
|
||||||
TargetTag: &router.RoutingRule_Tag{
|
TargetTag: &router.RoutingRule_Tag{
|
||||||
Tag: "portal",
|
Tag: "portal",
|
||||||
@@ -321,8 +322,8 @@ func TestReverseProxyLongRunning(t *testing.T) {
|
|||||||
serial.ToTypedMessage(&router.Config{
|
serial.ToTypedMessage(&router.Config{
|
||||||
Rule: []*router.RoutingRule{
|
Rule: []*router.RoutingRule{
|
||||||
{
|
{
|
||||||
Domain: []*router.Domain{
|
Domain: []*domain.Domain{
|
||||||
{Type: router.Domain_Full, Value: "test.example.com"},
|
{Type: domain.MatchingType_Full, Value: "test.example.com"},
|
||||||
},
|
},
|
||||||
TargetTag: &router.RoutingRule_Tag{
|
TargetTag: &router.RoutingRule_Tag{
|
||||||
Tag: "reverse",
|
Tag: "reverse",
|
||||||
|
|||||||
@@ -63,40 +63,23 @@ func (d *DefaultSystemDialer) lookupIP(domain string, strategy DomainStrategy, l
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var option = dns.IPOption{
|
var opt dns.Option
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strategy == DomainStrategy_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()):
|
case strategy == DomainStrategy_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()):
|
||||||
option = dns.IPOption{
|
opt = dns.LookupIPv4Only
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: false,
|
|
||||||
FakeEnable: false,
|
|
||||||
}
|
|
||||||
case strategy == DomainStrategy_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()):
|
case strategy == DomainStrategy_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()):
|
||||||
option = dns.IPOption{
|
opt = dns.LookupIPv6Only
|
||||||
IPv4Enable: false,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
}
|
|
||||||
case strategy == DomainStrategy_AS_IS:
|
case strategy == DomainStrategy_AS_IS:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.dns.LookupIP(domain, option)
|
return d.dns.LookupOptions(domain, opt, dns.LookupNoFake)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultSystemDialer) canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
|
func (d *DefaultSystemDialer) canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
|
||||||
if sockopt == nil || dst.Address.Family().IsIP() || d.dns == nil {
|
if sockopt == nil || dst.Address.Family().IsIP() || d.dns == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if dst.Address.Domain() == LookupDomainFromContext(ctx) {
|
|
||||||
newError("infinite loop detected").AtError().WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return sockopt.DomainStrategy != DomainStrategy_AS_IS
|
return sockopt.DomainStrategy != DomainStrategy_AS_IS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package internet
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type systemDialer int
|
|
||||||
|
|
||||||
const systemDialerKey systemDialer = 0
|
|
||||||
|
|
||||||
func ContextWithLookupDomain(ctx context.Context, domain string) context.Context {
|
|
||||||
return context.WithValue(ctx, systemDialerKey, domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookupDomainFromContext(ctx context.Context) string {
|
|
||||||
if domain, ok := ctx.Value(systemDialerKey).(string); ok {
|
|
||||||
return domain
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user