Bird 2


All Pages > howto > Routing daemons and dn42 > Bird 2

This guide is similar to the normal Bird guide in that it provides you with help setting up the BIRD routing daemon, with the difference that this page is dedicated to versions 2.x.

Arch Linux

The extra/bird package in the arch repositories will usually have a relatively recent version and there is (usually) no need for a manual install over the usual # pacman -S bird.

Bird2 Version <2.0.8 / Debian

Please note, that Bird2 versions before 2.0.8 don't support IPv6 extended nexthops for IPv4 destinations (https://bird.network.cz/pipermail/bird-users/2020-April/014412.html). Additionally Bird2 before 2.0.8 cannot automatically update filtered bgp routes when an used RPKI source changes.

Debian 11 Bullseye delivers Bird 2.0.7. But you can use the Debian Bullseye backport-repository which provides version 2.0.8 (see https://backports.debian.org/Instructions/ for adding backports repository and install packages from the repository).

Example configuration

Please note: This example configuration is made for use with IPv4 and IPv6 (Really, there is no excuse not to get started with IPv6 networking! :) )

The default config location in bird version 2.x is /etc/bird.conf, but this may vary depending on how your distribution compiled bird.

When copying the configuration below onto your system, you will have to enter the following values in the file header:

################################################
#               Variable header                #
################################################

define OWNAS =  <OWNAS>;
define OWNIP =  <OWNIP>;
define OWNIPv6 = <OWNIPv6>;
define OWNNET = <OWNNET>;
define OWNNETv6 = <OWNNETv6>;
define OWNNETSET = [<OWNNET>+];
define OWNNETSETv6 = [<OWNNETv6>+];

################################################
#                 Header end                   #
################################################

router id OWNIP;

protocol device {
    scan time 10;
}

/*
 *  Utility functions
 */

function is_self_net() {
  return net ~ OWNNETSET;
}

function is_self_net_v6() {
  return net ~ OWNNETSETv6;
}

function is_valid_network() {
  return net ~ [
    172.20.0.0/14{21,29}, # dn42
    172.20.0.0/24{28,32}, # dn42 Anycast
    172.21.0.0/24{28,32}, # dn42 Anycast
    172.22.0.0/24{28,32}, # dn42 Anycast
    172.23.0.0/24{28,32}, # dn42 Anycast
    172.31.0.0/16+,       # ChaosVPN
    10.100.0.0/14+,       # ChaosVPN
    10.127.0.0/16{16,32}, # neonetwork
    10.0.0.0/8{15,24}     # Freifunk.net
  ];
}

roa4 table dn42_roa;
roa6 table dn42_roa_v6;

protocol static {
    roa4 { table dn42_roa; };
    include "/etc/bird/roa_dn42.conf";
};

protocol static {
    roa6 { table dn42_roa_v6; };
    include "/etc/bird/roa_dn42_v6.conf";
};

function is_valid_network_v6() {
  return net ~ [
    fd00::/8{44,64} # ULA address space as per RFC 4193
  ];
}

protocol kernel {
    scan time 20;

    ipv6 {
        import none;
        export filter {
            if source = RTS_STATIC then reject;
            krt_prefsrc = OWNIPv6;
            accept;
        };
    };
};

protocol kernel {
    scan time 20;

    ipv4 {
        import none;
        export filter {
            if source = RTS_STATIC then reject;
            krt_prefsrc = OWNIP;
            accept;
        };
    };
}

protocol static {
    route OWNNET reject;

    ipv4 {
        import all;
        export none;
    };
}

protocol static {
    route OWNNETv6 reject;

    ipv6 {
        import all;
        export none;
    };
}

template bgp dnpeers {
    local as OWNAS;
    path metric 1;

    ipv4 {
        import filter {
          if is_valid_network() && !is_self_net() then {
            if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then {
              print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last;
              reject;
            } else accept;
          } else reject;
        };

        export filter { if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then accept; else reject; };
        import limit 1000 action block;
    };

    ipv6 {   
        import filter {
          if is_valid_network_v6() && !is_self_net_v6() then {
            if (roa_check(dn42_roa_v6, net, bgp_path.last) != ROA_VALID) then {
              print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last;
              reject;
            } else accept;
          } else reject;
        };
        export filter { if is_valid_network_v6() && source ~ [RTS_STATIC, RTS_BGP] then accept; else reject; };
        import limit 1000 action block; 
    };
}


include "/etc/bird/peers/*";

Setting up peers

Please note: This section assumes that you've already got a tunnel to your peering partner setup.

First, make sure the /etc/bird/peers directory exists:

# mkdir -p /etc/bird/peers

Then for each peer, create a configuration file similar to this one:

/etc/bird/peers/<NEIGHBOR_NAME>.conf:

protocol bgp <NEIGHBOR_NAME> from dnpeers {
        neighbor <NEIGHBOR_IP> as <NEIGHBOR_ASN>;
}

protocol bgp <NEIGHBOR_NAME>_v6 from dnpeers {
        neighbor <NEIGHBOR_IPv6>%<NEIGHBOR_INTERFACE> as <NEIGHBOR_ASN>;
}

Due to the special link local addresses of IPv6, an interface has to be specified using the %<if> syntax if a link local address is used (Which is recommended)

BGP communities

Communities can be used to prioritize traffic based on different flags, in DN42 we are using communities to prioritize based on latency, bandwidth and encryption. It is really easy to get started with communities and we encourage all of you to get the basic configuration done and to mark your peerings with the correct flags for improved routing. More information can be found here.

Route Origin Authorization

Route Origin Authorizations should be used in BIRD to authenticate prefix announcements. These check the originating AS and validate that they are allowed to advertise a prefix.

RPKI / RTR for ROA

To use an RTR server for ROA information, replace this config in your bird2 configuration file:

