Simplify plugin/pkg/fall (#1358)

* Simplify plugin/pkg/fall

* Remove unused import

* Fix fall_test

* Get fall coverage to 100% just because

* gofmt. sigh.
This commit is contained in:
John Belamaric 2018-01-07 14:51:32 -05:00 committed by GitHub
parent c6febe6250
commit c59f5f6e86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 117 additions and 142 deletions

View file

@ -21,7 +21,7 @@ import (
// Etcd is a plugin talks to an etcd cluster. // Etcd is a plugin talks to an etcd cluster.
type Etcd struct { type Etcd struct {
Next plugin.Handler Next plugin.Handler
Fall *fall.F Fall fall.F
Zones []string Zones []string
PathPrefix string PathPrefix string
Proxy proxy.Proxy // Proxy for looking up names during the resolution process Proxy proxy.Proxy // Proxy for looking up names during the resolution process

View file

@ -7,7 +7,6 @@ import (
"github.com/coredns/coredns/plugin/etcd/msg" "github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/dnstest" "github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/test" "github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -16,7 +15,6 @@ import (
func TestMultiLookup(t *testing.T) { func TestMultiLookup(t *testing.T) {
etc := newEtcdPlugin() etc := newEtcdPlugin()
etc.Zones = []string{"skydns.test.", "miek.nl."} etc.Zones = []string{"skydns.test.", "miek.nl."}
etc.Fall = fall.New()
etc.Next = test.ErrorHandler() etc.Next = test.ErrorHandler()
for _, serv := range servicesMulti { for _, serv := range servicesMulti {

View file

@ -6,7 +6,6 @@ import (
"github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnsutil" "github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/pkg/fall"
mwtls "github.com/coredns/coredns/plugin/pkg/tls" mwtls "github.com/coredns/coredns/plugin/pkg/tls"
"github.com/coredns/coredns/plugin/proxy" "github.com/coredns/coredns/plugin/proxy"
@ -74,8 +73,7 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
case "stubzones": case "stubzones":
stubzones = true stubzones = true
case "fallthrough": case "fallthrough":
etc.Fall = fall.New() etc.Fall.SetZonesFromArgs(c.RemainingArgs())
etc.Fall.SetZones(c.RemainingArgs())
case "debug": case "debug":
/* it is a noop now */ /* it is a noop now */
case "path": case "path":

View file

@ -17,7 +17,7 @@ type Hosts struct {
Next plugin.Handler Next plugin.Handler
*Hostsfile *Hostsfile
Fall *fall.F Fall fall.F
} }
// ServeDNS implements the plugin.Handle interface. // ServeDNS implements the plugin.Handle interface.

View file

@ -9,7 +9,6 @@ import (
"github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
@ -106,10 +105,9 @@ func hostsParse(c *caddy.Controller) (Hosts, error) {
for c.NextBlock() { for c.NextBlock() {
switch c.Val() { switch c.Val() {
case "fallthrough": case "fallthrough":
h.Fall = fall.New() h.Fall.SetZonesFromArgs(c.RemainingArgs())
h.Fall.SetZones(c.RemainingArgs())
default: default:
if h.Fall.IsNil() { if len(h.Fall.Zones) == 0 {
line := strings.Join(append([]string{c.Val()}, c.RemainingArgs()...), " ") line := strings.Join(append([]string{c.Val()}, c.RemainingArgs()...), " ")
inline = append(inline, line) inline = append(inline, line)
continue continue

View file

@ -14,48 +14,48 @@ func TestHostsParse(t *testing.T) {
shouldErr bool shouldErr bool
expectedPath string expectedPath string
expectedOrigins []string expectedOrigins []string
expectedFallthrough *fall.F expectedFallthrough fall.F
}{ }{
{ {
`hosts `hosts
`, `,
false, "/etc/hosts", nil, nil, false, "/etc/hosts", nil, fall.Zero(),
}, },
{ {
`hosts /tmp`, `hosts /tmp`,
false, "/tmp", nil, nil, false, "/tmp", nil, fall.Zero(),
}, },
{ {
`hosts /etc/hosts miek.nl.`, `hosts /etc/hosts miek.nl.`,
false, "/etc/hosts", []string{"miek.nl."}, nil, false, "/etc/hosts", []string{"miek.nl."}, fall.Zero(),
}, },
{ {
`hosts /etc/hosts miek.nl. pun.gent.`, `hosts /etc/hosts miek.nl. pun.gent.`,
false, "/etc/hosts", []string{"miek.nl.", "pun.gent."}, nil, false, "/etc/hosts", []string{"miek.nl.", "pun.gent."}, fall.Zero(),
}, },
{ {
`hosts { `hosts {
fallthrough fallthrough
}`, }`,
false, "/etc/hosts", nil, fall.Zero(), false, "/etc/hosts", nil, fall.Root(),
}, },
{ {
`hosts /tmp { `hosts /tmp {
fallthrough fallthrough
}`, }`,
false, "/tmp", nil, fall.Zero(), false, "/tmp", nil, fall.Root(),
}, },
{ {
`hosts /etc/hosts miek.nl. { `hosts /etc/hosts miek.nl. {
fallthrough fallthrough
}`, }`,
false, "/etc/hosts", []string{"miek.nl."}, fall.Zero(), false, "/etc/hosts", []string{"miek.nl."}, fall.Root(),
}, },
{ {
`hosts /etc/hosts miek.nl 10.0.0.9/8 { `hosts /etc/hosts miek.nl 10.0.0.9/8 {
fallthrough fallthrough
}`, }`,
false, "/etc/hosts", []string{"miek.nl.", "10.in-addr.arpa."}, fall.Zero(), false, "/etc/hosts", []string{"miek.nl.", "10.in-addr.arpa."}, fall.Root(),
}, },
} }
@ -92,7 +92,7 @@ func TestHostsInlineParse(t *testing.T) {
inputFileRules string inputFileRules string
shouldErr bool shouldErr bool
expectedbyAddr map[string][]string expectedbyAddr map[string][]string
expectedFallthrough *fall.F expectedFallthrough fall.F
}{ }{
{ {
`hosts highly_unlikely_to_exist_hosts_file example.org { `hosts highly_unlikely_to_exist_hosts_file example.org {
@ -105,7 +105,7 @@ func TestHostsInlineParse(t *testing.T) {
`example.org.`, `example.org.`,
}, },
}, },
fall.Zero(), fall.Root(),
}, },
{ {
`hosts highly_unlikely_to_exist_hosts_file example.org { `hosts highly_unlikely_to_exist_hosts_file example.org {
@ -117,7 +117,7 @@ func TestHostsInlineParse(t *testing.T) {
`example.org.`, `example.org.`,
}, },
}, },
nil, fall.Zero(),
}, },
{ {
`hosts highly_unlikely_to_exist_hosts_file example.org { `hosts highly_unlikely_to_exist_hosts_file example.org {
@ -126,14 +126,13 @@ func TestHostsInlineParse(t *testing.T) {
}`, }`,
true, true,
map[string][]string{}, map[string][]string{},
fall.Zero(), fall.Root(),
}, },
} }
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("dns", test.inputFileRules) c := caddy.NewTestController("dns", test.inputFileRules)
h, err := hostsParse(c) h, err := hostsParse(c)
if err == nil && test.shouldErr { if err == nil && test.shouldErr {
t.Fatalf("Test %d expected errors, but got no error", i) t.Fatalf("Test %d expected errors, but got no error", i)
} else if err != nil && !test.shouldErr { } else if err != nil && !test.shouldErr {

View file

@ -41,7 +41,7 @@ type Kubernetes struct {
Namespaces map[string]bool Namespaces map[string]bool
podMode string podMode string
endpointNameMode bool endpointNameMode bool
Fall *fall.F Fall fall.F
ttl uint32 ttl uint32
primaryZoneIndex int primaryZoneIndex int

View file

@ -10,7 +10,6 @@ import (
"github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnsutil" "github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/proxy" "github.com/coredns/coredns/plugin/proxy"
"github.com/mholt/caddy" "github.com/mholt/caddy"
@ -173,8 +172,7 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) {
} }
return nil, opts, c.ArgErr() return nil, opts, c.ArgErr()
case "fallthrough": case "fallthrough":
k8s.Fall = fall.New() k8s.Fall.SetZonesFromArgs(c.RemainingArgs())
k8s.Fall.SetZones(c.RemainingArgs())
case "upstream": case "upstream":
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) == 0 {

View file

@ -5,6 +5,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/mholt/caddy" "github.com/mholt/caddy"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -19,7 +21,7 @@ func TestKubernetesParse(t *testing.T) {
expectedResyncPeriod time.Duration // expected resync period value expectedResyncPeriod time.Duration // expected resync period value
expectedLabelSelector string // expected label selector value expectedLabelSelector string // expected label selector value
expectedPodMode string expectedPodMode string
expectedFallthrough *[]string expectedFallthrough fall.F
expectedUpstreams []string expectedUpstreams []string
}{ }{
// positive // positive
@ -32,7 +34,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -44,7 +46,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -57,7 +59,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -71,7 +73,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -85,7 +87,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -99,7 +101,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -113,7 +115,7 @@ func TestKubernetesParse(t *testing.T) {
30 * time.Second, 30 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -127,7 +129,7 @@ func TestKubernetesParse(t *testing.T) {
15 * time.Minute, 15 * time.Minute,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -141,7 +143,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"environment=prod", "environment=prod",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -155,7 +157,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"application=nginx,environment in (production,qa,staging)", "application=nginx,environment in (production,qa,staging)",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -173,7 +175,7 @@ func TestKubernetesParse(t *testing.T) {
15 * time.Minute, 15 * time.Minute,
"application=nginx,environment in (production,qa,staging)", "application=nginx,environment in (production,qa,staging)",
podModeDisabled, podModeDisabled,
&[]string{}, fall.Root(),
nil, nil,
}, },
// negative // negative
@ -188,7 +190,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -202,7 +204,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -216,7 +218,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Minute, 0 * time.Minute,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -230,7 +232,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Second, 0 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -244,7 +246,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Second, 0 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -258,7 +260,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Second, 0 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
{ {
@ -272,7 +274,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Second, 0 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
// pods disabled // pods disabled
@ -287,7 +289,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
// pods insecure // pods insecure
@ -302,7 +304,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeInsecure, podModeInsecure,
nil, fall.Zero(),
nil, nil,
}, },
// pods verified // pods verified
@ -317,7 +319,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeVerified, podModeVerified,
nil, fall.Zero(),
nil, nil,
}, },
// pods invalid // pods invalid
@ -332,7 +334,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeVerified, podModeVerified,
nil, fall.Zero(),
nil, nil,
}, },
// fallthrough with zones // fallthrough with zones
@ -347,7 +349,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
&[]string{"ip6.arpa.", "inaddr.arpa.", "foo.com."}, fall.F{Zones: []string{"ip6.arpa.", "inaddr.arpa.", "foo.com."}},
nil, nil,
}, },
// Valid upstream // Valid upstream
@ -362,7 +364,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
[]string{"13.14.15.16:53"}, []string{"13.14.15.16:53"},
}, },
// Invalid upstream // Invalid upstream
@ -377,7 +379,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
nil, fall.Zero(),
nil, nil,
}, },
} }
@ -443,23 +445,8 @@ func TestKubernetesParse(t *testing.T) {
} }
// fallthrough // fallthrough
foundFallthrough := k8sController.Fall if !k8sController.Fall.Equal(test.expectedFallthrough) {
if foundFallthrough != nil { t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, k8sController.Fall, test.input)
failed := false
if test.expectedFallthrough == nil {
failed = true
} else if len(*foundFallthrough) != len(*test.expectedFallthrough) {
failed = true
} else {
for i := range *foundFallthrough {
if (*foundFallthrough)[i] != (*test.expectedFallthrough)[i] {
failed = true
}
}
}
if failed {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, foundFallthrough, test.input)
}
} }
// upstream // upstream
foundUpstreams := k8sController.Proxy.Upstreams foundUpstreams := k8sController.Proxy.Upstreams

