DEV Community

pathvector-dev
pathvector-dev

Posted on • Originally published at blog.pathvector.dev

BGP UPDATE in Action: Watch a Route Appear, Disappear, and Come Back

Originally published at https://blog.pathvector.dev/protocol-lab-bgp-02/ — part of the free Protocol Lab series.

This post is part of Protocol Lab, a free, hands-on series for learning networking protocols by building and breaking them in a container lab. All the lab material — topologies, configs, and scripts — lives in the repo: github.com/pathvector-studio/protocol-lab.

In this lab you will watch a single BGP route appear on r2, disappear after a withdrawal, and come back after it is announced again. The theme is deliberately simple: an UPDATE message can announce reachability, and an UPDATE message can withdraw reachability. Lab 01 showed you one route sitting in a BGP table; this lab revisits that same route as a pair of movements — advertisement and withdrawal — carried by UPDATE messages.

Reading guide: rfc-notes/bgp-rfc4271-lab02.md

Expected time: 45–60 minutes.

The Goal

By the end, you should be able to explain this sequence:

203.0.113.0/24 is visible on r2
203.0.113.0/24 is withdrawn by r1
203.0.113.0/24 disappears from r2
203.0.113.0/24 is announced again by r1
203.0.113.0/24 returns on r2
Enter fullscreen mode Exit fullscreen mode

What You Will Learn

  • A BGP UPDATE message is used for both route announcements and route withdrawals.
  • In an announcement, the prefix goes into the NLRI, accompanied by path attributes such as ORIGIN, AS_PATH, and NEXT_HOP.
  • In a withdrawal, the prefix being withdrawn goes into Withdrawn Routes.
  • A withdrawal does not need to restate the route's AS_PATH or NEXT_HOP.
  • You can confirm a route vanishing from the BGP table in both FRRouting output and a pcap.

This lab does not cover:

  • competing origins
  • route leaks
  • RPKI / ROA / ROV
  • the BGP decision process
  • iBGP
  • route reflectors
  • public Internet announcements

Where to Read in the RFCs

The required reading for this lab is RFC 4271:

Section What to focus on
3.1 Routes are advertised — and withdrawn — via UPDATE
4.3 The UPDATE message's Withdrawn Routes, Path Attributes, and NLRI
5 Mandatory attributes when an UPDATE carries NLRI
5.1.1 ORIGIN
5.1.2 AS_PATH
5.1.3 NEXT_HOP

The Big Picture

We reuse the same two-router setup as Lab 01.

AS65001 / r1                         AS65002 / r2
10.0.0.1/30  ----------------------  10.0.0.2/30

Step 1:
  r1 announces 203.0.113.0/24
  r2 learns 203.0.113.0/24

Step 2:
  r1 withdraws 203.0.113.0/24
  r2 removes 203.0.113.0/24

Step 3:
  r1 announces 203.0.113.0/24 again
  r2 learns 203.0.113.0/24 again
Enter fullscreen mode Exit fullscreen mode

203.0.113.0/24 is an RFC 5737 documentation prefix. It is never advertised externally — it only exists inside the lab.

sequenceDiagram
  participant r1 as r1<br/>AS65001
  participant r2 as r2<br/>AS65002
  r1->>r2: UPDATE announce<br/>NLRI 203.0.113.0/24<br/>NEXT_HOP 10.0.0.1<br/>AS_PATH 65001
  Note over r2: route is visible
  r1->>r2: UPDATE withdraw<br/>Withdrawn Routes 203.0.113.0/24
  Note over r2: route disappears
  r1->>r2: UPDATE announce again<br/>NLRI 203.0.113.0/24
  Note over r2: route returns
Enter fullscreen mode Exit fullscreen mode
flowchart LR
  announce["Announcement UPDATE<br/>Path Attributes<br/>ORIGIN / AS_PATH / NEXT_HOP<br/>NLRI 203.0.113.0/24"]
  withdraw["Withdrawal UPDATE<br/>Withdrawn Routes<br/>203.0.113.0/24<br/>no NLRI needed for this withdrawal"]
  table["r2 BGP table<br/>route appears<br/>route disappears<br/>route returns"]

  announce --> table
  withdraw --> table
