BGP Route Reflector and Confederation
This lab builds a BGP topology that mixes the two classic ways of avoiding a full mesh of iBGP sessions: a route reflector in one AS, and a confederation split into two sub-AS in the other. It also mixes the two routing daemons shipped with BSDRP, Bird 3 and FRRouting 10, peering with each other, and protects two of the sessions with TCP-MD5.
Overview¶
Network diagram¶
Here is the BGP and logical view:
flowchart LR
subgraph AS100["AS 100"]
R1["R1 (bird)<br/>10.0.6.1<br/>RR client"]
R2["R2 (bird)<br/>10.0.6.2<br/>route reflector"]
R3["R3 (bird)<br/>10.0.6.3 / 10.0.2.3<br/>RR client, border"]
end
subgraph AS200["AS 200 (confederation)"]
subgraph SUB1["sub-AS 64512"]
R4["R4 (FRR)<br/>10.0.2.4 / 10.0.3.4"]
R5["R5 (FRR)<br/>10.0.3.5 / 10.0.4.5"]
end
subgraph SUB2["sub-AS 65535"]
R6["R6 (FRR)<br/>10.0.4.6 / 10.0.5.6"]
R7["R7 (FRR)<br/>10.0.5.7 / 10.0.7.7"]
end
end
R1 ---|"iBGP<br/>reflected"| R2
R2 ---|"iBGP<br/>reflected"| R3
R3 ---|"eBGP 100 to 200<br/>10.0.2.0/24, TCP-MD5"| R4
R4 ---|"intra sub-AS<br/>10.0.3.0/24"| R5
R5 ---|"inter sub-AS<br/>10.0.4.0/24"| R6
R6 ---|"intra sub-AS<br/>10.0.5.0/24, TCP-MD5"| R7
R1, R2 and R3 share a LAN (10.0.6.0/24 and 2001:db8:6::/64); every other link is a point-to-point Ethernet segment. Each address family is configured, so the whole lab runs dual-stack.
Preparing the lab¶
Setting up the lab¶
See How to build a BSDRP router lab.
Starting the lab¶
This lab needs 7 VMs and one common LAN. The -r bgp flag applies the
configuration of each router automatically on first boot, through the
labconfig script shipped in the image:
# tools/BSDRP-lab-bhyve.sh -i BSDRP-2.3-full-amd64.img.xz -n 7 -l 1 -r bgp
BSD Router Project (https://bsdrp.net) - bhyve full-meshed lab script
Setting-up a virtual lab with 7 VM(s):
- Working directory: /root/BSDRP-VMs
- Each VM has a total of 1 (1 cores and 1 threads) and 1G RAM
- Emulated NIC: virtio-net
- Boot mode: UEFI
- Switch mode: bridge + tap
- 1 LAN(s) between all VM
- Full mesh Ethernet links between each VM
- Regression test lab: bgp
[...]
To connect VM'serial console, you can use:
- VM 1 : sudo cu -l /dev/nmdm-BSDRP.1B
- VM 2 : sudo cu -l /dev/nmdm-BSDRP.2B
- VM 3 : sudo cu -l /dev/nmdm-BSDRP.3B
- VM 4 : sudo cu -l /dev/nmdm-BSDRP.4B
- VM 5 : sudo cu -l /dev/nmdm-BSDRP.5B
- VM 6 : sudo cu -l /dev/nmdm-BSDRP.6B
- VM 7 : sudo cu -l /dev/nmdm-BSDRP.7B
Router configuration¶
Each router below is exactly what labconfig bgp_vm<N> applies; enter it by
hand if you started the lab without -r bgp. Use labconfig only on a lab
machine; it replaces the running configuration.
labconfig bgp_vm[VM-NUMBER]
Router 1¶
R1 is a plain iBGP speaker of AS 100 and a client of the route reflector. It originates the 10.0.1.0/24 and 2001:db8:1::/64 stub prefixes (the far end of that link is not configured).
sysrc hostname=R1 \
ifconfig_vtnet6="10.0.6.1/24" \
ifconfig_vtnet6_ipv6="inet6 2001:db8:6::1 prefixlen 64" \
ifconfig_vtnet0="10.0.1.1/24" \
ifconfig_vtnet0_ipv6="inet6 2001:db8:1::1 prefixlen 64" \
bird_enable=YES
cat > /usr/local/etc/bird.conf <<EOF
# Configure logging
log syslog all;
log "/var/log/bird.log" all;
log stderr all;
# Override router ID
router id 0.0.0.101;
# Sync bird routing table with kernel
protocol kernel kernel4 {
ipv4 {
export all;
};
Router 2¶
R2 is the route reflector of AS 100. rr client on each session is the only thing that turns it into one: without it, R1 and R3 would need a direct session, because an iBGP speaker never re-advertises what it learned from another iBGP speaker.
sysrc hostname=R2 \
ifconfig_vtnet6="10.0.6.2/24" \
ifconfig_vtnet6_ipv6="inet6 2001:db8:6::2 prefixlen 64" \
bird_enable=YES
cat > /usr/local/etc/bird.conf <<EOF
# Configure logging
log syslog all;
log "/var/log/bird.log" all;
log stderr all;
# Override router ID
router id 0.0.0.102;
# Define variable
define myas = 100;
# Sync bird routing table with kernel
protocol kernel kernel4 {
ipv4 {
export all;
};
Router 3¶
R3 is the second reflector client, and the border router of AS 100: its session with R4 is eBGP toward the confederation, protected by TCP-MD5. next hop self on the iBGP side is needed because the AS 100 routers have no route to the 10.0.2.0/24 transit subnet.
sysrc hostname=R3 \
ifconfig_vtnet6="10.0.6.3/24" \
ifconfig_vtnet6_ipv6="inet6 2001:db8:6::3 prefixlen 64" \
ifconfig_vtnet2="10.0.2.3/24" \
ifconfig_vtnet2_ipv6="inet6 2001:db8:2::3 prefixlen 64" \
bird_enable=YES
cat > /usr/local/etc/bird.conf <<EOF
# Configure logging
log syslog all;
log "/var/log/bird.log" all;
log stderr all;
# Override router ID
router id 0.0.0.103;
# Define variable
define myas = 100;
# Sync bird routing table with kernel
protocol kernel kernel4 {
ipv4 {
export all;
};
Router 4¶
R4 belongs to sub-AS 64512 of confederation AS 200. bgp confederation identifier 200 is the AS number the outside world sees, bgp confederation peers 65535 declares the other sub-AS as internal. The TCP-MD5 password is the counterpart of R3's, with the security associations here written by hand in /etc/ipsec.conf.
sysrc hostname=R4 \
frr_enable=YES \
ipsec_enable=YES \
ipsec_file="/etc/ipsec.conf"
cat <<EOF > /etc/ipsec.conf
flush ;
add 10.0.2.3 10.0.2.4 tcp 0x1000 -A tcp-md5 "abigpassword" ;
add 10.0.2.4 10.0.2.3 tcp 0x1001 -A tcp-md5 "abigpassword" ;
add -6 2001:db8:2::3 2001:db8:2::4 tcp 0x1002 -A tcp-md5 "abigpassword" ;
add -6 2001:db8:2::4 2001:db8:2::3 tcp 0x1003 -A tcp-md5 "abigpassword" ;
EOF
service ipsec start
cat > /usr/local/etc/frr/frr.conf <<EOF
interface vtnet2
ip address 10.0.2.4/24
ipv6 address 2001:db8:2::4/64
interface vtnet3
ip address 10.0.3.4/24
ipv6 address 2001:db8:3::4/64
router bgp 64512
bgp router-id 0.0.0.204
bgp confederation identifier 200
bgp confederation peers 65535
no bgp ebgp-requires-policy
no bgp default ipv4-unicast
neighbor 10.0.2.3 remote-as 100
neighbor 10.0.2.3 password abigpassword
neighbor 10.0.3.5 remote-as 64512
neighbor 2001:db8:2::3 remote-as 100
neighbor 2001:db8:2::3 password abigpassword
neighbor 2001:db8:3::5 remote-as 64512
!
address-family ipv4 unicast
network 10.0.3.0/24
neighbor 10.0.2.3 activate
neighbor 10.0.3.5 activate
neighbor 10.0.3.5 next-hop-self
no neighbor 2001:db8:2::3 activate
no neighbor 2001:db8:3::5 activate
exit-address-family
!
address-family ipv6 unicast
network 2001:db8:3::/64
neighbor 2001:db8:2::3 activate
neighbor 2001:db8:3::5 activate
neighbor 2001:db8:3::5 next-hop-self
exit-address-family
!
EOF
hostname R4
service frr start
config save
Router 5¶
R5 is the second router of sub-AS 64512, and the one that peers with the other sub-AS.
sysrc hostname=R5 \
frr_enable=YES
cat <<EOF > /usr/local/etc/frr/frr.conf
log syslog
interface vtnet3
ip address 10.0.3.5/24
ipv6 address 2001:db8:3::5/64
!
interface vtnet4
ip address 10.0.4.5/24
ipv6 address 2001:db8:4::5/64
router bgp 64512
bgp router-id 0.0.0.205
bgp confederation identifier 200
bgp confederation peers 65535
no bgp ebgp-requires-policy
no bgp default ipv4-unicast
neighbor 10.0.3.4 remote-as 64512
neighbor 10.0.4.6 remote-as 65535
neighbor 2001:db8:3::4 remote-as 64512
neighbor 2001:db8:4::6 remote-as 65535
!
address-family ipv4 unicast
network 10.0.3.0/24
network 10.0.4.0/24
neighbor 10.0.3.4 activate
neighbor 10.0.3.4 next-hop-self
neighbor 10.0.4.6 activate
neighbor 10.0.4.6 next-hop-self
no neighbor 2001:db8:3::4 activate
no neighbor 2001:db8:4::6 activate
exit-address-family
!
address-family ipv6 unicast
network 2001:db8:3::/64
network 2001:db8:4::/64
neighbor 2001:db8:3::4 activate
neighbor 2001:db8:3::4 next-hop-self
neighbor 2001:db8:4::6 activate
neighbor 2001:db8:4::6 next-hop-self
exit-address-family
EOF
hostname R5
service frr start
config save
Router 6¶
R6 opens sub-AS 65535, peering with R5 across the confederation boundary and with R7 inside its own sub-AS, again with TCP-MD5.
sysrc hostname=R6 \
ipsec_enable=YES \
ipsec_file="/etc/ipsec.conf" \
frr_enable=YES
cat <<EOF > /etc/ipsec.conf
flush ;
add 10.0.5.6 10.0.5.7 tcp 0x1000 -A tcp-md5 "abcdefgh" ;
add 10.0.5.7 10.0.5.6 tcp 0x1001 -A tcp-md5 "abcdefgh" ;
add -6 2001:db8:5::6 2001:db8:5::7 tcp 0x1002 -A tcp-md5 "abcdefgh" ;
add -6 2001:db8:5::7 2001:db8:5::6 tcp 0x1003 -A tcp-md5 "abcdefgh" ;
EOF
service ipsec start
cat <<EOF > /usr/local/etc/frr/frr.conf
log syslog
interface vtnet4
ip address 10.0.4.6/24
ipv6 address 2001:db8:4::6/64
!
interface vtnet5
ip address 10.0.5.6/24
ipv6 address 2001:db8:5::6/64
router bgp 65535
bgp router-id 0.0.0.206
bgp confederation identifier 200
bgp confederation peers 64512
no bgp ebgp-requires-policy
no bgp default ipv4-unicast
neighbor 10.0.4.5 remote-as 64512
neighbor 10.0.5.7 remote-as 65535
neighbor 10.0.5.7 password abcdefgh
neighbor 2001:db8:4::5 remote-as 64512
neighbor 2001:db8:5::7 remote-as 65535
neighbor 2001:db8:5::7 password abcdefgh
!
address-family ipv4 unicast
network 10.0.5.0/24
neighbor 10.0.4.5 activate
neighbor 10.0.4.5 next-hop-self
neighbor 10.0.5.7 activate
neighbor 10.0.5.7 next-hop-self
no neighbor 2001:db8:4::5 activate
no neighbor 2001:db8:5::7 activate
exit-address-family
!
address-family ipv6 unicast
network 2001:db8:5::/64
neighbor 2001:db8:4::5 activate
neighbor 2001:db8:4::5 next-hop-self
neighbor 2001:db8:5::7 activate
neighbor 2001:db8:5::7 next-hop-self
exit-address-family
EOF
hostname R6
service frr start
config save
Router 7¶
R7 is the far end of the lab. It originates 10.0.7.0/24 and 2001:db8:7::/64, the prefixes used to check that a route crosses the whole topology.
sysrc hostname=R7 \
ipsec_enable=YES \
ipsec_file="/etc/ipsec.conf" \
frr_enable=YES
cat <<EOF > /etc/ipsec.conf
flush ;
add 10.0.5.6 10.0.5.7 tcp 0x1000 -A tcp-md5 "abcdefgh" ;
add 10.0.5.7 10.0.5.6 tcp 0x1001 -A tcp-md5 "abcdefgh" ;
add -6 2001:db8:5::6 2001:db8:5::7 tcp 0x1002 -A tcp-md5 "abcdefgh" ;
add -6 2001:db8:5::7 2001:db8:5::6 tcp 0x1003 -A tcp-md5 "abcdefgh" ;
EOF
service ipsec start
cat <<EOF > /usr/local/etc/frr/frr.conf
log syslog
interface vtnet0
ip address 10.0.7.7/24
ipv6 address 2001:db8:7::7/64
!
interface vtnet5
ip address 10.0.5.7/24
ipv6 address 2001:db8:5::7/64
router bgp 65535
bgp router-id 0.0.0.207
bgp confederation identifier 200
bgp confederation peers 64512
no bgp ebgp-requires-policy
no bgp default ipv4-unicast
neighbor 10.0.5.6 remote-as 65535
neighbor 10.0.5.6 password abcdefgh
neighbor 2001:db8:5::6 remote-as 65535
neighbor 2001:db8:5::6 password abcdefgh
!
address-family ipv4 unicast
network 10.0.5.0/24
network 10.0.7.0/24
neighbor 10.0.5.6 activate
no neighbor 2001:db8:5::6 activate
exit-address-family
!
address-family ipv6 unicast
network 2001:db8:5::/64
network 2001:db8:7::/64
neighbor 2001:db8:5::6 activate
exit-address-family
EOF
hostname R7
service frr start
config save
Final testing¶
The output below was captured on BSDRP 2.3, with bird 3.3.2 and FRRouting 10.7.1.
Route reflector side¶
All four sessions of the reflector are established, two per address family:
[root@R2]~# birdc show protocols
BIRD 3.3.2 ready.
Name Proto Table State Since Info
kernel4 Kernel master4 up 08:46:45.495
kernel6 Kernel master6 up 08:46:45.495
device1 Device --- up 08:46:45.495
direct1 Direct --- up 08:46:45.495
R1inet4 BGP --- up 08:46:49.363 Established
R3inet4 BGP --- up 08:46:49.530 Established
R1inet6 BGP --- up 08:46:48.889 Established
R3inet6 BGP --- up 08:46:49.363 Established
The reflection itself is visible on a client. R3 receives R1's prefix from the
reflector (from 10.0.6.2) while the next hop stays R1, and the two attributes
that a reflector adds are there: the originator, R1, and a cluster list holding
the reflector's router id.
[root@R3]~# birdc show route 10.0.1.0/24 all
BIRD 3.3.2 ready.
Table master4:
10.0.1.0/24 unicast [R2inet4 08:46:49.556 from 10.0.6.2] * (100) [i]
via 10.0.6.1 on vtnet6
hostentry: via 10.0.6.1 table master4
preference: 100
local_metric: 0
from: 10.0.6.2
source: BGP
bgp_origin: IGP
bgp_path:
bgp_next_hop: 10.0.6.1
bgp_local_pref: 100
bgp_originator_id: 0.0.0.101
bgp_cluster_list: 0.0.0.102
Those two attributes are what make the reflected route loop-free: a router that finds its own id in the cluster list drops the update.
Confederation side¶
R4 has one session with AS 100 and one inside its own sub-AS. Note that both are plain BGP sessions: the confederation only changes how the AS path is built.
[root@R4]~# vtysh -c 'show ip bgp summary'
IPv4 Unicast Summary:
BGP router identifier 0.0.0.204, local AS number 64512 VRF default vrf-id 0
BGP table version 7
RIB entries 7, using 1120 bytes of memory
Peers 2, using 46 KiB of memory
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
10.0.2.3 4 100 7 10 7 0 0 00:02:01 3 7 N/A
10.0.3.5 4 64512 7 8 7 0 0 00:02:02 4 4 N/A
Total number of neighbors 2
The IPv6 sessions are up too, with their own peers:
[root@R4]~# vtysh -c 'show bgp ipv6 summary'
IPv6 Unicast Summary:
BGP router identifier 0.0.0.204, local AS number 64512 VRF default vrf-id 0
BGP table version 7
RIB entries 7, using 1120 bytes of memory
Peers 2, using 46 KiB of memory
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
2001:db8:2::3 4 100 8 11 7 0 0 00:02:40 3 7 N/A
2001:db8:3::5 4 64512 9 10 7 0 0 00:02:41 4 4 N/A
Total number of neighbors 2
On R7, at the far end, the sub-AS numbers appear in the path
in parentheses, which is how a confederation marks its internal hops. They are
stripped before the route leaves AS 200, so AS 100 only ever sees 200.
[root@R7]~# vtysh -c 'show ip bgp'
BGP table version is 7, local router ID is 0.0.0.207, vrf id 0
Default local pref 100, local AS 65535
Status codes: s suppressed, d damped, h history, u unsorted, * 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
*>i 10.0.1.0/24 10.0.5.6 100 0 (64512) 100 i
*>i 10.0.2.0/24 10.0.5.6 100 0 (64512) 100 i
*>i 10.0.3.0/24 10.0.5.6 0 100 0 (64512) i
*>i 10.0.4.0/24 10.0.5.6 0 100 0 (64512) i
*> 10.0.5.0/24 0.0.0.0 0 32768 i
* i 10.0.5.6 0 100 0 i
*>i 10.0.6.0/24 10.0.5.6 100 0 (64512) 100 i
*> 10.0.7.0/24 0.0.0.0 0 32768 i
Displayed 7 routes and 8 total paths
End to end¶
R1, at one end of the lab, has learned every prefix, including R7's 10.0.7.0/24 with the confederation seen as plain AS 200:
[root@R1]~# birdc show route
BIRD 3.3.2 ready.
Table master4:
10.0.1.0/24 unicast [direct1 08:46:44.385] ! (240)
dev vtnet0
10.0.6.0/24 unicast [direct1 08:46:44.385] ! (240)
dev vtnet6
unicast [R2inet4 08:46:49.419] (100) [i]
via 10.0.6.2 on vtnet6
10.0.2.0/24 unicast [R2inet4 08:46:49.588 from 10.0.6.2] * (100) [i]
via 10.0.6.3 on vtnet6
10.0.3.0/24 unicast [R2inet4 08:46:49.630 from 10.0.6.2] * (100) [AS200i]
via 10.0.6.3 on vtnet6
10.0.4.0/24 unicast [R2inet4 08:46:49.630 from 10.0.6.2] * (100) [AS200i]
via 10.0.6.3 on vtnet6
10.0.5.0/24 unicast [R2inet4 08:46:49.630 from 10.0.6.2] * (100) [AS200i]
via 10.0.6.3 on vtnet6
10.0.7.0/24 unicast [R2inet4 08:46:49.630 from 10.0.6.2] * (100) [AS200i]
via 10.0.6.3 on vtnet6
And the data plane follows, in both address families:
[root@R1]~# ping -c 3 10.0.7.7
--- 10.0.7.7 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.887/1.235/1.418/0.246 ms
[root@R1]~# ping6 -c 3 2001:db8:7::7
--- 2001:db8:7::7 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 1.300/1.372/1.422/0.053 ms
TCP-MD5 sessions¶
On FreeBSD, the TCP-MD5 signature is not computed by the routing daemon but by
the kernel, from security associations in the SAD. The bird side of the R3-R4
session creates them on its own, from the password statement, which is why
R3 needs source address to know which local address to bind them to; the FRR
side has them written by hand in /etc/ipsec.conf.
[root@R3]~# setkey -D
2001:db8:2::4 2001:db8:2::3
tcp mode=any spi=238945079(0x0e3e0337) reqid=0(0x00000000)
A: tcp-md5 61626967 70617373 776f7264
seq=0x00000000 replay=0 flags=0x00000040 state=mature
created: Sep 21 08:46:44 2026 current: Sep 21 08:49:26 2026
diff: 162(s) hard: 0(s) soft: 0(s)
last: Sep 21 08:46:45 2026 hard: 0(s) soft: 0(s)
current: 2027(bytes) hard: 0(bytes) soft: 0(bytes)
allocated: 14 hard: 0 soft: 0
sadb_seq=3 pid=2853 refcnt=1
A session that stays in Connect or Active while the configuration looks
right is the usual symptom of a missing or mismatched SA on one of the two
ends.