1# This module enables Network Address Translation (NAT).
2# XXX: todo: support multiple upstream links
3# see http://yesican.chsoft.biz/lartc/MultihomedLinuxNetworking.html
4
5{ config, lib, pkgs, ... }:
6
7with lib;
8
9let
10
11 cfg = config.networking.nat;
12
13in
14
15{
16
17 options = {
18
19 networking.nat.enable = mkOption {
20 type = types.bool;
21 default = false;
22 description = lib.mdDoc ''
23 Whether to enable Network Address Translation (NAT).
24 '';
25 };
26
27 networking.nat.enableIPv6 = mkOption {
28 type = types.bool;
29 default = false;
30 description = lib.mdDoc ''
31 Whether to enable IPv6 NAT.
32 '';
33 };
34
35 networking.nat.internalInterfaces = mkOption {
36 type = types.listOf types.str;
37 default = [ ];
38 example = [ "eth0" ];
39 description = lib.mdDoc ''
40 The interfaces for which to perform NAT. Packets coming from
41 these interface and destined for the external interface will
42 be rewritten.
43 '';
44 };
45
46 networking.nat.internalIPs = mkOption {
47 type = types.listOf types.str;
48 default = [ ];
49 example = [ "192.168.1.0/24" ];
50 description = lib.mdDoc ''
51 The IP address ranges for which to perform NAT. Packets
52 coming from these addresses (on any interface) and destined
53 for the external interface will be rewritten.
54 '';
55 };
56
57 networking.nat.internalIPv6s = mkOption {
58 type = types.listOf types.str;
59 default = [ ];
60 example = [ "fc00::/64" ];
61 description = lib.mdDoc ''
62 The IPv6 address ranges for which to perform NAT. Packets
63 coming from these addresses (on any interface) and destined
64 for the external interface will be rewritten.
65 '';
66 };
67
68 networking.nat.externalInterface = mkOption {
69 type = types.nullOr types.str;
70 default = null;
71 example = "eth1";
72 description = lib.mdDoc ''
73 The name of the external network interface.
74 '';
75 };
76
77 networking.nat.externalIP = mkOption {
78 type = types.nullOr types.str;
79 default = null;
80 example = "203.0.113.123";
81 description = lib.mdDoc ''
82 The public IP address to which packets from the local
83 network are to be rewritten. If this is left empty, the
84 IP address associated with the external interface will be
85 used.
86 '';
87 };
88
89 networking.nat.externalIPv6 = mkOption {
90 type = types.nullOr types.str;
91 default = null;
92 example = "2001:dc0:2001:11::175";
93 description = lib.mdDoc ''
94 The public IPv6 address to which packets from the local
95 network are to be rewritten. If this is left empty, the
96 IP address associated with the external interface will be
97 used.
98 '';
99 };
100
101 networking.nat.forwardPorts = mkOption {
102 type = with types; listOf (submodule {
103 options = {
104 sourcePort = mkOption {
105 type = types.either types.int (types.strMatching "[[:digit:]]+:[[:digit:]]+");
106 example = 8080;
107 description = lib.mdDoc "Source port of the external interface; to specify a port range, use a string with a colon (e.g. \"60000:61000\")";
108 };
109
110 destination = mkOption {
111 type = types.str;
112 example = "10.0.0.1:80";
113 description = lib.mdDoc "Forward connection to destination ip:port (or [ipv6]:port); to specify a port range, use ip:start-end";
114 };
115
116 proto = mkOption {
117 type = types.str;
118 default = "tcp";
119 example = "udp";
120 description = lib.mdDoc "Protocol of forwarded connection";
121 };
122
123 loopbackIPs = mkOption {
124 type = types.listOf types.str;
125 default = [ ];
126 example = literalExpression ''[ "55.1.2.3" ]'';
127 description = lib.mdDoc "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort` from the host itself and from other hosts behind NAT";
128 };
129 };
130 });
131 default = [ ];
132 example = [
133 { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; }
134 { sourcePort = 8080; destination = "[fc00::2]:80"; proto = "tcp"; }
135 ];
136 description = lib.mdDoc ''
137 List of forwarded ports from the external interface to
138 internal destinations by using DNAT. Destination can be
139 IPv6 if IPv6 NAT is enabled.
140 '';
141 };
142
143 networking.nat.dmzHost = mkOption {
144 type = types.nullOr types.str;
145 default = null;
146 example = "10.0.0.1";
147 description = lib.mdDoc ''
148 The local IP address to which all traffic that does not match any
149 forwarding rule is forwarded.
150 '';
151 };
152
153 };
154
155
156 config = mkIf config.networking.nat.enable {
157
158 assertions = [
159 {
160 assertion = cfg.enableIPv6 -> config.networking.enableIPv6;
161 message = "networking.nat.enableIPv6 requires networking.enableIPv6";
162 }
163 {
164 assertion = (cfg.dmzHost != null) -> (cfg.externalInterface != null);
165 message = "networking.nat.dmzHost requires networking.nat.externalInterface";
166 }
167 {
168 assertion = (cfg.forwardPorts != [ ]) -> (cfg.externalInterface != null);
169 message = "networking.nat.forwardPorts requires networking.nat.externalInterface";
170 }
171 ];
172
173 # Use the same iptables package as in config.networking.firewall.
174 # When the firewall is enabled, this should be deduplicated without any
175 # error.
176 environment.systemPackages = [ config.networking.firewall.package ];
177
178 boot = {
179 kernelModules = [ "nf_nat_ftp" ];
180 kernel.sysctl = {
181 "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
182 "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
183 } // optionalAttrs cfg.enableIPv6 {
184 # Do not prevent IPv6 autoconfiguration.
185 # See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
186 "net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
187 "net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
188
189 # Forward IPv6 packets.
190 "net.ipv6.conf.all.forwarding" = mkOverride 99 true;
191 "net.ipv6.conf.default.forwarding" = mkOverride 99 true;
192 };
193 };
194
195 };
196}