Enter fullscreen mode Exit fullscreen mode

Note: This lab uses a documentation prefix (RFC 5737) inside a closed Docker environment, so nothing here touches the real internet.

What You Need

Recommended environment:

  • Linux / WSL2 / a Linux VM
  • Docker
  • containerlab
  • tcpdump on the host
  • Wireshark or tshark

Images used:

  • frrouting/frr:latest

On macOS, run this inside a Linux VM, a WSL-equivalent environment, or a Linux VM on OrbStack/Colima. Capturing BGP packets inside Linux network namespaces is much easier to work with for learning purposes.

Running the Lab

All of these steps run inside the Linux environment where containerlab is installed.

If you have the repo checked out, the quick path runs the verification script, which handles topology deploy, confirming the route appears, the withdraw, confirming it disappears, the re-announcement, the pcap capture, and the destroy:

./scripts/labctl.sh run bgp-02
Enter fullscreen mode Exit fullscreen mode

Or step through it manually:

1. Move into the working directory

cd protocol-lab/examples/bgp-02
Enter fullscreen mode Exit fullscreen mode

2. Deploy

sudo containerlab deploy -t bgp-02.clab.yml
Enter fullscreen mode Exit fullscreen mode

After it comes up, confirm the containers were created:

docker ps --format "table {{.Names}}\t{{.Status}}"
Enter fullscreen mode Exit fullscreen mode

What to check:

  • clab-bgp-02-r1 is running.
  • clab-bgp-02-r2 is running.

Give the neighbors a few seconds to reach Established, then look at the BGP summary:

docker exec -it clab-bgp-02-r2 vtysh -c "show bgp summary"
Enter fullscreen mode Exit fullscreen mode

What to observe:

  • Local AS is 65002.
  • The neighbor is 10.0.0.1.
  • The neighbor's AS is 65001.
  • State/PfxRcd is 1, or the output otherwise shows an Established session receiving one prefix.

3. Look at the route while it exists

docker exec -it clab-bgp-02-r2 vtysh -c "show bgp ipv4 unicast"
Enter fullscreen mode Exit fullscreen mode

Expected reading:

Network          Next Hop        Path
*> 203.0.113.0/24 10.0.0.1       65001 i
Enter fullscreen mode Exit fullscreen mode

What to look for:

  • 203.0.113.0/24 is in the BGP table.
  • NEXT_HOP is 10.0.0.1.
  • AS_PATH is 65001.
  • ORIGIN is i (shown as IGP in detailed output).

4. Start a packet capture

Open a second terminal — still inside the Linux environment running containerlab — and capture on r2's interface.

sudo ip netns list | grep clab-bgp-02
Enter fullscreen mode Exit fullscreen mode

What to check:

  • clab-bgp-02-r1 is listed.
  • clab-bgp-02-r2 is listed.

Start the capture:

sudo ip netns exec clab-bgp-02-r2 tcpdump -i eth1 -nn -s 0 -w bgp-02-r2.pcap tcp port 179
Enter fullscreen mode Exit fullscreen mode

5. Withdraw the route

With the capture still running, remove the network statement from r1 in another terminal:

docker exec -it clab-bgp-02-r1 vtysh \
  -c "configure terminal" \
  -c "router bgp 65001" \
  -c "address-family ipv4 unicast" \
  -c "no network 203.0.113.0/24"
Enter fullscreen mode Exit fullscreen mode

Wait a few seconds, then look at r2:

docker exec -it clab-bgp-02-r2 vtysh -c "show bgp ipv4 unicast"
Enter fullscreen mode Exit fullscreen mode

What to check:

  • 203.0.113.0/24 is no longer displayed.
  • The neighbor may stay Established — what disappeared was the route, not the session.

6. Re-announce the route

On the same r1, put the network statement back:

