layout: page title: Vorstellung meine bird2 Konfiguration für mein Lab AS permalink: /blog/lab-de/ keywords: dn42,ipv6,bird,bird2,rpki,roa,filter,configuration,bfd,templates,filter functions in bird,bgp communities,bgp large communities description: Hier stelle ich meine dn42-Konfiguration vor, die ich in meinem Lab verwende. Dies ist ein IPv6-only Netzwerk. Es wird der Routing-Daemon bird verwendet. Die Konfiguration enthält Funktionen, verschiedene Filter und Tipps. lang: de date: 2023-01-31 00:00:00 +0100 updated: 2024-06-30 00:00:00 +0100
Ich habe im dn42 ein Lab AS. AS4242422924. Dies ist ein IPv6-only Netzwerk, welches nur auf einem Knoten ist. Ich bentzte es einernseits zum Monitoring meines Hauptnetzwerkes und einernseites um Verbindungen mit GNS3 herzustellen.
Ich bentzte als BGP Daemon bird2. Heute möchte ich meine bird2 Konfiguration "vorstellen".
Ich habe versucht die Konfiguration etwas modularer aufzubauen. Daher besteht die Konfiguration aktuell aus fünf Dateien. bird.conf:
log syslog { warning, error, fatal };
log "/var/log/bird/remote.log" { remote };
log "/var/log/bird/bugs.log" { bug };
log "/var/log/bird/trace.log" { trace };
log "/var/log/bird/debug.log" { debug };
log "/var/log/bird/info.log" { info };
include "header.conf";
/* Set router details */
router id RID;
hostname HOSTNAME;
/* Set tables */
ipv6 table dn42;
/* Utility functions */
function is_self_net() -> bool {
return net ~ OWNNETSET;
}
function is_valid_network() -> bool {
return net ~ [
fd00::/8{44,64}
];
}
/* ROA */
roa6 table dnroa;
# import roa from file
protocol static {
roa6 {
table dnroa;
};
include "/var/lib/bird/dn42-roa6.conf";
}
include "filters.conf";
/* Export own net */
protocol static ownnet {
route OWNNET unreachable;
ipv6 {
table dn42;
};
}
/* Kernel */
protocol kernel {
metric 500;
ipv6 {
table dn42;
export where kernel_export();
};
}
/* make unknown routes unreachable */
protocol static {
route fd00::/8 unreachable;
ipv6 {
table dn42;
};
}
protocol bfd { }
protocol device { }
include "templates.conf";
include "peers/*.conf";
log syslog { warning, error, fatal };
log "/var/log/bird/remote.log" { remote };
log "/var/log/bird/bugs.log" { bug };
log "/var/log/bird/trace.log" { trace };
log "/var/log/bird/debug.log" { debug };
log "/var/log/bird/info.log" { info };
Damit nicht alles in das Syslog geschrieben wird und damit unübersichtlich wird, lasse ich nur Fehlermeldungen und Warnungen in Syslog schreiben. Andere Dinge lasse ich in Logdateien schreiben.
include "header.conf";
Danach importiere ich header.conf
.
define OWNAS = 4242422924;
define OWNIP = fd00:8e13:ce5d::1;
define OWNNET = fd00:8e13:ce5d::/48;
define OWNNETSET = [fd00:8e13:ce5d::/48+];
define RID = 42.0.29.24;
define HOSTNAME = "srv.dn42-lab.de";
define BANDWIDTH = 25;
define REGION_GEO = 41;
define REGION_COUNTRY = 1276;
In dieser sind Lediglich Variablen definiert. Darunter meine AS-Nummer, meine IP-Adresse, mein IP-Prefix, meine Router ID, mein Hostname und Parameter für die BGP communities.
In REGION_GEO
und REGION_COUNTRY
stehen die Regionscode gemäß des Wikis.
/* Set router details */
router id RID;
hostname HOSTNAME;
Diese teile ich in diesen Zeilen bird2 mit. Der Hostname muss nicht zwingend in bird stehen. Man kann es allerdings so einstellen, dass man über BGP den Hostname bekanntgibt. Ich dachte, dass ich dieses Feature mal ausprobieren wollte.
/* Set tables */
ipv6 table dn42;
Normallerweise bentzt bird die Tabellen master4 für IPv4 und master6 für IPv6 Routen, jedoch wollte ich eine sauberere Trennung und speichere alle dn42 Routen in der Tabelle dn42
.
/* Utility functions */
function is_self_net() -> bool {
return net ~ OWNNETSET;
}
function is_valid_network() -> bool {
return net ~ [
fd00::/8{44,64}
];
}
Diese zwei Funktionen habe ich aus dem Wiki übernommen. is_self_net
überprüft, ob ein Prefix im eigenen Netz liegt und mit is_valid_network
kann man alle nicht IPv6-ULAs aussortieren. Des Weiteren wird auch eine maximale und eine minimale Prefix Länge gesetzt: /44
bis /64
.
/* ROA */
roa6 table dnroa;
# import roa from file
protocol static {
roa6 {
table dnroa;
};
include "/var/lib/bird/dn42-roa6.conf";
}
Danach erstelle ich eine ROA Tabelle mit dem Namen dnroa
. Von der ROA Datei /var/lib/bird/dn42-roa6.conf
importiert bird alles. Dabei verwende ich dn42-roagen zur Erzeugung der ROA Dateien.
include "filters.conf";
Dann binde ich die filters.conf
ein. In dieser stehen meine Import-/Export Filter.
include "community_filter.conf";
define ASN_BLACKLIST = [
0
];
function kernel_export() {
krt_prefsrc = OWNIP;
accept;
}
function reject_invalid_roa() {
if (roa_check(dnroa, net, bgp_path.last) != ROA_VALID) then {
print "Reject: ROA failed|", net, "|", bgp_path;
reject;
}
}
function reject_blacklisted()
int set blacklist;
{
blacklist = ASN_BLACKLIST;
if ( bgp_path ~ blacklist ) then {
print "Reject: blacklisted ASN|", bgp_path;
reject;
}
}
function honor_graceful_shutdown() {
if (65535, 0) ~ bgp_community then {
bgp_local_pref = 0;
}
}
function add_own_communities(int link_type) {
bgp_large_community.add((OWNAS, 5, link_type));
}
function dn_import_filter(int link_latency; int link_bandwidth; int link_crypto; int link_type) {
if ( net.type != NET_IP6 ) then {
print "Reject: non-IPv6 on IPv6 Channel|", net, "|", bgp_path;
reject;
}
if ( ! is_valid_network() ) then {
print "Reject: invalid network|", net, "|", bgp_path;
reject;
}
if ( is_self_net() ) then {
print "Reject: export our network|", net, "|", bgp_path.first;
reject;
}
if ( bgp_path.len > 25 ) then {
print "Reject: AS path too long|", net, "|", bgp_path;
reject;
}
reject_blacklisted();
reject_invalid_roa();
if (bgp_path.len = 1) then
bgp_local_pref = bgp_local_pref + 500;
honor_graceful_shutdown();
update_flags(link_latency, link_bandwidth, link_crypto);
add_own_communities(link_type);
accept;
}
function dn_export_filter(int link_latency; int link_bandwidth; int link_crypto; int link_type) {
if (source !~ [RTS_STATIC, RTS_BGP]) then
reject;
if (bgp_path.last != bgp_path.first) then
reject;
update_flags(link_latency, link_bandwidth, link_crypto);
update_region();
bgp_med = 0;
bgp_med = bgp_med + ( ( 4 - ( link_crypto - 30 ) ) * 600 );
bgp_med = bgp_med + ( ( 9 - ( link_bandwidth - 20 ) ) * 100);
bgp_med = bgp_med + ( ( link_latency - 1) * 300);
bgp_med = bgp_med + ( link_type - 1 ) * 10;
accept;
}
function dn_export_collector() {
if (source !~ [RTS_STATIC, RTS_BGP]) then
reject;
update_region();
accept;
}
Die Filter Datei bindet wiederum community_filter.conf
ein. Dabei handelt es sich um eine leicht-modifizierte Kopie aus dem Wiki.
function update_latency(int link_latency) {
bgp_community.add((64511, link_latency));
if (64511, 9) ~ bgp_community then { bgp_community.delete([(64511, 1..8)]); }
else if (64511, 8) ~ bgp_community then { bgp_community.delete([(64511, 1..7)]); }
else if (64511, 7) ~ bgp_community then { bgp_community.delete([(64511, 1..6)]); }
else if (64511, 6) ~ bgp_community then { bgp_community.delete([(64511, 1..5)]); }
else if (64511, 5) ~ bgp_community then { bgp_community.delete([(64511, 1..4)]); }
else if (64511, 4) ~ bgp_community then { bgp_community.delete([(64511, 1..3)]); }
else if (64511, 3) ~ bgp_community then { bgp_community.delete([(64511, 1..2)]); }
else if (64511, 2) ~ bgp_community then { bgp_community.delete([(64511, 1..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)]); }
else if (64511, 22) ~ bgp_community then { bgp_community.delete([(64511, 23..29)]); }
else if (64511, 23) ~ bgp_community then { bgp_community.delete([(64511, 24..29)]); }
else if (64511, 24) ~ bgp_community then { bgp_community.delete([(64511, 25..29)]); }
else if (64511, 25) ~ bgp_community then { bgp_community.delete([(64511, 26..29)]); }
else if (64511, 26) ~ bgp_community then { bgp_community.delete([(64511, 27..29)]); }
else if (64511, 27) ~ bgp_community then { bgp_community.delete([(64511, 28..29)]); }
else if (64511, 28) ~ bgp_community then { bgp_community.delete([(64511, 29..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)]); }
else if (64511, 32) ~ bgp_community then { bgp_community.delete([(64511, 33..34)]); }
else if (64511, 33) ~ bgp_community then { bgp_community.delete([(64511, 34..34)]); }
}
function update_flags(int link_latency; int link_bandwidth; int link_crypto) {
if link_bandwidth > BANDWIDTH then link_bandwidth = BANDWIDTH;
update_latency(link_latency);
update_bandwidth(link_bandwidth);
update_crypto(link_crypto);
}
function update_region() {
if is_self_net() then {
bgp_community.add((64511, REGION_GEO));
bgp_community.add((64511, REGION_COUNTRY));
}
}
Ich habe dabei die unnötigen Variables aus der Funktion update_flags
entfernt sowie die Funktion update_region
hinzugefügt. Sollte ich eigene Routen exportieren, versehe ich diese mit einer Region Community.
define ASN_BLACKLIST = [
0
];
Danach definiere ich eine ASN Blacklist für den Fall der Fälle. Da bird mindentes einen Eintrag verlangt, habe ich die AS0 eingetragen.
function kernel_export() {
krt_prefsrc = OWNIP;
accept;
}
Die Funktion kernel_export
ist der Export Filter, was alles in den Kernel exportiert werden soll. In diesem Fall soll alles in den Kernel exportiert werden und meine IP-Adresse soll als Source IP verwendet werden.
function reject_invalid_roa() {
if (roa_check(dnroa, net, bgp_path.last) != ROA_VALID) then {
print "Reject: ROA failed|", net, "|", bgp_path;
reject;
}
}
Die Funktion reject_invalid_roa
lehnt invalide und unbekannte ROAs ab und schreibt ein Log im Format Reject: ROA failed|IPv6-Adresse|BGP Pfad
. Die Standard Funktion aus dem Wiki schreibt lediglich die Ursprung des nicht validen ROAs auf. Jedoch ist der AS Pfad auch sehr interessant, denn daran kann man erkennen, wer die nicht valide ROA an einen exportiert hat.
function reject_blacklisted()
int set blacklist;
{
blacklist = ASN_BLACKLIST;
if ( bgp_path ~ blacklist ) then {
print "Reject: blacklisted ASN|", bgp_path;
reject;
}
}
Die Funktion reject_blacklisted
prüft, ob eine geblacklistete ASN im AS Pfad vorkommt. Wenn ja, wird die Route abgelehnt und eine Log im Format Reject: blacklisted ASN|AS Pfad
geschrieben.
function honor_graceful_shutdown() {
if (65535, 0) ~ bgp_community then {
bgp_local_pref = 0;
}
}
Dies ist eine Kopie aus dem BGP Filter Guide. Falls ein Peer einen Graceful Shutdown sendet, werden allen Routen die Community (65535, 0)
angefügt. Man sollte nun schnell andere Routen finden. Mit bgp_local_pref = 0
teilen wir bird mit, dass er die Route vermeiden soll. Die local_pref ist ein Entscheidungskreterium bei der Routenauswahl, welches noch vor der AS Pfad Länge evaluiert wird.
function add_own_communities(int link_type) {
bgp_large_community.add((OWNAS, 5, link_type));
}
Des Weiteren habe ich noch eine eigene Community, welche ich an Routen abhäfte. Da ich eine 32-bit ASN habe, muss ich Large Communities verwenden. Je nachdem, wie die Verbindung zum anderen Peer hergestellt wird (physische Verbindung, IXP, Tunnel, ...) wird eine andere Community angehäftet.
function dn_import_filter(int link_latency; int link_bandwidth; int link_crypto; int link_type) {
Der Import Filter ist etwas komplexer.
if ( net.type != NET_IP6 ) then {
print "Reject: non-IPv6 on IPv6 Channel|", net, "|", bgp_path;
reject;
}
Als erstes wird alles abgelehnt, was keine IPv6 ist.
if ( ! is_valid_network() ) then {
print "Reject: invalid network|", net, "|", bgp_path;
reject;
}
Danach wird alles abgelehnt, was nicht in das dn42 Netz gehört oder wo der Prefix zu groß oder zu klein ist.
if ( is_self_net() ) then {
print "Reject: export our network|", net, "|", bgp_path.first;
reject;
}
Für mein Netzwerk kümmer ich mich. Daher muss ich dieses nicht von fremden Peer importieren.
if ( bgp_path.len > 25 ) then {
print "Reject: AS path too long|", net, "|", bgp_path;
reject;
}
Danach lehne ich zu Lange AS Pfade ab.
reject_blacklisted();
reject_invalid_roa();
Dann rufe ich alle Funktionen von oben auf und lehne Routen mit geblacklisteten ASNs ab, nicht valide ROAs.
if (bgp_path.len = 1) then
bgp_local_pref = bgp_local_pref + 500;
Sollte ich direkt mit einem Peer verbunden sein, möchte ich die Route immer bevorzugen. Daher erzwinge ich dies mit local_pref.
honor_graceful_shutdown();
update_flags(link_latency, link_bandwidth, link_crypto);
add_own_communities(link_type);
accept;
Danach prüfe ich auf Graceful Shutdown und markiere die Routen mit einer Community. Zum Schluss akzeptiere ich sie.
function dn_export_filter(int link_latency; int link_bandwidth; int link_crypto; int link_type) {
if (source !~ [RTS_STATIC, RTS_BGP]) then
reject;
Alle Routen, welche ich nicht selber via BGP bekommen habe oder statisch festgelegt habe, sollen nicht exportiert werden.
if (bgp_path.last != bgp_path.first) then
reject;
Da es ein Lab AS ist, möchte ich nur bedingt als Transit auftreten und exportiere daher nur Transit Routen zu meinen direkten Peers.
Es gibt einen Unterschied zwsichen bgp_path.last != bgp_path.first
und bgp_path.len = 1
. Es gibt eine Technik seine eigene Routen "unbeliebter" zu machen. Diese besteht im ASN prepending. Dadurch hängt man deine eigene ASN mehrfach in den AS Pfad. Dadurch wird dieser länger und unbeliebter. bgp_path.len = 1
erkennt direkte Peer ohne ASN prepending und bgp_path.last != bgp_path.first
erkennt auch direkt Peers mit ASN prepending. Wenn jemand eine Routen künstlich verlängert und dadurch unbeliebter macht, hat dies sicherlich schon einen Sinn. Für Prependete Pfade setzte ich daher keine höhrere local_pref. Jedoch möchte ich als Transit für direkte Peers fungieren - unabhängig vom Prepending. Das Prepending sende ich ja auch wiederum an meine Peers. Dadurch ist der Pfad bei denen genauso unbeliebt wie bei mir.
update_flags(link_latency, link_bandwidth, link_crypto);
update_region();
Auch hier markiere ich die Routen entsprechend den Communities im Wiki.
bgp_med = 0;
bgp_med = bgp_med + ( ( 4 - ( link_crypto - 30 ) ) * 600 );
bgp_med = bgp_med + ( ( 9 - ( link_bandwidth - 20 ) ) * 100);
bgp_med = bgp_med + ( ( link_latency - 1) * 300);
bgp_med = bgp_med + ( link_type - 1 ) * 10;
Hier lasse ich semiautomatisch das bgp_med
berechnen. Wenn zwei Autonome System an zwei Stellen miteinander verbunden sind, ist natürlich die Frage, wohin sollen die Anfragen gesendet werden. Dafür gibt es das bgp_med
. Wenn es zwei Routen vom selben AS gibt, entscheidet das bgp_med
, welche Route verwendet wird. Umso kleiner das bgp_med
, umso besser die Route.
Hier ist die Reihenfolge:
accept;
Danach exportiere ich die Route.
function dn_export_collector() {
if (source !~ [RTS_STATIC, RTS_BGP]) then
reject;
update_region();
accept;
}
Einem Route Collector möchte ich möglichst alle Routen unverändert senden, welche ich auch habe. Daher exportiere ich alle Routen, sofern ich sie statische oder über BGP bekommen habe. Die Region Community füge ich auch hinzu. Die Default Route exportiere ich auch zum Collector.
/* Export own net */
protocol static ownnet {
route OWNNET unreachable;
ipv6 {
table dn42;
};
}
Nachdem bird die Filter geladen hat, setze ich eine statische Route für mein eigenes Netzwerk. Dies bewirkt, dass bird nun eine Route zu meinem Netzwerk kennt und sie zu anderen exportiert.
/* Kernel */
protocol kernel {
metric 500;
ipv6 {
table dn42;
export where kernel_export();
};
}
Nun exportiere ich die Routen in den Kernel und damit in die Forwarding Table. Dabei bentzte ich den Filter, welchen ich zuvor definiert hatte. Die Routen bekommen alle die Metric 500 in der Forwarding Table. Dies mache ich, da es Probleme mit direkten Routen gab. Setzt man keine hohe Metric werden die Routen über BGP bevorzugt, obwohl man zum Beispiel wegen einer VPN Verbindung eine direkte Route kennt.
/* make unknown routes unreachable */
protocol static {
route fd00::/8 unreachable;
ipv6 {
table dn42;
};
}
Hier definiere ich die dn42 default route. Dies bewirkt, dass ich "unreachable" via ICMP zurückgebe falls ich eine Route nicht weiß, sie aber trotzdem über mich versucht wird zu erreichen.
protocol bfd { }
protocol device { }
Danach aktiviere ich zwei "spezielle" Protokolle. bfd wird dafür bentzt, um im Milisekundenbereich Verbindungsstörungen zu erkennen. Das device Protokoll wird bentzt, damit bird die IP-Adressen meiner lokalen Interfaces kennt.
include "templates.conf";
Danach importiere ich die Templates, welche ich zum Peering verwende.
template bgp dnpeer {
local as OWNAS;
enforce first as on;
graceful restart on;
long lived graceful restart on;
enable extended messages on;
advertise hostname on;
prefer older on;
# defaults
enable route refresh on;
interpret communities on;
default bgp_local_pref 100;
ipv6 {
mandatory on;
table dn42;
import table;
import limit 1000 action restart;
import none;
export none;
};
}
template bgp routecollector from dnpeer {
multihop;
ipv6 {
add paths tx;
};
}
local as OWNAS;
Damit teile ich bird mit, dass meine ASN Nummer für ein Peering verwendet werden soll.
enforce first as on;
Diese Option bewirkt, dass im AS Pfad von einem Peer, die ASN von Peer zuerst stehen muss. Dies ist außer bei Route Servern eigentlich immer der Fall. Um sich allerdings vor Fehlkonfigurationen seines Peer zu schützen, kann man diese Option aktivieren.
graceful restart on;
long lived graceful restart on;
enable extended messages on;
...
prefer older on;
Danach aktiviere einige Funktionen. Diese habe ich in Beginner Tips versucht zu erklären.
advertise hostname on;
Oben hatte ich ja meinen Hostname festgelegt. Damit dieser via BGP dem Peer mitgeteilt wird, muss diese Option aktiviert sein.
# defaults
enable route refresh on;
interpret communities on;
default bgp_local_pref 100;
Hier setzte ich zur Sicherheit einige Parameter auf ihren Default Wert. Dies ist eigentlich unnötig.
Im Channel sind einige Funktionen, welche im Wiki nicht sind.
import table;
Dies hatte ich auch bereits in Beginner Tips erklärt.
import limit 1000 action restart;
Falls der Peer über 1000 Prefixe exportiert, wird die Sitzung neugestartet. Eine Alternative ist die Aktion block
. Diese blockiert alle weiteren Imports anstatt die BGP Sitzung neuzustarten.
mandatory on;
Damit teile ich bird mit, dass der Peer zwingend IPv6 unterstützten muss. Wenn er es nicht unterstützt, wird keine BGP Sitzung hergestellt.
table dn42;
Diese Zeile bewirkt, dass alle Routen aus der Tabelle dn42
im- bzw. exportiert werden und nicht aus der Standardtabelle master6
.
import none;
export none;
Sollten noch keine Parameter angepasst sein, soll weder etwas im- noch exportiert werden. Für jeden Peer lege ich einzeln entsprechende Parameter wie Latenz oder Bandbreite fest.
template bgp routecollector from dnpeer {
multihop;
ipv6 {
add paths tx;
};
}
Die Vorlage für den Route Collector "erbt" von der dnpeer
Vorlage. Hier wird noch die Option multihop;
aktiviert, welche bewirkt, dass man keinen Tunnel zum Route Collector herstellen muss, sondern ihn "normal" erreichen kann. Die Option add paths tx;
bewirkt, dass alle Routen (nicht nur die besten) zum Collector exportiert werden.
include "peers/*.conf";
Danach lade ich meine Peers.
Folgendes ist eine Konfiguration für die BGP Sitzung mit meinem Hauptnetzwerk:
protocol bgp bandura from dnpeer {
neighbor fe80::2923%bandura as 4242422923;
bfd graceful;
ipv6 {
import where dn_import_filter(1, 25, 34, 1);
export where dn_export_filter(1, 25, 34, 1);
};
}
Mit dn_import_filter(1, 25, 34, 1)
und dn_export_filter(1, 25, 34, 1)
lege ich beispielsweise Parameter für die BGP communities fest und aktiviere den Import und Export.
Mit bfd graceful;
aktiviere ich BfD, welche standardmäßig für BGP Sitzungen deaktiviert ist.
Changelog:
30.06.2024 bird erfordert nun bei Funktionen, welche Werte zurückgeben, die explizite Angabe eines Return Types mittels -> [return type]
. Die zwei Funktionen is_self_net
und is_valid_network
wurden entsprechend angepasst. Ohne Angabe des Rückwertetypes wird eine Warnung wie Inferring function [function name] return type from its return value: [return type]
ausgegeben.