Base BIRD setup

This commit is contained in:
Théophile Bastian 2025-03-03 23:05:29 +01:00
parent 5c114b7602
commit 46b10f53af
15 changed files with 335 additions and 9 deletions

View file

@ -1,10 +1,3 @@
---
nullmailer_relay: srv-mail-infra.echirolles.fr
nullmailer_dest_addr: admininfrastructure@echirolles.fr
nullmailer_defaultdomain: echirolles.fr
borgmatic_server: 'ssh://borgbackup@pbs1.echirolles.fr/.'
firewall_allow_checkmk: true
checkmk_servers:
- 10.35.40.25
my_asn: 4242422112
my_net: 'fd73:ea37:6131::/48'

View file

@ -0,0 +1,2 @@
---
location_id: 16

View file

@ -1 +1,4 @@
---
router_ip6: 'fd73:ea37:6131:1600::1'
bird_region: 41
bird_country: 1250

View file

@ -4,6 +4,14 @@ soldanelle.tobast.dn42 ansible_host=2001:912:1ac0:2fb0:be24:11ff:fef3:1906
[fully_managed]
soldanelle.tobast.dn42
## Geo groups
[fr-gnb1]
soldanelle.tobast.dn42
## Functionnal groups
[edge_router]
soldanelle.tobast.dn42
[nginx]
## Groups configuration

View file

@ -0,0 +1,5 @@
---
- hosts: edge_router
roles:
- bird_base
- bird_edge

14
roles/bird_base/README.md Normal file
View file

@ -0,0 +1,14 @@
# Bird base
Base installation or BIRD routing daemon. Does not configure further routing.
## Variables
* `bird_region`: region, as of DN42 BGP communities
* `bird_country`: country, as ISO-3166 country code + 1000
### Shared between roles
* `router_ip6`: main ipv6 address of the router
* `location_id`: typically defined in the geographical group. ID of this
location, typically in 16-ff.

View file

@ -0,0 +1,35 @@
---
- name: Install BIRD
apt:
name: bird2
- name: Create configuration directiries
file:
path: "/etc/bird/{{ item }}"
state: directory
owner: root
group: bird
mode: 0750
loop:
- conf.d
- filter.d
- name: Install configuration templates
template:
src: '{{ item }}.conf.j2'
dest: '/etc/bird/{{ item }}.conf'
loop:
- header
- bird
- filter.d/10-base_filters
- name: Fetch ROA6 file (FIXME)
get_url:
url: 'https://dn42.burble.com/roa/dn42_roa_bird2_6.conf'
dest: '/etc/bird/dn42_roa_6.conf'
- name: Etckeeper - commit
import_role:
name: etckeeper_commit
vars:
etckeeper_reason: Configure base BIRD

View file

@ -0,0 +1,64 @@
{{ ansible_managed | comment }}
include "/etc/bird/header.conf";
# Configure logging
log syslog all;
# log "/var/log/bird.log" { debug, trace, info, remote, warning, error, auth, fatal, bug };
# Set router ID. It is a unique identification of your router, usually one of
# IPv4 addresses of the router. It is recommended to configure it explicitly.
# router id 198.51.100.1;
router id MY_RID;
hostname HOSTNAME;
#####
## END HEADER
#####
function is_self_net() {
return net ~ MY_NET;
}
function is_within_self_net() {
return net ~ MY_NETSET;
}
function is_valid_network() {
return net ~ [fd00::/8{44,64}];
}
include "/etc/bird/filter.d/*.conf";
include "/etc/bird/conf.d/*.conf";
# The Device protocol is not a real routing protocol. It does not generate any
# routes and it only serves as a module for getting information about network
# interfaces from the kernel. It is necessary in almost any configuration.
protocol device {}
# The direct protocol is not a real routing protocol. It automatically generates
# direct routes to all network interfaces. Can exist in as many instances as you
# wish if you want to populate multiple routing tables with direct routes.
protocol direct {
disabled; # Disable by default
#ipv4; # Connect to default IPv4 table
#ipv6; # ... and to default IPv6 table
}
# The Kernel protocol is not a real routing protocol. Instead of communicating
# with other routers in the network, it performs synchronization of BIRD
# routing tables with the OS kernel. One instance per table.
protocol kernel kernel4 {
description "Kernel IPv4";
ipv4 { export all; };
}
protocol kernel kernel6 {
description "Kernel IPv6";
ipv6 { export all; };
}
protocol static static_exported_my_AS {
description "AS{{ my_asn }}";
route MY_NET unreachable;
ipv6 {};
}

