diff --git a/README.md b/README.md index dec8c7f4..b76ad42b 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,9 @@ - [Xray4Magisk](https://github.com/CerteKim/Xray4Magisk) - [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk) - Homebrew - - [Repository 0](https://github.com/N4FA/homebrew-xray) - - [Repository 1](https://github.com/xiruizhao/homebrew-xray) + - `brew install xray` + - [(Tap) Repository 0](https://github.com/N4FA/homebrew-xray) + - [(Tap) Repository 1](https://github.com/xiruizhao/homebrew-xray) ## Usage diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index 8ecb6b28..1c0ba79c 100644 --- a/app/dispatcher/default.go +++ b/app/dispatcher/default.go @@ -196,7 +196,7 @@ func shouldOverride(ctx context.Context, result SniffResult, request session.Sni return true } if fakeDNSEngine != nil && protocolString != "bittorrent" && p == "fakedns" && - fakeDNSEngine.GetFakeIPRange().Contains(destination.Address.IP()) { + destination.Address.Family().IsIP() && fakeDNSEngine.GetFakeIPRange().Contains(destination.Address.IP()) { newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx)) return true } @@ -309,15 +309,10 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) { var handler outbound.Handler - skipRoutePick := false - if content := session.ContentFromContext(ctx); content != nil { - skipRoutePick = content.SkipRoutePick - } - routingLink := routing_session.AsRoutingContext(ctx) inTag := routingLink.GetInboundTag() isPickRoute := false - if d.router != nil && !skipRoutePick { + if d.router != nil { if route, err := d.router.PickRoute(routingLink); err == nil { outTag := route.GetOutboundTag() isPickRoute = true diff --git a/app/dns/config.go b/app/dns/config.go new file mode 100644 index 00000000..6236f7b5 --- /dev/null +++ b/app/dns/config.go @@ -0,0 +1,63 @@ +package dns + +import ( + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/strmatcher" + "github.com/xtls/xray-core/common/uuid" +) + +var typeMap = map[DomainMatchingType]strmatcher.Type{ + DomainMatchingType_Full: strmatcher.Full, + DomainMatchingType_Subdomain: strmatcher.Domain, + DomainMatchingType_Keyword: strmatcher.Substr, + DomainMatchingType_Regex: strmatcher.Regex, +} + +// 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 = []*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"}, +} + +var localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{ + Rule: "geosite:private", + Size: uint32(len(localTLDsAndDotlessDomains)), +} + +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 +} + +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() +} diff --git a/app/dns/config.pb.go b/app/dns/config.pb.go index 5a3368af..1715e408 100644 --- a/app/dns/config.pb.go +++ b/app/dns/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 -// protoc v3.14.0 +// protoc v3.15.8 // source: app/dns/config.proto package dns @@ -79,12 +79,112 @@ func (DomainMatchingType) EnumDescriptor() ([]byte, []int) { return file_app_dns_config_proto_rawDescGZIP(), []int{0} } +type QueryStrategy int32 + +const ( + QueryStrategy_USE_IP QueryStrategy = 0 + QueryStrategy_USE_IP4 QueryStrategy = 1 + QueryStrategy_USE_IP6 QueryStrategy = 2 +) + +// Enum value maps for QueryStrategy. +var ( + QueryStrategy_name = map[int32]string{ + 0: "USE_IP", + 1: "USE_IP4", + 2: "USE_IP6", + } + QueryStrategy_value = map[string]int32{ + "USE_IP": 0, + "USE_IP4": 1, + "USE_IP6": 2, + } +) + +func (x QueryStrategy) Enum() *QueryStrategy { + p := new(QueryStrategy) + *p = x + return p +} + +func (x QueryStrategy) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (QueryStrategy) Descriptor() protoreflect.EnumDescriptor { + return file_app_dns_config_proto_enumTypes[1].Descriptor() +} + +func (QueryStrategy) Type() protoreflect.EnumType { + return &file_app_dns_config_proto_enumTypes[1] +} + +func (x QueryStrategy) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use QueryStrategy.Descriptor instead. +func (QueryStrategy) EnumDescriptor() ([]byte, []int) { + return file_app_dns_config_proto_rawDescGZIP(), []int{1} +} + +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[2].Descriptor() +} + +func (CacheStrategy) Type() protoreflect.EnumType { + return &file_app_dns_config_proto_enumTypes[2] +} + +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{2} +} + type NameServer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` + SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"` PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"` Geoip []*router.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"` @@ -129,6 +229,20 @@ func (x *NameServer) GetAddress() *net.Endpoint { return nil } +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() []*NameServer_PriorityDomain { if x != nil { return x.PrioritizedDomain @@ -174,6 +288,10 @@ type Config struct { 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 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() { @@ -252,6 +370,27 @@ func (x *Config) GetTag() string { return "" } +func (x *Config) GetCacheStrategy() CacheStrategy { + if x != nil { + return x.CacheStrategy + } + return CacheStrategy_Cache_ALL +} + +func (x *Config) GetQueryStrategy() QueryStrategy { + if x != nil { + return x.QueryStrategy + } + return QueryStrategy_USE_IP +} + +func (x *Config) GetDisableFallback() bool { + if x != nil { + return x.DisableFallback + } + return false +} + type NameServer_PriorityDomain struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -371,8 +510,7 @@ type Config_HostMapping struct { Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,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 - // domain. Xray will use this domain for IP queries. This field is only - // effective if ip is empty. + // domain. Xray will use this domain for IP queries. ProxiedDomain string `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"` } @@ -446,77 +584,101 @@ var file_app_dns_config_proto_rawDesc = []byte{ 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, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xad, 0x03, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xee, 0x03, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 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, - 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x56, 0x0a, 0x12, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 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, - 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, - 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, - 0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 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, 0x5e, 0x0a, - 0x0e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 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, 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, 0x9f, 0x04, 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, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x61, + 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, + 0x69, 0x70, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x56, 0x0a, 0x12, 0x70, 0x72, + 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 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, + 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, + 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, + 0x12, 0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 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, 0x5e, + 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 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, 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, 0xd7, 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, 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, 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, + 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, 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, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, + 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, 0x2a, 0x35, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, + 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, + 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x02, 0x2a, 0x44, + 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, + 0x0d, 0x0a, 0x09, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x00, 0x12, 0x11, + 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x4e, 0x4f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, + 0x4c, 0x45, 0x10, 0x02, 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 ( @@ -531,37 +693,41 @@ func file_app_dns_config_proto_rawDescGZIP() []byte { 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, 3) var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_app_dns_config_proto_goTypes = []interface{}{ (DomainMatchingType)(0), // 0: xray.app.dns.DomainMatchingType - (*NameServer)(nil), // 1: xray.app.dns.NameServer - (*Config)(nil), // 2: xray.app.dns.Config - (*NameServer_PriorityDomain)(nil), // 3: xray.app.dns.NameServer.PriorityDomain - (*NameServer_OriginalRule)(nil), // 4: xray.app.dns.NameServer.OriginalRule - nil, // 5: xray.app.dns.Config.HostsEntry - (*Config_HostMapping)(nil), // 6: xray.app.dns.Config.HostMapping - (*net.Endpoint)(nil), // 7: xray.common.net.Endpoint - (*router.GeoIP)(nil), // 8: xray.app.router.GeoIP - (*net.IPOrDomain)(nil), // 9: xray.common.net.IPOrDomain + (QueryStrategy)(0), // 1: xray.app.dns.QueryStrategy + (CacheStrategy)(0), // 2: xray.app.dns.CacheStrategy + (*NameServer)(nil), // 3: xray.app.dns.NameServer + (*Config)(nil), // 4: xray.app.dns.Config + (*NameServer_PriorityDomain)(nil), // 5: xray.app.dns.NameServer.PriorityDomain + (*NameServer_OriginalRule)(nil), // 6: xray.app.dns.NameServer.OriginalRule + nil, // 7: xray.app.dns.Config.HostsEntry + (*Config_HostMapping)(nil), // 8: xray.app.dns.Config.HostMapping + (*net.Endpoint)(nil), // 9: xray.common.net.Endpoint + (*router.GeoIP)(nil), // 10: xray.app.router.GeoIP + (*net.IPOrDomain)(nil), // 11: xray.common.net.IPOrDomain } var file_app_dns_config_proto_depIdxs = []int32{ - 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, // 2: xray.app.dns.NameServer.geoip:type_name -> xray.app.router.GeoIP - 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 - 1, // 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 - 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 - 9, // 9: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain - 0, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType - 11, // [11:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 9, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint + 5, // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain + 10, // 2: xray.app.dns.NameServer.geoip:type_name -> xray.app.router.GeoIP + 6, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule + 9, // 4: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint + 3, // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer + 7, // 6: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry + 8, // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping + 2, // 8: xray.app.dns.Config.cache_strategy:type_name -> xray.app.dns.CacheStrategy + 1, // 9: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy + 0, // 10: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType + 11, // 11: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain + 0, // 12: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_app_dns_config_proto_init() } @@ -636,7 +802,7 @@ func file_app_dns_config_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_dns_config_proto_rawDesc, - NumEnums: 1, + NumEnums: 3, NumMessages: 6, NumExtensions: 0, NumServices: 0, diff --git a/app/dns/config.proto b/app/dns/config.proto index 94488719..1e395c43 100644 --- a/app/dns/config.proto +++ b/app/dns/config.proto @@ -12,6 +12,8 @@ import "app/router/config.proto"; message NameServer { xray.common.net.Endpoint address = 1; + bytes client_ip = 5; + bool skipFallback = 6; message PriorityDomain { DomainMatchingType type = 1; @@ -35,6 +37,18 @@ enum DomainMatchingType { Regex = 3; } +enum QueryStrategy { + USE_IP = 0; + USE_IP4 = 1; + USE_IP6 = 2; +} + +enum CacheStrategy { + Cache_ALL = 0; + Cache_NOERROR = 1; + Cache_DISABLE = 2; +} + message Config { // Nameservers used by this DNS. Only traditional UDP servers are support at // the moment. A special value 'localhost' as a domain address can be set to @@ -59,8 +73,7 @@ message Config { repeated bytes ip = 3; // 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 - // effective if ip is empty. + // domain. Xray will use this domain for IP queries. string proxied_domain = 4; } @@ -70,4 +83,11 @@ message Config { string tag = 6; reserved 7; + + // DisableCache disables DNS cache + CacheStrategy cache_strategy = 8; + + QueryStrategy query_strategy = 9; + + bool disableFallback = 10; } diff --git a/app/dns/dns.go b/app/dns/dns.go index e56671e4..73d417c3 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -2,3 +2,312 @@ package dns //go:generate go run github.com/xtls/xray-core/common/errors/errorgen + +import ( + "context" + "fmt" + "strings" + "sync" + + "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/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 strmatcher.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 := &strmatcher.MatcherGroup{} + geoipContainer := router.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 strmatcher.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)) + })) +} diff --git a/app/dns/server_test.go b/app/dns/dns_test.go similarity index 87% rename from app/dns/server_test.go rename to app/dns/dns_test.go index c2b984ad..8c96eeea 100644 --- a/app/dns/server_test.go +++ b/app/dns/dns_test.go @@ -101,8 +101,8 @@ func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { rr, _ := dns.NewRR("localhost-b. IN A 127.0.0.4") ans.Answer = append(ans.Answer, rr) - case q.Name == "mijia\\ cloud." && q.Qtype == dns.TypeA: - rr, _ := dns.NewRR("mijia\\ cloud. IN A 127.0.0.1") + case q.Name == "Mijia\\ Cloud." && q.Qtype == dns.TypeA: + rr, _ := dns.NewRR("Mijia\\ Cloud. IN A 127.0.0.1") ans.Answer = append(ans.Answer, rr) } } @@ -154,11 +154,7 @@ func TestUDPServerSubnet(t *testing.T) { client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) - ips, err := client.LookupIP("google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -213,11 +209,7 @@ func TestUDPServer(t *testing.T) { client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) { - ips, err := client.LookupIP("google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -228,11 +220,7 @@ func TestUDPServer(t *testing.T) { } { - ips, err := client.LookupIP("facebook.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("facebook.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -243,11 +231,7 @@ func TestUDPServer(t *testing.T) { } { - _, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + _, err := client.LookupIP("notexist.google.com") if err == nil { t.Fatal("nil error") } @@ -257,11 +241,8 @@ func TestUDPServer(t *testing.T) { } { - ips, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{ - IPv4Enable: false, - IPv6Enable: true, - FakeEnable: false, - }) + clientv6 := client.(feature_dns.IPv6Lookup) + ips, err := clientv6.LookupIPv6("ipv4only.google.com") if err != feature_dns.ErrEmptyResponse { t.Fatal("error: ", err) } @@ -273,11 +254,7 @@ func TestUDPServer(t *testing.T) { dnsServer.Shutdown() { - ips, err := client.LookupIP("google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -354,11 +331,7 @@ func TestPrioritizedDomain(t *testing.T) { startTime := time.Now() { - ips, err := client.LookupIP("google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -417,12 +390,9 @@ func TestUDPServerIPv6(t *testing.T) { common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) + client6 := client.(feature_dns.IPv6Lookup) { - ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{ - IPv4Enable: false, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client6.LookupIPv6("ipv6.google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -485,11 +455,7 @@ func TestStaticHostDomain(t *testing.T) { client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) { - ips, err := client.LookupIP("example.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("example.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -596,11 +562,7 @@ func TestIPMatch(t *testing.T) { startTime := time.Now() { - ips, err := client.LookupIP("google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -719,11 +681,7 @@ func TestLocalDomain(t *testing.T) { startTime := time.Now() { // Will match dotless: - ips, err := client.LookupIP("hostname", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("hostname") if err != nil { t.Fatal("unexpected error: ", err) } @@ -734,11 +692,7 @@ func TestLocalDomain(t *testing.T) { } { // Will match domain:local - ips, err := client.LookupIP("hostname.local", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("hostname.local") if err != nil { t.Fatal("unexpected error: ", err) } @@ -749,11 +703,7 @@ func TestLocalDomain(t *testing.T) { } { // Will match static ip - ips, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("hostnamestatic") if err != nil { t.Fatal("unexpected error: ", err) } @@ -764,11 +714,7 @@ func TestLocalDomain(t *testing.T) { } { // Will match domain replacing - ips, err := client.LookupIP("hostnamealias", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("hostnamealias") if err != nil { t.Fatal("unexpected error: ", err) } @@ -779,11 +725,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: - ips, err := client.LookupIP("localhost", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("localhost") if err != nil { t.Fatal("unexpected error: ", err) } @@ -794,11 +736,7 @@ func TestLocalDomain(t *testing.T) { } { // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 - ips, err := client.LookupIP("localhost-a", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("localhost-a") if err != nil { t.Fatal("unexpected error: ", err) } @@ -809,11 +747,7 @@ func TestLocalDomain(t *testing.T) { } { // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 - ips, err := client.LookupIP("localhost-b", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("localhost-b") if err != nil { t.Fatal("unexpected error: ", err) } @@ -824,11 +758,7 @@ func TestLocalDomain(t *testing.T) { } { // Will match dotless: - ips, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("Mijia Cloud") if err != nil { t.Fatal("unexpected error: ", err) } @@ -990,11 +920,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) { startTime := time.Now() { // Will match server 1,2 and server 1 returns expected ip - ips, err := client.LookupIP("google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -1005,11 +931,8 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) { } { // 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{ - IPv4Enable: true, - IPv6Enable: false, - FakeEnable: false, - }) + clientv4 := client.(feature_dns.IPv4Lookup) + ips, err := clientv4.LookupIPv4("ipv6.google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -1020,11 +943,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) { } { // Will match server 3,1,2 and server 3 returns expected one - ips, err := client.LookupIP("api.google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("api.google.com") if err != nil { t.Fatal("unexpected error: ", err) } @@ -1035,11 +954,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) { } { // Will match server 4,3,1,2 and server 4 returns expected one - ips, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := client.LookupIP("v2.api.google.com") if err != nil { t.Fatal("unexpected error: ", err) } diff --git a/app/dns/hosts.go b/app/dns/hosts.go index 0b584782..bfbe5d64 100644 --- a/app/dns/hosts.go +++ b/app/dns/hosts.go @@ -14,25 +14,6 @@ type StaticHosts struct { matchers *strmatcher.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. func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) { g := new(strmatcher.MatcherGroup) @@ -66,6 +47,9 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma id := g.Add(matcher) ips := make([]net.Address, 0, len(mapping.Ip)+1) switch { + case len(mapping.ProxiedDomain) > 0: + ips = append(ips, net.DomainAddress(mapping.ProxiedDomain)) + case len(mapping.Ip) > 0: for _, ip := range mapping.Ip { addr := net.IPAddress(ip) @@ -75,49 +59,56 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma ips = append(ips, addr) } - case len(mapping.ProxiedDomain) > 0: - ips = append(ips, net.DomainAddress(mapping.ProxiedDomain)) - default: 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 } 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)) for _, ip := range ips { if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) { filtered = append(filtered, ip) } } - if len(filtered) == 0 { - return nil - } return filtered } -// LookupIP returns IP address for the given domain, if exists in this StaticHosts. -func (h *StaticHosts) LookupIP(domain string, option dns.IPOption) []net.Address { - indices := h.matchers.Match(domain) - if len(indices) == 0 { - return nil - } - ips := []net.Address{} - for _, id := range indices { +func (h *StaticHosts) lookupInternal(domain string) []net.Address { + var ips []net.Address + for _, id := range h.matchers.Match(domain) { ips = append(ips, h.ips[id]...) } if len(ips) == 1 && ips[0].Family().IsDomain() { 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) } diff --git a/app/dns/hosts_test.go b/app/dns/hosts_test.go index 2d6929af..3e1b5437 100644 --- a/app/dns/hosts_test.go +++ b/app/dns/hosts_test.go @@ -20,6 +20,20 @@ func TestStaticHosts(t *testing.T) { {1, 1, 1, 1}, }, }, + { + Type: DomainMatchingType_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: DomainMatchingType_Full, + Domain: "proxy2.example.com", + ProxiedDomain: "proxy.example.com", + }, { Type: DomainMatchingType_Subdomain, Domain: "example.cn", @@ -32,6 +46,7 @@ func TestStaticHosts(t *testing.T) { Domain: "baidu.com", Ip: [][]byte{ {127, 0, 0, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, }, } @@ -40,7 +55,7 @@ func TestStaticHosts(t *testing.T) { common.Must(err) { - ips := hosts.LookupIP("example.com", dns.IPOption{ + ips := hosts.Lookup("example.com", &dns.IPOption{ IPv4Enable: true, IPv6Enable: true, }) @@ -53,7 +68,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, IPv6Enable: true, }) @@ -66,7 +107,7 @@ func TestStaticHosts(t *testing.T) { } { - ips := hosts.LookupIP("baidu.com", dns.IPOption{ + ips := hosts.Lookup("baidu.com", &dns.IPOption{ IPv4Enable: false, IPv6Enable: true, }) diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go index 2b557099..f0e546bb 100644 --- a/app/dns/nameserver.go +++ b/app/dns/nameserver.go @@ -2,40 +2,211 @@ package dns import ( "context" + "net/url" + "strings" + "time" + "github.com/xtls/xray-core/app/router" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/strmatcher" + core "github.com/xtls/xray-core/core" "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. -type Client interface { +// Server is the interface for Name Server. +type Server interface { // Name of the Client. Name() string - // 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 *localdns.Client +// Client is the interface for DNS client. +type Client struct { + server Server + clientIP net.IP + skipFallback bool + domains []string + expectIPs []*router.GeoIPMatcher } -func (s *LocalNameServer) QueryIP(_ context.Context, domain string, option dns.IPOption) ([]net.IP, error) { - if option.IPv4Enable || option.IPv6Enable { - return s.client.LookupIP(domain, option) +var errExpectedIPNonMatch = errors.New("expectIPs not match") + +// 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 router.GeoIPMatcherContainer, matcherInfos *[]DomainMatcherInfo, updateDomainRule func(strmatcher.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.Domain) + 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 []*router.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 { - return "localhost" +// Name returns the server name the client manages. +func (c *Client) Name() string { + return c.server.Name() } -func NewLocalNameServer() *LocalNameServer { - newError("DNS: created localhost client").AtInfo().WriteToLog() - return &LocalNameServer{ - client: localdns.New(), +// QueryIP send DNS query to the name server with the client's IP. +func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, cs CacheStrategy) ([]net.IP, error) { + ctx, cancel := context.WithTimeout(ctx, 4*time.Second) + 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 } diff --git a/app/dns/dohdns.go b/app/dns/nameserver_doh.go similarity index 85% rename from app/dns/dohdns.go rename to app/dns/nameserver_doh.go index 25d35074..2b38a87d 100644 --- a/app/dns/dohdns.go +++ b/app/dns/nameserver_doh.go @@ -42,10 +42,10 @@ type DoHNameServer struct { name string } -// NewDoHNameServer creates DOH client object for remote resolving -func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.IP) (*DoHNameServer, error) { +// NewDoHNameServer creates DOH server object for remote resolving +func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher) (*DoHNameServer, error) { newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog() - s := baseDOHNameServer(url, "DOH", clientIP) + s := baseDOHNameServer(url, "DOH") s.dispatcher = dispatcher tr := &http.Transport{ @@ -61,7 +61,8 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net. return nil, err } - dispatcherCtx = session.ContextWithContent(dispatcherCtx, &session.Content{Protocol: "tls"}) + dispatcherCtx = session.ContextWithContent(dispatcherCtx, session.ContentFromContext(ctx)) + dispatcherCtx = session.ContextWithInbound(dispatcherCtx, session.InboundFromContext(ctx)) dispatcherCtx = log.ContextWithAccessMessage(dispatcherCtx, &log.AccessMessage{ From: "DoH", To: s.dohURL, @@ -79,6 +80,12 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net. if err != nil { return nil, err } + log.Record(&log.AccessMessage{ + From: "DoH", + To: s.dohURL, + Status: log.AccessAccepted, + Detour: "local", + }) cc := common.ChainedClosable{} if cw, ok := link.Writer.(common.Closable); ok { @@ -103,9 +110,9 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net. } // 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" - s := baseDOHNameServer(url, "DOHL", clientIP) + s := baseDOHNameServer(url, "DOHL") tr := &http.Transport{ IdleConnTimeout: 90 * time.Second, ForceAttemptHTTP2: true, @@ -135,13 +142,12 @@ func NewDoHLocalNameServer(url *url.URL, clientIP net.IP) *DoHNameServer { return s } -func baseDOHNameServer(url *url.URL, prefix string, clientIP net.IP) *DoHNameServer { +func baseDOHNameServer(url *url.URL, prefix string) *DoHNameServer { s := &DoHNameServer{ - ips: make(map[string]record), - clientIP: clientIP, - pub: pubsub.NewService(), - name: prefix + "//" + url.Host, - dohURL: url.String(), + ips: make(map[string]record), + pub: pubsub.NewService(), + name: prefix + "//" + url.Host, + dohURL: url.String(), } s.cleanup = &task.Periodic{ Interval: time.Minute, @@ -151,7 +157,7 @@ func baseDOHNameServer(url *url.URL, prefix string, clientIP net.IP) *DoHNameSer return s } -// Name returns client name +// Name implements Server. func (s *DoHNameServer) Name() string { return s.name } @@ -234,7 +240,7 @@ func (s *DoHNameServer) newReqID() uint16 { 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)) if s.name+"." == "DOH//"+domain { @@ -242,7 +248,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns return } - reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP)) + reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP)) var deadline time.Time if d, ok := ctx.Deadline(); ok { @@ -263,8 +269,8 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns } dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{ - Protocol: "https", - //SkipRoutePick: true, + Protocol: "https", + SkipDNSResolve: true, }) // forced to use mux for DOH @@ -348,7 +354,7 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt } if len(ips) > 0 { - return toNetIP(ips), nil + return toNetIP(ips) } if lastErr != nil { @@ -362,15 +368,21 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt return nil, errRecordNotFound } -// QueryIP is called from dns.Server->queryIPTimeout -func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, error) { // nolint: dupl +// QueryIP implements Server. +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) - ips, err := s.findIPsForDomain(fqdn, option) - if err != errRecordNotFound { - 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 + 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 @@ -399,7 +411,7 @@ func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_f } close(done) }() - s.sendQuery(ctx, fqdn, option) + s.sendQuery(ctx, fqdn, clientIP, option) start := time.Now() for { diff --git a/app/dns/nameserver_doh_test.go b/app/dns/nameserver_doh_test.go new file mode 100644 index 00000000..f4c24194 --- /dev/null +++ b/app/dns/nameserver_doh_test.go @@ -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) + } +} diff --git a/app/dns/nameserver_fakedns.go b/app/dns/nameserver_fakedns.go index 32d8bcd6..382f8317 100644 --- a/app/dns/nameserver_fakedns.go +++ b/app/dns/nameserver_fakedns.go @@ -16,11 +16,13 @@ func NewFakeDNSServer() *FakeDNSServer { return &FakeDNSServer{} } +const FakeDNSName = "FakeDNS" + 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 err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) { f.fakeDNSEngine = fd @@ -30,9 +32,9 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ dns.IPOpti } ips := f.fakeDNSEngine.GetFakeIPForDomain(domain) - netIP := toNetIP(ips) - if netIP == nil { - return nil, newError("Unable to convert IP to net ip").AtError() + netIP, err := toNetIP(ips) + if err != nil { + return nil, newError("Unable to convert IP to net ip").Base(err).AtError() } newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog() diff --git a/app/dns/nameserver_local.go b/app/dns/nameserver_local.go new file mode 100644 index 00000000..a8a5732e --- /dev/null +++ b/app/dns/nameserver_local.go @@ -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()} +} diff --git a/app/dns/nameserver_test.go b/app/dns/nameserver_local_test.go similarity index 68% rename from app/dns/nameserver_test.go rename to app/dns/nameserver_local_test.go index 9bd1a4a1..ab89c9fb 100644 --- a/app/dns/nameserver_test.go +++ b/app/dns/nameserver_local_test.go @@ -7,17 +7,17 @@ import ( . "github.com/xtls/xray-core/app/dns" "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) { s := NewLocalNameServer() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) - ips, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{ IPv4Enable: true, IPv6Enable: true, - FakeEnable: false, - }) + }, CacheStrategy_Cache_ALL) cancel() common.Must(err) if len(ips) == 0 { diff --git a/app/dns/nameserver_quic.go b/app/dns/nameserver_quic.go new file mode 100644 index 00000000..f7f08927 --- /dev/null +++ b/app/dns/nameserver_quic.go @@ -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) +} diff --git a/app/dns/nameserver_quic_test.go b/app/dns/nameserver_quic_test.go new file mode 100644 index 00000000..51f655bd --- /dev/null +++ b/app/dns/nameserver_quic_test.go @@ -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) + } +} diff --git a/app/dns/udpns.go b/app/dns/nameserver_udp.go similarity index 86% rename from app/dns/udpns.go rename to app/dns/nameserver_udp.go index 15faa5e2..fa6327c5 100644 --- a/app/dns/udpns.go +++ b/app/dns/nameserver_udp.go @@ -2,7 +2,6 @@ package dns import ( "context" - "github.com/xtls/xray-core/transport/internet" "strings" "sync" "sync/atomic" @@ -32,10 +31,10 @@ type ClassicNameServer struct { udpServer *udp.Dispatcher cleanup *task.Periodic 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 if address.Port == 0 { address.Port = net.Port(53) @@ -45,7 +44,6 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher address: address, ips: make(map[string]record), requests: make(map[uint16]dnsRequest), - clientIP: clientIP, pub: pubsub.NewService(), name: strings.ToUpper(address.String()), } @@ -58,10 +56,12 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher return s } +// Name implements Server. func (s *ClassicNameServer) Name() string { return s.name } +// Cleanup clears expired items from cache func (s *ClassicNameServer) Cleanup() error { now := time.Now() s.Lock() @@ -103,6 +103,7 @@ func (s *ClassicNameServer) Cleanup() error { return nil } +// HandleResponse handles udp response packet from remote DNS server. func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) { ipRec, err := parseResponse(packet.Payload.Bytes()) if err != nil { @@ -180,10 +181,10 @@ func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) { 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)) - reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP)) + reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP)) for _, req := range reqs { s.addPendingRequest(req) @@ -192,7 +193,6 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option if inbound := session.InboundFromContext(ctx); inbound != nil { udpCtx = session.ContextWithInbound(udpCtx, inbound) } - udpCtx = internet.ContextWithLookupDomain(udpCtx, internet.LookupDomainFromContext(ctx)) udpCtx = session.ContextWithContent(udpCtx, &session.Content{ Protocol: "dns", }) @@ -234,7 +234,7 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.I } if len(ips) > 0 { - return toNetIP(ips), nil + return toNetIP(ips) } if lastErr != nil { @@ -245,14 +245,20 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.I } // 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) - ips, err := s.findIPsForDomain(fqdn, option) - if err != errRecordNotFound { - 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 + 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 @@ -281,7 +287,7 @@ func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option d } close(done) }() - s.sendQuery(ctx, fqdn, option) + s.sendQuery(ctx, fqdn, clientIP, option) start := time.Now() for { diff --git a/app/dns/options.go b/app/dns/options.go new file mode 100644 index 00000000..06d93e29 --- /dev/null +++ b/app/dns/options.go @@ -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) +} diff --git a/app/dns/server.go b/app/dns/server.go deleted file mode 100644 index 0090c998..00000000 --- a/app/dns/server.go +++ /dev/null @@ -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)) - })) -} diff --git a/app/router/command/config.go b/app/router/command/config.go index e95418dc..7b62e718 100644 --- a/app/router/command/config.go +++ b/app/router/command/config.go @@ -28,6 +28,12 @@ func (c routingContext) GetTargetPort() net.Port { 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. func AsRoutingContext(r *RoutingContext) routing.Context { return routingContext{r} diff --git a/app/router/router.go b/app/router/router.go index 5d2bdc34..8c8b32e4 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -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) { - 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) } @@ -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 } diff --git a/app/router/router_test.go b/app/router/router_test.go index 14ce8a3d..cd0b7154 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -9,7 +9,6 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" - "github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/outbound" routing_session "github.com/xtls/xray-core/features/routing/session" "github.com/xtls/xray-core/testing/mocks" @@ -116,11 +115,7 @@ func TestIPOnDemand(t *testing.T) { defer mockCtl.Finish() mockDNS := mocks.NewDNSClient(mockCtl) - mockDNS.EXPECT().LookupIP(gomock.Eq("example.com"), dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() + mockDNS.EXPECT().LookupIP(gomock.Eq("example.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) common.Must(r.Init(config, mockDNS, nil)) @@ -155,11 +150,7 @@ func TestIPIfNonMatchDomain(t *testing.T) { defer mockCtl.Finish() mockDNS := mocks.NewDNSClient(mockCtl) - mockDNS.EXPECT().LookupIP(gomock.Eq("example.com"), dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() + mockDNS.EXPECT().LookupIP(gomock.Eq("example.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) common.Must(r.Init(config, mockDNS, nil)) diff --git a/common/buf/buffer.go b/common/buf/buffer.go index ec0c32bd..9eef9997 100644 --- a/common/buf/buffer.go +++ b/common/buf/buffer.go @@ -38,7 +38,7 @@ func NewExisted(b []byte) *Buffer { oLen := len(b) if oLen < Size { - b = append(b, make([]byte, Size-oLen)...) + b = b[:Size] } return &Buffer{ diff --git a/common/session/session.go b/common/session/session.go index 4744adeb..24ac4631 100644 --- a/common/session/session.go +++ b/common/session/session.go @@ -75,7 +75,7 @@ type Content struct { Attributes map[string]string - SkipRoutePick bool + SkipDNSResolve bool } // Sockopt is the settings for socket connection. diff --git a/core/config.go b/core/config.go index b5cc8de7..c5318558 100644 --- a/core/config.go +++ b/core/config.go @@ -80,14 +80,25 @@ func getFormat(filename string) string { func LoadConfig(formatName string, input interface{}) (*Config, error) { switch v := input.(type) { case cmdarg.Arg: - formats := make([]string, len(v)) hasProtobuf := false for i, file := range v { - f := getFormat(file) - if f == "" { + var f string + + if formatName == "auto" { + if file != "stdin:" { + f = getFormat(file) + } else { + f = "json" + } + } else { f = formatName } + + if f == "" { + return nil, newError("Failed to get format of ", file).AtWarning() + } + if f == "protobuf" { hasProtobuf = true } diff --git a/core/core.go b/core/core.go index 7d988386..1553ab7b 100644 --- a/core/core.go +++ b/core/core.go @@ -18,7 +18,7 @@ import ( ) var ( - version = "1.4.0" + version = "1.4.2" build = "Custom" codename = "Xray, Penetrates Everything." intro = "A unified platform for anti-censorship." diff --git a/features/dns/client.go b/features/dns/client.go index 584e24f8..3fc9dce0 100644 --- a/features/dns/client.go +++ b/features/dns/client.go @@ -14,6 +14,12 @@ type IPOption struct { 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. // // xray:api:stable @@ -21,7 +27,24 @@ type Client interface { features.Feature // 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. @@ -50,3 +73,35 @@ func RCodeFromError(err error) uint16 { } 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 + } +) diff --git a/features/dns/localdns/client.go b/features/dns/localdns/client.go index 92419dfa..d47d29c0 100644 --- a/features/dns/localdns/client.go +++ b/features/dns/localdns/client.go @@ -20,41 +20,64 @@ func (*Client) Start() error { return nil } func (*Client) Close() error { return nil } // 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) if err != nil { return nil, err } 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 { parsed := net.IPAddress(ip) if parsed != nil { 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 { 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 { ipv6 = append(ipv6, ip) } } - switch { - case option.IPv4Enable && option.IPv6Enable: - 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 - } + if len(ipv6) == 0 { + return nil, dns.ErrEmptyResponse } - return nil, dns.ErrEmptyResponse + + return ipv6, nil } // New create a new dns.Client that queries localhost for DNS. diff --git a/features/routing/context.go b/features/routing/context.go index f5a732a4..e7867c32 100644 --- a/features/routing/context.go +++ b/features/routing/context.go @@ -37,4 +37,7 @@ type Context interface { // GetAttributes returns extra attributes from the conneciont content. GetAttributes() map[string]string + + // GetSkipDNSResolve returns a flag switch for weather skip dns resolve during route pick. + GetSkipDNSResolve() bool } diff --git a/features/routing/dns/context.go b/features/routing/dns/context.go index b4d07717..9912332a 100644 --- a/features/routing/dns/context.go +++ b/features/routing/dns/context.go @@ -26,16 +26,12 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP { } if domain := ctx.GetTargetDomain(); len(domain) != 0 { - ips, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - }) + ips, err := ctx.dnsClient.LookupIP(domain) if err == nil { ctx.resolvedIPs = ips return ips } - newError("resolve ip for ", domain).Base(err).WriteToLog() + newError("failed to resolve ip for ", domain).Base(err).WriteToLog() } return nil diff --git a/features/routing/session/context.go b/features/routing/session/context.go index 0b37ebfa..ce7e9493 100644 --- a/features/routing/session/context.go +++ b/features/routing/session/context.go @@ -109,6 +109,14 @@ func (ctx *Context) GetAttributes() map[string]string { 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. func AsRoutingContext(ctx context.Context) routing.Context { return &Context{ diff --git a/go.mod b/go.mod index 374be962..4c022822 100644 --- a/go.mod +++ b/go.mod @@ -5,22 +5,23 @@ go 1.16 require ( github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/golang/mock v1.5.0 - github.com/golang/protobuf v1.4.3 + github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.5 github.com/gorilla/websocket v1.4.2 - github.com/lucas-clemente/quic-go v0.19.3 - github.com/miekg/dns v1.1.40 + github.com/lucas-clemente/quic-go v0.20.0 + github.com/miekg/dns v1.1.41 github.com/pelletier/go-toml v1.8.1 github.com/pires/go-proxyproto v0.5.0 + github.com/refraction-networking/utls v0.0.0-20201210053706-2179f286686b github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c github.com/stretchr/testify v1.7.0 github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499 go.starlark.net v0.0.0-20210312235212-74c10e2c17dc - golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/net v0.0.0-20210330230544-e57232859fb2 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c - google.golang.org/grpc v1.36.0 - google.golang.org/protobuf v1.25.0 + golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 + google.golang.org/grpc v1.36.1 + google.golang.org/protobuf v1.26.0 h12.io/socks v1.0.2 ) diff --git a/go.sum b/go.sum index 88cc9e15..2b074e16 100644 --- a/go.sum +++ b/go.sum @@ -43,12 +43,9 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= @@ -62,8 +59,9 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -98,19 +96,19 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4= -github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= +github.com/lucas-clemente/quic-go v0.20.0 h1:FSU3YN5VnLafHR27Ejs1r1CYMS7XMyIVDzRewkDLNBw= +github.com/lucas-clemente/quic-go v0.20.0/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= -github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= -github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= -github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= -github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A= +github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-16 v0.1.3 h1:XEZ1xGorVy9u+lJq+WXNE+hiqRYLNvJGYmwfwKQN2gU= +github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA= -github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= @@ -139,6 +137,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/refraction-networking/utls v0.0.0-20201210053706-2179f286686b h1:lzo71oHzQEz0fKMSjR0BpVzuh2hOHvJTxnN3Rnikmtg= +github.com/refraction-networking/utls v0.0.0-20201210053706-2179f286686b/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c h1:pqy40B3MQWYrza7YZXOXgl0Nf0QGFqrOC0BKae1UNAA= github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= @@ -169,7 +169,6 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= @@ -180,7 +179,6 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499 h1:QHESTXtfgc1ABV+ArlbPVqUx9Ht5I0dDkYhxYoXFxNo= github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.starlark.net v0.0.0-20210312235212-74c10e2c17dc h1:pVkptfeOTFfx+zXZo7HEHN3d5LmhatBFvHdm/f2QnpY= go.starlark.net v0.0.0-20210312235212-74c10e2c17dc/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= @@ -191,14 +189,13 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -211,11 +208,11 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210330230544-e57232859fb2 h1:nGCZOty+lVDsc4H2qPFksI5Se296+V+GhMiL/TzmYNk= +golang.org/x/net v0.0.0-20210330230544-e57232859fb2/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -235,22 +232,19 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c h1:coiPEfMv+ThsjULRDygLrJVlNE1gDdL2g65s0LhV2os= -golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -267,7 +261,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -285,7 +278,6 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= @@ -293,12 +285,11 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.36.0 h1:o1bcQ6imQMIOpdrO3SWf2z5RV72WbDwdXuK0MDlc8As= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -307,8 +298,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -331,7 +324,5 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/infra/conf/dns.go b/infra/conf/dns.go index ea86359e..d667265c 100644 --- a/infra/conf/dns.go +++ b/infra/conf/dns.go @@ -11,10 +11,12 @@ import ( ) type NameServerConfig struct { - Address *Address - Port uint16 - Domains []string - ExpectIPs StringList + Address *Address + ClientIP *Address + Port uint16 + SkipFallback bool + Domains []string + ExpectIPs StringList } func (c *NameServerConfig) UnmarshalJSON(data []byte) error { @@ -25,14 +27,18 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error { } var advanced struct { - Address *Address `json:"address"` - Port uint16 `json:"port"` - Domains []string `json:"domains"` - ExpectIPs StringList `json:"expectIps"` + Address *Address `json:"address"` + ClientIP *Address `json:"clientIp"` + Port uint16 `json:"port"` + SkipFallback bool `json:"skipFallback"` + Domains []string `json:"domains"` + ExpectIPs StringList `json:"expectIps"` } if err := json.Unmarshal(data, &advanced); err == nil { c.Address = advanced.Address + c.ClientIP = advanced.ClientIP c.Port = advanced.Port + c.SkipFallback = advanced.SkipFallback c.Domains = advanced.Domains c.ExpectIPs = advanced.ExpectIPs return nil @@ -87,12 +93,21 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) { 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{ Address: &net.Endpoint{ Network: net.Network_UDP, Address: c.Address.Build(), Port: uint32(c.Port), }, + ClientIp: myClientIP, + SkipFallback: c.SkipFallback, PrioritizedDomain: domains, Geoip: geoipList, OriginalRules: originalRules, @@ -108,28 +123,72 @@ var typeMap = map[router.Domain_Type]dns.DomainMatchingType{ // DNSConfig is a JSON serializable object for dns.Config. type DNSConfig struct { - Servers []*NameServerConfig `json:"servers"` - Hosts map[string]*Address `json:"hosts"` - ClientIP *Address `json:"clientIp"` - Tag string `json:"tag"` + Servers []*NameServerConfig `json:"servers"` + Hosts map[string]*HostAddress `json:"hosts"` + ClientIP *Address `json:"clientIp"` + 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 { - if addr.Family().IsIP() { - return &dns.Config_HostMapping{ - Ip: [][]byte{[]byte(addr.IP())}, +type HostAddress struct { + addr *Address + addrs []*Address +} + +// 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{ - 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 func (c *DNSConfig) Build() (*dns.Config, error) { 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 { @@ -139,6 +198,25 @@ func (c *DNSConfig) Build() (*dns.Config, error) { 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 { ns, err := server.Build() if err != nil { diff --git a/infra/conf/dns_test.go b/infra/conf/dns_test.go index 421fb877..26e30158 100644 --- a/infra/conf/dns_test.go +++ b/infra/conf/dns_test.go @@ -67,17 +67,23 @@ func TestDNSConfigParsing(t *testing.T) { Input: `{ "servers": [{ "address": "8.8.8.8", + "clientIp": "10.0.0.1", "port": 5353, + "skipFallback": true, "domains": ["domain:example.com"] }], "hosts": { "example.com": "127.0.0.1", + "xtls.github.io": ["1.2.3.4", "5.6.7.8"], "domain:example.com": "google.com", - "geosite:test": "10.0.0.1", - "keyword:google": "8.8.8.8", + "geosite:test": ["127.0.0.1", "127.0.0.2"], + "keyword:google": ["8.8.8.8", "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(), Output: &dns.Config{ @@ -92,6 +98,8 @@ func TestDNSConfigParsing(t *testing.T) { Network: net.Network_UDP, Port: 5353, }, + ClientIp: []byte{10, 0, 0, 1}, + SkipFallback: true, PrioritizedDomain: []*dns.NameServer_PriorityDomain{ { Type: dns.DomainMatchingType_Subdomain, @@ -120,20 +128,28 @@ func TestDNSConfigParsing(t *testing.T) { { Type: dns.DomainMatchingType_Full, Domain: "example.com", - Ip: [][]byte{{10, 0, 0, 1}}, + Ip: [][]byte{{127, 0, 0, 1}, {127, 0, 0, 2}}, }, { Type: dns.DomainMatchingType_Keyword, Domain: "google", - Ip: [][]byte{{8, 8, 8, 8}}, + Ip: [][]byte{{8, 8, 8, 8}, {8, 8, 4, 4}}, }, { Type: dns.DomainMatchingType_Regex, Domain: ".*\\.com", Ip: [][]byte{{8, 8, 4, 4}}, }, + { + Type: dns.DomainMatchingType_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, }, }, }) diff --git a/infra/conf/freedom.go b/infra/conf/freedom.go index 29e778d2..60dfd5b8 100644 --- a/infra/conf/freedom.go +++ b/infra/conf/freedom.go @@ -22,11 +22,11 @@ func (c *FreedomConfig) Build() (proto.Message, error) { config := new(freedom.Config) config.DomainStrategy = freedom.Config_AS_IS switch strings.ToLower(c.DomainStrategy) { - case "useip", "use_ip": + case "useip", "use_ip", "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 - 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 } diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 15d5dd32..62c3f1a8 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -321,6 +321,7 @@ type TLSConfig struct { MaxVersion string `json:"maxVersion"` CipherSuites string `json:"cipherSuites"` PreferServerCipherSuites bool `json:"preferServerCipherSuites"` + Fingerprint string `json:"fingerprint"` } // Build implements Buildable. @@ -348,6 +349,7 @@ func (c *TLSConfig) Build() (proto.Message, error) { config.MaxVersion = c.MaxVersion config.CipherSuites = c.CipherSuites config.PreferServerCipherSuites = c.PreferServerCipherSuites + config.Fingerprint = strings.ToLower(c.Fingerprint) return config, nil } @@ -476,22 +478,19 @@ type SocketConfig struct { // Build implements Buildable. func (c *SocketConfig) Build() (*internet.SocketConfig, error) { - tfo := int32(-1) + tfo := int32(0) // don't invoke setsockopt() for TFO if c.TFO != nil { switch v := c.TFO.(type) { case bool: if v { tfo = 256 } else { - tfo = 0 + tfo = -1 // TFO need to be disabled } case float64: - if v < 0 { - return nil, newError("tcpFastOpen: only boolean and non-negative integer value is acceptable") - } tfo = int32(math.Min(v, math.MaxInt32)) default: - return nil, newError("tcpFastOpen: only boolean and non-negative integer value is acceptable") + return nil, newError("tcpFastOpen: only boolean and integer value is acceptable") } } var tproxy internet.SocketConfig_TProxyMode diff --git a/infra/conf/transport_test.go b/infra/conf/transport_test.go index a3e9cc74..3afe9729 100644 --- a/infra/conf/transport_test.go +++ b/infra/conf/transport_test.go @@ -31,6 +31,13 @@ func TestSocketConfig(t *testing.T) { } } + // test "tcpFastOpen": true, queue length 256 is expected. other parameters are tested here too + expectedOutput := &internet.SocketConfig{ + Mark: 1, + Tfo: 256, + DomainStrategy: internet.DomainStrategy_USE_IP, + DialerProxy: "tag", + } runMultiTestCase(t, []TestCase{ { Input: `{ @@ -40,38 +47,118 @@ func TestSocketConfig(t *testing.T) { "dialerProxy": "tag" }`, Parser: createParser(), - Output: &internet.SocketConfig{ - Mark: 1, - Tfo: 256, - DomainStrategy: internet.DomainStrategy_USE_IP, - DialerProxy: "tag", - }, + Output: expectedOutput, }, }) + if expectedOutput.ParseTFOValue() != 256 { + t.Fatalf("unexpected parsed TFO value, which should be 256") + } + + // test "tcpFastOpen": false, disabled TFO is expected + expectedOutput = &internet.SocketConfig{ + Mark: 0, + Tfo: -1, + } runMultiTestCase(t, []TestCase{ { Input: `{ "tcpFastOpen": false }`, Parser: createParser(), - Output: &internet.SocketConfig{ - Mark: 0, - Tfo: 0, - }, + Output: expectedOutput, }, }) + if expectedOutput.ParseTFOValue() != 0 { + t.Fatalf("unexpected parsed TFO value, which should be 0") + } + + // test "tcpFastOpen": 65535, queue length 65535 is expected + expectedOutput = &internet.SocketConfig{ + Mark: 0, + Tfo: 65535, + } runMultiTestCase(t, []TestCase{ { Input: `{ "tcpFastOpen": 65535 }`, Parser: createParser(), - Output: &internet.SocketConfig{ - Mark: 0, - Tfo: 65535, - }, + Output: expectedOutput, }, }) + if expectedOutput.ParseTFOValue() != 65535 { + t.Fatalf("unexpected parsed TFO value, which should be 65535") + } + + // test "tcpFastOpen": -65535, disable TFO is expected + expectedOutput = &internet.SocketConfig{ + Mark: 0, + Tfo: -65535, + } + runMultiTestCase(t, []TestCase{ + { + Input: `{ + "tcpFastOpen": -65535 + }`, + Parser: createParser(), + Output: expectedOutput, + }, + }) + if expectedOutput.ParseTFOValue() != 0 { + t.Fatalf("unexpected parsed TFO value, which should be 0") + } + + // test "tcpFastOpen": 0, no operation is expected + expectedOutput = &internet.SocketConfig{ + Mark: 0, + Tfo: 0, + } + runMultiTestCase(t, []TestCase{ + { + Input: `{ + "tcpFastOpen": 0 + }`, + Parser: createParser(), + Output: expectedOutput, + }, + }) + if expectedOutput.ParseTFOValue() != -1 { + t.Fatalf("unexpected parsed TFO value, which should be -1") + } + + // test omit "tcpFastOpen", no operation is expected + expectedOutput = &internet.SocketConfig{ + Mark: 0, + Tfo: 0, + } + runMultiTestCase(t, []TestCase{ + { + Input: `{}`, + Parser: createParser(), + Output: expectedOutput, + }, + }) + if expectedOutput.ParseTFOValue() != -1 { + t.Fatalf("unexpected parsed TFO value, which should be -1") + } + + // test "tcpFastOpen": null, no operation is expected + expectedOutput = &internet.SocketConfig{ + Mark: 0, + Tfo: 0, + } + runMultiTestCase(t, []TestCase{ + { + Input: `{ + "tcpFastOpen": null + }`, + Parser: createParser(), + Output: expectedOutput, + }, + }) + if expectedOutput.ParseTFOValue() != -1 { + t.Fatalf("unexpected parsed TFO value, which should be -1") + } } func TestTransportConfig(t *testing.T) { diff --git a/main/commands/all/tls/cert.go b/main/commands/all/tls/cert.go index 03dd764f..c7e39eb6 100644 --- a/main/commands/all/tls/cert.go +++ b/main/commands/all/tls/cert.go @@ -26,6 +26,9 @@ Arguments: -domain=domain_name The domain name for the certificate. + -name=common_name + The common name for the certificate. + -org=organization The organization name for the certificate. diff --git a/main/run.go b/main/run.go index 8bd4027c..f303f15a 100644 --- a/main/run.go +++ b/main/run.go @@ -11,6 +11,7 @@ import ( "regexp" "runtime" "runtime/debug" + "strings" "syscall" "github.com/xtls/xray-core/common/cmdarg" @@ -31,7 +32,7 @@ Xray. Multiple assign is accepted. The -confdir=dir flag sets a dir with multiple json config The -format=json flag sets the format of config files. -Default "json". +Default "auto". The -test flag tells Xray to test config files only, without launching the server @@ -46,7 +47,7 @@ var ( configFiles cmdarg.Arg // "Config file for Xray.", the option is customed type, parse in main configDir string test = cmdRun.Flag.Bool("test", false, "Test config file only, without launching Xray server.") - format = cmdRun.Flag.String("format", "json", "Format of input file.") + format = cmdRun.Flag.String("format", "auto", "Format of input file.") /* We have to do this here because Golang's Test will also need to parse flag, before * main func in this file is run. @@ -111,13 +112,26 @@ func dirExists(file string) bool { return err == nil && info.IsDir() } +func getRegepxByFormat() string { + switch strings.ToLower(*format) { + case "json": + return `^.+\.json$` + case "toml": + return `^.+\.toml$` + case "yaml", "yml": + return `^.+\.(yaml|yml)$` + default: + return `^.+\.(json|toml|yaml|yml)$` + } +} + func readConfDir(dirPath string) { confs, err := ioutil.ReadDir(dirPath) if err != nil { log.Fatalln(err) } for _, f := range confs { - matched, err := regexp.MatchString(`^.+\.(json|toml|yaml|yml)$`, f.Name()) + matched, err := regexp.MatchString(getRegepxByFormat(), f.Name()) if err != nil { log.Fatalln(err) } @@ -160,7 +174,7 @@ func getConfigFilePath() cmdarg.Arg { func getConfigFormat() string { f := core.GetFormatByExtension(*format) if f == "" { - f = "json" + f = "auto" } return f } diff --git a/proxy/dns/dns.go b/proxy/dns/dns.go index 01c02af6..8b9fd957 100644 --- a/proxy/dns/dns.go +++ b/proxy/dns/dns.go @@ -43,6 +43,7 @@ type Handler struct { func (h *Handler) Init(config *Config, dnsClient dns.Client) error { h.client = dnsClient + if v, ok := dnsClient.(ownLinkVerifier); ok { h.ownLinkVerifier = v } @@ -198,22 +199,16 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, var err error var ttl uint32 = 600 + var opt dns.Option switch qType { case dnsmessage.TypeA: - ips, err = h.client.LookupIP(domain, dns.IPOption{ - IPv4Enable: true, - IPv6Enable: false, - FakeEnable: true, - }) + opt = dns.LookupIPv4Only case dnsmessage.TypeAAAA: - ips, err = h.client.LookupIP(domain, dns.IPOption{ - IPv4Enable: false, - IPv6Enable: true, - FakeEnable: true, - }) + opt = dns.LookupIPv6Only } + ips, err = h.client.LookupOptions(domain, opt, dns.LookupFake) rcode := dns.RCodeFromError(err) if rcode == 0 && len(ips) == 0 && err != dns.ErrEmptyResponse { newError("ip query").Base(err).WriteToLog() @@ -228,7 +223,6 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, RecursionAvailable: true, RecursionDesired: true, Response: true, - Authoritative: true, }) builder.EnableCompression() common.Must(builder.StartQuestions()) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index a1f99cc4..de01e072 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -59,26 +59,14 @@ func (h *Handler) policy() policy.Session { } func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address { - var option dns.IPOption = dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - } + var opt dns.Option if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) { - option = dns.IPOption{ - IPv4Enable: true, - IPv6Enable: false, - FakeEnable: false, - } + opt = dns.LookupIPv4Only } else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) { - option = dns.IPOption{ - IPv4Enable: false, - IPv6Enable: true, - FakeEnable: false, - } + opt = dns.LookupIPv6Only } - ips, err := h.dns.LookupIP(domain, option) + ips, err := h.dns.LookupOptions(domain, opt, dns.LookupNoFake) if err != nil { newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx)) } diff --git a/testing/mocks/dns.go b/testing/mocks/dns.go index 73a219ed..1178790c 100644 --- a/testing/mocks/dns.go +++ b/testing/mocks/dns.go @@ -5,36 +5,37 @@ package mocks import ( - gomock "github.com/golang/mock/gomock" - dns "github.com/xtls/xray-core/features/dns" net "net" 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 { ctrl *gomock.Controller recorder *DNSClientMockRecorder } -// DNSClientMockRecorder is the mock recorder for DNSClient +// DNSClientMockRecorder is the mock recorder for DNSClient. type DNSClientMockRecorder struct { mock *DNSClient } -// NewDNSClient creates a new mock instance +// NewDNSClient creates a new mock instance. func NewDNSClient(ctrl *gomock.Controller) *DNSClient { mock := &DNSClient{ctrl: ctrl} mock.recorder = &DNSClientMockRecorder{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 { return m.recorder } -// Close mocks base method +// Close mocks base method. func (m *DNSClient) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") @@ -42,28 +43,48 @@ func (m *DNSClient) Close() error { return ret0 } -// Close indicates an expected call of Close +// Close indicates an expected call of Close. func (mr *DNSClientMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*DNSClient)(nil).Close)) } -// LookupIP mocks base method -func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, error) { +// LookupIP mocks base method. +func (m *DNSClient) LookupIP(arg0 string) ([]net.IP, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "LookupIP", arg0, arg1) + ret := m.ctrl.Call(m, "LookupIP", arg0) ret0, _ := ret[0].([]net.IP) ret1, _ := ret[1].(error) return ret0, ret1 } -// LookupIP indicates an expected call of LookupIP -func (mr *DNSClientMockRecorder) LookupIP(arg0, arg1 interface{}) *gomock.Call { +// LookupIP indicates an expected call of LookupIP. +func (mr *DNSClientMockRecorder) LookupIP(arg0 interface{}) *gomock.Call { 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 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Start") @@ -71,13 +92,13 @@ func (m *DNSClient) Start() error { return ret0 } -// Start indicates an expected call of Start +// Start indicates an expected call of Start. func (mr *DNSClientMockRecorder) Start() *gomock.Call { mr.mock.ctrl.T.Helper() 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{} { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Type") @@ -85,7 +106,7 @@ func (m *DNSClient) Type() interface{} { return ret0 } -// Type indicates an expected call of Type +// Type indicates an expected call of Type. func (mr *DNSClientMockRecorder) Type() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*DNSClient)(nil).Type)) diff --git a/testing/mocks/io.go b/testing/mocks/io.go index d1f92cea..a24e4f7c 100644 --- a/testing/mocks/io.go +++ b/testing/mocks/io.go @@ -5,34 +5,35 @@ package mocks import ( - gomock "github.com/golang/mock/gomock" 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 { ctrl *gomock.Controller recorder *ReaderMockRecorder } -// ReaderMockRecorder is the mock recorder for Reader +// ReaderMockRecorder is the mock recorder for Reader. type ReaderMockRecorder struct { mock *Reader } -// NewReader creates a new mock instance +// NewReader creates a new mock instance. func NewReader(ctrl *gomock.Controller) *Reader { mock := &Reader{ctrl: ctrl} mock.recorder = &ReaderMockRecorder{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 { return m.recorder } -// Read mocks base method +// Read mocks base method. func (m *Reader) Read(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Read", arg0) @@ -41,36 +42,36 @@ func (m *Reader) Read(arg0 []byte) (int, error) { 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 { mr.mock.ctrl.T.Helper() 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 { ctrl *gomock.Controller recorder *WriterMockRecorder } -// WriterMockRecorder is the mock recorder for Writer +// WriterMockRecorder is the mock recorder for Writer. type WriterMockRecorder struct { mock *Writer } -// NewWriter creates a new mock instance +// NewWriter creates a new mock instance. func NewWriter(ctrl *gomock.Controller) *Writer { mock := &Writer{ctrl: ctrl} mock.recorder = &WriterMockRecorder{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 { return m.recorder } -// Write mocks base method +// Write mocks base method. func (m *Writer) Write(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Write", arg0) @@ -79,7 +80,7 @@ func (m *Writer) Write(arg0 []byte) (int, error) { 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 { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*Writer)(nil).Write), arg0) diff --git a/testing/mocks/log.go b/testing/mocks/log.go index 77c821f5..b408d964 100644 --- a/testing/mocks/log.go +++ b/testing/mocks/log.go @@ -5,41 +5,42 @@ package mocks import ( + reflect "reflect" + gomock "github.com/golang/mock/gomock" 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 { ctrl *gomock.Controller recorder *LogHandlerMockRecorder } -// LogHandlerMockRecorder is the mock recorder for LogHandler +// LogHandlerMockRecorder is the mock recorder for LogHandler. type LogHandlerMockRecorder struct { mock *LogHandler } -// NewLogHandler creates a new mock instance +// NewLogHandler creates a new mock instance. func NewLogHandler(ctrl *gomock.Controller) *LogHandler { mock := &LogHandler{ctrl: ctrl} mock.recorder = &LogHandlerMockRecorder{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 { return m.recorder } -// Handle mocks base method +// Handle mocks base method. func (m *LogHandler) Handle(arg0 log.Message) { m.ctrl.T.Helper() 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 { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handle", reflect.TypeOf((*LogHandler)(nil).Handle), arg0) diff --git a/testing/mocks/mux.go b/testing/mocks/mux.go index 07d4c4ad..2e295099 100644 --- a/testing/mocks/mux.go +++ b/testing/mocks/mux.go @@ -5,35 +5,36 @@ package mocks import ( + reflect "reflect" + gomock "github.com/golang/mock/gomock" 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 { ctrl *gomock.Controller recorder *MuxClientWorkerFactoryMockRecorder } -// MuxClientWorkerFactoryMockRecorder is the mock recorder for MuxClientWorkerFactory +// MuxClientWorkerFactoryMockRecorder is the mock recorder for MuxClientWorkerFactory. type MuxClientWorkerFactoryMockRecorder struct { mock *MuxClientWorkerFactory } -// NewMuxClientWorkerFactory creates a new mock instance +// NewMuxClientWorkerFactory creates a new mock instance. func NewMuxClientWorkerFactory(ctrl *gomock.Controller) *MuxClientWorkerFactory { mock := &MuxClientWorkerFactory{ctrl: ctrl} mock.recorder = &MuxClientWorkerFactoryMockRecorder{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 { return m.recorder } -// Create mocks base method +// Create mocks base method. func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Create") @@ -42,7 +43,7 @@ func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) { return ret0, ret1 } -// Create indicates an expected call of Create +// Create indicates an expected call of Create. func (mr *MuxClientWorkerFactoryMockRecorder) Create() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MuxClientWorkerFactory)(nil).Create)) diff --git a/testing/mocks/outbound.go b/testing/mocks/outbound.go index 9cab8c34..2271aa75 100644 --- a/testing/mocks/outbound.go +++ b/testing/mocks/outbound.go @@ -6,35 +6,36 @@ package mocks import ( context "context" + reflect "reflect" + gomock "github.com/golang/mock/gomock" 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 { ctrl *gomock.Controller recorder *OutboundManagerMockRecorder } -// OutboundManagerMockRecorder is the mock recorder for OutboundManager +// OutboundManagerMockRecorder is the mock recorder for OutboundManager. type OutboundManagerMockRecorder struct { mock *OutboundManager } -// NewOutboundManager creates a new mock instance +// NewOutboundManager creates a new mock instance. func NewOutboundManager(ctrl *gomock.Controller) *OutboundManager { mock := &OutboundManager{ctrl: ctrl} mock.recorder = &OutboundManagerMockRecorder{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 { return m.recorder } -// AddHandler mocks base method +// AddHandler mocks base method. func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddHandler", arg0, arg1) @@ -42,13 +43,13 @@ func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler 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 { mr.mock.ctrl.T.Helper() 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 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") @@ -56,13 +57,13 @@ func (m *OutboundManager) Close() error { return ret0 } -// Close indicates an expected call of Close +// Close indicates an expected call of Close. func (mr *OutboundManagerMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() 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 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDefaultHandler") @@ -70,13 +71,13 @@ func (m *OutboundManager) GetDefaultHandler() outbound.Handler { return ret0 } -// GetDefaultHandler indicates an expected call of GetDefaultHandler +// GetDefaultHandler indicates an expected call of GetDefaultHandler. func (mr *OutboundManagerMockRecorder) GetDefaultHandler() *gomock.Call { mr.mock.ctrl.T.Helper() 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 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetHandler", arg0) @@ -84,13 +85,13 @@ func (m *OutboundManager) GetHandler(arg0 string) outbound.Handler { return ret0 } -// GetHandler indicates an expected call of GetHandler +// GetHandler indicates an expected call of GetHandler. func (mr *OutboundManagerMockRecorder) GetHandler(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() 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 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RemoveHandler", arg0, arg1) @@ -98,13 +99,13 @@ func (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error 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 { mr.mock.ctrl.T.Helper() 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 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Start") @@ -112,13 +113,13 @@ func (m *OutboundManager) Start() error { return ret0 } -// Start indicates an expected call of Start +// Start indicates an expected call of Start. func (mr *OutboundManagerMockRecorder) Start() *gomock.Call { mr.mock.ctrl.T.Helper() 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{} { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Type") @@ -126,36 +127,36 @@ func (m *OutboundManager) Type() interface{} { return ret0 } -// Type indicates an expected call of Type +// Type indicates an expected call of Type. func (mr *OutboundManagerMockRecorder) Type() *gomock.Call { mr.mock.ctrl.T.Helper() 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 { ctrl *gomock.Controller recorder *OutboundHandlerSelectorMockRecorder } -// OutboundHandlerSelectorMockRecorder is the mock recorder for OutboundHandlerSelector +// OutboundHandlerSelectorMockRecorder is the mock recorder for OutboundHandlerSelector. type OutboundHandlerSelectorMockRecorder struct { mock *OutboundHandlerSelector } -// NewOutboundHandlerSelector creates a new mock instance +// NewOutboundHandlerSelector creates a new mock instance. func NewOutboundHandlerSelector(ctrl *gomock.Controller) *OutboundHandlerSelector { mock := &OutboundHandlerSelector{ctrl: ctrl} mock.recorder = &OutboundHandlerSelectorMockRecorder{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 { return m.recorder } -// Select mocks base method +// Select mocks base method. func (m *OutboundHandlerSelector) Select(arg0 []string) []string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Select", arg0) @@ -163,7 +164,7 @@ func (m *OutboundHandlerSelector) Select(arg0 []string) []string { return ret0 } -// Select indicates an expected call of Select +// Select indicates an expected call of Select. func (mr *OutboundHandlerSelectorMockRecorder) Select(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*OutboundHandlerSelector)(nil).Select), arg0) diff --git a/testing/mocks/proxy.go b/testing/mocks/proxy.go index cba5b3ca..d0499b88 100644 --- a/testing/mocks/proxy.go +++ b/testing/mocks/proxy.go @@ -6,38 +6,39 @@ package mocks import ( context "context" + reflect "reflect" + gomock "github.com/golang/mock/gomock" net "github.com/xtls/xray-core/common/net" routing "github.com/xtls/xray-core/features/routing" transport "github.com/xtls/xray-core/transport" 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 { ctrl *gomock.Controller recorder *ProxyInboundMockRecorder } -// ProxyInboundMockRecorder is the mock recorder for ProxyInbound +// ProxyInboundMockRecorder is the mock recorder for ProxyInbound. type ProxyInboundMockRecorder struct { mock *ProxyInbound } -// NewProxyInbound creates a new mock instance +// NewProxyInbound creates a new mock instance. func NewProxyInbound(ctrl *gomock.Controller) *ProxyInbound { mock := &ProxyInbound{ctrl: ctrl} mock.recorder = &ProxyInboundMockRecorder{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 { return m.recorder } -// Network mocks base method +// Network mocks base method. func (m *ProxyInbound) Network() []net.Network { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") @@ -45,13 +46,13 @@ func (m *ProxyInbound) Network() []net.Network { return ret0 } -// Network indicates an expected call of Network +// Network indicates an expected call of Network. func (mr *ProxyInboundMockRecorder) Network() *gomock.Call { mr.mock.ctrl.T.Helper() 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 { m.ctrl.T.Helper() 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 } -// 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 { mr.mock.ctrl.T.Helper() 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 { ctrl *gomock.Controller recorder *ProxyOutboundMockRecorder } -// ProxyOutboundMockRecorder is the mock recorder for ProxyOutbound +// ProxyOutboundMockRecorder is the mock recorder for ProxyOutbound. type ProxyOutboundMockRecorder struct { mock *ProxyOutbound } -// NewProxyOutbound creates a new mock instance +// NewProxyOutbound creates a new mock instance. func NewProxyOutbound(ctrl *gomock.Controller) *ProxyOutbound { mock := &ProxyOutbound{ctrl: ctrl} mock.recorder = &ProxyOutboundMockRecorder{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 { 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 { m.ctrl.T.Helper() 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 } -// Process indicates an expected call of Process +// Process indicates an expected call of Process. func (mr *ProxyOutboundMockRecorder) Process(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyOutbound)(nil).Process), arg0, arg1, arg2) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 457f67df..99c483be 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -36,6 +36,7 @@ func init() { type dialerConf struct { net.Destination *internet.SocketConfig + *tls.Config } var ( @@ -46,14 +47,9 @@ var ( func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) { grpcSettings := streamSettings.ProtocolSettings.(*Config) - config := tls.ConfigFromStreamSettings(streamSettings) - var dialOption = grpc.WithInsecure() + tlsConfig := tls.ConfigFromStreamSettings(streamSettings) - if config != nil { - dialOption = grpc.WithTransportCredentials(credentials.NewTLS(config.GetTLSConfig())) - } - - conn, err := getGrpcClient(ctx, dest, dialOption, streamSettings.SocketSettings) + conn, err := getGrpcClient(ctx, dest, tlsConfig, streamSettings.SocketSettings) if err != nil { return nil, newError("Cannot dial gRPC").Base(err) @@ -76,7 +72,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne return encoding.NewHunkConn(grpcService, nil), nil } -func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.DialOption, sockopt *internet.SocketConfig) (*grpc.ClientConn, error) { +func getGrpcClient(ctx context.Context, dest net.Destination, tlsConfig *tls.Config, sockopt *internet.SocketConfig) (*grpc.ClientConn, error) { globalDialerAccess.Lock() defer globalDialerAccess.Unlock() @@ -84,12 +80,24 @@ func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.Di globalDialerMap = make(map[dialerConf]*grpc.ClientConn) } - if client, found := globalDialerMap[dialerConf{dest, sockopt}]; found && client.GetState() != connectivity.Shutdown { + if client, found := globalDialerMap[dialerConf{dest, sockopt, tlsConfig}]; found && client.GetState() != connectivity.Shutdown { return client, nil } + dialOption := grpc.WithInsecure() + + if tlsConfig != nil { + dialOption = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig.GetTLSConfig())) + } + + var grpcDestHost string + if dest.Address.Family().IsDomain() { + grpcDestHost = dest.Address.Domain() + } else { + grpcDestHost = dest.Address.IP().String() + } conn, err := grpc.Dial( - gonet.JoinHostPort(dest.Address.String(), dest.Port.String()), + gonet.JoinHostPort(grpcDestHost, dest.Port.String()), dialOption, grpc.WithConnectParams(grpc.ConnectParams{ Backoff: backoff.Config{ @@ -125,6 +133,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.Di return internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt) }), ) - globalDialerMap[dialerConf{dest, sockopt}] = conn + globalDialerMap[dialerConf{dest, sockopt, tlsConfig}] = conn return conn, err } diff --git a/transport/internet/grpc/encoding/hunkconn.go b/transport/internet/grpc/encoding/hunkconn.go index 50815144..a15a9577 100644 --- a/transport/internet/grpc/encoding/hunkconn.go +++ b/transport/internet/grpc/encoding/hunkconn.go @@ -5,12 +5,15 @@ import ( "io" "net" + "google.golang.org/grpc/peer" + "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/signal/done" ) type HunkConn interface { + Context() context.Context Send(*Hunk) error Recv() (*Hunk, error) SendMsg(m interface{}) error @@ -35,11 +38,23 @@ func NewHunkReadWriter(hc HunkConn, cancel context.CancelFunc) *HunkReaderWriter } func NewHunkConn(hc HunkConn, cancel context.CancelFunc) net.Conn { + var rAddr net.Addr + pr, ok := peer.FromContext(hc.Context()) + if ok { + rAddr = pr.Addr + } else { + rAddr = &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + Port: 0, + } + } + wrc := NewHunkReadWriter(hc, cancel) return cnc.NewConnection( cnc.ConnectionInput(wrc), cnc.ConnectionOutput(wrc), cnc.ConnectionOnClose(wrc), + cnc.ConnectionRemoteAddr(rAddr), ) } @@ -85,7 +100,7 @@ func (h *HunkReaderWriter) ReadMultiBuffer() (buf.MultiBuffer, error) { } } - if cap(h.buf) == buf.Size { + if cap(h.buf) >= buf.Size { b := h.buf h.index = len(h.buf) return buf.MultiBuffer{buf.NewExisted(b)}, nil diff --git a/transport/internet/grpc/encoding/multiconn.go b/transport/internet/grpc/encoding/multiconn.go index b2b865ab..31e8bb4b 100644 --- a/transport/internet/grpc/encoding/multiconn.go +++ b/transport/internet/grpc/encoding/multiconn.go @@ -5,12 +5,15 @@ import ( "io" "net" + "google.golang.org/grpc/peer" + "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/signal/done" ) type MultiHunkConn interface { + Context() context.Context Send(*MultiHunk) error Recv() (*MultiHunk, error) SendMsg(m interface{}) error @@ -30,11 +33,23 @@ func NewMultiHunkReadWriter(hc MultiHunkConn, cancel context.CancelFunc) *MultiH } func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc) net.Conn { + var rAddr net.Addr + pr, ok := peer.FromContext(hc.Context()) + if ok { + rAddr = pr.Addr + } else { + rAddr = &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + Port: 0, + } + } + wrc := NewMultiHunkReadWriter(hc, cancel) return cnc.NewConnection( cnc.ConnectionInputMulti(wrc), cnc.ConnectionOutputMulti(wrc), cnc.ConnectionOnClose(wrc), + cnc.ConnectionRemoteAddr(rAddr), ) } @@ -64,16 +79,20 @@ func (h *MultiHunkReaderWriter) ReadMultiBuffer() (buf.MultiBuffer, error) { var mb = make(buf.MultiBuffer, 0, len(h.buf)) for _, b := range h.buf { - if cap(b) >= buf.Size { - mb = append(mb, buf.NewExisted(b)) + if len(b) == 0 { continue } - nb := buf.New() - nb.Extend(int32(len(b))) - copy(nb.Bytes(), b) + if cap(b) >= buf.Size { + mb = append(mb, buf.NewExisted(b)) + } else { + nb := buf.New() + nb.Extend(int32(len(b))) + copy(nb.Bytes(), b) + + mb = append(mb, nb) + } - mb = append(mb, nb) } return mb, nil } @@ -84,12 +103,15 @@ func (h *MultiHunkReaderWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { return io.ErrClosedPipe } - hunk := &MultiHunk{Data: make([][]byte, len(mb))} + hunks := make([][]byte, 0, len(mb)) + for _, b := range mb { - hunk.Data = append(hunk.Data, b.Bytes()) + if b.Len() > 0 { + hunks = append(hunks, b.Bytes()) + } } - err := h.hc.Send(hunk) + err := h.hc.Send(&MultiHunk{Data: hunks}) if err != nil { return err } diff --git a/transport/internet/http/dialer.go b/transport/internet/http/dialer.go index dc2cd8ab..ae3ba6d2 100644 --- a/transport/internet/http/dialer.go +++ b/transport/internet/http/dialer.go @@ -21,6 +21,7 @@ import ( type dialerConf struct { net.Destination *internet.SocketConfig + *tls.Config } var ( @@ -36,7 +37,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.C globalDialerMap = make(map[dialerConf]*http.Client) } - if client, found := globalDialerMap[dialerConf{dest, sockopt}]; found { + if client, found := globalDialerMap[dialerConf{dest, sockopt, tlsSettings}]; found { return client, nil } @@ -92,7 +93,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.C Transport: transport, } - globalDialerMap[dialerConf{dest, sockopt}] = client + globalDialerMap[dialerConf{dest, sockopt, tlsSettings}] = client return client, nil } diff --git a/transport/internet/quic/dialer.go b/transport/internet/quic/dialer.go index f5b94956..a1d5488f 100644 --- a/transport/internet/quic/dialer.go +++ b/transport/internet/quic/dialer.go @@ -148,8 +148,7 @@ func (s *clientSessions) openConnection(destAddr net.Addr, config *Config, tlsCo quicConfig := &quic.Config{ ConnectionIDLength: 12, - HandshakeTimeout: time.Second * 8, - MaxIdleTimeout: time.Second * 30, + KeepAlive: true, } conn, err := wrapSysConn(rawConn, config) diff --git a/transport/internet/quic/hub.go b/transport/internet/quic/hub.go index b863b303..605b52dc 100644 --- a/transport/internet/quic/hub.go +++ b/transport/internet/quic/hub.go @@ -103,8 +103,7 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti quicConfig := &quic.Config{ ConnectionIDLength: 12, - HandshakeTimeout: time.Second * 8, - MaxIdleTimeout: time.Second * 45, + KeepAlive: true, MaxIncomingStreams: 32, MaxIncomingUniStreams: -1, } diff --git a/transport/internet/sockopt.go b/transport/internet/sockopt.go index 7facf30f..d79191ab 100644 --- a/transport/internet/sockopt.go +++ b/transport/internet/sockopt.go @@ -17,3 +17,14 @@ func isUDPSocket(network string) bool { return false } } + +func (v *SocketConfig) ParseTFOValue() int { + if v.Tfo == 0 { + return -1 + } + tfo := int(v.Tfo) + if tfo < 0 { + tfo = 0 + } + return tfo +} diff --git a/transport/internet/sockopt_darwin.go b/transport/internet/sockopt_darwin.go index bdbd1b33..399cc88f 100644 --- a/transport/internet/sockopt_darwin.go +++ b/transport/internet/sockopt_darwin.go @@ -15,12 +15,12 @@ const ( func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error { if isTCPSocket(network) { - tfo := config.Tfo + tfo := config.ParseTFOValue() if tfo > 0 { tfo = TCP_FASTOPEN_CLIENT } if tfo >= 0 { - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, int(tfo)); err != nil { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, tfo); err != nil { return err } } @@ -31,12 +31,12 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error { if isTCPSocket(network) { - tfo := config.Tfo + tfo := config.ParseTFOValue() if tfo > 0 { tfo = TCP_FASTOPEN_SERVER } if tfo >= 0 { - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, int(tfo)); err != nil { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, tfo); err != nil { return err } } diff --git a/transport/internet/sockopt_freebsd.go b/transport/internet/sockopt_freebsd.go index 5ffbeea7..29c19de5 100644 --- a/transport/internet/sockopt_freebsd.go +++ b/transport/internet/sockopt_freebsd.go @@ -130,7 +130,7 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf } if isTCPSocket(network) { - tfo := int(config.Tfo) + tfo := config.ParseTFOValue() if tfo > 0 { tfo = 1 } @@ -163,9 +163,10 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) } } if isTCPSocket(network) { - if config.Tfo >= 0 { - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, int(config.Tfo)); err != nil { - return newError("failed to set TCP_FASTOPEN=", config.Tfo).Base(err) + tfo := config.ParseTFOValue() + if tfo >= 0 { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, tfo); err != nil { + return newError("failed to set TCP_FASTOPEN=", tfo).Base(err) } } } diff --git a/transport/internet/sockopt_linux.go b/transport/internet/sockopt_linux.go index 75b7a5e4..13c284bc 100644 --- a/transport/internet/sockopt_linux.go +++ b/transport/internet/sockopt_linux.go @@ -48,7 +48,7 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf } if isTCPSocket(network) { - tfo := int(config.Tfo) + tfo := config.ParseTFOValue() if tfo > 0 { tfo = 1 } @@ -75,9 +75,10 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) } } if isTCPSocket(network) { - if config.Tfo >= 0 { - if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN, int(config.Tfo)); err != nil { - return newError("failed to set TCP_FASTOPEN=", config.Tfo).Base(err) + tfo := config.ParseTFOValue() + if tfo >= 0 { + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN, tfo); err != nil { + return newError("failed to set TCP_FASTOPEN=", tfo).Base(err) } } } diff --git a/transport/internet/sockopt_windows.go b/transport/internet/sockopt_windows.go index 50e3a14c..840834b6 100644 --- a/transport/internet/sockopt_windows.go +++ b/transport/internet/sockopt_windows.go @@ -8,12 +8,12 @@ const ( TCP_FASTOPEN = 15 ) -func setTFO(fd syscall.Handle, tfo int32) error { +func setTFO(fd syscall.Handle, tfo int) error { if tfo > 0 { tfo = 1 } if tfo >= 0 { - if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_FASTOPEN, int(tfo)); err != nil { + if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_FASTOPEN, tfo); err != nil { return err } } @@ -22,7 +22,7 @@ func setTFO(fd syscall.Handle, tfo int32) error { func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error { if isTCPSocket(network) { - if err := setTFO(syscall.Handle(fd), config.Tfo); err != nil { + if err := setTFO(syscall.Handle(fd), config.ParseTFOValue()); err != nil { return err } @@ -33,7 +33,7 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error { if isTCPSocket(network) { - if err := setTFO(syscall.Handle(fd), config.Tfo); err != nil { + if err := setTFO(syscall.Handle(fd), config.ParseTFOValue()); err != nil { return err } } diff --git a/transport/internet/system_dialer.go b/transport/internet/system_dialer.go index 7256df9f..22a9de8d 100644 --- a/transport/internet/system_dialer.go +++ b/transport/internet/system_dialer.go @@ -63,40 +63,23 @@ func (d *DefaultSystemDialer) lookupIP(domain string, strategy DomainStrategy, l return nil, nil } - var option = dns.IPOption{ - IPv4Enable: true, - IPv6Enable: true, - FakeEnable: false, - } - + var opt dns.Option switch { case strategy == DomainStrategy_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()): - option = dns.IPOption{ - IPv4Enable: true, - IPv6Enable: false, - FakeEnable: false, - } + opt = dns.LookupIPv4Only case strategy == DomainStrategy_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()): - option = dns.IPOption{ - IPv4Enable: false, - IPv6Enable: true, - FakeEnable: false, - } + opt = dns.LookupIPv6Only case strategy == DomainStrategy_AS_IS: 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 { if sockopt == nil || dst.Address.Family().IsIP() || d.dns == nil { 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 } diff --git a/transport/internet/system_dialer_context.go b/transport/internet/system_dialer_context.go deleted file mode 100644 index abc6c104..00000000 --- a/transport/internet/system_dialer_context.go +++ /dev/null @@ -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 "" -} diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index c8fda925..968acbeb 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -21,14 +21,14 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) - /* - if config.IsExperiment8357() { - conn = tls.UClient(conn, tlsConfig) - } else { - conn = tls.Client(conn, tlsConfig) + if fingerprint, ok := tls.Fingerprints[config.Fingerprint]; ok { + conn = tls.UClient(conn, tlsConfig, fingerprint) + if err := conn.(*tls.UConn).Handshake(); err != nil { + return nil, err } - */ - conn = tls.Client(conn, tlsConfig) + } else { + conn = tls.Client(conn, tlsConfig) + } } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { xtlsConfig := config.GetXTLSConfig(xtls.WithDestination(dest)) conn = xtls.Client(conn, xtlsConfig) diff --git a/transport/internet/tcp/hub.go b/transport/internet/tcp/hub.go index 2c95cb99..02042c3c 100644 --- a/transport/internet/tcp/hub.go +++ b/transport/internet/tcp/hub.go @@ -38,7 +38,8 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSe if streamSettings.SocketSettings == nil { streamSettings.SocketSettings = &internet.SocketConfig{} } - streamSettings.SocketSettings.AcceptProxyProtocol = l.config.AcceptProxyProtocol + streamSettings.SocketSettings.AcceptProxyProtocol = + l.config.AcceptProxyProtocol || streamSettings.SocketSettings.AcceptProxyProtocol } var listener net.Listener var err error diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 961bea09..e39a9a4f 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -18,8 +18,6 @@ var ( globalSessionCache = tls.NewLRUClientSessionCache(128) ) -const exp8357 = "experiment:8357" - // ParseCertificate converts a cert.Certificate to Certificate. func ParseCertificate(c *cert.Certificate) *Certificate { if c != nil { @@ -240,15 +238,7 @@ func getNewGetCertficateFunc(certs []*tls.Certificate) func(hello *tls.ClientHel } } -func (c *Config) IsExperiment8357() bool { - return strings.HasPrefix(c.ServerName, exp8357) -} - func (c *Config) parseServerName() string { - if c.IsExperiment8357() { - return c.ServerName[len(exp8357):] - } - return c.ServerName } diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 77dd879f..b58a660d 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 -// protoc v3.14.0 +// protoc v3.15.6 // source: transport/internet/tls/config.proto package tls @@ -200,6 +200,8 @@ type Config struct { CipherSuites string `protobuf:"bytes,9,opt,name=cipher_suites,json=cipherSuites,proto3" json:"cipher_suites,omitempty"` // Whether the server selects its most preferred ciphersuite. PreferServerCipherSuites bool `protobuf:"varint,10,opt,name=prefer_server_cipher_suites,json=preferServerCipherSuites,proto3" json:"prefer_server_cipher_suites,omitempty"` + // TLS Client Hello fingerprint (uTLS). + Fingerprint string `protobuf:"bytes,11,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` } func (x *Config) Reset() { @@ -304,6 +306,13 @@ func (x *Config) GetPreferServerCipherSuites() bool { return false } +func (x *Config) GetFingerprint() string { + if x != nil { + return x.Fingerprint + } + return "" +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ @@ -333,7 +342,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, - 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xd3, 0x03, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, + 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xf5, 0x03, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65, 0x72, @@ -362,15 +371,17 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x42, 0x73, 0x0a, - 0x1f, 0x63, 0x6f, 0x6d, 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, 0x74, 0x6c, 0x73, - 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, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, - 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, - 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x12, 0x20, 0x0a, + 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x42, + 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 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, 0x74, + 0x6c, 0x73, 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, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index bd2bbdb8..7cae8436 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -64,4 +64,7 @@ message Config { // Whether the server selects its most preferred ciphersuite. bool prefer_server_cipher_suites = 10; + + // TLS Client Hello fingerprint (uTLS). + string fingerprint = 11; } diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index 5704214b..b33eaf69 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -3,6 +3,8 @@ package tls import ( "crypto/tls" + utls "github.com/refraction-networking/utls" + "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" ) @@ -41,25 +43,43 @@ func Client(c net.Conn, config *tls.Config) net.Conn { return &Conn{Conn: tlsConn} } -/* -func copyConfig(c *tls.Config) *utls.Config { - return &utls.Config{ - NextProtos: c.NextProtos, - ServerName: c.ServerName, - InsecureSkipVerify: c.InsecureSkipVerify, - MinVersion: utls.VersionTLS12, - MaxVersion: utls.VersionTLS12, - } -} - -func UClient(c net.Conn, config *tls.Config) net.Conn { - uConfig := copyConfig(config) - return utls.Client(c, uConfig) -} -*/ - // Server initiates a TLS server handshake on the given connection. func Server(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Server(c, config) return &Conn{Conn: tlsConn} } + +type UConn struct { + *utls.UConn +} + +func (c *UConn) HandshakeAddress() net.Address { + if err := c.Handshake(); err != nil { + return nil + } + state := c.ConnectionState() + if state.ServerName == "" { + return nil + } + return net.ParseAddress(state.ServerName) +} + +func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn { + utlsConn := utls.UClient(c, copyConfig(config), *fingerprint) + return &UConn{UConn: utlsConn} +} + +func copyConfig(c *tls.Config) *utls.Config { + return &utls.Config{ + RootCAs: c.RootCAs, + ServerName: c.ServerName, + InsecureSkipVerify: c.InsecureSkipVerify, + } +} + +var Fingerprints = map[string]*utls.ClientHelloID{ + "chrome": &utls.HelloChrome_Auto, + "firefox": &utls.HelloFirefox_Auto, + "safari": &utls.HelloIOS_Auto, + "randomized": &utls.HelloRandomized, +} diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index 849beb5e..3dc56cc7 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -2,8 +2,12 @@ package websocket import ( "context" + _ "embed" "encoding/base64" + "fmt" "io" + "net/http" + "os" "time" "github.com/gorilla/websocket" @@ -15,6 +19,27 @@ import ( "github.com/xtls/xray-core/transport/internet/tls" ) +//go:embed dialer.html +var webpage []byte +var conns chan *websocket.Conn + +func init() { + if addr := os.Getenv("XRAY_BROWSER_DIALER"); addr != "" { + conns = make(chan *websocket.Conn, 256) + go http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/websocket" { + if conn, err := upgrader.Upgrade(w, r, nil); err == nil { + conns <- conn + } else { + fmt.Println("unexpected error") + } + } else { + w.Write(webpage) + } + })) + } +} + // Dial dials a WebSocket connection to the given destination. func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx)) @@ -66,6 +91,30 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in } uri := protocol + "://" + host + wsSettings.GetNormalizedPath() + if conns != nil { + data := []byte(uri) + if ed != nil { + data = append(data, " "+base64.RawURLEncoding.EncodeToString(ed)...) + } + var conn *websocket.Conn + for { + conn = <-conns + if conn.WriteMessage(websocket.TextMessage, data) != nil { + conn.Close() + } else { + break + } + } + if _, p, err := conn.ReadMessage(); err != nil { + conn.Close() + return nil, err + } else if s := string(p); s != "ok" { + conn.Close() + return nil, newError(s) + } + return newConnection(conn, conn.RemoteAddr(), nil), nil + } + header := wsSettings.GetRequestHeader() if ed != nil { header.Set("Sec-WebSocket-Protocol", base64.StdEncoding.EncodeToString(ed)) diff --git a/transport/internet/websocket/dialer.html b/transport/internet/websocket/dialer.html new file mode 100644 index 00000000..c141379d --- /dev/null +++ b/transport/internet/websocket/dialer.html @@ -0,0 +1,55 @@ + + + + Browser Dialer + + + + + diff --git a/transport/internet/websocket/hub.go b/transport/internet/websocket/hub.go index 0fd84a52..06ca2d28 100644 --- a/transport/internet/websocket/hub.go +++ b/transport/internet/websocket/hub.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "io" "net/http" + "strings" "sync" "time" @@ -25,6 +26,8 @@ type requestHandler struct { ln *Listener } +var replacer = strings.NewReplacer("+", "-", "/", "_", "=", "") + var upgrader = &websocket.Upgrader{ ReadBufferSize: 4 * 1024, WriteBufferSize: 4 * 1024, @@ -39,7 +42,17 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req writer.WriteHeader(http.StatusNotFound) return } - conn, err := upgrader.Upgrade(writer, request, nil) + + var extraReader io.Reader + var responseHeader = http.Header{} + if str := request.Header.Get("Sec-WebSocket-Protocol"); str != "" { + if ed, err := base64.RawURLEncoding.DecodeString(replacer.Replace(str)); err == nil && len(ed) > 0 { + extraReader = bytes.NewReader(ed) + responseHeader.Set("Sec-WebSocket-Protocol", str) + } + } + + conn, err := upgrader.Upgrade(writer, request, responseHeader) if err != nil { newError("failed to convert to WebSocket connection").Base(err).WriteToLog() return @@ -54,12 +67,6 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } } - var extraReader io.Reader - if str := request.Header.Get("Sec-WebSocket-Protocol"); str != "" { - if ed, err := base64.StdEncoding.DecodeString(str); err == nil && len(ed) > 0 { - extraReader = bytes.NewReader(ed) - } - } h.ln.addConn(newConnection(conn, remoteAddr, extraReader)) } @@ -82,7 +89,8 @@ func ListenWS(ctx context.Context, address net.Address, port net.Port, streamSet if streamSettings.SocketSettings == nil { streamSettings.SocketSettings = &internet.SocketConfig{} } - streamSettings.SocketSettings.AcceptProxyProtocol = l.config.AcceptProxyProtocol + streamSettings.SocketSettings.AcceptProxyProtocol = + l.config.AcceptProxyProtocol || streamSettings.SocketSettings.AcceptProxyProtocol } var listener net.Listener var err error @@ -128,7 +136,7 @@ func ListenWS(ctx context.Context, address net.Address, port net.Port, streamSet ln: l, }, ReadHeaderTimeout: time.Second * 4, - MaxHeaderBytes: 2048, + MaxHeaderBytes: 4096, } go func() {