aboutsummaryrefslogtreecommitdiff
path: root/plugin/etcd/stub_handler.go
blob: 92a0103c50192109dc64d3acb823b2b70803535b (plain) (blame)
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
package etcd

import (
	"context"
	"errors"

	"github.com/coredns/coredns/plugin/pkg/log"
	"github.com/coredns/coredns/request"

	"github.com/miekg/dns"
)

// Stub wraps an Etcd. We have this type so that it can have a ServeDNS method.
type Stub struct {
	*Etcd
	Zone string // for what zone (and thus what nameservers are we called)
}

// ServeDNS implements the plugin.Handler interface.
func (s Stub) ServeDNS(ctx context.Context, w dns.ResponseWriter, req *dns.Msg) (int, error) {
	if hasStubEdns0(req) {
		log.Warningf("Forwarding cycle detected, refusing msg: %s", req.Question[0].Name)
		return dns.RcodeRefused, errors.New("stub forward cycle")
	}
	req = addStubEdns0(req)
	proxy, ok := (*s.Etcd.Stubmap)[s.Zone]
	if !ok { // somebody made a mistake..
		return dns.RcodeServerFailure, nil
	}

	state := request.Request{W: w, Req: req}
	m, e := proxy.Forward(state)
	if e != nil {
		return dns.RcodeServerFailure, e
	}
	m.RecursionAvailable, m.Compress = true, true
	state.SizeAndDo(m)
	w.WriteMsg(m)
	return dns.RcodeSuccess, nil
}

// hasStubEdns0 checks if the message is carrying our special edns0 zero option.
func hasStubEdns0(m *dns.Msg) bool {
	option := m.IsEdns0()
	if option == nil {
		return false
	}
	for _, o := range option.Option {
		if o.Option() == ednsStubCode && len(o.(*dns.EDNS0_LOCAL).Data) == 1 &&
			o.(*dns.EDNS0_LOCAL).Data[0] == 1 {
			return true
		}
	}
	return false
}

// addStubEdns0 adds our special option to the message's OPT record.
func addStubEdns0(m *dns.Msg) *dns.Msg {
	option := m.IsEdns0()
	// Add a custom EDNS0 option to the packet, so we can detect loops when 2 stubs are forwarding to each other.
	if option != nil {
		option.Option = append(option.Option, &dns.EDNS0_LOCAL{Code: ednsStubCode, Data: []byte{1}})
		return m
	}

	m.Extra = append(m.Extra, ednsStub)
	return m
}

const (
	ednsStubCode = dns.EDNS0LOCALSTART + 10
	stubDomain   = "stub.dns"
)

var ednsStub = func() *dns.OPT {
	o := new(dns.OPT)
	o.Hdr.Name = "."
	o.Hdr.Rrtype = dns.TypeOPT
	o.SetUDPSize(4096)

	e := new(dns.EDNS0_LOCAL)
	e.Code = ednsStubCode
	e.Data = []byte{1}
	o.Option = append(o.Option, e)
	return o
}()