diff --git a/netsim/example_dns_test.go b/netsim/example_dns_test.go index 6f628a7..3e8f465 100644 --- a/netsim/example_dns_test.go +++ b/netsim/example_dns_test.go @@ -73,3 +73,65 @@ func Example_dnsOverUDP() { // Output: // 8.8.8.8 } + +// This example shows how to use [netsim] to simulate a DNS +// server that listens for incoming requests over TCP. +func Example_dnsOverTCP() { + // Create a new scenario using the given directory to cache + // the certificates used by the simulated PKI + scenario := netsim.NewScenario("testdata") + defer scenario.Close() + + // Create server stack emulating dns.google. + // + // This includes: + // + // 1. creating, attaching, and enabling routing for a server stack + // + // 2. registering the proper domain names and addresses + // + // 3. updating the PKI database to include the server's certificate + scenario.Attach(scenario.MustNewGoogleDNSStack()) + + // Create and attach the client stack. + clientStack := scenario.MustNewClientStack() + scenario.Attach(clientStack) + + // Create a context with a watchdog timeout. + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + // Create the client connection with the DNS server. + conn, err := clientStack.DialContext(ctx, "tcp", "8.8.8.8:53") + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + // Create the query to send + query := new(dns.Msg) + query.Id = dns.Id() + query.RecursionDesired = true + query.Question = []dns.Question{{ + Name: "dns.google.", + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }} + + // Perform the DNS round trip + clientDNS := &dns.Client{Net: "tcp"} + resp, _, err := clientDNS.ExchangeWithConnContext(ctx, query, &dns.Conn{Conn: conn}) + if err != nil { + log.Fatal(err) + } + + // Print the responses + for _, ans := range resp.Answer { + if a, ok := ans.(*dns.A); ok { + fmt.Printf("%s\n", a.A.String()) + } + } + + // Output: + // 8.8.8.8 +} diff --git a/netsim/scenario.go b/netsim/scenario.go index d084fcc..78a0b6d 100644 --- a/netsim/scenario.go +++ b/netsim/scenario.go @@ -79,6 +79,9 @@ func (s *Scenario) MustNewStack(config *StackConfig) *Stack { if config.DNSOverUDPHandler != nil { s.mustSetupDNSOverUDP(stack, config) } + if config.DNSOverTCPHandler != nil { + s.mustSetupDNSOverTCP(stack, config) + } // Start HTTP handlers. if config.HTTPHandler != nil { diff --git a/netsim/setup.go b/netsim/setup.go index 12c2eb6..7064a78 100644 --- a/netsim/setup.go +++ b/netsim/setup.go @@ -28,6 +28,9 @@ type StackConfig struct { // DNSOverUDPHandler optionally specifies a handler for DNS-over-UDP. DNSOverUDPHandler DNSHandler + // DNSOverTCPHandler optionally specifies a handler for DNS-over-TCP. + DNSOverTCPHandler DNSHandler + // DomainNames contains the optional domain names associated with this stack. // // If there are associated domain names, we will configure the DNS and @@ -107,6 +110,17 @@ func (s *Scenario) mustSetupDNSOverUDP(stack *Stack, cfg *StackConfig) { s.pool.Add(server) } +// mustSetupDNSOverTCP configures the DNS-over-TCP handler for the stack. +func (s *Scenario) mustSetupDNSOverTCP(stack *Stack, cfg *StackConfig) { + server := &dnscoretest.Server{ + Listen: func(network, address string) (net.Listener, error) { + return stack.Listen(context.Background(), network, "[::]:53") + }, + } + <-server.StartTCP(cfg.DNSOverTCPHandler) + s.pool.Add(server) +} + // mustSetupHTTPOverTCP configures the HTTP-over-TCP handler for the stack. func (s *Scenario) mustSetupHTTPOverTCP(stack *Stack, cfg *StackConfig) { listener := runtimex.Try1(stack.Listen(context.Background(), "tcp", "[::]:80")) diff --git a/netsim/wellknown.go b/netsim/wellknown.go index 2605464..a21a6a1 100644 --- a/netsim/wellknown.go +++ b/netsim/wellknown.go @@ -24,6 +24,7 @@ func (s *Scenario) MustNewGoogleDNSStack() *Stack { "8.8.8.8", }, DNSOverUDPHandler: s.DNSHandler(), + DNSOverTCPHandler: s.DNSHandler(), HTTPHandler: handler, HTTPSHandler: handler, })