View file

@ -0,0 +1,17 @@
{{ ansible_managed | comment }}
function is_martians() {
return net ~ [ 169.254.0.0/16+, 224.0.0.0/4+, 240.0.0.0/4+, 0.0.0.0/32{1,32}];
}
function is_rfc1918() {
return net ~ [ 172.16.0.0/12+, 192.168.0.0/16+, 10.0.0.0/8+ ];
}
function is_default () {
return net ~ [ ::/0 ];
}
function is_exportable() {
return net ~ MY_NETSET;
}

View file

@ -0,0 +1,11 @@
{{ ansible_managed | comment }}
define MY_ASN = {{ my_asn }};
define MY_IP = {{ router_ip6 }};
define MY_NET = {{ my_net }};
define MY_NETSET = [{{my_net}}+];
define MY_RID = 42.21.12.{{ location_id }};
define HOSTNAME = "{{ ansible_fqdn }}";
define REGION = {{ bird_region }};
define COUNTRY = {{ bird_country }};

View file

@ -0,0 +1,26 @@
---
- name: Create configuration directiries
file:
path: "/etc/bird/{{ item }}"
state: directory
owner: root
group: bird
mode: 0750
loop:
- bgp-peers.d
- name: Install configuration templates
template:
src: 'bird/{{ item }}.conf.j2'
dest: '/etc/bird/{{ item }}.conf'
loop:
- conf.d/30-bgp
- filter.d/25-community_filters
- filter.d/30-bgp_filters
- bgp-peers.d/00-dummy
- name: Etckeeper - commit
import_role:
name: etckeeper_commit
vars:
etckeeper_reason: Configure BIRD for edge routing

View file

@ -0,0 +1,4 @@
{{ ansible_managed | comment }}
# This page intentionnaly left blank
# (BIRD complains about empty glob otherwise)

View file

@ -0,0 +1,22 @@
{{ ansible_managed | comment }}
template bgp dn42_peer {
description "DN42 peering";
local as MY_ASN;
advertise hostname on;
enforce first as on;
prefer older on;
graceful restart on;
long lived graceful restart on;
enable extended messages on;
bfd graceful;
ipv6 {
next hop self;
};
}
include "/etc/bird/bgp-peers.d/*.conf";

View file

