Smokeping service

G’day,

It took longer than expected to get Smokeping working, so I thought I would post a working example for others. Now it will be indexed :slight_smile:

I notice there is no maintainer, and I guess if there was interest, I could update the module with a few of the tweaks I worked out. Or you could just copy this… I think most people have moved to prometheus blackbox
https://search.nixos.org/packages?channel=unstable&show=smokeping&from=0&size=50&sort=relevance&type=packages&query=smokeping

#
# nixos/hp/hp4/smokeping.nix
#

# https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/smokeping.nix
# https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix

# https://oss.oetiker.ch/smokeping/doc/smokeping_examples.en.html
# https://oss.oetiker.ch/smokeping/probe/Curl.en.html
# https://oss.oetiker.ch/smokeping/probe/DNS.en.html

{ config, lib, pkgs, ... }:

let
  targets = {
    # DNS Servers - ICMP ping testing
    "DNSServers" = {
      title = "DNS Server Connectivity (ICMP Ping)";
      menu = "DNS Servers";
      targets = {
        "Google_DNS_IPv4" = {
          name = "Google DNS IPv4";
          host = "8.8.8.8";
        };
        "Google_DNS_IPv6" = {
          name = "Google DNS IPv6";
          host = "2001:4860:4860::8888";
        };
        "Cloudflare_DNS_IPv4" = {
          name = "Cloudflare DNS IPv4";
          host = "1.1.1.1";
        };
        "Cloudflare_DNS_IPv6" = {
          name = "Cloudflare DNS IPv6";
          host = "2606:4700:4700::1111";
        };
        "Cloudflare_DNS_Secondary_IPv4" = {
          name = "Cloudflare DNS Secondary IPv4";
          host = "1.0.0.1";
        };
        "Cloudflare_DNS_Secondary_IPv6" = {
          name = "Cloudflare DNS Secondary IPv6";
          host = "2606:4700:4700::1001";
        };
      };
    };

    # Internet Connectivity
    "Internet" = {
      title = "Internet Connectivity Monitoring";
      menu = "Internet Connectivity";
      targets = {
        "Google_IPv4" = {
          name = "Google.com IPv4";
          host = "142.250.190.78";
        };
        "Google_IPv6" = {
          name = "Google.com IPv6";
          host = "2607:f8b0:4007:811::200e";
        };
        "Facebook_IPv6" = {
          name = "Facebook IPv6";
          host = "2a03:2880:f10d:183:face:b00c:0:25de";
        };
        "Yahoo_IPv6" = {
          name = "Yahoo IPv6";
          host = "2001:4998:24:120d::1:0";
        };
      };
    };

    # Add HTTP category and targets
    "HTTP" = {
      title = "HTTP Site Monitoring";
      menu = "HTTP Sites";
      targets = {
        "Google_HTTP" = {
          name = "Google HTTP";
          host = "google.com";
          probe = "Curl";
        };
        "IBM_HTTP" = {
          name = "IBM HTTP";
          host = "ibm.com";
          probe = "Curl";
        };
        "Yahoo_HTTP" = {
          name = "Yahoo HTTP";
          host = "yahoo.com";
          probe = "Curl";
        };
        "Facebook_HTTP" = {
          name = "Facebook HTTP";
          host = "facebook.com";
          probe = "Curl";
        };
      };
    };

    # Add DNS lookup testing
    "DNSLookup" = {
      title = "DNS Resolution Testing (dig queries)";
      menu = "DNS Resolution";
      targets = {
        "Google_DNS_Lookup" = {
          name = "Google DNS - google.com lookup";
          host = "8.8.8.8";
          probe = "DNS";
          lookup = "google.com";
        };
        "Cloudflare_DNS_Lookup" = {
          name = "Cloudflare DNS - google.com lookup";
          host = "1.1.1.1";
          probe = "DNS";
          lookup = "google.com";
        };
        "Local_DNS_Lookup" = {
          name = "Local DNS - google.com lookup";
          host = "::1";
          probe = "DNS";
          lookup = "google.com";
        };
      };
    };
  };

generateTargetConfig = categoryName: category: ''
+ ${categoryName}
menu = ${category.menu}
title = ${category.title}

