1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9let
10 cfg = config.services.tayga;
11
12 # Converts an address set to a string
13 strAddr = addr: "${addr.address}/${toString addr.prefixLength}";
14
15 configFile = pkgs.writeText "tayga.conf" ''
16 tun-device ${cfg.tunDevice}
17
18 ipv4-addr ${cfg.ipv4.address}
19 ${optionalString (cfg.ipv6.address != null) "ipv6-addr ${cfg.ipv6.address}"}
20
21 prefix ${strAddr cfg.ipv6.pool}
22 dynamic-pool ${strAddr cfg.ipv4.pool}
23 data-dir ${cfg.dataDir}
24
25 ${concatStringsSep "\n" (mapAttrsToList (ipv4: ipv6: "map " + ipv4 + " " + ipv6) cfg.mappings)}
26 '';
27
28 addrOpts =
29 v:
30 assert v == 4 || v == 6;
31 {
32 options = {
33 address = mkOption {
34 type = types.str;
35 description = "IPv${toString v} address.";
36 };
37
38 prefixLength = mkOption {
39 type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
40 description = ''
41 Subnet mask of the interface, specified as the number of
42 bits in the prefix ("${if v == 4 then "24" else "64"}").
43 '';
44 };
45 };
46 };
47
48 versionOpts = v: {
49 options = {
50 router = {
51 address = mkOption {
52 type = types.str;
53 description = "The IPv${toString v} address of the router.";
54 };
55 };
56
57 address = mkOption {
58 type = types.nullOr types.str;
59 default = null;
60 description = "The source IPv${toString v} address of the TAYGA server.";
61 };
62
63 pool = mkOption {
64 type = with types; nullOr (submodule (addrOpts v));
65 description = "The pool of IPv${toString v} addresses which are used for translation.";
66 };
67 };
68 };
69in
70{
71 options = {
72 services.tayga = {
73 enable = mkEnableOption "Tayga";
74
75 package = mkPackageOption pkgs "tayga" { };
76
77 ipv4 = mkOption {
78 type = types.submodule (versionOpts 4);
79 description = "IPv4-specific configuration.";
80 example = literalExpression ''
81 {
82 address = "192.0.2.0";
83 router = {
84 address = "192.0.2.1";
85 };
86 pool = {
87 address = "192.0.2.1";
88 prefixLength = 24;
89 };
90 }
91 '';
92 };
93
94 ipv6 = mkOption {
95 type = types.submodule (versionOpts 6);
96 description = "IPv6-specific configuration.";
97 example = literalExpression ''
98 {
99 address = "2001:db8::1";
100 router = {
101 address = "64:ff9b::1";
102 };
103 pool = {
104 address = "64:ff9b::";
105 prefixLength = 96;
106 };
107 }
108 '';
109 };
110
111 dataDir = mkOption {
112 type = types.path;
113 default = "/var/lib/tayga";
114 description = "Directory for persistent data.";
115 };
116
117 tunDevice = mkOption {
118 type = types.str;
119 default = "nat64";
120 description = "Name of the nat64 tun device.";
121 };
122
123 mappings = mkOption {
124 type = types.attrsOf types.str;
125 default = { };
126 description = "Static IPv4 -> IPv6 host mappings.";
127 example = literalExpression ''
128 {
129 "192.168.5.42" = "2001:db8:1:4444::1";
130 "192.168.5.43" = "2001:db8:1:4444::2";
131 "192.168.255.2" = "2001:db8:1:569::143";
132 }
133 '';
134 };
135 };
136 };
137
138 config = mkIf cfg.enable {
139 assertions = [
140 {
141 assertion = allUnique (attrValues cfg.mappings);
142 message = "Neither the IPv4 nor the IPv6 addresses must be entered twice in the mappings.";
143 }
144 ];
145
146 networking.interfaces."${cfg.tunDevice}" = {
147 virtual = true;
148 virtualType = "tun";
149 virtualOwner = mkIf config.networking.useNetworkd "";
150 ipv4 = {
151 addresses = [
152 {
153 address = cfg.ipv4.router.address;
154 prefixLength = 32;
155 }
156 ];
157 routes = [
158 cfg.ipv4.pool
159 ];
160 };
161 ipv6 = {
162 addresses = [
163 {
164 address = cfg.ipv6.router.address;
165 prefixLength = 128;
166 }
167 ];
168 routes = [
169 cfg.ipv6.pool
170 ];
171 };
172 };
173
174 systemd.services.tayga = {
175 description = "Stateless NAT64 implementation";
176 wantedBy = [ "multi-user.target" ];
177 after = [ "network.target" ];
178
179 serviceConfig = {
180 ExecStart = "${cfg.package}/bin/tayga -d --nodetach --config ${configFile}";
181 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
182 Restart = "always";
183
184 # Hardening Score:
185 # - nixos-scripts: 2.1
186 # - systemd-networkd: 1.6
187 ProtectHome = true;
188 SystemCallFilter = [
189 "@network-io"
190 "@system-service"
191 "~@privileged"
192 "~@resources"
193 ];
194 ProtectKernelLogs = true;
195 AmbientCapabilities = [
196 "CAP_NET_ADMIN"
197 ];
198 CapabilityBoundingSet = "";
199 RestrictAddressFamilies = [
200 "AF_INET"
201 "AF_INET6"
202 "AF_NETLINK"
203 ];
204 StateDirectory = "tayga";
205 DynamicUser = mkIf config.networking.useNetworkd true;
206 MemoryDenyWriteExecute = true;
207 RestrictRealtime = true;
208 RestrictSUIDSGID = true;
209 ProtectHostname = true;
210 ProtectKernelModules = true;
211 ProtectKernelTunables = true;
212 RestrictNamespaces = true;
213 NoNewPrivileges = true;
214 ProtectControlGroups = true;
215 SystemCallArchitectures = "native";
216 PrivateTmp = true;
217 LockPersonality = true;
218 ProtectSystem = true;
219 PrivateUsers = true;
220 ProtectProc = "invisible";
221 };
222 };
223 };
224}