@ -0,0 +1,47 @@
#/etc/bird/community_filters.conf
function update_latency(int link_latency) {
bgp_community.add((64511, link_latency));
if (64511, 9) ~ bgp_community then { bgp_community.delete([(64511, 1..8)]); return 9; }
else if (64511, 8) ~ bgp_community then { bgp_community.delete([(64511, 1..7)]); return 8; }
else if (64511, 7) ~ bgp_community then { bgp_community.delete([(64511, 1..6)]); return 7; }
else if (64511, 6) ~ bgp_community then { bgp_community.delete([(64511, 1..5)]); return 6; }
else if (64511, 5) ~ bgp_community then { bgp_community.delete([(64511, 1..4)]); return 5; }
else if (64511, 4) ~ bgp_community then { bgp_community.delete([(64511, 1..3)]); return 4; }
else if (64511, 3) ~ bgp_community then { bgp_community.delete([(64511, 1..2)]); return 3; }
else if (64511, 2) ~ bgp_community then { bgp_community.delete([(64511, 1..1)]); return 2; }
else return 1;
}
function update_bandwidth(int link_bandwidth) {
bgp_community.add((64511, link_bandwidth));
if (64511, 21) ~ bgp_community then { bgp_community.delete([(64511, 22..29)]); return 21; }
else if (64511, 22) ~ bgp_community then { bgp_community.delete([(64511, 23..29)]); return 22; }
else if (64511, 23) ~ bgp_community then { bgp_community.delete([(64511, 24..29)]); return 23; }
else if (64511, 24) ~ bgp_community then { bgp_community.delete([(64511, 25..29)]); return 24; }
else if (64511, 25) ~ bgp_community then { bgp_community.delete([(64511, 26..29)]); return 25; }
else if (64511, 26) ~ bgp_community then { bgp_community.delete([(64511, 27..29)]); return 26; }
else if (64511, 27) ~ bgp_community then { bgp_community.delete([(64511, 28..29)]); return 27; }
else if (64511, 28) ~ bgp_community then { bgp_community.delete([(64511, 29..29)]); return 28; }
else return 29;
}
function update_crypto(int link_crypto) {
bgp_community.add((64511, link_crypto));
if (64511, 31) ~ bgp_community then { bgp_community.delete([(64511, 32..34)]); return 31; }
else if (64511, 32) ~ bgp_community then { bgp_community.delete([(64511, 33..34)]); return 32; }
else if (64511, 33) ~ bgp_community then { bgp_community.delete([(64511, 34..34)]); return 33; }
else return 34;
}
function update_flags(int link_latency; int link_bandwidth; int link_crypto)
int dn42_latency;
int dn42_bandwidth;
int dn42_crypto;
{
dn42_latency = update_latency(link_latency);
dn42_bandwidth = update_bandwidth(link_bandwidth) - 20;
dn42_crypto = update_crypto(link_crypto) - 30;
# replace 4 with your calculated bandwidth value
if dn42_bandwidth > 4 then dn42_bandwidth = 4;
return true;
}

View file

@ -0,0 +1,75 @@
{{ ansible_managed | comment }}
roa6 table dn42_roa6;
protocol static {
roa6 { table dn42_roa6; };
include "/etc/bird/dn42_roa_6.conf";
}
function dn42_update_geo() {
if is_self_net() then {
bgp_community.add((64511, REGION));
bgp_community.add((64511, COUNTRY));
}
}
function dn42_reject_roa() {
if (roa_check(dn42_roa6, net, bgp_path.last) != ROA_VALID) then {
print "Reject: bad ROA";
reject;
}
}
function bgp_dn42_import(
string import_type; int link_latency; int link_bw; int link_crypto
) {
if ! (import_type = "transit-backup" || import_type = "transit" || import_type="core") then {
print "bgp_import: parametre import_type invalide";
reject;
}
# weird routes are not accepted
if is_default() || is_martians() || is_rfc1918() then reject;
# Restrict networks to DN42
if (!is_valid_network()) then reject;
# own routes are not accepted
if is_self_net() then reject;
# in case of peering or transit, the routes within own network are not accepted.
if import_type = "transit" || import_type = "transit-backup" then {
if is_within_self_net() then reject;
}
dn42_reject_roa();
update_flags(link_latency, link_bw, link_crypto);
dn42_update_geo();
# bgp local preference by default
if import_type = "transit-backup" then bgp_local_pref=50;
if import_type = "transit" then bgp_local_pref=100;
krt_prefsrc = MY_IP;
accept;
}
function bgp_dn42_export(
int link_latency; int link_bw; int link_crypto
) {
if (source !~ [RTS_STATIC, RTS_BGP]) then reject;
# weird routes are not accepted
if is_default() || is_martians() || is_rfc1918() then reject;
# Restrict networks to DN42
if (!is_valid_network()) then reject;
update_flags(link_latency, link_bw, link_crypto);
dn42_update_geo();
accept;
}