${lib.concatStringsSep "\n" (lib.mapAttrsToList (targetName: target: ''
++ ${targetName}
menu = ${target.name}
title = ${target.name}${lib.optionalString (target ? probe) "\nprobe = ${target.probe}"}
host = ${target.host}${lib.optionalString (target ? lookup) "\nlookup = ${target.lookup}"}
'' ) category.targets)}'';

  # Generate the complete target configuration
  targetConfig = ''
probe = FPing

menu = Top
title = Network Latency Grapher
remark = Welcome to the SmokePing website of Siden Network Operations. \
         Here you will learn all about the latency of our network.

${lib.concatStringsSep "\n" (lib.mapAttrsToList generateTargetConfig targets)}'';

  prometheusTargets = lib.flatten (lib.mapAttrsToList (categoryName: category:
    lib.mapAttrsToList (targetName: target:
      {
        name = "${categoryName}_${targetName}";
        host = target.host;
      }) category.targets
  ) targets);

in {
  # Smokeping configuration for network monitoring
  # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/smokeping.nix
  services.smokeping = {
    enable = true;
    webService = false;  # Disable automatic nginx configuration to avoid conflicts

    # Basic configuration
    owner = "Network Operations";
    ownerEmail = "ops@siden.io";
    hostName = "smokeping.localhost";

    # Database configuration (5 minute intervals, 20 pings per step)
    # Using AVERAGE as the consolidation function (MEDIAN is not supported)
    databaseConfig = ''
      step     = 300
      pings    = 20
      # consfn mrhb steps total
      AVERAGE  0.5   1  1008
      AVERAGE  0.5  12  4320
          MIN  0.5  12  4320
          MAX  0.5  12  4320
      AVERAGE  0.5 144   720
          MAX  0.5 144   720
          MIN  0.5 144   720
    '';

    # Probe configuration for both IPv4 and IPv6
    # Modern fping handles IPv6 addresses automatically
    probeConfig = ''
      + FPing
      binary = ${config.security.wrapperDir}/fping

      + Curl
      binary = ${pkgs.curl}/bin/curl
      urlformat = http://%host%/
      timeout = 10
      step = 300
      extraargs = --silent
      follow_redirects = yes
      include_redirects = no

      + DNS
      binary = ${pkgs.bind.dnsutils}/bin/dig
      timeout = 15
      step = 300
    '';

    # Target configuration generated from data structure
    inherit targetConfig;

    # Alert configuration
    alertConfig = ''
      to = root@localhost
      from = smokeping@localhost

      +someloss
      type = loss
      pattern = >0%,*12*,>0%,*12*,>0%
      comment = Loss of connectivity

      +highloss
      type = loss
      pattern = >50%,*12*,>50%,*12*,>50%
      comment = High loss of connectivity

      +highlatency
      type = rtt
      pattern = >100,*12*,>100,*12*,>100
      comment = High latency detected
    '';

    # Presentation configuration
    presentationConfig = ''
      + charts
      menu = Charts
      title = The most interesting destinations
      ++ stddev
      sorter = StdDev(entries=>4)
      title = Top Standard Deviation
      menu = Std Deviation
      format = Standard Deviation %f
      ++ max
      sorter = Max(entries=>5)
      title = Top Max Roundtrip Time
      menu = by Max
      format = Max Roundtrip Time %f seconds
      ++ loss
      sorter = Loss(entries=>5)
      title = Top Packet Loss
      menu = Loss
      format = Packets Lost %f
      ++ median
      sorter = Median(entries=>5)
      title = Top Median Roundtrip Time
      menu = by Median
      format = Median RTT %f seconds
      + overview
      width = 600
      height = 50
      range = 10h
      + detail
      width = 600
      height = 200
      unison_tolerance = 2
      "Last 3 Hours"    3h
      "Last 30 Hours"   30h
      "Last 10 Days"    10d
      "Last 360 Days"   360d
    '';
  };

  networking.firewall.allowedTCPPorts = [ 80 443 ];

  # Ensure nginx can read cache/data for static file serving
  users.users.nginx.extraGroups = [ "smokeping" ];

  systemd.tmpfiles.rules = [
    "d /var/lib/smokeping/cache 0750 smokeping smokeping"
    "d /var/lib/smokeping/data 0750 smokeping smokeping"
    "Z /var/lib/smokeping 0750 smokeping smokeping"
  ];

  # Systemd security measures for smokeping
  systemd.slices.smokeping = {
    description = "Smokeping network monitoring slice";
    sliceConfig = {
      MemoryHigh = "200M";
      MemoryMax = "300M";
      CPUQuota = "20%";
      TasksMax = 200;
    };
  };

  # Enhanced smokeping service configuration with security measures
  # systemd-analyze security smokeping
  systemd.services.smokeping = {
    serviceConfig = {
      # Resource limits
      Slice = "smokeping.slice";
      MemoryHigh = "200M";
      MemoryMax = "300M";
      CPUQuota = "20%";
      TasksMax = 200;

      # Process limits
      LimitNOFILE = 1024;
      LimitNPROC = 100;

      # Security restrictions
      NoNewPrivileges = true;
      ProtectSystem = "strict";
      ProtectHome = true;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
      ProtectControlGroups = true;
      ProtectKernelLogs = true;
      PrivateDevices = true;
      RestrictRealtime = true;
      # RestrictSUIDSGID = true;  # Disabled - smokeping needs SUID wrapper for ping
      RestrictNamespaces = true;
      LockPersonality = true;
      # MemoryDenyWriteExecute = true;  # Disabled - interferes with DNS resolution
      RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];

      # Additional security restrictions
      RemoveIPC = true;  # Clean up IPC objects
      UMask = "0077";  # Restrict file permissions
      SystemCallFilter = [ "@system-service" "~@privileged" "~@mount" "~@debug" "~@module" "~@reboot" "~@swap" "~@clock" "~@cpu-emulation" "~@obsolete" ];  # Allow raw-io for IPv6 ping
      CapabilityBoundingSet = [ "CAP_NET_RAW" "CAP_NET_BIND_SERVICE" ];  # Only network capabilities needed
      ProtectProc = "default";  # Allow access to process info for DNS resolution
      ProcSubset = "all";  # Allow access to all process info
      ProtectHostname = true;  # Prevent hostname changes
      ProtectClock = true;  # Prevent clock changes

      # File system restrictions - allow access to dig
      ReadWritePaths = [
        "/var/lib/smokeping"
        "/var/log"
        "/run"
      ];
      ReadOnlyPaths = [
        "/etc/smokeping.conf"
        "/nix/store"
        "${pkgs.curl}"
        "${config.services.smokeping.package}"
        "${config.security.wrapperDir}"
        "/etc/resolv.conf"
        "/etc/hosts"
        "/etc/nsswitch.conf"
        "/etc/ssl"
        "/etc/ca-bundle.crt"
        "/etc/ssl/certs"
      ];

      # User/group restrictions
      User = "smokeping";
      Group = "smokeping";
      SupplementaryGroups = [ "smokeping" ];

      # Restart policy
      Restart = "on-failure";
      RestartSec = "10s";

      # Nice priority (lower number = higher priority)
      Nice = 10;

      # Required by smokeping module
      ExecStart = "${config.services.smokeping.package}/bin/smokeping --config=/etc/smokeping.conf --nodaemon";
    };

    # Add curl package to the service environment
    path = [ pkgs.curl pkgs.bind.dnsutils ];
    environment = {
      # Ensure DNS resolution works
      NSS_WRAPPER_PASSWD = "/etc/passwd";
      NSS_WRAPPER_GROUP = "/etc/group";
      LD_LIBRARY_PATH = "${pkgs.curl}/lib";
    };
  };

  # Also secure the prometheus smokeping exporter - DISABLED
  # systemd.services.prometheus-smokeping-exporter = {
  #   serviceConfig = {
  #     # Resource limits
  #     MemoryHigh = "512M";
  #     MemoryMax = "1G";
  #     CPUQuota = "25%";
  #
  #     # Security restrictions
  #     NoNewPrivileges = true;
  #     ProtectSystem = "strict";
  #     ProtectHome = true;
  #     ProtectKernelTunables = true;
  #     ProtectKernelModules = true;
  #     ProtectControlGroups = true;
  #     ProtectKernelLogs = true;
  #     PrivateDevices = true;
  #     RestrictRealtime = true;
  #     RestrictSUIDSGID = true;
  #     RestrictNamespaces = true;
  #     LockPersonality = true;
  #     MemoryDenyWriteExecute = true;
  #     RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
  #
  #     # Additional security restrictions
  #     RemoveIPC = true;  # Clean up IPC objects
  #     UMask = "0077";  # Restrict file permissions
  #     SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" "~@mount" "~@debug" "~@module" "~@reboot" "~@swap" "~@clock" "~@cpu-emulation" "~@obsolete" "~@raw-io" ];
  #     CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];  # Only binding capability needed
  #     ProtectProc = "invisible";  # Hide other processes
  #     ProcSubset = "pid";  # Only show own process info
  #     ProtectHostname = true;  # Prevent hostname changes
  #     ProtectClock = true;  # Prevent clock changes
  #
  #     # File system restrictions
  #     ReadWritePaths = [
  #       "/var/log"
  #       "/run"
  #     ];
  #     ReadOnlyPaths = [
  #       "/nix/store"
  #     ];
  #
  #     # Restart policy
  #     Restart = "on-failure";
  #     RestartSec = "10s";
  #
  #     # Nice priority
  #     Nice = 15;
  #   };
  # };
}

