1 { config, lib, pkgs, ... }:
4 # Background information: FastNetMon requires a MongoDB to start. This is because
5 # it uses MongoDB to store its configuration. That is, in a normal setup there is
6 # one collection with one document.
7 # To provide declarative configuration in our NixOS module, this database is
8 # completely emptied and replaced on each boot by the fastnetmon-setup service
9 # using the configuration backup functionality.
11 cfg = config.services.fastnetmon-advanced;
12 settingsFormat = pkgs.formats.yaml { };
14 # obtain the default configs by starting up ferretdb and fcli in a derivation
15 default_configs = pkgs.runCommand "default-configs" {
18 pkgs.fastnetmon-advanced # for fcli
22 mkdir ferretdb fastnetmon $out
23 FERRETDB_TELEMETRY="disable" FERRETDB_HANDLER="sqlite" FERRETDB_STATE_DIR="$PWD/ferretdb" FERRETDB_SQLITE_URL="file:$PWD/ferretdb/" ferretdb &
25 cat << EOF > fastnetmon/fastnetmon.conf
27 mongodb_username = "";
30 proot -b fastnetmon:/etc/fastnetmon -0 fcli create_configuration
31 proot -b fastnetmon:/etc/fastnetmon -0 fcli set bgp default
32 proot -b fastnetmon:/etc/fastnetmon -0 fcli export_configuration backup.tar
33 tar -C $out --no-same-owner -xvf backup.tar
36 # merge the user configs into the default configs
37 config_tar = pkgs.runCommand "fastnetmon-config.tar" {
38 nativeBuildInputs = with pkgs; [ jq ];
40 jq -s add ${default_configs}/main.json ${pkgs.writeText "main-add.json" (builtins.toJSON cfg.settings)} > main.json
42 ${lib.concatImapStringsSep "\n" (pos: hostgroup: ''
43 jq -s add ${default_configs}/hostgroup/0.json ${pkgs.writeText "hostgroup-${toString (pos - 1)}-add.json" (builtins.toJSON hostgroup)} > hostgroup/${toString (pos - 1)}.json
46 ${lib.concatImapStringsSep "\n" (pos: bgp: ''
47 jq -s add ${default_configs}/bgp/0.json ${pkgs.writeText "bgp-${toString (pos - 1)}-add.json" (builtins.toJSON bgp)} > bgp/${toString (pos - 1)}.json
49 tar -cf $out main.json ${lib.concatImapStringsSep " " (pos: _: "hostgroup/${toString (pos - 1)}.json") hostgroups} ${lib.concatImapStringsSep " " (pos: _: "bgp/${toString (pos - 1)}.json") bgpPeers}
52 hostgroups = lib.mapAttrsToList (name: hostgroup: { inherit name; } // hostgroup) cfg.hostgroups;
53 bgpPeers = lib.mapAttrsToList (name: bgpPeer: { inherit name; } // bgpPeer) cfg.bgpPeers;
56 options.services.fastnetmon-advanced = with lib; {
57 enable = mkEnableOption "the fastnetmon-advanced DDoS Protection daemon";
61 Extra configuration options to declaratively load into FastNetMon Advanced.
63 See the [FastNetMon Advanced Configuration options reference](https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-configuration-options/) for more details.
65 type = settingsFormat.type;
67 example = literalExpression ''
69 networks_list = [ "192.0.2.0/24" ];
71 gobgp_flow_spec_announces = true;
75 hostgroups = mkOption {
76 description = "Hostgroups to declaratively load into FastNetMon Advanced";
77 type = types.attrsOf settingsFormat.type;
81 description = "BGP Peers to declaratively load into FastNetMon Advanced";
82 type = types.attrsOf settingsFormat.type;
86 enableAdvancedTrafficPersistence = mkOption {
87 description = "Store historical flow data in clickhouse";
92 traffic_db.settings = mkOption {
93 type = settingsFormat.type;
94 description = "Additional settings for /etc/fastnetmon/traffic_db.conf";
98 config = lib.mkMerge [ (lib.mkIf cfg.enable {
99 environment.systemPackages = with pkgs; [
100 fastnetmon-advanced # for fcli
103 environment.etc."fastnetmon/license.lic".source = "/var/lib/fastnetmon/license.lic";
104 environment.etc."fastnetmon/gobgpd.conf".source = "/run/fastnetmon/gobgpd.conf";
105 environment.etc."fastnetmon/fastnetmon.conf".source = pkgs.writeText "fastnetmon.conf" (builtins.toJSON {
106 mongodb_username = "";
109 services.ferretdb.enable = true;
111 systemd.services.fastnetmon-setup = {
112 wantedBy = [ "multi-user.target" ];
113 after = [ "ferretdb.service" ];
114 path = with pkgs; [ fastnetmon-advanced config.systemd.package ];
116 fcli create_configuration
117 fcli delete hostgroup global
118 fcli import_configuration ${config_tar}
119 systemctl --no-block try-restart fastnetmon
121 serviceConfig.Type = "oneshot";
124 systemd.services.fastnetmon = {
125 wantedBy = [ "multi-user.target" ];
126 after = [ "ferretdb.service" "fastnetmon-setup.service" "polkit.service" ];
127 path = with pkgs; [ iproute2 ];
129 # Disable logic which shuts service when we do too many restarts
130 # We do restarts from sudo fcli commit and it's expected that we may have many restarts
131 # Details: https://github.com/systemd/systemd/issues/2416
132 StartLimitInterval = 0;
135 ExecStart = "${pkgs.fastnetmon-advanced}/bin/fastnetmon --log_to_console";
138 # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened
139 Restart= "on-failure";
143 CacheDirectory = "fastnetmon";
144 RuntimeDirectory = "fastnetmon"; # for gobgpd config
145 StateDirectory = "fastnetmon"; # for license file
149 security.polkit.enable = true;
150 security.polkit.extraConfig = ''
151 polkit.addRule(function(action, subject) {
152 if (action.id == "org.freedesktop.systemd1.manage-units" &&
153 subject.isInGroup("fastnetmon")) {
154 if (action.lookup("unit") == "gobgp.service") {
155 var verb = action.lookup("verb");
156 if (verb == "start" || verb == "stop" || verb == "restart") {
157 return polkit.Result.YES;
164 # We don't use the existing gobgp NixOS module and package, because the gobgp
165 # version might not be compatible with fastnetmon. Also, the service name
166 # _must_ be 'gobgp' and not 'gobgpd', so that fastnetmon can reload the config.
167 systemd.services.gobgp = {
168 wantedBy = [ "multi-user.target" ];
169 after = [ "network.target" ];
170 description = "GoBGP Routing Daemon";
172 ConditionPathExists = "/run/fastnetmon/gobgpd.conf";
176 ExecStartPre = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf -d";
177 SupplementaryGroups = [ "fastnetmon" ];
178 ExecStart = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf --sdnotify";
179 ExecReload = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -r";
181 AmbientCapabilities = "cap_net_bind_service";
186 (lib.mkIf (cfg.enable && cfg.enableAdvancedTrafficPersistence) {
187 ## Advanced Traffic persistence
188 ## https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-traffic-persistency/
190 services.clickhouse.enable = true;
192 services.fastnetmon-advanced.settings.traffic_db = true;
194 services.fastnetmon-advanced.traffic_db.settings = {
195 clickhouse_batch_size = lib.mkDefault 1000;
196 clickhouse_batch_delay = lib.mkDefault 1;
197 traffic_db_host = lib.mkDefault "127.0.0.1";
198 traffic_db_port = lib.mkDefault 8100;
199 clickhouse_host = lib.mkDefault "127.0.0.1";
200 clickhouse_port = lib.mkDefault 9000;
201 clickhouse_user = lib.mkDefault "default";
202 clickhouse_password = lib.mkDefault "";
204 environment.etc."fastnetmon/traffic_db.conf".text = builtins.toJSON cfg.traffic_db.settings;
206 systemd.services.traffic_db = {
207 wantedBy = [ "multi-user.target" ];
208 after = [ "network.target" ];
210 ExecStart = "${pkgs.fastnetmon-advanced}/bin/traffic_db";
211 # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened
212 Restart= "on-failure";
221 meta.maintainers = lib.teams.wdz.members;