plugin/bind: exclude interface or ip address (#4543)

* plugin/bind: exclude interface or ip address

Signed-off-by: Mohammad Yosefpor <myusefpur@gmail.com>

* fix README.md

Signed-off-by: Mohammad Yosefpor <myusefpur@gmail.com>

* Apply suggestions, Fix test

Signed-off-by: Mohammad Yosefpor <myusefpur@gmail.com>

* Apply suggestions, move errs to setup

Signed-off-by: Mohammad Yosefpor <myusefpur@gmail.com>
This commit is contained in:
Mohammad Yosefpor 2021-03-25 20:08:17 +04:30 committed by GitHub
parent 5b9b079dab
commit ea41dd23a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 35 deletions

View file

@ -17,13 +17,25 @@ If the given argument is an interface name, and that interface has serveral IP a
## Syntax ## Syntax
In its basic form, a simple bind uses this syntax:
~~~ txt ~~~ txt
bind ADDRESS ... bind ADDRESS|IFACE ...
~~~ ~~~
**ADDRESS** is an IP address to bind to. You can also exclude some addresses with their IP address or interface name in expanded syntax:
When several addresses are provided a listener will be opened on each of the addresses.
~~~
bind ADDRESS|IFACE ... {
except ADDRESS|IFACE ...
}
~~~
* **ADDRESS|IFACE** is an IP address or interface name to bind to.
When several addresses are provided a listener will be opened on each of the addresses. Please read the *Description* for more details.
* `except`, excludes interfaces or IP addresses to bind to. `except` option only excludes addresses for the current `bind` directive if multiple `bind` directives are used in the same server block.
## Examples ## Examples
To make your socket accessible only to that machine, bind to IP 127.0.0.1 (localhost): To make your socket accessible only to that machine, bind to IP 127.0.0.1 (localhost):
@ -60,6 +72,16 @@ The following server block, binds on localhost with its interface name (both "12
} }
~~~ ~~~
You can exclude some addresses by their IP or interface name (The following will only listen on `::1` or whatever addresses have been assigned to the `lo` interface):
~~~ corefile
. {
bind lo {
except 127.0.0.1
}
}
~~~
## Bugs ## Bugs
When defining more than one server block, take care not to bind more than one server to the same When defining more than one server block, take care not to bind more than one server to the same
@ -78,4 +100,4 @@ a.bad.example.com {
bad.example.com { bad.example.com {
forward . 5.6.7.8 forward . 5.6.7.8
} }
``` ```

View file

@ -1,6 +1,17 @@
// Package bind allows binding to a specific interface instead of bind to all of them. // Package bind allows binding to a specific interface instead of bind to all of them.
package bind package bind
import "github.com/coredns/coredns/plugin" import (
"github.com/coredns/coredns/plugin"
)
func init() { plugin.Register("bind", setup) } func init() { plugin.Register("bind", setup) }
type bind struct {
Next plugin.Handler
addrs []string
except []string
}
// Name implements plugin.Handler.
func (b *bind) Name() string { return "bind" }

View file

@ -1,6 +1,7 @@
package bind package bind
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
@ -10,48 +11,101 @@ import (
) )
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
config := dnsserver.GetConfig(c)
config := dnsserver.GetConfig(c)
// addresses will be consolidated over all BIND directives available in that BlocServer // addresses will be consolidated over all BIND directives available in that BlocServer
all := []string{} all := []string{}
ifaces, err := net.Interfaces()
if err != nil {
return plugin.Error("bind", fmt.Errorf("failed to get interfaces list: %s", err))
}
for c.Next() { for c.Next() {
args := c.RemainingArgs() b, err := parse(c)
if len(args) == 0 {
return plugin.Error("bind", fmt.Errorf("at least one address or interface name is expected"))
}
ifaces, err := net.Interfaces()
if err != nil { if err != nil {
return plugin.Error("bind", fmt.Errorf("failed to get interfaces list")) return plugin.Error("bind", err)
} }
var isIface bool ips, err := listIP(b.addrs, ifaces)
for _, arg := range args { if err != nil {
isIface = false return plugin.Error("bind", err)
for _, iface := range ifaces { }
if arg == iface.Name {
isIface = true except, err := listIP(b.except, ifaces)
addrs, err := iface.Addrs() if err != nil {
if err != nil { return plugin.Error("bind", err)
return plugin.Error("bind", fmt.Errorf("failed to get the IP addresses of the interface: %q", arg)) }
}
for _, addr := range addrs { for _, ip := range ips {
if ipnet, ok := addr.(*net.IPNet); ok { if !isIn(ip, except) {
if ipnet.IP.To4() != nil || (!ipnet.IP.IsLinkLocalMulticast() && !ipnet.IP.IsLinkLocalUnicast()) { all = append(all, ip)
all = append(all, ipnet.IP.String()) }
} }
}
config.ListenHosts = all
return nil
}
func parse(c *caddy.Controller) (*bind, error) {
b := &bind{}
b.addrs = c.RemainingArgs()
if len(b.addrs) == 0 {
return nil, errors.New("at least one address or interface name is expected")
}
for c.NextBlock() {
switch c.Val() {
case "except":
b.except = c.RemainingArgs()
if len(b.except) == 0 {
return nil, errors.New("at least one address or interface must be given to except subdirective")
}
default:
return nil, fmt.Errorf("invalid option %q", c.Val())
}
}
return b, nil
}
// listIP returns a list of IP addresses from a list of arguments which can be either IP-Address or Interface-Name.
func listIP(args []string, ifaces []net.Interface) ([]string, error) {
all := []string{}
var isIface bool
for _, a := range args {
isIface = false
for _, iface := range ifaces {
if a == iface.Name {
isIface = true
addrs, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf("failed to get the IP addresses of the interface: %q", a)
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok {
if ipnet.IP.To4() != nil || (!ipnet.IP.IsLinkLocalMulticast() && !ipnet.IP.IsLinkLocalUnicast()) {
all = append(all, ipnet.IP.String())
} }
} }
} }
} }
if !isIface { }
if net.ParseIP(arg) == nil { if !isIface {
return plugin.Error("bind", fmt.Errorf("not a valid IP address or interface name: %q", arg)) if net.ParseIP(a) == nil {
} return nil, fmt.Errorf("not a valid IP address or interface name: %q", a)
all = append(all, arg)
} }
all = append(all, a)
} }
} }
config.ListenHosts = all return all, nil
return nil }
// isIn checks if a string array contains an element
func isIn(s string, list []string) bool {
is := false
for _, l := range list {
if s == l {
is = true
}
}
return is
} }

View file

@ -20,6 +20,7 @@ func TestSetup(t *testing.T) {
{`bind ::1 1.2.3.4 ::5 127.9.9.0`, []string{"::1", "1.2.3.4", "::5", "127.9.9.0"}, false}, {`bind ::1 1.2.3.4 ::5 127.9.9.0`, []string{"::1", "1.2.3.4", "::5", "127.9.9.0"}, false},
{`bind ::1 1.2.3.4 ::5 127.9.9.0 noone`, nil, true}, {`bind ::1 1.2.3.4 ::5 127.9.9.0 noone`, nil, true},
{`bind 1.2.3.4 lo`, []string{"1.2.3.4", "127.0.0.1", "::1"}, false}, {`bind 1.2.3.4 lo`, []string{"1.2.3.4", "127.0.0.1", "::1"}, false},
{"bind lo {\nexcept 127.0.0.1\n}\n", []string{"::1"}, false},
} { } {
c := caddy.NewTestController("dns", test.config) c := caddy.NewTestController("dns", test.config)
err := setup(c) err := setup(c)