mirror of
https://github.com/coredns/coredns.git
synced 2026-07-15 12:10:11 -04:00
plugin/hosts: add wildcard support (#8185)
* plugin/hosts: add wildcard support for owner names Signed-off-by: youknowforsearch <amirhebrahimzader@gmail.com> * plugin/hosts: document wildcard owner name support Signed-off-by: youknowforsearch <amirhebrahimzader@gmail.com> * plugin/hosts: remove unused lookupStaticHost Signed-off-by: Amirhossein Ebrahimzade <amirhossein.e@smartech.ir> --------- Signed-off-by: youknowforsearch <amirhebrahimzader@gmail.com> Signed-off-by: Amirhossein Ebrahimzade <amirhossein.e@smartech.ir> Co-authored-by: Amirhossein Ebrahimzade <amirhossein.e@smartech.ir>
This commit is contained in:
committed by
GitHub
parent
e45ad5f87a
commit
6805f6f8a0
@@ -37,6 +37,28 @@ Examples:
|
|||||||
fdfc:a744:27b5:3b0e::1 example.com example
|
fdfc:a744:27b5:3b0e::1 example.com example
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
### Wildcard records
|
||||||
|
|
||||||
|
Owner names may use a `*` as the leftmost label to match one additional label below
|
||||||
|
that name. This follows the same wildcard semantics as the *file* plugin.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
~~~
|
||||||
|
192.168.1.10 *.example.com
|
||||||
|
192.168.1.11 a.example.com
|
||||||
|
192.168.1.12 b.example.com
|
||||||
|
~~~
|
||||||
|
|
||||||
|
With the entries above:
|
||||||
|
|
||||||
|
* `a.example.com` and `b.example.com` resolve to their explicit addresses.
|
||||||
|
* `apps.example.com` resolves to `192.168.1.10`.
|
||||||
|
* `example.com` does not match the wildcard (the zone apex is excluded).
|
||||||
|
* `deep.apps.example.com` does not match `*.example.com` (only one label is matched).
|
||||||
|
|
||||||
|
Wildcard entries do not generate PTR records.
|
||||||
|
|
||||||
### PTR records
|
### PTR records
|
||||||
|
|
||||||
PTR records for reverse lookups are generated automatically by CoreDNS (based on the hosts file
|
PTR records for reverse lookups are generated automatically by CoreDNS (based on the hosts file
|
||||||
@@ -120,6 +142,20 @@ example.hosts example.org {
|
|||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
Resolve all single-label subdomains of `example.com` to one address, with explicit
|
||||||
|
exceptions, and fall through for everything else under `example.com`.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
. {
|
||||||
|
hosts example.hosts example.com {
|
||||||
|
192.168.1.10 *.example.com
|
||||||
|
192.168.1.11 www.example.com
|
||||||
|
fallthrough example.com
|
||||||
|
}
|
||||||
|
forward . 8.8.8.8
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
The form of the entries in the `/etc/hosts` file are based on IETF [RFC 952](https://tools.ietf.org/html/rfc952) which was updated by IETF [RFC 1123](https://tools.ietf.org/html/rfc1123).
|
The form of the entries in the `/etc/hosts` file are based on IETF [RFC 952](https://tools.ietf.org/html/rfc952) which was updated by IETF [RFC 1123](https://tools.ietf.org/html/rfc1123).
|
||||||
|
|||||||
@@ -136,6 +136,18 @@ var hostsTestCases = []test.Case{
|
|||||||
Qname: "fallthrough-example.org.", Qtype: dns.TypeAAAA,
|
Qname: "fallthrough-example.org.", Qtype: dns.TypeAAAA,
|
||||||
Answer: []dns.RR{}, Rcode: dns.RcodeSuccess,
|
Answer: []dns.RR{}, Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Qname: "apps.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("apps.example.com. 3600 IN A 5.6.7.8"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "aa.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("aa.example.com. 3600 IN A 1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const hostsExample = `
|
const hostsExample = `
|
||||||
@@ -144,6 +156,8 @@ const hostsExample = `
|
|||||||
10.0.0.1 example.org
|
10.0.0.1 example.org
|
||||||
::FFFF:10.0.0.2 example.com
|
::FFFF:10.0.0.2 example.com
|
||||||
10.0.0.3 fallthrough-example.org
|
10.0.0.3 fallthrough-example.org
|
||||||
|
1.2.3.4 aa.example.com
|
||||||
|
5.6.7.8 *.apps.example.com
|
||||||
reload 5s
|
reload 5s
|
||||||
timeout 3600
|
timeout 3600
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ type Map struct {
|
|||||||
name4 map[string][]net.IP
|
name4 map[string][]net.IP
|
||||||
name6 map[string][]net.IP
|
name6 map[string][]net.IP
|
||||||
|
|
||||||
|
// Wildcard owner names (e.g. *.example.com.) map to IP addresses.
|
||||||
|
wildName4 map[string][]net.IP
|
||||||
|
wildName6 map[string][]net.IP
|
||||||
|
|
||||||
// Key for the list of host names must be a literal IP address
|
// Key for the list of host names must be a literal IP address
|
||||||
// including IPv6 address without zone identifier.
|
// including IPv6 address without zone identifier.
|
||||||
// We don't support old-classful IP address notation.
|
// We don't support old-classful IP address notation.
|
||||||
@@ -63,9 +67,11 @@ type Map struct {
|
|||||||
|
|
||||||
func newMap() *Map {
|
func newMap() *Map {
|
||||||
return &Map{
|
return &Map{
|
||||||
name4: make(map[string][]net.IP),
|
name4: make(map[string][]net.IP),
|
||||||
name6: make(map[string][]net.IP),
|
name6: make(map[string][]net.IP),
|
||||||
addr: make(map[string][]string),
|
wildName4: make(map[string][]net.IP),
|
||||||
|
wildName6: make(map[string][]net.IP),
|
||||||
|
addr: make(map[string][]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +84,12 @@ func (h *Map) Len() int {
|
|||||||
for _, v6 := range h.name6 {
|
for _, v6 := range h.name6 {
|
||||||
l += len(v6)
|
l += len(v6)
|
||||||
}
|
}
|
||||||
|
for _, v4 := range h.wildName4 {
|
||||||
|
l += len(v4)
|
||||||
|
}
|
||||||
|
for _, v6 := range h.wildName6 {
|
||||||
|
l += len(v6)
|
||||||
|
}
|
||||||
for _, a := range h.addr {
|
for _, a := range h.addr {
|
||||||
l += len(a)
|
l += len(a)
|
||||||
}
|
}
|
||||||
@@ -184,6 +196,15 @@ func (h *Hostsfile) parse(r io.Reader) *Map {
|
|||||||
// name is not in Origins
|
// name is not in Origins
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if isWildcardName(name) {
|
||||||
|
switch family {
|
||||||
|
case 1:
|
||||||
|
hmap.wildName4[name] = append(hmap.wildName4[name], addr)
|
||||||
|
case 2:
|
||||||
|
hmap.wildName6[name] = append(hmap.wildName6[name], addr)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch family {
|
switch family {
|
||||||
case 1:
|
case 1:
|
||||||
hmap.name4[name] = append(hmap.name4[name], addr)
|
hmap.name4[name] = append(hmap.name4[name], addr)
|
||||||
@@ -202,38 +223,41 @@ func (h *Hostsfile) parse(r io.Reader) *Map {
|
|||||||
return hmap
|
return hmap
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupStaticHost looks up the IP addresses for the given host from the hosts file.
|
func (h *Hostsfile) lookupStaticHostLocked(m, wild map[string][]net.IP, host string) []net.IP {
|
||||||
func (h *Hostsfile) lookupStaticHost(m map[string][]net.IP, host string) []net.IP {
|
if ips, ok := m[host]; ok {
|
||||||
|
ipsCp := make([]net.IP, len(ips))
|
||||||
|
copy(ipsCp, ips)
|
||||||
|
return ipsCp
|
||||||
|
}
|
||||||
|
if pattern := replaceWithAsteriskLabel(host); pattern != "" {
|
||||||
|
if ips, ok := wild[pattern]; ok {
|
||||||
|
ipsCp := make([]net.IP, len(ips))
|
||||||
|
copy(ipsCp, ips)
|
||||||
|
return ipsCp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hostsfile) lookupStaticHostFamily(host string, m, wild, inlineM, inlineWild map[string][]net.IP) []net.IP {
|
||||||
|
host = strings.ToLower(host)
|
||||||
|
|
||||||
h.RLock()
|
h.RLock()
|
||||||
defer h.RUnlock()
|
defer h.RUnlock()
|
||||||
|
|
||||||
if len(m) == 0 {
|
ip1 := h.lookupStaticHostLocked(m, wild, host)
|
||||||
return nil
|
ip2 := h.lookupStaticHostLocked(inlineM, inlineWild, host)
|
||||||
}
|
return append(ip1, ip2...)
|
||||||
|
|
||||||
ips, ok := m[host]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ipsCp := make([]net.IP, len(ips))
|
|
||||||
copy(ipsCp, ips)
|
|
||||||
return ipsCp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupStaticHostV4 looks up the IPv4 addresses for the given host from the hosts file.
|
// LookupStaticHostV4 looks up the IPv4 addresses for the given host from the hosts file.
|
||||||
func (h *Hostsfile) LookupStaticHostV4(host string) []net.IP {
|
func (h *Hostsfile) LookupStaticHostV4(host string) []net.IP {
|
||||||
host = strings.ToLower(host)
|
return h.lookupStaticHostFamily(host, h.hmap.name4, h.hmap.wildName4, h.inline.name4, h.inline.wildName4)
|
||||||
ip1 := h.lookupStaticHost(h.hmap.name4, host)
|
|
||||||
ip2 := h.lookupStaticHost(h.inline.name4, host)
|
|
||||||
return append(ip1, ip2...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupStaticHostV6 looks up the IPv6 addresses for the given host from the hosts file.
|
// LookupStaticHostV6 looks up the IPv6 addresses for the given host from the hosts file.
|
||||||
func (h *Hostsfile) LookupStaticHostV6(host string) []net.IP {
|
func (h *Hostsfile) LookupStaticHostV6(host string) []net.IP {
|
||||||
host = strings.ToLower(host)
|
return h.lookupStaticHostFamily(host, h.hmap.name6, h.hmap.wildName6, h.inline.name6, h.inline.wildName6)
|
||||||
ip1 := h.lookupStaticHost(h.hmap.name6, host)
|
|
||||||
ip2 := h.lookupStaticHost(h.inline.name6, host)
|
|
||||||
return append(ip1, ip2...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupStaticAddr looks up the hosts for the given address from the hosts file.
|
// LookupStaticAddr looks up the hosts for the given address from the hosts file.
|
||||||
|
|||||||
18
plugin/hosts/wildcard.go
Normal file
18
plugin/hosts/wildcard.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package hosts
|
||||||
|
|
||||||
|
import "github.com/miekg/dns"
|
||||||
|
|
||||||
|
// isWildcardName reports whether name is a wildcard owner name (*.example.com.).
|
||||||
|
func isWildcardName(name string) bool {
|
||||||
|
return len(name) >= 2 && name[0] == '*' && name[1] == '.'
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceWithAsteriskLabel replaces the leftmost label with '*'.
|
||||||
|
func replaceWithAsteriskLabel(qname string) string {
|
||||||
|
i, shot := dns.NextLabel(qname, 0)
|
||||||
|
if shot {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "*." + qname[i:]
|
||||||
|
}
|
||||||
58
plugin/hosts/wildcard_test.go
Normal file
58
plugin/hosts/wildcard_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package hosts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReplaceWithAsteriskLabel(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in, out string
|
||||||
|
}{
|
||||||
|
{".", ""},
|
||||||
|
{"example.com.", "*.com."},
|
||||||
|
{"foo.example.com.", "*.example.com."},
|
||||||
|
{"bar.intern.example.com.", "*.intern.example.com."},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
got := replaceWithAsteriskLabel(tc.in)
|
||||||
|
if got != tc.out {
|
||||||
|
t.Errorf("replaceWithAsteriskLabel(%q) = %q, want %q", tc.in, got, tc.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceWithAsteriskLabelApex(t *testing.T) {
|
||||||
|
// Apex names produce a *.parent pattern but must not match *.zone wildcards.
|
||||||
|
if got := replaceWithAsteriskLabel("example.com."); got != "*.com." {
|
||||||
|
t.Fatalf("replaceWithAsteriskLabel(example.com.) = %q, want *.com.", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupWildcardHost(t *testing.T) {
|
||||||
|
const hosts = `
|
||||||
|
127.0.0.53 *.example.org
|
||||||
|
127.0.1.52 *.intern.example.org
|
||||||
|
127.0.0.54 foo.example.org
|
||||||
|
192.168.33.10 *.example.com
|
||||||
|
192.168.33.11 a.example.com
|
||||||
|
192.168.33.12 b.example.com
|
||||||
|
`
|
||||||
|
|
||||||
|
h := testHostsfile(hosts)
|
||||||
|
|
||||||
|
tests := []staticHostEntry{
|
||||||
|
{"foo.example.org.", []string{"127.0.0.54"}, []string{}},
|
||||||
|
{"bar.example.org.", []string{"127.0.0.53"}, []string{}},
|
||||||
|
{"bar.intern.example.org.", []string{"127.0.1.52"}, []string{}},
|
||||||
|
{"a.example.com.", []string{"192.168.33.11"}, []string{}},
|
||||||
|
{"b.example.com.", []string{"192.168.33.12"}, []string{}},
|
||||||
|
{"c.example.com.", []string{"192.168.33.10"}, []string{}},
|
||||||
|
{"example.com.", []string{}, []string{}},
|
||||||
|
{"deep.foo.example.org.", []string{}, []string{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ent := range tests {
|
||||||
|
testStaticHost(t, ent, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user