mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-21 17:46:58 -05:00
Merge pull request #89 from Vigilans/vigilans/dns-dotless-domain
Dotless and local domain support in built-in DNS
This commit is contained in:
commit
111d48130d
@ -86,10 +86,18 @@ func New(ctx context.Context, config *Config) (*Server, error) {
|
|||||||
}
|
}
|
||||||
server.hosts = hosts
|
server.hosts = hosts
|
||||||
|
|
||||||
addNameServer := func(endpoint *net.Endpoint) int {
|
addNameServer := func(ns *NameServer) int {
|
||||||
|
endpoint := ns.Address
|
||||||
address := endpoint.Address.AsAddress()
|
address := endpoint.Address.AsAddress()
|
||||||
if address.Family().IsDomain() && address.Domain() == "localhost" {
|
if address.Family().IsDomain() && address.Domain() == "localhost" {
|
||||||
server.clients = append(server.clients, NewLocalNameServer())
|
server.clients = append(server.clients, NewLocalNameServer())
|
||||||
|
if len(ns.PrioritizedDomain) == 0 { // Priotize local domain with .local domain or without any dot to local DNS
|
||||||
|
ns.PrioritizedDomain = []*NameServer_PriorityDomain{
|
||||||
|
{Type: DomainMatchingType_Regex, Domain: "^[^.]*$"}, // This will only match domain without any dot
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https+local://") {
|
} else if address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https+local://") {
|
||||||
// URI schemed string treated as domain
|
// URI schemed string treated as domain
|
||||||
// DOH Local mode
|
// DOH Local mode
|
||||||
@ -137,7 +145,7 @@ func New(ctx context.Context, config *Config) (*Server, error) {
|
|||||||
if len(config.NameServers) > 0 {
|
if len(config.NameServers) > 0 {
|
||||||
features.PrintDeprecatedFeatureWarning("simple DNS server")
|
features.PrintDeprecatedFeatureWarning("simple DNS server")
|
||||||
for _, destPB := range config.NameServers {
|
for _, destPB := range config.NameServers {
|
||||||
addNameServer(destPB)
|
addNameServer(&NameServer{Address: destPB})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +156,7 @@ func New(ctx context.Context, config *Config) (*Server, error) {
|
|||||||
var geoIPMatcherContainer router.GeoIPMatcherContainer
|
var geoIPMatcherContainer router.GeoIPMatcherContainer
|
||||||
|
|
||||||
for _, ns := range config.NameServer {
|
for _, ns := range config.NameServer {
|
||||||
idx := addNameServer(ns.Address)
|
idx := addNameServer(ns)
|
||||||
|
|
||||||
for _, domain := range ns.PrioritizedDomain {
|
for _, domain := range ns.PrioritizedDomain {
|
||||||
matcher, err := toStrMatcher(domain.Type, domain.Domain)
|
matcher, err := toStrMatcher(domain.Type, domain.Domain)
|
||||||
@ -307,11 +315,6 @@ func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, err
|
|||||||
domain = domain[:len(domain)-1]
|
domain = domain[:len(domain)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip domain without any dot
|
|
||||||
if !strings.Contains(domain, ".") {
|
|
||||||
return nil, newError("invalid domain name").AtWarning()
|
|
||||||
}
|
|
||||||
|
|
||||||
ips := s.lookupStatic(domain, option, 0)
|
ips := s.lookupStatic(domain, option, 0)
|
||||||
if ips != nil && ips[0].Family().IsIP() {
|
if ips != nil && ips[0].Family().IsIP() {
|
||||||
newError("returning ", len(ips), " IPs for domain ", domain).WriteToLog()
|
newError("returning ", len(ips), " IPs for domain ", domain).WriteToLog()
|
||||||
|
@ -63,6 +63,27 @@ func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
ans.Answer = append(ans.Answer, rr)
|
ans.Answer = append(ans.Answer, rr)
|
||||||
} else if q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA {
|
} else if q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA {
|
||||||
ans.MsgHdr.Rcode = dns.RcodeNameError
|
ans.MsgHdr.Rcode = dns.RcodeNameError
|
||||||
|
} else if q.Name == "hostname." && q.Qtype == dns.TypeA {
|
||||||
|
rr, _ := dns.NewRR("hostname. IN A 127.0.0.1")
|
||||||
|
ans.Answer = append(ans.Answer, rr)
|
||||||
|
} else if q.Name == "hostname.local." && q.Qtype == dns.TypeA {
|
||||||
|
rr, _ := dns.NewRR("hostname.local. IN A 127.0.0.1")
|
||||||
|
ans.Answer = append(ans.Answer, rr)
|
||||||
|
} else if q.Name == "hostname.localdomain." && q.Qtype == dns.TypeA {
|
||||||
|
rr, _ := dns.NewRR("hostname.localdomain. IN A 127.0.0.1")
|
||||||
|
ans.Answer = append(ans.Answer, rr)
|
||||||
|
} else if q.Name == "localhost." && q.Qtype == dns.TypeA {
|
||||||
|
rr, _ := dns.NewRR("localhost. IN A 127.0.0.2")
|
||||||
|
ans.Answer = append(ans.Answer, rr)
|
||||||
|
} else if q.Name == "localhost-a." && q.Qtype == dns.TypeA {
|
||||||
|
rr, _ := dns.NewRR("localhost-a. IN A 127.0.0.3")
|
||||||
|
ans.Answer = append(ans.Answer, rr)
|
||||||
|
} else if q.Name == "localhost-b." && q.Qtype == dns.TypeA {
|
||||||
|
rr, _ := dns.NewRR("localhost-b. IN A 127.0.0.4")
|
||||||
|
ans.Answer = append(ans.Answer, rr)
|
||||||
|
} else if q.Name == "Mijia\\ Cloud." && q.Qtype == dns.TypeA {
|
||||||
|
rr, _ := dns.NewRR("Mijia\\ Cloud. IN A 127.0.0.1")
|
||||||
|
ans.Answer = append(ans.Answer, rr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.WriteMsg(ans)
|
w.WriteMsg(ans)
|
||||||
@ -537,3 +558,199 @@ func TestIPMatch(t *testing.T) {
|
|||||||
t.Error("DNS query doesn't finish in 2 seconds.")
|
t.Error("DNS query doesn't finish in 2 seconds.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalDomain(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: 9999, /* unreachable */
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServer: []*NameServer{
|
||||||
|
{
|
||||||
|
Address: &net.Endpoint{
|
||||||
|
Network: net.Network_UDP,
|
||||||
|
Address: &net.IPOrDomain{
|
||||||
|
Address: &net.IPOrDomain_Ip{
|
||||||
|
Ip: []byte{127, 0, 0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Port: uint32(port),
|
||||||
|
},
|
||||||
|
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||||
|
// Equivalent of dotless:localhost
|
||||||
|
{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
|
||||||
|
},
|
||||||
|
Geoip: []*router.GeoIP{
|
||||||
|
{ // Will match localhost, localhost-a and localhost-b,
|
||||||
|
CountryCode: "local",
|
||||||
|
Cidr: []*router.CIDR{
|
||||||
|
{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
|
||||||
|
{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
|
||||||
|
{Ip: []byte{127, 0, 0, 4}, Prefix: 32},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: &net.Endpoint{
|
||||||
|
Network: net.Network_UDP,
|
||||||
|
Address: &net.IPOrDomain{
|
||||||
|
Address: &net.IPOrDomain_Ip{
|
||||||
|
Ip: []byte{127, 0, 0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Port: uint32(port),
|
||||||
|
},
|
||||||
|
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||||
|
// Equivalent of dotless: and domain:local
|
||||||
|
{Type: DomainMatchingType_Regex, Domain: "^[^.]*$"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
StaticHosts: []*Config_HostMapping{
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Full,
|
||||||
|
Domain: "hostnamestatic",
|
||||||
|
Ip: [][]byte{{127, 0, 0, 53}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Full,
|
||||||
|
Domain: "hostnamealias",
|
||||||
|
ProxiedDomain: "hostname.localdomain",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
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)
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
{ // Will match dotless:
|
||||||
|
ips, err := client.LookupIP("hostname")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Will match domain:local
|
||||||
|
ips, err := client.LookupIP("hostname.local")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Will match static ip
|
||||||
|
ips, err := client.LookupIP("hostnamestatic")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 53}}); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Will match domain replacing
|
||||||
|
ips, err := client.LookupIP("hostnamealias")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
|
||||||
|
ips, err := client.LookupIP("localhost")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 2}}); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
||||||
|
ips, err := client.LookupIP("localhost-a")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 3}}); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
||||||
|
ips, err := client.LookupIP("localhost-b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 4}}); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Will match dotless:
|
||||||
|
ips, err := client.LookupIP("Mijia Cloud")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endTime := time.Now()
|
||||||
|
if startTime.After(endTime.Add(time.Second * 2)) {
|
||||||
|
t.Error("DNS query doesn't finish in 2 seconds.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -134,7 +134,7 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
|
|||||||
}
|
}
|
||||||
|
|
||||||
elapsed := time.Since(req.start)
|
elapsed := time.Since(req.start)
|
||||||
newError(s.name, " got answere: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
|
newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
|
||||||
if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) {
|
if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) {
|
||||||
s.updateIP(req.domain, rec)
|
s.updateIP(req.domain, rec)
|
||||||
}
|
}
|
||||||
|
@ -299,6 +299,16 @@ func parseDomainRule(domain string) ([]*router.Domain, error) {
|
|||||||
case strings.HasPrefix(domain, "keyword:"):
|
case strings.HasPrefix(domain, "keyword:"):
|
||||||
domainRule.Type = router.Domain_Plain
|
domainRule.Type = router.Domain_Plain
|
||||||
domainRule.Value = domain[8:]
|
domainRule.Value = domain[8:]
|
||||||
|
case strings.HasPrefix(domain, "dotless:"):
|
||||||
|
domainRule.Type = router.Domain_Regex
|
||||||
|
switch substr := domain[8:]; {
|
||||||
|
case substr == "":
|
||||||
|
domainRule.Value = "^[^.]*$"
|
||||||
|
case !strings.Contains(substr, "."):
|
||||||
|
domainRule.Value = "^[^.]*" + substr + "[^.]*$"
|
||||||
|
default:
|
||||||
|
return nil, newError("Substr in dotless rule should not contain a dot: ", substr)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
domainRule.Type = router.Domain_Plain
|
domainRule.Type = router.Domain_Plain
|
||||||
domainRule.Value = domain
|
domainRule.Value = domain
|
||||||
|
Loading…
Reference in New Issue
Block a user