1{ lib, config, ... } @ inputs:
2
3with lib;
4let
5 inherit (import ../../lib/ipv4.nix inputs) ipv4;
6
7 cfg = config.modules.router;
8
9 leaseType = types.submodule {
10 options = {
11 macAddress = mkOption {
12 type = types.str;
13 example = "00:00:00:00:00:00";
14 };
15 ipAddress = mkOption {
16 type = types.str;
17 example = "10.0.0.10";
18 };
19 };
20 };
21
22 interfaceType = types.submodule {
23 options = {
24 name = mkOption {
25 type = types.str;
26 example = "eth0";
27 };
28 macAddress = mkOption {
29 type = types.str;
30 example = "00:00:00:00:00:00";
31 };
32 adoptMacAddress = mkOption {
33 type = types.nullOr types.str;
34 example = "00:00:00:00:00:00";
35 };
36 cidr = mkOption {
37 type = types.str;
38 default = "0.0.0.0/0";
39 example = "10.0.0.1/24";
40 };
41 };
42 };
43
44 extern = cfg.interfaces.external;
45 intern = cfg.interfaces.internal;
46in {
47 options.modules.router = let
48 defaultAddress = if intern != null
49 then ipv4.prettyIp (ipv4.cidrToIpAddress intern.cidr)
50 else "127.0.0.1";
51 in {
52 address = mkOption {
53 type = types.str;
54 default = defaultAddress;
55 example = "127.0.0.1";
56 };
57 mdns = mkOption {
58 type = types.bool;
59 default = !config.services.avahi.enable;
60 };
61 ipv6 = mkOption {
62 type = types.bool;
63 default = false;
64 };
65 interfaces = {
66 external = mkOption {
67 type = interfaceType;
68 };
69 internal = mkOption {
70 type = types.nullOr interfaceType;
71 default = null;
72 };
73 };
74 leases = mkOption {
75 default = [];
76 type = types.listOf leaseType;
77 description = "List of reserved IP address leases";
78 };
79 };
80
81 config = let
82 links = {
83 "10-${extern.name}" = {
84 matchConfig.PermanentMACAddress = extern.macAddress;
85 linkConfig = {
86 Description = "External Network Interface";
87 Name = extern.name;
88 MACAddress = extern.adoptMacAddress;
89 MTUBytes = "1500";
90 };
91 };
92 } // (optionalAttrs (intern != null) {
93 "11-${intern.name}" = {
94 matchConfig.PermanentMACAddress = intern.macAddress;
95 linkConfig = {
96 Description = "Internal Network Interface";
97 Name = intern.name;
98 MTUBytes = "1500";
99 };
100 };
101 });
102 in mkIf cfg.enable {
103 networking = {
104 useNetworkd = true;
105 hosts."127.0.0.2" = mkForce [];
106 networkmanager.enable = mkForce false;
107 firewall = mkIf (intern != null) {
108 trustedInterfaces = [ "lo" intern.name ];
109 };
110 nameservers = [
111 "1.1.1.1#cloudflare-dns.com"
112 "9.9.9.9#dns.quad9.net"
113 "8.8.8.8#dns.google"
114 ] ++ (optionals cfg.ipv6 [
115 "2606:4700:4700::1111#cloudflare-dns.com"
116 "2620:fe::9#dns.quad9.net"
117 "2001:4860:4860::8888#dns.google"
118 ]);
119 };
120
121 boot.initrd.systemd.network = {
122 enable = true;
123 inherit links;
124 };
125
126 systemd.network = {
127 enable = true;
128 inherit links;
129 networks = let
130 gatewayAddress = ipv4.prettyIp (ipv4.cidrToIpAddress intern.cidr);
131 in {
132 "10-${extern.name}" = {
133 name = extern.name;
134 networkConfig = {
135 DHCP = if cfg.ipv6 then "yes" else "ipv4";
136 IPv4Forwarding = true;
137 IPv6Forwarding = true;
138 IPv6AcceptRA = mkIf cfg.ipv6 true;
139 };
140 cakeConfig = {
141 Parent = "root";
142 };
143 dhcpV4Config = {
144 UseDNS = false;
145 UseDomains = false;
146 UseNTP = !cfg.timeserver.enable;
147 };
148 dhcpV6Config = mkIf cfg.ipv6 {
149 WithoutRA = "solicit";
150 UseDNS = false;
151 UseDomains = false;
152 UseAddress = false;
153 DUIDType = "link-layer";
154 DUIDRawData = mkIf (extern.adoptMacAddress != null) "00:01:${extern.adoptMacAddress}";
155 };
156 dhcpPrefixDelegationConfig = mkIf cfg.ipv6 {
157 UplinkInterface = ":self";
158 Announce = false;
159 };
160 ipv6AcceptRAConfig = mkIf cfg.ipv6 {
161 UseDNS = false;
162 UseDomains = false;
163 DHCPv6Client = "always";
164 Token = mkIf (extern.adoptMacAddress != null) "static:::${extern.adoptMacAddress}";
165 };
166 };
167 } // (optionalAttrs (intern != null) {
168 "11-${intern.name}" = {
169 name = intern.name;
170 networkConfig = {
171 Address = intern.cidr;
172 DHCPServer = true;
173 IPv4Forwarding = true;
174 IPv6Forwarding = cfg.ipv6;
175 IPMasquerade = "ipv4";
176 ConfigureWithoutCarrier = true;
177 MulticastDNS = cfg.mdns;
178 DHCPPrefixDelegation = cfg.ipv6;
179 IPv6SendRA = cfg.ipv6;
180 IPv6AcceptRA = mkIf cfg.ipv6 false;
181 };
182 fairQueueingControlledDelayConfig = {
183 Parent = "root";
184 };
185 dhcpServerStaticLeases = builtins.map (lease: {
186 Address = lease.ipAddress;
187 MACAddress = lease.macAddress;
188 }) cfg.leases;
189 dhcpServerConfig = {
190 EmitDNS = true;
191 EmitNTP = true;
192 DNS = gatewayAddress;
193 NTP = gatewayAddress;
194 DefaultLeaseTimeSec = 43200;
195 MaxLeaseTimeSec = 86400;
196 };
197 dhcpPrefixDelegationConfig = mkIf cfg.ipv6 {
198 UplinkInterface = extern.name;
199 Token = "static:::1";
200 Announce = true;
201 };
202 };
203 });
204 };
205
206 services.resolved = {
207 enable = true;
208 llmnr = "false";
209 domains = [ "~." ];
210 fallbackDns = [
211 "1.0.0.1"
212 "8.8.4.4"
213 ] ++ (optionals cfg.ipv6 [
214 "2606:4700:4700::1001"
215 "2001:4860:4860::8844"
216 ]);
217 dnsovertls = "opportunistic";
218 extraConfig = strings.concatStringsSep "\n" [
219 "[Resolve]"
220 (optionalString cfg.mdns ''
221 MulticastDNS=yes
222 '')
223 (optionalString (intern != null) ''
224 DNSStubListener=yes
225 DNSStubListenerExtra=${ipv4.prettyIp (ipv4.cidrToIpAddress intern.cidr)}
226 '')
227 ];
228 };
229 };
230}