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/BUILD.bazel b/osbase/net/dns/test/BUILD.bazel
new file mode 100644
index 0000000..3cbd61d
--- /dev/null
+++ b/osbase/net/dns/test/BUILD.bazel
@@ -0,0 +1,16 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "test",
+ srcs = ["server.go"],
+ importpath = "source.monogon.dev/osbase/net/dns/test",
+ visibility = ["//osbase/net/dns:__subpackages__"],
+ deps = ["@com_github_miekg_dns//:dns"],
+)
+
+go_test(
+ name = "test_test",
+ srcs = ["server_test.go"],
+ embed = [":test"],
+ deps = ["@com_github_miekg_dns//:dns"],
+)
diff --git a/osbase/net/dns/test/LICENSE-3RD-PARTY.txt b/osbase/net/dns/test/LICENSE-3RD-PARTY.txt
new file mode 100644
index 0000000..98f9935
--- /dev/null
+++ b/osbase/net/dns/test/LICENSE-3RD-PARTY.txt
@@ -0,0 +1,13 @@
+Copyright 2016-2024 The CoreDNS authors and contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
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
+}
diff --git a/osbase/net/dns/test/server_test.go b/osbase/net/dns/test/server_test.go
new file mode 100644
index 0000000..ea60845
--- /dev/null
+++ b/osbase/net/dns/test/server_test.go
@@ -0,0 +1,39 @@
+package test
+
+// Taken and modified from CoreDNS, under Apache 2.0.
+
+import (
+ "testing"
+
+ "github.com/miekg/dns"
+)
+
+func TestNewServer(t *testing.T) {
+ s := NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
+ ret := new(dns.Msg)
+ ret.SetReply(r)
+ w.WriteMsg(ret)
+ })
+ defer s.Close()
+
+ c := new(dns.Client)
+ c.Net = "tcp"
+ m := new(dns.Msg)
+ m.SetQuestion("example.org.", dns.TypeSOA)
+ ret, _, err := c.Exchange(m, s.Addr)
+ if err != nil {
+ t.Fatalf("Could not send message to dnstest.Server: %s", err)
+ }
+ if ret.Id != m.Id {
+ t.Fatalf("Msg ID's should match, expected %d, got %d", m.Id, ret.Id)
+ }
+
+ c.Net = "udp"
+ ret, _, err = c.Exchange(m, s.Addr)
+ if err != nil {
+ t.Fatalf("Could not send message to dnstest.Server: %s", err)
+ }
+ if ret.Id != m.Id {
+ t.Fatalf("Msg ID's should match, expected %d, got %d", m.Id, ret.Id)
+ }
+}