Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions bfdd/bfd.c
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ void bfd_session_disable(struct bfd_session *bs)
bs->ifp = NULL;

/* Set session down so it doesn't report UP and disabled. */
ptm_bfd_sess_dn(bs, BD_PATH_DOWN);
ptm_bfd_sess_dn(bs, BD_PATH_DOWN, false);
}

static uint32_t ptm_bfd_gen_ID(void)
Expand Down Expand Up @@ -601,7 +601,17 @@ void ptm_bfd_sess_up(struct bfd_session *bfd)
}
}

void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag)
/*
* Transition BFD session to DOWN state.
*
* @param bfd BFD session
* @param diag Diagnostic code explaining reason for transition
* @param notify_admin_down If true, notify clients with ADMIN_DOWN status
* instead of DOWN. This is used when remote peer
* sends Admin Down - local state goes to DOWN but
* clients should not tear down protocol sessions.
*/
void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag, bool notify_admin_down)
{
int old_state = bfd->ses_state;

Expand All @@ -625,8 +635,17 @@ void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag)
bs_set_slow_timers(bfd);

/* only signal clients when going from up->down state */
if (old_state == PTM_BFD_UP)
ptm_bfd_notify(bfd, PTM_BFD_DOWN);
if (old_state == PTM_BFD_UP) {
/*
* If remote peer sent Admin Down, notify clients with
* ADMIN_DOWN status so they don't tear down protocol sessions.
* Otherwise, notify with DOWN status as usual.
*/
if (notify_admin_down)
ptm_bfd_notify(bfd, PTM_BFD_ADM_DOWN);
else
ptm_bfd_notify(bfd, PTM_BFD_DOWN);
}

/* Stop echo packet transmission if they are active */
if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE))
Expand Down Expand Up @@ -834,7 +853,7 @@ void bfd_recvtimer_cb(struct event *t)
switch (bs->ses_state) {
case PTM_BFD_INIT:
case PTM_BFD_UP:
ptm_bfd_sess_dn(bs, BD_CONTROL_EXPIRED);
ptm_bfd_sess_dn(bs, BD_CONTROL_EXPIRED, false);
break;
}
}
Expand All @@ -852,7 +871,7 @@ void bfd_echo_recvtimer_cb(struct event *t)
switch (bs->ses_state) {
case PTM_BFD_INIT:
case PTM_BFD_UP:
ptm_bfd_sess_dn(bs, BD_ECHO_FAILED);
ptm_bfd_sess_dn(bs, BD_ECHO_FAILED, false);
break;
}
}
Expand Down Expand Up @@ -1253,10 +1272,11 @@ static void bs_init_handler(struct bfd_session *bs, int nstate)
switch (nstate) {
case PTM_BFD_ADM_DOWN:
/*
* Remote peer doesn't want to talk, so lets make the
* connection down.
* Remote peer sent Admin Down.
* Go to DOWN state but notify clients with ADMIN_DOWN
* so they don't tear down their protocol sessions.
*/
ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN);
ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN, true);
break;

case PTM_BFD_DOWN:
Expand All @@ -1281,9 +1301,16 @@ static void bs_up_handler(struct bfd_session *bs, int nstate)
{
switch (nstate) {
case PTM_BFD_ADM_DOWN:
/*
* Remote peer sent Admin Down.
* Go to DOWN state but notify clients with ADMIN_DOWN
* so they don't tear down their protocol sessions.
*/
ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN, true);
break;
case PTM_BFD_DOWN:
/* Peer lost or asked to shutdown connection. */
ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN);
/* Peer lost connection - bring session down normally. */
ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN, false);
break;