docker exec -it clab-bgp-02-r1 vtysh \
  -c "configure terminal" \
  -c "router bgp 65001" \
  -c "address-family ipv4 unicast" \
  -c "network 203.0.113.0/24"
Enter fullscreen mode Exit fullscreen mode

Wait a few seconds, then check r2 again:

docker exec -it clab-bgp-02-r2 vtysh -c "show bgp ipv4 unicast"
Enter fullscreen mode Exit fullscreen mode

What to check:

  • 203.0.113.0/24 is back.
  • NEXT_HOP is 10.0.0.1.
  • AS_PATH is 65001.
  • ORIGIN is i / IGP.

Stop tcpdump with Ctrl-C. The file bgp-02-r2.pcap remains in your current working directory.

7. Inspect the UPDATEs in the pcap

Open bgp-02-r2.pcap in Wireshark.

Because the capture was taken on r2's interface, you may see UPDATEs in both directions (r1 -> r2 and r2 -> r1). Focus first on the UPDATEs from 10.0.0.1 -> 10.0.0.2.

Where to look:

  • announce UPDATE
    • Path attributes
    • ORIGIN
    • AS_PATH: 65001
    • NEXT_HOP: 10.0.0.1
    • NLRI: 203.0.113.0/24
  • withdraw UPDATE
    • Withdrawn Routes
    • 203.0.113.0/24

Key points:

  • For the announcement, look at Path Attributes + NLRI.
  • For the withdrawal, look at Withdrawn Routes.
  • The withdrawal is not a message that restates NEXT_HOP or AS_PATH.

If you want to open the pcap in Wireshark on macOS, move the generated bgp-02-r2.pcap to the Mac via a shared directory or scp first.

Expected Output

The point here is confirming the route's state changes, not matching output byte-for-byte.

Before the withdraw

On r2:

Network          Next Hop        Path
*> 203.0.113.0/24 10.0.0.1       65001 i
Enter fullscreen mode Exit fullscreen mode

What to look for:

  • 203.0.113.0/24 is in the BGP table.
  • NEXT_HOP is 10.0.0.1.
  • AS_PATH is 65001.

After the withdraw

On r2:

Displayed 0 routes and 0 total paths
Enter fullscreen mode Exit fullscreen mode

The exact wording varies a little between FRRouting versions. What matters is that 203.0.113.0/24 is no longer displayed.

After the re-announcement

On r2:

Network          Next Hop        Path
*> 203.0.113.0/24 10.0.0.1       65001 i
Enter fullscreen mode Exit fullscreen mode

What to look for:

  • 203.0.113.0/24 is back.
  • NEXT_HOP and AS_PATH match the original announcement.

Why It Works

The UPDATE message defined in RFC 4271 Section 4.3 has three key fields: Withdrawn Routes, Path Attributes, and Network Layer Reachability Information (NLRI).

While r1 has network 203.0.113.0/24 configured, it advertises 203.0.113.0/24 as NLRI. That UPDATE carries path attributes such as ORIGIN, AS_PATH, and NEXT_HOP.

When you run no network 203.0.113.0/24 on r1, it withdraws the reachability it previously advertised. In the withdrawal UPDATE, the prefix being withdrawn goes into Withdrawn Routes. r2 receives that UPDATE and removes the matching route from its BGP table.

Configure network 203.0.113.0/24 again, and r1 re-advertises the same prefix. r2 receives the new announcement and puts the route back in its BGP table.

Common Misconceptions

  • A withdraw does not tear down the BGP session. The neighbor can stay Established while only the route disappears.
  • A withdraw is not "sending an unreachable next hop." It lists the withdrawn prefix in Withdrawn Routes.
  • no network 203.0.113.0/24 does not delete the loopback's IP address. It removes the configuration that originates the prefix into BGP.
  • When 203.0.113.0/24 vanishes from the BGP table, nothing was advertised to — or withdrawn from — the real internet. This lab runs entirely inside a closed Docker environment.

Common Pitfalls

