Browse Source

support domain to domain mapping in static host

Darien Raymond 6 years ago
parent
commit
ffb3793b26
7 changed files with 180 additions and 57 deletions
  1. 52 41
      app/dns/config.pb.go
  2. 5 0
      app/dns/config.proto
  3. 22 11
      app/dns/hosts.go
  4. 2 2
      app/dns/hosts_test.go
  5. 33 2
      app/dns/server.go
  6. 65 0
      app/dns/server_test.go
  7. 1 1
      app/dns/udpns.go

+ 52 - 41
app/dns/config.pb.go

@@ -232,12 +232,15 @@ func (m *Config) GetTag() string {
 }
 }
 
 
 type Config_HostMapping struct {
 type Config_HostMapping struct {
-	Type                 DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=v2ray.core.app.dns.DomainMatchingType" json:"type,omitempty"`
-	Domain               string             `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
-	Ip                   [][]byte           `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}           `json:"-"`
-	XXX_unrecognized     []byte             `json:"-"`
-	XXX_sizecache        int32              `json:"-"`
+	Type   DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=v2ray.core.app.dns.DomainMatchingType" json:"type,omitempty"`
+	Domain string             `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
+	Ip     [][]byte           `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
+	// ProxiedDomain indicates the mapped domain has the same IP address on this domain. V2Ray will use this domain for IP queries.
+	// This field is only effective if ip is empty.
+	ProxiedDomain        string   `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 }
 
 
 func (m *Config_HostMapping) Reset()         { *m = Config_HostMapping{} }
 func (m *Config_HostMapping) Reset()         { *m = Config_HostMapping{} }
@@ -286,6 +289,13 @@ func (m *Config_HostMapping) GetIp() [][]byte {
 	return nil
 	return nil
 }
 }
 
 
+func (m *Config_HostMapping) GetProxiedDomain() string {
+	if m != nil {
+		return m.ProxiedDomain
+	}
+	return ""
+}
+
 func init() {
 func init() {
 	proto.RegisterEnum("v2ray.core.app.dns.DomainMatchingType", DomainMatchingType_name, DomainMatchingType_value)
 	proto.RegisterEnum("v2ray.core.app.dns.DomainMatchingType", DomainMatchingType_name, DomainMatchingType_value)
 	proto.RegisterType((*NameServer)(nil), "v2ray.core.app.dns.NameServer")
 	proto.RegisterType((*NameServer)(nil), "v2ray.core.app.dns.NameServer")
@@ -300,39 +310,40 @@ func init() {
 }
 }
 
 
 var fileDescriptor_ed5695198e3def8f = []byte{
 var fileDescriptor_ed5695198e3def8f = []byte{
-	// 530 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x53, 0xdb, 0x6e, 0xd3, 0x30,
-	0x18, 0x26, 0x49, 0xdb, 0xad, 0x7f, 0xc6, 0x54, 0x7c, 0x31, 0x45, 0x45, 0x82, 0x32, 0xc4, 0xa8,
-	0x40, 0x38, 0x52, 0x40, 0x02, 0x76, 0x33, 0xb1, 0xad, 0x88, 0x0a, 0x0d, 0x2a, 0x0f, 0x71, 0x01,
-	0x48, 0x95, 0x97, 0x98, 0xce, 0xa2, 0xb1, 0x8d, 0xed, 0x16, 0xc2, 0x2b, 0xf0, 0x08, 0xbc, 0x01,
-	0x4f, 0x89, 0x6a, 0x77, 0xb4, 0xb0, 0x0e, 0xb8, 0xe1, 0xce, 0x87, 0xef, 0x94, 0xef, 0x77, 0xe0,
-	0xe6, 0x34, 0xd3, 0xb4, 0xc2, 0xb9, 0x2c, 0xd3, 0x5c, 0x6a, 0x96, 0x52, 0xa5, 0xd2, 0x42, 0x98,
-	0x34, 0x97, 0xe2, 0x3d, 0x1f, 0x61, 0xa5, 0xa5, 0x95, 0x08, 0x9d, 0x81, 0x34, 0xc3, 0x54, 0x29,
-	0x5c, 0x08, 0xd3, 0xbe, 0xfd, 0x1b, 0x31, 0x97, 0x65, 0x29, 0x45, 0x2a, 0x98, 0x4d, 0x69, 0x51,
-	0x68, 0x66, 0x8c, 0x27, 0xb7, 0xef, 0x5e, 0x0c, 0x2c, 0x98, 0xb1, 0x5c, 0x50, 0xcb, 0xa5, 0xf0,
-	0xe0, 0xed, 0xaf, 0x21, 0xc0, 0x0b, 0x5a, 0xb2, 0x63, 0xa6, 0xa7, 0x4c, 0xa3, 0xc7, 0xb0, 0x36,
-	0x17, 0x4b, 0x82, 0x4e, 0xd0, 0x8d, 0xb3, 0xeb, 0x78, 0x29, 0x8a, 0x57, 0xc2, 0x82, 0x59, 0xdc,
-	0x13, 0x85, 0x92, 0x5c, 0x58, 0x72, 0x86, 0x47, 0xef, 0x00, 0x29, 0xcd, 0xa5, 0xe6, 0x96, 0x7f,
-	0x61, 0xc5, 0xb0, 0x90, 0x25, 0xe5, 0x22, 0x09, 0x3b, 0x51, 0x37, 0xce, 0xee, 0xe1, 0xf3, 0x1f,
-	0x84, 0x17, 0xb6, 0x78, 0xe0, 0x89, 0xd5, 0xa1, 0x23, 0x91, 0x2b, 0x4b, 0x42, 0xfe, 0xa8, 0x5d,
-	0xc0, 0xe6, 0xaf, 0x20, 0xb4, 0x0b, 0x35, 0x5b, 0x29, 0xe6, 0x72, 0x6e, 0x66, 0x3b, 0xab, 0x1c,
-	0x3c, 0xf2, 0x88, 0xda, 0xfc, 0x94, 0x8b, 0xd1, 0xab, 0x4a, 0x31, 0xe2, 0x38, 0x68, 0x0b, 0x1a,
-	0x3f, 0xf3, 0x05, 0xdd, 0x26, 0x99, 0xef, 0xb6, 0xbf, 0xd5, 0xa0, 0x71, 0xe0, 0x06, 0x81, 0x7a,
-	0x10, 0x2f, 0x02, 0xce, 0xda, 0x88, 0xfe, 0xa1, 0x8d, 0xfd, 0x30, 0x09, 0xc8, 0x32, 0x0f, 0xed,
-	0x41, 0x2c, 0x68, 0xc9, 0x86, 0xc6, 0xed, 0x93, 0xba, 0x93, 0xb9, 0xf6, 0xe7, 0x3a, 0x08, 0x88,
-	0xc5, 0x44, 0xf6, 0xa0, 0xfe, 0x4c, 0x1a, 0x6b, 0xe6, 0x4d, 0xde, 0x5a, 0x45, 0xf5, 0x91, 0xb1,
-	0xc3, 0xf5, 0x84, 0xd5, 0x95, 0xcb, 0xe1, 0x79, 0xe8, 0x2a, 0x34, 0xf3, 0x31, 0x67, 0xc2, 0x0e,
-	0xb9, 0x4a, 0xa2, 0x4e, 0xd0, 0xdd, 0x20, 0xeb, 0xfe, 0xa0, 0xaf, 0x50, 0x1f, 0x36, 0x8c, 0xa5,
-	0x96, 0xe7, 0xc3, 0x53, 0x67, 0x52, 0x73, 0x26, 0x3b, 0x7f, 0x31, 0x39, 0xa2, 0x4a, 0x71, 0x31,
-	0x22, 0xb1, 0xe7, 0x7a, 0x9f, 0x16, 0x44, 0x96, 0x8e, 0x92, 0x86, 0x2b, 0x74, 0xb6, 0x6c, 0xbf,
-	0x05, 0x58, 0x44, 0x9a, 0xdd, 0x7f, 0x60, 0x95, 0x1b, 0x57, 0x93, 0xcc, 0x96, 0xe8, 0x21, 0xd4,
-	0xa7, 0x74, 0x3c, 0x61, 0x6e, 0x08, 0x71, 0x76, 0xe3, 0x82, 0x72, 0xfb, 0x83, 0x97, 0x7a, 0xfe,
-	0x30, 0x3c, 0x7e, 0x37, 0x7c, 0x14, 0xb4, 0x3f, 0x42, 0xbc, 0x14, 0xe5, 0x7f, 0xbc, 0x06, 0xb4,
-	0x09, 0xa1, 0xab, 0x2c, 0xea, 0x6e, 0x90, 0x90, 0xab, 0x3b, 0x3d, 0x40, 0xe7, 0x35, 0xd0, 0x3a,
-	0xd4, 0x9e, 0x4e, 0xc6, 0xe3, 0xd6, 0x25, 0x74, 0x19, 0x9a, 0xc7, 0x93, 0x13, 0x4f, 0x6e, 0x05,
-	0x28, 0x86, 0xb5, 0xe7, 0xac, 0xfa, 0x24, 0x75, 0xd1, 0x0a, 0x51, 0x13, 0xea, 0x84, 0x8d, 0xd8,
-	0xe7, 0x56, 0xb4, 0xff, 0x00, 0xb6, 0x72, 0x59, 0xae, 0x48, 0x38, 0x08, 0xde, 0x44, 0x85, 0x30,
-	0xdf, 0x43, 0xf4, 0x3a, 0x23, 0xb4, 0xc2, 0x07, 0xb3, 0xbb, 0x27, 0x4a, 0xe1, 0x43, 0x61, 0x4e,
-	0x1a, 0xee, 0x7f, 0xbd, 0xff, 0x23, 0x00, 0x00, 0xff, 0xff, 0x83, 0x2b, 0x1c, 0x4c, 0x40, 0x04,
-	0x00, 0x00,
+	// 552 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x53, 0xd1, 0x6e, 0xd3, 0x30,
+	0x14, 0x25, 0x49, 0xdb, 0xad, 0x37, 0x5d, 0x55, 0xfc, 0x30, 0x45, 0x45, 0x82, 0x32, 0xb4, 0x51,
+	0x81, 0x70, 0xa4, 0x80, 0x04, 0xec, 0x65, 0x62, 0x5b, 0x11, 0x15, 0x1a, 0x54, 0x1e, 0xe2, 0x01,
+	0x90, 0x2a, 0x2f, 0x31, 0x9d, 0x45, 0x63, 0x5b, 0x8e, 0x5b, 0x16, 0x7e, 0x81, 0x1f, 0xe0, 0x1b,
+	0xf8, 0x0d, 0x7e, 0x0c, 0xd5, 0xee, 0x68, 0x61, 0x1d, 0xf0, 0xb2, 0xb7, 0xf8, 0xfa, 0x9c, 0x7b,
+	0x8e, 0xcf, 0xbd, 0x81, 0x3b, 0xd3, 0x44, 0xd3, 0x12, 0xa7, 0x32, 0x8f, 0x53, 0xa9, 0x59, 0x4c,
+	0x95, 0x8a, 0x33, 0x51, 0xc4, 0xa9, 0x14, 0x1f, 0xf9, 0x08, 0x2b, 0x2d, 0x8d, 0x44, 0xe8, 0x1c,
+	0xa4, 0x19, 0xa6, 0x4a, 0xe1, 0x4c, 0x14, 0xed, 0xbb, 0x7f, 0x10, 0x53, 0x99, 0xe7, 0x52, 0xc4,
+	0x82, 0x99, 0x98, 0x66, 0x99, 0x66, 0x45, 0xe1, 0xc8, 0xed, 0xfb, 0x97, 0x03, 0x33, 0x56, 0x18,
+	0x2e, 0xa8, 0xe1, 0x52, 0x38, 0xf0, 0xd6, 0x57, 0x1f, 0xe0, 0x15, 0xcd, 0xd9, 0x31, 0xd3, 0x53,
+	0xa6, 0xd1, 0x53, 0x58, 0x9b, 0x37, 0x8b, 0xbc, 0x8e, 0xd7, 0x0d, 0x93, 0x5b, 0x78, 0xc9, 0x8a,
+	0xeb, 0x84, 0x05, 0x33, 0xb8, 0x27, 0x32, 0x25, 0xb9, 0x30, 0xe4, 0x1c, 0x8f, 0x3e, 0x00, 0x52,
+	0x9a, 0x4b, 0xcd, 0x0d, 0xff, 0xc2, 0xb2, 0x61, 0x26, 0x73, 0xca, 0x45, 0xe4, 0x77, 0x82, 0x6e,
+	0x98, 0x3c, 0xc0, 0x17, 0x1f, 0x84, 0x17, 0xb2, 0x78, 0xe0, 0x88, 0xe5, 0xa1, 0x25, 0x91, 0xeb,
+	0x4b, 0x8d, 0x5c, 0xa9, 0x9d, 0x41, 0xf3, 0x77, 0x10, 0xda, 0x85, 0x8a, 0x29, 0x15, 0xb3, 0x3e,
+	0x9b, 0xc9, 0xce, 0x2a, 0x05, 0x87, 0x3c, 0xa2, 0x26, 0x3d, 0xe5, 0x62, 0xf4, 0xa6, 0x54, 0x8c,
+	0x58, 0x0e, 0xda, 0x84, 0xda, 0x2f, 0x7f, 0x5e, 0xb7, 0x4e, 0xe6, 0xa7, 0xad, 0x1f, 0x15, 0xa8,
+	0x1d, 0xd8, 0x41, 0xa0, 0x1e, 0x84, 0x0b, 0x83, 0xb3, 0x34, 0x82, 0xff, 0x48, 0x63, 0xdf, 0x8f,
+	0x3c, 0xb2, 0xcc, 0x43, 0x7b, 0x10, 0x0a, 0x9a, 0xb3, 0x61, 0x61, 0xcf, 0x51, 0xd5, 0xb6, 0xb9,
+	0xf9, 0xf7, 0x38, 0x08, 0x88, 0xc5, 0x44, 0xf6, 0xa0, 0xfa, 0x42, 0x16, 0xa6, 0x98, 0x27, 0xb9,
+	0xbd, 0x8a, 0xea, 0x2c, 0x63, 0x8b, 0xeb, 0x09, 0xa3, 0x4b, 0xeb, 0xc3, 0xf1, 0xd0, 0x0d, 0xa8,
+	0xa7, 0x63, 0xce, 0x84, 0x19, 0x72, 0x15, 0x05, 0x1d, 0xaf, 0xdb, 0x20, 0xeb, 0xae, 0xd0, 0x57,
+	0xa8, 0x0f, 0x8d, 0xc2, 0x50, 0xc3, 0xd3, 0xe1, 0xa9, 0x15, 0xa9, 0x58, 0x91, 0x9d, 0x7f, 0x88,
+	0x1c, 0x51, 0xa5, 0xb8, 0x18, 0x91, 0xd0, 0x71, 0x9d, 0x4e, 0x0b, 0x02, 0x43, 0x47, 0x51, 0xcd,
+	0x06, 0x3a, 0xfb, 0x6c, 0xbf, 0x07, 0x58, 0x58, 0x9a, 0xdd, 0x7f, 0x62, 0xa5, 0x1d, 0x57, 0x9d,
+	0xcc, 0x3e, 0xd1, 0x63, 0xa8, 0x4e, 0xe9, 0x78, 0xc2, 0xec, 0x10, 0xc2, 0xe4, 0xf6, 0x25, 0xe1,
+	0xf6, 0x07, 0xaf, 0xf5, 0x7c, 0x31, 0x1c, 0x7e, 0xd7, 0x7f, 0xe2, 0xb5, 0xbf, 0x79, 0x10, 0x2e,
+	0x79, 0xb9, 0x8a, 0x75, 0x40, 0x4d, 0xf0, 0x6d, 0x66, 0x41, 0xb7, 0x41, 0x7c, 0xae, 0xd0, 0x36,
+	0x34, 0x95, 0x96, 0x67, 0x7c, 0xb1, 0xde, 0x15, 0x8b, 0xdf, 0x98, 0x57, 0x9d, 0xc0, 0xbd, 0x1e,
+	0xa0, 0x8b, 0x52, 0x68, 0x1d, 0x2a, 0xcf, 0x27, 0xe3, 0x71, 0xeb, 0x1a, 0xda, 0x80, 0xfa, 0xf1,
+	0xe4, 0xc4, 0x75, 0x68, 0x79, 0x28, 0x84, 0xb5, 0x97, 0xac, 0xfc, 0x2c, 0x75, 0xd6, 0xf2, 0x51,
+	0x1d, 0xaa, 0x84, 0x8d, 0xd8, 0x59, 0x2b, 0xd8, 0x7f, 0x04, 0x9b, 0xa9, 0xcc, 0x57, 0x3c, 0x64,
+	0xe0, 0xbd, 0x0b, 0x32, 0x51, 0x7c, 0xf7, 0xd1, 0xdb, 0x84, 0xd0, 0x12, 0x1f, 0xcc, 0xee, 0x9e,
+	0x29, 0x85, 0x0f, 0x45, 0x71, 0x52, 0xb3, 0xff, 0xf5, 0xc3, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff,
+	0x15, 0xed, 0x7b, 0x41, 0x68, 0x04, 0x00, 0x00,
 }
 }

+ 5 - 0
app/dns/config.proto

@@ -45,7 +45,12 @@ message Config {
   message HostMapping {
   message HostMapping {
     DomainMatchingType type = 1;
     DomainMatchingType type = 1;
     string domain = 2;
     string domain = 2;
+
     repeated bytes ip = 3;
     repeated bytes ip = 3;
+
+    // ProxiedDomain indicates the mapped domain has the same IP address on this domain. V2Ray will use this domain for IP queries.
+    // This field is only effective if ip is empty.
+    string proxied_domain = 4;
   }
   }
 
 
   repeated HostMapping static_hosts = 4;
   repeated HostMapping static_hosts = 4;

+ 22 - 11
app/dns/hosts.go

@@ -63,25 +63,32 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
 			return nil, newError("failed to create domain matcher").Base(err)
 			return nil, newError("failed to create domain matcher").Base(err)
 		}
 		}
 		id := g.Add(matcher)
 		id := g.Add(matcher)
-		ips := make([]net.Address, 0, len(mapping.Ip))
-		for _, ip := range mapping.Ip {
-			addr := net.IPAddress(ip)
-			if addr == nil {
-				return nil, newError("invalid IP address in static hosts: ", ip).AtWarning()
+		ips := make([]net.Address, 0, len(mapping.Ip)+1)
+		if len(mapping.Ip) > 0 {
+			for _, ip := range mapping.Ip {
+				addr := net.IPAddress(ip)
+				if addr == nil {
+					return nil, newError("invalid IP address in static hosts: ", ip).AtWarning()
+				}
+				ips = append(ips, addr)
 			}
 			}
-			ips = append(ips, addr)
+		} else if len(mapping.ProxiedDomain) > 0 {
+			ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
+		} else {
+			return nil, newError("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning()
 		}
 		}
+
 		sh.ips[id] = ips
 		sh.ips[id] = ips
 	}
 	}
 
 
 	return sh, nil
 	return sh, nil
 }
 }
 
 
-func filterIP(ips []net.Address, option IPOption) []net.IP {
-	filtered := make([]net.IP, 0, len(ips))
+func filterIP(ips []net.Address, option IPOption) []net.Address {
+	filtered := make([]net.Address, 0, len(ips))
 	for _, ip := range ips {
 	for _, ip := range ips {
 		if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {
 		if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {
-			filtered = append(filtered, ip.IP())
+			filtered = append(filtered, ip)
 		}
 		}
 	}
 	}
 	if len(filtered) == 0 {
 	if len(filtered) == 0 {
@@ -91,10 +98,14 @@ func filterIP(ips []net.Address, option IPOption) []net.IP {
 }
 }
 
 
 // LookupIP returns IP address for the given domain, if exists in this StaticHosts.
 // LookupIP returns IP address for the given domain, if exists in this StaticHosts.
-func (h *StaticHosts) LookupIP(domain string, option IPOption) []net.IP {
+func (h *StaticHosts) LookupIP(domain string, option IPOption) []net.Address {
 	id := h.matchers.Match(domain)
 	id := h.matchers.Match(domain)
 	if id == 0 {
 	if id == 0 {
 		return nil
 		return nil
 	}
 	}
-	return filterIP(h.ips[id], option)
+	ips := h.ips[id]
+	if len(ips) == 1 && ips[0].Family().IsDomain() {
+		return ips
+	}
+	return filterIP(ips, option)
 }
 }

+ 2 - 2
app/dns/hosts_test.go

@@ -38,7 +38,7 @@ func TestStaticHosts(t *testing.T) {
 		if len(ips) != 1 {
 		if len(ips) != 1 {
 			t.Error("expect 1 IP, but got ", len(ips))
 			t.Error("expect 1 IP, but got ", len(ips))
 		}
 		}
-		if diff := cmp.Diff([]byte(ips[0]), []byte{1, 1, 1, 1}); diff != "" {
+		if diff := cmp.Diff([]byte(ips[0].IP()), []byte{1, 1, 1, 1}); diff != "" {
 			t.Error(diff)
 			t.Error(diff)
 		}
 		}
 	}
 	}
@@ -51,7 +51,7 @@ func TestStaticHosts(t *testing.T) {
 		if len(ips) != 1 {
 		if len(ips) != 1 {
 			t.Error("expect 1 IP, but got ", len(ips))
 			t.Error("expect 1 IP, but got ", len(ips))
 		}
 		}
-		if diff := cmp.Diff([]byte(ips[0]), []byte{2, 2, 2, 2}); diff != "" {
+		if diff := cmp.Diff([]byte(ips[0].IP()), []byte{2, 2, 2, 2}); diff != "" {
 			t.Error(diff)
 			t.Error(diff)
 		}
 		}
 	}
 	}

+ 33 - 2
app/dns/server.go

@@ -155,9 +155,40 @@ func (s *Server) LookupIPv6(domain string) ([]net.IP, error) {
 	})
 	})
 }
 }
 
 
+func (s *Server) lookupStatic(domain string, option IPOption, depth int32) []net.Address {
+	ips := s.hosts.LookupIP(domain, option)
+	if ips == nil {
+		return nil
+	}
+	if ips[0].Family().IsDomain() && depth < 5 {
+		if newIPs := s.lookupStatic(ips[0].Domain(), option, depth+1); newIPs != nil {
+			return newIPs
+		}
+	}
+	return ips
+}
+
+func toNetIP(ips []net.Address) []net.IP {
+	if len(ips) == 0 {
+		return nil
+	}
+	netips := make([]net.IP, 0, len(ips))
+	for _, ip := range ips {
+		netips = append(netips, ip.IP())
+	}
+	return netips
+}
+
 func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, error) {
 func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, error) {
-	if ip := s.hosts.LookupIP(domain, option); len(ip) > 0 {
-		return ip, nil
+	ips := s.lookupStatic(domain, option, 0)
+	if ips != nil && ips[0].Family().IsIP() {
+		return toNetIP(ips), nil
+	}
+
+	if ips != nil && ips[0].Family().IsDomain() {
+		newdomain := ips[0].Domain()
+		newError("domain replaced: ", domain, " -> ", newdomain).WriteToLog()
+		domain = newdomain
 	}
 	}
 
 
 	var lastErr error
 	var lastErr error

+ 65 - 0
app/dns/server_test.go

@@ -339,3 +339,68 @@ func TestUDPServerIPv6(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestStaticHostDomain(t *testing.T) {
+	port := udp.PickPort()
+
+	dnsServer := dns.Server{
+		Addr:    "127.0.0.1:" + port.String(),
+		Net:     "udp",
+		Handler: &staticHandler{},
+		UDPSize: 1200,
+	}
+
+	go dnsServer.ListenAndServe()
+	time.Sleep(time.Second)
+
+	config := &core.Config{
+		App: []*serial.TypedMessage{
+			serial.ToTypedMessage(&Config{
+				NameServers: []*net.Endpoint{
+					{
+						Network: net.Network_UDP,
+						Address: &net.IPOrDomain{
+							Address: &net.IPOrDomain_Ip{
+								Ip: []byte{127, 0, 0, 1},
+							},
+						},
+						Port: uint32(port),
+					},
+				},
+				StaticHosts: []*Config_HostMapping{
+					{
+						Type:          DomainMatchingType_Full,
+						Domain:        "example.com",
+						ProxiedDomain: "google.com",
+					},
+				},
+			}),
+			serial.ToTypedMessage(&dispatcher.Config{}),
+			serial.ToTypedMessage(&proxyman.OutboundConfig{}),
+			serial.ToTypedMessage(&policy.Config{}),
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
+			},
+		},
+	}
+
+	v, err := core.New(config)
+	common.Must(err)
+
+	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
+
+	{
+		ips, err := client.LookupIP("example.com")
+		if err != nil {
+			t.Fatal("unexpected error: ", err)
+		}
+
+		if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" {
+			t.Fatal(r)
+		}
+	}
+
+	dnsServer.Shutdown()
+}

+ 1 - 1
app/dns/udpns.go

@@ -324,7 +324,7 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option IPOption) []n
 				ips = append(ips, rec.IP)
 				ips = append(ips, rec.IP)
 			}
 			}
 		}
 		}
-		return filterIP(ips, option)
+		return toNetIP(filterIP(ips, option))
 	}
 	}
 	return nil
 	return nil
 }
 }