• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

joohoi / acme-dns / 21714519299

05 Feb 2026 02:04PM UTC coverage: 77.229% (+4.0%) from 73.276%
21714519299

push

github

web-flow
Refactoring (#325)

* Refactor core

* Re-added tests

* Small fixes

* Add tests for acmetxt cidrslice and util funcs

* Remove the last dangling reference to old logging package

* Refactoring (#327)

* chore: enable more linters and fix linter issues

* ci: enable linter checks on all branches and disable recurring checks

recurring linter checks don't make that much sense. The code & linter checks should not change on their own over night ;)

* chore: update packages

* Revert "chore: update packages"

This reverts commit 30250bf28.

* chore: manually upgrade some packages

* Updated dependencies, wrote changelog entry and fixed namespace for release

* Refactoring - improving coverage (#371)

* Increase code coverage in acmedns

* More testing of ReadConfig() and its fallback mechanism

* Found that if someone put a '"' double quote into the filename that we configure zap to log to, it would cause the the JSON created to be invalid. I have replaced the JSON string with proper config

* Better handling of config options for api.TLS - we now error on an invalid value instead of silently failing.

added a basic test for api.setupTLS() (to increase test coverage)

* testing nameserver isOwnChallenge and isAuthoritative methods

* add a unit test for nameserver answerOwnChallenge

* fix linting errors

* bump go and golangci-lint versions in github actions

* Update golangci-lint.yml

Bumping github-actions workflow versions to accommodate some changes in upstream golanci-lint

* Bump Golang version to 1.23 (currently the oldest supported version)

Bump golanglint-ci to 2.0.2 and migrate the config file.

This should resolve the math/rand/v2 issue

* bump golanglint-ci action version

* Fixing up new golanglint-ci warnings and errors

---------

Co-authored-by: Joona Hoikkala <5235109+joohoi@users.noreply.github.com>

* Minor refactoring, error returns and e2e testing suite

* Add a few tests

* Fix linter an... (continued)

582 of 764 new or added lines in 20 files covered. (76.18%)

3 existing lines in 1 file now uncovered.

797 of 1032 relevant lines covered (77.23%)

7.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.2
/pkg/nameserver/handler.go
1
package nameserver
2

3
import (
4
        "fmt"
5
        "strings"
6

7
        "github.com/miekg/dns"
8
)
9

10
func (n *Nameserver) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
10✔
11
        m := new(dns.Msg)
10✔
12
        m.SetReply(r)
10✔
13
        // handle edns0
10✔
14
        opt := r.IsEdns0()
10✔
15
        if opt != nil {
12✔
16
                if opt.Version() != 0 {
3✔
17
                        // Only EDNS0 is standardized
1✔
18
                        m.Rcode = dns.RcodeBadVers
1✔
19
                        m.SetEdns0(512, false)
1✔
20
                } else {
2✔
21
                        // We can safely do this as we know that we're not setting other OPT RRs within acme-dns.
1✔
22
                        m.SetEdns0(512, false)
1✔
23
                        if r.Opcode == dns.OpcodeQuery {
2✔
24
                                n.readQuery(m)
1✔
25
                        }
1✔
26
                }
27
        } else {
8✔
28
                if r.Opcode == dns.OpcodeQuery {
16✔
29
                        n.readQuery(m)
8✔
30
                }
8✔
31
        }
32
        _ = w.WriteMsg(m)
10✔
33
}
34

35
func (n *Nameserver) readQuery(m *dns.Msg) {
9✔
36
        var authoritative = false
9✔
37
        for _, que := range m.Question {
18✔
38
                if rr, rc, auth, err := n.answer(que); err == nil {
18✔
39
                        if auth {
16✔
40
                                authoritative = auth
7✔
41
                        }
7✔
42
                        m.Rcode = rc
9✔
43
                        m.Answer = append(m.Answer, rr...)
9✔
44
                }
45
        }
46
        m.Authoritative = authoritative
9✔
47
        if authoritative {
16✔
48
                if m.Rcode == dns.RcodeNameError {
9✔
49
                        m.Ns = append(m.Ns, n.SOA)
2✔
50
                }
2✔
51
        }
52
}
53

54
func (n *Nameserver) answer(q dns.Question) ([]dns.RR, int, bool, error) {
13✔
55
        var rcode int
13✔
56
        var err error
13✔
57
        var txtRRs []dns.RR
13✔
58
        loweredName := strings.ToLower(q.Name)
13✔
59
        var authoritative = n.isAuthoritative(loweredName)
13✔
60
        if !n.isOwnChallenge(loweredName) && !n.answeringForDomain(loweredName) {
21✔
61
                rcode = dns.RcodeNameError
8✔
62
        }
8✔
63
        r, _ := n.getRecord(loweredName, q.Qtype)
13✔
64
        if q.Qtype == dns.TypeTXT {
16✔
65
                if n.isOwnChallenge(loweredName) {
3✔
NEW
66
                        txtRRs, err = n.answerOwnChallenge(q)
×
67
                } else {
3✔
68
                        txtRRs, err = n.answerTXT(q)
3✔
69
                }
3✔
70
                if err == nil {
6✔
71
                        r = append(r, txtRRs...)
3✔
72
                }
3✔
73
        }
74
        if len(r) > 0 {
19✔
75
                // Make sure that we return NOERROR if there were dynamic records for the domain
6✔
76
                rcode = dns.RcodeSuccess
6✔
77
        }
6✔
78
        n.Logger.Debugw("Answering question for domain",
13✔
79
                "qtype", dns.TypeToString[q.Qtype],
13✔
80
                "domain", q.Name,
13✔
81
                "rcode", dns.RcodeToString[rcode])
13✔
82
        return r, rcode, authoritative, nil
13✔
83
}
84

85
func (n *Nameserver) answerTXT(q dns.Question) ([]dns.RR, error) {
5✔
86
        var ra []dns.RR
5✔
87
        subdomain := sanitizeDomainQuestion(q.Name)
5✔
88
        atxt, err := n.DB.GetTXTForDomain(subdomain)
5✔
89
        if err != nil {
7✔
90
                n.Logger.Errorw("Error while trying to get record",
2✔
91
                        "error", err.Error())
2✔
92
                return ra, err
2✔
93
        }
2✔
94
        for _, v := range atxt {
7✔
95
                if len(v) > 0 {
6✔
96
                        r := new(dns.TXT)
2✔
97
                        r.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}
2✔
98
                        r.Txt = append(r.Txt, v)
2✔
99
                        ra = append(ra, r)
2✔
100
                }
2✔
101
        }
102
        return ra, nil
3✔
103
}
104

105
func (n *Nameserver) isAuthoritative(name string) bool {
17✔
106
        if n.answeringForDomain(name) {
24✔
107
                return true
7✔
108
        }
7✔
109
        off := 0
10✔
110
        for {
26✔
111
                i, next := dns.NextLabel(name, off)
16✔
112
                if next {
20✔
113
                        return false
4✔
114
                }
4✔
115
                off = i
12✔
116
                if n.answeringForDomain(name[off:]) {
18✔
117
                        return true
6✔
118
                }
6✔
119
        }
120
}
121

122
func (n *Nameserver) isOwnChallenge(name string) bool {
20✔
123
        if strings.HasPrefix(name, "_acme-challenge.") {
22✔
124
                domain := name[16:]
2✔
125
                if domain == n.OwnDomain {
3✔
126
                        return true
1✔
127
                }
1✔
128
        }
129
        return false
19✔
130
}
131

132
// answeringForDomain checks if we have any records for a domain
133
func (n *Nameserver) answeringForDomain(name string) bool {
42✔
134
        if n.OwnDomain == name {
57✔
135
                return true
15✔
136
        }
15✔
137
        _, ok := n.Domains[name]
27✔
138
        return ok
27✔
139
}
140

141
func (n *Nameserver) getRecord(name string, qtype uint16) ([]dns.RR, error) {
13✔
142
        var rr []dns.RR
13✔
143
        var cnames []dns.RR
13✔
144
        domain, ok := n.Domains[name]
13✔
145
        if !ok {
21✔
146
                return rr, fmt.Errorf("no records for domain %s", name)
8✔
147
        }
8✔
148
        for _, ri := range domain.Records {
14✔
149
                if ri.Header().Rrtype == qtype {
13✔
150
                        rr = append(rr, ri)
4✔
151
                }
4✔
152
                if ri.Header().Rrtype == dns.TypeCNAME {
10✔
153
                        cnames = append(cnames, ri)
1✔
154
                }
1✔
155
        }
156
        if len(rr) == 0 {
6✔
157
                return cnames, nil
1✔
158
        }
1✔
159
        return rr, nil
4✔
160
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc