From 16b789287654273d696f3fec2f1890a9fe3e9948 Mon Sep 17 00:00:00 2001 From: Alex Zero Date: Mon, 5 Jul 2021 14:00:05 +0100 Subject: nixos/tayga: init --- .../from_md/release-notes/rl-2211.section.xml | 7 + nixos/doc/manual/release-notes/rl-2211.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/networking/tayga.nix | 195 +++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 nixos/modules/services/networking/tayga.nix 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 b72c4326004..61ba5e7a36b 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 @@ -1799,6 +1799,13 @@ services.github-runner.serviceOverrides.SupplementaryGroups = [ services.tandoor-recipes. + + + TAYGA, + an out-of-kernel stateless NAT64 implementation. Available as + services.tayga. + + tmate-ssh-server, diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md index e0aef342c1a..c7dc68f54d5 100644 --- a/nixos/doc/manual/release-notes/rl-2211.section.md +++ b/nixos/doc/manual/release-notes/rl-2211.section.md @@ -524,6 +524,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 215e001c2ec..81fe37a4a64 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -966,6 +966,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"; + }; + }; + }; +} -- cgit 1.4.1 From 8d47058b32f1f0fb1b21d73392f3b4e96d139a81 Mon Sep 17 00:00:00 2001 From: Georg Haas Date: Wed, 12 Oct 2022 21:16:02 +0200 Subject: nixos/tests/tayga: init --- nixos/tests/all-tests.nix | 1 + nixos/tests/tayga.nix | 235 ++++++++++++++++++++++++++++++++ pkgs/tools/networking/tayga/default.nix | 4 +- 3 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 nixos/tests/tayga.nix 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..651f9ad5545 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 = '' -- cgit 1.4.1 From 3e534cfde55bfd5a2e1348d25bd27bfe9c615746 Mon Sep 17 00:00:00 2001 From: Georg Haas Date: Sun, 16 Oct 2022 00:10:40 +0200 Subject: tayga: correct license to GPL2+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The head of tayga.c says „This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.“ --- pkgs/tools/networking/tayga/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/tools/networking/tayga/default.nix b/pkgs/tools/networking/tayga/default.nix index 651f9ad5545..ef393f5bb15 100644 --- a/pkgs/tools/networking/tayga/default.nix +++ b/pkgs/tools/networking/tayga/default.nix @@ -21,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; }; -- cgit 1.4.1