aboutsummaryrefslogtreecommitdiff
path: root/plugin/pkg/response/typify.go
blob: df314d41a44adf86aa6eece87c2e90bf4a41460d (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
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
146
147
148
149
150
151
package response

import (
	"fmt"
	"time"

	"github.com/miekg/dns"
)

// Type is the type of the message.
type Type int

const (
	// NoError indicates a positive reply
	NoError Type = iota
	// NameError is a NXDOMAIN in header, SOA in auth.
	NameError
	// ServerError is a set of errors we want to cache, for now it contains SERVFAIL and NOTIMPL.
	ServerError
	// NoData indicates name found, but not the type: NOERROR in header, SOA in auth.
	NoData
	// Delegation is a msg with a pointer to another nameserver: NOERROR in header, NS in auth, optionally fluff in additional (not checked).
	Delegation
	// Meta indicates a meta message, NOTIFY, or a transfer:  qType is IXFR or AXFR.
	Meta
	// Update is an dynamic update message.
	Update
	// OtherError indicates any other error: don't cache these.
	OtherError
)

var toString = map[Type]string{
	NoError:     "NOERROR",
	NameError:   "NXDOMAIN",
	ServerError: "SERVERERROR",
	NoData:      "NODATA",
	Delegation:  "DELEGATION",
	Meta:        "META",
	Update:      "UPDATE",
	OtherError:  "OTHERERROR",
}

func (t Type) String() string { return toString[t] }

// TypeFromString returns the type from the string s. If not type matches
// the OtherError type and an error are returned.
func TypeFromString(s string) (Type, error) {
	for t, str := range toString {
		if s == str {
			return t, nil
		}
	}
	return NoError, fmt.Errorf("invalid Type: %s", s)
}

// Typify classifies a message, it returns the Type.
func Typify(m *dns.Msg, t time.Time) (Type, *dns.OPT) {
	if m == nil {
		return OtherError, nil
	}
	opt := m.IsEdns0()
	do := false
	if opt != nil {
		do = opt.Do()
	}

	if m.Opcode == dns.OpcodeUpdate {
		return Update, opt
	}

	// Check transfer and update first
	if m.Opcode == dns.OpcodeNotify {
		return Meta, opt
	}

	if len(m.Question) > 0 {
		if m.Question[0].Qtype == dns.TypeAXFR || m.Question[0].Qtype == dns.TypeIXFR {
			return Meta, opt
		}
	}

	// If our message contains any expired sigs and we care about that, we should return expired
	if do {
		if expired := typifyExpired(m, t); expired {
			return OtherError, opt
		}
	}

	if len(m.Answer) > 0 && m.Rcode == dns.RcodeSuccess {
		return NoError, opt
	}

	soa := false
	ns := 0
	for _, r := range m.Ns {
		if r.Header().Rrtype == dns.TypeSOA {
			soa = true
			continue
		}
		if r.Header().Rrtype == dns.TypeNS {
			ns++
		}
	}

	if soa && m.Rcode == dns.RcodeSuccess {
		return NoData, opt
	}
	if soa && m.Rcode == dns.RcodeNameError {
		return NameError, opt
	}

	if m.Rcode == dns.RcodeServerFailure || m.Rcode == dns.RcodeNotImplemented {
		return ServerError, opt
	}

	if ns > 0 && m.Rcode == dns.RcodeSuccess {
		return Delegation, opt
	}

	if m.Rcode == dns.RcodeSuccess {
		return NoError, opt
	}

	return OtherError, opt
}

func typifyExpired(m *dns.Msg, t time.Time) bool {
	if expired := typifyExpiredRRSIG(m.Answer, t); expired {
		return true
	}
	if expired := typifyExpiredRRSIG(m.Ns, t); expired {
		return true
	}
	if expired := typifyExpiredRRSIG(m.Extra, t); expired {
		return true
	}
	return false
}

func typifyExpiredRRSIG(rrs []dns.RR, t time.Time) bool {
	for _, r := range rrs {
		if r.Header().Rrtype != dns.TypeRRSIG {
			continue
		}
		ok := r.(*dns.RRSIG).ValidityPeriod(t)
		if !ok {
			return true
		}
	}
	return false
}