View file

@ -7,71 +7,52 @@ import (
// F can be nil to allow for no fallthrough, empty allow all zones to fallthrough or // F can be nil to allow for no fallthrough, empty allow all zones to fallthrough or
// contain a zone list that is checked. // contain a zone list that is checked.
type F []string type F struct {
Zones []string
// New returns a new F. }
func New() *F { return new(F) }
// Through will check if we should fallthrough for qname. Note that we've named the // Through will check if we should fallthrough for qname. Note that we've named the
// variable in each plugin "Fall", so this then reads Fall.Through(). // variable in each plugin "Fall", so this then reads Fall.Through().
func (f *F) Through(qname string) bool { func (f F) Through(qname string) bool {
if f == nil { return plugin.Zones(f.Zones).Matches(qname) != ""
return false
}
if len(*f) == 0 {
return true
}
zone := plugin.Zones(*f).Matches(qname)
return zone != ""
} }
// SetZones will set zones in f. // setZones will set zones in f.
func (f *F) SetZones(zones []string) { func (f *F) setZones(zones []string) {
for i := range zones { for i := range zones {
zones[i] = plugin.Host(zones[i]).Normalize() zones[i] = plugin.Host(zones[i]).Normalize()
} }
*f = zones f.Zones = zones
} }
// Example returns an F with example.org. as the zone name. // SetZonesFromArgs sets zones in f to the passed value or to "." if the slice is empty.
var Example = func() *F { func (f *F) SetZonesFromArgs(zones []string) {
f := F([]string{"example.org."}) if len(zones) == 0 {
return &f f.setZones(Root().Zones)
}() return
}
// Zero returns a zero valued F. f.setZones(zones)
var Zero = func() *F {
f := F([]string{})
return &f
} }
// IsNil returns true is f is nil. // Equal returns true if f and g are equal.
func (f *F) IsNil() bool { return f == nil } func (f F) Equal(g F) bool {
if len(f.Zones) != len(g.Zones) {
// IsZero returns true is f is zero (and not nil).
func (f *F) IsZero() bool {
if f == nil {
return false return false
} }
return len(*f) == 0 for i := range f.Zones {
} if f.Zones[i] != g.Zones[i] {
return false
// Equal returns true if f and g are equal. Only useful in tests, The (possible) zones
// are *not* checked.
func (f *F) Equal(g *F) bool {
if f.IsNil() {
if g.IsNil() {
return true
} }
return false
}
if f.IsZero() {
if g.IsZero() {
return true
}
}
if len(*f) != len(*g) {
return false
} }
return true return true
} }
// Zero returns a zero valued F.
var Zero = func() F {
return F{[]string{}}
}
// Root returns F set to only ".".
var Root = func() F {
return F{[]string{"."}}
}

View file

@ -2,41 +2,58 @@ package fall
import "testing" import "testing"
func TestIsNil(t *testing.T) { func TestEqual(t *testing.T) {
var f *F var z F
if !f.IsNil() { f := F{Zones: []string{"example.com."}}
t.Errorf("F should be nil") g := F{Zones: []string{"example.net."}}
h := F{Zones: []string{"example.com."}}
if !f.Equal(h) {
t.Errorf("%v should equal %v", f, h)
}
if z.Equal(f) {
t.Errorf("%v should not be equal to %v", z, f)
}
if f.Equal(g) {
t.Errorf("%v should not be equal to %v", f, g)
} }
} }
func TestIsZero(t *testing.T) { func TestZero(t *testing.T) {
f := New() var f F
if !f.IsZero() { if !f.Equal(Zero()) {
t.Errorf("F should be zero") t.Errorf("F should be zero")
} }
} }
func TestFallThroughExample(t *testing.T) { func TestSetZonesFromArgs(t *testing.T) {
if !Example.Through("example.org.") { var f F
t.Errorf("example.org. should fall through") f.SetZonesFromArgs([]string{})
if !f.Equal(Root()) {
t.Errorf("F should have the root zone")
} }
if Example.Through("example.net.") {
t.Errorf("example.net. should not fall through") f.SetZonesFromArgs([]string{"example.com", "example.net."})
expected := F{Zones: []string{"example.com.", "example.net."}}
if !f.Equal(expected) {
t.Errorf("F should be %v but is %v", expected, f)
} }
} }
func TestFallthrough(t *testing.T) { func TestFallthrough(t *testing.T) {
var fall *F var fall F
if fall.Through("foo.com.") { if fall.Through("foo.com.") {
t.Errorf("Expected false, got true for nil fallthrough") t.Errorf("Expected false, got true for zero fallthrough")
} }
fall = New() fall.SetZonesFromArgs([]string{})
if !fall.Through("foo.net.") { if !fall.Through("foo.net.") {
t.Errorf("Expected true, got false for all zone fallthrough") t.Errorf("Expected true, got false for all zone fallthrough")
} }
fall.SetZones([]string{"foo.com", "bar.com"}) fall.SetZonesFromArgs([]string{"foo.com", "bar.com"})
if fall.Through("foo.net.") { if fall.Through("foo.net.") {
t.Errorf("Expected false, got true for non-matching fallthrough zone") t.Errorf("Expected false, got true for non-matching fallthrough zone")

View file

@ -17,7 +17,7 @@ type Reverse struct {
Next plugin.Handler Next plugin.Handler
Networks networks Networks networks
Fall *fall.F Fall fall.F
} }
// ServeDNS implements the plugin.Handler interface. // ServeDNS implements the plugin.Handler interface.

View file

@ -34,7 +34,7 @@ func setupReverse(c *caddy.Controller) error {
return nil return nil
} }
func reverseParse(c *caddy.Controller) (nets networks, f *fall.F, err error) { func reverseParse(c *caddy.Controller) (nets networks, f fall.F, err error) {
zones := make([]string, len(c.ServerBlockKeys)) zones := make([]string, len(c.ServerBlockKeys))
wildcard := false wildcard := false
@ -87,8 +87,7 @@ func reverseParse(c *caddy.Controller) (nets networks, f *fall.F, err error) {
wildcard = true wildcard = true
case "fallthrough": case "fallthrough":
f = fall.New() f.SetZonesFromArgs(c.RemainingArgs())
f.SetZones(c.RemainingArgs())
default: default:
return nil, f, c.ArgErr() return nil, f, c.ArgErr()