The route is still there after the withdraw

Wait a few seconds and check again:

docker exec -it clab-bgp-02-r2 vtysh -c "show bgp ipv4 unicast"
Enter fullscreen mode Exit fullscreen mode

If it's still there, check r1's running config:

docker exec -it clab-bgp-02-r1 vtysh -c "show running-config"
Enter fullscreen mode Exit fullscreen mode

If network 203.0.113.0/24 is still present under address-family ipv4 unicast, the withdraw never actually happened.

The route doesn't come back after re-announcing

Check whether r1 actually holds 203.0.113.0/24 locally:

docker exec -it clab-bgp-02-r1 ip addr show lo
Enter fullscreen mode Exit fullscreen mode

FRRouting's network 203.0.113.0/24 only advertises the prefix when a matching prefix exists in the routing table. In this topology, that condition is satisfied by putting 203.0.113.1/24 on r1's loopback.

The withdraw doesn't show up in the pcap

If you withdraw before starting the capture, you miss the withdrawal UPDATE. Start tcpdump first, and only then run no network 203.0.113.0/24.

Also double-check which interface tcpdump is capturing on:

sudo ip netns exec clab-bgp-02-r2 ip link
Enter fullscreen mode Exit fullscreen mode

Check Your Understanding

  1. In the announcement UPDATE, which part carries 203.0.113.0/24?
  2. In the withdrawal UPDATE, which part carries 203.0.113.0/24?
  3. Explain, in your own words, why AS_PATH and NEXT_HOP are not required in a withdrawal.
  4. Explain why the BGP neighbor can stay Established after the withdraw.
  5. Compare no network 203.0.113.0/24 with ip addr del 203.0.113.1/24 dev lo: what looks similar in the observed results, and what differs?

Cleanup

When you're done, destroy the containerlab topology:

sudo containerlab destroy -t bgp-02.clab.yml
Enter fullscreen mode Exit fullscreen mode

Confirm no containers are left behind:

docker ps --format "table {{.Names}}\t{{.Status}}" | grep clab-bgp-02 || true
Enter fullscreen mode Exit fullscreen mode

If you want to keep the pcap, move it into assets/bgp-02/:

mkdir -p ../../assets/bgp-02
cp bgp-02-r2.pcap ../../assets/bgp-02/
Enter fullscreen mode Exit fullscreen mode

Otherwise, delete it:

rm -f bgp-02-r2.pcap
Enter fullscreen mode Exit fullscreen mode

What to Read Next

The next lab constructs a state where the same prefix is visible from different origin ASes — the entry point to competing origins and route leaks.

Read next:

  • RFC 4271 Section 5.1.2, AS_PATH
  • RFC 4271 Section 9, UPDATE Message Handling
  • As a doorway into RPKI / ROA: the question of which AS is allowed to originate a prefix

References

Verified Run Log (2026-05-08, from assets/bgp-02/runs/20260508T232252Z/run.log)

This lab has been confirmed reproducible on real hardware.

Verification results:

The run.log doesn't include raw show bgp summary output, so what follows quotes the BGP table observations and the verification script's state-transition log from that same log. The whole point of this lab is confirming that the route alone was withdrawn and re-advertised over an Established session — the BGP session itself never went down.

Verification summary

[protocol-lab][bgp-02] waiting for BGP route on r2: before withdraw
[protocol-lab][bgp-02] BGP route present after 3s: before withdraw
...
[protocol-lab][bgp-02] withdrawing 203.0.113.0/24 from r1
[protocol-lab][bgp-02] waiting for BGP route withdrawal on r2: after withdraw
[protocol-lab][bgp-02] BGP route absent after 2s: after withdraw
...
[protocol-lab][bgp-02] announcing 203.0.113.0/24 from r1
[protocol-lab][bgp-02] waiting for BGP route on r2: after reannounce
[protocol-lab][bgp-02] BGP route present after 1s: after reannounce
...
[protocol-lab][bgp-02] verification OK
Enter fullscreen mode Exit fullscreen mode

