mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-22 01:57:12 -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
|
||||
|
||||
addNameServer := func(endpoint *net.Endpoint) int {
|
||||
addNameServer := func(ns *NameServer) int {
|
||||
endpoint := ns.Address
|
||||
address := endpoint.Address.AsAddress()
|
||||
if address.Family().IsDomain() && address.Domain() == "localhost" {
|
||||
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://") {
|
||||
// URI schemed string treated as domain
|
||||
// DOH Local mode
|
||||
@ -137,7 +145,7 @@ func New(ctx context.Context, config *Config) (*Server, error) {
|
||||
if len(config.NameServers) > 0 {
|
||||
features.PrintDeprecatedFeatureWarning("simple DNS server")
|
||||
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
|
||||
|
||||
for _, ns := range config.NameServer {
|
||||
idx := addNameServer(ns.Address)
|
||||
idx := addNameServer(ns)
|
||||
|
||||
for _, domain := range ns.PrioritizedDomain {
|
||||
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]
|
||||
}
|
||||
|
||||
// skip domain without any dot
|
||||
if !strings.Contains(domain, ".") {
|
||||
return nil, newError("invalid domain name").AtWarning()
|
||||
}
|
||||
|
||||
ips := s.lookupStatic(domain, option, 0)
|
||||
if ips != nil && ips[0].Family().IsIP() {
|
||||
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)
|
||||
} else if q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA {
|
||||
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)
|
||||
@ -537,3 +558,199 @@ func TestIPMatch(t *testing.T) {
|
||||
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)
|
||||
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) {
|
||||
s.updateIP(req.domain, rec)
|
||||
}
|
||||
|
@ -299,6 +299,16 @@ func parseDomainRule(domain string) ([]*router.Domain, error) {
|
||||
case strings.HasPrefix(domain, "keyword:"):
|
||||
domainRule.Type = router.Domain_Plain
|
||||
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:
|
||||
domainRule.Type = router.Domain_Plain
|
||||
domainRule.Value = domain
|
||||
|
Loading…
Reference in New Issue
Block a user