Skip to content

fix: peer template review follow-ups - #30

Merged
floreabogdan merged 19 commits into
mainfrom
fix/peer-template-review
Oct 5, 2026
Merged

floreabogdan merged 19 commits into
mainfrom
fix/peer-template-review

Conversation

@floreabogdan

Copy link
Copy Markdown
Owner

Follow-ups from the review of #21 (peer templates). Each finding was checked against main first. The name clash was reproduced with real bird -p. Each fix has a test that was watched failing. One commit per fix:

Fix Commit
Template names could clash with any BIRD symbol. template bgp TRANSIT beside protocol bgp TRANSIT, a set, a community or a generated name failed every apply with "Symbol already defined". store.SymbolUses finds every owner of a name; template saves refuse a taken name, and peer saves refuse a template's. af861d8
A community only a template referenced could be deleted, leaving every later-attached peer with an undefined symbol. Templates now count as users. 0e3f540
A drained peer that became iBGP through a template kept its drain, so the iBGP filters set local-pref 0 and GRACEFUL_SHUTDOWN. updatePeerShape clears it for iBGP, like validateShape. 51baf44
Folded lint findings lost the peer names and counted findings, not peers. They now read IX_PEERS: rs3_v4, rs7_v4 and fold only across 2+ distinct peers. cec6003
The bulk bar defaulted to "Attach to ", and detach posted an empty value. There's now a required, disabled placeholder and an explicit detach; an empty choice is refused. 40370ad
Link and bulk attach read outside their transaction, and the bulk bar committed one transaction per peer and audited after the loop. store.AttachPeers does the whole selection in one transaction, reading inside it; link and detach are its one-peer cases. edca30b
iBGP template previews used AS64496, so every one showed a false "marked iBGP" danger. The sample name also exceeded 63 chars for long template names. bc78ff0
A failed rename re-rendered posting to the typed name, so the resubmit hit "not found". Fixed for templates and for the peer form, which had the same bug. 4054ed2
The policies list ignored template chains, showing "nothing" for a policy delete refuses. 93730b9
Every failed template delete said "detach those peers first". It's now a typed TemplateInUseError, and only that gets the hint. 904b757

Not changed, deliberately:

  • Peers already named templates/attach get no migration. That's vanishingly rare, and renaming would restart live BGP sessions.
  • Linked peers aren't rendered from the template row (review item 14). That's a design change to "the template is not consulted at render time".
  • Preview query counts (item 15) are left alone.
  • The peer form's own read-then-save of a linked peer, and import-from-BIRD's create-then-chain, keep their existing two-step shape.

Verification. Actions is billing-locked, so this was run locally with the ci.yml steps (go1.26.8, GOTOOLCHAIN=local): gofmt clean, vet (including -tags integration) ok, golangci-lint 0 issues, go test -race ./... ok, govulncheck with nothing reachable, the build ok, and both BIRD 2.14 integration tests passing.

🤖 Generated with Claude Code

floreabogdan and others added 19 commits October 1, 2026 10:23
BIRD keeps protocols, templates, defines, functions and filters in one
namespace. A template named like a peer (`template bgp TRANSIT` beside
`protocol bgp TRANSIT`), a prefix set, a community, an RPKI server or
anything birdy generates saved without complaint, then failed every
apply with "Symbol 'TRANSIT' already defined".

