first draft setting up bird post

This commit is contained in:
Fisher Darling
2022-10-30 17:12:07 +01:00
parent 1b1196b2bf
commit f458cbc8b2
+267
View File
@@ -0,0 +1,267 @@
# Setting up BIRD
BIRD, or the "BIRD Internet Routing Daemon", is a daemon that turns a dump linux box into a fully fledged router that
can support many different routing protocols. It speaks those protocols with other routers and builds a dynamic routing
table that is then sent to the kernel (which does the actual packet forwarding).
This doc tracks my learnings from install and using bird on a cheap, $4/month DO droplet.
# My Network
I'm not trying to to any sort of dynamic routing yet, but here's what I have working right now. My current network is
the entire `0200::/7` address space (all addresses prefixed with `0x2` and `0x00`).
```
┌──────────┐ wg tunnel
│ hyperion ◄───────────────┐
│ 200::3 │ │
└──────────┘ │
┌────────▼────────────┐
│ router running bird │
│ 200::1 │
┌─────────┐ └────────▲────────────┘
│ lykos │ │
│ 200::55 ◄────────────────┘
└─────────┘ wg tunnel
```
# Installing `bird`
Ubuntu's apt repo only has version `1.6.8` and I think we want the newer `2.0.10`. This means compiling it from source
which wasn't too bad.
First we need some dependencies:
```
sudo apt install gcc flex bison make m4 libncurses5-dev libncursesw5-dev libreadline6-dev
```
Actually compiling it:
```
curl -LO https://bird.network.cz/download/bird-2.0.10.tar.gz
tar -xvf bird-2.0.10.tar.gz
cd bird
./configure
make
make install
bird --version
```
I moved my bin to `/usr/local/bin/bird` after having some weird env issues.
~~Now we need to create a user with `CAP_NET_*` privileges that bird can run as~~. We actually don't need to do that
immediately. We can use `-u` to drop to a user like `nobody` and bird will only give itself the privileges that it
needs.
# Configuration
Bird is configured with a pretty simple file (or list of files). You can use the global `/etc/bird/bird.conf` but I've
chosen to use a local config file under `~/.bird/bird.conf`. You can give bird the path to the config file with `-c`.
dn42 has a [great guide](https://dn42.eu/howto/Bird) on using bird (with peers!). I'm going to copy the configuration
structure from that page here and attempt to add more commentary.
The guide requires a few pieces of information (what we'll need to decide on):
* `<AS>`: your [Autonomous System Number](https://en.wikipedia.org/wiki/Autonomous_system_(Internet))
* `<GATEWAY_IP>`: your gateway ip, the internal `0200::/7` address that the router will run on.
* `<SUBNET>`: whatever subnet you control. Such as `0200:1234::/16`.
* `<PEER_IP>`: the ip of a peer connected to you.
* `<PEER_AS>`: the AS number of the peer.
* `<PEER_NAME>`: what you want to call that peer.
> Most of this information should be located in (and authenticated through) a git repo. If we do it with git, then
> updating bird should simply be a `git pull` and then running a cli tool to generate the config and reload bird. We need
> to find a balance between magic and understanding of what's happening. Maybe a tool that can describe what's going on
> at any level of detail would be enough?
Now for the actual configuration. We're going to be using IPv6 only. If we did v4 we would need a separate set of rules
files, subnets, ips, etc.
The first file is the root `bird.conf`. This is what we point bird to when starting the daemon. I will try to comment
every line and block with my understanding of what it means.
## bird.conf
```
# "everything" is a protocol in bird. The `device` protocol is not really
# a protocol, it just instructs bird to ask the kernel for information
# on devices (interfaces).
protocol device {
# scan devices every 10 seconds.
scan time 10;
}
# Include our local configuration.
#
# We define our own AS and subnet in here along with some
# helper functions for determining if a route is in our
# own network. We'll cover this short file later.
include "/etc/bird/local6.conf
# Tell bird how to interact with the kernel (modify routes):
#
# Again, not really a protocol but ¯\_(ツ)_/¯
# Comment from dn42.
/*
krt_prefsrc defines the source address for outgoing connections.
On Linux, this causes the "src" attribute of a route to be set.
Without this option outgoing connections would use the peering IP which
would cause packet loss if some peering disconnects but the interface
is still available. (The route would still exist and thus route through
the TUN/TAP interface but the VPN daemon would simply drop the packet.)
*/
protocol kernel {
# scan the routing table every 20 seconds (update bird state).
scan time 20;
# Don't import any routes from the kernel, let bird figure out
# what routes to add or change. `none` is a "filter" that discards
# routes that are given to it. The kernel gives bird routes and we
# want bird to ignore them. kernel -> bird.
import none;
# We write an inline filter that describes what routes to give the
# kernel from bird. bird -> kernel.
export filter {
# if the source of this route is static configuration, then we
# don't want to give it to the kernel. (only want dynamic routes).
# I don't know why this is important? Is that right?
if source = RTS_STATIC then reject;
# set the `krt_prefsrc` attribute for this route.
# see comment above above the kernel block.
krt_prefsrc = OWNIP;
# accept this route (don't filter it out).
accept;
};
}
# Static protocol lets you define static routes that do not change.
protocol static {
# reject any routes in our subnet (I think?)
route <SUBNET> reject;
# tell bird about everything. static -> bird.
import all;
# bird can't configure static routes (default)?.
# bird -> static.
export none;
}
# Create a template for the `bgp` protocol. This is where the real
# magic happens. We use a template since every peer will essentially
# have the same BGP configuration. The template name is `alapeers`.
#
# Each peer gets its own template nad file under `peers6/*``.
#
# We're using BGP in "exterior" or `eBGP` mode. This means we're using
# the protocol to define routes between Autonomous Systems, not really
# between individual IP addrs.
#
# Each instance of the `bgp` protocol corresponds with one neighboring
# router, or in our case, a peer.
template bgp alapeers {
# our `as` number is the OWNAS constant. This is defined in the local
# defs file that comes later.
local as OWNAS;
# compare path lengths to determine which route is the best one.
path metric 1;
# remember rejected routes for later debugging.
import keep filtered;
# only accept routes from peers that follow this inline filter.
# bgp -> bird.
import filter {
# Accept the route if its a valid network and is not our
# own network. (fns defined in local defs file).
if is_valid_network() && !is_self_net() then {
accept;
}
reject;
};
# only send routes that follow this inline filter.
# bird -> bgp (other peers).
export filter {
# Only send if it's a valid netowrk and the route comes from
# static configuration or from BGP.
if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then {
accept;
}
reject;
};
# Only allow for 1000 total routes to be imported. Once we hit
# 1000, block further routes.
import limit 1000 action block;
}
# Include all of our peers!
include "/etc/bird/peers6/*";
```
Whew, that was a rather larger file but that's pretty much it. Now for our local configuration and what a peer looks
like.
## local6.conf
```
# Our globally unique identifier. Most people in dn42 us their internal router's ip.
# I wonder if a UUID or something would work here?
router id <GATEWAY_IP>;
# Define our own Autonomous System Number here:
define OWNAS = <AS>;
# Define the router's ip:
define OWNIP = <GATEWAY_IP>;
# Now for some helper functions (filters) that we use in the config above:
# Returns if the `net` special variable (what the route is actually talking about, could be prefix),
# is in our own subnet. The `+` after the subnet is shorthand for "every possible address in this subnet".
#
# `a ~ b` means "a is in b".
function is_self_net() {
return net ~ [<SUBNET>+];
}
# We use this function to limit our routes to those that are in the alanet.
function is_valid_network() {
return net ~ [
0200::/7+ # All address that start with 0200 are in our network.
]
}
```
That's it for the local configuration! Define some constants and two simple helper functions.
## peers/*.conf
Peer configuration is super simple with the template we defined above:
```
protocol bgp <PEER_NAME> from alapeers {
neighbor <PEERING_IP> as <PEER_AS>; # as == autonomous system
}
```
Done! Adding a new peer just means adding a new file in the peers directory.
# ROA Tables
Route origin authorization. This basically is a central list of who owns what routes and is allowed to send changes.
There's some crypto stuff you can do too, but that's really only important if we start letting people join arbitrarily.
There is a concern that one of us messes up the config and starts sending out bad routes, but we'll cross that bridge
when we get there (large outage lol).
# What's Next
Now I need to actually test this with another router. I'll most likely setup another VPS or ask someone else to spin
something up.
I need to determine a fake AS number, subnets, etc.