summary refs log blame commit diff
path: root/nixos/modules/services/networking/networkmanager.nix
blob: c8861171dd6c9dc0d33d6e5f4ca9932b43356fe2 (plain) (tree)
1
2
3
4
5
6
7
8
                           
 
         

   

                                         
                             







                              
                       
                                                                  
 

                                                                                      

                                        















                                       

     








                                                                                              
                                             

















                                                                 
    








                                           
                                        


                     




                                              
                                                                     
                                                                      


                                       

     
                                         


                                                   
                                                                 
             
                                                               
                 




                                                                                               

     
                              


                             

    




                                                                                          






















                                                                                                        


       
    
 



                                            



                  


                                 
                          
                        







                                                                           
 





















                                                                                                        




                                                                      









                                                                                                        


           
                            
                                      


                                                                        









                                                                                                                    


           
                           
                                          



                                                             
                                           

        

                                                             
                             




                                                                      








                                                                                              







                                                                        
                                    
                                      
                     
                        

                                                                       


           
                                    
                                      
                     
                        

                                                                    

           
 
                                          



                                   








                                                                                                           
















                                                                         
 
                      

                                                                                      
                        


                                                                       








                                                                                                        


           



                                              
                                
                              
                                        



                             
                                                                     

                                



                                                                                                                                                                                     




                     





                                                  
                        






                                                       



                                                                                   












                                                                              


      
             
                                                                                                                  









                                                                               


                       

                            
                  




                                                                                               
       
      
 

                                               



















                                                                                           


                                                                             
       













                                                                                                                              
 
                                              
 














                                                          
 

                                    


                                                                 
                                                              




                                                             
                                       
                                      
                                       
 
                                                                  




                                                                           
      
 



                                                   
                                                                                             
 






                                                                                                       
                                                  
                                      
                                                                 

                                                
                                                              
                                                                 

      
                                                                                              











                                                                     











                                                                  
      
 

                                   
                                             
 


                                                        

                                          

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

with lib;

let
  cfg = config.networking.networkmanager;

  basePackages = with pkgs; [
    modemmanager
    networkmanager
    networkmanager-fortisslvpn
    networkmanager-iodine
    networkmanager-l2tp
    networkmanager-openconnect
    networkmanager-openvpn
    networkmanager-vpnc
    networkmanager-sstp
   ] ++ optional (!delegateWireless && !enableIwd) wpa_supplicant;

  delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [];

  enableIwd = cfg.wifi.backend == "iwd";

  mkValue = v:
    if v == true then "yes"
    else if v == false then "no"
    else if lib.isInt v then toString v
    else v;

  mkSection = name: attrs: ''
    [${name}]
    ${
      lib.concatStringsSep "\n"
        (lib.mapAttrsToList
          (k: v: "${k}=${mkValue v}")
          (lib.filterAttrs
            (k: v: v != null)
            attrs))
    }
  '';

  configFile = pkgs.writeText "NetworkManager.conf" (lib.concatStringsSep "\n" [
    (mkSection "main" {
      plugins = "keyfile";
      dhcp = cfg.dhcp;
      dns = cfg.dns;
      # If resolvconf is disabled that means that resolv.conf is managed by some other module.
      rc-manager =
        if config.networking.resolvconf.enable then "resolvconf"
        else "unmanaged";
      firewall-backend = cfg.firewallBackend;
    })
    (mkSection "keyfile" {
      unmanaged-devices =
        if cfg.unmanaged == [] then null
        else lib.concatStringsSep ";" cfg.unmanaged;
    })
    (mkSection "logging" {
      audit = config.security.audit.enable;
      level = cfg.logLevel;
    })
    (mkSection "connection" cfg.connectionConfig)
    (mkSection "device" {
      "wifi.scan-rand-mac-address" = cfg.wifi.scanRandMacAddress;
      "wifi.backend" = cfg.wifi.backend;
    })
    cfg.extraConfig
  ]);

  /*
    [network-manager]
    Identity=unix-group:networkmanager
    Action=org.freedesktop.NetworkManager.*
    ResultAny=yes
    ResultInactive=no
    ResultActive=yes

    [modem-manager]
    Identity=unix-group:networkmanager
    Action=org.freedesktop.ModemManager*
    ResultAny=yes
    ResultInactive=no
    ResultActive=yes
  */
  polkitConf = ''
    polkit.addRule(function(action, subject) {
      if (
        subject.isInGroup("networkmanager")
        && (action.id.indexOf("org.freedesktop.NetworkManager.") == 0
            || action.id.indexOf("org.freedesktop.ModemManager")  == 0
        ))
          { return polkit.Result.YES; }
    });
  '';

  ns = xs: pkgs.writeText "nameservers" (
    concatStrings (map (s: "nameserver ${s}\n") xs)
  );

  overrideNameserversScript = pkgs.writeScript "02overridedns" ''
    #!/bin/sh
    PATH=${with pkgs; makeBinPath [ gnused gnugrep coreutils ]}
    tmp=$(mktemp)
    sed '/nameserver /d' /etc/resolv.conf > $tmp
    grep 'nameserver ' /etc/resolv.conf | \
      grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
    cat $tmp ${ns cfg.insertNameservers} $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf
    rm -f $tmp $tmp.ns
  '';

  dispatcherTypesSubdirMap = {
    basic = "";
    pre-up = "pre-up.d/";
    pre-down = "pre-down.d/";
  };

  macAddressOpt = mkOption {
    type = types.either types.str (types.enum ["permanent" "preserve" "random" "stable"]);
    default = "preserve";
    example = "00:11:22:33:44:55";
    description = ''
      Set the MAC address of the interface.
      <variablelist>
        <varlistentry>
          <term>"XX:XX:XX:XX:XX:XX"</term>
          <listitem><para>MAC address of the interface</para></listitem>
        </varlistentry>
        <varlistentry>
          <term><literal>"permanent"</literal></term>
          <listitem><para>Use the permanent MAC address of the device</para></listitem>
        </varlistentry>
        <varlistentry>
          <term><literal>"preserve"</literal></term>
          <listitem><para>Don’t change the MAC address of the device upon activation</para></listitem>
        </varlistentry>
        <varlistentry>
          <term><literal>"random"</literal></term>
          <listitem><para>Generate a randomized value upon each connect</para></listitem>
        </varlistentry>
        <varlistentry>
          <term><literal>"stable"</literal></term>
          <listitem><para>Generate a stable, hashed MAC address</para></listitem>
        </varlistentry>
      </variablelist>
    '';
  };

