IfState: Difference between revisions

Felbinger (talk | contribs)
m ifstate/netns-examples: add wireguard public keys to configuration direct instead of including files
m dhcpv4: replace custom script with packaged udhcpc/default.script
 
(10 intermediate revisions by 3 users not shown)
Line 1: Line 1:
[https://ifstate.net/2.0/ IfState] is a python 3 utility designed for declarative management of Linux network interfaces. It acts as a frontend to the kernel's Netlink interface, using the <code>pyroute2</code> library to configure network settings such as IP addresses, bridges, traffic control, and WireGuard in an idempotent manner—much like an <code>iproute2</code>/<code>ethtool</code>/<code>tc</code>/<code>wg</code> wrapper.
[https://ifstate.net/2.0/ IfState] is a python 3 utility designed for declarative management of Linux network interfaces. It acts as a frontend to the kernel's Netlink interface, using the <code>pyroute2</code> library to configure network settings such as IP addresses, bridges, traffic control, and WireGuard in an idempotent manner—much like an <code>iproute2</code>/<code>ethtool</code>/<code>tc</code>/<code>wg</code> wrapper.


It will be probably be available with NixOS 25.11 (see https://github.com/NixOS/nixpkgs/pull/431047).
It is available since NixOS 25.11 (see https://github.com/NixOS/nixpkgs/pull/431047).


=== Examples ===
=== Examples ===
You can find several examples on the [https://ifstate.net/2.0/examples/ IfState website]. Due to the fact that these are yaml examples, I decided to provide some nix examples here too:<syntaxhighlight lang="nixos">{
You can find several examples on the [https://ifstate.net/2.0/examples/ IfState website]. Some include NixOS configuration instructions, while the more complex examples are covered in detail here.
  networking.ifstate = {
    enable = true;
    settings = {
      interfaces.enp3s0 = {
        addresses = [
          "192.0.2.10/24"
          "2001:db8:dead:c0de::10/64"
        ];
        link = {
          state = "up";
          kind = "physical";
        };
        identify.perm_address = "00:00:5e:00:53:00";
      };
      routing.routes = [
        {
          to = "0.0.0.0/0";
          via = "192.0.2.1";
        }
        {
          to = "::/0";
          via = "fe80::1";
        }
      ];
    };
  };
}</syntaxhighlight>


==== Network Namespaces (netns) ====
==== Network Namespaces (netns) ====
Line 92: Line 65:


To achieve this, you might want to isolate the provider network from your Global Routing Table (GRT) and bind the WireGuard endpoints. The <code>IfState</code> tool offers a link configuration option called <code>bind_netns</code>, which can be used with tunnel links (such as WireGuard, GRE, SIT, etc.) to implement this separation.
To achieve this, you might want to isolate the provider network from your Global Routing Table (GRT) and bind the WireGuard endpoints. The <code>IfState</code> tool offers a link configuration option called <code>bind_netns</code>, which can be used with tunnel links (such as WireGuard, GRE, SIT, etc.) to implement this separation.
[[File:Ifstate-vpn-gw.png|center|frameless]]


'''Important Note:''' If <code>enp0s3</code> is your provider interface, this configuration will move it into an external network namespace that contains nothing except the bound WireGuard endpoint. As a result, you won’t be able to access systemd services like your SSH server without an active WireGuard connection. Plan accordingly to avoid losing access to critical services.<syntaxhighlight lang="nixos">
'''Important Note:''' If <code>enp0s3</code> is your provider interface, this configuration will move it into an external network namespace that contains nothing except the bound WireGuard endpoint. As a result, you won’t be able to access systemd services like your SSH server without an active WireGuard connection. Plan accordingly to avoid losing access to critical services.<syntaxhighlight lang="nixos">
Line 102: Line 76:
           # public ip addresses from your upstream provider
           # public ip addresses from your upstream provider
           addresses = [
           addresses = [
             "10.20.30.10/26"
             "198.51.100.10/26"
             "2001:db8::10/64"
             "2001:db8::10/64"
           ];
           ];
Line 114: Line 88:
           {
           {
             to = "0.0.0.0/0";
             to = "0.0.0.0/0";
             via = "10.20.30.62";
             via = "198.51.100.62";
           }
           }
           {
           {
             to = "::/0";
             to = "::/0";
            dev = "enp3s0";
             via = "fe80::1";
             via = "fe80::1";
           }
           }
Line 134: Line 109:
         # the tunnel addresses for your upstream wireguard
         # the tunnel addresses for your upstream wireguard
         addresses = [
         addresses = [
           "172.16.0.100/32"
           "203.0.113.100/25"
           "2001:db8:bad:c0de::100/128"
           "2001:db8:bad:c0de::100/128"
         ];
         ];
Line 157: Line 132:
           bind_netns = "outside";
           bind_netns = "outside";
         };
         };
         # the tunnel addresses for your upstream wireguard
         # the tunnel addresses for your own clients
         addresses = [
         addresses = [
           "172.16.0.100/32"
           "203.0.113.129/25"
           "2001:db8:bad:c0de::100/128"
           "2001:db8:dead:beef::1/128"
         ];
         ];
         wireguard = {
         wireguard = {
Line 167: Line 142:
           peers."GGavg2J9HdumqCgfpFXD85GYb6T0vWmtXBVQmlj9d0w=" = {
           peers."GGavg2J9HdumqCgfpFXD85GYb6T0vWmtXBVQmlj9d0w=" = {
             allowedips = [
             allowedips = [
               "172.16.1.2/32"
               "203.0.113.130/32"
               "2001:db8:dead:beef::2/128"
               "2001:db8:dead:beef::2/128"
             ];
             ];
Line 174: Line 149:
       };
       };
     };
     };
    routing.routes = [
      {
        to = "0.0.0.0/0";
        via = "203.0.113.1";
      }
      {
        to = "::/0";
        via = "2001:db8:bad:c0de::1";
      }
    ];
   };
   };
}
}