store.SymbolUses lists every owner of a name: the tables whose rows
render a symbol, the built-ins birdy renders itself, and the prefixes of
derived names (imp_, ebgp_in_, originate_, ...). A template save refuses
a name any of them holds, and a peer save refuses a template's name, so
the clash cannot be built from either side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The delete guard scanned peers and policies but not templates, so a
community named only by a template (say one with no peers linked yet)
could be deleted. Every peer attached to that template afterwards then
referenced an undefined symbol: danger findings on Changes, `bird -p`
failing the apply, and a template that would not save until someone
recreated the community. Templates now count as users.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
validateShape clears Drained on an iBGP peer, but a template save or a
link rewrites a peer's shape without the peer form running. A drained
eBGP peer attached to an iBGP template, or linked to a template whose
role became ibgp, stayed drained. With chains attached, its iBGP filters
then set local-pref 0 and tagged exports with GRACEFUL_SHUTDOWN, from a
switch the form hides on iBGP. updatePeerShape now clears it for iBGP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folding replaced the peer names with "IX_PEERS (2 peers)". That is fine
for a finding the template can fix, but not for one it cannot: two
drained sessions, or two link-local neighbors without an interface,
folded into a line that sent the operator to the template and left no
way to tell which sessions to fix. The count was also of findings, not
peers, so one peer tripping a check twice showed as "(2 peers)".

A folded line now reads "IX_PEERS: rs3_v4, rs7_v4" (the first five,
then "and N more"), and a group folds only across two or more distinct
peers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bulk select's first option, and so its default, was "Attach to
<first template>", and "Detach" posted an empty value. Selecting twenty
peers and pressing "Apply to selected" without touching the select, past
a generic confirm, linked all twenty to whichever template sorted first,
overwriting their chains, limits and safeguards.

The select now starts on a disabled "Choose an action" placeholder and
is required. Detach posts an explicit "detach". The handler refuses an
empty choice instead of reading it as detach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LinkPeerToTemplate read the template and the peer before opening its
transaction, so a template save committing in between (which rewrites
only the peers linked at its own commit) missed the newly linked peer.
That peer kept the old chain and limit while rendering session options
from the new template block, until the next template save. The bulk bar
also committed one transaction per peer and audited only after the loop,
so an error on peer 12 of 30 left 11 rewritten and none audited.

store.AttachPeers links (or, with template 0, detaches) a whole
selection in one transaction, reading the template and each peer
through it. LinkPeerToTemplate and DetachPeer are its one-peer cases,
and the bulk handler resolves names first, then makes one call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The template editor renders the shape on a sample neighbor, always
AS64496. For an iBGP template that is an eBGP session: the preview
showed `neighbor 192.0.2.1 as 64496` and a danger finding against the
template, "marked iBGP but its remote AS is 64496", on every iBGP
template. The sample now uses our own AS when the template is iBGP.

The sample was also named "<template>_example", which exceeds BIRD's
63-character limit for any template name of 56 characters or more, so
those templates showed "Fix the errors above" with nothing to fix. The
template part is now trimmed to fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a template rename failed validation (say IX_PEERS to IX_RS with a
bad community), the form re-rendered with the typed name and built its
action from it: /peers/templates/IX_RS/edit. The corrected resubmit went
to a template that did not exist, got "not found", and lost the edit.
The peer form had the same bug. Both now post to the name the record is
stored under while still showing what the operator typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeletePolicy refuses a policy a template's chain holds, but the list
counted only peer chains. A policy used only by a template with no
peers linked showed "Used by: nothing", and Delete then failed with
"attached to 1 peer template(s)" without the list ever naming it. The
list now shows template use beside peer use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed template delete always appended "Detach those peers first",
including for a database error or a template that had just been deleted
elsewhere, sending the operator to detach peers from a template with
none linked. DeletePeerTemplate now returns a TemplateInUseError for
linked peers, and only that gets the hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The save-time name check covered only the template and peer forms. A
prefix set, AS set, community, RPKI server or BMP station named like a
template, a session imported from BIRD under a template's name, or data
that already clashed, all still reached `bird -p` and failed with
"Symbol already defined".

The renderer is the one place that sees every symbol, so Sections now
scans what it rendered for declarations (define, function, filter,
template bgp, named protocols and tables, plus BIRD's own master4 and
master6) and refuses a duplicate with a message naming both
declarations. Preview, Changes and apply all show it before BIRD is
asked. Raw configuration is left to BIRD: it is free text, and a
commented-out block there is not a clash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SymbolUses refused any name starting with imp_, exp_, ebgp_in_,
originate_ and so on, though only an exact derived name clashes:
`exp_transit` was refused although no policy "transit" existed. A
derived name now counts as taken only while the policy, peer or prefix
set it derives from exists. BIRD's own master4/master6 tables join the
built-ins, which they were missing.

Library community validation kept its own shorter list of reserved
names and let BOGON_ASNS, rpki4 and the rest through. It now checks the
same built-in list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The save-time check ran only on the template and peer forms. A prefix
set, AS set, community, RPKI server or BMP station named like a template
saved, and so did a session imported from BIRD under a template's name;
each then declared the name twice. The renderer now catches that, but
the form is where it should be refused. refuseTemplateName runs on each
of those saves, on the peer form, and on import (which skips the
session).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its tx

Review of the transactional attach found two hazards. It opened a
deferred transaction and read before writing: under WAL, a commit from
another connection in between makes the first write fail with
SQLITE_BUSY_SNAPSHOT, which busy_timeout does not retry. It now opens
by touching the template's row, which takes the write lock (waiting its
turn) and proves the template exists, as the store's other transactions
already do by writing first. And chainFor read each export policy's set
IDs through the pool, not the transaction: a second connection held
while the first waited, which deadlocks once the pool is exhausted.
policySetIDs now takes the querier (TestAttachPeersReadsOnlyThroughItsTransaction
hangs without it).

LinkPeerToTemplate(peer, 0) silently detached, because 0 is AttachPeers'
detach; it is now ErrNotFound. DetachPeer had no callers left and is
gone. The bulk handler turns a peer or template deleted mid-request into
"nothing was changed" instead of a 500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
handleCommunityDelete went ahead with the delete whenever communityInUse
returned an error, so a transient failure in that check, which now also
reads templates, deleted a community that peers or templates still
referenced. A failed check now refuses the delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rename fix moved only the form's action to the stored name. "Save as
template" (?from=), and on a template the "peers linked to it" and "add
a new peer from it" links, still used the typed name, which matches no
peer or template until the rename saves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The policies list loaded every template with its chains (a query per
template, and one per chained export policy for its sets) only to count
how many templates chain each policy. One GROUP BY over template_policies
gives the same counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@floreabogdan

Copy link
Copy Markdown
Owner Author

Review follow-up (pushed):

  • Namespace check, at the right altitude. The renderer now refuses any model that declares one BIRD symbol twice, naming both declarations, plus BIRD's own master4/master6. That covers every creation path, including import, restores and data that already clashes. Prefix sets, AS sets, communities, RPKI servers, BMP stations and import-from-BIRD also refuse a template's name at save (6e6b97e, bfcd263).
  • Derived names clash only while their source exists. exp_transit is free unless a policy transit exists. Community validation uses the shared built-in list instead of its own shorter copy (147c897).
  • AttachPeers writes first. It touches the template row to take SQLite's write lock up front instead of read-then-upgrade, which risks SQLITE_BUSY_SNAPSHOT. It also reads set IDs through its transaction: policySetIDs takes the querier, and a pool-of-1 test hung without it. LinkPeerToTemplate(peer, 0) is ErrNotFound, not a detach. The unused DetachPeer is removed. A mid-request deletion in the bulk bar says "nothing was changed" instead of returning a 500 (b2835d5).
  • The community delete guard now refuses when its in-use check fails (8fef211).
  • All the form's name links use the stored name after a failed rename (da9cebc).
  • The policies list counts template use with one GROUP BY (96418a6).

Left as is:

  • Two saves of the same new name racing between check and insert. The render check now catches that before any apply.
  • The duplicate settings read in the live preview.

Re-verified locally: gofmt, vet (including -tags integration), golangci-lint, go test -race ./..., govulncheck, the build, and BIRD 2.14 integration all pass.

@floreabogdan
floreabogdan merged commit 9cdcc09 into main Oct 5, 2026
0 of 2 checks passed
@floreabogdan
floreabogdan deleted the fix/peer-template-review branch October 5, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant