1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9
10 cfg = config.services.frr;
11
12 daemons = [
13 "bgpd"
14 "ospfd"
15 "ospf6d"
16 "ripd"
17 "ripngd"
18 "isisd"
19 "pimd"
20 "pim6d"
21 "ldpd"
22 "nhrpd"
23 "eigrpd"
24 "babeld"
25 "sharpd"
26 "pbrd"
27 "bfdd"
28 "fabricd"
29 "vrrpd"
30 "pathd"
31 ];
32
33 daemonDefaultOptions = {
34 zebra = "-A 127.0.0.1 -s 90000000";
35 mgmtd = "-A 127.0.0.1";
36 bgpd = "-A 127.0.0.1";
37 ospfd = "-A 127.0.0.1";
38 ospf6d = "-A ::1";
39 ripd = "-A 127.0.0.1";
40 ripngd = "-A ::1";
41 isisd = "-A 127.0.0.1";
42 pimd = "-A 127.0.0.1";
43 pim6d = "-A ::1";
44 ldpd = "-A 127.0.0.1";
45 nhrpd = "-A 127.0.0.1";
46 eigrpd = "-A 127.0.0.1";
47 babeld = "-A 127.0.0.1";
48 sharpd = "-A 127.0.0.1";
49 pbrd = "-A 127.0.0.1";
50 staticd = "-A 127.0.0.1";
51 bfdd = "-A 127.0.0.1";
52 fabricd = "-A 127.0.0.1";
53 vrrpd = "-A 127.0.0.1";
54 pathd = "-A 127.0.0.1";
55 };
56
57 renamedServices = [
58 "bgp"
59 "ospf"
60 "ospf6"
61 "rip"
62 "ripng"
63 "isis"
64 "pim"
65 "ldp"
66 "nhrp"
67 "eigrp"
68 "babel"
69 "sharp"
70 "pbr"
71 "bfd"
72 "fabric"
73 ];
74
75 obsoleteServices = renamedServices ++ [
76 "static"
77 "mgmt"
78 "zebra"
79 ];
80
81 allDaemons = builtins.attrNames daemonDefaultOptions;
82
83 isEnabled = service: cfg.${service}.enable;
84
85 daemonLine = d: "${d}=${if isEnabled d then "yes" else "no"}";
86
87 configFile =
88 if cfg.configFile != null then
89 cfg.configFile
90 else
91 pkgs.writeText "frr.conf" ''
92 ! FRR configuration
93 !
94 hostname ${config.networking.hostName}
95 log syslog
96 service password-encryption
97 service integrated-vtysh-config
98 !
99 ${cfg.config}
100 !
101 end
102 '';
103
104 serviceOptions =
105 service:
106 {
107 options = lib.mkOption {
108 type = lib.types.listOf lib.types.str;
109 default = [ daemonDefaultOptions.${service} ];
110 description = ''
111 Options for the FRR ${service} daemon.
112 '';
113 };
114 extraOptions = lib.mkOption {
115 type = lib.types.listOf lib.types.str;
116 default = [ ];
117 description = ''
118 Extra options to be appended to the FRR ${service} daemon options.
119 '';
120 };
121 }
122 // (
123 if (builtins.elem service daemons) then { enable = lib.mkEnableOption "FRR ${service}"; } else { }
124 );
125
126in
127
128{
129
130 ###### interface
131 imports = [
132 {
133 options.services.frr = {
134 configFile = lib.mkOption {
135 type = lib.types.nullOr lib.types.path;
136 default = null;
137 example = "/etc/frr/frr.conf";
138 description = ''
139 Configuration file to use for FRR.
140 By default the NixOS generated files are used.
141 '';
142 };
143 config = lib.mkOption {
144 type = lib.types.lines;
145 default = "";
146 example = ''
147 router rip
148 network 10.0.0.0/8
149 router ospf
150 network 10.0.0.0/8 area 0
151 router bgp 65001
152 neighbor 10.0.0.1 remote-as 65001
153 '';
154 description = ''
155 FRR configuration statements.
156 '';
157 };
158 openFilesLimit = lib.mkOption {
159 type = lib.types.ints.unsigned;
160 default = 1024;
161 description = ''
162 This is the maximum number of FD's that will be available. Use a
163 reasonable value for your setup if you are expecting a large number
164 of peers in say BGP.
165 '';
166 };
167 };
168 }
169 { options.services.frr = (lib.genAttrs allDaemons serviceOptions); }
170 (lib.mkRemovedOptionModule [ "services" "frr" "zebra" "enable" ] "FRR zebra is always enabled")
171 ]
172 ++ (map (
173 d: lib.mkRenamedOptionModule [ "services" "frr" d "enable" ] [ "services" "frr" "${d}d" "enable" ]
174 ) renamedServices)
175 ++ (map
176 (
177 d:
178 lib.mkRenamedOptionModule
179 [ "services" "frr" d "extraOptions" ]
180 [ "services" "frr" "${d}d" "extraOptions" ]
181 )
182 (
183 renamedServices
184 ++ [
185 "static"
186 "mgmt"
187 ]
188 )
189 )
190 ++ (map (d: lib.mkRemovedOptionModule [ "services" "frr" d "enable" ] "FRR ${d}d is always enabled")
191 [
192 "static"
193 "mgmt"
194 ]
195 )
196 ++ (map (
197 d:
198 lib.mkRemovedOptionModule [
199 "services"
200 "frr"
201 d
202 "config"
203 ] "FRR switched to integrated-vtysh-config, please use services.frr.config"
204 ) obsoleteServices)
205 ++ (map (
206 d:
207 lib.mkRemovedOptionModule [ "services" "frr" d "configFile" ]
208 "FRR switched to integrated-vtysh-config, please use services.frr.config or services.frr.configFile"
209 ) obsoleteServices)
210 ++ (map (
211 d:
212 lib.mkRemovedOptionModule [
213 "services"
214 "frr"
215 d
216 "vtyListenAddress"
217 ] "Please change -A option in services.frr.${d}.options instead"
218 ) obsoleteServices)
219 ++ (map (
220 d:
221 lib.mkRemovedOptionModule [ "services" "frr" d "vtyListenPort" ]
222 "Please use `-P «vtyListenPort»` option with services.frr.${d}.extraOptions instead, or change services.frr.${d}.options accordingly"
223 ) obsoleteServices);
224
225 ###### implementation
226
227 config =
228 let
229 daemonList = lib.concatStringsSep "\n" (map daemonLine daemons);
230 daemonOptionLine =
231 d: "${d}_options=\"${lib.concatStringsSep " " (cfg.${d}.options ++ cfg.${d}.extraOptions)}\"";
232 daemonOptions = lib.concatStringsSep "\n" (map daemonOptionLine allDaemons);
233 in
234 lib.mkIf (lib.any isEnabled daemons || cfg.configFile != null || cfg.config != "") {
235
236 environment.systemPackages = [
237 pkgs.frr # for the vtysh tool
238 ];
239
240 users.users.frr = {
241 description = "FRR daemon user";
242 isSystemUser = true;
243 group = "frr";
244 };
245
246 users.groups = {
247 frr = { };
248 # Members of the frrvty group can use vtysh to inspect the FRR daemons
249 frrvty = {
250 members = [ "frr" ];
251 };
252 };
253
254 environment.etc = {
255 "frr/frr.conf".source = configFile;
256 "frr/vtysh.conf".text = ''
257 service integrated-vtysh-config
258 '';
259 "frr/daemons".text = ''
260 # This file tells the frr package which daemons to start.
261 #
262 # The watchfrr, zebra and staticd daemons are always started.
263 #
264 # This part is auto-generated from services.frr.<daemon>.enable config
265 ${daemonList}
266
267 # If this option is set the /etc/init.d/frr script automatically loads
268 # the config via "vtysh -b" when the servers are started.
269 #
270 vtysh_enable=yes
271
272 # This part is auto-generated from services.frr.<daemon>.options or
273 # services.frr.<daemon>.extraOptions
274 ${daemonOptions}
275 '';
276 };
277
278 systemd.tmpfiles.rules = [ "d /run/frr 0755 frr frr -" ];
279
280 systemd.services.frr = {
281 description = "FRRouting";
282 documentation = [ "https://frrouting.readthedocs.io/en/latest/setup.html" ];
283 wants = [ "network.target" ];
284 after = [
285 "network-pre.target"
286 "systemd-sysctl.service"
287 ];
288 before = [ "network.target" ];
289 wantedBy = [ "multi-user.target" ];
290 startLimitIntervalSec = 180;
291 reloadIfChanged = true;
292 restartTriggers = [
293 configFile
294 daemonList
295 ];
296 serviceConfig = {
297 Nice = -5;
298 Type = "forking";
299 NotifyAccess = "all";
300 TimeoutSec = 120;
301 WatchdogSec = 60;
302 RestartSec = 5;
303 Restart = "always";
304 LimitNOFILE = cfg.openFilesLimit;
305 PIDFile = "/run/frr/watchfrr.pid";
306 ExecStart = "${pkgs.frr}/libexec/frr/frrinit.sh start";
307 ExecStop = "${pkgs.frr}/libexec/frr/frrinit.sh stop";
308 ExecReload = "${pkgs.frr}/libexec/frr/frrinit.sh reload";
309 };
310 unitConfig = {
311 StartLimitBurst = "3";
312 };
313 };
314 };
315
316 meta.maintainers = with lib.maintainers; [ woffs ];
317}