-
Notifications
You must be signed in to change notification settings - Fork 20
/
unbound.go
145 lines (119 loc) · 3.4 KB
/
unbound.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package unbound
import (
"context"
"fmt"
"strconv"
"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"
"github.com/miekg/unbound"
)
var log = clog.NewWithPlugin("unbound")
// Unbound is a plugin that resolves requests using libunbound.
type Unbound struct {
u *unbound.Unbound
t *unbound.Unbound
from []string
except []string
Next plugin.Handler
}
// options for unbound, see unbound.conf(5).
var options = map[string]string{
"msg-cache-size": "0",
"rrset-cache-size": "0",
}
// New returns a pointer to an initialzed Unbound.
func New() *Unbound {
udp := unbound.New()
tcp := unbound.New()
tcp.SetOption("tcp-upstream:", "yes")
u := &Unbound{u: udp, t: tcp}
for k, v := range options {
if err := u.setOption(k, v); err != nil {
log.Warningf("Could not set option: %s", err)
}
}
return u
}
// Stop stops unbound and cleans up the memory used.
func (u *Unbound) Stop() error {
u.u.Destroy()
u.t.Destroy()
return nil
}
// setOption sets option k to value v in u.
func (u *Unbound) setOption(k, v string) error {
// Add ":" as unbound expects it
k += ":"
// Set for both udp and tcp handlers, return the error from the latter.
u.u.SetOption(k, v)
err := u.t.SetOption(k, v)
if err != nil {
return fmt.Errorf("failed to set option %q with value %q: %s", k, v, err)
}
return nil
}
// config reads the file f and sets unbound configuration
func (u *Unbound) config(f string) error {
var err error
err = u.u.Config(f)
if err != nil {
return fmt.Errorf("failed to read config file (%s) UDP context: %s", f, err)
}
err = u.t.Config(f)
if err != nil {
return fmt.Errorf("failed to read config file (%s) TCP context: %s", f, err)
}
return nil
}
// ServeDNS implements the plugin.Handler interface.
func (u *Unbound) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
if !u.match(state) {
return plugin.NextOrFailure(u.Name(), u.Next, ctx, w, r)
}
var (
res *unbound.Result
err error
)
switch state.Proto() {
case "tcp":
res, err = u.t.Resolve(state.QName(), state.QType(), state.QClass())
case "udp":
res, err = u.u.Resolve(state.QName(), state.QType(), state.QClass())
}
rcode := dns.RcodeServerFailure
if err == nil {
rcode = res.AnswerPacket.Rcode
}
rc, ok := dns.RcodeToString[rcode]
if !ok {
rc = strconv.Itoa(rcode)
}
server := metrics.WithServer(ctx)
RcodeCount.WithLabelValues(server, rc).Add(1)
RequestDuration.WithLabelValues(server).Observe(res.Rtt.Seconds())
if err != nil {
return dns.RcodeServerFailure, err
}
// If the client *didn't* set the opt record, and specifically not the DO bit,
// strip this from the reply (unbound default to setting DO).
if !state.Do() {
// technically we can still set bufsize and fluff, for now remove the entire OPT record.
for i := 0; i < len(res.AnswerPacket.Extra); i++ {
rr := res.AnswerPacket.Extra[i]
if _, ok := rr.(*dns.OPT); ok {
res.AnswerPacket.Extra = append(res.AnswerPacket.Extra[:i], res.AnswerPacket.Extra[i+1:]...)
break // TODO(miek): more than one? Think TSIG?
}
}
filter(res.AnswerPacket, dnssec)
}
res.AnswerPacket.Id = r.Id
w.WriteMsg(res.AnswerPacket)
return 0, nil
}
// Name implements the Handler interface.
func (u *Unbound) Name() string { return "unbound" }