protocol static {
    roa4 { table dn42_roa; };
    include "/etc/bird/roa_dn42.conf";
};

protocol static {
    roa6 { table dn42_roa_v6; };
    include "/etc/bird/roa_dn42_v6.conf";
};

... with this one (by changing address and port so it points to your RTR server)

protocol rpki roa_dn42 {
        roa4 { table dn42_roa; };
        roa6 { table dn42_roa_v6; };
        remote 10.1.3.3;
        port 323;
        refresh 600;
        retry 300;
        expire 7200;
}

To reflect changes in the ROA table without a manual reload, ADD "import table" switch for both channels in your DN42 BGP template:

template bgp dnpeers {
  ipv4 {
    ...existing configuration
    import table;
  };
  ipv6 {
    ...existing configuration
    import table;
  };
}

ROA Tables

The ROA table can be generated from the registry directly or you can use the following pre-built ROA tables for BIRD:

ROA files generated by dn42regsrv are available from burble.dn42:

URL IPv4/IPv6 Description
https://dn42.burble.com/roa/dn42_roa_46.json   Both JSON format for use with RPKI
https://dn42.burble.com/roa/dn42_roa_bird1_46.conf   Both Bird1 format
https://dn42.burble.com/roa/dn42_roa_bird1_4.conf   IPv4 Only Bird1 format
https://dn42.burble.com/roa/dn42_roa_bird1_6.conf   IPv6 Only Bird1 format
https://dn42.burble.com/roa/dn42_roa_bird2_46.conf   Both Bird2 format
https://dn42.burble.com/roa/dn42_roa_bird2_4.conf   IPv4 Only Bird2 format
https://dn42.burble.com/roa/dn42_roa_bird2_6.conf   IPv6 Only Bird2 format

ROA files generated by roa_wizard are available:

URL IPv4/IPv6 Description
https://kioubit-roa.dn42.dev/?type=v4   IPv4 Only Bird2 format
https://kioubit-roa.dn42.dev/?type=v6   IPv6 Only Bird2 format
https://kioubit-roa.dn42.dev/?type=json   Both JSON format for use with RPKI

Updating ROA tables

You can add cron entries to periodically update the tables:

*/15 * * * * curl -sfSLR {-o,-z}/var/lib/bird/bird6_roa_dn42.conf https://dn42.burble.com/roa/dn42_roa_bird1_6.conf && chronic birdc6 configure
*/15 * * * * curl -sfSLR {-o,-z}/var/lib/bird/bird_roa_dn42.conf https://dn42.burble.com/roa/dn42_roa_bird1_4.conf && chronic birdc configure

Debian version:

*/15 * * * * curl -sfSLR -o/var/lib/bird/bird6_roa_dn42.conf -z/var/lib/bird/bird6_roa_dn42.conf https://dn42.burble.com/roa/dn42_roa_bird1_6.conf && /usr/sbin/birdc6 configure
*/15 * * * * curl -sfSLR -o/var/lib/bird/bird_roa_dn42.conf -z/var/lib/bird/bird_roa_dn42.conf https://dn42.burble.com/roa/dn42_roa_bird1_4.conf && /usr/sbin/birdc configure

then create the directory to make sure curls can save the files:

mkdir -p /var/lib/bird/

Or use a systemd timer: (check the commands before copy-pasting)

# /etc/systemd/system/dn42-roa.service
[Unit]
Description=Update DN42 ROA

[Service]
Type=oneshot
ExecStart=curl -sfSLR -o /etc/bird/roa_dn42.conf -z /etc/bird/roa_dn42.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf
ExecStart=curl -sfSLR -o /etc/bird/roa_dn42_v6.conf -z /etc/bird/roa_dn42_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf
ExecStart=birdc configure
# /etc/systemd/system/dn42-roa.timer
[Unit]
Description=Update DN42 ROA periodically

[Timer]
OnBootSec=2m
OnUnitActiveSec=15m
AccuracySec=1m

[Install]
WantedBy=timers.target

then enable and start the timer with systemctl enable --now dn42-roa.timer.

More advanced script with error checking:

#!/bin/bash
roa4URL=""
roa6URL=""

roa4FILE="/etc/bird/roa/roa_dn42.conf"
roa6FILE="/etc/bird/roa/roa_dn42_v6.conf"

cp "${roa4FILE}" "${roa4FILE}.old"
cp "${roa6FILE}" "${roa6FILE}.old"

if curl -f -o "${roa4FILE}.new" "${roa4URL};" ;then
    mv "${roa4FILE}.new" "${roa4FILE}"
fi

if curl -f -o "${roa6FILE}.new" "${roa6URL};" ;then
    mv "${roa6FILE}.new" "${roa6FILE}"
fi

if birdc configure ; then
    rm "${roa4FILE}.old"
    rm "${roa6FILE}.old"
else
    mv "${roa4FILE}.old" "${roa4FILE}"
    mv "${roa6FILE}.old" "${roa6FILE}"
fi

Use RPKI ROA in bird2

https://github.com/cloudflare/gortr/releases

./gortr -verify=false -checktime=false -cache=https://dn42.burble.com/roa/dn42_roa_46.json
docker pull cloudflare/gortr
docker run --name dn42rpki -p 8282:8282 --restart=always -d cloudflare/gortr -verify=false -checktime=false -cache=https://dn42.burble.com/roa/dn42_roa_46.json
protocol rpki rpki_dn42{
  roa4 { table dn42_roa; };
  roa6 { table dn42_roa_v6; };

  remote "<your rpki server ip or domain>" port 8282;

  retry keep 90;
  refresh keep 900;
  expire keep 172800;
}

Filter configuration

In your import filter add the following to reject invalid routes:

if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then {
   print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last;
   reject;
}