in {

  meta = {
    maintainers = teams.freedesktop.members;
  };

  ###### interface

  options = {

    networking.networkmanager = {

      enable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to use NetworkManager to obtain an IP address and other
          configuration for all network interfaces that are not manually
          configured. If enabled, a group <literal>networkmanager</literal>
          will be created. Add all users that should have permission
          to change network settings to this group.
        '';
      };

      connectionConfig = mkOption {
        type = with types; attrsOf (nullOr (oneOf [
          bool
          int
          str
        ]));
        default = {};
        description = ''
          Configuration for the [connection] section of NetworkManager.conf.
          Refer to
          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#id-1.2.3.11
          </link>
          or
          <citerefentry>
            <refentrytitle>NetworkManager.conf</refentrytitle>
            <manvolnum>5</manvolnum>
          </citerefentry>
          for more information.
        '';
      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Configuration appended to the generated NetworkManager.conf.
          Refer to
          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
          </link>
          or
          <citerefentry>
            <refentrytitle>NetworkManager.conf</refentrytitle>
            <manvolnum>5</manvolnum>
          </citerefentry>
          for more information.
        '';
      };

      unmanaged = mkOption {
        type = types.listOf types.str;
        default = [];
        description = ''
          List of interfaces that will not be managed by NetworkManager.
          Interface name can be specified here, but if you need more fidelity,
          refer to
          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec">
            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec
          </link>
          or the "Device List Format" Appendix of
          <citerefentry>
            <refentrytitle>NetworkManager.conf</refentrytitle>
            <manvolnum>5</manvolnum>
          </citerefentry>.
        '';
      };

      packages = mkOption {
        type = types.listOf types.package;
        default = [ ];
        description = ''
          Extra packages that provide NetworkManager plugins.
        '';
        apply = list: basePackages ++ list;
      };

      dhcp = mkOption {
        type = types.enum [ "dhclient" "dhcpcd" "internal" ];
        default = "internal";
        description = ''
          Which program (or internal library) should be used for DHCP.
        '';
      };

      firewallBackend = mkOption {
        type = types.enum [ "iptables" "nftables" "none" ];
        default = "iptables";
        description = ''
          Which firewall backend should be used for configuring masquerading with shared mode.
          If set to none, NetworkManager doesn't manage the configuration at all.
        '';
      };

      logLevel = mkOption {
        type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
        default = "WARN";
        description = ''
          Set the default logging verbosity level.
        '';
      };

      appendNameservers = mkOption {
        type = types.listOf types.str;
        default = [];
        description = ''
          A list of name servers that should be appended
          to the ones configured in NetworkManager or received by DHCP.
        '';
      };

      insertNameservers = mkOption {
        type = types.listOf types.str;
        default = [];
        description = ''
          A list of name servers that should be inserted before
          the ones configured in NetworkManager or received by DHCP.
        '';
      };

      ethernet.macAddress = macAddressOpt;

      wifi = {
        macAddress = macAddressOpt;

        backend = mkOption {
          type = types.enum [ "wpa_supplicant" "iwd" ];
          default = "wpa_supplicant";
          description = ''
            Specify the Wi-Fi backend used for the device.
            Currently supported are <option>wpa_supplicant</option> or <option>iwd</option> (experimental).
          '';
        };

        powersave = mkOption {
          type = types.nullOr types.bool;
          default = null;
          description = ''
            Whether to enable Wi-Fi power saving.
          '';
        };

        scanRandMacAddress = mkOption {
          type = types.bool;
          default = true;
          description = ''
            Whether to enable MAC address randomization of a Wi-Fi device
            during scanning.
          '';
        };
      };

      dns = mkOption {
        type = types.enum [ "default" "dnsmasq" "unbound" "systemd-resolved" "none" ];
        default = "default";
        description = ''
          Set the DNS (<literal>resolv.conf</literal>) processing mode.
          </para>
          <para>
          A description of these modes can be found in the main section of
          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
          </link>
          or in
          <citerefentry>
            <refentrytitle>NetworkManager.conf</refentrytitle>
            <manvolnum>5</manvolnum>
          </citerefentry>.
        '';
      };

      dispatcherScripts = mkOption {
        type = types.listOf (types.submodule {
          options = {
            source = mkOption {
              type = types.path;
              description = ''
                Path to the hook script.
              '';
            };

            type = mkOption {
              type = types.enum (attrNames dispatcherTypesSubdirMap);
              default = "basic";
              description = ''
                Dispatcher hook type. Look up the hooks described at
                <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.html">https://developer.gnome.org/NetworkManager/stable/NetworkManager.html</link>
                and choose the type depending on the output folder.
                You should then filter the event type (e.g., "up"/"down") from within your script.
              '';
            };
          };
        });
        default = [];
        example = literalExample ''
        [ {
              source = pkgs.writeText "upHook" '''

                if [ "$2" != "up" ]; then
                    logger "exit: event $2 != up"
                    exit
                fi

                # coreutils and iproute are in PATH too
                logger "Device $DEVICE_IFACE coming up"
            ''';
            type = "basic";
        } ]'';
        description = ''
          A list of scripts which will be executed in response to  network  events.
        '';
      };

      enableStrongSwan = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Enable the StrongSwan plugin.
          </para><para>
          If you enable this option the
          <literal>networkmanager_strongswan</literal> plugin will be added to
          the <option>networking.networkmanager.packages</option> option
          so you don't need to to that yourself.
        '';
      };
    };
  };

  imports = [
    (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ])
    (mkRemovedOptionModule ["networking" "networkmanager" "dynamicHosts"] ''
      This option was removed because allowing (multiple) regular users to
      override host entries affecting the whole system opens up a huge attack
      vector. There seem to be very rare cases where this might be useful.
      Consider setting system-wide host entries using networking.hosts, provide
      them via the DNS server in your network, or use environment.etc
      to add a file into /etc/NetworkManager/dnsmasq.d reconfiguring hostsdir.
    '')
  ];


  ###### implementation

  config = mkIf cfg.enable {

    assertions = [
      { assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [];
        message = ''
          You can not use networking.networkmanager with networking.wireless.
          Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
        '';
      }
    ];

    hardware.wirelessRegulatoryDatabase = true;

    environment.etc = with pkgs; {
      "NetworkManager/NetworkManager.conf".source = configFile;

      "NetworkManager/VPN/nm-openvpn-service.name".source =
        "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name";

      "NetworkManager/VPN/nm-vpnc-service.name".source =
        "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name";

      "NetworkManager/VPN/nm-openconnect-service.name".source =
        "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name";

      "NetworkManager/VPN/nm-fortisslvpn-service.name".source =
        "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name";

      "NetworkManager/VPN/nm-l2tp-service.name".source =
        "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";

      "NetworkManager/VPN/nm-iodine-service.name".source =
        "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";

      "NetworkManager/VPN/nm-sstp-service.name".source =
        "${networkmanager-sstp}/lib/NetworkManager/VPN/nm-sstp-service.name";
      }
      // optionalAttrs (cfg.appendNameservers != [] || cfg.insertNameservers != [])
         {
           "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript;
         }
      // optionalAttrs cfg.enableStrongSwan
         {
           "NetworkManager/VPN/nm-strongswan-service.name".source =
             "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
         }
      // listToAttrs (lib.imap1 (i: s:
         {
            name = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
            value = { mode = "0544"; inherit (s) source; };
         }) cfg.dispatcherScripts);

    environment.systemPackages = cfg.packages;

    users.groups = {
      networkmanager.gid = config.ids.gids.networkmanager;
      nm-openvpn.gid = config.ids.gids.nm-openvpn;
    };

    users.users = {
      nm-openvpn = {
        uid = config.ids.uids.nm-openvpn;
        extraGroups = [ "networkmanager" ];
      };
      nm-iodine = {
        isSystemUser = true;
        group = "networkmanager";
      };
    };

    systemd.packages = cfg.packages;

    systemd.tmpfiles.rules = [
      "d /etc/NetworkManager/system-connections 0700 root root -"
      "d /etc/ipsec.d 0700 root root -"
      "d /var/lib/NetworkManager-fortisslvpn 0700 root root -"

      "d /var/lib/dhclient 0755 root root -"
      "d /var/lib/misc 0755 root root -" # for dnsmasq.leases
    ];

    systemd.services.NetworkManager = {
      wantedBy = [ "network.target" ];
      restartTriggers = [ configFile ];

      aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];

      serviceConfig = {
        StateDirectory = "NetworkManager";
        StateDirectoryMode = 755; # not sure if this really needs to be 755
      };
    };

    systemd.services.NetworkManager-wait-online = {
      wantedBy = [ "network-online.target" ];
    };

    systemd.services.ModemManager.aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];

    # override unit as recommended by upstream - see https://github.com/NixOS/nixpkgs/issues/88089
    # TODO: keep an eye on modem-manager releases as this will eventually be added to the upstream unit
    systemd.services.ModemManager.serviceConfig.ExecStart = [
      ""
      "${pkgs.modemmanager}/sbin/ModemManager --filter-policy=STRICT"
    ];

    systemd.services.NetworkManager-dispatcher = {
      wantedBy = [ "network.target" ];
      restartTriggers = [ configFile overrideNameserversScript ];

      # useful binaries for user-specified hooks
      path = [ pkgs.iproute2 pkgs.util-linux pkgs.coreutils ];
      aliases = [ "dbus-org.freedesktop.nm-dispatcher.service" ];
    };

    # Turn off NixOS' network management when networking is managed entirely by NetworkManager
    networking = mkMerge [
      (mkIf (!delegateWireless) {
        useDHCP = false;
      })

      (mkIf cfg.enableStrongSwan {
        networkmanager.packages = [ pkgs.networkmanager_strongswan ];
      })

      (mkIf enableIwd {
        wireless.iwd.enable = true;
      })

      {
        networkmanager.connectionConfig = {
          "ipv6.ip6-privacy" = 2;
          "ethernet.cloned-mac-address" = cfg.ethernet.macAddress;
          "wifi.cloned-mac-address" = cfg.wifi.macAddress;
          "wifi.powersave" =
            if cfg.wifi.powersave == null then null
            else if cfg.wifi.powersave then 3
            else 2;
        };
      }
    ];

    boot.kernelModules = [ "ctr" ];

    security.polkit.extraConfig = polkitConf;

    services.dbus.packages = cfg.packages
      ++ optional cfg.enableStrongSwan pkgs.strongswanNM
      ++ optional (cfg.dns == "dnsmasq") pkgs.dnsmasq;

    services.udev.packages = cfg.packages;
  };
}