495 lines
20 KiB
Nix
495 lines
20 KiB
Nix
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
{ depot, lib, config, ... }:
|
|
let
|
|
generateSnippet = base: args: lib.concatStringsSep "\n" (lib.mapAttrsToList ( ixName: ix: generateSnippetForIX (args // { ixName = ixName; ix = ix; }) ) base );
|
|
generateSnippetForIX = { ixName, ix, ... }@args: ''
|
|
${lib.optionalString (doesIPv4 ix) ''
|
|
ipv4 table ${ixName}4;
|
|
filter bgp_in_${ixName}4
|
|
prefix set allnet;
|
|
int set allas;
|
|
{
|
|
if ! (avoid_martians4()) then reject;
|
|
${if ix.remote.must_be_next_hop then "if (bgp_path.first != ${toString ix.remote.asn}) then reject;" else "# no next-hop requirement"}
|
|
${if ix.remote.set_imported_next_hop_to != null then "bgp_next_hop = ${ix.remote.set_imported_next_hop_to};" else "# no imported bgp_next_hop override"}
|
|
${lib.concatMapStringsSep "\n" (asn: "if (bgp_path ~ [= * ${toString asn} * =]) then reject;") ix.remote.drop_asns}
|
|
${lib.optionalString (ixName == "quadv") ''
|
|
bgp_ext_community.add((ro, 205479, 1000));
|
|
bgp_ext_community.add((ro, 205479, 4000)); # etheroute
|
|
bgp_ext_community.add((ro, 205479, 6000)); # cofractal-ams01
|
|
#bgp_ext_community.add((ro, 205479, 4002)); # gsl
|
|
|
|
# Etheroute communities
|
|
bgp_community.add((3170, 63257)); # GTT Corero
|
|
bgp_community.add((64600, 3356)); # no Lumen
|
|
bgp_community.add((64600, 5459)); # no LINX peers (inc. RS)
|
|
''}
|
|
${if ix.remote.pref_src.v4 != null then "krt_prefsrc = ${ix.remote.pref_src.v4};" else "# no krt_prefsrc"}
|
|
bgp_local_pref = ${toString ix.remote.bgp_local_pref};
|
|
accept;
|
|
}
|
|
filter bgp_export_${ixName}4
|
|
{
|
|
if ! ((ro, ${toString ix.local.asn}, 1000) ~ bgp_ext_community) then reject;
|
|
bgp_ext_community.delete([(ro, ${toString ix.local.asn}, *)]);
|
|
accept;
|
|
}
|
|
protocol pipe ${ixName}pipe_4 {
|
|
table ${ixName}4;
|
|
peer table master4;
|
|
import ${if ix.remote.is_route_collector then "all" else "where ((ro, ${toString ix.local.asn}, ${toString ix.remote.export_community}) ~ bgp_ext_community)"};
|
|
export filter bgp_in_${ixName}4;
|
|
};
|
|
''}
|
|
|
|
${lib.optionalString (doesIPv6 ix) ''
|
|
ipv6 table ${ixName}6;
|
|
filter bgp_in_${ixName}6
|
|
prefix set allnet;
|
|
int set allas;
|
|
{
|
|
if ! (avoid_martians6()) then reject;
|
|
${if ix.remote.must_be_next_hop then "if (bgp_path.first != ${toString ix.remote.asn}) then reject;" else "# no next-hop requirement"}
|
|
${lib.concatMapStringsSep "\n" (asn: "if (bgp_path ~ [= * ${toString asn} * =]) then reject;") ix.remote.drop_asns}
|
|
${if ix.remote.pref_src.v6 != null then "krt_prefsrc = ${ix.remote.pref_src.v6};" else "# no krt_prefsrc"}
|
|
bgp_local_pref = ${toString ix.remote.bgp_local_pref};
|
|
accept;
|
|
}
|
|
filter bgp_export_${ixName}6
|
|
{
|
|
if ! ((ro, ${toString ix.local.asn}, 1000) ~ bgp_ext_community) then reject;
|
|
bgp_ext_community.delete([(ro, ${toString ix.local.asn}, *)]);
|
|
accept;
|
|
}
|
|
protocol pipe ${ixName}pipe_6 {
|
|
table ${ixName}6;
|
|
peer table master6;
|
|
import ${if ix.remote.is_route_collector then "all" else "where ((ro, ${toString ix.local.asn}, ${toString ix.remote.export_community}) ~ bgp_ext_community)"};
|
|
export filter bgp_in_${ixName}6;
|
|
};
|
|
''}
|
|
'' + lib.concatImapStringsSep "\n" ( i: v: generateSnippetForRouter (args // { routerNum = i; router = v; }) ) ix.remote.routers;
|
|
doesIPv4 = ix: (ix.local.v4 != null) || ix.v4onv6;
|
|
doesIPv6 = ix: (ix.local.v6 != null);
|
|
enabledSnippet = { enabled ? true, ... }: "disabled ${if enabled then "off" else "on"};";
|
|
passwordSnippet = { password ? null, ... }: if password == null then "# no password" else "password \"${password}\";";
|
|
multihopSnippet = { multihop ? null, ... }: if multihop == null then "# not multihop" else "multihop ${toString multihop};";
|
|
nexthopSnippet = { next_hop ? null, ... }: if next_hop == null then "# no next hop override" else "next hop ${toString next_hop};";
|
|
passiveSnippet = { passive, ... }: "passive ${if passive then "on" else "off"};";
|
|
prefixLimitSnippet = limit: if limit == null then "# no import limit" else "import limit ${toString limit} action restart;";
|
|
generateSnippetForRouter = { ixName, ix, routerNum, router, ... }: ''
|
|
${lib.optionalString (ix.local.v4 != null) ''
|
|
protocol bgp ${ixName}${toString routerNum}_4 {
|
|
${enabledSnippet router}
|
|
${passwordSnippet router}
|
|
${multihopSnippet router}
|
|
${nexthopSnippet router}
|
|
${passiveSnippet ix.remote}
|
|
local ${ix.local.v4} as ${toString ix.local.asn};
|
|
neighbor ${router.v4} as ${toString ix.remote.asn};
|
|
graceful restart on;
|
|
long lived graceful restart on;
|
|
ipv4 {
|
|
table ${ixName}4;
|
|
import all;
|
|
export ${if ix.remote.is_route_collector then "all" else "filter bgp_export_${ixName}4"};
|
|
${prefixLimitSnippet ix.remote.prefix_limit.v4}
|
|
};
|
|
};
|
|
''}
|
|
${lib.optionalString (ix.local.v6 != null) ''
|
|
protocol bgp ${ixName}${toString routerNum}_6 {
|
|
${enabledSnippet router}
|
|
${passwordSnippet router}
|
|
${multihopSnippet router}
|
|
${passiveSnippet ix.remote}
|
|
local ${ix.local.v6} as ${toString ix.local.asn};
|
|
neighbor ${router.v6} as ${toString ix.remote.asn};
|
|
graceful restart on;
|
|
long lived graceful restart on;
|
|
${lib.optionalString (ix.v4onv6) ''
|
|
ipv4 {
|
|
table ${ixName}4;
|
|
import all;
|
|
export ${if ix.remote.is_route_collector then "all" else "filter bgp_export_${ixName}4"};
|
|
${prefixLimitSnippet ix.remote.prefix_limit.v4}
|
|
extended next hop;
|
|
};
|
|
''}
|
|
ipv6 {
|
|
table ${ixName}6;
|
|
import all;
|
|
export ${if ix.remote.is_route_collector then "all" else "filter bgp_export_${ixName}6"};
|
|
${prefixLimitSnippet ix.remote.prefix_limit.v6}
|
|
};
|
|
};
|
|
''}
|
|
'';
|
|
|
|
inherit (lib) mkOption mkAfter types;
|
|
in {
|
|
options.services.lukegbgp = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
};
|
|
config = mkOption { # lukegbgp.config
|
|
type = with types; submodule {
|
|
options = {
|
|
local = mkOption { # lukegbgp.config.local
|
|
type = submodule {
|
|
options = {
|
|
routerID = mkOption { # lukegbgp.config.local.routerID
|
|
type = str;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
peering = mkOption { # lukegbgp.config.peering
|
|
type = attrsOf (submodule {
|
|
options = {
|
|
local = mkOption { # lukegbgp.config.peering.<foo>.local
|
|
type = submodule {
|
|
options = {
|
|
asn = mkOption { # lukegbgp.config.peering.<foo>.local.asn
|
|
type = int;
|
|
};
|
|
v4 = mkOption { # lukegbgp.config.peering.<foo>.local.v4
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
v6 = mkOption { # lukegbgp.config.peering.<foo>.local.v6
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
v4onv6 = mkOption {
|
|
type = bool;
|
|
default = false;
|
|
};
|
|
remote = mkOption { # lukegbgp.config.peering.<foo>.remote
|
|
type = submodule {
|
|
options = {
|
|
asn = mkOption { # lukegbgp.config.peering.<foo>.remote.asn
|
|
type = int;
|
|
};
|
|
passive = mkOption { # lukegbgp.config.peering.<foo>.remote.passive
|
|
type = bool;
|
|
default = false;
|
|
};
|
|
export_community = mkOption { # lukegbgp.config.peering.<foo>.remote.export_community
|
|
type = int;
|
|
};
|
|
prefix_limit.v4 = mkOption { # lukegbgp.config.peering.<foo>.remote.prefix_limit.v4
|
|
type = nullOr int;
|
|
default = null;
|
|
};
|
|
prefix_limit.v6 = mkOption { # lukegbgp.config.peering.<foo>.remote.prefix_limit.v6
|
|
type = nullOr int;
|
|
default = null;
|
|
};
|
|
pref_src.v4 = mkOption { # lukegbgp.config.peering.<foo>.pref_src.v4
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
pref_src.v6 = mkOption { # lukegbgp.config.peering.<foo>.pref_src.v6
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
must_be_next_hop = mkOption { # lukegbgp.config.peering.<foo>.remote.must_be_next_hop
|
|
type = bool;
|
|
default = true;
|
|
};
|
|
set_imported_next_hop_to = mkOption { # lukegbgp.config.peering.<foo>.remote.set_imported_next_hop_to
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
drop_asns = mkOption { # lukegbgp.config.peering.<foo>.remote.drop_asns
|
|
type = listOf int;
|
|
default = [];
|
|
};
|
|
bgp_local_pref = mkOption { # lukegbgp.config.peering.<foo>.remote.bgp_local_pref
|
|
type = int;
|
|
default = 100;
|
|
};
|
|
is_route_collector = mkOption { # lukegbgp.config.peering.<foo>.remote.is_route_collector
|
|
type = bool;
|
|
default = false;
|
|
};
|
|
routers = mkOption { # lukegbgp.config.peering.<foo>.remote.routers
|
|
type = listOf (submodule {
|
|
options = {
|
|
enabled = mkOption { # lukegbgp.config.peering.<foo>.remote.routers.<n>.enabled
|
|
type = bool;
|
|
default = true;
|
|
};
|
|
v4 = mkOption { # lukegbgp.config.peering.<foo>.remote.routers.<n>.v4
|
|
type = str;
|
|
};
|
|
v6 = mkOption { # lukegbgp.config.peering.<foo>.remote.routers.<n>.v6
|
|
type = str;
|
|
};
|
|
multihop = mkOption { # lukegbgp.config.peering.<foo>.remote.routers.<n>.multihop
|
|
type = nullOr int;
|
|
default = null;
|
|
};
|
|
password = mkOption { # lukegbgp.config.peering.<foo>.remote.routers.<n>.password
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
next_hop = mkOption { # lukegbgp.config.peering.<foo>.remote.routers.<n>.next_hop
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
};
|
|
});
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
});
|
|
};
|
|
export = mkOption { # lukegbgp.config.export
|
|
default = { v4 = []; v6 = ["2a09:a440::/48"]; };
|
|
type = submodule {
|
|
options = {
|
|
v4 = mkOption { # lukegbgp.config.export.v4
|
|
type = listOf str;
|
|
default = [];
|
|
};
|
|
v4Extra = mkOption { #lukegbgp.config.export.v4Extra
|
|
type = lines;
|
|
default = "";
|
|
};
|
|
v6 = mkOption { # lukegbgp.config.export.v6
|
|
type = listOf str;
|
|
default = ["2a09:a440::/48"];
|
|
};
|
|
v6Extra = mkOption { #lukegbgp.config.export.v6Extra
|
|
type = lines;
|
|
default = "";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
internal.export = mkOption {
|
|
default = { v4 = []; v6 = []; };
|
|
type = submodule {
|
|
options = {
|
|
v4 = mkOption { # lukegbgp.config.internal.export.v4
|
|
type = listOf str;
|
|
default = [];
|
|
};
|
|
v4Extra = mkOption { #lukegbgp.config.internal.export.v4Extra
|
|
type = lines;
|
|
default = "";
|
|
};
|
|
v6 = mkOption { # lukegbgp.config.internal.export.v6
|
|
type = listOf str;
|
|
default = [];
|
|
};
|
|
v6Extra = mkOption { #lukegbgp.config.internal.export.v6Extra
|
|
type = lines;
|
|
default = "";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
bfd = mkOption { # lukegbgp.config.bfd
|
|
type = lines;
|
|
default = "";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = {
|
|
services.bird2 = lib.mkIf config.services.lukegbgp.enable {
|
|
enable = true;
|
|
config = ''
|
|
router id ${config.services.lukegbgp.config.local.routerID};
|
|
|
|
function avoid_martians4()
|
|
prefix set martians;
|
|
{
|
|
martians = [
|
|
169.254.0.0/16+,
|
|
172.16.0.0/12+,
|
|
192.168.0.0/16+,
|
|
192.0.0.0/24+,
|
|
192.0.2.0/24+,
|
|
192.88.99.0/24+,
|
|
198.18.0.0/15+,
|
|
198.51.100.0/24+,
|
|
203.0.113.0/24+,
|
|
10.0.0.0/8+,
|
|
100.64.0.0/10+,
|
|
224.0.0.0/4+,
|
|
240.0.0.0/4+,
|
|
0.0.0.0/32-,
|
|
0.0.0.0/0{25,32},
|
|
0.0.0.0/0{0,7} ];
|
|
|
|
if net ~ martians then return false;
|
|
return true;
|
|
}
|
|
|
|
function avoid_martians6()
|
|
prefix set martians;
|
|
{
|
|
martians = [
|
|
::/128-,
|
|
::1/128-,
|
|
::ffff:0:0/96+,
|
|
::ffff:0:0:0/96+,
|
|
64:ff9b::/96+,
|
|
100::/64+,
|
|
::/0{64,128},
|
|
::/0{0,15},
|
|
2001:db8::/32+,
|
|
fc00::/7+,
|
|
fe80::/10+,
|
|
ff00::/8+ ];
|
|
|
|
if net ~ martians then return false;
|
|
return true;
|
|
}
|
|
|
|
${generateSnippet config.services.lukegbgp.config.peering {}}
|
|
|
|
protocol kernel {
|
|
persist;
|
|
ipv4 {
|
|
import none;
|
|
export all;
|
|
};
|
|
};
|
|
protocol kernel {
|
|
persist;
|
|
ipv6 {
|
|
import none;
|
|
export all;
|
|
};
|
|
};
|
|
protocol device {
|
|
};
|
|
|
|
protocol static export4 {
|
|
ipv4 {
|
|
import filter {
|
|
bgp_ext_community.add((ro, 205479, 1000));
|
|
bgp_ext_community.add((ro, 205479, 2000));
|
|
bgp_ext_community.add((ro, 205479, 2001));
|
|
bgp_ext_community.add((ro, 205479, 2002));
|
|
bgp_ext_community.add((ro, 205479, 2003));
|
|
bgp_ext_community.add((ro, 205479, 3000));
|
|
bgp_ext_community.add((ro, 205479, 4000));
|
|
bgp_ext_community.add((ro, 205479, 4001));
|
|
bgp_ext_community.add((ro, 205479, 4002));
|
|
bgp_ext_community.add((ro, 205479, 5000));
|
|
bgp_ext_community.add((ro, 205479, 5001));
|
|
bgp_ext_community.add((ro, 205479, 5002));
|
|
bgp_ext_community.add((ro, 205479, 5003));
|
|
bgp_ext_community.add((ro, 205479, 5004));
|
|
bgp_ext_community.add((ro, 205479, 5005));
|
|
bgp_ext_community.add((ro, 205479, 5006));
|
|
bgp_ext_community.add((ro, 205479, 5007));
|
|
bgp_ext_community.add((ro, 205479, 5008));
|
|
bgp_ext_community.add((ro, 205479, 5009)); # fastly from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5010)); # ovh from rexxar
|
|
bgp_ext_community.add((ro, 205479, 6000)); # EMF: EMF-IX Route Server
|
|
|
|
# do not export to clouvider; they do... strange things.
|
|
bgp_ext_community.add((rt, 0, 62240));
|
|
|
|
accept;
|
|
};
|
|
};
|
|
${lib.concatMapStrings (ip: "route ${ip} blackhole;") config.services.lukegbgp.config.export.v4}
|
|
${config.services.lukegbgp.config.export.v4Extra}
|
|
};
|
|
protocol static exportinternal4 {
|
|
ipv4 {
|
|
import filter {
|
|
bgp_ext_community.add((ro, 205479, 10)); # internal only
|
|
accept;
|
|
};
|
|
};
|
|
${lib.concatMapStrings (ip: "route ${ip} blackhole;") config.services.lukegbgp.config.internal.export.v4}
|
|
${config.services.lukegbgp.config.internal.export.v4Extra}
|
|
};
|
|
protocol static export6 {
|
|
ipv6 {
|
|
import filter {
|
|
bgp_ext_community.add((ro, 205479, 1000)); # export
|
|
bgp_ext_community.add((ro, 205479, 2000)); #
|
|
bgp_ext_community.add((ro, 205479, 2001)); #
|
|
bgp_ext_community.add((ro, 205479, 2002)); #
|
|
bgp_ext_community.add((ro, 205479, 2003)); #
|
|
bgp_ext_community.add((ro, 205479, 3000)); # clouvider from clouvider-lon01
|
|
bgp_ext_community.add((ro, 205479, 4000)); # frantech from frantech-nyc01/veloxserv from etheroute-lon01
|
|
bgp_ext_community.add((ro, 205479, 4001)); # veloxserv from rexxar
|
|
bgp_ext_community.add((ro, 205479, 4002)); # mercury from etheroute-lon01
|
|
bgp_ext_community.add((ro, 205479, 5000)); # linx route collector from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5001)); # linx route server from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5002)); # facebook from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5003)); # openpeering from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5004)); # freetransitnet from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5005)); # he from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5006)); # clouvider from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5007)); # google from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5008)); # cloudflare from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5009)); # fastly from rexxar
|
|
bgp_ext_community.add((ro, 205479, 5010)); # ovh from rexxar
|
|
bgp_ext_community.add((ro, 205479, 6000)); # EMF: EMF-IX Route Server
|
|
|
|
# do not export to clouvider; they do... strange things.
|
|
bgp_ext_community.add((rt, 0, 62240));
|
|
|
|
accept;
|
|
};
|
|
};
|
|
${lib.concatMapStrings (ip: "route ${ip} blackhole;") config.services.lukegbgp.config.export.v6}
|
|
${config.services.lukegbgp.config.export.v6Extra}
|
|
};
|
|
protocol static exportinternal6 {
|
|
ipv6 {
|
|
import filter {
|
|
bgp_ext_community.add((ro, 205479, 10)); # internal only
|
|
accept;
|
|
};
|
|
};
|
|
${lib.concatMapStrings (ip: "route ${ip} blackhole;") config.services.lukegbgp.config.internal.export.v6}
|
|
${config.services.lukegbgp.config.internal.export.v6Extra}
|
|
};
|
|
|
|
protocol bfd {
|
|
${config.services.lukegbgp.config.bfd}
|
|
};
|
|
'';
|
|
};
|
|
services.prometheus.exporters.bird.enable = config.services.bird2.enable;
|
|
|
|
networking.firewall.allowedTCPPorts = lib.mkIf config.services.lukegbgp.enable (lib.mkAfter [ 179 ]);
|
|
networking.firewall.checkReversePath = false;
|
|
|
|
boot.kernel.sysctl = {
|
|
"net.ipv6.conf.default.accept_ra" = 0;
|
|
"net.ipv6.conf.all.accept_ra" = 0;
|
|
"net.ipv6.conf.default.autoconf" = 0;
|
|
"net.ipv6.conf.all.autoconf" = 0;
|
|
};
|
|
|
|
services.tailscale.package = depot.nix.pkgs.tailscale;
|
|
systemd.network.config.networkConfig.ManageForeignRoutes = "no";
|
|
|
|
systemd.services."systemd-resolved".environment.SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME = "false";
|
|
};
|
|
}
|