Browse Source

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 <kslrwang@gmail.com>
Xiaokang Wang 4 years ago
parent
commit
38da831b75

+ 58 - 24
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) {

+ 51 - 0
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
+}

+ 83 - 13
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 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
+}
 
 type Sniffer struct {
-	sniffer []protocolSniffer
+	sniffer []protocolSnifferWithMetadata
 }
 
-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) },
+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
+}

+ 2 - 0
app/dns/config.proto

@@ -69,4 +69,6 @@ message Config {
 
   // Tag is the inbound tag of DNS client.
   string tag = 6;
+
+  reserved 7;
 }

+ 6 - 5
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 {

+ 9 - 0
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{})
+}

+ 132 - 0
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
+	}))
+}

+ 5 - 0
app/dns/fakedns/fakedns.go

@@ -0,0 +1,5 @@
+// +build !confonly
+
+package fakedns
+
+//go:generate go run v2ray.com/core/common/errors/errorgen

+ 164 - 0
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
+}

+ 12 - 0
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
+}

+ 114 - 0
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)
+		}
+	}
+}

+ 2 - 0
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 {

+ 41 - 0
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
+}

+ 103 - 91
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 (

+ 5 - 1
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 {

+ 2 - 0
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,

+ 2 - 0
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,

+ 13 - 1
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 {

+ 76 - 0
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()
+	}
+}

+ 69 - 0
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)
+	}
+}

+ 1 - 0
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.

+ 12 - 0
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
+}

+ 9 - 0
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{})
+}

+ 65 - 0
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
+}

+ 5 - 0
infra/conf/init.go

@@ -0,0 +1,5 @@
+package conf
+
+func init() {
+	RegisterConfigureFilePostProcessingStage("FakeDNS", &FakeDNSPostProcessingStage{})
+}

+ 23 - 0
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
+}

+ 21 - 0
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 {

+ 1 - 0
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"

+ 5 - 1
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