Allow overlapping Zones if binding addresses are different (#1530)
* add OverlapChecker, move the test of overlap AFTER the directive setup process, change key of configs to allow multiple same key * glitch when rebase. init of Config should include the default host * add tests for the registering of configuration rename multicast in 'unbound'. add comments on the validator * - merged zoneAddr and addrKey that are very similar - move maps of Validator to zoneAddr, avoinding need to have string representation of zoneaddr - moving key build for saving Config at Config side instead of dnsContext * - UT on saving config is now useless. * - cannot cleanup access to Configs after setup. Deferred function to Start, use it * - cleanup register unit tests. remove useless function * - address comments of review. name of validator, comments, simplify registerAndCheck * - fixes after review. renaming a function and a comment
This commit is contained in:
parent
455040c143
commit
9047bdf3a0
6 changed files with 164 additions and 19 deletions
|
@ -15,10 +15,17 @@ type zoneAddr struct {
|
||||||
Port string
|
Port string
|
||||||
Transport string // dns, tls or grpc
|
Transport string // dns, tls or grpc
|
||||||
IPNet *net.IPNet // if reverse zone this hold the IPNet
|
IPNet *net.IPNet // if reverse zone this hold the IPNet
|
||||||
|
Address string // used for bound zoneAddr - validation of overlapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// String return the string representation of z.
|
// String return the string representation of z.
|
||||||
func (z zoneAddr) String() string { return z.Transport + "://" + z.Zone + ":" + z.Port }
|
func (z zoneAddr) String() string {
|
||||||
|
s := z.Transport + "://" + z.Zone + ":" + z.Port
|
||||||
|
if z.Address != "" {
|
||||||
|
s += " on " + z.Address
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// Transport returns the protocol of the string s
|
// Transport returns the protocol of the string s
|
||||||
func Transport(s string) string {
|
func Transport(s string) string {
|
||||||
|
@ -94,3 +101,37 @@ const (
|
||||||
TransportTLS = "tls"
|
TransportTLS = "tls"
|
||||||
TransportGRPC = "grpc"
|
TransportGRPC = "grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type zoneOverlap struct {
|
||||||
|
registeredAddr map[zoneAddr]zoneAddr // each zoneAddr is registered once by its key
|
||||||
|
unboundOverlap map[zoneAddr]zoneAddr // the "no bind" equiv ZoneAdddr is registered by its original key
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOverlapZone() *zoneOverlap {
|
||||||
|
return &zoneOverlap{registeredAddr: make(map[zoneAddr]zoneAddr), unboundOverlap: make(map[zoneAddr]zoneAddr)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerAndCheck adds a new zoneAddr for validation, it returns information about existing or overlapping with already registered
|
||||||
|
// we consider that an unbound address is overlapping all bound addresses for same zone, same port
|
||||||
|
func (zo *zoneOverlap) registerAndCheck(z zoneAddr) (existingZone *zoneAddr, overlappingZone *zoneAddr) {
|
||||||
|
|
||||||
|
if exist, ok := zo.registeredAddr[z]; ok {
|
||||||
|
// exact same zone already registered
|
||||||
|
return &exist, nil
|
||||||
|
}
|
||||||
|
uz := zoneAddr{Zone: z.Zone, Address: "", Port: z.Port, Transport: z.Transport}
|
||||||
|
if already, ok := zo.unboundOverlap[uz]; ok {
|
||||||
|
if z.Address == "" {
|
||||||
|
// current is not bound to an address, but there is already another zone with a bind address registered
|
||||||
|
return nil, &already
|
||||||
|
}
|
||||||
|
if _, ok := zo.registeredAddr[uz]; ok {
|
||||||
|
// current zone is bound to an address, but there is already an overlapping zone+port with no bind address
|
||||||
|
return nil, &uz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// there is no overlap, keep the current zoneAddr for future checks
|
||||||
|
zo.registeredAddr[z] = z
|
||||||
|
zo.unboundOverlap[uz] = z
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
@ -108,3 +108,72 @@ func TestSplitProtocolHostPort(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type checkCall struct {
|
||||||
|
zone zoneAddr
|
||||||
|
same bool
|
||||||
|
overlap bool
|
||||||
|
overlapKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type checkTest struct {
|
||||||
|
sequence []checkCall
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverlapAddressChecker(t *testing.T) {
|
||||||
|
for i, test := range []checkTest{
|
||||||
|
{sequence: []checkCall{
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "", Port: "53"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "", Port: "53"}, true, false, ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{sequence: []checkCall{
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "", Port: "53"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "", Port: "54"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "127.0.0.1", Port: "53"}, false, true, "dns://.:53"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{sequence: []checkCall{
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "127.0.0.1", Port: "53"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "", Port: "54"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "127.0.0.1", Port: "53"}, true, false, ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{sequence: []checkCall{
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "127.0.0.1", Port: "53"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "", Port: "54"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "128.0.0.1", Port: "53"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "129.0.0.1", Port: "53"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "", Port: "53"}, false, true, "dns://.:53 on 129.0.0.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{sequence: []checkCall{
|
||||||
|
{zoneAddr{Transport: "dns", Zone: ".", Address: "127.0.0.1", Port: "53"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: "com.", Address: "127.0.0.1", Port: "53"}, false, false, ""},
|
||||||
|
{zoneAddr{Transport: "dns", Zone: "com.", Address: "", Port: "53"}, false, true, "dns://com.:53 on 127.0.0.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
|
||||||
|
checker := newOverlapZone()
|
||||||
|
for _, call := range test.sequence {
|
||||||
|
same, overlap := checker.registerAndCheck(call.zone)
|
||||||
|
sZone := call.zone.String()
|
||||||
|
if (same != nil) != call.same {
|
||||||
|
t.Errorf("Test %d: error, for zone %s, 'same' (%v) has not the expected value (%v)", i, sZone, same != nil, call.same)
|
||||||
|
}
|
||||||
|
if same == nil {
|
||||||
|
if (overlap != nil) != call.overlap {
|
||||||
|
t.Errorf("Test %d: error, for zone %s, 'overlap' (%v) has not the expected value (%v)", i, sZone, overlap != nil, call.overlap)
|
||||||
|
}
|
||||||
|
if overlap != nil {
|
||||||
|
if overlap.String() != call.overlapKey {
|
||||||
|
t.Errorf("Test %d: error, for zone %s, 'overlap Key' (%v) has not the expected value (%v)", i, sZone, overlap.String(), call.overlapKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package dnsserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
|
@ -68,16 +69,22 @@ func (c *Config) HostAddresses() string {
|
||||||
return all
|
return all
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keyForConfig build a key for identifying the configs during setup time
|
||||||
|
func keyForConfig(blocIndex int, blocKeyIndex int) string {
|
||||||
|
return fmt.Sprintf("%d:%d", blocIndex, blocKeyIndex)
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfig gets the Config that corresponds to c.
|
// GetConfig gets the Config that corresponds to c.
|
||||||
// If none exist nil is returned.
|
// If none exist nil is returned.
|
||||||
func GetConfig(c *caddy.Controller) *Config {
|
func GetConfig(c *caddy.Controller) *Config {
|
||||||
ctx := c.Context().(*dnsContext)
|
ctx := c.Context().(*dnsContext)
|
||||||
if cfg, ok := ctx.keysToConfigs[c.Key]; ok {
|
key := keyForConfig(c.ServerBlockIndex, c.ServerBlockKeyIndex)
|
||||||
|
if cfg, ok := ctx.keysToConfigs[key]; ok {
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
// we should only get here during tests because directive
|
// we should only get here during tests because directive
|
||||||
// actions typically skip the server blocks where we make
|
// actions typically skip the server blocks where we make
|
||||||
// the configs.
|
// the configs.
|
||||||
ctx.saveConfig(c.Key, &Config{ListenHosts: []string{""}})
|
ctx.saveConfig(key, &Config{ListenHosts: []string{""}})
|
||||||
return GetConfig(c)
|
return GetConfig(c)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,28 +55,23 @@ func (h *dnsContext) saveConfig(key string, cfg *Config) {
|
||||||
// be parsed and executed.
|
// be parsed and executed.
|
||||||
func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
|
func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
|
||||||
// Normalize and check all the zone names and check for duplicates
|
// Normalize and check all the zone names and check for duplicates
|
||||||
dups := map[string]string{}
|
for ib, s := range serverBlocks {
|
||||||
for _, s := range serverBlocks {
|
for ik, k := range s.Keys {
|
||||||
for i, k := range s.Keys {
|
|
||||||
za, err := normalizeZone(k)
|
za, err := normalizeZone(k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.Keys[i] = za.String()
|
s.Keys[ik] = za.String()
|
||||||
if v, ok := dups[za.String()]; ok {
|
|
||||||
return nil, fmt.Errorf("cannot serve %s - zone already defined for %v", za, v)
|
|
||||||
}
|
|
||||||
dups[za.String()] = za.String()
|
|
||||||
|
|
||||||
// Save the config to our master list, and key it for lookups.
|
// Save the config to our master list, and key it for lookups.
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Zone: za.Zone,
|
Zone: za.Zone,
|
||||||
|
ListenHosts: []string{""},
|
||||||
Port: za.Port,
|
Port: za.Port,
|
||||||
Transport: za.Transport,
|
Transport: za.Transport,
|
||||||
ListenHosts: []string{""},
|
|
||||||
}
|
}
|
||||||
|
keyConfig := keyForConfig(ib, ik)
|
||||||
if za.IPNet == nil {
|
if za.IPNet == nil {
|
||||||
h.saveConfig(za.String(), cfg)
|
h.saveConfig(keyConfig, cfg)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +86,7 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy
|
||||||
return za.IPNet.Contains(net.ParseIP(addr))
|
return za.IPNet.Contains(net.ParseIP(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.saveConfig(za.String(), cfg)
|
h.saveConfig(keyConfig, cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return serverBlocks, nil
|
return serverBlocks, nil
|
||||||
|
@ -100,6 +95,13 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy
|
||||||
// MakeServers uses the newly-created siteConfigs to create and return a list of server instances.
|
// MakeServers uses the newly-created siteConfigs to create and return a list of server instances.
|
||||||
func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
|
func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
|
||||||
|
|
||||||
|
// Now that all Keys and Directives are parsed and initialized
|
||||||
|
// lets verify that there is no overlap on the zones and addresses to listen for
|
||||||
|
errValid := h.validateZonesAndListeningAddresses()
|
||||||
|
if errValid != nil {
|
||||||
|
return nil, errValid
|
||||||
|
}
|
||||||
|
|
||||||
// we must map (group) each config to a bind address
|
// we must map (group) each config to a bind address
|
||||||
groups, err := groupConfigsByListenAddr(h.configs)
|
groups, err := groupConfigsByListenAddr(h.configs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -183,14 +185,35 @@ func (c *Config) Handlers() []plugin.Handler {
|
||||||
return hs
|
return hs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *dnsContext) validateZonesAndListeningAddresses() error {
|
||||||
|
//Validate Zone and addresses
|
||||||
|
checker := newOverlapZone()
|
||||||
|
for _, conf := range h.configs {
|
||||||
|
for _, h := range conf.ListenHosts {
|
||||||
|
// Validate the overlapping of ZoneAddr
|
||||||
|
akey := zoneAddr{Transport: conf.Transport, Zone: conf.Zone, Address: h, Port: conf.Port}
|
||||||
|
existZone, overlapZone := checker.registerAndCheck(akey)
|
||||||
|
if existZone != nil {
|
||||||
|
return fmt.Errorf("cannot serve %s - it is already defined", akey.String())
|
||||||
|
}
|
||||||
|
if overlapZone != nil {
|
||||||
|
return fmt.Errorf("cannot serve %s - zone overlap listener capacity with %v", akey.String(), overlapZone.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// groupSiteConfigsByListenAddr groups site configs by their listen
|
// groupSiteConfigsByListenAddr groups site configs by their listen
|
||||||
// (bind) address, so sites that use the same listener can be served
|
// (bind) address, so sites that use the same listener can be served
|
||||||
// on the same server instance. The return value maps the listen
|
// on the same server instance. The return value maps the listen
|
||||||
// address (what you pass into net.Listen) to the list of site configs.
|
// address (what you pass into net.Listen) to the list of site configs.
|
||||||
// This function does NOT vet the configs to ensure they are compatible.
|
// This function does NOT vet the configs to ensure they are compatible.
|
||||||
func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
|
func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
|
||||||
groups := make(map[string][]*Config)
|
|
||||||
|
|
||||||
|
groups := make(map[string][]*Config)
|
||||||
for _, conf := range configs {
|
for _, conf := range configs {
|
||||||
for _, h := range conf.ListenHosts {
|
for _, h := range conf.ListenHosts {
|
||||||
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(h, conf.Port))
|
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(h, conf.Port))
|
||||||
|
@ -201,6 +224,7 @@ func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
|
||||||
groups[addrstr] = append(groups[addrstr], conf)
|
groups[addrstr] = append(groups[addrstr], conf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ func TestGroupingServers(t *testing.T) {
|
||||||
expectedGroups: []string{"dns://:53", "dns://:54"},
|
expectedGroups: []string{"dns://:53", "dns://:54"},
|
||||||
failing: false},
|
failing: false},
|
||||||
|
|
||||||
// 2 configs on same port, same broadcast address, diff zones -> 1 group
|
// 2 configs on same port, both not using bind, diff zones -> 1 group
|
||||||
{configs: []*Config{
|
{configs: []*Config{
|
||||||
{Transport: "dns", Zone: ".", Port: "53", ListenHosts: []string{""}},
|
{Transport: "dns", Zone: ".", Port: "53", ListenHosts: []string{""}},
|
||||||
{Transport: "dns", Zone: "com.", Port: "53", ListenHosts: []string{""}},
|
{Transport: "dns", Zone: "com.", Port: "53", ListenHosts: []string{""}},
|
||||||
|
@ -59,7 +59,7 @@ func TestGroupingServers(t *testing.T) {
|
||||||
expectedGroups: []string{"dns://:53"},
|
expectedGroups: []string{"dns://:53"},
|
||||||
failing: false},
|
failing: false},
|
||||||
|
|
||||||
// 2 configs on same port, same address, diff zones -> 1 group
|
// 2 configs on same port, one addressed - one not using bind, diff zones -> 1 group
|
||||||
{configs: []*Config{
|
{configs: []*Config{
|
||||||
{Transport: "dns", Zone: ".", Port: "53", ListenHosts: []string{"127.0.0.1"}},
|
{Transport: "dns", Zone: ".", Port: "53", ListenHosts: []string{"127.0.0.1"}},
|
||||||
{Transport: "dns", Zone: ".", Port: "54", ListenHosts: []string{""}},
|
{Transport: "dns", Zone: ".", Port: "54", ListenHosts: []string{""}},
|
||||||
|
@ -74,7 +74,7 @@ func TestGroupingServers(t *testing.T) {
|
||||||
expectedGroups: []string{"dns://127.0.0.1:53", "dns://[::1]:53", "dns://:54"},
|
expectedGroups: []string{"dns://127.0.0.1:53", "dns://[::1]:53", "dns://:54"},
|
||||||
failing: false},
|
failing: false},
|
||||||
|
|
||||||
// 2 configs on same port, same unicast address, diff zones -> 1 group
|
// 2 configs on same port, same address, diff zones -> 1 group
|
||||||
{configs: []*Config{
|
{configs: []*Config{
|
||||||
{Transport: "dns", Zone: ".", Port: "53", ListenHosts: []string{"127.0.0.1", "::1"}},
|
{Transport: "dns", Zone: ".", Port: "53", ListenHosts: []string{"127.0.0.1", "::1"}},
|
||||||
{Transport: "dns", Zone: "com.", Port: "53", ListenHosts: []string{"127.0.0.1", "::1"}},
|
{Transport: "dns", Zone: "com.", Port: "53", ListenHosts: []string{"127.0.0.1", "::1"}},
|
||||||
|
|
|
@ -65,6 +65,10 @@ func NewServer(addr string, group []*Config) (*Server, error) {
|
||||||
// set the config per zone
|
// set the config per zone
|
||||||
s.zones[site.Zone] = site
|
s.zones[site.Zone] = site
|
||||||
// compile custom plugin for everything
|
// compile custom plugin for everything
|
||||||
|
if site.registry != nil {
|
||||||
|
// this config is already computed with the chain of plugin
|
||||||
|
continue
|
||||||
|
}
|
||||||
var stack plugin.Handler
|
var stack plugin.Handler
|
||||||
for i := len(site.Plugin) - 1; i >= 0; i-- {
|
for i := len(site.Plugin) - 1; i >= 0; i-- {
|
||||||
stack = site.Plugin[i](stack)
|
stack = site.Plugin[i](stack)
|
||||||
|
|
Loading…
Add table
Reference in a new issue