osbase/net/dns: add new DNS server

This adds a new DNS server service, which will replace CoreDNS. The
service has built-in handlers for certain names, but all other names
will be handled by runtime configurable handlers.

Change-Id: I4184d11422496e899794ef658ca1450e7bb01471
Reviewed-on: https://review.monogon.dev/c/monogon/+/3126
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/osbase/net/dns/test/server.go b/osbase/net/dns/test/server.go
new file mode 100644
index 0000000..a877820
--- /dev/null
+++ b/osbase/net/dns/test/server.go
@@ -0,0 +1,73 @@
+package test
+
+// Taken and modified from CoreDNS, under Apache 2.0.
+
+import (
+	"fmt"
+	"net"
+
+	"github.com/miekg/dns"
+)
+
+// A Server is an DNS server listening on a system-chosen port on the local
+// loopback interface, for use in end-to-end DNS tests.
+type Server struct {
+	Addr string // Address where the server listening.
+
+	s1 *dns.Server // udp
+	s2 *dns.Server // tcp
+}
+
+// NewServer starts and returns a new Server. The caller should call Close when
+// finished, to shut it down.
+func NewServer(f dns.HandlerFunc) *Server {
+	ch1 := make(chan bool)
+	ch2 := make(chan bool)
+
+	s1 := &dns.Server{Handler: f} // udp
+	s2 := &dns.Server{Handler: f} // tcp
+
+	for i := 0; i < 5; i++ { // 5 attempts
+		s2.Listener, _ = net.Listen("tcp", "[::1]:0")
+		if s2.Listener == nil {
+			continue
+		}
+
+		s1.PacketConn, _ = net.ListenPacket("udp", s2.Listener.Addr().String())
+		if s1.PacketConn != nil {
+			break
+		}
+
+		// perhaps UDP port is in use, try again
+		s2.Listener.Close()
+		s2.Listener = nil
+	}
+	if s2.Listener == nil {
+		panic("dnstest.NewServer(): failed to create new server")
+	}
+
+	s1.NotifyStartedFunc = func() { close(ch1) }
+	s2.NotifyStartedFunc = func() { close(ch2) }
+	go s1.ActivateAndServe()
+	go s2.ActivateAndServe()
+
+	<-ch1
+	<-ch2
+
+	return &Server{s1: s1, s2: s2, Addr: s2.Listener.Addr().String()}
+}
+
+// Close shuts down the server.
+func (s *Server) Close() {
+	s.s1.Shutdown()
+	s.s2.Shutdown()
+}
+
+// RR parses s as a DNS resource record.
+func RR(s string) dns.RR {
+	rr, err := dns.NewRR(s)
+	if err != nil {
+		panic(fmt.Sprintf("Failed to parse rr %q: %v", s, err))
+	}
+	return rr
+}