first draft setting up bird post
This commit is contained in:
@@ -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.
|
||||||
Reference in New Issue
Block a user