summary refs log tree commit diff
diff options
context:
space:
mode:
authorRyan Lahfa <masterancpp@gmail.com>2022-12-01 21:14:50 +0100
committerGitHub <noreply@github.com>2022-12-01 21:14:50 +0100
commit5d87a1b9b8dd88ac213244aa9ce6094b68d44433 (patch)
treec438b1af8f490e3e6801c6f698d6d6cc4313ce40
parent831b9b4c3628f1c2858b4ec82e1de4a2ecba2c9c (diff)
parent3e534cfde55bfd5a2e1348d25bd27bfe9c615746 (diff)
downloadnixpkgs-5d87a1b9b8dd88ac213244aa9ce6094b68d44433.tar
nixpkgs-5d87a1b9b8dd88ac213244aa9ce6094b68d44433.tar.gz
nixpkgs-5d87a1b9b8dd88ac213244aa9ce6094b68d44433.tar.bz2
nixpkgs-5d87a1b9b8dd88ac213244aa9ce6094b68d44433.tar.lz
nixpkgs-5d87a1b9b8dd88ac213244aa9ce6094b68d44433.tar.xz
nixpkgs-5d87a1b9b8dd88ac213244aa9ce6094b68d44433.tar.zst
nixpkgs-5d87a1b9b8dd88ac213244aa9ce6094b68d44433.zip
Merge pull request #195735 from hax404/tayga_init
nixos/tayga: init
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2211.section.xml7
-rw-r--r--nixos/doc/manual/release-notes/rl-2211.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/networking/tayga.nix195
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/tayga.nix235
-rw-r--r--pkgs/tools/networking/tayga/default.nix6
7 files changed, 445 insertions, 2 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index f912c467c7e..1fe25e7be78 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -1811,6 +1811,13 @@ services.github-runner.serviceOverrides.SupplementaryGroups = [
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="http://www.litech.org/tayga/">TAYGA</link>,
+          an out-of-kernel stateless NAT64 implementation. Available as
+          <link linkend="opt-services.tayga.enable">services.tayga</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://github.com/tmate-io/tmate-ssh-server">tmate-ssh-server</link>,
           server side part of
           <link xlink:href="https://tmate.io/">tmate</link>. Available
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index d427597cc7b..b6e0824f8b4 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -526,6 +526,8 @@ In addition to numerous new and upgraded packages, this release includes the fol
 
 - [Tandoor Recipes](https://tandoor.dev), a self-hosted multi-tenant recipe collection. Available as [services.tandoor-recipes](options.html#opt-services.tandoor-recipes.enable).
 
+- [TAYGA](http://www.litech.org/tayga/), an out-of-kernel stateless NAT64 implementation. Available as [services.tayga](#opt-services.tayga.enable).
+
 - [tmate-ssh-server](https://github.com/tmate-io/tmate-ssh-server), server side part of [tmate](https://tmate.io/). Available as [services.tmate-ssh-server](#opt-services.tmate-ssh-server.enable).
 
 - [Uptime Kuma](https://uptime.kuma.pet/), a fancy self-hosted monitoring tool. Available as [services.uptime-kuma](#opt-services.uptime-kuma.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d5550cd878e..18ce9e8a151 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -967,6 +967,7 @@
   ./services/networking/syncthing-relay.nix
   ./services/networking/syncplay.nix
   ./services/networking/tailscale.nix
+  ./services/networking/tayga.nix
   ./services/networking/tcpcrypt.nix
   ./services/networking/teamspeak3.nix
   ./services/networking/tedicross.nix
diff --git a/nixos/modules/services/networking/tayga.nix b/nixos/modules/services/networking/tayga.nix
new file mode 100644
index 00000000000..299ae2777f7
--- /dev/null
+++ b/nixos/modules/services/networking/tayga.nix
@@ -0,0 +1,195 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.tayga;
+
+  # Converts an address set to a string
+  strAddr = addr: "${addr.address}/${toString addr.prefixLength}";
+
+  configFile = pkgs.writeText "tayga.conf" ''
+    tun-device ${cfg.tunDevice}
+
+    ipv4-addr ${cfg.ipv4.address}
+    ${optionalString (cfg.ipv6.address != null) "ipv6-addr ${cfg.ipv6.address}"}
+
+    prefix ${strAddr cfg.ipv6.pool}
+    dynamic-pool ${strAddr cfg.ipv4.pool}
+    data-dir ${cfg.dataDir}
+  '';
+
+  addrOpts = v:
+    assert v == 4 || v == 6;
+    {
+      options = {
+        address = mkOption {
+          type = types.str;
+          description = lib.mdDoc "IPv${toString v} address.";
+        };
+
+        prefixLength = mkOption {
+          type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
+          description = lib.mdDoc ''
+            Subnet mask of the interface, specified as the number of
+            bits in the prefix ("${if v == 4 then "24" else "64"}").
+          '';
+        };
+      };
+    };
+
+  versionOpts = v: {
+    options = {
+      router = {
+        address = mkOption {
+          type = types.str;
+          description = lib.mdDoc "The IPv${toString v} address of the router.";
+        };
+      };
+
+      address = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = lib.mdDoc "The source IPv${toString v} address of the TAYGA server.";
+      };
+
+      pool = mkOption {
+        type = with types; nullOr (submodule (addrOpts v));
+        description = lib.mdDoc "The pool of IPv${toString v} addresses which are used for translation.";
+      };
+    };
+  };
+in
+{
+  options = {
+    services.tayga = {
+      enable = mkEnableOption (lib.mdDoc "Tayga");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.tayga;
+        defaultText = lib.literalMD "pkgs.tayga";
+        description = lib.mdDoc "This option specifies the TAYGA package to use.";
+      };
+
+      ipv4 = mkOption {
+        type = types.submodule (versionOpts 4);
+        description = lib.mdDoc "IPv4-specific configuration.";
+        example = literalExpression ''
+          {
+            address = "192.0.2.0";
+            router = {
+              address = "192.0.2.1";
+            };
+            pool = {
+              address = "192.0.2.1";
+              prefixLength = 24;
+            };
+          }
+        '';
+      };
+
+      ipv6 = mkOption {
+        type = types.submodule (versionOpts 6);
+        description = lib.mdDoc "IPv6-specific configuration.";
+        example = literalExpression ''
+          {
+            address = "2001:db8::1";
+            router = {
+              address = "64:ff9b::1";
+            };
+            pool = {
+              address = "64:ff9b::";
+              prefixLength = 96;
+            };
+          }
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/tayga";
+        description = lib.mdDoc "Directory for persistent data";
+      };
+
+      tunDevice = mkOption {
+        type = types.str;
+        default = "nat64";
+        description = lib.mdDoc "Name of the nat64 tun device";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.interfaces."${cfg.tunDevice}" = {
+      virtual = true;
+      virtualType = "tun";
+      virtualOwner = mkIf config.networking.useNetworkd "";
+      ipv4 = {
+        addresses = [
+          { address = cfg.ipv4.router.address; prefixLength = 32; }
+        ];
+        routes = [
+          cfg.ipv4.pool
+        ];
+      };
+      ipv6 = {
+        addresses = [
+          { address = cfg.ipv6.router.address; prefixLength = 128; }
+        ];
+        routes = [
+          cfg.ipv6.pool
+        ];
+      };
+    };
+
+    systemd.services.tayga = {
+      description = "Stateless NAT64 implementation";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/tayga -d --nodetach --config ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        Restart = "always";
+
+        # Hardening Score:
+        #  - nixos-scripts: 2.1
+        #  - systemd-networkd: 1.6
+        ProtectHome = true;
+        SystemCallFilter = [
+          "@network-io"
+          "@system-service"
+          "~@privileged"
+          "~@resources"
+        ];
+        ProtectKernelLogs = true;
+        AmbientCapabilities = [
+          "CAP_NET_ADMIN"
+        ];
+        CapabilityBoundingSet = "";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+        ];
+        StateDirectory = "tayga";
+        DynamicUser = mkIf config.networking.useNetworkd true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        ProtectHostname = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictNamespaces = true;
+        NoNewPrivileges = true;
+        ProtectControlGroups = true;
+        SystemCallArchitectures = "native";
+        PrivateTmp = true;
+        LockPersonality = true;
+        ProtectSystem = true;
+        PrivateUsers = true;
+        ProtectProc = "invisible";
+      };
+    };
+  };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index f97d7b184af..895cbe4290d 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -638,6 +638,7 @@ in {
   systemd-misc = handleTest ./systemd-misc.nix {};
   tandoor-recipes = handleTest ./tandoor-recipes.nix {};
   taskserver = handleTest ./taskserver.nix {};
+  tayga = handleTest ./tayga.nix {};
   teeworlds = handleTest ./teeworlds.nix {};
   telegraf = handleTest ./telegraf.nix {};
   teleport = handleTest ./teleport.nix {};
diff --git a/nixos/tests/tayga.nix b/nixos/tests/tayga.nix
new file mode 100644
index 00000000000..44974f6efea
--- /dev/null
+++ b/nixos/tests/tayga.nix
@@ -0,0 +1,235 @@
+# This test verifies that we can ping an IPv4-only server from an IPv6-only
+# client via a NAT64 router. The hosts and networks are configured as follows:
+#
+#        +------
+# Client | eth1    Address: 2001:db8::2/64
+#        |  |      Route:   64:ff9b::/96 via 2001:db8::1
+#        +--|---
+#           | VLAN 3
+#        +--|---
+#        | eth2    Address: 2001:db8::1/64
+# Router |
+#        | nat64   Address: 64:ff9b::1/128
+#        |         Route:   64:ff9b::/96
+#        |         Address: 192.0.2.0/32
+#        |         Route:   192.0.2.0/24
+#        |
+#        | eth1    Address: 100.64.0.1/24
+#        +--|---
+#           | VLAN 2
+#        +--|---
+# Server | eth1    Address: 100.64.0.2/24
+#        |         Route:   192.0.2.0/24 via 100.64.0.1
+#        +------
+
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "tayga";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hax404 ];
+  };
+
+  nodes = {
+    # The server is configured with static IPv4 addresses. RFC 6052 Section 3.1
+    # disallows the mapping of non-global IPv4 addresses like RFC 1918 into the
+    # Well-Known Prefix 64:ff9b::/96. TAYGA also does not allow the mapping of
+    # documentation space (RFC 5737). To circumvent this, 100.64.0.2/24 from
+    # RFC 6589 (Carrier Grade NAT) is used here.
+    # To reach the IPv4 address pool of the NAT64 gateway, there is a static
+    # route configured. In normal cases, where the router would also source NAT
+    # the pool addresses to one IPv4 addresses, this would not be needed.
+    server = {
+      virtualisation.vlans = [
+        2 # towards router
+      ];
+      networking = {
+        useDHCP = false;
+        interfaces.eth1 = lib.mkForce {};
+      };
+      systemd.network = {
+        enable = true;
+        networks."vlan1" = {
+          matchConfig.Name = "eth1";
+          address = [
+            "100.64.0.2/24"
+          ];
+          routes = [
+            { routeConfig = { Destination = "192.0.2.0/24"; Gateway = "100.64.0.1"; }; }
+          ];
+        };
+      };
+    };
+
+    # The router is configured with static IPv4 addresses towards the server
+    # and IPv6 addresses towards the client. For NAT64, the Well-Known prefix
+    # 64:ff9b::/96 is used. NAT64 is done with TAYGA which provides the
+    # tun-interface nat64 and does the translation over it. The IPv6 packets
+    # are sent to this interfaces and received as IPv4 packets and vice versa.
+    # As TAYGA only translates IPv6 addresses to dedicated IPv4 addresses, it
+    # needs a pool of IPv4 addresses which must be at least as big as the
+    # expected amount of clients. In this test, the packets from the pool are
+    # directly routed towards the client. In normal cases, there would be a
+    # second source NAT44 to map all clients behind one IPv4 address.
+    router_systemd = {
+      boot.kernel.sysctl = {
+        "net.ipv4.ip_forward" = 1;
+        "net.ipv6.conf.all.forwarding" = 1;
+      };
+
+      virtualisation.vlans = [
+        2 # towards server
+        3 # towards client
+      ];
+
+      networking = {
+        useDHCP = false;
+        useNetworkd = true;
+        firewall.enable = false;
+        interfaces.eth1 = lib.mkForce {
+          ipv4 = {
+            addresses = [ { address = "100.64.0.1"; prefixLength = 24; } ];
+          };
+        };
+        interfaces.eth2 = lib.mkForce {
+          ipv6 = {
+            addresses = [ { address = "2001:db8::1"; prefixLength = 64; } ];
+          };
+        };
+      };
+
+      services.tayga = {
+        enable = true;
+        ipv4 = {
+          address = "192.0.2.0";
+          router = {
+            address = "192.0.2.1";
+          };
+          pool = {
+            address = "192.0.2.0";
+            prefixLength = 24;
+          };
+        };
+        ipv6 = {
+          address = "2001:db8::1";
+          router = {
+            address = "64:ff9b::1";
+          };
+          pool = {
+            address = "64:ff9b::";
+            prefixLength = 96;
+          };
+        };
+      };
+    };
+
+    router_nixos = {
+      boot.kernel.sysctl = {
+        "net.ipv4.ip_forward" = 1;
+        "net.ipv6.conf.all.forwarding" = 1;
+      };
+
+      virtualisation.vlans = [
+        2 # towards server
+        3 # towards client
+      ];
+
+      networking = {
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1 = lib.mkForce {
+          ipv4 = {
+            addresses = [ { address = "100.64.0.1"; prefixLength = 24; } ];
+          };
+        };
+        interfaces.eth2 = lib.mkForce {
+          ipv6 = {
+            addresses = [ { address = "2001:db8::1"; prefixLength = 64; } ];
+          };
+        };
+      };
+
+      services.tayga = {
+        enable = true;
+        ipv4 = {
+          address = "192.0.2.0";
+          router = {
+            address = "192.0.2.1";
+          };
+          pool = {
+            address = "192.0.2.0";
+            prefixLength = 24;
+          };
+        };
+        ipv6 = {
+          address = "2001:db8::1";
+          router = {
+            address = "64:ff9b::1";
+          };
+          pool = {
+            address = "64:ff9b::";
+            prefixLength = 96;
+          };
+        };
+      };
+    };
+
+    # The client is configured with static IPv6 addresses. It has also a static
+    # route for the NAT64 IP space where the IPv4 addresses are mapped in. In
+    # normal cases, there would be only a default route.
+    client = {
+      virtualisation.vlans = [
+        3 # towards router
+      ];
+
+      networking = {
+        useDHCP = false;
+        interfaces.eth1 = lib.mkForce {};
+      };
+
+      systemd.network = {
+        enable = true;
+        networks."vlan1" = {
+          matchConfig.Name = "eth1";
+          address = [
+            "2001:db8::2/64"
+          ];
+          routes = [
+            { routeConfig = { Destination = "64:ff9b::/96"; Gateway = "2001:db8::1"; }; }
+          ];
+        };
+      };
+      environment.systemPackages = [ pkgs.mtr ];
+    };
+  };
+
+  testScript = ''
+    # start client and server
+    for machine in client, server:
+      machine.wait_for_unit("network-online.target")
+      machine.log(machine.execute("ip addr")[1])
+      machine.log(machine.execute("ip route")[1])
+      machine.log(machine.execute("ip -6 route")[1])
+
+    # test systemd-networkd and nixos-scripts based router
+    for router in router_systemd, router_nixos:
+      router.start()
+      router.wait_for_unit("network-online.target")
+      router.wait_for_unit("tayga.service")
+      router.log(machine.execute("ip addr")[1])
+      router.log(machine.execute("ip route")[1])
+      router.log(machine.execute("ip -6 route")[1])
+
+      with subtest("Wait for tayga"):
+        router.wait_for_unit("tayga.service")
+
+      with subtest("Test ICMP"):
+        client.wait_until_succeeds("ping -c 3 64:ff9b::100.64.0.2 >&2")
+
+      with subtest("Test ICMP and show a traceroute"):
+        client.wait_until_succeeds("mtr --show-ips --report-wide 64:ff9b::100.64.0.2 >&2")
+
+      router.log(router.execute("systemd-analyze security tayga.service")[1])
+      router.shutdown()
+  '';
+})
diff --git a/pkgs/tools/networking/tayga/default.nix b/pkgs/tools/networking/tayga/default.nix
index 8d0de6a6c18..ef393f5bb15 100644
--- a/pkgs/tools/networking/tayga/default.nix
+++ b/pkgs/tools/networking/tayga/default.nix
@@ -1,4 +1,4 @@
-{ lib, stdenv, fetchurl }:
+{ lib, stdenv, fetchurl, nixosTests }:
 
 stdenv.mkDerivation rec {
   version = "0.9.2";
@@ -9,6 +9,8 @@ stdenv.mkDerivation rec {
     sha256 = "1700y121lhvpna49bjpssb7jq1abj9qw5wxgjn8gzp6jm4kpj7rb";
   };
 
+  passthru.tests.tayga = nixosTests.tayga;
+
   meta = with lib; {
     description = "Userland stateless NAT64 daemon";
     longDescription = ''
@@ -19,7 +21,7 @@ stdenv.mkDerivation rec {
       for networks where dedicated NAT64 hardware would be overkill.
     '';
     homepage = "http://www.litech.org/tayga";
-    license = licenses.gpl2;
+    license = licenses.gpl2Plus;
     maintainers = with maintainers; [ _0x4A6F ];
     platforms = platforms.linux;
   };