naive implementation

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
Adphi 2022-03-24 18:33:18 +01:00
parent 5b45e22631
commit 99971d8b4c
Signed by: adphi
GPG Key ID: 46BE4062DB2397FF
7 changed files with 132 additions and 47 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
.idea
tests

View File

@ -6,9 +6,15 @@
## Description
The split plugin allows filtering DNS Server response Records based on network definitions. That way
The split plugin allows filtering DNS Server responses Records based on network definitions. That way
you do not need to run multiple DNS servers to handle split DNS.
If there are multiple A Records in the response, only the records matching the defined network will be returned
to a matching querier, and the records not matching the network to the other sources.
This plugin is not about security, it is design only to give a better answer to the incoming source IP,
if you need to apply security filtering rules, please consider using the [**coredns** *acl*](https://coredns.io/plugins/acl/) plugin.
## Compilation
This package will always be compiled as part of CoreDNS and not in a standalone way. It will require you to use `go get` or as a dependency on [plugin.cfg](https://github.com/coredns/coredns/blob/master/plugin.cfg).
@ -21,7 +27,7 @@ A simple way to consume this plugin, is by adding the following on [plugin.cfg](
split:go.linka.cloud/coredns-split
~~~
Put this lower in the plugin list, so that *split* is executed after any of the other plugins.
Put this higher in the plugin list, so that *split* is before after any of the other plugins.
After this you can compile coredns by:
@ -57,22 +63,17 @@ This plugin reports readiness to the ready plugin. It will be immediately ready.
## Examples
In this configuration, we forward all queries to 9.9.9.9 and print "example" whenever we receive
a query.
In this configuration, we forward all queries to 9.9.9.9 and filter out A records pointing to an IP address
in the 10.10.10.0/24 network except for queries coming from the 192.168.0.0/24 and 192.168.1.0/24 networks.
~~~ corefile
. {
forward . 9.9.9.9
example
example {
10.10.10.0/24 {
net 192.168.0.0/24 192.168.1.0/24
}
~~~
Or without any external connectivity:
~~~ corefile
. {
whoami
example
}
forward . 9.9.9.9
}
~~~

View File

@ -2,4 +2,6 @@ package split
// Ready implements the ready.Readiness interface, once this flips to true CoreDNS
// assumes this plugin is ready for queries; it is not checked again.
func (e Split) Ready() bool { return true }
func (s Split) Ready() bool {
return true
}

View File

@ -1,6 +1,8 @@
package split
import (
"net"
"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
@ -12,17 +14,46 @@ func init() { plugin.Register("split", setup) }
// setup is the function that gets called when the config parser see the token "example". Setup is responsible
// for parsing any extra options the example plugin may have. The first token this function sees is "example".
func setup(c *caddy.Controller) error {
c.Next() // Ignore "example" and give us the next token.
if c.NextArg() {
// If there was another token, return an error, because we don't have any configuration.
// Any errors returned from this setup function should be wrapped with plugin.Error, so we
// can present a slightly nicer error message to the user.
return plugin.Error("split", c.ArgErr())
s := Split{}
for c.Next() {
r := rule{}
args := c.RemainingArgs()
r.zones = plugin.OriginsFromArgsOrServerBlock(args, c.ServerBlockKeys)
if c.NextBlock() {
n := network{}
_, ipnet, err := net.ParseCIDR(c.Val())
if err != nil {
return err
}
n.record = ipnet
for c.NextBlock() {
for c.NextLine() {
a := c.Val()
_ = a
argsLoop:
for _, v := range c.RemainingArgs() {
_, ipnet, err := net.ParseCIDR(v)
if err != nil {
return err
}
for _, vv := range n.allowed {
if vv.Contains(ipnet.IP) {
continue argsLoop
}
}
n.allowed = append(n.allowed, ipnet)
}
}
r.networks = append(r.networks, n)
}
s.Rule = append(s.Rule, r)
}
}
// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return Split{Next: next}
s.Next = next
return s
})
// All OK, return a nil error.

View File

@ -13,9 +13,4 @@ func TestSetup(t *testing.T) {
if err := setup(c); err != nil {
t.Fatalf("Expected no errors, but got: %v", err)
}
c = caddy.NewTestController("dns", `split more`)
if err := setup(c); err == nil {
t.Fatalf("Expected errors, but got: %v", err)
}
}

View File

@ -5,11 +5,12 @@ package split
import (
"context"
"net"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -20,44 +21,108 @@ var log = clog.NewWithPlugin("split")
// Split is an example plugin to show how to write a plugin.
type Split struct {
Next plugin.Handler
Rule []rule
}
type rule struct {
zones []string
networks []network
}
type network struct {
record *net.IPNet
allowed []*net.IPNet
}
// ServeDNS implements the plugin.Handler interface. This method gets called when example is used
// in a Server.
func (e Split) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
func (s Split) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
// This function could be simpler. I.e. just fmt.Println("example") here, but we want to show
// a slightly more complex example as to make this more interesting.
// Here we wrap the dns.ResponseWriter in a new ResponseWriter and call the next plugin, when the
// answer comes back, it will print "example".
// Debug log that we've have seen the query. This will only be shown when the debug plugin is loaded.
// Debug log that we've seen the query. This will only be shown when the debug plugin is loaded.
log.Debug("Received response")
// Wrap.
pw := NewResponsePrinter(w)
pw := s.NewResponsePrinter(w, r)
// Export metric with the server label set to the current server handling the request.
requestCount.WithLabelValues(metrics.WithServer(ctx)).Inc()
// Call next plugin (if any).
return plugin.NextOrFailure(e.Name(), e.Next, ctx, pw, r)
return plugin.NextOrFailure(s.Name(), s.Next, ctx, pw, r)
}
// Name implements the Handler interface.
func (e Split) Name() string { return "split" }
func (s Split) Name() string { return "split" }
// ResponsePrinter wrap a dns.ResponseWriter and will write example to standard output when WriteMsg is called.
type ResponsePrinter struct {
dns.ResponseWriter
state request.Request
r *dns.Msg
src net.IP
rules []rule
}
// NewResponsePrinter returns ResponseWriter.
func NewResponsePrinter(w dns.ResponseWriter) *ResponsePrinter {
return &ResponsePrinter{ResponseWriter: w}
func (s Split) NewResponsePrinter(w dns.ResponseWriter, r *dns.Msg) *ResponsePrinter {
state := request.Request{W: w, Req: r}
ip := net.ParseIP(state.IP())
return &ResponsePrinter{ResponseWriter: w, r: r, src: ip, rules: s.Rule, state: state}
}
// WriteMsg calls the underlying ResponseWriter's WriteMsg method and prints "example" to standard output.
func (r *ResponsePrinter) WriteMsg(res *dns.Msg) error {
log.Info("example")
var rule rule
for _, v := range r.rules {
zone := plugin.Zones(v.zones).Matches(r.state.Name())
if zone == "" {
continue
}
rule = v
break
}
var answers []dns.RR
var netAnswers []dns.RR
for _, v := range res.Answer {
rec, ok := v.(*dns.A)
if !ok {
answers = append(answers, v)
continue
}
var net *network
for _, vv := range rule.networks {
if vv.record.Contains(rec.A) {
net = &vv
break
}
}
if net == nil {
answers = append(answers, v)
continue
}
allowed := false
for _, vv := range net.allowed {
if vv.Contains(r.src) {
allowed = true
break
}
}
if allowed {
answers = append(answers, v)
netAnswers = append(netAnswers, v)
continue
}
log.Infof("request source %s: %s: filtering %s", r.src.String(), rec.Hdr.Name, rec.A)
}
if len(netAnswers) != 0 {
res.Answer = netAnswers
} else {
res.Answer = answers
}
return r.ResponseWriter.WriteMsg(res)
}

View File

@ -1,10 +1,7 @@
package split
import (
"bytes"
"context"
golog "log"
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnstest"
@ -17,11 +14,6 @@ func TestExample(t *testing.T) {
// Create a new Split Plugin. Use the test.ErrorHandler as the next plugin.
x := Split{Next: test.ErrorHandler()}
// Setup a new output buffer that is *not* standard output, so we can check if
// example is really being printed.
b := &bytes.Buffer{}
golog.SetOutput(b)
ctx := context.TODO()
r := new(dns.Msg)
r.SetQuestion("example.org.", dns.TypeA)
@ -31,7 +23,5 @@ func TestExample(t *testing.T) {
// Call our plugin directly, and check the result.
x.ServeDNS(ctx, rec, r)
if a := b.String(); !strings.Contains(a, "[INFO] plugin/split: example") {
t.Errorf("Failed to print '%s', got %s", "[INFO] plugin/split: example", a)
}
t.Log(rec.Msg)
}