/* * Copyright (c) 2023 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Common.Core.Networking; using ProtonVPN.Common.Legacy.KillSwitch; using ProtonVPN.OperatingSystems.Network.Contracts; using ProtonVPN.ProcessCommunication.Contracts.Entities.Settings; using ProtonVPN.ProcessCommunication.Contracts.Entities.Vpn; using ProtonVPN.Service.Firewall; using ProtonVPN.Service.Settings; namespace ProtonVPN.Service.Tests.KillSwitch; [TestClass] public class KillSwitchTest { private const string REMOTE_IP = "2.2.2.2"; private IFirewall _firewall; private IServiceSettings _serviceSettings; private INetworkInterfaceProvider _networkInterfaceProvider; [TestInitialize] public void Setup() { _firewall = Substitute.For(); _serviceSettings = Substitute.For(); _networkInterfaceProvider = Substitute.For(); } [TestMethod] [DataRow(SplitTunnelModeIpcEntity.Block, false)] [DataRow(SplitTunnelModeIpcEntity.Permit, true)] [DataRow(SplitTunnelModeIpcEntity.Disabled, false)] public void OnVpnConnecting_SplitTunnelBlockMode_BlockInternet(SplitTunnelModeIpcEntity mode, bool dnsLeakOnly) { // Arrange Service.KillSwitch.KillSwitch killSwitch = GetKillSwitch(mode); // Act killSwitch.OnVpnConnecting(GetConnectingVpnState()); // Assert _firewall.ReceivedWithAnyArgs(1) .EnableLeakProtection(Arg.Is(p => p.DnsLeakOnly == dnsLeakOnly)); } [TestMethod] public void OnVpnConnected_WhenSplitTunnelPermitMode_DoNotBlockInternet() { // Arrange Service.KillSwitch.KillSwitch killSwitch = GetKillSwitch(SplitTunnelModeIpcEntity.Permit); // Act killSwitch.OnVpnConnected(GetConnectedVpnState()); // Assert _firewall.Received(0) .EnableLeakProtection(new FirewallParams {ServerIp = "127.0.0.1"}); } [TestMethod] public void OnVpnDisconnected_ManualDisconnect_RestoreInternet() { // Arrange Service.KillSwitch.KillSwitch sut = new Service.KillSwitch.KillSwitch(_firewall, _serviceSettings, _networkInterfaceProvider); // Act sut.OnVpnDisconnected(GetDisconnectedVpnState(manualDisconnect: true)); // Assert _firewall.Received(1).DisableLeakProtection(); } [TestMethod] public void OnVpnDisconnected_UnexpectedDisconnectWithKillSwitchOff_RestoreInternet() { // Arrange _serviceSettings.KillSwitchMode.Returns(KillSwitchMode.Off); var sut = new Service.KillSwitch.KillSwitch(_firewall, _serviceSettings, _networkInterfaceProvider); // Act sut.OnVpnDisconnected(GetDisconnectedVpnState()); // Assert _firewall.Received(1).DisableLeakProtection(); } [TestMethod] [DataRow(VpnStatus.Pinging)] [DataRow(VpnStatus.Connecting)] [DataRow(VpnStatus.Reconnecting)] public void ExpectedLeakProtectionStatus_ShouldBe_Enabled_WhenConnecting(VpnStatus status) { // Arrange var state = new VpnState(status, default); _serviceSettings.SplitTunnelSettings.Returns(new SplitTunnelSettingsIpcEntity { Mode = SplitTunnelModeIpcEntity.Block }); Service.KillSwitch.KillSwitch killSwitch = new(_firewall, _serviceSettings, _networkInterfaceProvider); // Act bool result = killSwitch.GetExpectedLeakProtectionStatus(state); //Assert result.Should().Be(true); } [TestMethod] [DataRow(VpnStatus.Disconnecting, VpnError.None, KillSwitchMode.Off, false, false)] [DataRow(VpnStatus.Disconnecting, VpnError.None, KillSwitchMode.Soft, false, false)] [DataRow(VpnStatus.Disconnecting, VpnError.None, KillSwitchMode.Off, true, false)] [DataRow(VpnStatus.Disconnecting, VpnError.None, KillSwitchMode.Soft, true, false)] [DataRow(VpnStatus.Disconnecting, VpnError.NetshError, KillSwitchMode.Off, false, false)] [DataRow(VpnStatus.Disconnecting, VpnError.NetshError, KillSwitchMode.Off, true, false)] [DataRow(VpnStatus.Disconnecting, VpnError.NetshError, KillSwitchMode.Soft, false, true)] [DataRow(VpnStatus.Disconnecting, VpnError.NetshError, KillSwitchMode.Soft, true, true)] [DataRow(VpnStatus.Disconnected, VpnError.None, KillSwitchMode.Off, false, false)] [DataRow(VpnStatus.Disconnected, VpnError.None, KillSwitchMode.Soft, false, false)] [DataRow(VpnStatus.Disconnected, VpnError.None, KillSwitchMode.Off, true, false)] [DataRow(VpnStatus.Disconnected, VpnError.None, KillSwitchMode.Soft, true, false)] [DataRow(VpnStatus.Disconnected, VpnError.NetshError, KillSwitchMode.Off, false, false)] [DataRow(VpnStatus.Disconnected, VpnError.NetshError, KillSwitchMode.Off, true, false)] [DataRow(VpnStatus.Disconnected, VpnError.NetshError, KillSwitchMode.Soft, false, true)] [DataRow(VpnStatus.Disconnected, VpnError.NetshError, KillSwitchMode.Soft, true, true)] public void ExpectedLeakProtectionStatus_ShouldBe_Expected_WhenDisconnecting(VpnStatus status, VpnError error, KillSwitchMode killSwitchMode, bool leakProtectionEnabled, bool expected) { // Arrange var state = new VpnState(status, error, default); _serviceSettings.KillSwitchMode.Returns(killSwitchMode); _firewall.LeakProtectionEnabled.Returns(leakProtectionEnabled); Service.KillSwitch.KillSwitch killSwitch = new Service.KillSwitch.KillSwitch(_firewall, _serviceSettings, _networkInterfaceProvider); // Act bool result = killSwitch.GetExpectedLeakProtectionStatus(state); //Assert result.Should().Be(expected); } [TestMethod] [DataRow(VpnStatus.Waiting, false)] [DataRow(VpnStatus.Waiting, true)] [DataRow(VpnStatus.Authenticating, false)] [DataRow(VpnStatus.Authenticating, true)] [DataRow(VpnStatus.RetrievingConfiguration, false)] [DataRow(VpnStatus.RetrievingConfiguration, true)] [DataRow(VpnStatus.AssigningIp, false)] [DataRow(VpnStatus.AssigningIp, true)] public void ExpectedLeakProtectionStatus_ShouldBe_Firewall_LeakProtectionEnabled_WhenOtherStatus( VpnStatus status, bool leakProtectionEnabled) { // Arrange var state = new VpnState(status, default); _firewall.LeakProtectionEnabled.Returns(leakProtectionEnabled); Service.KillSwitch.KillSwitch killSwitch = new Service.KillSwitch.KillSwitch(_firewall, _serviceSettings, _networkInterfaceProvider); // Act bool result = killSwitch.GetExpectedLeakProtectionStatus(state); //Assert result.Should().Be(leakProtectionEnabled); } private Service.KillSwitch.KillSwitch GetKillSwitch(SplitTunnelModeIpcEntity mode) { _serviceSettings.SplitTunnelSettings.Returns(new SplitTunnelSettingsIpcEntity { Mode = mode, AppPaths = new string[0], Ips = new string[0] }); return new Service.KillSwitch.KillSwitch(_firewall, _serviceSettings, _networkInterfaceProvider); } private VpnState GetDisconnectedVpnState(bool manualDisconnect = false) { return new VpnState( VpnStatus.Disconnected, manualDisconnect ? VpnError.None : VpnError.Unknown, "1.1.1.1", REMOTE_IP, 443, default); } private VpnState GetConnectedVpnState() { return new VpnState( VpnStatus.Connected, VpnError.None, "1.1.1.1", REMOTE_IP, 443, default); } private VpnState GetConnectingVpnState() { return new VpnState( VpnStatus.Connecting, VpnError.None, "1.1.1.1", REMOTE_IP, 443, default); } }