package poke import ( "context" "crypto/ed25519" "crypto/rand" "encoding/pem" "errors" "io" "log" "strings" "sync" "testing" "gitea.dooplex.hu/admin/felhom-hub/internal/store" "golang.org/x/crypto/ssh" ) func quietLogger() *log.Logger { return log.New(io.Discard, "", 0) } type fakePeerStore struct { byHost map[string]*store.WGPeer all []store.WGPeer err error } func (f *fakePeerStore) GetWGPeerForHost(hostID string) (*store.WGPeer, error) { if f.err != nil { return nil, f.err } return f.byHost[hostID], nil } func (f *fakePeerStore) ListWGPeers() ([]store.WGPeer, error) { if f.err != nil { return nil, f.err } return f.all, nil } type fakeSender struct { mu sync.Mutex targets []string err error } func (f *fakeSender) Poke(_ context.Context, boxWGIP string) error { f.mu.Lock() f.targets = append(f.targets, boxWGIP) f.mu.Unlock() return f.err } func (f *fakeSender) got() []string { f.mu.Lock() defer f.mu.Unlock() return append([]string(nil), f.targets...) } // pokeHost resolves the host's WG /32 and sends the poke there — the address is looked up, never // caller-supplied. func TestPokeHost_SendsToResolvedWGIP(t *testing.T) { st := &fakePeerStore{byHost: map[string]*store.WGPeer{ "host-abc": {HostID: "host-abc", AssignedIP: "10.77.0.5"}, }} snd := &fakeSender{} n := NewNotifier(st, snd, quietLogger()) if err := n.pokeHost(context.Background(), "host-abc"); err != nil { t.Fatalf("pokeHost: %v", err) } got := snd.got() if len(got) != 1 || got[0] != "10.77.0.5" { t.Fatalf("sender targets = %v, want [10.77.0.5]", got) } } // A host with no WG peer is a no-op error the caller logs — never a send, never a save failure. func TestPokeHost_NoPeerNoSend(t *testing.T) { snd := &fakeSender{} n := NewNotifier(&fakePeerStore{byHost: map[string]*store.WGPeer{}}, snd, quietLogger()) if err := n.pokeHost(context.Background(), "unknown-host"); err == nil { t.Fatal("pokeHost for a peerless host returned nil, want an error") } if len(snd.got()) != 0 { t.Fatalf("sender was called for a peerless host: %v", snd.got()) } } // A store error propagates as an error (caller logs, never fails the save) and sends nothing. func TestPokeHost_StoreErrorNoSend(t *testing.T) { snd := &fakeSender{} n := NewNotifier(&fakePeerStore{err: errors.New("db down")}, snd, quietLogger()) if err := n.pokeHost(context.Background(), "host-abc"); err == nil { t.Fatal("pokeHost on a store error returned nil") } if len(snd.got()) != 0 { t.Fatalf("sender called despite store error: %v", snd.got()) } } // A nil *Notifier is a safe no-op (poke disabled) — the public entrypoints must not panic. func TestNilNotifierIsNoOp(t *testing.T) { var n *Notifier n.PokeHost("h") // must not panic n.PokeAllHosts() } // Client.Poke REFUSES any target outside the WG /24 BEFORE any network I/O — the confinement the // forced command and the WG kernel also enforce, checked here at the source. func TestClientPoke_RefusesNonWGTarget(t *testing.T) { _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("keygen: %v", err) } signer, err := ssh.NewSignerFromKey(priv) if err != nil { t.Fatalf("signer: %v", err) } pemBlock, err := ssh.MarshalPrivateKey(priv, "") if err != nil { t.Fatalf("marshal key: %v", err) } hostLine := string(ssh.MarshalAuthorizedKey(signer.PublicKey())) c, err := New(Config{ Addr: "198.51.100.1:22", // TEST-NET-2 — must never actually be dialed User: "felhom-peersync", PrivateKey: pem.EncodeToMemory(pemBlock), HostKeyLine: hostLine, }, quietLogger()) if err != nil { t.Fatalf("New: %v", err) } err = c.Poke(context.Background(), "8.8.8.8") // public IP — outside 10.77.0.0/24 if err == nil || !strings.Contains(err.Error(), "refusing non-WG target") { t.Fatalf("Poke to a public IP: err = %v, want a pre-dial 'refusing non-WG target' refusal", err) } }