diff --git a/ops/nixos/cofractal-ams01/default.nix b/ops/nixos/cofractal-ams01/default.nix index 4043d6502f..334e0cb8b6 100644 --- a/ops/nixos/cofractal-ams01/default.nix +++ b/ops/nixos/cofractal-ams01/default.nix @@ -6,6 +6,7 @@ { imports = [ ../lib/zfs.nix + ../lib/bgp.nix ]; # Otherwise _this_ machine won't enumerate things properly. @@ -136,5 +137,31 @@ (bindMountSvc "/var/lib/tailscale" "tailscaled.service") ]; + services.lukegbgp = let + local.asn = 205479; + in { + enable = true; + config = { + local = { + routerID = "199.19.152.160"; + }; + export.v4 = [ ]; + peering.cofractal = { + local = local // { + v6 = "2a09:a446:1337:ffff::10"; + }; + remote = { + asn = 26073; + export_community = 6000; + routers = [{ + v6 = "2a09:a446:1337:ffff::2"; + } { + v6 = "2a09:a446:1337:ffff::3"; + }]; + }; + }; + }; + }; + system.stateVersion = "23.05"; } diff --git a/ops/nixos/lib/bgp.nix b/ops/nixos/lib/bgp.nix index 4086f96c2f..c3dab57294 100644 --- a/ops/nixos/lib/bgp.nix +++ b/ops/nixos/lib/bgp.nix @@ -6,8 +6,8 @@ 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; - ipv6 table ${ixName}6; filter bgp_in_${ixName}4 prefix set allnet; int set allas; @@ -19,35 +19,38 @@ let 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"} - ${lib.concatMapStringsSep "\n" (asn: "if (bgp_path ~ [= * ${toString asn} * =]) then reject;") ix.remote.drop_asns} - if (bgp_path ~ [= * 16276 * =] && gw = 2001:7f8:4::3f94:2) then gw = 2001:7f8:4::3f94:1; # OVH must go via router 1; router 2 is bork. - 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; } - 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_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; }; + ''} + + 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 (bgp_path ~ [= * 16276 * =] && gw = 2001:7f8:4::3f94:2) then gw = 2001:7f8:4::3f94:1; # OVH must go via router 1; router 2 is bork. + 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; @@ -55,12 +58,14 @@ let 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; 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, ... }: '' + ${lib.optionalString (ix.local.v4 != null) '' protocol bgp ${ixName}${toString routerNum}_4 { ${enabledSnippet router} ${passwordSnippet router} @@ -77,6 +82,7 @@ let ${prefixLimitSnippet ix.remote.prefix_limit.v4} }; }; + ''} protocol bgp ${ixName}${toString routerNum}_6 { ${enabledSnippet router} ${passwordSnippet router} @@ -86,6 +92,14 @@ let 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} + }; + ''} ipv6 { table ${ixName}6; import all; @@ -124,7 +138,8 @@ in { type = int; }; v4 = mkOption { # lukegbgp.config.peering..local.v4 - type = str; + type = nullOr str; + default = null; }; v6 = mkOption { # lukegbgp.config.peering..local.v6 type = str; @@ -132,6 +147,10 @@ in { }; }; }; + v4onv6 = mkOption { + type = bool; + default = false; + }; remote = mkOption { # lukegbgp.config.peering..remote type = submodule { options = {