feat(app): add internal components batch 7
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func writeDNSFixture(writer http.ResponseWriter, addresses ...string) {
|
||||
answers := make([]map[string]any, 0, len(addresses))
|
||||
for _, address := range addresses {
|
||||
answers = append(answers, map[string]any{"type": 1, "TTL": 37, "data": address})
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/dns-json")
|
||||
json.NewEncoder(writer).Encode(map[string]any{"Status": 0, "Answer": answers})
|
||||
}
|
||||
|
||||
func TestDNSResolverRetriesUnsafeOrInvalidAnswers(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
addresses []string
|
||||
body string
|
||||
status int
|
||||
}{
|
||||
{name: "fake-ip", addresses: []string{"198.18.0.42"}},
|
||||
{name: "private", addresses: []string{"172.18.0.2"}},
|
||||
{name: "loopback", addresses: []string{"127.0.0.1"}},
|
||||
{name: "metadata", addresses: []string{"169.254.169.254"}},
|
||||
{name: "carrier-network", addresses: []string{"100.64.0.1"}},
|
||||
{name: "reserved", addresses: []string{"203.0.113.1"}},
|
||||
{name: "mixed-private", addresses: []string{"93.184.216.35", "10.0.0.1"}},
|
||||
{name: "mixed-fake-ip", addresses: []string{"93.184.216.35", "198.19.0.1"}},
|
||||
{name: "invalid-address", addresses: []string{"93.184.216.35", "invalid address"}},
|
||||
{name: "ipv6-in-a-record", addresses: []string{"2606:4700:4700::1111"}},
|
||||
{name: "empty-answer"},
|
||||
{name: "nxdomain", body: `{"Status":3}`},
|
||||
{name: "malformed-json", body: `{"Status":`},
|
||||
{name: "http-error", addresses: []string{"93.184.216.35"}, status: http.StatusServiceUnavailable},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var firstCalls, secondCalls atomic.Int32
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
query := request.URL.Query()
|
||||
if query.Get("name") != "covers.example.org" || query.Get("type") != "A" || query.Get("edns_client_subnet") != dnsAlternateSubnet || request.Header.Get("Accept") != "application/dns-json" {
|
||||
t.Error("unexpected DNS request", request.URL)
|
||||
}
|
||||
if request.URL.Path == "/first" {
|
||||
firstCalls.Add(1)
|
||||
if test.status != 0 {
|
||||
writer.WriteHeader(test.status)
|
||||
}
|
||||
if test.body != "" {
|
||||
io.WriteString(writer, test.body)
|
||||
} else {
|
||||
writeDNSFixture(writer, test.addresses...)
|
||||
}
|
||||
return
|
||||
}
|
||||
secondCalls.Add(1)
|
||||
writeDNSFixture(writer, "93.184.216.34")
|
||||
}))
|
||||
defer server.Close()
|
||||
standard := http.DefaultTransport.(*http.Transport).Clone()
|
||||
standard.Proxy = nil
|
||||
resolver := newDNSResolver(standard)
|
||||
defer resolver.client.CloseIdleConnections()
|
||||
resolver.endpoints = []string{server.URL + "/first", server.URL + "/second"}
|
||||
for range 2 {
|
||||
entry, err := resolver.lookupEntry(context.Background(), "covers.example.org", dnsAlternateSubnet)
|
||||
if err != nil || !reflect.DeepEqual(entry.addresses, []string{"93.184.216.34"}) {
|
||||
t.Fatal("DNS failed to continue to a valid public answer", entry.addresses, err)
|
||||
}
|
||||
if remaining := time.Until(entry.expires); remaining <= 0 || remaining > 37*time.Second {
|
||||
t.Fatal("DNS cache ignored the answer TTL", remaining)
|
||||
}
|
||||
}
|
||||
if firstCalls.Load() != 1 || secondCalls.Load() != 1 {
|
||||
t.Fatal("DNS fallback did not reuse its successful cache", firstCalls.Load(), secondCalls.Load())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSResolverDoesNotCacheUnsafeAnswers(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
calls.Add(1)
|
||||
writeDNSFixture(writer, "93.184.216.34", "198.18.0.42")
|
||||
}))
|
||||
defer server.Close()
|
||||
standard := http.DefaultTransport.(*http.Transport).Clone()
|
||||
standard.Proxy = nil
|
||||
resolver := newDNSResolver(standard)
|
||||
defer resolver.client.CloseIdleConnections()
|
||||
resolver.endpoints = []string{server.URL + "/first", server.URL + "/second"}
|
||||
for range 2 {
|
||||
addresses, err := resolver.lookup(context.Background(), "covers.example.org")
|
||||
if err == nil || len(addresses) != 0 {
|
||||
t.Fatal("unsafe DNS answer was returned", addresses, err)
|
||||
}
|
||||
}
|
||||
if calls.Load() != 4 {
|
||||
t.Fatal("unsafe DNS answer was cached or prevented trying another service", calls.Load())
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
if _, err := resolver.lookup(ctx, "cancelled.example.org"); !errors.Is(err, context.Canceled) || calls.Load() != 4 {
|
||||
t.Fatal("cancelled DNS lookup did not stop", calls.Load(), err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user