DEV Community

pathvector-dev
pathvector-dev

Posted on • Originally published at blog.pathvector.dev

Competing Origins: When Two ASes Announce the Same Prefix, Who Do You Believe?

Originally published at https://blog.pathvector.dev/protocol-lab-bgp-03/ — 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 make two different ASNs announce the same prefix and observe how an upstream router sees the competing origins. The theme is simple but foundational: seeing a prefix in BGP is not the same as knowing who is allowed to originate it.

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

Expected time: 45–60 minutes.

The Goal

Two ASes originate 203.0.113.0/24, and the observer router r2 ends up holding two paths for the same prefix. By the end, you should be able to explain this observation:

203.0.113.0/24 has two BGP paths on r2
path via 10.0.12.1 has origin AS65001
path via 10.0.23.2 has origin AS65003
Enter fullscreen mode Exit fullscreen mode

You will read the rightmost AS in each AS_PATH as the origin AS, and explain why a competing origin — the same prefix visible from multiple origin ASes — is the doorway to some of BGP's most notorious failure modes.

What You Will Learn

  • The rightmost AS in the AS_PATH is read as the route's origin AS.
  • The same prefix can be visible from multiple origin ASes at once.
  • Competing origins can happen for several reasons: misconfiguration, intentional multihoming, route leaks, or hijacks.
  • The BGP table alone cannot tell you whether an origin AS is actually authorized.
  • RPKI origin validation — the subject of the next lab — is the tool that answers that question.

This lab does not cover:

  • RPKI / ROA / ROV
  • Route-leak taxonomy in detail
  • The BGP decision process in detail
  • AS-path validation
  • Announcements to the public internet

Where to Read in the RFCs

The required reading this time is RFC 4271, in these sections:

Section What to focus on
1.1 Terminology: AS, BGP speaker, route, RIB
3.1 A route is a pairing of a prefix and its path attributes
4.3 UPDATE messages carry path attributes and NLRI
5.1.2 AS_PATH and AS_SEQUENCE
9 How a BGP speaker processes received UPDATEs, storing and selecting routes

As supporting material, read the problem statement in RFC 7908 as your entry point into route leaks.

The Big Picture

We build three virtual routers.

AS65001 / r1                         AS65002 / r2                         AS65003 / r3
10.0.12.1/30  ----------------------  10.0.12.2/30
                                      10.0.23.1/30  ----------------------  10.0.23.2/30

r1 advertises:
  203.0.113.0/24

r3 also advertises:
  203.0.113.0/24

r2 observes:
  203.0.113.0/24 via 10.0.12.1, AS_PATH 65001
  203.0.113.0/24 via 10.0.23.2, AS_PATH 65003
Enter fullscreen mode Exit fullscreen mode

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

flowchart LR
  r1["r1<br/>AS65001<br/>originates 203.0.113.0/24"]
  r2["r2<br/>AS65002<br/>observer"]
  r3["r3<br/>AS65003<br/>also originates 203.0.113.0/24"]

  r1 -- "eBGP<br/>10.0.12.0/30" --> r2
  r3 -- "eBGP<br/>10.0.23.0/30" --> r2
Enter fullscreen mode Exit fullscreen mode
flowchart TB
  prefix["203.0.113.0/24"]
  path1["Path 1<br/>NEXT_HOP 10.0.12.1<br/>AS_PATH 65001<br/>origin AS65001"]
  path2["Path 2<br/>NEXT_HOP 10.0.23.2<br/>AS_PATH 65003<br/>origin AS65003"]
  question["Question<br/>Which origin is authorized?"]

  prefix --> path1
  prefix --> path2
  path1 --> question
  path2 --> question
Enter fullscreen mode Exit fullscreen mode

Note: This lab runs entirely inside a closed Docker environment using an RFC 5737 documentation prefix. Never advertise documentation prefixes to the real internet.

What You Need

Recommended environment:

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

Image used:

  • frrouting/frr:latest

On macOS, run this inside a Linux VM, a WSL-equivalent environment, or a Linux VM on OrbStack/Colima.

Running the Lab

All steps below happen inside the Linux environment where containerlab runs.

If you have the repo checked out, the verification script does everything in one go:

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

labctl.sh run bgp-03 handles the topology deploy, FRR output checks, pcap capture, and destroy.

Or step through it manually:

1. Move into the working directory

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

2. Deploy

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

After the deploy, confirm the containers are up:

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

What to check:

  • clab-bgp-03-r1 is running.
  • clab-bgp-03-r2 is running.
  • clab-bgp-03-r3 is running.

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

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

