Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for dynamic updates (RFC2136) #31

Open
wants to merge 60 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
48520b1
Typo in the readme
tarnfeld Sep 20, 2014
a100666
[WIP]
tarnfeld Sep 21, 2014
159702b
WIP
tarnfeld Sep 22, 2014
ffca58d
Fixed compiler errors
tarnfeld Sep 23, 2014
43abcd9
Add a boolean argument to prevent resolving CNAMEs
tarnfeld Sep 25, 2014
da29d03
Ensure there is no leading slash from nameToKey
tarnfeld Sep 29, 2014
e5e685e
Fixed type error
tarnfeld Sep 29, 2014
ddc71c8
Added helper methods for domain/rrset validation checks
tarnfeld Sep 29, 2014
1f68dca
Added more useful debug logging
tarnfeld Sep 29, 2014
683576d
Added some basic test cases for panic based locks
tarnfeld Nov 21, 2014
266b5bd
Merge branch 'master' into feature/nsupdate
tarnfeld Nov 24, 2014
6838133
Added MOTD for makefile
tarnfeld Feb 3, 2015
d67d220
Test race conditions too
tarnfeld Feb 3, 2015
a65461d
Text alignment
tarnfeld Feb 3, 2015
1b93f29
Progress with add/delete dynamic updates
tarnfeld Feb 3, 2015
bfbe460
Make sure we send a TSIG response back
tarnfeld Feb 6, 2015
d18eb08
Create new records as keys inside the type directory
tarnfeld Feb 9, 2015
eb36f88
Fix creating directories for records:
Feb 10, 2015
8a395e6
Fix 'given peers are not reachable' test errs:
Feb 9, 2015
0e36d77
Typo
Feb 6, 2015
8bcd572
Correct signature for not-yet-implementeds
Feb 6, 2015
5a8ba10
Use correct TTL etcd keyname:
Feb 9, 2015
1f9ea6b
Implement update record converters: SRV, TXT, PTR
Feb 6, 2015
5b09124
Clean up any stale locks in etcd before tests:
Feb 9, 2015
d0a67d6
Use etcd prefixes for locks:
Feb 10, 2015
9b87d5c
Add (failing) test for multiple records:
Feb 10, 2015
a3dda33
Merge remote-tracking branch 'origin/master' into feature/nsupdate
Feb 19, 2015
b7244d9
Merge branch 'master' into feature/nsupdate
Feb 20, 2015
6d6729b
Fix NameExists to use simpler flow from master
Feb 20, 2015
ac8fd0e
Fix some merge issues
Feb 20, 2015
c037341
Add CLI option for unauthenticated updates:
Feb 18, 2015
6232c76
Use consistent etcd keys for new records:
Feb 18, 2015
0bd0d05
Replace panic-based locking with blocking locks:
Feb 10, 2015
dd80139
Add simple lock test
Feb 18, 2015
5536ccc
Add conflicting lock test
Feb 18, 2015
3a396b6
Merge pull request #37 from duedil-ltd/nsupdate/consistent-keys
Feb 20, 2015
4d82fc8
Merge pull request #39 from duedil-ltd/nsupdate/allow-unauthed
Feb 20, 2015
edca961
Merge pull request #40 from duedil-ltd/nsupdate/syncronous-locking
Feb 20, 2015
c81e5fb
Annotate prereq conditions with their RFC meanings
Feb 23, 2015
36a19cc
Tests for Name is/is not in use
Feb 23, 2015
18a38b2
Fix prereq checks for name-(not-)in-use
Feb 23, 2015
383a06c
Tests for RRset exists (value independent)
Feb 23, 2015
0233539
Use the rrset-exists helper in prereq checks
Feb 23, 2015
8dc8889
Add strict-rr-match helper to resolver
Feb 23, 2015
b8fa209
Validate RRset matches in prereq checks
Feb 23, 2015
82b0506
Factor out test boilerplate via reflection
Feb 24, 2015
f145857
Make test faliures report right line
Feb 24, 2015
16786e0
Tests for RRset exists (value dependent)
Feb 24, 2015
ab7021f
Cover RRset does not exist too
Feb 24, 2015
ab3aaa9
Internal notes on fixes needed in performUpdate
Feb 24, 2015
b6c5859
Full validation of update RRS before updating
Feb 24, 2015
a99838a
Don't shadow the etcd package
Feb 24, 2015
ffb2ff6
Fix delete tests:
Feb 25, 2015
cc870c8
Check TTL on inserted record
Feb 25, 2015
fc4b785
Add (failing) test for Delete-RRset:
Feb 25, 2015
dbe9da9
WIP: rewrite performUpdate:
Feb 25, 2015
2bb281e
More refactoring of performUpdate:
Feb 25, 2015
6b0f450
Full CNAME support, warts and all
Feb 26, 2015
c750c0e
Don't call `SetRcode`, set it directly:
May 22, 2015
65cb285
Send correct response opcode
May 22, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ An authoritative DNS nameserver that queries an [etcd](http://github.com/coreos/
- Support for TTLs
- Global default on all records
- Individual TTL values for individual records
- Runtime and application metrics are captured regularly for monitoring (stdout or grahite)
- Runtime and application metrics are captured regularly for monitoring (stdout or graphite)
- Incoming query filters

#### Production Readyness
Expand Down
23 changes: 23 additions & 0 deletions format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"bytes"
"strings"
)

