diff --git a/nixos/modules/services/networking/nordvpn.md b/nixos/modules/services/networking/nordvpn.md index 7a7c4b46206aed..031b8fba92cc28 100644 --- a/nixos/modules/services/networking/nordvpn.md +++ b/nixos/modules/services/networking/nordvpn.md @@ -42,16 +42,42 @@ in which case add your user to the `nordvpn` group. } ``` -NordVPN provides several CLI commands, including: +To use NordVPN's Meshnet, +declare the IP to peer mapping explicitly, +for example via `networking.hosts`: + +```nix +{ + networking.hosts = { + "1.2.3.4" = [ + "my-alias" + "my-nick" + ]; + }; +} +``` + +Find the IP to peer mapping using the command + +```bash +nordvpn mesh peer list +``` + +which also lists devices *not* present inside {file}`/etc/hosts`. + +NordVPN provides several useful CLI commands, including: ```bash nordvpn login # Log in using an OAuth URL nordvpn login --token # Log in with a token from your account nordvpn c # Connect to the VPN nordvpn c ie # Connect to a NordVPN server in Ireland -nordvpn d # Disconnect from the VPN +nordvpn set mesh on # Turn on meshnet +nordvpn mesh peer list # List meshnet peers +nordvpn fileshare list # List incoming/outgoing file transfers nordvpn set protocol openvpn_tcp # Switch to OpenVPN protocol nordvpn c # Reconnect after changing protocol +nordvpn d # Disconnect from the VPN ``` Additionally, if you prefer to use the GUI, @@ -65,7 +91,13 @@ The NixOS nordvpn service includes the following security improvements: - `nordvpnd` runs as an unprivileged user - `norduserd` spawns as a user service - `nordvpnd`'s systemd unit applies additional sandboxing, -further minimizing its blast radius + further minimizing its blast radius +- `nordvpnd` can only *read* {file}`/etc/hosts` + +Additionally, the service: + +- adds `nordlynx` to the firewall's `trustedInterfaces` -**Disclaimer:** NixOS does not support meshnet. -Contributions welcome! +**Disclaimer:** this module does not support nordwhisper, +NordVPN's closed-source protocol, +since it requires the proprietary `quench` library. diff --git a/nixos/modules/services/networking/nordvpn.nix b/nixos/modules/services/networking/nordvpn.nix index b9a89ac23fe57b..e0fae6562fd108 100644 --- a/nixos/modules/services/networking/nordvpn.nix +++ b/nixos/modules/services/networking/nordvpn.nix @@ -82,6 +82,12 @@ in # nordvpnd uses resolved to configure dns services.resolved.enable = true; + networking.firewall = { + # encrypted tunnels with the server and with other meshnet peers + # can trust those + trustedInterfaces = [ "nordlynx" ]; + }; + # policy that allows nordvpnd to configure dns security.polkit = { enable = true; @@ -125,16 +131,19 @@ in ); serviceConfig = { # nordvpnd needs CAP_NET_ADMIN to configure network interfaces. + # libtelio needs CAP_NET_RAW for ping. AmbientCapabilities = [ "CAP_NET_ADMIN" + "CAP_NET_RAW" ]; CapabilityBoundingSet = [ "CAP_NET_ADMIN" + "CAP_NET_RAW" ]; DeviceAllow = [ "/dev/net/tun rw" ]; - ExecStart = lib.getExe' nordvpn "nordvpnd"; + ExecStart = "${lib.getExe' nordvpn "nordvpnd"} --hosts-readonly"; Group = cfg.group; KillMode = "process"; NoNewPrivileges = "yes"; @@ -179,13 +188,14 @@ in systemd.user.services.norduserd = { after = [ "network-online.target" ]; description = "NordUserD Service"; + unitConfig.ConditionGroup = cfg.group; serviceConfig = { ExecStart = lib.getExe' nordvpn "norduserd"; NonBlocking = true; Restart = "on-failure"; RestartSec = 5; }; - wantedBy = [ "default.target" ]; + wantedBy = [ "graphical-session.target" ]; wants = [ "network-online.target" ]; }; }; diff --git a/nixos/tests/nordvpn.nix b/nixos/tests/nordvpn.nix index 198ae290121808..18d9c5be17d777 100644 --- a/nixos/tests/nordvpn.nix +++ b/nixos/tests/nordvpn.nix @@ -5,6 +5,18 @@ nodes = let commonConfig = user: { + # Use the nftables-based firewall backend so `verify_trusted_interfaces` + # can inspect a native nftables ruleset. It achieves this by checking for + # the "trusted interfaces" comment, which the default `iptables-nft` + # backend omits. + networking.nftables.enable = true; + + # a user outside the nordvpn group. + users.users.mallory = { + isNormalUser = true; + linger = true; + }; + # norduserd reads DBUS_SESSION_BUS_ADDRESS which the # desktopManager sets on user session creation (i.e. login) services.xserver.enable = true; @@ -100,6 +112,7 @@ self.verify_nordvpn_group() self.verify_services() self.verify_polkit_rules() + self.verify_trusted_interfaces() self.verify_kernel_modules_loaded() # nordvpnd related tests @@ -109,6 +122,11 @@ self.verify_nordvpnd_runtime_directory() self.verify_nordvpnd_state_directory() + # norduserd/fileshare related tests + self.verify_norduserd_socket() + self.verify_fileshare_socket() + self.verify_nonmember_service() + self.machine.shutdown() def verify_nordvpn_user(self): @@ -137,6 +155,14 @@ expected = f'subject.isInGroup("{self.nordvpn_group}")' assert expected in rules, f"expected polkit rules to reference group {self.nordvpn_group}" + def verify_trusted_interfaces(self): + ruleset = self.machine.succeed("${lib.getExe pkgs.nftables} list ruleset") + trusted_line = next( + (line for line in ruleset.splitlines() if "trusted interfaces" in line), None + ) + assert trusted_line is not None, "expected a trusted interfaces rule in the nftables ruleset" + assert "nordlynx" in trusted_line, f"expected nordlynx to be a trusted interface, got: {trusted_line}" + def verify_kernel_modules_loaded(self): lines = self.machine.succeed("lsmod").splitlines() loaded = {line.split()[0] for line in lines[1:] if line.strip()} @@ -160,11 +186,11 @@ key, value = line.split(":") caps[key.strip()] = int(value.strip(), base=16) - # CAP_NET_ADMIN (bit 12) - expected = (1 << 12) + # CAP_NET_ADMIN (bit 12) and CAP_NET_RAW (bit 13) + expected = (1 << 12) | (1 << 13) for cap_set in ["CapAmb", "CapBnd", "CapEff", "CapPrm"]: assert caps[cap_set] == expected, ( - f"expected {cap_set} to be exactly CAP_NET_ADMIN" + f"expected {cap_set} to be exactly CAP_NET_ADMIN|CAP_NET_RAW " f"({expected:#x}), got {caps[cap_set]:#x}" ) @@ -182,6 +208,28 @@ def verify_nordvpnd_state_directory(self): self.__verify_group_and_permissions("/var/lib/nordvpn", perm="750") + def verify_norduserd_socket(self): + # norduserd listens on /tmp/-norduserd.sock + uid = self.machine.succeed(f"id -u {self.machine_user}").strip() + self.__verify_group_and_permissions(f"/tmp/{uid}-norduserd.sock", perm="660") + + def verify_fileshare_socket(self): + # fileshare is only spawned by norduserd once meshnet is enabled, + # which requires a logged in account. Instead, run nordfileshare directly. + self.machine.execute(f"sudo -u {self.machine_user} nordfileshare") + self.__verify_group_and_permissions("/tmp/fileshare.sock", perm="660") + + def verify_nonmember_service(self): + uid = self.machine.succeed("id -u mallory").strip() + self.machine.wait_for_file(f"/run/user/{uid}/bus") + status, output = self.machine.systemctl("start norduserd.service", user="mallory") + assert status == 0, f"expected norduserd to be skipped for a non-member: {output}" + for prop, expected in [("ConditionResult", "no"), ("ActiveState", "inactive"), ("NRestarts", "0")]: + actual = self.machine.get_unit_property("norduserd.service", prop, user="mallory") + assert actual == expected, f"expected {prop}={expected} for a non-member, got {actual}" + self.machine.fail(f"test -e /tmp/{uid}-norduserd.sock") + self.machine.fail("sudo -u mallory nordvpn status") + def __verify_group_and_permissions(self, path, perm): # has the correct group actual_group, actual_perm = self.machine.succeed( diff --git a/pkgs/by-name/no/nordvpn/cli.nix b/pkgs/by-name/no/nordvpn/cli.nix index 7e9115d735142c..0694853f3ea277 100644 --- a/pkgs/by-name/no/nordvpn/cli.nix +++ b/pkgs/by-name/no/nordvpn/cli.nix @@ -5,6 +5,7 @@ version, buildGoModule, + callPackage, copyDesktopItems, e2fsprogs, iproute2, @@ -15,14 +16,21 @@ nftables, openvpn, procps, + sqlite, systemdMinimal, wireguard-tools, }: +let + libdrop = callPackage ./libdrop { }; + libtelio = callPackage ./libtelio { }; +in buildGoModule (finalAttrs: { inherit src version; pname = "nordvpn-cli"; + patches = [ ./hosts-readonly.patch ]; + nativeBuildInputs = [ copyDesktopItems makeWrapper @@ -30,6 +38,12 @@ buildGoModule (finalAttrs: { vendorHash = "sha256-8HgrhVg7ERalBJRPC2+mACOyeG0iXENMc3PaWxsX41Y="; + buildInputs = [ + libdrop + libtelio + sqlite + ]; + preBuild = '' # redirect AppDataPathStatic (/usr/lib/nordvpn) to $out/bin so that # NorduserdBinaryPath resolves to $out/bin/norduserd. @@ -46,11 +60,19 @@ buildGoModule (finalAttrs: { ldflags = [ "-X main.Environment=prod" "-X main.Version=${finalAttrs.version}" + "-X main.RemotePath=/apps/linux/config" + ]; + + tags = [ + "cdnrc" + "drop" + "telio" ]; subPackages = [ "cmd/cli" "cmd/daemon" + "cmd/fileshare" "cmd/norduser" ]; @@ -63,25 +85,27 @@ buildGoModule (finalAttrs: { # skip tests requiring network access, root/CAP_NET_ADMIN, # or network state the sandbox doesn't provide - # skip libtelio tests as the package lacks meshnet support - go test $(go list ./daemon/... | grep -v /daemon/vpn/nordlynx/libtelio) -skip \ - 'Test.*FileList_RealURL|'\ - 'TestAddAllowlistRules|'\ - 'TestAddDevice|'\ - 'TestConnectionInfo_TransferRatesShallBeProvidedOnlyForConnectedState|'\ - 'TestCustomTable|'\ - 'TestDiscoverNameserverIp|'\ - 'TestFwmarkRule|'\ - 'TestH1Transport_RoundTrip|'\ - 'TestMeshnetRuleset|'\ - 'TestMultiFwmarkRule|'\ - 'TestNetlinkManager_SetUnset|'\ - 'TestRouter_Add|'\ - 'TestSetMTU|'\ - 'TestSuppressRule|'\ - 'TestTransports|'\ - 'TestUpWGInterface|'\ + skip_tests=( + 'Test.*FileList_RealURL' + 'TestAddAllowlistRules' + 'TestAddDevice' + 'TestConnectionInfo_TransferRatesShallBeProvidedOnlyForConnectedState' + 'TestCustomTable' + 'TestDiscoverNameserverIp' + 'TestFwmarkRule' + 'TestH1Transport_RoundTrip' + 'TestMeshnetRuleset' + 'TestMultiFwmarkRule' + 'TestNetlinkManager_SetUnset' + 'TestRouter_Add' + 'TestSetMTU' + 'TestSuppressRule' + 'TestTransports' + 'TestUpWGInterface' 'TestVPNRuleset' + ) + skip_pattern=$(printf '%s|' "''${skip_tests[@]}") + go test ./daemon/... -skip "''${skip_pattern%|}" runHook postCheck ''; @@ -91,6 +115,7 @@ buildGoModule (finalAttrs: { BIN_DIR=$out/bin mv $BIN_DIR/cli $BIN_DIR/nordvpn mv $BIN_DIR/daemon $BIN_DIR/nordvpnd + mv $BIN_DIR/fileshare $BIN_DIR/nordfileshare mv $BIN_DIR/norduser $BIN_DIR/norduserd # nordvpn needs icons for the system tray and notifications diff --git a/pkgs/by-name/no/nordvpn/hosts-readonly.patch b/pkgs/by-name/no/nordvpn/hosts-readonly.patch new file mode 100644 index 00000000000000..7fd30762f8758d --- /dev/null +++ b/pkgs/by-name/no/nordvpn/hosts-readonly.patch @@ -0,0 +1,166 @@ +Declined upstream, but required with a read-only /etc/hosts. +See https://github.com/NordSecurity/nordvpn-linux/issues/1648. + +diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go +--- a/cmd/daemon/main.go ++++ b/cmd/daemon/main.go +@@ -131,6 +131,7 @@ + func main() { + appStartTime := time.Now() + ksMode := flag.Bool("killswitch-mode", false, "sets killswitch rules and stops") ++ hostsReadOnly := flag.Bool("hosts-readonly", false, "treat /etc/hosts as read-only") + flag.Parse() + if *ksMode { + log.Info("Daemon running in killswitch mode") +@@ -321,7 +322,7 @@ + gwret := netlinkrouter.Retriever{} + + dnsSetter := dns.NewDNSServiceSetter(daemonEvents.Debugger.DebuggerEvents) +- dnsHostSetter := dns.NewHostsFileSetter(dns.HostsFilePath) ++ dnsHostSetter := dns.NewHostnameSetter(*hostsReadOnly, dns.HostsFilePath) + + eventsDbPath := filepath.Join(internal.DatFilesPathCommon, "moose.db") + if err := assignMooseDBPermissions(eventsDbPath); err != nil { +diff --git a/cmd/daemon/vpn_factory.go b/cmd/daemon/vpn_factory.go +--- a/cmd/daemon/vpn_factory.go ++++ b/cmd/daemon/vpn_factory.go +@@ -39,7 +39,7 @@ + + nordWhisperVPN, nordWhisperErr := getNordWhisperVPN(fwmark, envIsDev, eventsPublisher, libquenchCfg) + if nordWhisperErr != nil { +- log.Error("getting NordWhisper vpn:", nordWhisperErr) ++ log.Info("getting NordWhisper vpn:", nordWhisperErr) + } + + return func(tech config.Technology) (vpn.VPN, error) { +diff --git a/daemon/dns/hosts.go b/daemon/dns/hosts.go +--- a/daemon/dns/hosts.go ++++ b/daemon/dns/hosts.go +@@ -41,6 +41,22 @@ + } + } + ++// NoopHostsSetter is a HostnameSetter that never touches the filesystem. ++// Use it when /etc/hosts is managed externally (e.g. NixOS). ++type NoopHostsSetter struct{} ++ ++func (NoopHostsSetter) SetHosts(Hosts) error { return nil } ++func (NoopHostsSetter) UnsetHosts() error { return nil } ++ ++// NewHostnameSetter picks the HostnameSetter implementation based on ++// hostsReadOnly: true means /etc/hosts is never modified. ++func NewHostnameSetter(hostsReadOnly bool, path string) HostnameSetter { ++ if hostsReadOnly { ++ return NoopHostsSetter{} ++ } ++ return NewHostsFileSetter(path) ++} ++ + func (h Host) String() string { + return fmt.Sprintf("%s\t%s\t%s\t%s", h.IP, h.FQDN, strings.Join(h.DomainNames, "\t"), mark) + } +diff --git a/daemon/dns/hosts_test.go b/daemon/dns/hosts_test.go +--- a/daemon/dns/hosts_test.go ++++ b/daemon/dns/hosts_test.go +@@ -4,6 +4,7 @@ + "fmt" + "net/netip" + "os" ++ "path/filepath" + "testing" + + "github.com/NordSecurity/nordvpn-linux/test/category" +@@ -247,3 +248,32 @@ + }) + } + } ++ ++func TestNoopHostsSetter(t *testing.T) { ++ category.Set(t, category.Unit) ++ ++ filename := filepath.Join(t.TempDir(), "hosts") ++ original := "127.0.0.1\tlocalhost\n" ++ require.NoError(t, os.WriteFile(filename, []byte(original), 0644)) ++ before, err := os.Stat(filename) ++ require.NoError(t, err) ++ ++ setter := NoopHostsSetter{} ++ assert.NoError(t, setter.SetHosts(Hosts{{FQDN: "peer.nord"}})) ++ assert.NoError(t, setter.UnsetHosts()) ++ ++ after, err := os.Stat(filename) ++ require.NoError(t, err) ++ content, err := os.ReadFile(filename) ++ require.NoError(t, err) ++ ++ assert.Equal(t, original, string(content)) // bytes untouched ++ assert.Equal(t, before.ModTime(), after.ModTime()) // never opened ++} ++ ++func TestNewHostnameSetter(t *testing.T) { ++ category.Set(t, category.Unit) ++ ++ assert.IsType(t, NoopHostsSetter{}, NewHostnameSetter(true, HostsFilePath)) ++ assert.IsType(t, &HostsFileSetter{}, NewHostnameSetter(false, HostsFilePath)) ++} +diff --git a/norduser/process/norduser_process_manager.go b/norduser/process/norduser_process_manager.go +--- a/norduser/process/norduser_process_manager.go ++++ b/norduser/process/norduser_process_manager.go +@@ -29,7 +29,7 @@ + socket := internal.GetNorduserdSocket(uid) + if snapconf.IsUnderSnap() { + socket = internal.GetNorduserSocketSnap(uid) +- } else if _, err := os.Stat(socket); os.IsNotExist(err) { ++ } else if _, err := os.Stat(socket); os.IsNotExist(err) || os.IsPermission(err) { + socket = internal.GetNorduserSocketFork(uid) + } + +diff --git a/cmd/norduser/main.go b/cmd/norduser/main.go +--- a/cmd/norduser/main.go ++++ b/cmd/norduser/main.go +@@ -298,12 +298,19 @@ + if err := os.Remove(connURL); err != nil && !errors.Is(err, os.ErrNotExist) { + log.Error("Failed to remove old socket file:", err) + } +- listenerFunction := internal.ManualListener(connURL, internal.PermUserRWX) ++ listenerFunction := internal.ManualListener(connURL, internal.PermUserRWGroupRW) + + listener, err := listenerFunction() + if err != nil { + log.Fatalf("Error on listening to UNIX domain socket: %s", err) + } ++ ++ if gid, err := internal.GetNordvpnGid(); err != nil { ++ log.Fatalf("Error looking up nordvpn group: %s", err) ++ } else if err := os.Chown(connURL, -1, gid); err != nil { ++ log.Fatalf("Error chowning unix socket: %s", err) ++ } ++ + listener = netutil.LimitListener(listener, 100) + + usr, err := user.Current() +diff --git a/cmd/fileshare/main.go b/cmd/fileshare/main.go +--- a/cmd/fileshare/main.go ++++ b/cmd/fileshare/main.go +@@ -102,11 +102,20 @@ + log.Warn("failed to remove old socket file:", err) + } + +- listener, err := internal.ManualListener(internal.FileshareSocket, internal.PermUserRWX)() ++ listener, err := internal.ManualListener(internal.FileshareSocket, internal.PermUserRWGroupRW)() + if err != nil { + log.Error("failed to open unix socket:", err) + os.Exit(int(childprocess.CodeFailedToCreateUnixScoket)) + } ++ ++ if gid, err := internal.GetNordvpnGid(); err != nil { ++ log.Error("failed to look up nordvpn group:", err) ++ os.Exit(int(childprocess.CodeFailedToCreateUnixScoket)) ++ } else if err := os.Chown(internal.FileshareSocket, -1, gid); err != nil { ++ log.Error("failed to chown unix socket:", err) ++ os.Exit(int(childprocess.CodeFailedToCreateUnixScoket)) ++ } ++ + limitedListener := netutil.LimitListener(listener, 100) + + defer func() { diff --git a/pkgs/by-name/no/nordvpn/libdrop/default.nix b/pkgs/by-name/no/nordvpn/libdrop/default.nix new file mode 100644 index 00000000000000..b8b68404081604 --- /dev/null +++ b/pkgs/by-name/no/nordvpn/libdrop/default.nix @@ -0,0 +1,39 @@ +{ + fetchFromGitHub, + lib, + rustPlatform, + sqlite, +}: + +rustPlatform.buildRustPackage (finalAttrs: { + pname = "libdrop"; + version = "9.0.0"; + + src = fetchFromGitHub { + owner = "NordSecurity"; + repo = "libdrop"; + tag = "v${finalAttrs.version}"; + hash = "sha256-SGS8RCfIM42wglKI2pBHRocuGkT/9TsyxC5ftexPZQ4="; + }; + + buildInputs = [ sqlite ]; + + cargoHash = "sha256-trRj485fa/ShsIx+oyjgR1GImCG5Zw0sZz0oHkZe4iU="; + + doCheck = true; + + env = { + LIBDROP_RELEASE_NAME = "v${finalAttrs.version}"; + }; + + meta = { + description = "NordVPN's filesharing library."; + homepage = "https://github.com/NordSecurity/libdrop"; + license = lib.licenses.gpl3; + maintainers = with lib.maintainers; [ + different-error + novalkun + ]; + platforms = lib.platforms.linux; + }; +}) diff --git a/pkgs/by-name/no/nordvpn/libtelio/default.nix b/pkgs/by-name/no/nordvpn/libtelio/default.nix new file mode 100644 index 00000000000000..e58acaf86df7dc --- /dev/null +++ b/pkgs/by-name/no/nordvpn/libtelio/default.nix @@ -0,0 +1,80 @@ +{ + cmake, + fetchFromGitHub, + git, + lib, + libpcap, + llvmPackages, + pkg-config, + protobuf, + rustPlatform, +}: + +rustPlatform.buildRustPackage (finalAttrs: { + pname = "libtelio"; + version = "6.2.5"; + + src = fetchFromGitHub { + owner = "NordSecurity"; + repo = "libtelio"; + tag = "v${finalAttrs.version}"; + hash = "sha256-I3Tph3NOj/swbCZGbIqr2l20TwDW0oOkf4DRGYOeqf0="; + }; + + nativeBuildInputs = [ + cmake # needed by aws-lc-sys + git # needed by neptun + pkg-config + protobuf + rustPlatform.bindgenHook # needed by aws-lc-sys + ]; + + preBuild = '' + # use vendored AWS-LC instead of the one included in nixpkgs + unset AWS_LC_SYS_USE_SYSTEM + ''; + + buildInputs = [ + llvmPackages.libclang.lib # needed by aws-lc-sys + libpcap # needed by neptun + ]; + + cargoHash = "sha256-Lk959Aa9zz+pDLxO/P98ZTSWTsZ2VQrFFsi1ExfS2QQ="; + + # skip p2p network tests that won't run inside a nix sandbox + cargoTestFlags = [ + "--" + "--skip=device::tests::test_default_features_when_provider_is_empty" + "--skip=device::tests::test_default_features_when_direct_is_empty" + "--skip=device::tests::test_enable_all_direct_features" + ]; + + patches = [ + ./libtelio.patch + ]; + + cargoPatches = [ + ./llt-proto-rev.patch + ]; + + postPatch = '' + patchShebangs test_runner.sh + ''; + + env = { + BYPASS_LLT_SECRETS = "1"; + }; + + doCheck = true; + + meta = { + description = "A P2P networking library used by NordVPN."; + homepage = "https://github.com/NordSecurity/libtelio"; + license = lib.licenses.gpl3; + maintainers = with lib.maintainers; [ + different-error + novalkun + ]; + platforms = lib.platforms.linux; + }; +}) diff --git a/pkgs/by-name/no/nordvpn/libtelio/libtelio.patch b/pkgs/by-name/no/nordvpn/libtelio/libtelio.patch new file mode 100644 index 00000000000000..7d14eddf82d5f9 --- /dev/null +++ b/pkgs/by-name/no/nordvpn/libtelio/libtelio.patch @@ -0,0 +1,11 @@ +--- a/test_runner.sh ++++ b/test_runner.sh +@@ -1,7 +1,3 @@ + #!/usr/bin/env bash + +-if command -v sudo &> /dev/null; then +- sudo -E $@ +-else +- $@ +-fi ++$@ diff --git a/pkgs/by-name/no/nordvpn/libtelio/llt-proto-rev.patch b/pkgs/by-name/no/nordvpn/libtelio/llt-proto-rev.patch new file mode 100644 index 00000000000000..cf17fa9cb5539f --- /dev/null +++ b/pkgs/by-name/no/nordvpn/libtelio/llt-proto-rev.patch @@ -0,0 +1,26 @@ +The llt-proto repo has an ens.proto file required by libtelio. +The rust crate for llt-proto incorrectly does not contain that file. +Use an unmerged PR change which fixes the broken rust crate. +Note: https://github.com/NordSecurity/llt-proto/pull/2 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -2520,7 +2520,7 @@ + [[package]] + name = "llt-proto" + version = "0.1.0" +-source = "git+https://github.com/NordSecurity/llt-proto.git?tag=v1.0.0#4a9e79beebee837a7873636a164c88cf0971a4df" ++source = "git+https://github.com/NordSecurity/llt-proto.git?rev=2226e2bac38ac6f3343bf551308691f378608cd5#2226e2bac38ac6f3343bf551308691f378608cd5" + dependencies = [ + "anyhow", + "prost", +--- a/crates/telio-proto/Cargo.toml ++++ b/crates/telio-proto/Cargo.toml +@@ -11,7 +11,7 @@ + + [dependencies] + http = "1.3.1" +-llt-proto = { git = "https://github.com/NordSecurity/llt-proto.git", tag = "v1.0.0" } ++llt-proto = { git = "https://github.com/NordSecurity/llt-proto.git", rev = "2226e2bac38ac6f3343bf551308691f378608cd5" } + hyper-util.workspace = true + prost = "0.14.1" + protobuf = "3.7.2" diff --git a/pkgs/by-name/no/nordvpn/package.nix b/pkgs/by-name/no/nordvpn/package.nix index 46d3941c267e75..ce7ba1e1f563b8 100644 --- a/pkgs/by-name/no/nordvpn/package.nix +++ b/pkgs/by-name/no/nordvpn/package.nix @@ -77,10 +77,7 @@ symlinkJoin { description = "NordVPN client and GUI for Linux"; longDescription = '' NordVPN CLI and GUI applications for Linux. - This package currently does not support meshnet. - Additionally, if `networking.firewall.enable = true;`, - then also set `networking.firewall.checkReversePath = "loose";`. - The closed-source nordwhisper protocol is also not supported. + This package does not support the closed-source nordwhisper protocol. ''; }; }