case PTM_BFD_INIT:
Expand Down
2 changes: 1 addition & 1 deletion bfdd/bfd.h
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ int bfd_session_enable(struct bfd_session *bs);
void bfd_session_disable(struct bfd_session *bs);
struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc);
int ptm_bfd_sess_del(struct bfd_peer_cfg *bpc);
void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag);
void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag, bool notify_admin_down);
void ptm_bfd_sess_up(struct bfd_session *bfd);
void ptm_bfd_echo_stop(struct bfd_session *bfd);
void ptm_bfd_echo_start(struct bfd_session *bfd);
Expand Down
14 changes: 14 additions & 0 deletions bgpd/bgp_bfd.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ static void bfd_session_status_update(struct bfd_session_params *bsp,
bfd_get_status_str(bss->previous_state),
bfd_get_status_str(bss->state));

/*
* Handle Admin Down from peer separately.
* When BFD receives Admin Down from peer, we should NOT tear down
* the BGP session. The peer is administratively shutting down BFD,
* but the BGP session should remain up.
*/
if (bss->state == BSS_ADMIN_DOWN && bss->previous_state == BSS_UP) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug("%s: BFD received Admin Down from peer %s - BGP session maintained",
__func__, peer->host);
/* Don't tear down BGP session, just log the event */
return;
}