# Available Probes in NixOS Smokeping 2.8.2:
#
# Network/Connectivity Probes:
# - FPing: Standard ping using fping binary (IPv4/IPv6)
# - FPing6: Legacy IPv6 ping (deprecated, use FPing)
# - FPingContinuous: Continuous ping monitoring
# - RemoteFPing: Ping through remote host
# - TCPPing: TCP connection testing
# - TraceroutePing: Traceroute-based ping
#
# HTTP/Web Probes:
# - Curl: HTTP/HTTPS testing using curl binary
# - EchoPingHttp: HTTP echo ping
# - EchoPingHttps: HTTPS echo ping
# - WebProxyFilter: Web proxy testing
#
# DNS Probes:
# - DNS: DNS query testing
# - AnotherDNS: Alternative DNS testing
# - EchoPingDNS: DNS echo ping
# - CiscoRTTMonDNS: Cisco DNS monitoring
#
# SSH/Telnet Probes:
# - SSH: SSH connection testing
# - AnotherSSH: Alternative SSH testing
# - TelnetIOSPing: Cisco IOS telnet ping
# - TelnetJunOSPing: Juniper telnet ping
# - OpenSSHEOSPing: OpenSSH to Cisco IOS
# - OpenSSHJunOSPing: OpenSSH to Juniper
#
# Application Probes:
# - LDAP: LDAP connection testing
# - EchoPingLDAP: LDAP echo ping
# - Radius: RADIUS authentication testing
# - TacacsPlus: TACACS+ authentication testing
# - FTPtransfer: FTP file transfer testing
# - NFSping: NFS mount testing
# - Qstat: Quake server status
# - SipSak: SIP protocol testing
#
# Network Equipment Probes:
# - CiscoRTTMonEchoICMP: Cisco ICMP echo monitoring
# - CiscoRTTMonTcpConnect: Cisco TCP connection monitoring
# - DismanPing: DISMAN-PING-MIB SNMP ping
# - IOSPing: Cisco IOS ping
# - IRTT: In-band Round Trip Time
#
# Email Probes:
# - EchoPingSmtp: SMTP echo ping
# - SendEmail: Email sending test
#
# Other Probes:
# - EchoPingChargen: Chargen echo ping
# - EchoPingDiscard: Discard echo ping
# - EchoPingIcp: ICP echo ping
# - EchoPingWhois: Whois echo ping
# - EchoPingPlugin: Plugin-based echo ping
# - passwordchecker: Password checking
#
# Note: The HTTP probe is NOT available in NixOS smokeping 2.8.2.
# Use Curl probe for HTTP/HTTPS testing instead.

