From f610ecd084ffbc66aedb77a8e15d4b00eded7e93 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sun, 13 Jun 2021 21:59:02 +0200 Subject: [PATCH 1/3] ucarp: init at 1.5.2 --- pkgs/servers/ucarp/default.nix | 28 ++++++++++++++++++++++++++++ pkgs/top-level/all-packages.nix | 2 ++ 2 files changed, 30 insertions(+) create mode 100644 pkgs/servers/ucarp/default.nix diff --git a/pkgs/servers/ucarp/default.nix b/pkgs/servers/ucarp/default.nix new file mode 100644 index 000000000000..321a725cc89a --- /dev/null +++ b/pkgs/servers/ucarp/default.nix @@ -0,0 +1,28 @@ +{ stdenv, lib, fetchurl, libpcap }: + +stdenv.mkDerivation rec { + pname = "ucarp"; + version = "1.5.2"; + + src = fetchurl { + url = "https://download.pureftpd.org/pub/ucarp/ucarp-${version}.tar.bz2"; + sha256 = "0qidz5sr55nxlmnl8kcbjsrff2j97b44h9l1dmhvvjl46iji7q7j"; + }; + + buildInputs = [ libpcap ]; + + meta = with lib; { + description = "Userspace implementation of CARP"; + longDescription = '' + UCARP allows a couple of hosts to share common virtual IP addresses in + order to provide automatic failover. It is a portable userland + implementation of the secure and patent-free Common Address Redundancy + Protocol (CARP, OpenBSD's alternative to the patents-bloated VRRP). + + Warning: This package has not received any upstream updates for a long + time and can be considered as unmaintained. + ''; + license = with licenses; [ isc bsdOriginal bsd2 gpl2Plus ]; + maintainers = with maintainers; [ oxzi ]; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 393e83fc1518..d84cf5092644 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -9389,6 +9389,8 @@ in ubertooth = callPackage ../applications/radio/ubertooth { }; + ucarp = callPackage ../servers/ucarp { }; + ucl = callPackage ../development/libraries/ucl { }; ucspi-tcp = callPackage ../tools/networking/ucspi-tcp { }; From 8673a40eda6784a33f8e03521e9132ebe557d94f Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sun, 13 Jun 2021 22:00:09 +0200 Subject: [PATCH 2/3] nixos/ucarp: init --- .../from_md/release-notes/rl-2111.section.xml | 8 + .../manual/release-notes/rl-2111.section.md | 4 + nixos/modules/module-list.nix | 1 + nixos/modules/services/networking/ucarp.nix | 183 ++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 nixos/modules/services/networking/ucarp.nix diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml index 781734a189a8..72fee8d16d02 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml @@ -40,6 +40,14 @@ services.sourcehut. + + + ucarp, + an userspace implementation of the Common Address Redundancy + Protocol (CARP). Available as + networking.ucarp. + +
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index 3a29b29f1063..1aa22b3a4b6f 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -18,6 +18,10 @@ In addition to numerous new and upgraded packages, this release has the followin development. Available as [services.sourcehut](options.html#opt-services.sourcehut.enable). +* [ucarp](https://download.pureftpd.org/pub/ucarp/README), an userspace + implementation of the Common Address Redundancy Protocol (CARP). Available as + [networking.ucarp](options.html#opt-networking.ucarp.enable). + ## Backward Incompatibilities * The `staticjinja` package has been upgraded from 1.0.4 to 2.0.0 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2938f79fb16a..1a4c2fb719dc 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -839,6 +839,7 @@ ./services/networking/tox-node.nix ./services/networking/toxvpn.nix ./services/networking/tvheadend.nix + ./services/networking/ucarp.nix ./services/networking/unbound.nix ./services/networking/unifi.nix ./services/networking/v2ray.nix diff --git a/nixos/modules/services/networking/ucarp.nix b/nixos/modules/services/networking/ucarp.nix new file mode 100644 index 000000000000..9b19a19687bc --- /dev/null +++ b/nixos/modules/services/networking/ucarp.nix @@ -0,0 +1,183 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.networking.ucarp; + + ucarpExec = concatStringsSep " " ( + [ + "${cfg.package}/bin/ucarp" + "--interface=${cfg.interface}" + "--srcip=${cfg.srcIp}" + "--vhid=${toString cfg.vhId}" + "--passfile=${cfg.passwordFile}" + "--addr=${cfg.addr}" + "--advbase=${toString cfg.advBase}" + "--advskew=${toString cfg.advSkew}" + "--upscript=${cfg.upscript}" + "--downscript=${cfg.downscript}" + "--deadratio=${toString cfg.deadratio}" + ] + ++ (optional cfg.preempt "--preempt") + ++ (optional cfg.neutral "--neutral") + ++ (optional cfg.shutdown "--shutdown") + ++ (optional cfg.ignoreIfState "--ignoreifstate") + ++ (optional cfg.noMcast "--nomcast") + ++ (optional (cfg.extraParam != null) "--xparam=${cfg.extraParam}") + ); +in { + options.networking.ucarp = { + enable = mkEnableOption "ucarp, userspace implementation of CARP"; + + interface = mkOption { + type = types.str; + description = "Network interface to bind to."; + example = "eth0"; + }; + + srcIp = mkOption { + type = types.str; + description = "Source (real) IP address of this host."; + }; + + vhId = mkOption { + type = types.ints.between 1 255; + description = "Virtual IP identifier shared between CARP hosts."; + example = 1; + }; + + passwordFile = mkOption { + type = types.str; + description = "File containing shared password between CARP hosts."; + example = "/run/keys/ucarp-password"; + }; + + preempt = mkOption { + type = types.bool; + description = '' + Enable preemptive failover. + Thus, this host becomes the CARP master as soon as possible. + ''; + default = false; + }; + + neutral = mkOption { + type = types.bool; + description = "Do not run downscript at start if the host is the backup."; + default = false; + }; + + addr = mkOption { + type = types.str; + description = "Virtual shared IP address."; + }; + + advBase = mkOption { + type = types.ints.unsigned; + description = "Advertisement frequency in seconds."; + default = 1; + }; + + advSkew = mkOption { + type = types.ints.unsigned; + description = "Advertisement skew in seconds."; + default = 0; + }; + + upscript = mkOption { + type = types.path; + description = '' + Command to run after become master, the interface name, virtual address + and optional extra parameters are passed as arguments. + ''; + example = '' + pkgs.writeScript "upscript" ''' + #!/bin/sh + $\{pkgs.iproute2\}/bin/ip addr add "$2"/24 dev "$1" + '''; + ''; + }; + + downscript = mkOption { + type = types.path; + description = '' + Command to run after become backup, the interface name, virtual address + and optional extra parameters are passed as arguments. + ''; + example = '' + pkgs.writeScript "downscript" ''' + #!/bin/sh + $\{pkgs.iproute2\}/bin/ip addr del "$2"/24 dev "$1" + '''; + ''; + }; + + deadratio = mkOption { + type = types.ints.unsigned; + description = "Ratio to consider a host as dead."; + default = 3; + }; + + shutdown = mkOption { + type = types.bool; + description = "Call downscript at exit."; + default = false; + }; + + ignoreIfState = mkOption { + type = types.bool; + description = "Ignore interface state, e.g., down or no carrier."; + default = false; + }; + + noMcast = mkOption { + type = types.bool; + description = "Use broadcast instead of multicast advertisements."; + default = false; + }; + + extraParam = mkOption { + type = types.nullOr types.str; + description = "Extra parameter to pass to the up/down scripts."; + default = null; + }; + + package = mkOption { + type = types.package; + description = '' + Package that should be used for ucarp. + + Please note that the default package, pkgs.ucarp, has not received any + upstream updates for a long time and can be considered as unmaintained. + ''; + default = pkgs.ucarp; + defaultText = "pkgs.ucarp"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.ucarp = { + description = "ucarp, userspace implementation of CARP"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + Type = "exec"; + ExecStart = ucarpExec; + + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + ProtectClock = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ oxzi ]; +} From 95ca79092e3275d1ac8e3fa4d97e7c1a111f7d14 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sun, 13 Jun 2021 22:00:25 +0200 Subject: [PATCH 3/3] nixos/test/ucarp: init --- nixos/tests/all-tests.nix | 1 + nixos/tests/ucarp.nix | 66 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 nixos/tests/ucarp.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index d8fcbde6bc0c..e8c86394831d 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -433,6 +433,7 @@ in trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {}; tuptime = handleTest ./tuptime.nix {}; turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {}; + ucarp = handleTest ./ucarp.nix {}; ucg = handleTest ./ucg.nix {}; udisks2 = handleTest ./udisks2.nix {}; unbound = handleTest ./unbound.nix {}; diff --git a/nixos/tests/ucarp.nix b/nixos/tests/ucarp.nix new file mode 100644 index 000000000000..1f60f770d3a8 --- /dev/null +++ b/nixos/tests/ucarp.nix @@ -0,0 +1,66 @@ +import ./make-test-python.nix ({ pkgs, lib, ...} : + +let + addrShared = "192.168.0.1"; + addrHostA = "192.168.0.10"; + addrHostB = "192.168.0.11"; + + mkUcarpHost = addr: { config, pkgs, lib, ... }: { + networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ + { address = addr; prefixLength = 24; } + ]; + + networking.ucarp = { + enable = true; + interface = "eth1"; + srcIp = addr; + vhId = 1; + passwordFile = "${pkgs.writeText "ucarp-pass" "secure"}"; + addr = addrShared; + upscript = pkgs.writeScript "upscript" '' + #!/bin/sh + ${pkgs.iproute2}/bin/ip addr add "$2"/24 dev "$1" + ''; + downscript = pkgs.writeScript "downscript" '' + #!/bin/sh + ${pkgs.iproute2}/bin/ip addr del "$2"/24 dev "$1" + ''; + }; + }; +in { + name = "ucarp"; + meta.maintainers = with lib.maintainers; [ oxzi ]; + + nodes = { + hostA = mkUcarpHost addrHostA; + hostB = mkUcarpHost addrHostB; + }; + + testScript = '' + def is_master(host): + ipOutput = host.succeed("ip addr show dev eth1") + return "inet ${addrShared}/24" in ipOutput + + + start_all() + + # First, let both hosts start and let a master node be selected + for host, peer in [(hostA, "${addrHostB}"), (hostB, "${addrHostA}")]: + host.wait_for_unit("ucarp.service") + host.succeed(f"ping -c 1 {peer}") + + hostA.sleep(5) + + hostA_master, hostB_master = is_master(hostA), is_master(hostB) + assert hostA_master != hostB_master, "only one master node is allowed" + + master_host = hostA if hostA_master else hostB + backup_host = hostB if hostA_master else hostA + + # Let's crash the master host and let the backup take over + master_host.crash() + + backup_host.sleep(5) + assert is_master(backup_host), "backup did not take over" + ''; +})