Skip to content

Commit

Permalink
Add $OPTION directive and "fallthrough" option
Browse files Browse the repository at this point in the history
Add the `$OPTION` directive to allow configuring the plugin.

Implement an option, "fallthrough" which causes the plugin to continue to the next plugin in the chain when there is no match, instead of always returning NXDOMAIN.

Signed-Off-By: Dan Fuhry <[email protected]>
  • Loading branch information
fuhry committed Mar 14, 2023
1 parent e8b4cfd commit 89a9aa8
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This plugin can only be used once per Server Block.

~~~
records [ZONES...] {
$OPTION [OPTION]
[INLINE]
}
~~~
Expand All @@ -42,6 +43,9 @@ records [ZONES...] {
* **INLINE** the resource record that are to be served. These must be specified as the text
represenation (as specifed in RFC 1035) of the record. See the examples below. Each record must
be on a single line.
* **OPTION** is a configuration option for the plugin. The following options are supported:
* `fallthrough`: When no matching record is found, instead of returning NXDOMAIN, the plugin will
call to the next plugin in the chain.

If domain name in **INLINE** are not fully qualifed each of the **ZONES** are used as the origin and
added to the names.
Expand Down Expand Up @@ -73,6 +77,22 @@ RFC 1035 zone file and everything after it will be ignored, hence the need for q
}
~~~

Override the record for `example.com`, without overriding anything else. Subdomains, like
`foo.example.com`, will continue to be resolved normally (the `forward` plugin, in this case).

~~~
. {
records . {
$OPTION fallthrough
example.com. 300 IN A 127.0.0.1
}
forward . 192.168.0.1 {
except example.com
}
}
~~~

## Bugs

DNSSEC, nor wildcards are implemented. The lookup algorithm is pretty basic. Future enhancements
Expand Down
6 changes: 6 additions & 0 deletions records.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"
"github.com/coredns/coredns/plugin/pkg/fall"

"github.com/miekg/dns"
)
Expand All @@ -15,6 +16,7 @@ type Records struct {
m map[string][]dns.RR

Next plugin.Handler
Fall *fall.F
}

// ServeDNS implements the plugin.Handle interface.
Expand Down Expand Up @@ -48,6 +50,9 @@ func (re *Records) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms

// handle NXDOMAIN, NODATA and normal response here.
if nxdomain {
if re.Fall.Through(qname) {
return plugin.NextOrFailure(re.Name(), re.Next, ctx, w, r)
}
m.Rcode = dns.RcodeNameError
if soa != nil {
m.Ns = []dns.RR{soa}
Expand All @@ -73,5 +78,6 @@ func (re *Records) Name() string { return "records" }
func New() *Records {
re := new(Records)
re.m = make(map[string][]dns.RR)
re.Fall = &fall.F{}
return re
}
71 changes: 71 additions & 0 deletions records_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package records

import (
"context"
"errors"
"testing"

"github.com/coredns/coredns/plugin/pkg/dnstest"
Expand Down Expand Up @@ -175,3 +176,73 @@ var testCasesMultipleOrigins = []test.Case{
},
},
}

func TestLookupFallThrough(t *testing.T) {
const input = `
records example.org {
$OPTION fallthrough
@ 60 IN A 127.0.0.1
}
`

c := caddy.NewTestController("dns", input)
re, err := recordsParse(c)
if err != nil {
t.Fatal(err)
}

tests:
for i, tc := range testCasesFallThrough {
m := tc.Msg()

rec := dnstest.NewRecorder(&test.ResponseWriter{})
_, err := re.ServeDNS(context.Background(), rec, m)
if tc.Error != nil && err == nil {
t.Errorf("Test %d, expected error %q, no error returned", i, tc.Error.Error())
return
}

if tc.Error == nil && err != nil {
t.Errorf("Test %d, expected no error, got %v", i, err)
return
}

if tc.Error != nil && err != nil {
if tc.Error.Error() != err.Error() {
t.Errorf("Test %d, expected no error, got %v", i, err)
return
}
continue tests
}

if rec.Msg == nil {
t.Errorf("Test %d, no message received", i)
return
}

if rec.Msg.Rcode != tc.Rcode {
t.Errorf("Test %d, expected rcode is %d, but got %d", i, tc.Rcode, rec.Msg.Rcode)
return
}

if resp := rec.Msg; rec.Msg != nil {
if err := test.SortAndCheck(resp, tc); err != nil {
t.Errorf("Test %d: %v", i, err)
}
}
}
}

var testCasesFallThrough = []test.Case{
{
Qname: "example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("example.org. 60 IN A 127.0.0.1"),
},
},
{
Qname: "foo.example.net.", Qtype: dns.TypeA,
Rcode: dns.RcodeServerFailure,
Error: errors.New("plugin/records: no next plugin found"),
},
}
22 changes: 20 additions & 2 deletions setup.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package records

import (
"fmt"
"strings"

"github.com/coredns/caddy"
Expand Down Expand Up @@ -57,9 +58,26 @@ func recordsParse(c *caddy.Controller) (*Records, error) {
// c.Val() + c.RemainingArgs() is the record we need to parse (for each zone given; now tracked in re.origins). When parsing
// the record we just set the ORIGIN to the correct value and magic will happen. If no origin we set it to "."

parseBlocks:
for c.NextBlock() {
s := c.Val() + " "
s += strings.Join(c.RemainingArgs(), " ")
s := c.Val()
if s == "$OPTION" {
if !c.NextArg() {
return nil, fmt.Errorf("parsing block failed: $OPTION missing argument")
}
opt := c.Val()
switch opt {
case "fallthrough":
re.Fall.SetZonesFromArgs(re.origins)
default:
return nil, fmt.Errorf("parsing block failed: unknown option: %q", opt)
}
if len(c.RemainingArgs()) > 0 {
return nil, fmt.Errorf("parsing block failed: extra arguments after option %q", opt)
}
continue parseBlocks
}
s += " " + strings.Join(c.RemainingArgs(), " ")
for _, o := range re.origins {
rr, err := dns.NewRR("$ORIGIN " + o + "\n" + s + "\n")
if err != nil {
Expand Down

0 comments on commit 89a9aa8

Please sign in to comment.