Skip to content

Commit 2967388

Browse files
authored
fix(ipparser): return SOA with NOERROR responses (#61)
* fix(ipparser): return SOA with NOERROR responses aims to fix #52 according to RFC 2308 NODATA responses (NOERROR with no answers) should include an SOA in the AUTHORITY section to specify the negative caching TTL ipparser produced synthetic responses which were done by hand, and thus did not benefit from coredns setting SOA. this code reads SOA from the zone file, and attaches it to NOERROR responses produced by ipparser plugin * refactor: reuse entire dns.RR this is a static response, fine to reuse across all responses
1 parent 695bd14 commit 2967388

1 file changed

Lines changed: 47 additions & 1 deletion

File tree

ipparser/plugin.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package ipparser
22

33
import (
44
"context"
5+
"fmt"
56
"net/netip"
7+
"os"
8+
"path/filepath"
69
"strings"
710
"time"
811

@@ -32,9 +35,43 @@ func setup(c *caddy.Controller) error {
3235
return plugin.Error(pluginName, c.ArgErr())
3336
}
3437

38+
// Read SOA from zone/{forgeDomain} file
39+
var soa *dns.SOA
40+
config := dnsserver.GetConfig(c)
41+
zoneFile := filepath.Join(config.Root, "zones", forgeDomain)
42+
f, err := os.Open(filepath.Clean(zoneFile))
43+
if err != nil {
44+
return plugin.Error(pluginName, fmt.Errorf("failed to open zone file %s: %v", zoneFile, err))
45+
}
46+
defer f.Close()
47+
zp := dns.NewZoneParser(f, forgeDomain+".", zoneFile)
48+
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
49+
if s, ok := rr.(*dns.SOA); ok {
50+
soa = s
51+
break
52+
}
53+
}
54+
soaRR := []dns.RR{
55+
&dns.SOA{
56+
Hdr: dns.RR_Header{
57+
Name: dns.Fqdn(forgeDomain + "."),
58+
Rrtype: dns.TypeSOA,
59+
Class: dns.ClassINET,
60+
Ttl: soa.Hdr.Ttl,
61+
},
62+
Ns: soa.Ns,
63+
Mbox: soa.Mbox,
64+
Serial: soa.Serial,
65+
Refresh: soa.Refresh,
66+
Retry: soa.Retry,
67+
Expire: soa.Expire,
68+
Minttl: soa.Minttl,
69+
},
70+
}
71+
3572
// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
3673
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
37-
return ipParser{Next: next, ForgeDomain: strings.ToLower(forgeDomain)}
74+
return ipParser{Next: next, ForgeDomain: strings.ToLower(forgeDomain), SOA: soaRR}
3875
})
3976

4077
return nil
@@ -43,6 +80,7 @@ func setup(c *caddy.Controller) error {
4380
type ipParser struct {
4481
Next plugin.Handler
4582
ForgeDomain string
83+
SOA []dns.RR // Cached SOA record from zone file
4684
}
4785

4886
// The TTL for self-referential ip.peerid.etld A/AAAA records can be as long as possible.
@@ -141,6 +179,14 @@ func (p ipParser) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
141179
m.SetReply(r)
142180
m.Authoritative = true
143181
m.Answer = answers
182+
183+
// RFC 2308 Compliance: NODATA responses (NOERROR with no answers)
184+
// should include an SOA in the AUTHORITY section to specify the
185+
// negative caching TTL (https://github.com/ipshipyard/p2p-forge/issues/52).
186+
if containsNODATAResponse {
187+
m.Ns = p.SOA
188+
}
189+
144190
err := w.WriteMsg(&m)
145191
if err != nil {
146192
return dns.RcodeServerFailure, err

0 commit comments

Comments
 (0)