diff options
-rw-r--r-- | plugin/hosts/README.md | 15 | ||||
-rw-r--r-- | plugin/hosts/hostsfile.go | 16 | ||||
-rw-r--r-- | plugin/hosts/setup.go | 6 | ||||
-rw-r--r-- | plugin/hosts/setup_test.go | 74 | ||||
-rw-r--r-- | test/hosts_file_test.go | 48 |
5 files changed, 157 insertions, 2 deletions
diff --git a/plugin/hosts/README.md b/plugin/hosts/README.md index 60c738077..5e6ab0ec7 100644 --- a/plugin/hosts/README.md +++ b/plugin/hosts/README.md @@ -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 +} +~~~ diff --git a/plugin/hosts/hostsfile.go b/plugin/hosts/hostsfile.go index 91e828099..0862711fe 100644 --- a/plugin/hosts/hostsfile.go +++ b/plugin/hosts/hostsfile.go @@ -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 { diff --git a/plugin/hosts/setup.go b/plugin/hosts/setup.go index c7c0c728a..0c13140bb 100644 --- a/plugin/hosts/setup.go +++ b/plugin/hosts/setup.go @@ -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()) } } diff --git a/plugin/hosts/setup_test.go b/plugin/hosts/setup_test.go index a4c95b1c6..fefed6c72 100644 --- a/plugin/hosts/setup_test.go +++ b/plugin/hosts/setup_test.go @@ -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]) + } + } + } + } + } + } + +} diff --git a/test/hosts_file_test.go b/test/hosts_file_test.go new file mode 100644 index 000000000..fa50233f0 --- /dev/null +++ b/test/hosts_file_test.go @@ -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()) + } +} |