watcher: an IPv6 address is written in brackets before its port (closes #270)
check / check (push) Canceled after 0s
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
This commit was merged in pull request #282.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user