Commit ea41dd23 authored by Mohammad Yosefpor's avatar Mohammad Yosefpor Committed by GitHub

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

* plugin/bind: exclude interface or ip address
Signed-off-by: default avatarMohammad Yosefpor <myusefpur@gmail.com>

* fix README.md
Signed-off-by: default avatarMohammad Yosefpor <myusefpur@gmail.com>

* Apply suggestions, Fix test
Signed-off-by: default avatarMohammad Yosefpor <myusefpur@gmail.com>

* Apply suggestions, move errs to setup
Signed-off-by: default avatarMohammad Yosefpor <myusefpur@gmail.com>
parent 5b9b079d
...@@ -17,13 +17,25 @@ If the given argument is an interface name, and that interface has serveral IP a ...@@ -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 ...
~~~
You can also exclude some addresses with their IP address or interface name in expanded syntax:
~~~ ~~~
bind ADDRESS|IFACE ... {
except ADDRESS|IFACE ...
}
~~~
**ADDRESS** is an IP address to bind to.
When several addresses are provided a listener will be opened on each of the addresses.
* **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 ...@@ -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
......
// 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" }
package bind package bind
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
...@@ -10,30 +11,74 @@ import ( ...@@ -10,30 +11,74 @@ 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 { if err != nil {
return plugin.Error("bind", fmt.Errorf("at least one address or interface name is expected")) return plugin.Error("bind", err)
} }
ifaces, err := net.Interfaces() ips, err := listIP(b.addrs, ifaces)
if err != nil { if err != nil {
return plugin.Error("bind", fmt.Errorf("failed to get interfaces list")) return plugin.Error("bind", err)
} }
except, err := listIP(b.except, ifaces)
if err != nil {
return plugin.Error("bind", err)
}
for _, ip := range ips {
if !isIn(ip, except) {
all = append(all, ip)
}
}
}
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 var isIface bool
for _, arg := range args { for _, a := range args {
isIface = false isIface = false
for _, iface := range ifaces { for _, iface := range ifaces {
if arg == iface.Name { if a == iface.Name {
isIface = true isIface = true
addrs, err := iface.Addrs() addrs, err := iface.Addrs()
if err != nil { if err != nil {
return plugin.Error("bind", fmt.Errorf("failed to get the IP addresses of the interface: %q", arg)) return nil, fmt.Errorf("failed to get the IP addresses of the interface: %q", a)
} }
for _, addr := range addrs { for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok { if ipnet, ok := addr.(*net.IPNet); ok {
...@@ -45,13 +90,22 @@ func setup(c *caddy.Controller) error { ...@@ -45,13 +90,22 @@ func setup(c *caddy.Controller) error {
} }
} }
if !isIface { if !isIface {
if net.ParseIP(arg) == nil { if net.ParseIP(a) == nil {
return plugin.Error("bind", fmt.Errorf("not a valid IP address or interface name: %q", arg)) return nil, fmt.Errorf("not a valid IP address or interface name: %q", a)
} }
all = append(all, arg) all = append(all, a)
} }
} }
return all, 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
} }
config.ListenHosts = all }
return nil return is
} }
...@@ -20,6 +20,7 @@ func TestSetup(t *testing.T) { ...@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment