Remove a peer's live routes when its session goes down (closes #56)
check / check (push) Waiting to run
check / check (push) Waiting to run
RIS sends no withdrawals for the routes of a peer whose BGP session goes down, so routewatch kept them. The streamer now hands STATE messages to the prefix handler. They are never dropped for backpressure: one that finds the queue full is held and queued first once there is room, and the handler's other messages are dropped meanwhile, so none overtakes it. Nothing waits. On a down, the prefix handler drops the peer's pending updates and its next flush deletes all the peer's live routes before the batch's other writes, keeping the in-memory counts exact. The live route tables are unique on peer and prefix, which also finds a peer's routes. Model: opus-5-5
This commit was merged in pull request #88.
This commit is contained in:
@@ -542,6 +542,91 @@ func (d *Database) DeleteLiveRouteBatch(deletions []LiveRouteDeletion) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deleting all live routes of a peer. The first query of each table selects the
|
||||
// mask length of each of the peer's routes whose prefix no other peer has a
|
||||
// live route for: the delete takes those prefixes out of the prefix
|
||||
// distribution.
|
||||
const (
|
||||
peerOnlyRouteMaskLengthsV4SQL = `SELECT r.mask_length FROM live_routes_v4 AS r WHERE r.peer_ip = ?
|
||||
AND NOT EXISTS (SELECT 1 FROM live_routes_v4 AS o WHERE o.prefix = r.prefix AND o.peer_ip != r.peer_ip)`
|
||||
deletePeerLiveRoutesV4SQL = `DELETE FROM live_routes_v4 WHERE peer_ip = ?`
|
||||
|
||||
peerOnlyRouteMaskLengthsV6SQL = `SELECT r.mask_length FROM live_routes_v6 AS r WHERE r.peer_ip = ?
|
||||
AND NOT EXISTS (SELECT 1 FROM live_routes_v6 AS o WHERE o.prefix = r.prefix AND o.peer_ip != r.peer_ip)`
|
||||
deletePeerLiveRoutesV6SQL = `DELETE FROM live_routes_v6 WHERE peer_ip = ?`
|
||||
)
|
||||
|
||||
// deletePeerRoutes deletes all live routes of peerIP from one table, given that
|
||||
// table's two statements above. It returns how many routes it deleted and the
|
||||
// mask length of each prefix it left with no live route.
|
||||
func deletePeerRoutes(tx *loggingTx, onlyRouteMaskLengthsSQL, deleteSQL, peerIP string) (
|
||||
deleted int64, goneMaskLengths []int, err error) {
|
||||
rows, err := tx.Query(onlyRouteMaskLengthsSQL, peerIP)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
for rows.Next() {
|
||||
var maskLength int
|
||||
if err := rows.Scan(&maskLength); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
goneMaskLengths = append(goneMaskLengths, maskLength)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
res, err := tx.Exec(deleteSQL, peerIP)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
deleted, err = res.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return deleted, goneMaskLengths, nil
|
||||
}
|
||||
|
||||
// DeleteLiveRoutesOfPeer deletes every live route of the peer, IPv4 and IPv6,
|
||||
// in one transaction, as when the peer's BGP session goes down. It returns how
|
||||
// many routes it deleted.
|
||||
func (d *Database) DeleteLiveRoutesOfPeer(peerIP string) (int, error) {
|
||||
d.lock("DeleteLiveRoutesOfPeer")
|
||||
defer d.unlock()
|
||||
|
||||
tx, err := d.beginTx()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
|
||||
d.logger.Error("Failed to rollback transaction", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
deletedV4, goneMaskLengthsV4, err := deletePeerRoutes(tx,
|
||||
peerOnlyRouteMaskLengthsV4SQL, deletePeerLiveRoutesV4SQL, peerIP)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to delete the IPv4 routes of peer %s: %w", peerIP, err)
|
||||
}
|
||||
deletedV6, goneMaskLengthsV6, err := deletePeerRoutes(tx,
|
||||
peerOnlyRouteMaskLengthsV6SQL, deletePeerLiveRoutesV6SQL, peerIP)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to delete the IPv6 routes of peer %s: %w", peerIP, err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return 0, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
d.counts.addRoutes(-int(deletedV4), -int(deletedV6))
|
||||
d.counts.addToDistribution(goneMaskLengthsV4, goneMaskLengthsV6, -1)
|
||||
|
||||
return int(deletedV4 + deletedV6), nil
|
||||
}
|
||||
|
||||
// UpdatePrefixesBatch updates the last_seen time for multiple prefixes in a single transaction
|
||||
func (d *Database) UpdatePrefixesBatch(prefixes map[string]time.Time) error {
|
||||
if len(prefixes) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user