# SPDX-FileCopyrightText: 2020 Luke Granger-Brown # # 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; 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"} bgp_local_pref = ${toString ix.remote.bgp_local_pref}; accept; } 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"} bgp_local_pref = ${toString ix.remote.bgp_local_pref}; accept; } 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 filter bgp_in_${ixName}4; }; 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 filter bgp_in_${ixName}6; }; '' + 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};"; 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, ... }: '' protocol bgp ${ixName}${toString routerNum}_4 { ${enabledSnippet router} ${passwordSnippet router} ${multihopSnippet 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 where ((ro, ${toString ix.local.asn}, 1000) ~ bgp_ext_community); ${prefixLimitSnippet ix.remote.prefix_limit.v4} }; }; 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; ipv6 { table ${ixName}6; import all; export where ((ro, ${toString ix.local.asn}, 1000) ~ bgp_ext_community); ${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..local type = submodule { options = { asn = mkOption { # lukegbgp.config.peering..local.asn type = int; }; v4 = mkOption { # lukegbgp.config.peering..local.v4 type = str; }; v6 = mkOption { # lukegbgp.config.peering..local.v6 type = str; }; }; }; }; remote = mkOption { # lukegbgp.config.peering..remote type = submodule { options = { asn = mkOption { # lukegbgp.config.peering..remote.asn type = int; }; passive = mkOption { # lukegbgp.config.peering..remote.passive type = bool; default = false; }; export_community = mkOption { # lukegbgp.config.peering..remote.export_community type = int; }; prefix_limit.v4 = mkOption { # lukegbgp.config.peering..remote.prefix_limit.v4 type = nullOr int; default = null; }; prefix_limit.v6 = mkOption { # lukegbgp.config.peering..remote.prefix_limit.v6 type = nullOr int; default = null; }; must_be_next_hop = mkOption { # lukegbgp.config.peering..remote.must_be_next_hop type = bool; default = true; }; bgp_local_pref = mkOption { # lukegbgp.config.peering..remote.bgp_local_pref type = int; default = 100; }; routers = mkOption { # lukegbgp.config.peering..remote.routers type = listOf (submodule { options = { enabled = mkOption { # lukegbgp.config.peering..remote.routers..enabled type = bool; default = true; }; v4 = mkOption { # lukegbgp.config.peering..remote.routers..v4 type = str; }; v6 = mkOption { # lukegbgp.config.peering..remote.routers..v6 type = str; }; multihop = mkOption { # lukegbgp.config.peering..remote.routers..multihop type = nullOr int; default = null; }; password = mkOption { # lukegbgp.config.peering..remote.routers..password 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 = ""; }; }; }; }; 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)); accept; }; }; ${lib.concatMapStrings (ip: "route ${ip} blackhole;") config.services.lukegbgp.config.export.v4} ${config.services.lukegbgp.config.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 blade-tuvok/blade-paris bgp_ext_community.add((ro, 205479, 4002)); # bgp_ext_community.add((ro, 205479, 5000)); # linx route collector from blade-tuvok bgp_ext_community.add((ro, 205479, 5001)); # linx route server from blade-tuvok bgp_ext_community.add((ro, 205479, 5002)); # facebook from blade-tuvok bgp_ext_community.add((ro, 205479, 5003)); # openpeering from blade-tuvok bgp_ext_community.add((ro, 205479, 5004)); # freetransitnet from blade-tuvok bgp_ext_community.add((ro, 205479, 5005)); # he from blade-tuvok bgp_ext_community.add((ro, 205479, 5006)); # clouvider from blade-tuvok bgp_ext_community.add((ro, 205479, 5007)); # google from blade-tuvok bgp_ext_community.add((ro, 205479, 5008)); # cloudflare from blade-tuvok accept; }; }; ${lib.concatMapStrings (ip: "route ${ip} blackhole;") config.services.lukegbgp.config.export.v6} ${config.services.lukegbgp.config.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; }; }; }