· 9 min read
Configure BGP easily with BIRD and Ansible
Many of our clients ask for help with configuration of BIRD deamon (mostly for BGP). While you'll still need our help to make configuration on the network side, it'll still be necessary to configure your server correctly.
BIRD is an Internet Routing Daemon designed to avoid all of shortcomings, to support all the routing technology used in the today's Internet or planned to be used in near future and to have a clean extensible architecture allowing new routing protocols to be incorporated easily.
Unfortunately, no solution just works as-is. You'll have to configure not only BIRD but network interfaces and firewalls as well. These configs are NOT optional and will cause problems if done wrong.
All settings should be done on (at least mostly) a clean-install server to avoid problems with debugging.
To make the process easier, we have prepared an ansible playbook to ease the process. We strongly recommend you use it to avoid misconfiguration.
Ansible
According to their website:
Ansible is the simplest way to automate apps and IT infrastructure. Application Deployment + Configuration Management + Continuous Delivery.
And I couldn't agree more. If you don't use it yet, you really should. All it needs to work is ssh access and python installed on the target machine. After that, you simply cd to playbook directory and run ansible-playbook bgp.yml
and watch everything config by itself.
To make everything work, Ansible will install the following packages:
- vlan
- iptables-persistent
- bird
- ifupdown
It's important to mention that this playbook is prepared with ifupdown, iptables, and Debian in mind. If you use netplan.io (now default in Ubuntu) to manage your interfaces, you should either uninstall netplan (after you run the playbook, you would end up with no connectivity on your server otherwise) or bend the playbook to configure netplan instead. To uninstall netplan, follow these steps:
- in
/etc/default/grub
edit this line
GRUB_CMDLINE_LINUX="netcfg/do_not_use_netplan=true"
- update-grub
- rm /etc/netplan/*
- apt purge netplan.io
- make sure you have ifupdown installed & configured
Playbook description for dummies
You can get the playbook here. You'll need to make a couple of changes based on your current setup. Let's get to it.
ansible.cfg
By default Ansible will look at /etc/ansible/hosts
for hosts. We'll want to change that, same as path to roles. A little dictionary:
- inventory - a set of servers you'll want to configure by Ansible
- roles - what exactly you wish to happen there
[defaults]
callback_whitelist = profile_tasks
stdout_callback = yaml
roles_path = your/path/to_playbook/roles/
inventory = your/path/to_playbook/hosts
hosts
In the example, we call the set of hosts we want to configure with bird "bgp". It's generally not advised to use root as ansible_user as it can be unsafe. It's also the fastest way to get the work done, however. If you ssh into your machines using a password, you can add the ansible_password
parameter, but you'll also need to install sshpass in addition to Ansible. This is considered unsafe, and you should definitely use passwordless authentication by setting ansible_ssh_private_key_file=path/to/private_key
.
[bgp]
Server1 ansible_port=61234 ansible_user=root ansible_host={URL or IP address}
Server2 ansible_port=61234 ansible_user=root ansible_host={URL or IP address}
host_vars/server1/vars
This file contains the actual configuration used for interfaces and bird. You will have to contact support to get all the information, such as datapacket_asn_ip or vlan_id. You'll also have to create and modify a copy of this file for every single server in your hosts
file.
# you can learn vlan_id from support
vlan_id: 3502
# set to your IP address, gateway and name, set on your main network interface
ip_address: 192.0.2.0/24
gateway: 192.0.2.254
main_iface: enp2s0f0
# asn of datapacket - you don't need to change this
datapacket_asn: 60068
# asn you wish to propagate
customer_asn: 64511
# a unique identifier of our router - learn this from support
datapacket_asn_ip: 198.51.100.0
# unique identifier of your router
customer_asn_ip: 192.168.101.111
# prefix you want to propagate
networks_to_propagate: "203.0.113.0/24"
propagated_ip_assign:
- 203.0.113.100/24
To better understand the settings, you should take a look at the files that will actually be changed. Namely:
- /etc/network/interfaces
- /etc/iproute2/rt_tables
- /etc/iptables/rules.v4
- /etc/bird/bird.conf
- /etc/sysctl.conf
You can also use following templates to manually configure each component.
Interfaces
ifupdown package stores network configuration in /etc/network/interfaces
. We recommend bonding your interfaces even if you are using a single uplink simply to save time in the future. It's not completely necessary, however.
As we are also setting up VLANs, you'll need to coordinate with our team to correctly set up the network side.
#loopback
auto lo
iface lo inet loopback
{% for ip in propagated_ip_assign %}
up ip a a dev lo brd + {{ ip }}
{% endfor %}
{% if main_iface == "bond0" %}
auto bond0
iface bond0 inet static
address {{ ip_address }}
gateway {{ gateway }}
dns-nameservers 8.8.8.8 8.8.4.4
dns-search datapacket.com
bond-xmit_hash_policy encap3+4
bond-mode 802.3ad
bond-miimon 100
bond-downdelay 200
bond-updelay 200
bond-slaves {{ bond_slave1 }} {{ bond_slave2 }}
auto {{ bond_slave1 }}
iface {{ bond_slave1 }} inet manual
bond-master bond0
auto {{ bond_slave2 }}
iface {{ bond_slave2 }} inet manual
bond-master bond0
{% else %}
auto {{ main_iface }}
iface {{ main_iface }} inet static
address {{ ip_address }}
gateway {{ gateway }}
dns-nameservers 8.8.8.8 8.8.4.4
dns-search datapacket.com
{% endif %}
#----------BGP vlan {{ vlan_id }}
auto {{ main_iface }}.{{ vlan_id }}
iface {{ main_iface }}.{{ vlan_id }} inet static
address {{ customer_asn_ip }}/31
vlan-raw-device {{ main_iface }}
up ip r a table vlan{{ vlan_id }}_int default via {{ datapacket_asn_ip }}
up ip rule add priority 70 from {{ datapacket_asn_ip }}/31 table vlan{{ vlan_id }}_int
up ip rule add fwmark 2 table vlan{{ vlan_id }}_int
down ip rule del priority 70 from {{ datapacket_asn_ip }}/31 table vlan{{ vlan_id }}_int
down ip rule del fwmark 2 table vlan{{ vlan_id }}_int
{% if vlan_id_secondary is defined %}
#----------BGP vlan {{ vlan_id_secondary }}
auto {{ main_iface }}.{{ vlan_id_secondary }}
iface {{ main_iface }}.{{ vlan_id_secondary }} inet static
address {{ customer_asn_ip_secondary }}/31
vlan-raw-device {{ main_iface }}
up ip r a table vlan{{ vlan_id_secondary }}_int default via {{ datapacket_asn_ip_secondary }}
up ip rule add priority 71 from {{ datapacket_asn_ip_secondary }}/31 table vlan{{ vlan_id_secondary }}_int
up ip rule add fwmark 3 table vlan{{ vlan_id_secondary }}_int
down ip rule del priority 71 from {{ datapacket_asn_ip_secondary }}/31 table vlan{{ vlan_id_secondary }}_int
down ip rule del fwmark 3 table vlan{{ vlan_id_secondary }}_int
{% endif %}
rt_tables
If you're not familiar with /etc/iproute2/rt_tables
don't worry about it too much. My favourite description of it comes from stackexchange:
That's just the flat file used by the iproute2 utilities which translates user friendly names given on the command line into integer values used by the kernel. It's just to maintain a consistent mapping of "this name is this integer"
#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
#1 inr.ruhep
250 vlan{{ vlan_id }}_int
Iptables
Iptables is probably the most well-known tool for IP packet filtering. If you use any other, you'll have to set it up equivalently. The rules are stored at /etc/iptables/rules.v4
. For instant apply do iptables-restore /etc/iptables/rules.v4
.
# Generated by iptables-save v1.6.1
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
# Completed
# Generated by iptables-save v1.6.1
*filter
:INPUT ACCEPT [137377297:94770129917]
:FORWARD ACCEPT [768366:46389689]
:OUTPUT ACCEPT [146520452:85216948160]
-A INPUT -i lo -j ACCEPT
COMMIT
# Completed
*mangle
:PREROUTING ACCEPT [309818506:257622963617]
:INPUT ACCEPT [309818506:257622963617]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [152148313:642044956423]
:POSTROUTING ACCEPT [152148313:642044956423]
-A INPUT -i {{ main_iface }}.{{ vlan_id }} -j MARK --set-mark 2
-A INPUT -j CONNMARK --save-mark
-A OUTPUT -j CONNMARK --restore-mark
COMMIT
BIRD
All the work we've done so far leads to configuring BIRD for BGP. This config is stored at /etc/bird/bird.conf.
Make sure to enable bird service to autostart: systemctl enable bird.service
BIRD uses an internal routing table. We use import and export rules to keep it in sync with kernel's routing table.
log syslog { debug, trace, info, remote, warning, error, auth, fatal, bug };
define myas = {{ customer_asn }};
define myip = {{ customer_asn_ip }};
{% if vlan_id_secondary is defined %}
define myip2 = {{ customer_asn_ip_secondary }};
{% endif %}
router id {{ customer_asn_ip }};
listen bgp address myip;
# Change this into your BIRD router ID. It's a world-wide unique identification
# of your router, usually one of router's IPv4 addresses.
protocol device {
scan time 60;
}
protocol direct {
export none;
import where net ~ [ {{ networks_to_propagate }} ] ;
}
protocol kernel {
import none; # Default is import all
export all; # Default is export none
}
template bgp PEERS {
local myip as myas;
neighbor {{ datapacket_asn_ip }} as {{ datapacket_asn }};
import none;
export all;
import limit 1000 action restart;
bfd on;
interpret communities off;
connect retry time 5;
}
protocol bgp BGP1 from PEERS {
import none;
export all;
next hop self;
}
{% if vlan_id_secondary is defined %}
template bgp PEERS2 {
local myip2 as myas;
neighbor {{ datapacket_asn_ip_secondary }} as {{ datapacket_asn }};
import none;
export all;
import limit 1000 action restart;
bfd on;
interpret communities off;
connect retry time 5;
}
protocol bgp BGP2 from PEERS2 {
import none;
export all;
next hop self;
}
{% endif %}
sysctl
With /etc/sysctl.conf
you can configure various Linux networking and system settings. There's a bit more settings in the file itself, but this part is important for you.
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.lo.rp_filter = 0
net.ipv4.conf.{{ main_iface }}/{{ vlan_id }}.rp_filter = 0
net.ipv4.conf.all.arp_announce = 1
net.ipv4.conf.{{ main_iface }}/{{ vlan_id }}.arp_announce = 1
net.ipv4.ip_forward = 1
When you run ansible playbook and perform a reboot, everything should work as expected. You should be able to see with birdc show protocols
something along the lines of:
BGP1 in state Established
And that's it! BIRD should now be able to propagate your AS using BGP without any problems. Of course, if you run into any trouble, don't hesitate to contact our support.