// nameToKey returns a string representing the etcd version of a domain, replacing dots with slashes
// and reversing it (foo.net. -> /net/foo)
func nameToKey(name string, suffix string) string {
segments := strings.Split(name, ".")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you really want to use dns.Split or dns.SplitDomainName in these cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I only just came across all of the domain label splitting functions earlier for this. Will update them all! Thanks.


var keyBuffer bytes.Buffer
for i := len(segments) - 1; i >= 0; i-- {
if len(segments[i]) > 0 {
keyBuffer.WriteString("/")
keyBuffer.WriteString(segments[i])
}
}

keyBuffer.WriteString(suffix)
return keyBuffer.String()
}
13 changes: 13 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var (
DefaultTtl uint32 `short:"t" long:"default-ttl" description:"Default TTL to return on records without an explicit TTL" default:"300"`
Accept []string `long:"accept" description:"Limit DNS queries to a set of domain:[type,...] pairs"`
Reject []string `long:"reject" description:"Limit DNS queries to a set of domain:[type,...] pairs"`
TsigSecret []string `short:"s" long:"tsig" description:"Transaction signature secret in the format name:secret"`
}
)

Expand Down Expand Up @@ -79,6 +80,17 @@ func main() {
logger.Printf("Metric logging disabled")
}

// Parse the tsig arguments
tsigSecret := map[string]string{}
for _, arg := range Options.TsigSecret {
components := strings.SplitN(arg, ":", 2)
if len(components) != 2 {
logger.Printf("Failed to parse TSIG argument")
continue
}
tsigSecret[dns.Fqdn(components[0])] = components[1]
}

