mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 08:14:18 -04:00
Add inline support for middleware/hosts (#1072)
This fix add inline support for middleware/hosts so that
it is possible to specify hosts file insides the Corefile:
```
hosts inline example.org {
10.0.0.1 example.org
fallthrough
}
```
This fix fixes 999.
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
@@ -11,6 +11,7 @@ available hosts files that block access to advertising servers.
|
||||
|
||||
~~~
|
||||
hosts [FILE [ZONES...]] {
|
||||
[INLINE]
|
||||
fallthrough
|
||||
}
|
||||
~~~
|
||||
@@ -18,7 +19,10 @@ hosts [FILE [ZONES...]] {
|
||||
* **FILE** the hosts file to read and parse. If the path is relative the path from the *root*
|
||||
directive will be prepended to it. Defaults to /etc/hosts if omitted
|
||||
* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block
|
||||
are used.
|
||||
are used.
|
||||
* **INLINE** the hosts file contents inlined in Corefile. If there are any lines before fallthrough
|
||||
then all of them will be treated as the additional content for hosts file. The specified hosts
|
||||
file path will still be read but entries will be overrided.
|
||||
* `fallthrough` If zone matches and no record can be generated, pass request to the next plugin.
|
||||
|
||||
## Examples
|
||||
@@ -43,3 +47,12 @@ hosts example.hosts example.org example.net {
|
||||
fallthrough
|
||||
}
|
||||
~~~
|
||||
|
||||
Load hosts file inlined in Corefile.
|
||||
|
||||
~~~
|
||||
hosts example.hosts example.org {
|
||||
10.0.0.1 example.org
|
||||
fallthrough
|
||||
}
|
||||
~~~
|
||||
|
||||
@@ -53,6 +53,11 @@ type Hostsfile struct {
|
||||
// We don't support old-classful IP address notation.
|
||||
byAddr map[string][]string
|
||||
|
||||
// inline saves the hosts file is inlined in Corefile
|
||||
// We need a copy here as we want to use inline to override
|
||||
// the default /etc/hosts
|
||||
inline []string
|
||||
|
||||
expire time.Time
|
||||
path string
|
||||
mtime time.Time
|
||||
@@ -74,6 +79,10 @@ func (h *Hostsfile) ReadHosts() {
|
||||
|
||||
var file *os.File
|
||||
if file, _ = os.Open(h.path); file == nil {
|
||||
// If this is the first time then we will try to parse inline
|
||||
if len(h.byAddr) == 0 && len(h.inline) > 0 {
|
||||
h.Parse(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
@@ -92,7 +101,12 @@ func (h *Hostsfile) Parse(file io.Reader) {
|
||||
hsv6 := make(map[string][]net.IP)
|
||||
is := make(map[string][]string)
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
var readers []io.Reader
|
||||
if file != nil {
|
||||
readers = append(readers, file)
|
||||
}
|
||||
readers = append(readers, strings.NewReader(strings.Join(h.inline, "\n")))
|
||||
scanner := bufio.NewScanner(io.MultiReader(readers...))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Bytes()
|
||||
if i := bytes.Index(line, []byte{'#'}); i >= 0 {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
@@ -80,6 +81,11 @@ func hostsParse(c *caddy.Controller) (Hosts, error) {
|
||||
}
|
||||
return h, c.ArgErr()
|
||||
default:
|
||||
if !h.Fallthrough {
|
||||
line := strings.Join(append([]string{c.Val()}, c.RemainingArgs()...), " ")
|
||||
h.inline = append(h.inline, line)
|
||||
continue
|
||||
}
|
||||
return h, c.Errf("unknown property '%s'", c.Val())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,3 +84,77 @@ func TestHostsParse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostsInlineParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
inputFileRules string
|
||||
shouldErr bool
|
||||
expectedbyAddr map[string][]string
|
||||
expectedFallthrough bool
|
||||
}{
|
||||
{
|
||||
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
||||
10.0.0.1 example.org
|
||||
fallthrough
|
||||
}`,
|
||||
false,
|
||||
map[string][]string{
|
||||
`10.0.0.1`: {
|
||||
`example.org.`,
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
||||
10.0.0.1 example.org
|
||||
}`,
|
||||
false,
|
||||
map[string][]string{
|
||||
`10.0.0.1`: {
|
||||
`example.org.`,
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
||||
fallthrough
|
||||
10.0.0.1 example.org
|
||||
}`,
|
||||
true,
|
||||
map[string][]string{},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
c := caddy.NewTestController("dns", test.inputFileRules)
|
||||
h, err := hostsParse(c)
|
||||
|
||||
if err == nil && test.shouldErr {
|
||||
t.Fatalf("Test %d expected errors, but got no error", i)
|
||||
} else if err != nil && !test.shouldErr {
|
||||
t.Fatalf("Test %d expected no errors, but got '%v'", i, err)
|
||||
} else if !test.shouldErr {
|
||||
if h.Fallthrough != test.expectedFallthrough {
|
||||
t.Fatalf("Test %d expected fallthrough of %v, got %v", i, test.expectedFallthrough, h.Fallthrough)
|
||||
}
|
||||
for k, expectedVal := range test.expectedbyAddr {
|
||||
if val, ok := h.byAddr[k]; !ok {
|
||||
t.Fatalf("Test %d expected %v, got no entry", i, k)
|
||||
} else {
|
||||
if len(expectedVal) != len(val) {
|
||||
t.Fatalf("Test %d expected %v records for %v, got %v", i, len(expectedVal), k, len(val))
|
||||
}
|
||||
for j := range expectedVal {
|
||||
if expectedVal[j] != val[j] {
|
||||
t.Fatalf("Test %d expected %v for %v, got %v", i, expectedVal[j], j, val[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
48
test/hosts_file_test.go
Normal file
48
test/hosts_file_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin/proxy"
|
||||
"github.com/coredns/coredns/plugin/test"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func TestHostsInlineLookup(t *testing.T) {
|
||||
corefile := `example.org:0 {
|
||||
hosts highly_unlikely_to_exist_hosts_file example.org {
|
||||
10.0.0.1 example.org
|
||||
fallthrough
|
||||
}
|
||||
}`
|
||||
|
||||
i, udp, _, err := CoreDNSServerAndPorts(corefile)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
|
||||
}
|
||||
defer i.Stop()
|
||||
|
||||
log.SetOutput(ioutil.Discard)
|
||||
|
||||
p := proxy.NewLookup([]string{udp})
|
||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
||||
|
||||
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
|
||||
if err != nil {
|
||||
t.Fatal("Expected to receive reply, but didn't")
|
||||
}
|
||||
// expect answer section with A record in it
|
||||
if len(resp.Answer) == 0 {
|
||||
t.Fatal("Expected to at least one RR in the answer section, got none")
|
||||
}
|
||||
if resp.Answer[0].Header().Rrtype != dns.TypeA {
|
||||
t.Errorf("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
|
||||
}
|
||||
if resp.Answer[0].(*dns.A).A.String() != "10.0.0.1" {
|
||||
t.Errorf("Expected 10.0.0.1, got: %s", resp.Answer[0].(*dns.A).A.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user