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 {
134 options.services.frr = {
135 configFile = lib.mkOption {
136 type = lib.types.nullOr lib.types.path;
137 default = null;
138 example = "/etc/frr/frr.conf";
139 description = ''
140 Configuration file to use for FRR.
141 By default the NixOS generated files are used.
142 '';
143 };
144 config = lib.mkOption {
145 type = lib.types.lines;
146 default = "";
147 example = ''
148 router rip
149 network 10.0.0.0/8
150 router ospf
151 network 10.0.0.0/8 area 0
152 router bgp 65001
153 neighbor 10.0.0.1 remote-as 65001
154 '';
155 description = ''
156 FRR configuration statements.
157 '';
158 };
159 openFilesLimit = lib.mkOption {
160 type = lib.types.ints.unsigned;
161 default = 1024;
162 description = ''
163 This is the maximum number of FD's that will be available. Use a
164 reasonable value for your setup if you are expecting a large number
165 of peers in say BGP.
166 '';
167 };
168 };
169 }
170 { options.services.frr = (lib.genAttrs allDaemons serviceOptions); }
171 (lib.mkRemovedOptionModule [ "services" "frr" "zebra" "enable" ] "FRR zebra is always enabled")
172 ]
173 ++ (map (
174 d: lib.mkRenamedOptionModule [ "services" "frr" d "enable" ] [ "services" "frr" "${d}d" "enable" ]
175 ) renamedServices)
176 ++ (map
177 (
178 d:
179 lib.mkRenamedOptionModule
180 [ "services" "frr" d "extraOptions" ]
181 [ "services" "frr" "${d}d" "extraOptions" ]
182 )
183 (
184 renamedServices
185 ++ [
186 "static"
187 "mgmt"
188 ]
189 )
190 )
191 ++ (map (d: lib.mkRemovedOptionModule [ "services" "frr" d "enable" ] "FRR ${d}d is always enabled")
192 [
193 "static"
194 "mgmt"
195 ]
196 )
197 ++ (map (
198 d:
199 lib.mkRemovedOptionModule [
200 "services"
201 "frr"
202 d
203 "config"
204 ] "FRR switched to integrated-vtysh-config, please use services.frr.config"
205 ) obsoleteServices)
206 ++ (map (
207 d:
208 lib.mkRemovedOptionModule [ "services" "frr" d "configFile" ]
209 "FRR switched to integrated-vtysh-config, please use services.frr.config or services.frr.configFile"
210 ) obsoleteServices)
211 ++ (map (
212 d:
213 lib.mkRemovedOptionModule [
214 "services"
215 "frr"
216 d
217 "vtyListenAddress"
218 ] "Please change -A option in services.frr.${d}.options instead"
219 ) obsoleteServices)
220 ++ (map (
221 d:
222 lib.mkRemovedOptionModule [ "services" "frr" d "vtyListenPort" ]
223 "Please use `-P «vtyListenPort»` option with services.frr.${d}.extraOptions instead, or change services.frr.${d}.options accordingly"
224 ) obsoleteServices);
225
226 ###### implementation
227
228 config =
229 let
230 daemonList = lib.concatStringsSep "\n" (map daemonLine daemons);
231 daemonOptionLine =
232 d: "${d}_options=\"${lib.concatStringsSep " " (cfg.${d}.options ++ cfg.${d}.extraOptions)}\"";
233 daemonOptions = lib.concatStringsSep "\n" (map daemonOptionLine allDaemons);
234 in
235 lib.mkIf (lib.any isEnabled daemons || cfg.configFile != null || cfg.config != "") {
236
237 environment.systemPackages = [
238 pkgs.frr # for the vtysh tool
239 ];
240
241 users.users.frr = {
242 description = "FRR daemon user";
243 isSystemUser = true;
244 group = "frr";
245 };
246
247 users.groups = {
248 frr = { };
249 # Members of the frrvty group can use vtysh to inspect the FRR daemons
250 frrvty = {
251 members = [ "frr" ];
252 };
253 };
254
255 environment.etc = {
256 "frr/frr.conf".source = configFile;
257 "frr/vtysh.conf".text = ''
258 service integrated-vtysh-config
259 '';
260 "frr/daemons".text = ''
261 # This file tells the frr package which daemons to start.
262 #
263 # The watchfrr, zebra and staticd daemons are always started.
264 #
265 # This part is auto-generated from services.frr.<daemon>.enable config
266 ${daemonList}
267
268 # If this option is set the /etc/init.d/frr script automatically loads
269 # the config via "vtysh -b" when the servers are started.
270 #
271 vtysh_enable=yes
272
273 # This part is auto-generated from services.frr.<daemon>.options or
274 # services.frr.<daemon>.extraOptions
275 ${daemonOptions}
276 '';
277 };
278
279 systemd.tmpfiles.rules = [ "d /run/frr 0755 frr frr -" ];
280
281 systemd.services.frr = {
282 description = "FRRouting";
283 documentation = [ "https://frrouting.readthedocs.io/en/latest/setup.html" ];
284 wants = [ "network.target" ];
285 after = [
286 "network-pre.target"
287 "systemd-sysctl.service"
288 ];
289 before = [ "network.target" ];
290 wantedBy = [ "multi-user.target" ];
291 startLimitIntervalSec = 180;
292 reloadIfChanged = true;
293 restartTriggers = [
294 configFile
295 daemonList
296 ];
297 serviceConfig = {
298 Nice = -5;
299 Type = "forking";
300 NotifyAccess = "all";
301 StartLimitBurst = "3";
302 TimeoutSec = 120;
303 WatchdogSec = 60;
304 RestartSec = 5;
305 Restart = "always";
306 LimitNOFILE = cfg.openFilesLimit;
307 PIDFile = "/run/frr/watchfrr.pid";
308 ExecStart = "${pkgs.frr}/libexec/frr/frrinit.sh start";
309 ExecStop = "${pkgs.frr}/libexec/frr/frrinit.sh stop";
310 ExecReload = "${pkgs.frr}/libexec/frr/frrinit.sh reload";
311 };
312 };
313 };
314
315 meta.maintainers = with lib.maintainers; [ woffs ];
316}