check / check (push) Canceled after 0s
Port and certificate keys were joined as address:port, so an IPv6 address ran into its port: 2a01:4f8:2200:fd11::1:80 is also an address on its own. Keys are now joined with net.JoinHostPort and split with net.SplitHostPort, giving [2a01:4f8:2200:fd11::1]:80 in the state file, /api/v1/status, the dashboard and Port Change notifications; an IPv4 address is unchanged. A certificate key still splits at its last colon, since a hostname has none. Tests that built IPv6 keys by hand now use the bracketed form, and a test checks that an IPv6 address's certificate entry is removed when its port 443 closes. Model: opus-5-5
84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
package watcher_test
|
|
|
|
import (
|
|
"maps"
|
|
"slices"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/dnswatcher/internal/state"
|
|
"sneak.berlin/go/dnswatcher/internal/watcher"
|
|
)
|
|
|
|
// TestIPv6AddressInBrackets runs the port checks, not the watcher's
|
|
// first, on hostname state built here for a hostname at an IPv6 address
|
|
// whose port 443 the previous check found closed. It is open now. Then
|
|
// it runs the TLS checks and the port checks again. The port and
|
|
// certificate keys and the Port Change notification write the address
|
|
// in brackets, so its last group cannot be read as the port, and the
|
|
// second port checks keep the certificate entry, whose address they read
|
|
// back from its key. When a last port check finds port 443 closed, the
|
|
// certificate entry is removed. Nothing is looked up.
|
|
func TestIPv6AddressInBrackets(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const aaaaType = "AAAA"
|
|
|
|
cfg := defaultTestConfig(t)
|
|
cfg.Hostnames = []string{host}
|
|
|
|
deps := newTestDeps(t, cfg)
|
|
w := watcher.NewForTest(
|
|
cfg, deps.state, nil,
|
|
deps.portChecker, deps.tlsChecker, deps.notifier,
|
|
)
|
|
w.SetFirstRun(false)
|
|
|
|
deps.state.SetHostnameState(host, saved(
|
|
map[string]*state.NameserverRecordState{
|
|
nsA: answered(map[string][]string{aaaaType: {"2001:db8::1"}}),
|
|
},
|
|
))
|
|
deps.state.SetPortState("[2001:db8::1]:443", &state.PortState{
|
|
Hostnames: []string{host},
|
|
})
|
|
|
|
w.CheckAllPorts(t.Context())
|
|
w.RunTLSChecks(t.Context())
|
|
w.CheckAllPorts(t.Context())
|
|
|
|
snap := deps.state.GetSnapshot()
|
|
|
|
gotPorts := slices.Sorted(maps.Keys(snap.Ports))
|
|
wantPorts := []string{"[2001:db8::1]:443", "[2001:db8::1]:80"}
|
|
|
|
if !slices.Equal(gotPorts, wantPorts) {
|
|
t.Errorf("port entries %v, want %v", gotPorts, wantPorts)
|
|
}
|
|
|
|
gotCerts := slices.Sorted(maps.Keys(snap.Certificates))
|
|
wantCerts := []string{"[2001:db8::1]:443:www.example.net"}
|
|
|
|
if !slices.Equal(gotCerts, wantCerts) {
|
|
t.Errorf("certificate entries %v, want %v", gotCerts, wantCerts)
|
|
}
|
|
|
|
title := "Port Change: [2001:db8::1]:443"
|
|
want := `Hostnames: www.example.net
|
|
Address: [2001:db8::1]:443
|
|
Port now open`
|
|
|
|
got := deps.notifier.getNotifications()
|
|
if len(got) != 1 || got[0].Title != title || got[0].Message != want {
|
|
t.Errorf("sent %v, want one %q with message:\n%s", got, title, want)
|
|
}
|
|
|
|
deps.portChecker.closed = true
|
|
|
|
w.CheckAllPorts(t.Context())
|
|
|
|
gotCerts = slices.Sorted(maps.Keys(deps.state.GetSnapshot().Certificates))
|
|
if len(gotCerts) != 0 {
|
|
t.Errorf("certificate entries %v with port 443 closed, want none", gotCerts)
|
|
}
|
|
}
|