IT FUCKING WORKS

This commit is contained in:
Fisher Darling
2022-11-01 10:56:35 +01:00
parent 59730c534c
commit 47a99bfcbe
7 changed files with 704 additions and 109 deletions
@@ -0,0 +1,136 @@
# ==== CONSTANTS ====
define OWNAS = 550001; # your autonomous system number
# the first non-zero IPv4 you control. Since we're only using
# IPv6. Use the first non-zero 32 bits you control. E.g., if
# you control 255::/16, use 2.85.0.1 (255 is two hex bytes,
# 0x02, and 0x55 which is 2.85 in a v4 addr).
define OWNIP = 2.0.0.1;
# the router's actual IPv6 addr that it can be reached by.
define OWNIPv6 = 200::1;
# the subnet you control
define OWNNETv6 = 200::/16;
# set of all addrs you control.
define OWNNETSETv6 = [200::/16+];
# ===================
router id OWNIP;
# "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;
}
# ==== UTILITY FUNCTIONS ====
function is_self_net() {
return net ~ OWNNETSETv6;
}
function is_valid_network() {
return net ~ [
0200::/7+
];
}
# Tell bird how to interact with the kernel (modify routes):
#
# Again, not really a protocol but ¯\_(ツ)_/¯
protocol kernel {
# scan the routing table every 20 seconds (update bird state).
scan time 20;
# configure ipv6
ipv6 {
# 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 = OWNIPv6;
# 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 OWNNETv6 reject;
ipv6 {
# 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;
ipv6 {
# 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 "peers/*";
@@ -0,0 +1,3 @@
protocol bgp test_router_peer from alapeers {
neighbor 255::1 as 550002; # as == autonomous system
}
@@ -0,0 +1,136 @@
# ==== CONSTANTS ====
define OWNAS = <OWNAS>; # your autonomous system number
# the first non-zero IPv4 you control. Since we're only using
# IPv6. Use the first non-zero 32 bits you control. E.g., if
# you control 255::/16, use 2.85.0.1 (255 is two hex bytes,
# 0x02, and 0x55 which is 2.85 in a v4 addr).
define OWNIP = <OWNIP>;
# the router's actual IPv6 addr that it can be reached by.
define OWNIPv6 = <OWNIPv6>;
# the subnet you control
define OWNNETv6 = <OWNNET>;
# set of all addrs you control.
define OWNNETSETv6 = [<OWNNET>+];
# ===================
router id OWNIP;
# "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;
}
# ==== UTILITY FUNCTIONS ====
function is_self_net() {
return net ~ OWNNETSETv6;
}
function is_valid_network() {
return net ~ [
0200::/7+
];
}
# Tell bird how to interact with the kernel (modify routes):
#
# Again, not really a protocol but ¯\_(ツ)_/¯
protocol kernel {
# scan the routing table every 20 seconds (update bird state).
scan time 20;
# configure ipv6
ipv6 {
# 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 = OWNIPv6;
# 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 OWNNETv6 reject;
ipv6 {
# 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;
ipv6 {
# 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 "peers/*";
@@ -0,0 +1,3 @@
protocol bgp <PEER_NAME> from alapeers {
neighbor <PEERING_IP> as <PEER_AS>; # as == autonomous system
}
@@ -0,0 +1,136 @@
# ==== CONSTANTS ====
define OWNAS = 550002; # your autonomous system number
# the first non-zero IPv4 you control. Since we're only using
# IPv6. Use the first non-zero 32 bits you control. E.g., if
# you control 255::/16, use 2.85.0.1 (255 is two hex bytes,
# 0x02, and 0x55 which is 2.85 in a v4 addr).
define OWNIP = 2.85.0.1;
# the router's actual IPv6 addr that it can be reached by.
define OWNIPv6 = 255::1;
# the subnet you control
define OWNNETv6 = 255::/16;
# set of all addrs you control.
define OWNNETSETv6 = [255::/16+];
# ===================
router id OWNIP;
# "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;
}
# ==== UTILITY FUNCTIONS ====
function is_self_net() {
return net ~ OWNNETSETv6;
}
function is_valid_network() {
return net ~ [
0200::/7+
];
}
# Tell bird how to interact with the kernel (modify routes):
#
# Again, not really a protocol but ¯\_(ツ)_/¯
protocol kernel {
# scan the routing table every 20 seconds (update bird state).
scan time 20;
# configure ipv6
ipv6 {
# 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 = OWNIPv6;
# 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 OWNNETv6 reject;
ipv6 {
# 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;
ipv6 {
# 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 "peers/*";
@@ -0,0 +1,3 @@
protocol bgp og_router_peer from alapeers {
neighbor 200::1 as 550001; # as == autonomous system
}
+240 -62
View File
@@ -10,7 +10,7 @@ with another router will come in an edit or separate post.
# 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 `0x02` and `0x0[01]`).
the entire `0200::/7` address space (all addresses prefixed with `0x0(2|3)`).
```
┌──────────┐ wg tunnel
@@ -88,6 +88,29 @@ in bird. Protocols describe how to interact with the kernel, BGP, static route,
## bird.conf
```
# ==== CONSTANTS ====
define OWNAS = <OWNAS>; # your autonomous system number
# the first non-zero IPv4 you control. Since we're only using
# IPv6. Use the first non-zero 32 bits you control. E.g., if
# you control 255::/16, use 2.85.0.1 (255 is two hex bytes,
# 0x02, and 0x55 which is 2.85 in a v4 addr).
define OWNIP = <OWNIP>;
# the router's actual IPv6 addr that it can be reached by.
define OWNIPv6 = <OWNIPv6>;
# the subnet you control
define OWNNETv6 = <OWNNET>;
# set of all addrs you control.
define OWNNETSETv6 = [<OWNNET>+];
# ===================
router id OWNIP;
# "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).
@@ -96,31 +119,27 @@ protocol device {
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
# ==== UTILITY FUNCTIONS ====
function is_self_net() {
return net ~ OWNNETSETv6;
}
function is_valid_network() {
return net ~ [
0200::/7+
];
}
# Tell bird how to interact with the kernel (modify routes):
#
# Again, not really a protocol but ¯\_(ツ)_/¯
# Comment from dn42's guide:
/*
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;
# configure ipv6
ipv6 {
# 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
@@ -136,21 +155,25 @@ protocol kernel {
if source = RTS_STATIC then reject;
# set the `krt_prefsrc` attribute for this route.
# see comment above above the kernel block.
krt_prefsrc = OWNIP;
krt_prefsrc = OWNIPv6;
# 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;
route OWNNETv6 reject;
ipv6 {
# 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
@@ -172,9 +195,7 @@ template bgp alapeers {
# compare path lengths to determine which route is the best one.
path metric 1;
# remember rejected routes for later debugging.
import keep filtered;
ipv6 {
# only accept routes from peers that follow this inline filter.
# bgp -> bird.
import filter {
@@ -195,53 +216,17 @@ template bgp alapeers {
}
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/*";
include "peers/*";
```
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. generally some kind of address),
# 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.
Whew, that was a rather larger file but that's pretty much it. Now to configure a peer.
## peers/*.conf
@@ -271,3 +256,196 @@ something up.
I need to determine a fake AS number, subnets, etc, and then I'll report back if it works or not. To test the bird
configuration, I plan to connect lykos to a different router running on a different subnet, and then ssh into hyperion
from lykos. The routes should "just work" (right lol).
# (edit) Testing Bird with Two Routers
I have two DO droplets that will run bird. Here's how I'm setting up the network.
OG Router (OGR):
- `AS`: AS550001
- `router id`: 2.0.0.1
- `GATEWAY_IP`: `0200::1`
- `SUBNET`: `0200::/16`
- Internal WG Interface: ohea0
Test Router (TR):
- `AS`: AS550002
- `router id`: 2.85.0.1
- `GATEWAY_IP`: `0255::1`
- `SUBNET`: `0255::/16`
- Internal WG Interface: alanet0
I'm keeping hyperion connected to OGR with address `0200::3`. Lykos will connect to TR with address `0255::2`. I've
setup wireguard (wg) on my two clients, hyperion and lykos, to allow all IPs from `0200::/7`.
Things started getting a little hairy here. `wg-quick` automatically sets up the routing table which interferes with
bird, so we need to disable it with `Table = off` in our WG configuration. I also enabled `PersistentKeepalive` with
my two routers in order to confirm that a tunnel could actually be created. Since I disabled the wg routing table, I
can't seem to ping one router from the other. I believe this is because there is nothing telling the kernel to route
arbitrary `200::/7` packets to the wg tunnel and from the wg tunnel to the router I'm trying to ping. I'm guessing bird
will set this up for me when I add a peer.
## Adding bird to each router
I've put the configuration for both routers under the bird_conf dir. `ogr` for the OG Router, `tr` for the test
router, and `template` for the template files above.
Updating a server conf:
```
rsync -a ogr/* router:.bird
```
### First Snag, the Router ID
The `router id` in `local.conf` needs to be an IPv4 address (ugh). I'm just going to use my router's public ipv4 addr
and call it good.
### Second Snag, Syntax
Syntax errors everywhere.
### Third Snag, wrong bird version
FML. The guide from dn42 was for bird 1.6.3, and I'm running bird 2.0.10. dn42 has a [bird2 guide](https://dn42.eu/howto/Bird2)
which I'm using to modify the configuration that I wrote about above. This has led to more syntax errors...
Some of the errors have been rather helpful though!
```
bird: bird.conf:15:28 Invalid IPv6 prefix 200::1/16, maybe you wanted 200::/16
```
## Running bird
After fixing those errors and changing the conf file, here's the command I'm using to run bird:
```
bird -d -c bird.conf
```
`-d` for debug messages and for running in the foreground and `-c` to point to my local config file, `.bird/bird.conf`.
Apparently ubuntu has a `bird2` package that's bird version `2.0.9-3`. I'll attempt to use that for the test-router.
I have bird running on both routers now. Now the _real_ debugging begins.
# Further wg configuration
After reading [dn42's guide on wireguard](https://dn42.eu/howto/wireguard) I believe we need tell the interface that
it's point-to-point and split up the config.
Here's my wireguard setup for ogr:
I now have two wireguard config files, `ohea0.conf` and `peer-tr.conf`. `ohea0.conf` has all of my configuration for my
local devices and lets lykos ssh into hyperion through ogr. It's a standard wg configuration. The key here is that **I'm
listening on port 50005**. `peer-tr.conf` is an interface just for peering with the `test-router`. I expect that wg
configuration to be copy-pasted for each peer, albeit with modified `ListenPort`s.
Internal Network: `ohea0.conf`
```
[Interface]
PrivateKey = (redacted)
Address = 200::1/16
ListenPort = 50005
[Peer]
PublicKey = ZKR6n8/IersOc1SKoUyU6Z3/q/jPeZs5ljf4hg04pA0=
AllowedIPs = 0200::3/128
```
BGP Peer: `peer-tr.conf`
```
[Interface]
PrivateKey =
# Different listen port for peers!
ListenPort = 50004
# Make this link a point-to-point connection.
# Note ogr's ip 200::1 and the peer's (tr's) ip 255::1
PostUp = /sbin/ip addr add dev %i 200::1/128 peer 255::1/128
# Don't modify any routes
Table = off
[Peer]
PublicKey = C6RPH/RMqZ9u0ot6XN8ZcS+vTtU2zo9ZD7+wCfPzHxg=
AllowedIPs = 0200::/7
Endpoint = 46.101.144.104:50004
PersistentKeepalive = 25
```
The config for `tr` is essentially the same, just flipping the ips and keys.
`ogr` can now ping `tr` and we're now split on multiple interfaces.
# Debugging BGP
After making the peer wg config point-to-point, ogr's and tr's birds starting speaking BGP with eachother. I used the
bird client `birdc` to see if anything changed. `birdc` connects to the currently running bird process over the control
socket created when bird starts.
Run `show protocols` to get the list of configured protocols (blocks in the config file). Here's what ogr's protocols
look like:
```
bird> show protocols
Name Proto Table State Since Info
device1 Device --- up 09:10:13.122
kernel1 Kernel master6 up 09:10:13.122
static1 Static master6 up 09:10:13.122
peer_test_router BGP --- up 09:10:22.257 Established
```
And `show protocols peer_test_router` gives much more info on BGP that's running. I saw in that output that some routes
were added/seen by bird.
Here's `show route stats`:
```
bird> show route stats
Table master4:
0 of 0 routes for 0 networks in table master4
Table master6:
200::/16 unreachable [static1 09:10:13.122] * (200)
255::/16 unicast [peer_test_router 09:10:23.124] * (100) [AS550002i]
via 255::1 on peer-tr
2 of 2 routes for 2 networks in table master6
Total: 2 of 2 routes for 2 networks in 2 tables
```
Seeing `255::/16` in there is promising, since that's the subnet that the `test-router` controls (comes from
AS550002).
Let's try it out!
# IT FUCKING WORKS
I ssh'ed into `hyperion` FROM `lykos`! This is pretty fuckin cool. I feel like some sort of internet god.
Here's my final topology:
```
┌───────────┐ ┌───────────┐
│ ogr │peer-tr │ tr │
│ 200::1/16 ◄────────────────► 255::1/16 │
│ AS55001 │ peer-ogr│ AS55002 │
└────▲──────┘ └────▲──────┘
│ │
│ohea0 alanet0│
│ │
│ │
┌────▼─────┐ ┌────▼───┐
│ hyperion │ │ lykos │
│ 200::3 │ │ 255::2 │
└──────────┘ └────────┘
```
So in order to ssh from lykos, tr needed to know that it could send `200::/16` packets to ogr. Remember that the wg link
from tr <-> ogr is only point-to-point, there was no concept of a subnet. That means routing info for ogr must have been
set by bird in tr's routing table, which allowed the IP/TCP packets to be forwarded to hyperion.
# Conclusion
That was a grind. I think like 99% of it can be automated with a nice tool and config file, at least all of the bird
configuration. Auto configuring WG is also pretty doable.
We still need to test this with multiple peers using an actual "network", but that's not too bad.