Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package config

import (
_ "embed"
"fmt"
"os"
"strings"
Expand Down Expand Up @@ -36,6 +37,8 @@ type DnsConfig struct {
ListenPort uint `yaml:"port" envconfig:"DNS_LISTEN_PORT"`
ListenTlsPort uint `yaml:"tlsPort" envconfig:"DNS_LISTEN_TLS_PORT"`
RecursionEnabled bool `yaml:"recursionEnabled" envconfig:"DNS_RECURSION"`
RootHints string `yaml:"rootHints" envconfig:"DNS_ROOT_HINTS"`
RootHintsFile string `yaml:"rootHintsFile" envconfig:"DNS_ROOT_HINTS_FILE"`
FallbackServers []string `yaml:"fallbackServers" envconfig:"DNS_FALLBACK_SERVERS"`
}

Expand Down Expand Up @@ -69,6 +72,9 @@ type TlsConfig struct {
KeyFilePath string `yaml:"keyFilePath" envconfig:"TLS_KEY_FILE_PATH"`
}

//go:embed named.root
var defaultRootHints []byte

// Singleton config instance with default values
var globalConfig = &Config{
Logging: LoggingConfig{
Expand All @@ -78,6 +84,7 @@ var globalConfig = &Config{
ListenAddress: "",
ListenPort: 8053,
ListenTlsPort: 8853,
RootHints: string(defaultRootHints),
// hdns.io
FallbackServers: []string{
"103.196.38.38",
Expand Down Expand Up @@ -170,6 +177,14 @@ func Load(configFile string) (*Config, error) {
globalConfig.Indexer.InterceptSlot = interceptSlot
}
}
// Load root hints
if globalConfig.Dns.RootHintsFile != "" {
hintsContent, err := os.ReadFile(globalConfig.Dns.RootHintsFile)
if err != nil {
return nil, err
}
globalConfig.Dns.RootHints = string(hintsContent)
}
return globalConfig, nil
}

Expand Down
92 changes: 92 additions & 0 deletions internal/config/named.root
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
; This file holds the information on root name servers needed to
; initialize cache of Internet domain name servers
; (e.g. reference this file in the "cache . <file>"
; configuration file of BIND domain name servers).
;
; This file is made available by InterNIC
; under anonymous FTP as
; file /domain/named.cache
; on server FTP.INTERNIC.NET
; -OR- RS.INTERNIC.NET
;
; last update: November 20, 2025
; related version of root zone: 2025112001
;
; FORMERLY NS.INTERNIC.NET
;
. 3600000 NS A.ROOT-SERVERS.NET.
A.ROOT-SERVERS.NET. 3600000 A 198.41.0.4
A.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:ba3e::2:30
;
; FORMERLY NS1.ISI.EDU
;
. 3600000 NS B.ROOT-SERVERS.NET.
B.ROOT-SERVERS.NET. 3600000 A 170.247.170.2
B.ROOT-SERVERS.NET. 3600000 AAAA 2801:1b8:10::b
;
; FORMERLY C.PSI.NET
;
. 3600000 NS C.ROOT-SERVERS.NET.
C.ROOT-SERVERS.NET. 3600000 A 192.33.4.12
C.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2::c
;
; FORMERLY TERP.UMD.EDU
;
. 3600000 NS D.ROOT-SERVERS.NET.
D.ROOT-SERVERS.NET. 3600000 A 199.7.91.13
D.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2d::d
;
; FORMERLY NS.NASA.GOV
;
. 3600000 NS E.ROOT-SERVERS.NET.
E.ROOT-SERVERS.NET. 3600000 A 192.203.230.10
E.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:a8::e
;
; FORMERLY NS.ISC.ORG
;
. 3600000 NS F.ROOT-SERVERS.NET.
F.ROOT-SERVERS.NET. 3600000 A 192.5.5.241
F.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2f::f
;
; FORMERLY NS.NIC.DDN.MIL
;
. 3600000 NS G.ROOT-SERVERS.NET.
G.ROOT-SERVERS.NET. 3600000 A 192.112.36.4
G.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:12::d0d
;
; FORMERLY AOS.ARL.ARMY.MIL
;
. 3600000 NS H.ROOT-SERVERS.NET.
H.ROOT-SERVERS.NET. 3600000 A 198.97.190.53
H.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:1::53
;
; FORMERLY NIC.NORDU.NET
;
. 3600000 NS I.ROOT-SERVERS.NET.
I.ROOT-SERVERS.NET. 3600000 A 192.36.148.17
I.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fe::53
;
; OPERATED BY VERISIGN, INC.
;
. 3600000 NS J.ROOT-SERVERS.NET.
J.ROOT-SERVERS.NET. 3600000 A 192.58.128.30
J.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:c27::2:30
;
; OPERATED BY RIPE NCC
;
. 3600000 NS K.ROOT-SERVERS.NET.
K.ROOT-SERVERS.NET. 3600000 A 193.0.14.129
K.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fd::1
;
; OPERATED BY ICANN
;
. 3600000 NS L.ROOT-SERVERS.NET.
L.ROOT-SERVERS.NET. 3600000 A 199.7.83.42
L.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:9f::42
;
; OPERATED BY WIDE
;
. 3600000 NS M.ROOT-SERVERS.NET.
M.ROOT-SERVERS.NET. 3600000 A 202.12.27.33
M.ROOT-SERVERS.NET. 3600000 AAAA 2001:dc3::35
; End of file
48 changes: 46 additions & 2 deletions internal/dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ var metricQueryTotal = promauto.NewCounter(prometheus.CounterOpts{
Help: "total DNS queries handled",
})

var rootHints map[uint16]map[string][]dns.RR

func Start() error {
cfg := config.GetConfig()
listenAddr := fmt.Sprintf(
Expand All @@ -40,6 +42,10 @@ func Start() error {
slog.Info(
"starting DNS listener on " + listenAddr,
)
// Load root hints
if err := loadRootHints(cfg); err != nil {
return err
}
// Setup handler
dns.HandleFunc(".", handleQuery)
// UDP listener
Expand Down Expand Up @@ -76,6 +82,28 @@ func Start() error {
return nil
}

func loadRootHints(cfg *config.Config) error {
rootHints = make(map[uint16]map[string][]dns.RR)
for line := range strings.SplitSeq(cfg.Dns.RootHints, "\n") {
tmpRR, err := dns.NewRR(line)
if err != nil {
return fmt.Errorf("load root hints: %w", err)
}
if tmpRR == nil {
continue
}
rrType := tmpRR.Header().Rrtype
if _, ok := rootHints[rrType]; !ok {
rootHints[rrType] = make(map[string][]dns.RR)
}
rootHints[rrType][tmpRR.Header().Name] = append(
rootHints[rrType][tmpRR.Header().Name],
tmpRR,
)
}
return nil
}

func startListener(server *dns.Server) {
if err := server.ListenAndServe(); err != nil {
slog.Error(
Expand Down Expand Up @@ -492,8 +520,24 @@ func findNameserversForDomain(
return dns.Fqdn(lookupDomainName), ret, nil
}
}

return "", nil, nil
// Return root hints
ret := map[string][]net.IP{}
if rootHints != nil && rootHints[dns.TypeNS] != nil {
for _, tmpRecord := range rootHints[dns.TypeNS][`.`] {
nsRec := tmpRecord.(*dns.NS).Ns
if rootHints[dns.TypeA] != nil {
for _, aRecord := range rootHints[dns.TypeA][nsRec] {
ret[nsRec] = append(ret[nsRec], aRecord.(*dns.A).A)
}
}
if rootHints[dns.TypeAAAA] != nil {
for _, aaaaRecord := range rootHints[dns.TypeAAAA][nsRec] {
ret[nsRec] = append(ret[nsRec], aaaaRecord.(*dns.AAAA).AAAA)
}
}
}
}
return `.`, ret, nil
}

func getNameserversFromResponse(msg *dns.Msg) map[string][]net.IP {
Expand Down