mirror of
https://github.com/coredns/coredns.git
synced 2026-07-10 09:40:12 -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
|
||||
~~~
|
||||
|
||||
### 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 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
|
||||
|
||||
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,
|
||||
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 = `
|
||||
@@ -144,6 +156,8 @@ const hostsExample = `
|
||||
10.0.0.1 example.org
|
||||
::FFFF:10.0.0.2 example.com
|
||||
10.0.0.3 fallthrough-example.org
|
||||
1.2.3.4 aa.example.com
|
||||
5.6.7.8 *.apps.example.com
|
||||
reload 5s
|
||||
timeout 3600
|
||||
`
|
||||
|
||||
@@ -55,6 +55,10 @@ type Map struct {
|
||||
name4 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
|
||||
// including IPv6 address without zone identifier.
|
||||
// We don't support old-classful IP address notation.
|
||||
@@ -63,9 +67,11 @@ type Map struct {
|
||||
|
||||
func newMap() *Map {
|
||||
return &Map{
|
||||
name4: make(map[string][]net.IP),
|
||||
name6: make(map[string][]net.IP),
|
||||
addr: make(map[string][]string),
|
||||
name4: make(map[string][]net.IP),
|
||||
name6: make(map[string][]net.IP),
|
||||
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 {
|
||||
l += len(v6)
|
||||
}
|
||||
for _, v4 := range h.wildName4 {
|
||||
l += len(v4)
|
||||
}
|
||||
for _, v6 := range h.wildName6 {
|
||||
l += len(v6)
|
||||
}
|
||||
for _, a := range h.addr {
|
||||
l += len(a)
|
||||
}
|
||||
@@ -184,6 +196,15 @@ func (h *Hostsfile) parse(r io.Reader) *Map {
|
||||
// name is not in Origins
|
||||
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 {
|
||||
case 1:
|
||||
hmap.name4[name] = append(hmap.name4[name], addr)
|
||||
@@ -202,38 +223,41 @@ func (h *Hostsfile) parse(r io.Reader) *Map {
|
||||
return hmap
|
||||
}
|
||||
|
||||
// lookupStaticHost looks up the IP addresses for the given host from the hosts file.
|
||||
func (h *Hostsfile) lookupStaticHost(m map[string][]net.IP, host string) []net.IP {
|
||||
func (h *Hostsfile) lookupStaticHostLocked(m, wild 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()
|
||||
defer h.RUnlock()
|
||||
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ips, ok := m[host]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ipsCp := make([]net.IP, len(ips))
|
||||
copy(ipsCp, ips)
|
||||
return ipsCp
|
||||
ip1 := h.lookupStaticHostLocked(m, wild, host)
|
||||
ip2 := h.lookupStaticHostLocked(inlineM, inlineWild, host)
|
||||
return append(ip1, ip2...)
|
||||
}
|
||||
|
||||
// LookupStaticHostV4 looks up the IPv4 addresses for the given host from the hosts file.
|
||||
func (h *Hostsfile) LookupStaticHostV4(host string) []net.IP {
|
||||
host = strings.ToLower(host)
|
||||
ip1 := h.lookupStaticHost(h.hmap.name4, host)
|
||||
ip2 := h.lookupStaticHost(h.inline.name4, host)
|
||||
return append(ip1, ip2...)
|
||||
return h.lookupStaticHostFamily(host, h.hmap.name4, h.hmap.wildName4, h.inline.name4, h.inline.wildName4)
|
||||
}
|
||||
|
||||
// LookupStaticHostV6 looks up the IPv6 addresses for the given host from the hosts file.
|
||||
func (h *Hostsfile) LookupStaticHostV6(host string) []net.IP {
|
||||
host = strings.ToLower(host)
|
||||
ip1 := h.lookupStaticHost(h.hmap.name6, host)
|
||||
ip2 := h.lookupStaticHost(h.inline.name6, host)
|
||||
return append(ip1, ip2...)
|
||||
return h.lookupStaticHostFamily(host, h.hmap.name6, h.hmap.wildName6, h.inline.name6, h.inline.wildName6)
|
||||
}
|
||||
|
||||
// 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