</syntaxhighlight>
</syntaxhighlight>
==== DHCPv4 ====
<syntaxhighlight lang="nixos">
{ lib, pkgs, ... }:
{
  networking.ifstate = {
    enable = true;
    settings = {
      parameters.hooks.dhcp.script = pkgs.writeScript "ifstate-udhcp-wrapper-script.sh" ''
        ${lib.getExe' pkgs.busybox "udhcpc"} --quit --now -i $IFS_IFNAME -b --script ${pkgs.busybox}/default.script
      '';
      interfaces.eth1 = {
        addresses = [ ];
        hooks = [
          { name = "dhcp"; }
        ];
        link = {
          state = "up";
          kind = "physical";
        };
      };
    };
  };
}
</syntaxhighlight>
=== Known Issues ===
==== Firewall for netns ====
Currently the nixos modules for firewall configuration are not capable of configuring a firewall for a network namespace (see [https://github.com/NixOS/nixpkgs/issues/372414 github:nixpkgs/nixos#372414]).
It's possible to apply the nftables firewall ruleset in all network namespaces by adding the following nix configuration, but this comes with the limitation, that interface names have to be unique across all network namespaces.<syntaxhighlight lang="nixos">
# stolen from https://github.com/secshellnet/nixos/blob/main/modules/firewall.nix
{
  lib,
  pkgs,
  config,
  ...
}:
let
  netns = [ ]; # TODO add the network namespaces to apply the firewall ruleset to here
in
{
  systemd.services = builtins.listToAttrs (
    map (key: {
      name = "nftables@${key}";
      value =
        let
          cfg = config.systemd.services.nftables;
          map' = f: x: if lib.isList x then map f x else f x;
          mapFunc = file: "${lib.getExe' pkgs.iproute2 "ip"} netns exec %i ${file}";
        in
        {
          inherit (cfg)
            conflicts
            wants
            wantedBy
            reloadIfChanged
            ;
          description = "nftables firewall for network namespace %i";
          before = [ "network.target" ];
          after = [
            "network-setup.service"
            "network-pre.target"
            # netns must exist, before firewall rules can be applied
            "ifstate.service"
          ];
          serviceConfig = {
            inherit (cfg.serviceConfig) Type RemainAfterExit StateDirectory;
          }
          // builtins.listToAttrs (
            map
              (key: {
                name = key;
                value = map' mapFunc cfg.serviceConfig.${key};
              })
              [
                "ExecStart"
                "ExecStartPost"
                "ExecStop"
                "ExecReload"
              ]
          );
          unitConfig.DefaultDependencies = false;
        };
    }) netns
  );
}
</syntaxhighlight>
[[Category:Networking]]
[[Category:Python]]