// Start up the DNS resolver server
server := &Server{
addr: Options.ListenAddress,
Expand All @@ -87,6 +99,7 @@ func main() {
rTimeout: time.Duration(5) * time.Second,
wTimeout: time.Duration(5) * time.Second,
defaultTtl: Options.DefaultTtl,
tsigSecret: tsigSecret,
queryFilterer: &QueryFilterer{acceptFilters: parseFilters(Options.Accept),
rejectFilters: parseFilters(Options.Reject)}}

Expand Down
20 changes: 1 addition & 19 deletions resolver.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"bytes"
"fmt"
"github.com/coreos/go-etcd/etcd"
"github.com/miekg/dns"
Expand Down Expand Up @@ -246,7 +245,7 @@ func (r *Resolver) AnswerQuestion(answers chan dns.RR, errors chan error, q dns.

for rrType, _ := range converters {
go func(rrType uint16) {
defer func() { recover() }()
defer recover()
defer wg.Done()

results, err := r.LookupAnswersForType(q.Name, rrType)
Expand Down Expand Up @@ -325,23 +324,6 @@ func (r *Resolver) LookupAnswersForType(name string, rrType uint16) (answers []d
return
}

// nameToKey returns a string representing the etcd version of a domain, replacing dots with slashes
// and reversing it (foo.net. -> /net/foo)
func nameToKey(name string, suffix string) string {
segments := strings.Split(name, ".")

var keyBuffer bytes.Buffer
for i := len(segments) - 1; i >= 0; i-- {
if len(segments[i]) > 0 {
keyBuffer.WriteString("/")
keyBuffer.WriteString(segments[i])
}
}

keyBuffer.WriteString(suffix)
return keyBuffer.String()
}

// Map of conversion functions that turn individual etcd nodes into dns.RR answers
var converters = map[uint16]func (node *etcd.Node, header dns.RR_Header) (rr dns.RR, err error) {

Expand Down
98 changes: 69 additions & 29 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,86 @@ type Server struct {
rTimeout time.Duration
wTimeout time.Duration
defaultTtl uint32
tsigSecret map[string]string
queryFilterer *QueryFilterer
}

type Handler struct {
resolver *Resolver
queryFilterer *QueryFilterer
updateManager *DynamicUpdateManager

// Metrics
requestCounter metrics.Counter
acceptCounter metrics.Counter
rejectCounter metrics.Counter
// authFailCounter metrics.Counter
// authSuccessCounter metrics.Counter
responseTimer metrics.Timer
}

func (h *Handler) Handle(response dns.ResponseWriter, req *dns.Msg) {
h.requestCounter.Inc(1)
h.responseTimer.Time(func() {
debugMsg("Handling incoming query for domain " + req.Question[0].Name)

// Lookup the dns record for the request
// This method will add any answers to the message
var msg *dns.Msg
if h.queryFilterer.ShouldAcceptQuery(req) != true {
debugMsg("Query not accepted")

h.rejectCounter.Inc(1)

msg = new(dns.Msg)
msg.SetReply(req)
msg.SetRcode(req, dns.RcodeNameError)
msg.Authoritative = true
msg.RecursionAvailable = false

// Add a useful TXT record
header := dns.RR_Header{Name: req.Question[0].Name,
Class: dns.ClassINET,
Rrtype: dns.TypeTXT}
msg.Ns = []dns.RR{&dns.TXT{header, []string{"Rejected query based on matched filters"}}}
debugMsg("Incoming message with opcode " + dns.OpcodeToString[req.MsgHdr.Opcode])

var res *dns.Msg
if req.MsgHdr.Opcode == dns.OpcodeQuery {
// TODO(tarnfeld): Support for multiple questions?
debugMsg("Handling incoming query for domain " + req.Question[0].Name)

// Lookup the dns record for the request
// This method will add any answers to the message
if h.queryFilterer.ShouldAcceptQuery(req) != true {
debugMsg("Query not accepted")

h.rejectCounter.Inc(1)

res = new(dns.Msg)
res.SetReply(req)
res.SetRcode(req, dns.RcodeNameError)
res.Authoritative = true
res.RecursionAvailable = false

// Add a useful TXT record
header := dns.RR_Header{Name: req.Question[0].Name,
Class: dns.ClassINET,
Rrtype: dns.TypeTXT}
res.Ns = []dns.RR{&dns.TXT{header, []string{"Rejected query based on matched filters"}}}
} else {
h.acceptCounter.Inc(1)
res = h.resolver.Lookup(req)
}
} else if req.MsgHdr.Opcode == dns.OpcodeUpdate {
zone := req.Question[0].Name
debugMsg("Handling incoming update for zone " + zone)

res = new(dns.Msg)
res.SetReply(req)

// Authenticate the request
if req.IsTsig() != nil && response.TsigStatus() == nil {
sig := req.IsTsig()
debugMsg("Authenticated update request")

// Verify the tsig is for the correct zone
if sig.Hdr.Name != zone {
res.SetRcode(req, dns.RcodeBadSig)
} else {
res = h.updateManager.Update(zone, req)
}
} else {
debugMsg("Authentication failed")
res.SetRcode(req, dns.RcodeNotAuth)
}
} else {
h.acceptCounter.Inc(1)
msg = h.resolver.Lookup(req)
res = new(dns.Msg)
res.SetReply(req)
res.SetRcode(req, dns.RcodeNotImplemented)
}

if msg != nil {
err := response.WriteMsg(msg)
if res != nil {
err := response.WriteMsg(res)
if err != nil {
debugMsg("Error writing message: ", err)
}
Expand Down Expand Up @@ -94,20 +129,23 @@ func (s *Server) Run() {
metrics.Register("request.handler.udp.filter_rejects", udpRejectCounter)

resolver := Resolver{etcd: s.etcd, defaultTtl: s.defaultTtl}
updateManager := DynamicUpdateManager{etcd: s.etcd}
tcpDNShandler := &Handler{
resolver: &resolver,
requestCounter: tcpRequestCounter,
acceptCounter: tcpAcceptCounter,
rejectCounter: tcpRejectCounter,
responseTimer: tcpResponseTimer,
queryFilterer: s.queryFilterer}
queryFilterer: s.queryFilterer,
updateManager: &updateManager}
udpDNShandler := &Handler{
resolver: &resolver,
requestCounter: udpRequestCounter,
acceptCounter: udpAcceptCounter,
rejectCounter: udpRejectCounter,
responseTimer: udpResponseTimer,
queryFilterer: s.queryFilterer}
queryFilterer: s.queryFilterer,
updateManager: &updateManager}

udpHandler := dns.NewServeMux()
tcpHandler := dns.NewServeMux()
Expand All @@ -119,14 +157,16 @@ func (s *Server) Run() {
Net: "tcp",
Handler: tcpHandler,
ReadTimeout: s.rTimeout,
WriteTimeout: s.wTimeout}
WriteTimeout: s.wTimeout,
TsigSecret: s.tsigSecret}

udpServer := &dns.Server{Addr: s.Addr(),
Net: "udp",
Handler: udpHandler,
UDPSize: 65535,
ReadTimeout: s.rTimeout,
WriteTimeout: s.wTimeout}
WriteTimeout: s.wTimeout,
TsigSecret: s.tsigSecret}

go s.start(udpServer)
go s.start(tcpServer)
Expand Down
Loading