1
0

dns_server.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. package nebula
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "net"
  7. "net/netip"
  8. "strconv"
  9. "strings"
  10. "sync"
  11. "sync/atomic"
  12. "github.com/gaissmai/bart"
  13. "github.com/miekg/dns"
  14. "github.com/slackhq/nebula/config"
  15. )
  16. type dnsServer struct {
  17. sync.RWMutex
  18. l *slog.Logger
  19. ctx context.Context
  20. dnsMap4 map[string]netip.Addr
  21. dnsMap6 map[string]netip.Addr
  22. hostMap *HostMap
  23. myVpnAddrsTable *bart.Lite
  24. mux *dns.ServeMux
  25. // enabled mirrors `lighthouse.serve_dns && lighthouse.am_lighthouse`.
  26. // Start, Add, and reload consult it so callers don't need to know the
  27. // gating rules. When it toggles off via reload, accumulated records are
  28. // cleared so a later re-enable starts with a fresh map populated from
  29. // new handshakes.
  30. enabled atomic.Bool
  31. serverMu sync.Mutex
  32. server *dns.Server
  33. // started is closed once `server` has finished binding (or after
  34. // ListenAndServe returns on a bind failure). Stop waits on it before
  35. // calling Shutdown to avoid the miekg/dns "server not started" race
  36. // where a Shutdown that arrives before bind completes is silently
  37. // ignored, leaving the listener running forever.
  38. started chan struct{}
  39. addr string
  40. }
  41. // newDnsServerFromConfig builds a dnsServer, applies the initial config, and
  42. // registers a reload callback. The reload callback is registered before the
  43. // initial config is applied, so a SIGHUP can later enable, fix, or disable
  44. // DNS even if the initial application failed.
  45. //
  46. // The dnsServer internally gates on `lighthouse.serve_dns &&
  47. // lighthouse.am_lighthouse`. Start and Add are safe to call unconditionally,
  48. // they no-op when DNS isn't enabled. Each Start invocation owns a ctx-cancel
  49. // watcher that tears the listener down on nebula shutdown. The returned
  50. // pointer is always non-nil, even on error.
  51. func newDnsServerFromConfig(ctx context.Context, l *slog.Logger, cs *CertState, hostMap *HostMap, c *config.C) (*dnsServer, error) {
  52. ds := &dnsServer{
  53. l: l,
  54. ctx: ctx,
  55. dnsMap4: make(map[string]netip.Addr),
  56. dnsMap6: make(map[string]netip.Addr),
  57. hostMap: hostMap,
  58. myVpnAddrsTable: cs.myVpnAddrsTable,
  59. }
  60. ds.mux = dns.NewServeMux()
  61. ds.mux.HandleFunc(".", ds.handleDnsRequest)
  62. c.RegisterReloadCallback(func(c *config.C) {
  63. if err := ds.reload(c, false); err != nil {
  64. ds.l.Error("Failed to reload DNS responder from config", "error", err)
  65. }
  66. })
  67. if err := ds.reload(c, true); err != nil {
  68. return ds, err
  69. }
  70. return ds, nil
  71. }
  72. // reload applies the latest config and reconciles the running state with it:
  73. // - enabled toggled on -> spawn a runner
  74. // - enabled toggled off -> stop the runner
  75. // - listen address changed (while running) -> restart on the new address
  76. // - everything else -> no-op
  77. //
  78. // On the initial call it only records configuration; Control.Start is what
  79. // launches the first runner via dnsStart.
  80. func (d *dnsServer) reload(c *config.C, initial bool) error {
  81. wantsDns := c.GetBool("lighthouse.serve_dns", false)
  82. amLighthouse := c.GetBool("lighthouse.am_lighthouse", false)
  83. enabled := wantsDns && amLighthouse
  84. newAddr := getDnsServerAddr(c)
  85. d.serverMu.Lock()
  86. running := d.server
  87. runningStarted := d.started
  88. sameAddr := d.addr == newAddr
  89. d.addr = newAddr
  90. d.enabled.Store(enabled)
  91. d.serverMu.Unlock()
  92. if initial {
  93. if wantsDns && !amLighthouse {
  94. d.l.Warn("DNS server refusing to run because this host is not a lighthouse.")
  95. }
  96. return nil
  97. }
  98. if !enabled {
  99. if running != nil {
  100. d.Stop()
  101. }
  102. // Drop any records that accumulated while enabled; a later re-enable
  103. // will repopulate from fresh handshakes.
  104. d.clearRecords()
  105. return nil
  106. }
  107. if running == nil {
  108. // Was disabled (or never started); bring it up now.
  109. go d.Start()
  110. return nil
  111. }
  112. if sameAddr {
  113. return nil
  114. }
  115. d.shutdownServer(running, runningStarted, "reload")
  116. // Old Start goroutine has now exited; bring up a fresh listener on the
  117. // new address.
  118. go d.Start()
  119. return nil
  120. }
  121. // shutdownServer waits for the server to finish binding (so Shutdown actually
  122. // stops it rather than no-oping) and then shuts it down.
  123. func (d *dnsServer) shutdownServer(srv *dns.Server, started chan struct{}, reason string) {
  124. if srv == nil {
  125. return
  126. }
  127. if started != nil {
  128. <-started
  129. }
  130. if err := srv.Shutdown(); err != nil {
  131. d.l.Warn("Failed to shut down the DNS responder", "reason", reason, "error", err)
  132. }
  133. }
  134. // Start binds and serves the DNS responder. Blocks until Stop is called or
  135. // the listener errors. Safe to call when DNS is disabled (returns
  136. // immediately). This is what Control.dnsStart points at.
  137. //
  138. // Must be invoked after the tun device is active so that lighthouse.dns.host
  139. // may bind to a nebula IP.
  140. func (d *dnsServer) Start() {
  141. if !d.enabled.Load() {
  142. return
  143. }
  144. started := make(chan struct{})
  145. d.serverMu.Lock()
  146. if d.ctx.Err() != nil {
  147. d.serverMu.Unlock()
  148. return
  149. }
  150. addr := d.addr
  151. server := &dns.Server{
  152. Addr: addr,
  153. Net: "udp",
  154. Handler: d.mux,
  155. NotifyStartedFunc: func() { close(started) },
  156. }
  157. d.server = server
  158. d.started = started
  159. d.serverMu.Unlock()
  160. // Per-invocation ctx watcher. Exits when Start does, so we don't leak a
  161. // watcher per reload-driven restart.
  162. done := make(chan struct{})
  163. go func() {
  164. select {
  165. case <-d.ctx.Done():
  166. d.shutdownServer(server, started, "shutdown")
  167. case <-done:
  168. }
  169. }()
  170. d.l.Info("Starting DNS responder", "dnsListener", addr)
  171. err := server.ListenAndServe()
  172. close(done)
  173. // If the listener never bound (bind error) NotifyStartedFunc never fires,
  174. // so close started here to release any Stop caller waiting on it.
  175. select {
  176. case <-started:
  177. default:
  178. close(started)
  179. }
  180. if err != nil {
  181. d.l.Warn("Failed to run the DNS responder", "error", err)
  182. }
  183. }
  184. // Stop shuts down the active server, if any. Idempotent.
  185. func (d *dnsServer) Stop() {
  186. d.serverMu.Lock()
  187. srv := d.server
  188. started := d.started
  189. d.server = nil
  190. d.started = nil
  191. d.serverMu.Unlock()
  192. d.shutdownServer(srv, started, "stop")
  193. }
  194. // Query returns the address for the given name and query type. The second
  195. // return value reports whether the name is known at all (in either A or AAAA),
  196. // which lets callers distinguish NODATA from NXDOMAIN.
  197. func (d *dnsServer) Query(q uint16, data string) (netip.Addr, bool) {
  198. data = strings.ToLower(data)
  199. d.RLock()
  200. defer d.RUnlock()
  201. addr4, haveV4 := d.dnsMap4[data]
  202. addr6, haveV6 := d.dnsMap6[data]
  203. nameExists := haveV4 || haveV6
  204. switch q {
  205. case dns.TypeA:
  206. if haveV4 {
  207. return addr4, nameExists
  208. }
  209. case dns.TypeAAAA:
  210. if haveV6 {
  211. return addr6, nameExists
  212. }
  213. }
  214. return netip.Addr{}, nameExists
  215. }
  216. func (d *dnsServer) QueryCert(data string) string {
  217. if len(data) < 2 {
  218. return ""
  219. }
  220. ip, err := netip.ParseAddr(data[:len(data)-1])
  221. if err != nil {
  222. return ""
  223. }
  224. hostinfo := d.hostMap.QueryVpnAddr(ip)
  225. if hostinfo == nil {
  226. return ""
  227. }
  228. q := hostinfo.GetCert()
  229. if q == nil {
  230. return ""
  231. }
  232. b, err := q.Certificate.MarshalJSON()
  233. if err != nil {
  234. return ""
  235. }
  236. return string(b)
  237. }
  238. // clearRecords drops all DNS records.
  239. func (d *dnsServer) clearRecords() {
  240. d.Lock()
  241. defer d.Unlock()
  242. clear(d.dnsMap4)
  243. clear(d.dnsMap6)
  244. }
  245. // Add adds the first IPv4 and IPv6 address that appears in `addresses` as the record for `host`
  246. func (d *dnsServer) Add(host string, addresses []netip.Addr) {
  247. if !d.enabled.Load() {
  248. return
  249. }
  250. host = strings.ToLower(host)
  251. d.Lock()
  252. defer d.Unlock()
  253. haveV4 := false
  254. haveV6 := false
  255. for _, addr := range addresses {
  256. if addr.Is4() && !haveV4 {
  257. d.dnsMap4[host] = addr
  258. haveV4 = true
  259. } else if addr.Is6() && !haveV6 {
  260. d.dnsMap6[host] = addr
  261. haveV6 = true
  262. }
  263. if haveV4 && haveV6 {
  264. break
  265. }
  266. }
  267. }
  268. func (d *dnsServer) isSelfNebulaOrLocalhost(addr string) bool {
  269. a, _, _ := net.SplitHostPort(addr)
  270. b, err := netip.ParseAddr(a)
  271. if err != nil {
  272. return false
  273. }
  274. if b.IsLoopback() {
  275. return true
  276. }
  277. //if we found it in this table, it's good
  278. return d.myVpnAddrsTable.Contains(b)
  279. }
  280. func (d *dnsServer) parseQuery(m *dns.Msg, w dns.ResponseWriter) {
  281. debugEnabled := d.l.Enabled(context.Background(), slog.LevelDebug)
  282. // Per RFC 2308 §2.2, a name that exists but has no record of the requested
  283. // type must be answered with NOERROR and an empty answer section (NODATA),
  284. // not NXDOMAIN (RFC 2308 §2.1), which is reserved for names that do not
  285. // exist at all.
  286. anyNameExists := false
  287. for _, q := range m.Question {
  288. switch q.Qtype {
  289. case dns.TypeA, dns.TypeAAAA:
  290. qType := dns.TypeToString[q.Qtype]
  291. if debugEnabled {
  292. d.l.Debug("DNS query", "type", qType, "name", q.Name)
  293. }
  294. ip, nameExists := d.Query(q.Qtype, q.Name)
  295. if nameExists {
  296. anyNameExists = true
  297. }
  298. if ip.IsValid() {
  299. rr, err := dns.NewRR(fmt.Sprintf("%s %s %s", q.Name, qType, ip))
  300. if err == nil {
  301. m.Answer = append(m.Answer, rr)
  302. }
  303. }
  304. case dns.TypeTXT:
  305. // We only answer these queries from nebula nodes or localhost
  306. if !d.isSelfNebulaOrLocalhost(w.RemoteAddr().String()) {
  307. return
  308. }
  309. if debugEnabled {
  310. d.l.Debug("DNS query", "type", "TXT", "name", q.Name)
  311. }
  312. ip := d.QueryCert(q.Name)
  313. if ip != "" {
  314. rr, err := dns.NewRR(fmt.Sprintf("%s TXT %s", q.Name, ip))
  315. if err == nil {
  316. m.Answer = append(m.Answer, rr)
  317. }
  318. }
  319. }
  320. }
  321. if len(m.Answer) == 0 && !anyNameExists {
  322. m.Rcode = dns.RcodeNameError
  323. }
  324. }
  325. func (d *dnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) {
  326. m := new(dns.Msg)
  327. m.SetReply(r)
  328. m.Compress = false
  329. switch r.Opcode {
  330. case dns.OpcodeQuery:
  331. d.parseQuery(m, w)
  332. }
  333. w.WriteMsg(m)
  334. }
  335. func getDnsServerAddr(c *config.C) string {
  336. dnsHost := strings.TrimSpace(c.GetString("lighthouse.dns.host", ""))
  337. // Old guidance was to provide the literal `[::]` in `lighthouse.dns.host` but that won't resolve.
  338. if dnsHost == "[::]" {
  339. dnsHost = "::"
  340. }
  341. return net.JoinHostPort(dnsHost, strconv.Itoa(c.GetInt("lighthouse.dns.port", 53)))
  342. }