This flow maps directly onto RFC 4271 Section 3.1's statement that routes are advertised — and withdrawn — via UPDATE.

Before the withdraw: r2 holds the route

BGP table version is 1, local router ID is 2.2.2.2, vrf id 0
Default local pref 100, local AS 65002
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*> 203.0.113.0/24   10.0.0.1                 0             0 65001 i

Displayed  1 routes and 1 total paths
Enter fullscreen mode Exit fullscreen mode

How to read it:

  • Network 203.0.113.0/24 is the prefix advertised as the UPDATE's NLRI.
  • Next Hop 10.0.0.1 is the NEXT_HOP from RFC 4271 Section 5.1.3.
  • Path 65001 is the AS_PATH from RFC 4271 Section 5.1.2.
  • The trailing i is ORIGIN = IGP from RFC 4271 Section 5.1.1.

After the withdraw: the route vanishes from r2

[protocol-lab][bgp-02] withdrawing 203.0.113.0/24 from r1
[protocol-lab][bgp-02] waiting for BGP route withdrawal on r2: after withdraw
[protocol-lab][bgp-02] BGP route absent after 2s: after withdraw
No BGP prefixes displayed, 0 exist
Enter fullscreen mode Exit fullscreen mode

In the withdrawal UPDATE, the prefix being withdrawn goes into the Withdrawn Routes field of RFC 4271 Section 4.3. Here 203.0.113.0/24 was the withdrawal target, and the matching route disappeared from r2's BGP table.

Crucially, a withdraw is not a message that re-advertises AS_PATH or NEXT_HOP as some "unreachable" value. Listing the prefix in Withdrawn Routes is what invalidates the previously advertised reachability.

After the re-advertise: the route returns on r2

[protocol-lab][bgp-02] announcing 203.0.113.0/24 from r1
[protocol-lab][bgp-02] waiting for BGP route on r2: after reannounce
[protocol-lab][bgp-02] BGP route present after 1s: after reannounce
BGP table version is 3, local router ID is 2.2.2.2, vrf id 0
Default local pref 100, local AS 65002
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*> 203.0.113.0/24   10.0.0.1                 0             0 65001 i

Displayed  1 routes and 1 total paths
Enter fullscreen mode Exit fullscreen mode

On re-advertise, 203.0.113.0/24 is announced as NLRI once more, arriving at r2 together with its path attributes — ORIGIN, AS_PATH, NEXT_HOP. The same route lands back in r2's BGP table.

Mapping observations to RFC 4271 terminology

Observation RFC 4271 term What this lab showed
route announcement UPDATE message with NLRI and Path Attributes 203.0.113.0/24, 10.0.0.1, 65001 i
NLRI Network Layer Reachability Information Network 203.0.113.0/24
NEXT_HOP NEXT_HOP path attribute Next Hop 10.0.0.1
AS_PATH AS_PATH path attribute Path 65001
ORIGIN ORIGIN path attribute i / IGP
route withdrawal UPDATE message with Withdrawn Routes BGP route absent, No BGP prefixes displayed, 0 exist
re-advertise new UPDATE announcement after withdrawal table version changes to 3, route appears again

Cleanup

The verification script destroys the topology at the end:

[protocol-lab][bgp-02] destroying topology
08:23:08 INFO Destroying lab name=bgp-02
08:23:08 INFO Removed container name=clab-bgp-02-r1
08:23:08 INFO Removed container name=clab-bgp-02-r2
Enter fullscreen mode Exit fullscreen mode

That's the heart of BGP dynamics: one message type, UPDATE, carries both the "here's a route" and the "forget that route" — and you've now seen both on the wire.

Explore the full Protocol Lab series here: github.com/pathvector-studio/protocol-lab. If these labs are useful to you, please ⭐ star the repo on GitHub — it genuinely helps others find the project.

Next up, we'll create a state where the same prefix is announced from two different origin ASes — the doorway to competing origins, route leaks, and eventually RPKI.

Top comments (0)