What to observe:

  • r2's local AS is 65002.
  • Neighbor 10.0.12.1 is AS 65001.
  • Neighbor 10.0.23.2 is AS 65003.
  • A prefix has been received from both neighbors.

3. See the two paths for the same prefix on r2

docker exec -it clab-bgp-03-r2 vtysh -c "show bgp ipv4 unicast 203.0.113.0/24"
Enter fullscreen mode Exit fullscreen mode

How to read the output:

Paths: (2 available, best #...)
  65001
    10.0.12.1 from 10.0.12.1 ...
      Origin IGP ...
  65003
    10.0.23.2 from 10.0.23.2 ...
      Origin IGP ...
Enter fullscreen mode Exit fullscreen mode

The ordering can vary with the FRRouting version and how the best path is chosen. What matters is that the same 203.0.113.0/24 shows two origin ASes: 65001 and 65003.

4. See both UPDATEs in a packet capture

Open another terminal and, still inside the Linux environment running containerlab, capture in r2's namespace:

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

With the capture running, bounce r2's BGP sessions from another terminal:

docker exec -it clab-bgp-03-r2 vtysh -c "clear bgp *"
Enter fullscreen mode Exit fullscreen mode

A few seconds later, stop tcpdump with Ctrl-C. bgp-03-r2.pcap is left in your current working directory.

Open it in Wireshark and look for:

  • The BGP UPDATE from 10.0.12.1 -> 10.0.12.2
    • AS_PATH: 65001
    • NLRI: 203.0.113.0/24
  • The BGP UPDATE from 10.0.23.2 -> 10.0.23.1
    • AS_PATH: 65003
    • NLRI: 203.0.113.0/24

The point:

  • The prefix is the same.
  • The origin AS is different.
  • Nothing in the BGP UPDATE itself tells you which origin is the authorized one.

Expected Output

Rather than an exact character-for-character match, focus on being able to pull out these fields.

show bgp summary

On r2, confirm:

Local AS number 65002
Neighbor        V         AS   ...   State/PfxRcd
10.0.12.1       4      65001   ...   1
10.0.23.2       4      65003   ...   1
Enter fullscreen mode Exit fullscreen mode

show bgp ipv4 unicast 203.0.113.0/24

On r2, confirm:

Paths: (2 available, best #...)
  65001
    10.0.12.1 from 10.0.12.1 ...
      Origin IGP ...
  65003
    10.0.23.2 from 10.0.23.2 ...
      Origin IGP ...
Enter fullscreen mode Exit fullscreen mode

What to look for:

  • 203.0.113.0/24 is visible via two paths.
  • One AS_PATH is 65001.
  • The other AS_PATH is 65003.
  • The rightmost AS in each AS_PATH reads as the origin AS.

Why It Works

r1 originates 203.0.113.0/24 as AS65001. r3 also originates the same 203.0.113.0/24 — but as AS65003.

r2 receives UPDATEs carrying the same NLRI from two eBGP neighbors. Each UPDATE carries its own path attributes, and the AS_PATH differs between them.

At this point, r2 is looking at two claims of reachability for the same prefix. BGP may pick a single best path, but the important observation is that multiple origin ASes are visible for the same prefix.

This is not always an attack. Intentional multihoming or a migration in progress can legitimately produce multiple origins. But it is also the entry point for misadvertisements, route leaks, and prefix hijacks. And the BGP table alone gives you no way to judge whether an origin AS is authorized.

Common Misconceptions

  • A competing origin does not automatically mean a hijack.
  • Route leaks and competing origins are not synonyms. A route leak is about the propagation scope and business relationships of an advertisement; a competing origin is the state where multiple origin ASes are visible for the same prefix.
  • Paths that lose the best-path decision still matter for observation.
  • The AS_PATH is not proof of prefix ownership.
  • This lab runs only inside a closed Docker environment. Never advertise documentation prefixes to the real internet.

Troubleshooting

r2 only sees one path

Check that both neighbors are Established:

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

If you don't see both 10.0.12.1 and 10.0.23.2, verify the interface addresses:

docker exec -it clab-bgp-03-r1 ip addr show eth1
docker exec -it clab-bgp-03-r2 ip addr show eth1
docker exec -it clab-bgp-03-r2 ip addr show eth2
docker exec -it clab-bgp-03-r3 ip addr show eth1
Enter fullscreen mode Exit fullscreen mode

r1 or r3 isn't advertising the prefix

Check each router's loopback and FRR config:

docker exec -it clab-bgp-03-r1 ip addr show lo
docker exec -it clab-bgp-03-r3 ip addr show lo
docker exec -it clab-bgp-03-r1 vtysh -c "show running-config"
docker exec -it clab-bgp-03-r3 vtysh -c "show running-config"
Enter fullscreen mode Exit fullscreen mode

FRRouting's network 203.0.113.0/24 only advertises the prefix when a matching route exists in the routing table.

Only one UPDATE shows up in the pcap

Bounce the BGP sessions after the capture has started:

docker exec -it clab-bgp-03-r2 vtysh -c "clear bgp *"
Enter fullscreen mode Exit fullscreen mode

If you still only see one, confirm you're capturing with -i any inside the r2 namespace.

Check Your Understanding

  1. What are the two AS_PATHs that r2 sees for 203.0.113.0/24?
  2. What is the origin AS of each?
  3. Why is seeing multiple origins for the same prefix a doorway to danger?
  4. What is the difference between a competing origin and a route leak?
  5. Explain why the BGP table alone cannot tell you whether an origin is authorized.

Cleanup

When you're done, destroy the containerlab topology:

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

Confirm no containers are left behind:

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

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

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

Otherwise, delete it:

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

What to Read Next

The next lab compares RPKI ROAs / VRPs against BGP routes and shows why an origin AS comes out as valid, invalid, or not-found.

Read next:

  • RFC 6482: Route Origin Authorization
  • RFC 6811: BGP Prefix Origin Validation
  • RFC 8210: RPKI to Router Protocol

References

  • RFC 4271, Section 1.1: Definition of common BGP terms
  • RFC 4271, Section 3.1: Routes, advertisement, and storage
  • RFC 4271, Section 4.3: UPDATE Message Format
  • RFC 4271, Section 5.1.2: AS_PATH
  • RFC 4271, Section 9: UPDATE Message Handling
  • RFC 5737, Section 3: Documentation address blocks, including 203.0.113.0/24
  • RFC 7908: Problem Definition and Classification of BGP Route Leaks

Verified Run Log (2026-05-09)

This lab has been confirmed reproducible. The log below is based on assets/bgp-03/runs/20260509T060606Z/run.log.

verification.json reported:

{
  "lab": "bgp-03",
  "run_id": "20260509T060606Z",
  "status": "verified",
  "message": "BGP route 203.0.113.0/24 is visible on r2 from origin AS65001 and AS65003."
}
Enter fullscreen mode Exit fullscreen mode

On r2 — the observation point of the three-router topology — each of the two eBGP neighbors advertised one prefix:

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
10.0.12.1       4      65001         4         4        0    0    0 00:00:01            1        1 N/A
10.0.23.2       4      65003         4         4        0    0    0 00:00:01            1        1 N/A
Enter fullscreen mode Exit fullscreen mode

For the same 203.0.113.0/24, r2's BGP table showed two paths:

   Network          Next Hop            Metric LocPrf Weight Path
*> 203.0.113.0/24   10.0.12.1                0             0 65001 i
*                   10.0.23.2                0             0 65003 i

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

Comparing the two paths, the rightmost origin AS differs:

Next hop AS_PATH Origin AS BGP table status
10.0.12.1 65001 65001 valid, best
10.0.23.2 65003 65003 valid

In the detailed view, FRRouting selected the 65001 side as the best path:

BGP routing table entry for 203.0.113.0/24, version 1
Paths: (2 available, best #1, table default)
  Advertised to non peer-group peers:
  10.0.12.1 10.0.23.2
  65001
    10.0.12.1 from 10.0.12.1 (1.1.1.1)
      Origin IGP, metric 0, valid, external, best (Router ID)
      Last update: Sat May  9 06:06:09 2026
  65003
    10.0.23.2 from 10.0.23.2 (3.3.3.3)
      Origin IGP, metric 0, valid, external
      Last update: Sat May  9 06:06:09 2026
Enter fullscreen mode Exit fullscreen mode

In this run, 203.0.113.0/24 was visible from both AS65001 and AS65003, and r2 chose the AS65001 path as best. But a best path in the BGP table is not proof of an authorized origin. To judge which origin AS is legitimate, you need the ROAs / VRPs and origin validation covered in the next lab.


That's the competing-origins problem in a nutshell: BGP faithfully carries every claim of "I can reach this prefix," and nothing in the protocol says which claim is true.

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 bring in RPKI: matching ROAs against BGP routes to see exactly why an origin comes out valid, invalid, or not-found.

Top comments (0)