diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index a2dc0775..8ecb6b28 100644 --- a/app/dispatcher/default.go +++ b/app/dispatcher/default.go @@ -15,6 +15,7 @@ import ( "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" @@ -175,17 +176,28 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran return inboundLink, outboundLink } -func shouldOverride(result SniffResult, request session.SniffingRequest) bool { +func shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool { domain := result.Domain() for _, d := range request.ExcludeForDomain { if domain == d { return false } } - - protocol := result.Protocol() + var fakeDNSEngine dns.FakeDNSEngine + core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) { + fakeDNSEngine = fdns + }) + protocolString := result.Protocol() + if resComp, ok := result.(SnifferResultComposite); ok { + protocolString = resComp.ProtocolForDomainResult() + } for _, p := range request.OverrideDestinationForProtocol { - if strings.HasPrefix(protocol, p) { + if strings.HasPrefix(protocolString, p) { + return true + } + if fakeDNSEngine != nil && protocolString != "bittorrent" && p == "fakedns" && + fakeDNSEngine.GetFakeIPRange().Contains(destination.Address.IP()) { + newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx)) return true } } @@ -210,19 +222,33 @@ 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) + 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(ctx, result, sniffingRequest, destination) { + 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) - } else { + 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() } - if err == nil && shouldOverride(result, sniffingRequest) { + if err == nil && shouldOverride(ctx, result, sniffingRequest, destination) { domain := result.Domain() newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) destination.Address = net.ParseAddress(domain) @@ -234,34 +260,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) + + metaresult, metadataErr := sniffer.SniffMetadata(ctx) + + if metadataOnly { + return metaresult, metadataErr + } - cReader.Cache(payload) - if !payload.IsEmpty() { - result, err := sniffer.Sniff(payload.Bytes()) - if err != common.ErrNoClue { - return result, err + 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 00000000..fa34179f --- /dev/null +++ b/app/dispatcher/fakednssniffer.go @@ -0,0 +1,51 @@ +// +build !confonly + +package dispatcher + +import ( + "context" + + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/features/dns" +) + +// 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 53bc1560..988d3079 100644 --- a/app/dispatcher/sniffer.go +++ b/app/dispatcher/sniffer.go @@ -1,6 +1,8 @@ package dispatcher import ( + "context" + "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/protocol/bittorrent" "github.com/xtls/xray-core/common/protocol/http" @@ -12,30 +14,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 } @@ -51,3 +69,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 3ab82500..94488719 100644 --- a/app/dns/config.proto +++ b/app/dns/config.proto @@ -68,4 +68,6 @@ message Config { // Tag is the inbound tag of DNS client. string tag = 6; + + reserved 7; } diff --git a/app/dns/dnscommon.go b/app/dns/dnscommon.go index a9e4b014..6f1fada2 100644 --- a/app/dns/dnscommon.go +++ b/app/dns/dnscommon.go @@ -2,6 +2,7 @@ package dns import ( "encoding/binary" + "strings" "time" "github.com/xtls/xray-core/common" @@ -13,7 +14,7 @@ import ( // Fqdn normalize domain make sure it ends with '.' func Fqdn(domain string) string { - if len(domain) > 0 && domain[len(domain)-1] == '.' { + if len(domain) > 0 && strings.HasSuffix(domain, ".") { return domain } return domain + "." @@ -112,7 +113,7 @@ func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource { return opt } -func buildReqMsgs(domain string, option IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest { +func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest { qA := dnsmessage.Question{ Name: dnsmessage.MustNewName(domain), Type: dnsmessage.TypeA, diff --git a/app/dns/dnscommon_test.go b/app/dns/dnscommon_test.go index 3d21cf16..58462655 100644 --- a/app/dns/dnscommon_test.go +++ b/app/dns/dnscommon_test.go @@ -9,6 +9,7 @@ import ( "github.com/miekg/dns" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" + dns_feature "github.com/xtls/xray-core/features/dns" "golang.org/x/net/dns/dnsmessage" ) @@ -92,7 +93,7 @@ func Test_buildReqMsgs(t *testing.T) { } type args struct { domain string - option IPOption + option dns_feature.IPOption reqOpts *dnsmessage.Resource } tests := []struct { @@ -100,10 +101,26 @@ func Test_buildReqMsgs(t *testing.T) { args args want int }{ - {"dual stack", args{"test.com", IPOption{true, true}, nil}, 2}, - {"ipv4 only", args{"test.com", IPOption{true, false}, nil}, 1}, - {"ipv6 only", args{"test.com", IPOption{false, true}, nil}, 1}, - {"none/error", args{"test.com", IPOption{false, false}, nil}, 0}, + {"dual stack", args{"test.com", dns_feature.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }, nil}, 2}, + {"ipv4 only", args{"test.com", dns_feature.IPOption{ + IPv4Enable: true, + IPv6Enable: false, + FakeEnable: false, + }, nil}, 1}, + {"ipv6 only", args{"test.com", dns_feature.IPOption{ + IPv4Enable: false, + IPv6Enable: true, + FakeEnable: false, + }, nil}, 1}, + {"none/error", args{"test.com", dns_feature.IPOption{ + IPv4Enable: false, + IPv6Enable: false, + FakeEnable: false, + }, nil}, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/app/dns/dohdns.go b/app/dns/dohdns.go index 85fc6967..b53ebe76 100644 --- a/app/dns/dohdns.go +++ b/app/dns/dohdns.go @@ -234,7 +234,7 @@ func (s *DoHNameServer) newReqID() uint16 { return uint16(atomic.AddUint32(&s.reqID, 1)) } -func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPOption) { +func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns_feature.IPOption) { newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx)) if s.name+"." == "DOH//"+domain { @@ -320,7 +320,7 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, return ioutil.ReadAll(resp.Body) } -func (s *DoHNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) { +func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) { s.RLock() record, found := s.ips[domain] s.RUnlock() @@ -363,7 +363,7 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option IPOption) ([]net. } // QueryIP is called from dns.Server->queryIPTimeout -func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { +func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, error) { // nolint: dupl fqdn := Fqdn(domain) ips, err := s.findIPsForDomain(fqdn, option) diff --git a/app/dns/fakedns/errors.generated.go b/app/dns/fakedns/errors.generated.go new file mode 100644 index 00000000..84e0448c --- /dev/null +++ b/app/dns/fakedns/errors.generated.go @@ -0,0 +1,9 @@ +package fakedns + +import "github.com/xtls/xray-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 00000000..4860b42d --- /dev/null +++ b/app/dns/fakedns/fake.go @@ -0,0 +1,136 @@ +// +build !confonly + +package fakedns + +import ( + "context" + "math" + "math/big" + gonet "net" + "time" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/cache" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/features/dns" +) + +type Holder struct { + domainToIP cache.Lru + 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.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, 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 err error + + if _, ipRange, err = gonet.ParseCIDR(ipPoolCidr); err != nil { + return newError("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError() + } + + 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 + 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 currentTimeMillis = uint64(time.Now().UnixNano() / 1e6) + ones, bits := fkdns.ipRange.Mask.Size() + rooms := bits - ones + if rooms < 64 { + currentTimeMillis %= (uint64(1) << rooms) + } + var bigIntIP = big.NewInt(0).SetBytes(fkdns.ipRange.IP) + bigIntIP = bigIntIP.Add(bigIntIP, new(big.Int).SetUint64(currentTimeMillis)) + var ip net.Address + for { + ip = net.IPAddress(bigIntIP.Bytes()) + + // if we run for a long time, we may go back to beginning and start seeing the IP in use + if _, ok := fkdns.domainToIP.PeekKeyFromValue(ip); !ok { + break + } + + bigIntIP = bigIntIP.Add(bigIntIP, big.NewInt(1)) + if !fkdns.ipRange.Contains(bigIntIP.Bytes()) { + bigIntIP = big.NewInt(0).SetBytes(fkdns.ipRange.IP) + } + } + 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) + } + newError("A fake ip request to ", ip, ", however there is no matching domain name in fake DNS").AtInfo().WriteToLog() + return "" +} + +// GetFakeIPRange return fake IP range from configuration +func (fkdns *Holder) GetFakeIPRange() *gonet.IPNet { + return fkdns.ipRange +} + +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 00000000..ac28414c --- /dev/null +++ b/app/dns/fakedns/fakedns.go @@ -0,0 +1,5 @@ +// +build !confonly + +package fakedns + +//go:generate go run github.com/xtls/xray-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 00000000..025029de --- /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: xray.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 00000000..69a344ac --- /dev/null +++ b/app/dns/fakedns/fakedns.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package xray.app.dns.fakedns; +option csharp_namespace = "Xray.App.Dns.Fakedns"; +option go_package = "github.com/xtls/xray-core/app/dns/fakedns"; +option java_package = "com.xray.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 00000000..a9ddc4cf --- /dev/null +++ b/app/dns/fakedns/fakedns_test.go @@ -0,0 +1,99 @@ +package fakedns + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-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.", addr[0].IP().String()[0:4]) +} + +func TestFakeDnsHolderCreateMappingMany(t *testing.T) { + fkdns, err := NewFakeDNSHolder() + common.Must(err) + + addr := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + assert.Equal(t, "240.", addr[0].IP().String()[0:4]) + + addr2 := fkdns.GetFakeIPForDomain("fakednstest2.v2fly.org") + assert.Equal(t, "240.", addr2[0].IP().String()[0:4]) + assert.NotEqual(t, addr[0].IP().String(), addr2[0].IP().String()) +} + +func TestFakeDnsHolderCreateMappingManyAndResolve(t *testing.T) { + fkdns, err := NewFakeDNSHolder() + common.Must(err) + + addr := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + addr2 := fkdns.GetFakeIPForDomain("fakednstest2.v2fly.org") + + { + result := fkdns.GetDomainFromFakeDNS(addr[0]) + assert.Equal(t, "fakednstest.v2fly.org", result) + } + + { + result := fkdns.GetDomainFromFakeDNS(addr2[0]) + assert.Equal(t, "fakednstest2.v2fly.org", result) + } +} + +func TestFakeDnsHolderCreateMappingManySingleDomain(t *testing.T) { + fkdns, err := NewFakeDNSHolder() + common.Must(err) + + addr := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + addr2 := fkdns.GetFakeIPForDomain("fakednstest.v2fly.org") + assert.Equal(t, addr[0].IP().String(), 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") + addr2 := fkdns.GetFakeIPForDomain("fakednstest2.v2fly.org") + + for i := 0; i <= 8192; i++ { + { + result := fkdns.GetDomainFromFakeDNS(addr[0]) + assert.Equal(t, "fakednstest.v2fly.org", result) + } + + { + result := fkdns.GetDomainFromFakeDNS(addr2[0]) + assert.Equal(t, "fakednstest2.v2fly.org", result) + } + + { + uuid := uuid.New() + domain := uuid.String() + ".fakednstest.v2fly.org" + tempAddr := fkdns.GetFakeIPForDomain(domain) + rsaddr := tempAddr[0].IP().String() + + result := fkdns.GetDomainFromFakeDNS(net.ParseAddress(rsaddr)) + assert.Equal(t, domain, result) + } + } +} diff --git a/app/dns/hosts.go b/app/dns/hosts.go index 424d3c8c..0b584782 100644 --- a/app/dns/hosts.go +++ b/app/dns/hosts.go @@ -5,6 +5,7 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/strmatcher" "github.com/xtls/xray-core/features" + "github.com/xtls/xray-core/features/dns" ) // StaticHosts represents static domain-ip mapping in DNS server. @@ -92,7 +93,7 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma return sh, nil } -func filterIP(ips []net.Address, option IPOption) []net.Address { +func filterIP(ips []net.Address, option dns.IPOption) []net.Address { filtered := make([]net.Address, 0, len(ips)) for _, ip := range ips { if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) { @@ -106,7 +107,7 @@ func filterIP(ips []net.Address, option IPOption) []net.Address { } // LookupIP returns IP address for the given domain, if exists in this StaticHosts. -func (h *StaticHosts) LookupIP(domain string, option IPOption) []net.Address { +func (h *StaticHosts) LookupIP(domain string, option dns.IPOption) []net.Address { indices := h.matchers.Match(domain) if len(indices) == 0 { return nil diff --git a/app/dns/hosts_test.go b/app/dns/hosts_test.go index c2f99694..2d6929af 100644 --- a/app/dns/hosts_test.go +++ b/app/dns/hosts_test.go @@ -8,6 +8,7 @@ import ( . "github.com/xtls/xray-core/app/dns" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/features/dns" ) func TestStaticHosts(t *testing.T) { @@ -39,7 +40,7 @@ func TestStaticHosts(t *testing.T) { common.Must(err) { - ips := hosts.LookupIP("example.com", IPOption{ + ips := hosts.LookupIP("example.com", dns.IPOption{ IPv4Enable: true, IPv6Enable: true, }) @@ -52,7 +53,7 @@ func TestStaticHosts(t *testing.T) { } { - ips := hosts.LookupIP("www.example.cn", IPOption{ + ips := hosts.LookupIP("www.example.cn", dns.IPOption{ IPv4Enable: true, IPv6Enable: true, }) @@ -65,7 +66,7 @@ func TestStaticHosts(t *testing.T) { } { - ips := hosts.LookupIP("baidu.com", IPOption{ + ips := hosts.LookupIP("baidu.com", dns.IPOption{ IPv4Enable: false, IPv6Enable: true, }) diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go index 35f08802..2b557099 100644 --- a/app/dns/nameserver.go +++ b/app/dns/nameserver.go @@ -4,39 +4,26 @@ import ( "context" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns/localdns" ) -// IPOption is an object for IP query options. -type IPOption struct { - IPv4Enable bool - IPv6Enable bool -} - // Client is the interface for DNS client. type Client interface { // Name of the Client. Name() string // QueryIP sends IP queries to its configured server. - QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) + QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, error) } type LocalNameServer struct { client *localdns.Client } -func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { - if option.IPv4Enable && option.IPv6Enable { - return s.client.LookupIP(domain) - } - - if option.IPv4Enable { - return s.client.LookupIPv4(domain) - } - - if option.IPv6Enable { - return s.client.LookupIPv6(domain) +func (s *LocalNameServer) QueryIP(_ context.Context, domain string, option dns.IPOption) ([]net.IP, error) { + if option.IPv4Enable || option.IPv6Enable { + return s.client.LookupIP(domain, option) } return nil, newError("neither IPv4 nor IPv6 is enabled") diff --git a/app/dns/nameserver_fakedns.go b/app/dns/nameserver_fakedns.go new file mode 100644 index 00000000..1d008620 --- /dev/null +++ b/app/dns/nameserver_fakedns.go @@ -0,0 +1,43 @@ +// +build !confonly + +package dns + +import ( + "context" + + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-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, _ dns.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 := toNetIP(ips) + if netIP == nil { + return nil, newError("Unable to convert IP to net ip").AtError() + } + + newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog() + + return netIP, nil +} diff --git a/app/dns/nameserver_test.go b/app/dns/nameserver_test.go index 53638d2d..9bd1a4a1 100644 --- a/app/dns/nameserver_test.go +++ b/app/dns/nameserver_test.go @@ -7,14 +7,16 @@ import ( . "github.com/xtls/xray-core/app/dns" "github.com/xtls/xray-core/common" + dns_feature "github.com/xtls/xray-core/features/dns" ) func TestLocalNameServer(t *testing.T) { s := NewLocalNameServer() ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) - ips, err := s.QueryIP(ctx, "google.com", IPOption{ + ips, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ IPv4Enable: true, IPv6Enable: true, + FakeEnable: false, }) cancel() common.Must(err) diff --git a/app/dns/server.go b/app/dns/server.go index 1c2796e6..65aa7b7b 100644 --- a/app/dns/server.go +++ b/app/dns/server.go @@ -31,6 +31,7 @@ type Server struct { hosts *StaticHosts clientIP net.IP clients []Client // clientIdx -> Client + ctx context.Context ipIndexMap []*MultiGeoIPMatcher // clientIdx -> *MultiGeoIPMatcher domainRules [][]string // clientIdx -> domainRuleIdx -> DomainRule domainMatcher strmatcher.IndexMatcher @@ -75,6 +76,7 @@ func generateRandomTag() string { func New(ctx context.Context, config *Config) (*Server, error) { server := &Server{ clients: make([]Client, 0, len(config.NameServers)+len(config.NameServer)), + ctx: ctx, tag: config.Tag, } if server.tag == "" { @@ -144,6 +146,9 @@ func New(ctx context.Context, config *Config) (*Server, error) { server.clients[idx] = c })) + case address.Family().IsDomain() && address.Domain() == "fakedns": + server.clients = append(server.clients, NewFakeDNSServer()) + default: // UDP classic DNS mode dest := endpoint.AsDestination() @@ -295,8 +300,8 @@ func (s *Server) Match(idx int, client Client, domain string, ips []net.IP) ([]n return newIps, nil } -func (s *Server) queryIPTimeout(idx int, client Client, domain string, option IPOption) ([]net.IP, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*4) +func (s *Server) queryIPTimeout(idx int, client Client, domain string, option dns.IPOption) ([]net.IP, error) { + ctx, cancel := context.WithTimeout(s.ctx, time.Second*4) if len(s.tag) > 0 { ctx = session.ContextWithInbound(ctx, &session.Inbound{ Tag: s.tag, @@ -314,31 +319,7 @@ func (s *Server) queryIPTimeout(idx int, client Client, domain string, option IP return ips, err } -// LookupIP implements dns.Client. -func (s *Server) LookupIP(domain string) ([]net.IP, error) { - return s.lookupIPInternal(domain, IPOption{ - IPv4Enable: true, - IPv6Enable: true, - }) -} - -// LookupIPv4 implements dns.IPv4Lookup. -func (s *Server) LookupIPv4(domain string) ([]net.IP, error) { - return s.lookupIPInternal(domain, IPOption{ - IPv4Enable: true, - IPv6Enable: false, - }) -} - -// LookupIPv6 implements dns.IPv6Lookup. -func (s *Server) LookupIPv6(domain string) ([]net.IP, error) { - return s.lookupIPInternal(domain, IPOption{ - IPv4Enable: false, - IPv6Enable: true, - }) -} - -func (s *Server) lookupStatic(domain string, option IPOption, depth int32) []net.Address { +func (s *Server) lookupStatic(domain string, option dns.IPOption, depth int32) []net.Address { ips := s.hosts.LookupIP(domain, option) if ips == nil { return nil @@ -362,14 +343,15 @@ func toNetIP(ips []net.Address) []net.IP { return netips } -func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, error) { +// LookupIP implements dns.Client. +func (s *Server) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) { if domain == "" { return nil, newError("empty domain name") } domain = strings.ToLower(domain) // normalize the FQDN form query - if domain[len(domain)-1] == '.' { + if strings.HasSuffix(domain, ".") { domain = domain[:len(domain)-1] } @@ -406,6 +388,10 @@ func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, err for _, idx := range indices { clientIdx := int(s.matcherInfos[idx].clientIdx) matchedClient = s.clients[clientIdx] + if !option.FakeEnable && strings.EqualFold(matchedClient.Name(), "FakeDNS") { + newError("skip DNS resolution for domain ", domain, " at server ", matchedClient.Name()).AtDebug().WriteToLog() + continue + } ips, err := s.queryIPTimeout(clientIdx, matchedClient, domain, option) if len(ips) > 0 { return ips, nil @@ -425,7 +411,10 @@ func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, err newError("domain ", domain, " at server ", client.Name(), " idx:", idx, " already lookup failed, just ignore").AtDebug().WriteToLog() continue } - + if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") { + newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).AtDebug().WriteToLog() + continue + } ips, err := s.queryIPTimeout(idx, client, domain, option) if len(ips) > 0 { return ips, nil diff --git a/app/dns/server_test.go b/app/dns/server_test.go index 03ae1bfc..c2b984ad 100644 --- a/app/dns/server_test.go +++ b/app/dns/server_test.go @@ -154,7 +154,11 @@ func TestUDPServerSubnet(t *testing.T) { client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) - ips, err := client.LookupIP("google.com") + ips, err := client.LookupIP("google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -209,7 +213,11 @@ func TestUDPServer(t *testing.T) { client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) { - ips, err := client.LookupIP("google.com") + ips, err := client.LookupIP("google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -220,7 +228,11 @@ func TestUDPServer(t *testing.T) { } { - ips, err := client.LookupIP("facebook.com") + ips, err := client.LookupIP("facebook.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -231,7 +243,11 @@ func TestUDPServer(t *testing.T) { } { - _, err := client.LookupIP("notexist.google.com") + _, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err == nil { t.Fatal("nil error") } @@ -241,8 +257,11 @@ func TestUDPServer(t *testing.T) { } { - clientv6 := client.(feature_dns.IPv6Lookup) - ips, err := clientv6.LookupIPv6("ipv4only.google.com") + ips, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{ + IPv4Enable: false, + IPv6Enable: true, + FakeEnable: false, + }) if err != feature_dns.ErrEmptyResponse { t.Fatal("error: ", err) } @@ -254,7 +273,11 @@ func TestUDPServer(t *testing.T) { dnsServer.Shutdown() { - ips, err := client.LookupIP("google.com") + ips, err := client.LookupIP("google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -331,7 +354,11 @@ func TestPrioritizedDomain(t *testing.T) { startTime := time.Now() { - ips, err := client.LookupIP("google.com") + ips, err := client.LookupIP("google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -390,10 +417,12 @@ func TestUDPServerIPv6(t *testing.T) { common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) - client6 := client.(feature_dns.IPv6Lookup) - { - ips, err := client6.LookupIPv6("ipv6.google.com") + ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{ + IPv4Enable: false, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -456,7 +485,11 @@ func TestStaticHostDomain(t *testing.T) { client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) { - ips, err := client.LookupIP("example.com") + ips, err := client.LookupIP("example.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -563,7 +596,11 @@ func TestIPMatch(t *testing.T) { startTime := time.Now() { - ips, err := client.LookupIP("google.com") + ips, err := client.LookupIP("google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -682,7 +719,11 @@ func TestLocalDomain(t *testing.T) { startTime := time.Now() { // Will match dotless: - ips, err := client.LookupIP("hostname") + ips, err := client.LookupIP("hostname", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -693,7 +734,11 @@ func TestLocalDomain(t *testing.T) { } { // Will match domain:local - ips, err := client.LookupIP("hostname.local") + ips, err := client.LookupIP("hostname.local", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -704,7 +749,11 @@ func TestLocalDomain(t *testing.T) { } { // Will match static ip - ips, err := client.LookupIP("hostnamestatic") + ips, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -715,7 +764,11 @@ func TestLocalDomain(t *testing.T) { } { // Will match domain replacing - ips, err := client.LookupIP("hostnamealias") + ips, err := client.LookupIP("hostnamealias", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -726,7 +779,11 @@ func TestLocalDomain(t *testing.T) { } { // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless: - ips, err := client.LookupIP("localhost") + ips, err := client.LookupIP("localhost", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -737,7 +794,11 @@ func TestLocalDomain(t *testing.T) { } { // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 - ips, err := client.LookupIP("localhost-a") + ips, err := client.LookupIP("localhost-a", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -748,7 +809,11 @@ func TestLocalDomain(t *testing.T) { } { // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 - ips, err := client.LookupIP("localhost-b") + ips, err := client.LookupIP("localhost-b", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -759,7 +824,11 @@ func TestLocalDomain(t *testing.T) { } { // Will match dotless: - ips, err := client.LookupIP("Mijia Cloud") + ips, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -921,7 +990,11 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) { startTime := time.Now() { // Will match server 1,2 and server 1 returns expected ip - ips, err := client.LookupIP("google.com") + ips, err := client.LookupIP("google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -932,8 +1005,11 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) { } { // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one - clientv4 := client.(feature_dns.IPv4Lookup) - ips, err := clientv4.LookupIPv4("ipv6.google.com") + ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: false, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -944,7 +1020,11 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) { } { // Will match server 3,1,2 and server 3 returns expected one - ips, err := client.LookupIP("api.google.com") + ips, err := client.LookupIP("api.google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } @@ -955,7 +1035,11 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) { } { // Will match server 4,3,1,2 and server 4 returns expected one - ips, err := client.LookupIP("v2.api.google.com") + ips, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err != nil { t.Fatal("unexpected error: ", err) } diff --git a/app/dns/udpns.go b/app/dns/udpns.go index 06d834bc..15faa5e2 100644 --- a/app/dns/udpns.go +++ b/app/dns/udpns.go @@ -54,7 +54,7 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher Execute: s.Cleanup, } s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse) - newError("DNS: created udp client inited for ", address.NetAddr()).AtInfo().WriteToLog() + newError("DNS: created UDP client initialized for ", address.NetAddr()).AtInfo().WriteToLog() return s } @@ -180,7 +180,7 @@ func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) { s.requests[id] = *req } -func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option IPOption) { +func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option dns_feature.IPOption) { newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx)) reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP)) @@ -206,7 +206,7 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option } } -func (s *ClassicNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) { +func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) { s.RLock() record, found := s.ips[domain] s.RUnlock() @@ -244,7 +244,8 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option IPOption) ([] return nil, dns_feature.ErrEmptyResponse } -func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { +// QueryIP implements Server. +func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, error) { fqdn := Fqdn(domain) ips, err := s.findIPsForDomain(fqdn, option) diff --git a/app/proxyman/config.pb.go b/app/proxyman/config.pb.go index c6bb8c5a..4e42cfd5 100644 --- a/app/proxyman/config.pb.go +++ b/app/proxyman/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 -// protoc v3.14.0 +// protoc (unknown) // source: app/proxyman/config.proto package proxyman @@ -239,9 +239,12 @@ 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"` DomainsExcluded []string `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,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,4,opt,name=metadata_only,json=metadataOnly,proto3" json:"metadata_only,omitempty"` } func (x *SniffingConfig) Reset() { @@ -297,6 +300,13 @@ func (x *SniffingConfig) GetDomainsExcluded() []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 @@ -764,7 +774,7 @@ var file_app_proxyman_config_proto_rawDesc = []byte{ 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, 0x88, 0x01, 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, + 0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0xad, 0x01, 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, @@ -773,86 +783,88 @@ var file_app_proxyman_config_proto_rawDesc = []byte{ 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, - 0x22, 0x90, 0x04, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, - 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x33, - 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, - 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x6c, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x12, 0x56, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, - 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x4e, 0x0a, 0x0f, 0x73, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 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, 0x4e, 0x0a, - 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, - 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61, 0x79, 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, 0x4e, 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, 0x21, 0x2e, 0x78, 0x72, 0x61, 0x79, 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, 0xc0, 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, 0x4d, - 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, - 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x72, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, + 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x6c, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x90, 0x04, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, + 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, + 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, + 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, + 0x6e, 0x67, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x52, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x12, 0x56, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, + 0x12, 0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x12, 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, 0x4e, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x76, 0x65, + 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x78, 0x72, + 0x61, 0x79, 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, 0x4e, 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, 0x21, 0x2e, + 0x78, 0x72, 0x61, 0x79, 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, 0xc0, 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, 0x4d, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, + 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, + 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x12, 0x47, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, + 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, + 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xb0, 0x02, + 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, + 0x0a, 0x03, 0x76, 0x69, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, + 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61, 0x12, 0x4e, 0x0a, + 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4b, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xb0, 0x02, 0x0a, 0x0c, 0x53, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a, 0x03, 0x76, 0x69, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61, 0x12, 0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4b, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, - 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, - 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, - 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, - 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x50, 0x0a, 0x12, 0x4d, - 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, - 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x2a, 0x23, 0x0a, - 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, - 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x4c, 0x53, - 0x10, 0x01, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, - 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x26, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, - 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, - 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, + 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x6d, 0x75, + 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, + 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, + 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x22, 0x50, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07, + 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, + 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, + 0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, + 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, + 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, + 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/proxyman/config.proto b/app/proxyman/config.proto index 27b3a18e..83d5ad5e 100644 --- a/app/proxyman/config.proto +++ b/app/proxyman/config.proto @@ -54,9 +54,13 @@ 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; repeated string domains_excluded = 3; + + // 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 = 4; } message ReceiverConfig { diff --git a/app/proxyman/inbound/always.go b/app/proxyman/inbound/always.go index 27716971..a29e7d32 100644 --- a/app/proxyman/inbound/always.go +++ b/app/proxyman/inbound/always.go @@ -133,6 +133,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig * 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 71e46c46..b23e66db 100644 --- a/app/proxyman/inbound/dynamic.go +++ b/app/proxyman/inbound/dynamic.go @@ -153,6 +153,7 @@ func (h *DynamicInboundHandler) refresh() error { 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 c7c7a045..04094a4b 100644 --- a/app/proxyman/inbound/worker.go +++ b/app/proxyman/inbound/worker.go @@ -98,6 +98,7 @@ func (w *tcpWorker) callback(conn internet.Connection) { content.SniffingRequest.Enabled = w.sniffingConfig.Enabled content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded + content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly } ctx = session.ContextWithContent(ctx, content) @@ -235,6 +236,7 @@ type udpWorker struct { tag string stream *internet.MemoryStreamConfig dispatcher routing.Dispatcher + sniffingConfig *proxyman.SniffingConfig uplinkCounter stats.Counter downlinkCounter stats.Counter @@ -297,7 +299,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) @@ -311,6 +313,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)) } @@ -451,6 +460,7 @@ func (w *dsWorker) callback(conn internet.Connection) { content.SniffingRequest.Enabled = w.sniffingConfig.Enabled content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded + content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly } ctx = session.ContextWithContent(ctx, content) diff --git a/app/router/router_test.go b/app/router/router_test.go index cd0b7154..14ce8a3d 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -9,6 +9,7 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/outbound" routing_session "github.com/xtls/xray-core/features/routing/session" "github.com/xtls/xray-core/testing/mocks" @@ -115,7 +116,11 @@ func TestIPOnDemand(t *testing.T) { defer mockCtl.Finish() mockDNS := mocks.NewDNSClient(mockCtl) - mockDNS.EXPECT().LookupIP(gomock.Eq("example.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() + mockDNS.EXPECT().LookupIP(gomock.Eq("example.com"), dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) common.Must(r.Init(config, mockDNS, nil)) @@ -150,7 +155,11 @@ func TestIPIfNonMatchDomain(t *testing.T) { defer mockCtl.Finish() mockDNS := mocks.NewDNSClient(mockCtl) - mockDNS.EXPECT().LookupIP(gomock.Eq("example.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() + mockDNS.EXPECT().LookupIP(gomock.Eq("example.com"), dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) common.Must(r.Init(config, mockDNS, nil)) diff --git a/common/cache/lru.go b/common/cache/lru.go new file mode 100644 index 00000000..65f10a3e --- /dev/null +++ b/common/cache/lru.go @@ -0,0 +1,85 @@ +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) + PeekKeyFromValue(value interface{}) (key interface{}, ok bool) // Peek means check but NOT bring to top + 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) PeekKeyFromValue(value interface{}) (key interface{}, ok bool) { + if k, ok := l.valueToElement.Load(value); ok { + element := k.(*list.Element) + 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 00000000..6bbae4fd --- /dev/null +++ b/common/cache/lru_test.go @@ -0,0 +1,86 @@ +package cache_test + +import ( + "testing" + + . "github.com/xtls/xray-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.GetKeyFromValue(3) + lru.Put(1, 1) + v, ok := lru.GetKeyFromValue(2) + if ok { + t.Error("should get nil", v) + } + v, _ = lru.GetKeyFromValue(3) + if v != 3 { + t.Error("should get 3", v) + } +} + +func TestPeekKeyFromValue(t *testing.T) { + lru := NewLru(2) + lru.Put(3, 3) + lru.Put(2, 2) + lru.PeekKeyFromValue(3) + lru.Put(1, 1) + v, ok := lru.PeekKeyFromValue(3) + if ok { + t.Error("should get nil", v) + } + v, _ = lru.PeekKeyFromValue(2) + if v != 2 { + t.Error("should get 2", v) + } +} diff --git a/common/session/session.go b/common/session/session.go index de2fc854..4744adeb 100644 --- a/common/session/session.go +++ b/common/session/session.go @@ -63,6 +63,7 @@ type SniffingRequest struct { ExcludeForDomain []string OverrideDestinationForProtocol []string Enabled bool + MetadataOnly bool } // Content is the metadata of the connection content. diff --git a/features/dns/client.go b/features/dns/client.go index 5591a81d..584e24f8 100644 --- a/features/dns/client.go +++ b/features/dns/client.go @@ -7,6 +7,13 @@ import ( "github.com/xtls/xray-core/features" ) +// IPOption is an object for IP query options. +type IPOption struct { + IPv4Enable bool + IPv6Enable bool + FakeEnable bool +} + // Client is a Xray feature for querying DNS information. // // xray:api:stable @@ -14,21 +21,7 @@ type Client interface { features.Feature // LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses. - LookupIP(domain string) ([]net.IP, error) -} - -// IPv4Lookup is an optional feature for querying IPv4 addresses only. -// -// xray:api:beta -type IPv4Lookup interface { - LookupIPv4(domain string) ([]net.IP, error) -} - -// IPv6Lookup is an optional feature for querying IPv6 addresses only. -// -// xray:api:beta -type IPv6Lookup interface { - LookupIPv6(domain string) ([]net.IP, error) + LookupIP(domain string, option IPOption) ([]net.IP, error) } // ClientType returns the type of Client interface. Can be used for implementing common.HasType. diff --git a/features/dns/fakedns.go b/features/dns/fakedns.go new file mode 100644 index 00000000..f0755bfd --- /dev/null +++ b/features/dns/fakedns.go @@ -0,0 +1,15 @@ +package dns + +import ( + gonet "net" + + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/features" +) + +type FakeDNSEngine interface { + features.Feature + GetFakeIPForDomain(domain string) []net.Address + GetDomainFromFakeDNS(ip net.Address) string + GetFakeIPRange() *gonet.IPNet +} diff --git a/features/dns/localdns/client.go b/features/dns/localdns/client.go index c6a9297b..92419dfa 100644 --- a/features/dns/localdns/client.go +++ b/features/dns/localdns/client.go @@ -20,58 +20,41 @@ func (*Client) Start() error { return nil } func (*Client) Close() error { return nil } // LookupIP implements Client. -func (*Client) LookupIP(host string) ([]net.IP, error) { +func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, error) { ips, err := net.LookupIP(host) if err != nil { return nil, err } parsedIPs := make([]net.IP, 0, len(ips)) + ipv4 := make([]net.IP, 0, len(ips)) + ipv6 := make([]net.IP, 0, len(ips)) for _, ip := range ips { parsed := net.IPAddress(ip) if parsed != nil { parsedIPs = append(parsedIPs, parsed.IP()) } - } - if len(parsedIPs) == 0 { - return nil, dns.ErrEmptyResponse - } - return parsedIPs, nil -} - -// LookupIPv4 implements IPv4Lookup. -func (c *Client) LookupIPv4(host string) ([]net.IP, error) { - ips, err := c.LookupIP(host) - if err != nil { - return nil, err - } - ipv4 := make([]net.IP, 0, len(ips)) - for _, ip := range ips { if len(ip) == net.IPv4len { ipv4 = append(ipv4, ip) } - } - if len(ipv4) == 0 { - return nil, dns.ErrEmptyResponse - } - return ipv4, nil -} - -// LookupIPv6 implements IPv6Lookup. -func (c *Client) LookupIPv6(host string) ([]net.IP, error) { - ips, err := c.LookupIP(host) - if err != nil { - return nil, err - } - ipv6 := make([]net.IP, 0, len(ips)) - for _, ip := range ips { if len(ip) == net.IPv6len { ipv6 = append(ipv6, ip) } } - if len(ipv6) == 0 { - return nil, dns.ErrEmptyResponse + switch { + case option.IPv4Enable && option.IPv6Enable: + if len(parsedIPs) > 0 { + return parsedIPs, nil + } + case option.IPv4Enable: + if len(ipv4) > 0 { + return ipv4, nil + } + case option.IPv6Enable: + if len(ipv6) > 0 { + return ipv6, nil + } } - return ipv6, nil + return nil, dns.ErrEmptyResponse } // New create a new dns.Client that queries localhost for DNS. diff --git a/features/dns/localdns/errors.generated.go b/features/dns/localdns/errors.generated.go new file mode 100644 index 00000000..a501d5e6 --- /dev/null +++ b/features/dns/localdns/errors.generated.go @@ -0,0 +1,9 @@ +package localdns + +import "github.com/xtls/xray-core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/features/routing/dns/context.go b/features/routing/dns/context.go index 8c8c9332..b4d07717 100644 --- a/features/routing/dns/context.go +++ b/features/routing/dns/context.go @@ -26,7 +26,11 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP { } if domain := ctx.GetTargetDomain(); len(domain) != 0 { - ips, err := ctx.dnsClient.LookupIP(domain) + ips, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + }) if err == nil { ctx.resolvedIPs = ips return ips diff --git a/infra/conf/fakedns.go b/infra/conf/fakedns.go new file mode 100644 index 00000000..47fc62dc --- /dev/null +++ b/infra/conf/fakedns.go @@ -0,0 +1,65 @@ +package conf + +import ( + "github.com/golang/protobuf/proto" + "github.com/xtls/xray-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 00000000..519d9fb2 --- /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 00000000..93da35d4 --- /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/xray.go b/infra/conf/xray.go index 948112f5..0b139792 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -62,6 +62,7 @@ type SniffingConfig struct { Enabled bool `json:"enabled"` DestOverride *StringList `json:"destOverride"` DomainsExcluded *StringList `json:"domainsExcluded"` + MetadataOnly bool `json:"metadataOnly"` } // Build implements Buildable. @@ -74,6 +75,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: ", protocol) } @@ -91,6 +94,7 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { Enabled: c.Enabled, DestinationOverride: p, DomainsExcluded: d, + MetadataOnly: c.MetadataOnly, }, nil } @@ -395,6 +399,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 { @@ -448,6 +453,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 @@ -519,6 +528,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{}), @@ -585,6 +598,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 68e36e5c..0284b33a 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -16,6 +16,7 @@ import ( // Other optional features. _ "github.com/xtls/xray-core/app/dns" + _ "github.com/xtls/xray-core/app/dns/fakedns" _ "github.com/xtls/xray-core/app/log" _ "github.com/xtls/xray-core/app/policy" _ "github.com/xtls/xray-core/app/reverse" diff --git a/proxy/dns/dns.go b/proxy/dns/dns.go index b424a623..01c02af6 100644 --- a/proxy/dns/dns.go +++ b/proxy/dns/dns.go @@ -36,25 +36,13 @@ type ownLinkVerifier interface { } type Handler struct { - ipv4Lookup dns.IPv4Lookup - ipv6Lookup dns.IPv6Lookup + client dns.Client ownLinkVerifier ownLinkVerifier server net.Destination } func (h *Handler) Init(config *Config, dnsClient dns.Client) error { - ipv4lookup, ok := dnsClient.(dns.IPv4Lookup) - if !ok { - return newError("dns.Client doesn't implement IPv4Lookup") - } - h.ipv4Lookup = ipv4lookup - - ipv6lookup, ok := dnsClient.(dns.IPv6Lookup) - if !ok { - return newError("dns.Client doesn't implement IPv6Lookup") - } - h.ipv6Lookup = ipv6lookup - + h.client = dnsClient if v, ok := dnsClient.(ownLinkVerifier); ok { h.ownLinkVerifier = v } @@ -209,11 +197,21 @@ 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) + ips, err = h.client.LookupIP(domain, dns.IPOption{ + IPv4Enable: true, + IPv6Enable: false, + FakeEnable: true, + }) case dnsmessage.TypeAAAA: - ips, err = h.ipv6Lookup.LookupIPv6(domain) + ips, err = h.client.LookupIP(domain, dns.IPOption{ + IPv4Enable: false, + IPv6Enable: true, + FakeEnable: true, + }) } rcode := dns.RCodeFromError(err) @@ -241,7 +239,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 diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 7117930b..a1f99cc4 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -59,19 +59,26 @@ func (h *Handler) policy() policy.Session { } func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address { - var lookupFunc func(string) ([]net.IP, error) = h.dns.LookupIP - + var option dns.IPOption = dns.IPOption{ + IPv4Enable: true, + IPv6Enable: true, + FakeEnable: false, + } if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) { - if lookupIPv4, ok := h.dns.(dns.IPv4Lookup); ok { - lookupFunc = lookupIPv4.LookupIPv4 + option = dns.IPOption{ + IPv4Enable: true, + IPv6Enable: false, + FakeEnable: false, } } else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) { - if lookupIPv6, ok := h.dns.(dns.IPv6Lookup); ok { - lookupFunc = lookupIPv6.LookupIPv6 + option = dns.IPOption{ + IPv4Enable: false, + IPv6Enable: true, + FakeEnable: false, } } - ips, err := lookupFunc(domain) + ips, err := h.dns.LookupIP(domain, option) if err != nil { newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx)) } @@ -125,7 +132,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte Address: ip, Port: dialDest.Port, } - newError("dialing to to ", dialDest).WriteToLog(session.ExportIDToError(ctx)) + newError("dialing to ", dialDest).WriteToLog(session.ExportIDToError(ctx)) } } diff --git a/testing/mocks/dns.go b/testing/mocks/dns.go index 98267107..73a219ed 100644 --- a/testing/mocks/dns.go +++ b/testing/mocks/dns.go @@ -6,6 +6,7 @@ package mocks import ( gomock "github.com/golang/mock/gomock" + dns "github.com/xtls/xray-core/features/dns" net "net" reflect "reflect" ) @@ -48,18 +49,18 @@ func (mr *DNSClientMockRecorder) Close() *gomock.Call { } // LookupIP mocks base method -func (m *DNSClient) LookupIP(arg0 string) ([]net.IP, error) { +func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "LookupIP", arg0) + ret := m.ctrl.Call(m, "LookupIP", arg0, arg1) ret0, _ := ret[0].([]net.IP) ret1, _ := ret[1].(error) return ret0, ret1 } // LookupIP indicates an expected call of LookupIP -func (mr *DNSClientMockRecorder) LookupIP(arg0 interface{}) *gomock.Call { +func (mr *DNSClientMockRecorder) LookupIP(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupIP", reflect.TypeOf((*DNSClient)(nil).LookupIP), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupIP", reflect.TypeOf((*DNSClient)(nil).LookupIP), arg0, arg1) } // Start mocks base method