if (bss->state == BSS_DOWN && bss->previous_state == BSS_UP) {
if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE)
&& bfd_sess_cbit(bsp) && !bss->remote_cbit) {
Expand Down
13 changes: 13 additions & 0 deletions ospf6d/ospf6_bfd.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ static void ospf6_bfd_callback(struct bfd_session_params *bsp,
{
struct ospf6_neighbor *on = arg;

/*
* Handle Admin Down from peer separately.
* When BFD receives Admin Down from peer, we should NOT tear down
* the OSPFv6 neighbor. The peer is administratively shutting down BFD,
* but the OSPFv6 adjacency should remain up.
*/
if (bss->state == BSS_ADMIN_DOWN && bss->previous_state == BSS_UP) {
/* Don't tear down OSPFv6 neighbor, just log the event */
zlog_info("OSPFv6 BFD: Neighbor %pI6: Received Admin Down from peer - adjacency maintained",
&on->linklocal_addr);
return;
}

if (bss->state == BFD_STATUS_DOWN
&& bss->previous_state == BFD_STATUS_UP) {
event_cancel(&on->inactivity_timer);
Expand Down
14 changes: 14 additions & 0 deletions ospfd/ospf_bfd.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ static void ospf_bfd_session_change(struct bfd_session_params *bsp,
{
struct ospf_neighbor *nbr = arg;

/*
* Handle Admin Down from peer separately.
* When BFD receives Admin Down from peer, we should NOT tear down
* the OSPF neighbor. The peer is administratively shutting down BFD,
* but the OSPF adjacency should remain up.
*/
if (bss->state == BSS_ADMIN_DOWN && bss->previous_state == BSS_UP) {
if (IS_DEBUG_OSPF(bfd, BFD_LIB))
zlog_debug("%s: NSM[%s:%pI4]: BFD received Admin Down from peer - OSPF adjacency maintained",
__func__, IF_NAME(nbr->oi), &nbr->address.u.prefix4);
/* Don't tear down OSPF neighbor, just log the event */
return;
}

/* BFD peer went down. */
if (bss->state == BFD_STATUS_DOWN
&& bss->previous_state == BFD_STATUS_UP) {
Expand Down
14 changes: 14 additions & 0 deletions pimd/pim_bfd.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ static void pim_neighbor_bfd_cb(struct bfd_session_params *bsp,
bfd_get_status_str(bss->previous_state));
}

/*
* Handle Admin Down from peer separately.
* When BFD receives Admin Down from peer, we should NOT tear down
* the PIM neighbor. The peer is administratively shutting down BFD,
* but the PIM adjacency should remain up.
*/
if (bss->state == BSS_ADMIN_DOWN && bss->previous_state == BSS_UP) {
if (PIM_DEBUG_PIM_TRACE)
zlog_debug("%s: BFD received Admin Down from peer - PIM neighbor maintained",
__func__);
/* Don't delete PIM neighbor, just log the event */
return;
}

if (bss->state == BFD_STATUS_DOWN
&& bss->previous_state == BFD_STATUS_UP)
pim_neighbor_delete(nbr->interface, nbr, "BFD Session Expired");
Expand Down
75 changes: 75 additions & 0 deletions tests/topotests/bfd_admin_down_no_impact/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# BFD Admin Down No Impact Test

## Purpose

This test verifies that when BFD receives an Admin Down message from a peer,
it does NOT tear down protocol sessions (BGP, OSPF, PIM). This is the expected
behavior because Admin Down indicates an administrative action (e.g., manual
shutdown of BFD), not an actual connectivity failure.

## Test Scenario

1. Two routers (r1 and r2) are connected via a single link
2. BGP (eBGP), OSPF, and PIM are configured on both routers
3. BFD is enabled for BGP and OSPF neighbors
4. All protocols converge and BFD sessions come UP
5. r1 administratively shuts down its BFD peer (sending Admin Down to r2)
6. r2 receives the Admin Down notification
7. **Expected behavior**: r2's BFD state goes to DOWN, but BGP/OSPF/PIM sessions remain UP
8. r1 re-enables BFD and the session comes back up

## Topology

```
r1 ----------- r2
.1 s1 .2
10.0.1.0/24

r1: AS 65001, Router-ID 1.1.1.1, Loopback 1.1.1.1/32
r2: AS 65002, Router-ID 2.2.2.2, Loopback 2.2.2.2/32
```

## Test Coverage

### Direct Peer Shutdown Test
- **BGP with BFD**: Verifies BGP session remains Established after BFD Admin Down
- **OSPF with BFD**: Verifies OSPF neighbor remains in Full state after BFD Admin Down
- **PIM with BFD**: Verifies PIM neighbor remains UP after BFD Admin Down
- **BFD state transition**: Verifies BFD correctly moves to DOWN state on Admin Down
- **BFD re-enable**: Verifies BFD can be re-enabled and session comes back UP

### Profile-Based Shutdown Test
- **BFD Profile Configuration**: Creates and applies BFD profiles to peers
- **Profile Shutdown**: Shuts down BFD profile (affects all peers using that profile)
- **Protocol Stability**: Verifies BGP/OSPF/PIM remain UP when profile is shut down
- **Profile Re-enable**: Verifies BFD recovers when profile is re-enabled

## Implementation Details

The test validates the changes made to handle BFD Admin Down:

1. **bfdd/bfd.c**: Added `notify_admin_down` parameter to `ptm_bfd_sess_dn()`
2. **bfdd/bfd.c**: When receiving Admin Down from peer, BFD notifies clients with `PTM_BFD_ADM_DOWN` status instead of `PTM_BFD_DOWN`
3. **bgpd/bgp_bfd.c**: BGP client checks for `BSS_ADMIN_DOWN` and doesn't tear down session
4. **ospfd/ospf_bfd.c**: OSPF client checks for `BSS_ADMIN_DOWN` and doesn't tear down adjacency
5. **ospf6d/ospf6_bfd.c**: OSPFv6 client checks for `BSS_ADMIN_DOWN` and doesn't tear down adjacency
6. **pimd/pim_bfd.c**: PIM client checks for `BSS_ADMIN_DOWN` and doesn't delete neighbor

## Running the Test

```bash
cd tests/topotests/bfd_admin_down_no_impact
sudo -E pytest test_bfd_admin_down_no_impact.py -s -vv
```

## Expected Output

All tests should pass:
- `test_wait_protocols_convergence`: Verifies initial convergence
- `test_bfd_peers_up`: Verifies BFD sessions come UP
- `test_bfd_admin_down_no_protocol_impact`: **Main test** - verifies protocols remain UP after direct peer shutdown
- `test_bfd_reenable`: Verifies BFD can be re-enabled after peer shutdown
- `test_bfd_profile_shutdown_no_protocol_impact`: **Profile test** - verifies protocols remain UP after profile shutdown
- `test_memory_leak`: Checks for memory leaks


Loading
Loading