The resulting service has some security protections

[das@hp4:~/nixos/hp/hp4]$ cat /etc/systemd/system/smokeping.service
[Unit]
X-Reload-Triggers=/nix/store/df5bf4z39qizhp2i9kyh7p8fp04sl30m-X-Reload-Triggers-smokeping

[Service]
Environment="LD_LIBRARY_PATH=/nix/store/zl7h70n70g5m57iw5pa8gqkxz6y0zfcf-curl-8.12.1-bin/lib"
Environment="LOCALE_ARCHIVE=/nix/store/nqixjjil6mbzf0rdqqr2sm47w5av72f6-glibc-locales-2.40-66/lib/locale/locale-archive"
Environment="NSS_WRAPPER_GROUP=/etc/group"
Environment="NSS_WRAPPER_PASSWD=/etc/passwd"
Environment="PATH=/nix/store/zl7h70n70g5m57iw5pa8gqkxz6y0zfcf-curl-8.12.1-bin/bin:/nix/store/vi3pdc7q416gh2njxhlnc8xrnbkxxjw0-bind-9.18.33-dnsutils/bin:/nix/store/440q5scq8paszj2sdgz98hxl1rz12i88-coreutils-9.5/bin:/nix/store/lpz4m485bm2y8mann5c4xcf4p4hlls3k-findutils-4.10.0/bin:/nix/store/0mqngkz34kv5z6hz91bbbgzgrnz56c4y-gnugrep-3.11/bin:/nix/store/q5b7l0zfk7k6nadpj80niqzymaq0z9yp-gnused-4.9/bin:/nix/store/v9fckvvzbzw55xgj7pk8vfxwsx96d3qw-systemd-256.10/bin:/nix/store/zl7h70n70g5m57iw5pa8gqkxz6y0zfcf-curl-8.12.1-bin/sbin:/nix/store/vi3pdc7q416gh2njxhlnc8xrnbkxxjw0-bind-9.18.33-dnsutils/sbin:/nix/store/440q5scq8paszj2sdgz98hxl1rz12i88-coreutils-9.5/sbin:/nix/store/lpz4m485bm2y8mann5c4xcf4p4hlls3k-findutils-4.10.0/sbin:/nix/store/0mqngkz34kv5z6hz91bbbgzgrnz56c4y-gnugrep-3.11/sbin:/nix/store/q5b7l0zfk7k6nadpj80niqzymaq0z9yp-gnused-4.9/sbin:/nix/store/v9fckvvzbzw55xgj7pk8vfxwsx96d3qw-systemd-256.10/sbin"
Environment="TZDIR=/nix/store/d4prd3ravga8vikbr625apnspk477p1q-tzdata-2025b/share/zoneinfo"
CPUQuota=20%
CapabilityBoundingSet=CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
ExecStart=/nix/store/fljhb3x3kxx4aqjbdmjmc7w1zx6dcdr2-smokeping-2.8.2/bin/smokeping --config=/etc/smokeping.conf --nodaemon
ExecStartPre=/nix/store/cfnfd8vmrsn1dd3grhny3imrppxsigj6-unit-script-smokeping-pre-start/bin/smokeping-pre-start
Group=smokeping
LimitNOFILE=1024
LimitNPROC=100
LockPersonality=true
MemoryHigh=200M
MemoryMax=300M
Nice=10
NoNewPrivileges=true
PrivateDevices=true
ProcSubset=all
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=default
ProtectSystem=strict
ReadOnlyPaths=/etc/smokeping.conf
ReadOnlyPaths=/nix/store
ReadOnlyPaths=/nix/store/zl7h70n70g5m57iw5pa8gqkxz6y0zfcf-curl-8.12.1-bin
ReadOnlyPaths=/nix/store/fljhb3x3kxx4aqjbdmjmc7w1zx6dcdr2-smokeping-2.8.2
ReadOnlyPaths=/run/wrappers/bin
ReadOnlyPaths=/etc/resolv.conf
ReadOnlyPaths=/etc/hosts
ReadOnlyPaths=/etc/nsswitch.conf
ReadOnlyPaths=/etc/ssl
ReadOnlyPaths=/etc/ca-bundle.crt
ReadOnlyPaths=/etc/ssl/certs
ReadWritePaths=/var/lib/smokeping
ReadWritePaths=/var/log
ReadWritePaths=/run
RemoveIPC=true
Restart=on-failure
RestartSec=10s
RestrictAddressFamilies=AF_INET
RestrictAddressFamilies=AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
Slice=smokeping.slice
SupplementaryGroups=smokeping
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
SystemCallFilter=~@mount
SystemCallFilter=~@debug
SystemCallFilter=~@module
SystemCallFilter=~@reboot
SystemCallFilter=~@swap
SystemCallFilter=~@clock
SystemCallFilter=~@cpu-emulation
SystemCallFilter=~@obsolete
TasksMax=200
UMask=0077
User=smokeping

[das@hp4:~/nixos/hp/hp4]$

Kind regards,
Dave

Video regarding Smokeping on NixOS

1 Like