client/local, ipn/localapi, ipn/ipnlocal: add PeerByID

Add a narrow LocalAPI accessor and matching client/LocalBackend method
to look up a single peer's current full [tailcfg.Node] by NodeID, in
O(1) time on the daemon side, without fetching the entire netmap.

Useful for callers that need the latest state of a single peer (e.g.
in response to a peer-mutation event on the IPN bus) without paying
for a full netmap fetch.

Updates #12542

Change-Id: I1cb2d350e6ad846a5dabc1f5368dfc8121387f7c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-05-01 04:03:55 +00:00
committed by Brad Fitzpatrick
parent cac94f51cc
commit 89a78dc9b7
4 changed files with 130 additions and 0 deletions
+66
View File
@@ -202,6 +202,72 @@ func TestWhoIsArgTypes(t *testing.T) {
}
}
type fakePeerByIDBackend map[tailcfg.NodeID]*tailcfg.Node
func (f fakePeerByIDBackend) PeerByID(id tailcfg.NodeID) (tailcfg.NodeView, bool) {
n, ok := f[id]
if !ok {
return tailcfg.NodeView{}, false
}
return n.View(), true
}
func TestServePeerByID(t *testing.T) {
h := handlerForTest(t, &Handler{PermitRead: true})
b := fakePeerByIDBackend{
42: {
ID: 42,
Name: "alpha",
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.0.42/32"),
},
},
}
tests := []struct {
name string
query string
wantCode int
wantNodeID tailcfg.NodeID
}{
{"hit", "id=42", 200, 42},
{"miss", "id=99", 404, 0},
{"bad_id", "id=garbage", 400, 0},
{"missing_id", "", 400, 0},
{"zero_id", "id=0", 400, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rec := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/v0/peer-by-id?"+tt.query, nil)
h.servePeerByIDWithBackend(rec, req, b)
if rec.Code != tt.wantCode {
t.Fatalf("status = %d, want %d; body=%q", rec.Code, tt.wantCode, rec.Body.String())
}
if tt.wantCode != 200 {
return
}
var got tailcfg.Node
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
t.Fatalf("unmarshal body %q: %v", rec.Body.Bytes(), err)
}
if got.ID != tt.wantNodeID {
t.Errorf("Node.ID = %d, want %d", got.ID, tt.wantNodeID)
}
})
}
t.Run("forbidden", func(t *testing.T) {
hh := handlerForTest(t, &Handler{PermitRead: false})
rec := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/v0/peer-by-id?id=42", nil)
hh.servePeerByIDWithBackend(rec, req, b)
if rec.Code != http.StatusForbidden {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusForbidden)
}
})
}
func TestShouldDenyServeConfigForGOOSAndUserContext(t *testing.T) {
newHandler := func(connIsLocalAdmin bool) *Handler {
return handlerForTest(t, &Handler{