depot/ops/nixos/lib/bgp.nix

225 lines
8.3 KiB
Nix

# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ lib, config, ... }:
let
generateSnippet = base: args: lib.concatStringsSep "\n" (lib.mapAttrsToList ( ixName: ix: generateSnippetForIX (args // { ixName = ixName; ix = ix; }) ) base );
generateSnippetForIX = { ixName, ix, ... }@args: ''
ipv4 table ${ixName}4;
ipv6 table ${ixName}6;
protocol pipe ${ixName}pipe_4 {
table ${ixName}4;
peer table master4;
import where ((ro, ${toString ix.local.asn}, ${toString ix.remote.export_community}) ~ bgp_ext_community);
export all;
};
protocol pipe ${ixName}pipe_6 {
table ${ixName}6;
peer table master6;
import where ((ro, ${toString ix.local.asn}, ${toString ix.remote.export_community}) ~ bgp_ext_community);
export all;
};
'' + lib.concatImapStringsSep "\n" ( i: v: generateSnippetForRouter (args // { routerNum = i; router = v; }) ) ix.remote.routers;
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};";
generateSnippetForRouter = { ixName, ix, routerNum, router, ... }: ''
protocol bgp ${ixName}${toString routerNum}_4 {
${enabledSnippet router}
${passwordSnippet router}
${multihopSnippet router}
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 where ((ro, ${toString ix.local.asn}, 1000) ~ bgp_ext_community);
};
};
protocol bgp ${ixName}${toString routerNum}_6 {
${enabledSnippet router}
${passwordSnippet router}
${multihopSnippet router}
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;
ipv6 {
table ${ixName}6;
import all;
export where ((ro, ${toString ix.local.asn}, 1000) ~ bgp_ext_community);
};
};
'';
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 = str;
};
v6 = mkOption { # lukegbgp.config.peering.<foo>.local.v6
type = str;
};
};
};
};
remote = mkOption { # lukegbgp.config.peering.<foo>.remote
type = submodule {
options = {
asn = mkOption { # lukegbgp.config.peering.<foo>.remote.asn
type = int;
};
export_community = mkOption { # lukegbgp.config.peering.<foo>.remote.export_community
type = int;
};
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;
};
};
});
};
};
};
};
};
});
};
export = mkOption { # lukegbgp.config.export
default = { v4 = ["92.118.31.0/24"]; v6 = ["2a09:a440::/48"]; };
type = submodule {
options = {
v4 = mkOption { # lukegbgp.config.export.v4
type = listOf str;
default = ["92.118.31.0/24"];
};
v6 = mkOption { # lukegbgp.config.export.v6
type = listOf str;
default = ["2a09:a440::/48"];
};
};
};
};
};
};
};
};
config = {
services.bird2 = lib.mkIf config.services.lukegbgp.enable {
enable = true;
config = ''
router id ${config.services.lukegbgp.config.local.routerID};
${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));
accept;
};
};
${lib.concatMapStrings (ip: "route ${ip} blackhole;") config.services.lukegbgp.config.export.v4}
};
protocol static export6 {
ipv6 {
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));
accept;
};
};
${lib.concatMapStrings (ip: "route ${ip} blackhole;") config.services.lukegbgp.config.export.v6}
};
'';
};
networking.firewall.allowedTCPPorts = lib.mkIf config.services.lukegbgp.enable (lib.mkAfter [ 179 ]);
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;
};
};
}