From 38da831b7550e88bb4e5044ca9856d22a556ac94 Mon Sep 17 00:00:00 2001 From: Xiaokang Wang Date: Mon, 8 Feb 2021 10:18:52 +0000 Subject: [PATCH] Feature: Fake DNS support (#406) * Add fake dns A new config object "fake" in DnsObject for toggling fake dns function Compare with sniffing, fake dns is not limited to http and tls traffic. It works across all inbounds. For example, when dns request come from one inbound, the local DNS server of v2ray will response with a unique fake IP for every unique domain name. Then later on v2ray received a request to one of the fake IP from any inbounds, it will override the request destination with the previously saved domain. By default, v2ray cache up to 65535 addresses. The old records will be discarded bases on LRU. The fake IP will be 240.x.x.x * fix an edge case when encounter a fake IP in use * Move lru to common.cache package * Added the necessary change to obtain request IP from sniffer * Refactor the code so that it may stop depending on global variables in the future. * Replace string manipulation code with more generic codes, hopefully this will work for both IPv4 and IPv6 networks. * Try to use IPv4 version of address if possible * Added Test Case for Fake Dns * Added More Test Case for Fake Dns * Stop user from creating a instance with LRU size more than subnet size, it will create a infinite loop * Move Fake DNS to a separate package * Generated Code for fakedns * Encapsulate Fake DNS as a Instance wide service * Added Support for metadata sniffer, which will be used for Fake DNS * Dependency injection for fake dns * Fake DNS As a Sniffer * Remove stub object * Remove global variable * Update generated protobuf file for metadata only sniffing * Apply Fake DNS config to session * Loading for fake dns settings * Bug fix * Include fake dns in all * Fix FakeDns Lint Condition * Fix sniffer config * Fix lint message * Fix dependency resolution * Fix fake dns not loaded as sniffer * reduce ttl for fake dns * Apply Coding Style * Apply Coding Style * Apply Coding Style * Apply Coding Style * Apply Coding Style * Fix crashed when no fake dns * Apply Coding Style * Fix Fake DNS do not apply to UDP socket * Fixed a bug prevent FakeDNS App Setting from become effective * Fixed a caveat prevent FakeDNS App Setting from become effective * Use log comparison to reduce in issue when it comes to really high value typical for ipv6 subnet * Add build tag for fakedns * Removal of FakeDNS specific logic at DNS client: making it a standard dns client * Regenerate auto generated file * Amended version of configure file * Bug fixes for fakeDNS * Bug fixes for fakeDNS * Fix test: remove reference to removed attribute * Test: fix codacy issue * Conf: Remove old field support * Test: fix codacy issue * Change test scale for TestFakeDnsHolderCreateMappingAndRollOver * Test: fix codacy issue Co-authored-by: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Co-authored-by: loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Co-authored-by: kslr --- app/dispatcher/default.go | 82 ++++++--- app/dispatcher/fakednssniffer.go | 51 ++++++ app/dispatcher/sniffer.go | 98 +++++++++-- app/dns/config.proto | 2 + app/dns/dns.go | 11 +- app/dns/fakedns/errors.generated.go | 9 + app/dns/fakedns/fake.go | 132 +++++++++++++++ app/dns/fakedns/fakedns.go | 5 + app/dns/fakedns/fakedns.pb.go | 164 ++++++++++++++++++ app/dns/fakedns/fakedns.proto | 12 ++ app/dns/fakedns/fakedns_test.go | 114 +++++++++++++ app/dns/nameserver.go | 2 + app/dns/nameserver_fakedns.go | 41 +++++ app/proxyman/config.pb.go | 194 ++++++++++++---------- app/proxyman/config.proto | 6 +- app/proxyman/inbound/always.go | 2 + app/proxyman/inbound/dynamic.go | 2 + app/proxyman/inbound/worker.go | 14 +- common/cache/lru.go | 76 +++++++++ common/cache/lru_test.go | 69 ++++++++ common/session/session.go | 1 + features/dns/fakedns.go | 12 ++ features/dns/localdns/errors.generated.go | 9 + infra/conf/fakedns.go | 65 ++++++++ infra/conf/init.go | 5 + infra/conf/lint.go | 23 +++ infra/conf/v2ray.go | 21 +++ main/distro/all/all.go | 1 + proxy/dns/dns.go | 6 +- 29 files changed, 1092 insertions(+), 137 deletions(-) create mode 100644 app/dispatcher/fakednssniffer.go create mode 100644 app/dns/fakedns/errors.generated.go create mode 100644 app/dns/fakedns/fake.go create mode 100644 app/dns/fakedns/fakedns.go create mode 100644 app/dns/fakedns/fakedns.pb.go create mode 100644 app/dns/fakedns/fakedns.proto create mode 100644 app/dns/fakedns/fakedns_test.go create mode 100644 app/dns/nameserver_fakedns.go create mode 100644 common/cache/lru.go create mode 100644 common/cache/lru_test.go create mode 100644 features/dns/fakedns.go create mode 100644 features/dns/localdns/errors.generated.go create mode 100644 infra/conf/fakedns.go create mode 100644 infra/conf/init.go create mode 100644 infra/conf/lint.go diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index 1531866f4..c4629cc4f 100644 --- a/app/dispatcher/default.go +++ b/app/dispatcher/default.go @@ -178,8 +178,12 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran } func shouldOverride(result SniffResult, domainOverride []string) bool { + protocolString := result.Protocol() + if resComp, ok := result.(SnifferResultComposite); ok { + protocolString = resComp.ProtocolForDomainResult() + } for _, p := range domainOverride { - if strings.HasPrefix(result.Protocol(), p) { + if strings.HasPrefix(protocolString, p) { return true } } @@ -203,15 +207,29 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin ctx = session.ContextWithContent(ctx, content) } sniffingRequest := content.SniffingRequest - if destination.Network != net.Network_TCP || !sniffingRequest.Enabled { + switch { + case !sniffingRequest.Enabled: go d.routedDispatch(ctx, outbound, destination) - } else { + case destination.Network != net.Network_TCP: + // Only metadata sniff will be used for non tcp connection + result, err := sniffer(ctx, nil, true) + if err == nil { + content.Protocol = result.Protocol() + if shouldOverride(result, sniffingRequest.OverrideDestinationForProtocol) { + domain := result.Domain() + newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) + destination.Address = net.ParseAddress(domain) + ob.Target = destination + } + } + go d.routedDispatch(ctx, outbound, destination) + default: go func() { cReader := &cachedReader{ reader: outbound.Reader.(*pipe.Reader), } outbound.Reader = cReader - result, err := sniffer(ctx, cReader) + result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly) if err == nil { content.Protocol = result.Protocol() } @@ -227,34 +245,50 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin return inbound, nil } -func sniffer(ctx context.Context, cReader *cachedReader) (SniffResult, error) { +func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (SniffResult, error) { payload := buf.New() defer payload.Release() - sniffer := NewSniffer() - totalAttempt := 0 - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - totalAttempt++ - if totalAttempt > 2 { - return nil, errSniffingTimeout - } + sniffer := NewSniffer(ctx) - cReader.Cache(payload) - if !payload.IsEmpty() { - result, err := sniffer.Sniff(payload.Bytes()) - if err != common.ErrNoClue { - return result, err + metaresult, metadataErr := sniffer.SniffMetadata(ctx) + + if metadataOnly { + return metaresult, metadataErr + } + + contentResult, contentErr := func() (SniffResult, error) { + totalAttempt := 0 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + totalAttempt++ + if totalAttempt > 2 { + return nil, errSniffingTimeout + } + + cReader.Cache(payload) + if !payload.IsEmpty() { + result, err := sniffer.Sniff(ctx, payload.Bytes()) + if err != common.ErrNoClue { + return result, err + } + } + if payload.IsFull() { + return nil, errUnknownContent } } - if payload.IsFull() { - return nil, errUnknownContent - } } + }() + if contentErr != nil && metadataErr == nil { + return metaresult, nil } + if contentErr == nil && metadataErr == nil { + return CompositeResult(metaresult, contentResult), nil + } + return contentResult, contentErr } func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) { diff --git a/app/dispatcher/fakednssniffer.go b/app/dispatcher/fakednssniffer.go new file mode 100644 index 000000000..cab292551 --- /dev/null +++ b/app/dispatcher/fakednssniffer.go @@ -0,0 +1,51 @@ +// +build !confonly + +package dispatcher + +import ( + "context" + + "v2ray.com/core" + "v2ray.com/core/common" + "v2ray.com/core/common/net" + "v2ray.com/core/common/session" + "v2ray.com/core/features/dns" +) + +// newFakeDNSSniffer Create a Fake DNS metadata sniffer +func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) { + var fakeDNSEngine dns.FakeDNSEngine + err := core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) { + fakeDNSEngine = fdns + }) + if err != nil { + return protocolSnifferWithMetadata{}, err + } + if fakeDNSEngine == nil { + errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError() + return protocolSnifferWithMetadata{}, errNotInit + } + return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) { + Target := session.OutboundFromContext(ctx).Target + if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP { + domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address) + if domainFromFakeDNS != "" { + newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", Target.Address.String()).WriteToLog(session.ExportIDToError(ctx)) + return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil + } + } + return nil, common.ErrNoClue + }, metadataSniffer: true}, nil +} + +type fakeDNSSniffResult struct { + domainName string +} + +func (fakeDNSSniffResult) Protocol() string { + return "fakedns" +} + +func (f fakeDNSSniffResult) Domain() string { + return f.domainName +} diff --git a/app/dispatcher/sniffer.go b/app/dispatcher/sniffer.go index 34851ab8b..613dc8eba 100644 --- a/app/dispatcher/sniffer.go +++ b/app/dispatcher/sniffer.go @@ -3,6 +3,8 @@ package dispatcher import ( + "context" + "v2ray.com/core/common" "v2ray.com/core/common/protocol/bittorrent" "v2ray.com/core/common/protocol/http" @@ -14,30 +16,46 @@ type SniffResult interface { Domain() string } -type protocolSniffer func([]byte) (SniffResult, error) +type protocolSniffer func(context.Context, []byte) (SniffResult, error) -type Sniffer struct { - sniffer []protocolSniffer +type protocolSnifferWithMetadata struct { + protocolSniffer protocolSniffer + // A Metadata sniffer will be invoked on connection establishment only, with nil body, + // for both TCP and UDP connections + // It will not be shown as a traffic type for routing unless there is no other successful sniffing. + metadataSniffer bool } -func NewSniffer() *Sniffer { - return &Sniffer{ - sniffer: []protocolSniffer{ - func(b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, - func(b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, - func(b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, +type Sniffer struct { + sniffer []protocolSnifferWithMetadata +} + +func NewSniffer(ctx context.Context) *Sniffer { + ret := &Sniffer{ + sniffer: []protocolSnifferWithMetadata{ + {func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false}, + {func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false}, + {func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false}, }, } + if sniffer, err := newFakeDNSSniffer(ctx); err == nil { + ret.sniffer = append(ret.sniffer, sniffer) + } + return ret } var errUnknownContent = newError("unknown content") -func (s *Sniffer) Sniff(payload []byte) (SniffResult, error) { - var pendingSniffer []protocolSniffer - for _, s := range s.sniffer { - result, err := s(payload) +func (s *Sniffer) Sniff(c context.Context, payload []byte) (SniffResult, error) { + var pendingSniffer []protocolSnifferWithMetadata + for _, si := range s.sniffer { + s := si.protocolSniffer + if si.metadataSniffer { + continue + } + result, err := s(c, payload) if err == common.ErrNoClue { - pendingSniffer = append(pendingSniffer, s) + pendingSniffer = append(pendingSniffer, si) continue } @@ -53,3 +71,55 @@ func (s *Sniffer) Sniff(payload []byte) (SniffResult, error) { return nil, errUnknownContent } + +func (s *Sniffer) SniffMetadata(c context.Context) (SniffResult, error) { + var pendingSniffer []protocolSnifferWithMetadata + for _, si := range s.sniffer { + s := si.protocolSniffer + if !si.metadataSniffer { + pendingSniffer = append(pendingSniffer, si) + continue + } + result, err := s(c, nil) + if err == common.ErrNoClue { + pendingSniffer = append(pendingSniffer, si) + continue + } + + if err == nil && result != nil { + return result, nil + } + } + + if len(pendingSniffer) > 0 { + s.sniffer = pendingSniffer + return nil, common.ErrNoClue + } + + return nil, errUnknownContent +} + +func CompositeResult(domainResult SniffResult, protocolResult SniffResult) SniffResult { + return &compositeResult{domainResult: domainResult, protocolResult: protocolResult} +} + +type compositeResult struct { + domainResult SniffResult + protocolResult SniffResult +} + +func (c compositeResult) Protocol() string { + return c.protocolResult.Protocol() +} + +func (c compositeResult) Domain() string { + return c.domainResult.Domain() +} + +func (c compositeResult) ProtocolForDomainResult() string { + return c.domainResult.Protocol() +} + +type SnifferResultComposite interface { + ProtocolForDomainResult() string +} diff --git a/app/dns/config.proto b/app/dns/config.proto index cdce754aa..cba15eec4 100644 --- a/app/dns/config.proto +++ b/app/dns/config.proto @@ -69,4 +69,6 @@ message Config { // Tag is the inbound tag of DNS client. string tag = 6; + + reserved 7; } diff --git a/app/dns/dns.go b/app/dns/dns.go index 0e135a1f2..8c0c5dfe2 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -23,10 +23,10 @@ import ( // DNS is a DNS rely server. type DNS struct { sync.Mutex - tag string - hosts *StaticHosts - clients []*Client - + tag string + hosts *StaticHosts + clients []*Client + ctx context.Context domainMatcher strmatcher.IndexMatcher matcherInfos []DomainMatcherInfo } @@ -113,6 +113,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) { tag: tag, hosts: hosts, clients: clients, + ctx: ctx, domainMatcher: domainMatcher, matcherInfos: matcherInfos, }, nil @@ -189,7 +190,7 @@ func (s *DNS) lookupIPInternal(domain string, option IPOption) ([]net.IP, error) // Name servers lookup errs := []error{} - ctx := session.ContextWithInbound(context.Background(), &session.Inbound{Tag: s.tag}) + ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag}) for _, client := range s.sortClients(domain) { ips, err := client.QueryIP(ctx, domain, option) if len(ips) > 0 { diff --git a/app/dns/fakedns/errors.generated.go b/app/dns/fakedns/errors.generated.go new file mode 100644 index 000000000..c8ad60eda --- /dev/null +++ b/app/dns/fakedns/errors.generated.go @@ -0,0 +1,9 @@ +package fakedns + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/app/dns/fakedns/fake.go b/app/dns/fakedns/fake.go new file mode 100644 index 000000000..22ff3ceb1 --- /dev/null +++ b/app/dns/fakedns/fake.go @@ -0,0 +1,132 @@ +// +build !confonly + +package fakedns + +import ( + "context" + "math" + "math/big" + gonet "net" + + "v2ray.com/core/common" + "v2ray.com/core/common/cache" + "v2ray.com/core/common/net" + "v2ray.com/core/features/dns" +) + +type Holder struct { + domainToIP cache.Lru + nextIP *big.Int + + ipRange *gonet.IPNet + + config *FakeDnsPool +} + +func (*Holder) Type() interface{} { + return (*dns.FakeDNSEngine)(nil) +} + +func (fkdns *Holder) Start() error { + return fkdns.initializeFromConfig() +} + +func (fkdns *Holder) Close() error { + fkdns.domainToIP = nil + fkdns.nextIP = nil + fkdns.ipRange = nil + return nil +} + +func NewFakeDNSHolder() (*Holder, error) { + var fkdns *Holder + var err error + + if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil { + return nil, newError("Unable to create Fake Dns Engine").Base(err).AtError() + } + err = fkdns.initialize("240.0.0.0/8", 65535) + if err != nil { + return nil, err + } + return fkdns, nil +} + +func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) { + return &Holder{nil, nil, nil, conf}, nil +} + +func (fkdns *Holder) initializeFromConfig() error { + return fkdns.initialize(fkdns.config.IpPool, int(fkdns.config.LruSize)) +} + +func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error { + var ipRange *gonet.IPNet + var ipaddr gonet.IP + var currentIP *big.Int + var err error + + if ipaddr, ipRange, err = gonet.ParseCIDR(ipPoolCidr); err != nil { + return newError("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError() + } + + currentIP = big.NewInt(0).SetBytes(ipaddr) + if ipaddr.To4() != nil { + currentIP = big.NewInt(0).SetBytes(ipaddr.To4()) + } + + ones, bits := ipRange.Mask.Size() + rooms := bits - ones + if math.Log2(float64(lruSize)) >= float64(rooms) { + return newError("LRU size is bigger than subnet size").AtError() + } + fkdns.domainToIP = cache.NewLru(lruSize) + fkdns.ipRange = ipRange + fkdns.nextIP = currentIP + return nil +} + +// GetFakeIPForDomain check and generate a fake IP for a domain name +func (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address { + if v, ok := fkdns.domainToIP.Get(domain); ok { + return []net.Address{v.(net.Address)} + } + var ip net.Address + for { + ip = net.IPAddress(fkdns.nextIP.Bytes()) + + fkdns.nextIP = fkdns.nextIP.Add(fkdns.nextIP, big.NewInt(1)) + if !fkdns.ipRange.Contains(fkdns.nextIP.Bytes()) { + fkdns.nextIP = big.NewInt(0).SetBytes(fkdns.ipRange.IP) + } + + // if we run for a long time, we may go back to beginning and start seeing the IP in use + if _, ok := fkdns.domainToIP.GetKeyFromValue(ip); !ok { + break + } + } + fkdns.domainToIP.Put(domain, ip) + return []net.Address{ip} +} + +// GetDomainFromFakeDNS check if an IP is a fake IP and have corresponding domain name +func (fkdns *Holder) GetDomainFromFakeDNS(ip net.Address) string { + if !ip.Family().IsIP() || !fkdns.ipRange.Contains(ip.IP()) { + return "" + } + if k, ok := fkdns.domainToIP.GetKeyFromValue(ip); ok { + return k.(string) + } + return "" +} + +func init() { + common.Must(common.RegisterConfig((*FakeDnsPool)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + var f *Holder + var err error + if f, err = NewFakeDNSHolderConfigOnly(config.(*FakeDnsPool)); err != nil { + return nil, err + } + return f, nil + })) +} diff --git a/app/dns/fakedns/fakedns.go b/app/dns/fakedns/fakedns.go new file mode 100644 index 000000000..0e55e5bd5 --- /dev/null +++ b/app/dns/fakedns/fakedns.go @@ -0,0 +1,5 @@ +// +build !confonly + +package fakedns + +//go:generate go run v2ray.com/core/common/errors/errorgen diff --git a/app/dns/fakedns/fakedns.pb.go b/app/dns/fakedns/fakedns.pb.go new file mode 100644 index 000000000..1ed892ef9 --- /dev/null +++ b/app/dns/fakedns/fakedns.pb.go @@ -0,0 +1,164 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.13.0 +// source: app/dns/fakedns/fakedns.proto + +package fakedns + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type FakeDnsPool struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IpPool string `protobuf:"bytes,1,opt,name=ip_pool,json=ipPool,proto3" json:"ip_pool,omitempty"` //CIDR of IP pool used as fake DNS IP + LruSize int64 `protobuf:"varint,2,opt,name=lruSize,proto3" json:"lruSize,omitempty"` //Size of Pool for remembering relationship between domain name and IP address +} + +func (x *FakeDnsPool) Reset() { + *x = FakeDnsPool{} + if protoimpl.UnsafeEnabled { + mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FakeDnsPool) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FakeDnsPool) ProtoMessage() {} + +func (x *FakeDnsPool) ProtoReflect() protoreflect.Message { + mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FakeDnsPool.ProtoReflect.Descriptor instead. +func (*FakeDnsPool) Descriptor() ([]byte, []int) { + return file_app_dns_fakedns_fakedns_proto_rawDescGZIP(), []int{0} +} + +func (x *FakeDnsPool) GetIpPool() string { + if x != nil { + return x.IpPool + } + return "" +} + +func (x *FakeDnsPool) GetLruSize() int64 { + if x != nil { + return x.LruSize + } + return 0 +} + +var File_app_dns_fakedns_fakedns_proto protoreflect.FileDescriptor + +var file_app_dns_fakedns_fakedns_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, + 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, + 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x0b, 0x46, + 0x61, 0x6b, 0x65, 0x44, 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, + 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x50, + 0x6f, 0x6f, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x5f, 0x0a, + 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x50, + 0x01, 0x5a, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, + 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, + 0x73, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, + 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x2e, 0x46, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_app_dns_fakedns_fakedns_proto_rawDescOnce sync.Once + file_app_dns_fakedns_fakedns_proto_rawDescData = file_app_dns_fakedns_fakedns_proto_rawDesc +) + +func file_app_dns_fakedns_fakedns_proto_rawDescGZIP() []byte { + file_app_dns_fakedns_fakedns_proto_rawDescOnce.Do(func() { + file_app_dns_fakedns_fakedns_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_dns_fakedns_fakedns_proto_rawDescData) + }) + return file_app_dns_fakedns_fakedns_proto_rawDescData +} + +var file_app_dns_fakedns_fakedns_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_app_dns_fakedns_fakedns_proto_goTypes = []interface{}{ + (*FakeDnsPool)(nil), // 0: v2ray.core.app.dns.fakedns.FakeDnsPool +} +var file_app_dns_fakedns_fakedns_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_app_dns_fakedns_fakedns_proto_init() } +func file_app_dns_fakedns_fakedns_proto_init() { + if File_app_dns_fakedns_fakedns_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_app_dns_fakedns_fakedns_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FakeDnsPool); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_app_dns_fakedns_fakedns_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_app_dns_fakedns_fakedns_proto_goTypes, + DependencyIndexes: file_app_dns_fakedns_fakedns_proto_depIdxs, + MessageInfos: file_app_dns_fakedns_fakedns_proto_msgTypes, + }.Build() + File_app_dns_fakedns_fakedns_proto = out.File + file_app_dns_fakedns_fakedns_proto_rawDesc = nil + file_app_dns_fakedns_fakedns_proto_goTypes = nil + file_app_dns_fakedns_fakedns_proto_depIdxs = nil +} diff --git a/app/dns/fakedns/fakedns.proto b/app/dns/fakedns/fakedns.proto new file mode 100644 index 000000000..9f64b3863 --- /dev/null +++ b/app/dns/fakedns/fakedns.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package v2ray.core.app.dns.fakedns; +option csharp_namespace = "V2Ray.Core.App.Dns.Fakedns"; +option go_package = "v2ray.com/core/app/dns/fakedns"; +option java_package = "com.v2ray.core.app.dns.fakedns"; +option java_multiple_files = true; + +message FakeDnsPool{ + string ip_pool = 1; //CIDR of IP pool used as fake DNS IP + int64 lruSize = 2; //Size of Pool for remembering relationship between domain name and IP address +} \ No newline at end of file diff --git a/app/dns/fakedns/fakedns_test.go b/app/dns/fakedns/fakedns_test.go new file mode 100644 index 000000000..8484b0c2b --- /dev/null +++ b/app/dns/fakedns/fakedns_test.go @@ -0,0 +1,114 @@ +package fakedns + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "v2ray.com/core/common" + "v2ray.com/core/common/net" + "v2ray.com/core/common/uuid" +) + +func TestNewFakeDnsHolder(_ *testing.T) { + _, err := NewFakeDNSHolder() + common.Must(err) +} + +func TestFakeDnsHolderCreateMapping(t *testing.T) { + fkdns, err := NewFakeDNSHolder() + common.Must(err) + + addr := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + assert.Equal(t, "240.0.0.0", addr[0].IP().String()) +} + +func TestFakeDnsHolderCreateMappingMany(t *testing.T) { + fkdns, err := NewFakeDNSHolder() + common.Must(err) + + addr := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + assert.Equal(t, "240.0.0.0", addr[0].IP().String()) + + addr2 := fkdns.GetFakeIPForDomain("fakednstest2.v2fly.org") + assert.Equal(t, "240.0.0.1", addr2[0].IP().String()) +} + +func TestFakeDnsHolderCreateMappingManyAndResolve(t *testing.T) { + fkdns, err := NewFakeDNSHolder() + common.Must(err) + + { + addr := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + assert.Equal(t, "240.0.0.0", addr[0].IP().String()) + } + + { + addr2 := fkdns.GetFakeIPForDomain("fakednstest2.v2fly.org") + assert.Equal(t, "240.0.0.1", addr2[0].IP().String()) + } + + { + result := fkdns.GetDomainFromFakeDNS(net.ParseAddress("240.0.0.0")) + assert.Equal(t, "fakednstest.v2fly.org", result) + } + + { + result := fkdns.GetDomainFromFakeDNS(net.ParseAddress("240.0.0.1")) + assert.Equal(t, "fakednstest2.v2fly.org", result) + } +} + +func TestFakeDnsHolderCreateMappingManySingleDomain(t *testing.T) { + fkdns, err := NewFakeDNSHolder() + common.Must(err) + + addr := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + assert.Equal(t, "240.0.0.0", addr[0].IP().String()) + + addr2 := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + assert.Equal(t, "240.0.0.0", addr2[0].IP().String()) +} + +func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) { + fkdns, err := NewFakeDNSHolderConfigOnly(&FakeDnsPool{ + IpPool: "240.0.0.0/12", + LruSize: 256, + }) + common.Must(err) + + err = fkdns.Start() + + common.Must(err) + + { + addr := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + assert.Equal(t, "240.0.0.0", addr[0].IP().String()) + } + + { + addr2 := fkdns.GetFakeIPForDomain("fakednstest2.v2fly.org") + assert.Equal(t, "240.0.0.1", addr2[0].IP().String()) + } + + for i := 0; i <= 8192; i++ { + { + result := fkdns.GetDomainFromFakeDNS(net.ParseAddress("240.0.0.0")) + assert.Equal(t, "fakednstest.v2fly.org", result) + } + + { + result := fkdns.GetDomainFromFakeDNS(net.ParseAddress("240.0.0.1")) + assert.Equal(t, "fakednstest2.v2fly.org", result) + } + + { + uuid := uuid.New() + domain := uuid.String() + ".fakednstest.v2fly.org" + addr := fkdns.GetFakeIPForDomain(domain) + rsaddr := addr[0].IP().String() + + result := fkdns.GetDomainFromFakeDNS(net.ParseAddress(rsaddr)) + assert.Equal(t, domain, result) + } + } +} diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go index bbc1bdd1b..22aab78e5 100644 --- a/app/dns/nameserver.go +++ b/app/dns/nameserver.go @@ -55,6 +55,8 @@ func NewServer(dest net.Destination, dispatcher routing.Dispatcher) (Server, err return NewDoHLocalNameServer(u), nil case u.Scheme == "quic+local": // DNS-over-QUIC Local mode return NewQUICNameServer(u) + case u.String() == "fakedns": + return NewFakeDNSServer(), nil } } if dest.Network == net.Network_Unknown { diff --git a/app/dns/nameserver_fakedns.go b/app/dns/nameserver_fakedns.go new file mode 100644 index 000000000..9fa01b4b1 --- /dev/null +++ b/app/dns/nameserver_fakedns.go @@ -0,0 +1,41 @@ +// +build !confonly + +package dns + +import ( + "context" + + "v2ray.com/core" + "v2ray.com/core/common/net" + "v2ray.com/core/features/dns" +) + +type FakeDNSServer struct { + fakeDNSEngine dns.FakeDNSEngine +} + +func NewFakeDNSServer() *FakeDNSServer { + return &FakeDNSServer{} +} + +func (FakeDNSServer) Name() string { + return "FakeDNS" +} + +func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, _ IPOption) ([]net.IP, error) { + if f.fakeDNSEngine == nil { + if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) { + f.fakeDNSEngine = fd + }); err != nil { + return nil, newError("Unable to locate a fake DNS Engine").Base(err).AtError() + } + } + ips := f.fakeDNSEngine.GetFakeIPForDomain(domain) + + netIP, err := toNetIP(ips) + if err != nil { + return nil, newError("Unable to convert IP to net ip").Base(err).AtError() + } + + return netIP, nil +} diff --git a/app/proxyman/config.pb.go b/app/proxyman/config.pb.go index ce670a283..476e43afd 100644 --- a/app/proxyman/config.pb.go +++ b/app/proxyman/config.pb.go @@ -239,8 +239,11 @@ type SniffingConfig struct { // Whether or not to enable content sniffing on an inbound connection. Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` // Override target destination if sniff'ed protocol is in the given list. - // Supported values are "http", "tls". + // Supported values are "http", "tls", "fakedns". DestinationOverride []string `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"` + // Whether should only try to sniff metadata without waiting for client input. + // Can be used to support SMTP like protocol where server send the first message. + MetadataOnly bool `protobuf:"varint,3,opt,name=metadata_only,json=metadataOnly,proto3" json:"metadata_only,omitempty"` } func (x *SniffingConfig) Reset() { @@ -289,6 +292,13 @@ func (x *SniffingConfig) GetDestinationOverride() []string { return nil } +func (x *SniffingConfig) GetMetadataOnly() bool { + if x != nil { + return x.MetadataOnly + } + return false +} + type ReceiverConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -757,97 +767,99 @@ var file_app_proxyman_config_proto_rawDesc = []byte{ 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x10, 0x01, 0x12, - 0x0c, 0x0a, 0x08, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0x5d, 0x0a, - 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, - 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x22, 0xb4, 0x04, 0x0a, - 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x3f, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, - 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, - 0x12, 0x39, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x52, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x12, 0x5c, 0x0a, 0x13, 0x61, - 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, - 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, - 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, - 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x54, 0x0a, 0x0f, 0x73, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x40, 0x0a, 0x1c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, - 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x54, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, - 0x72, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, - 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, - 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6c, 0x73, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4f, - 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x54, 0x0a, 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, - 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x6e, 0x69, - 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x6e, 0x69, - 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4a, 0x04, 0x08, - 0x06, 0x10, 0x07, 0x22, 0xcc, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, - 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, - 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x53, - 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, - 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x12, 0x4d, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, - 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x22, 0xc8, 0x02, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x03, 0x76, 0x69, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61, 0x12, 0x54, 0x0a, 0x0f, 0x73, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x12, 0x51, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x0c, 0x0a, 0x08, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0x82, 0x01, + 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4f, 0x6e, + 0x6c, 0x79, 0x22, 0xb4, 0x04, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, + 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, + 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, + 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, + 0x6e, 0x12, 0x5c, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, + 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, + 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c, 0x6c, + 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, + 0x54, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x12, 0x5a, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, - 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, - 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, - 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75, - 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, - 0x50, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, - 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, - 0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x56, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, - 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x1b, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, - 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x17, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, - 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, + 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, + 0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, + 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x54, 0x0a, + 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, + 0x61, 0x6e, 0x2e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x10, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xcc, 0x01, 0x0a, 0x14, 0x49, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x74, 0x61, 0x67, 0x12, 0x53, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, + 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4d, 0x0a, 0x0e, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xc8, 0x02, 0x0a, 0x0c, 0x53, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x03, 0x76, + 0x69, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, + 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61, + 0x12, 0x54, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, + 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x51, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, + 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, + 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x50, + 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, + 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x5a, 0x0a, 0x12, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, + 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x50, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, + 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, + 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x56, 0x0a, 0x1b, + 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, + 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x1b, 0x76, + 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, + 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x17, 0x56, 0x32, 0x52, + 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, + 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/proxyman/config.proto b/app/proxyman/config.proto index 7f4b7b8d2..ca9e625c0 100644 --- a/app/proxyman/config.proto +++ b/app/proxyman/config.proto @@ -54,8 +54,12 @@ message SniffingConfig { bool enabled = 1; // Override target destination if sniff'ed protocol is in the given list. - // Supported values are "http", "tls". + // Supported values are "http", "tls", "fakedns". repeated string destination_override = 2; + + // Whether should only try to sniff metadata without waiting for client input. + // Can be used to support SMTP like protocol where server send the first message. + bool metadata_only = 3; } message ReceiverConfig { diff --git a/app/proxyman/inbound/always.go b/app/proxyman/inbound/always.go index f44747bd9..33014416c 100644 --- a/app/proxyman/inbound/always.go +++ b/app/proxyman/inbound/always.go @@ -128,11 +128,13 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig * if net.HasNetwork(nl, net.Network_UDP) { worker := &udpWorker{ + ctx: ctx, tag: tag, proxy: p, address: address, port: net.Port(port), dispatcher: h.mux, + sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(), uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter, stream: mss, diff --git a/app/proxyman/inbound/dynamic.go b/app/proxyman/inbound/dynamic.go index 3dbd89abe..f20920699 100644 --- a/app/proxyman/inbound/dynamic.go +++ b/app/proxyman/inbound/dynamic.go @@ -148,11 +148,13 @@ func (h *DynamicInboundHandler) refresh() error { if net.HasNetwork(nl, net.Network_UDP) { worker := &udpWorker{ + ctx: h.ctx, tag: h.tag, proxy: p, address: address, port: port, dispatcher: h.mux, + sniffingConfig: h.receiverConfig.GetEffectiveSniffingSettings(), uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter, stream: h.streamSettings, diff --git a/app/proxyman/inbound/worker.go b/app/proxyman/inbound/worker.go index 16e15aa8f..38f86910d 100644 --- a/app/proxyman/inbound/worker.go +++ b/app/proxyman/inbound/worker.go @@ -87,6 +87,7 @@ func (w *tcpWorker) callback(conn internet.Connection) { if w.sniffingConfig != nil { content.SniffingRequest.Enabled = w.sniffingConfig.Enabled content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride + content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly } ctx = session.ContextWithContent(ctx, content) if w.uplinkCounter != nil || w.downlinkCounter != nil { @@ -230,11 +231,14 @@ type udpWorker struct { tag string stream *internet.MemoryStreamConfig dispatcher routing.Dispatcher + sniffingConfig *proxyman.SniffingConfig uplinkCounter stats.Counter downlinkCounter stats.Counter checker *task.Periodic activeConn map[connID]*udpConn + + ctx context.Context } func (w *udpWorker) getConnection(id connID) (*udpConn, bool) { @@ -286,7 +290,7 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest common.Must(w.checker.Start()) go func() { - ctx := context.Background() + ctx := w.ctx sid := session.NewID() ctx = session.ContextWithID(ctx, sid) @@ -300,6 +304,13 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest Gateway: net.UDPDestination(w.address, w.port), Tag: w.tag, }) + content := new(session.Content) + if w.sniffingConfig != nil { + content.SniffingRequest.Enabled = w.sniffingConfig.Enabled + content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride + content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly + } + ctx = session.ContextWithContent(ctx, content) if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil { newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx)) } @@ -428,6 +439,7 @@ func (w *dsWorker) callback(conn internet.Connection) { if w.sniffingConfig != nil { content.SniffingRequest.Enabled = w.sniffingConfig.Enabled content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride + content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly } ctx = session.ContextWithContent(ctx, content) if w.uplinkCounter != nil || w.downlinkCounter != nil { diff --git a/common/cache/lru.go b/common/cache/lru.go new file mode 100644 index 000000000..2594a8b1b --- /dev/null +++ b/common/cache/lru.go @@ -0,0 +1,76 @@ +package cache + +import ( + "container/list" + sync "sync" +) + +// Lru simple, fast lru cache implementation +type Lru interface { + Get(key interface{}) (value interface{}, ok bool) + GetKeyFromValue(value interface{}) (key interface{}, ok bool) + Put(key, value interface{}) +} + +type lru struct { + capacity int + doubleLinkedlist *list.List + keyToElement *sync.Map + valueToElement *sync.Map + mu *sync.Mutex +} + +type lruElement struct { + key interface{} + value interface{} +} + +// NewLru init a lru cache +func NewLru(cap int) Lru { + return lru{ + capacity: cap, + doubleLinkedlist: list.New(), + keyToElement: new(sync.Map), + valueToElement: new(sync.Map), + mu: new(sync.Mutex), + } +} + +func (l lru) Get(key interface{}) (value interface{}, ok bool) { + if v, ok := l.keyToElement.Load(key); ok { + element := v.(*list.Element) + l.doubleLinkedlist.MoveBefore(element, l.doubleLinkedlist.Front()) + return element.Value.(lruElement).value, true + } + return nil, false +} + +func (l lru) GetKeyFromValue(value interface{}) (key interface{}, ok bool) { + if k, ok := l.valueToElement.Load(value); ok { + element := k.(*list.Element) + l.doubleLinkedlist.MoveBefore(element, l.doubleLinkedlist.Front()) + return element.Value.(lruElement).key, true + } + return nil, false +} + +func (l lru) Put(key, value interface{}) { + e := lruElement{key, value} + if v, ok := l.keyToElement.Load(key); ok { + element := v.(*list.Element) + element.Value = e + l.doubleLinkedlist.MoveBefore(element, l.doubleLinkedlist.Front()) + } else { + l.mu.Lock() + element := l.doubleLinkedlist.PushFront(e) + l.keyToElement.Store(key, element) + l.valueToElement.Store(value, element) + if l.doubleLinkedlist.Len() > l.capacity { + toBeRemove := l.doubleLinkedlist.Back() + l.doubleLinkedlist.Remove(toBeRemove) + l.keyToElement.Delete(toBeRemove.Value.(lruElement).key) + l.valueToElement.Delete(toBeRemove.Value.(lruElement).value) + } + l.mu.Unlock() + } +} diff --git a/common/cache/lru_test.go b/common/cache/lru_test.go new file mode 100644 index 000000000..e2bb767f6 --- /dev/null +++ b/common/cache/lru_test.go @@ -0,0 +1,69 @@ +package cache_test + +import ( + "testing" + + . "v2ray.com/core/common/cache" +) + +func TestLruReplaceValue(t *testing.T) { + lru := NewLru(2) + lru.Put(2, 6) + lru.Put(1, 5) + lru.Put(1, 2) + v, _ := lru.Get(1) + if v != 2 { + t.Error("should get 2", v) + } + v, _ = lru.Get(2) + if v != 6 { + t.Error("should get 6", v) + } +} + +func TestLruRemoveOld(t *testing.T) { + lru := NewLru(2) + v, ok := lru.Get(2) + if ok { + t.Error("should get nil", v) + } + lru.Put(1, 1) + lru.Put(2, 2) + v, _ = lru.Get(1) + if v != 1 { + t.Error("should get 1", v) + } + lru.Put(3, 3) + v, ok = lru.Get(2) + if ok { + t.Error("should get nil", v) + } + lru.Put(4, 4) + v, ok = lru.Get(1) + if ok { + t.Error("should get nil", v) + } + v, _ = lru.Get(3) + if v != 3 { + t.Error("should get 3", v) + } + v, _ = lru.Get(4) + if v != 4 { + t.Error("should get 4", v) + } +} + +func TestGetKeyFromValue(t *testing.T) { + lru := NewLru(2) + lru.Put(3, 3) + lru.Put(2, 2) + lru.Put(1, 1) + v, ok := lru.GetKeyFromValue(3) + if ok { + t.Error("should get nil", v) + } + v, _ = lru.GetKeyFromValue(2) + if v != 2 { + t.Error("should get 2", v) + } +} diff --git a/common/session/session.go b/common/session/session.go index 463ebac80..2b67e2baa 100644 --- a/common/session/session.go +++ b/common/session/session.go @@ -57,6 +57,7 @@ type Outbound struct { type SniffingRequest struct { OverrideDestinationForProtocol []string Enabled bool + MetadataOnly bool } // Content is the metadata of the connection content. diff --git a/features/dns/fakedns.go b/features/dns/fakedns.go new file mode 100644 index 000000000..0206d8307 --- /dev/null +++ b/features/dns/fakedns.go @@ -0,0 +1,12 @@ +package dns + +import ( + "v2ray.com/core/common/net" + "v2ray.com/core/features" +) + +type FakeDNSEngine interface { + features.Feature + GetFakeIPForDomain(domain string) []net.Address + GetDomainFromFakeDNS(ip net.Address) string +} diff --git a/features/dns/localdns/errors.generated.go b/features/dns/localdns/errors.generated.go new file mode 100644 index 000000000..c63727c75 --- /dev/null +++ b/features/dns/localdns/errors.generated.go @@ -0,0 +1,9 @@ +package localdns + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/infra/conf/fakedns.go b/infra/conf/fakedns.go new file mode 100644 index 000000000..d731bed56 --- /dev/null +++ b/infra/conf/fakedns.go @@ -0,0 +1,65 @@ +package conf + +import ( + "github.com/golang/protobuf/proto" + "v2ray.com/core/app/dns/fakedns" +) + +type FakeDNSConfig struct { + IPPool string `json:"ipPool"` + LruSize int64 `json:"poolSize"` +} + +func (f FakeDNSConfig) Build() (proto.Message, error) { + return &fakedns.FakeDnsPool{ + IpPool: f.IPPool, + LruSize: f.LruSize, + }, nil +} + +type FakeDNSPostProcessingStage struct{} + +func (FakeDNSPostProcessingStage) Process(conf *Config) error { + var fakeDNSInUse bool + + if conf.DNSConfig != nil { + for _, v := range conf.DNSConfig.Servers { + if v.Address.Family().IsDomain() { + if v.Address.Domain() == "fakedns" { + fakeDNSInUse = true + } + } + } + } + + if fakeDNSInUse { + if conf.FakeDNS == nil { + // Add a Fake DNS Config if there is none + conf.FakeDNS = &FakeDNSConfig{ + IPPool: "240.0.0.0/8", + LruSize: 65535, + } + } + found := false + // Check if there is a Outbound with necessary sniffer on + var inbounds []InboundDetourConfig + + if len(conf.InboundConfigs) > 0 { + inbounds = append(inbounds, conf.InboundConfigs...) + } + for _, v := range inbounds { + if v.SniffingConfig != nil && v.SniffingConfig.Enabled && v.SniffingConfig.DestOverride != nil { + for _, dov := range *v.SniffingConfig.DestOverride { + if dov == "fakedns" { + found = true + } + } + } + } + if !found { + newError("Defined Fake DNS but haven't enabled fake dns sniffing at any inbound.").AtWarning().WriteToLog() + } + } + + return nil +} diff --git a/infra/conf/init.go b/infra/conf/init.go new file mode 100644 index 000000000..519d9fb25 --- /dev/null +++ b/infra/conf/init.go @@ -0,0 +1,5 @@ +package conf + +func init() { + RegisterConfigureFilePostProcessingStage("FakeDNS", &FakeDNSPostProcessingStage{}) +} diff --git a/infra/conf/lint.go b/infra/conf/lint.go new file mode 100644 index 000000000..93da35d40 --- /dev/null +++ b/infra/conf/lint.go @@ -0,0 +1,23 @@ +package conf + +type ConfigureFilePostProcessingStage interface { + Process(conf *Config) error +} + +var configureFilePostProcessingStages map[string]ConfigureFilePostProcessingStage + +func RegisterConfigureFilePostProcessingStage(name string, stage ConfigureFilePostProcessingStage) { + if configureFilePostProcessingStages == nil { + configureFilePostProcessingStages = make(map[string]ConfigureFilePostProcessingStage) + } + configureFilePostProcessingStages[name] = stage +} + +func PostProcessConfigureFile(conf *Config) error { + for k, v := range configureFilePostProcessingStages { + if err := v.Process(conf); err != nil { + return newError("Rejected by Postprocessing Stage ", k).AtError().Base(err) + } + } + return nil +} diff --git a/infra/conf/v2ray.go b/infra/conf/v2ray.go index 5ae3aa3bb..390058992 100644 --- a/infra/conf/v2ray.go +++ b/infra/conf/v2ray.go @@ -59,6 +59,7 @@ func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) { type SniffingConfig struct { Enabled bool `json:"enabled"` DestOverride *StringList `json:"destOverride"` + MetadataOnly bool `json:"metadataOnly"` } // Build implements Buildable. @@ -71,6 +72,8 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { p = append(p, "http") case "tls", "https", "ssl": p = append(p, "tls") + case "fakedns": + p = append(p, "fakedns") default: return nil, newError("unknown protocol: ", domainOverride) } @@ -80,6 +83,7 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { return &proxyman.SniffingConfig{ Enabled: c.Enabled, DestinationOverride: p, + MetadataOnly: c.MetadataOnly, }, nil } @@ -346,6 +350,7 @@ type Config struct { API *APIConfig `json:"api"` Stats *StatsConfig `json:"stats"` Reverse *ReverseConfig `json:"reverse"` + FakeDNS *FakeDNSConfig `json:"fakeDns"` } func (c *Config) findInboundTag(tag string) int { @@ -399,6 +404,10 @@ func (c *Config) Override(o *Config, fn string) { c.Reverse = o.Reverse } + if o.FakeDNS != nil { + c.FakeDNS = o.FakeDNS + } + // deprecated attrs... keep them for now if o.InboundConfig != nil { c.InboundConfig = o.InboundConfig @@ -470,6 +479,10 @@ func applyTransportConfig(s *StreamConfig, t *TransportConfig) { // Build implements Buildable. func (c *Config) Build() (*core.Config, error) { + if err := PostProcessConfigureFile(c); err != nil { + return nil, err + } + config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dispatcher.Config{}), @@ -536,6 +549,14 @@ func (c *Config) Build() (*core.Config, error) { config.App = append(config.App, serial.ToTypedMessage(r)) } + if c.FakeDNS != nil { + r, err := c.FakeDNS.Build() + if err != nil { + return nil, err + } + config.App = append(config.App, serial.ToTypedMessage(r)) + } + var inbounds []InboundDetourConfig if c.InboundConfig != nil { diff --git a/main/distro/all/all.go b/main/distro/all/all.go index f7ae90eb7..afacd2a94 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -16,6 +16,7 @@ import ( // Other optional features. _ "v2ray.com/core/app/dns" + _ "v2ray.com/core/app/dns/fakedns" _ "v2ray.com/core/app/log" _ "v2ray.com/core/app/policy" _ "v2ray.com/core/app/reverse" diff --git a/proxy/dns/dns.go b/proxy/dns/dns.go index 4117c0c36..cb3376870 100644 --- a/proxy/dns/dns.go +++ b/proxy/dns/dns.go @@ -38,6 +38,7 @@ type ownLinkVerifier interface { } type Handler struct { + client dns.Client ipv4Lookup dns.IPv4Lookup ipv6Lookup dns.IPv6Lookup ownLinkVerifier ownLinkVerifier @@ -45,6 +46,7 @@ type Handler struct { } func (h *Handler) Init(config *Config, dnsClient dns.Client) error { + h.client = dnsClient ipv4lookup, ok := dnsClient.(dns.IPv4Lookup) if !ok { return newError("dns.Client doesn't implement IPv4Lookup") @@ -211,6 +213,8 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, var ips []net.IP var err error + var ttl uint32 = 600 + switch qType { case dnsmessage.TypeA: ips, err = h.ipv4Lookup.LookupIPv4(domain) @@ -243,7 +247,7 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, })) common.Must(builder.StartAnswers()) - rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: 600} + rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl} for _, ip := range ips { if len(ip) == net.IPv4len { var r dnsmessage.AResource