Route based VPNs are becoming more and more common, and it’s important to know how to configure an eBGP peer over an IPsec tunnel. This post is designed to show you how to configure eBGP using Birdc, over an IPsec tunel using Strongswan on swanctl.
Prerequisites
Raspberry Pi set up and configured – Installing Raspberry Pi OS 6.12 and SSHing into a Raspberry Pi
BIRD installed and configured – How to Install and Configure the BIRD Routing Daemon (BGP) on Raspberry Pi OS
Strongswan installed and configured- Configuring Strongswan 5.9.8 with swanctl on Raspberry Pi OS
Modifying /etc/sysctl.conf
First we’ll want to add these lines into /etc/sysctl.conf – these will allow your Raspberry Pi to forward packets between interfaces, and will also disable Reverse Path Filtering, which helps fix any issues relating to different virtual IPsec interfaces being used. You can add these anywhere within the file with your favourite text editor.
net.ipv4.ip_forward=1
net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.all.rp_filter=0Save the changes with the following:
sysctl --systemAdding the ipsec0 interface
We’re now going to add some ip links that will relate to the IPs used for BGP over IPsec. The IP address you add should be your IP, and the peer IP should be the other in the /30. We’ll set an if_id that’ll be used in Strongswan config later, and assign it all to ipsec0. Change “wlan0” to the eth interface in use.
sudo ip link add ipsec0 type xfrm dev wlan0 if_id 42
sudo ip link set ipsec0 up
sudo ip addr add 10.255.254.1/32 peer 10.255.254.2 dev ipsec0
sudo ip route replace 10.255.254.2/32 dev ipsec0 scope linkConfiguring /etc/swanctl/swanctl.conf
Now, we need to add a peer to form the BGP over IPsec tunnel with. Here, I’m going to add another connection on top of the config that matches my other blog post, but I’ll share the individual config needed standalone (with no other peers). The Local/Remote TS can technically be set to anything, but I like to set it as just the /32s since routing of client traffic is done independently to the configuration here. We’ll be configuring xfrm interfaces using the “if_id_in” option.
Edit /etc/swanctl/swanctl.conf with this config (and change proposals and other features as required):
connections {
Phase1-Meraki-eBGP {
local_addrs=192.168.10.11
remote_addrs=192.168.60.5
version=2
proposals=aes256-sha1-modp2048
reauth_time=14400
local {
id=192.168.10.11
auth=psk
}
remote {
id=192.168.60.5
auth=psk
}
children {
Phase2-Meraki-eBGP {
local_ts=10.255.254.1/32
remote_ts=10.255.254.2/32
esp_proposals=aes256-sha1
life_time=3600
policies = yes
if_id_in = 42
if_id_out = 42
}
}
}
secrets {
ike-1 {
secret = "MerakiTunnel"
}
}You can then reload strongswan using swanctl -q and swanctl -r.
Configuring /etc/bird/bird.conf
Now we’ll wait to configure some base config for /etc/bird/bird.conf. As explained in my other blog post, you can add/remove routes being shared here as well, and also exclude routes being added or removed. Here I’m advertising 192.0.2.0/24, and rejecting 192.1.2.0/24 and 192.1.2.0/25, to give an example. My router_id matches my wlan interface IP, and the source address matches the eBGP IP I’ve set above in the ipsec0 configuration.
router id 192.168.10.11;
protocol kernel {
scan time 20;
ipv4 {
import all;
export all;
};
}
protocol static {
ipv4;
route 192.0.2.0/24 via 192.168.10.1; #192.168.60.8 for normal ebgp
}
protocol device {
scan time 10;
}
protocol direct {
ipv4;
}
protocol bgp MX67C_IPSEC {
local as 64000;
neighbor 10.255.254.2 as 59999;
source address 10.255.254.1;
strict bind yes;
multihop 2;
ttl security off;
hold time 240;
graceful restart on;
ipv4 {
import all;
export filter {
if net ~ [ 192.1.2.0/24, 192.1.2.0/25 ] then reject;
accept;
};
};
}—
Testing the Configuration
Now, let’s test everything. First, make sure that all config is loaded in:
swanctl -q
swanctl -r
swanctl -t -i Phase1-Meraki-eBGP ### breaking any existing tunnel that might have existed before everything loaded
systemctl start bird
birdc confMake sure the other peer is configured too (if they’re both Linux, set up the same way), otherwise I’ve formed a BGP over IPsec peer with a Cisco Meraki MX, following the documentation here
Check swanctl -l and see if the tunnel has formed correctly, if it hasn’t, troubleshoot appropriately in /var/log/strongswan.log. Make sure that you see the 0x00000 values next to in and out, otherwise the id_in and id_out config has not applied.
root@raspberrypi:/home/burstbytes# swanctl -l
Phase1-Meraki-eBGP: #1, ESTABLISHED, IKEv2, c524654be7b484c1_i 54bfe1c2b2c7290d_r*
local '192.168.10.11' @ 192.168.10.11[4500]
remote '192.168.60.5' @ 192.168.60.5[4500]
AES_CBC-256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048
established 11s ago, reauth in 13171s
Phase2-Meraki-eBGP: #1, reqid 1, INSTALLED, TUNNEL, ESP:AES_CBC-256/HMAC_SHA1_96
installed 11s ago, rekeying in 3589s, expires in 3591s
in cf6042ae (-|0x0000002a), 570 bytes, 8 packets, 6s ago
out c98ae241 (-|0x0000002a), 606 bytes, 8 packets, 6s ago
local 10.255.254.1/32
remote 10.255.254.2/32Check birdc show proto and see if the eBGP relationship has been formed:
root@raspberrypi:/home/burstbytes# birdc show proto
BIRD 2.0.12 ready.
Name Proto Table State Since Info
kernel1 Kernel master4 up 13:33:41.872
static1 Static master4 up 13:33:41.872
device1 Device --- up 13:33:41.872
direct1 Direct --- up 13:33:41.872
MX67C_IPSEC BGP --- up 13:35:55.934 EstablishedTake a packet capture on the ipsec0 interface and see the BGP traffic:
root@raspberrypi:/home/burstbytes# tcpdump -nei ipsec0
tcpdump: verbose output suppressed, use -v[v]… for full protocol decode
listening on ipsec0, link-type RAW (Raw IP), snapshot length 262144 bytes
12:54:39.353001 ip: 10.255.254.1.52207 > 10.255.254.2.179: Flags [P.], seq 2724588861:2724588880, ack 2845760694, win 1003, options [nop,nop,TS val 3375357794 ecr 595397226], length 19: BGP
12:54:39.360553 ip: 10.255.254.2.179 > 10.255.254.1.52207: Flags [.], ack 19, win 253, options [nop,nop,TS val 595431472 ecr 3375357794], length 0
12:55:08.419724 ip: 10.255.254.2.179 > 10.255.254.1.52207: Flags [P.], seq 1:20, ack 19, win 253, options [nop,nop,TS val 595460527 ecr 3375357794], length 19: BGP
12:55:08.419930 ip: 10.255.254.1.52207 > 10.255.254.2.179: Flags [.], ack 20, win 1003, options [nop,nop,TS val 3375386861 ecr 595460527], length 0
12:55:50.708113 ip: 10.255.254.1.52207 > 10.255.254.2.179: Flags [P.], seq 19:38, ack 20, win 1003, options [nop,nop,TS val 3375429149 ecr 595460527], length 19: BGP
12:55:50.716243 ip: 10.255.254.2.179 > 10.255.254.1.52207: Flags [.], ack 38, win 253, options [nop,nop,TS val 595502827 ecr 3375429149], length 0
12:56:24.389912 ip: 10.255.254.2.179 > 10.255.254.1.52207: Flags [P.], seq 20:39, ack 38, win 253, options [nop,nop,TS val 595536496 ecr 3375429149], length 19: BGPIf you see all of these, the tunnel has formed successfully and routes are sharing over the IPSEC tunnel!
Conclusion
This guide has used Strongswan (swanctl) and Birdc to form a BGP over IPsec tunnel with a Cisco Meraki MX. Utilising xfrm interfaces, we’re able to create a virtual interface that can be used by Bird to form a eBGP relationship through the IPsec tunnel encrypted. This is easy to set up and generally doesn’t pose many problems. A great tool to share eBGP routes in a different physical network environment with no local connectivity.
Get notified whenever I post something new. No spam, and it helps a lot!






Leave a Reply