summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/networking/quagga.nix187
-rw-r--r--nixos/release.nix1
-rw-r--r--nixos/tests/quagga.nix97
4 files changed, 286 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index dfc1d694e976..27fbe55cd429 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -388,6 +388,7 @@
   ./services/networking/prayer.nix
   ./services/networking/privoxy.nix
   ./services/networking/prosody.nix
+  ./services/networking/quagga.nix
   ./services/networking/quassel.nix
   ./services/networking/racoon.nix
   ./services/networking/radicale.nix
diff --git a/nixos/modules/services/networking/quagga.nix b/nixos/modules/services/networking/quagga.nix
new file mode 100644
index 000000000000..ac83da920638
--- /dev/null
+++ b/nixos/modules/services/networking/quagga.nix
@@ -0,0 +1,187 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.quagga;
+
+  services = [ "babel" "bgp" "isis" "ospf6" "ospf" "pim" "rip" "ripng" ];
+  allServices = services ++ [ "zebra" ];
+
+  isEnabled = service: cfg.${service}.enable;
+
+  daemonName = service: if service == "zebra" then service else "${service}d";
+
+  configFile = service:
+    let
+      scfg = cfg.${service};
+    in
+      if scfg.configFile != null then scfg.configFile
+      else pkgs.writeText "${daemonName service}.conf"
+        ''
+          ! Quagga ${daemonName service} configuration
+          !
+          hostname ${config.networking.hostName}
+          log syslog
+          service password-encryption
+          !
+          ${scfg.config}
+          !
+          end
+        '';
+
+  serviceOptions = service:
+    {
+      enable = mkEnableOption "the Quagga ${toUpper service} routing protocol";
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/etc/quagga/${daemonName service}.conf";
+        description = ''
+          Configuration file to use for Quagga ${daemonName service}.
+          By default the NixOS generated files are used.
+        '';
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          let
+            examples = {
+              rip = ''
+                router rip
+                  network 10.0.0.0/8
+              '';
+
+              ospf = ''
+                router ospf
+                  network 10.0.0.0/8 area 0
+              '';
+
+              bgp = ''
+                router bgp 65001
+                  neighbor 10.0.0.1 remote-as 65001
+              '';
+            };
+          in
+            examples.${service} or "";
+        description = ''
+          ${daemonName service} configuration statements.
+        '';
+      };
+
+      vtyListenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          Address to bind to for the VTY interface.
+        '';
+      };
+
+      vtyListenPort = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          TCP Port to bind to for the VTY interface.
+        '';
+      };
+    };
+
+in
+
+{
+
+  ###### interface
+
+  options.services.quagga =
+    {
+
+      zebra = (serviceOptions "zebra") // {
+
+        enable = mkOption {
+          type = types.bool;
+          default = any isEnabled services;
+          example = true;
+          description = ''
+            Whether to enable the Zebra routing manager.
+
+            The Zebra routing manager is automatically enabled
+            if any routing protocols are configured.
+          '';
+        };
+
+      };
+
+    } // (genAttrs services serviceOptions);
+
+  ###### implementation
+
+  config = mkIf (any isEnabled allServices) {
+
+    environment.systemPackages = [
+      pkgs.quagga               # for the vtysh tool
+    ];
+
+    users.users.quagga = {
+      description = "Quagga daemon user";
+      isSystemUser = true;
+      group = "quagga";
+    };
+
+    users.groups = {
+      quagga = {};
+      # Members of the quaggavty group can use vtysh to inspect the Quagga daemons
+      quaggavty = {};
+    };
+
+    systemd.services =
+      let
+        quaggaService = service:
+          let
+            scfg = cfg.${service};
+            daemon = daemonName service;
+          in
+            nameValuePair daemon ({
+              wantedBy = [ "multi-user.target" ];
+              restartTriggers = [ (configFile service) ];
+
+              serviceConfig = {
+                Type = "forking";
+                PIDFile = "/run/quagga/${daemon}.pid";
+                ExecStart = "@${pkgs.quagga}/libexec/quagga/${daemon} ${daemon} -d -f ${configFile service}"
+                  + optionalString (scfg.vtyListenAddress != "") " -A ${scfg.vtyListenAddress}"
+                  + optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}";
+                ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+                Restart = "on-abort";
+              };
+            } // (
+              if service == "zebra" then
+                {
+                  description = "Quagga Zebra routing manager";
+                  unitConfig.Documentation = "man:zebra(8)";
+                  after = [ "network.target" ];
+                  preStart = ''
+                    install -m 0755 -o quagga -g quagga -d /run/quagga
+
+                    ${pkgs.iproute}/bin/ip route flush proto zebra
+                  '';
+                }
+              else
+                {
+                  description = "Quagga ${toUpper service} routing daemon";
+                  unitConfig.Documentation = "man:${daemon}(8) man:zebra(8)";
+                  bindsTo = [ "zebra.service" ];
+                  after = [ "network.target" "zebra.service" ];
+                }
+            ));
+       in
+         listToAttrs (map quaggaService (filter isEnabled allServices));
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ tavyc ];
+
+}
diff --git a/nixos/release.nix b/nixos/release.nix
index 70a7ba5af89d..4dd1311e2d0c 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -279,6 +279,7 @@ in rec {
   tests.printing = callTest tests/printing.nix {};
   tests.proxy = callTest tests/proxy.nix {};
   tests.pumpio = callTest tests/pump.io.nix {};
+  tests.quagga = callTest tests/quagga.nix {};
   tests.quake3 = callTest tests/quake3.nix {};
   tests.runInMachine = callTest tests/run-in-machine.nix {};
   tests.sddm = callTest tests/sddm.nix {};
diff --git a/nixos/tests/quagga.nix b/nixos/tests/quagga.nix
new file mode 100644
index 000000000000..b9644b4768c0
--- /dev/null
+++ b/nixos/tests/quagga.nix
@@ -0,0 +1,97 @@
+# This test runs Quagga and checks if OSPF routing works.
+#
+# Network topology:
+#   [ client ]--net1--[ router1 ]--net2--[ router2 ]--net3--[ server ]
+#
+# All interfaces are in OSPF Area 0.
+
+import ./make-test.nix ({ pkgs, ... }:
+  let
+
+    ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ip4).address;
+
+    ospfConf = ''
+      interface eth2
+        ip ospf hello-interval 1
+        ip ospf dead-interval 5
+      !
+      router ospf
+        network 192.168.0.0/16 area 0
+    '';
+
+  in
+    {
+      name = "quagga";
+
+      meta = with pkgs.stdenv.lib.maintainers; {
+        maintainers = [ tavyc ];
+      };
+
+      nodes = {
+
+        client =
+          { config, pkgs, nodes, ... }:
+          {
+            virtualisation.vlans = [ 1 ];
+            networking.defaultGateway = ifAddr nodes.router1 "eth1";
+          };
+
+        router1 =
+          { config, pkgs, nodes, ... }:
+          {
+            virtualisation.vlans = [ 1 2 ];
+            boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+            networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospf -j ACCEPT";
+            services.quagga.ospf = {
+              enable = true;
+              config = ospfConf;
+            };
+          };
+
+        router2 =
+          { config, pkgs, nodes, ... }:
+          {
+            virtualisation.vlans = [ 3 2 ];
+            boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+            networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospf -j ACCEPT";
+            services.quagga.ospf = {
+              enable = true;
+              config = ospfConf;
+            };
+          };
+
+        server =
+          { config, pkgs, nodes, ... }:
+          {
+            virtualisation.vlans = [ 3 ];
+            networking.defaultGateway = ifAddr nodes.router2 "eth1";
+            networking.firewall.allowedTCPPorts = [ 80 ];
+            networking.firewall.allowPing = true;
+            services.httpd.enable = true;
+            services.httpd.adminAddr = "foo@example.com";
+          };
+      };
+
+      testScript =
+        { nodes, ... }:
+        ''
+          startAll;
+
+          # Wait for the networking to start on all machines
+          $_->waitForUnit("network.target") foreach values %vms;
+
+          # Wait for OSPF to form adjacencies
+          for my $gw ($router1, $router2) {
+              $gw->waitForUnit("ospfd");
+              $gw->waitUntilSucceeds("vtysh -c 'show ip ospf neighbor' | grep Full");
+              $gw->waitUntilSucceeds("vtysh -c 'show ip route' | grep '^O>'");
+          }
+
+          # Test ICMP.
+          $client->succeed("ping -c 3 server >&2");
+
+          # Test whether HTTP works.
+          $server->waitForUnit("httpd");
+          $client->succeed("curl --fail http://server/ >&2");
+        '';
+    })