diff --git a/Linguard/Cli.Test/AddClientCommandShould.cs b/Linguard/Cli.Test/AddClientCommandShould.cs index 1be4fc8..9b766c3 100644 --- a/Linguard/Cli.Test/AddClientCommandShould.cs +++ b/Linguard/Cli.Test/AddClientCommandShould.cs @@ -6,6 +6,7 @@ using Linguard.Cli.Commands; using Linguard.Core.Managers; using Linguard.Core.Models.Wireguard; +using Linguard.Core.OS; using Linguard.Core.Services; using Moq; using Typin.Attributes; @@ -152,6 +153,6 @@ public async Task CreateClientWithAllowedIPs() { private Interface GenerateInterface(IConfigurationManager configuration) { return new DefaultInterfaceGenerator(configuration, - WireguardServiceMock.Object).Generate(); + WireguardServiceMock.Object, new SystemWrapper(configuration)).Generate(); } } \ No newline at end of file diff --git a/Linguard/Cli.Test/ListClientsCommandShould.cs b/Linguard/Cli.Test/ListClientsCommandShould.cs index 3045d41..6fb4900 100644 --- a/Linguard/Cli.Test/ListClientsCommandShould.cs +++ b/Linguard/Cli.Test/ListClientsCommandShould.cs @@ -5,6 +5,7 @@ using Linguard.Cli.Commands; using Linguard.Core.Managers; using Linguard.Core.Models.Wireguard; +using Linguard.Core.OS; using Linguard.Core.Services; using Moq; using Typin.Attributes; @@ -61,7 +62,7 @@ public async Task ListPeersForSpecificInterface() { } private Interface GenerateInterface(IConfigurationManager configuration) { - return new DefaultInterfaceGenerator(configuration, WireguardServiceMock.Object) + return new DefaultInterfaceGenerator(configuration, WireguardServiceMock.Object, new SystemWrapper(configuration)) .Generate(); } diff --git a/Linguard/Cli.Test/ListInterfacesCommandShould.cs b/Linguard/Cli.Test/ListInterfacesCommandShould.cs index 59cfbb9..4dd7152 100644 --- a/Linguard/Cli.Test/ListInterfacesCommandShould.cs +++ b/Linguard/Cli.Test/ListInterfacesCommandShould.cs @@ -5,6 +5,7 @@ using Linguard.Cli.Commands; using Linguard.Core.Managers; using Linguard.Core.Models.Wireguard; +using Linguard.Core.OS; using Linguard.Core.Services; using Moq; using Typin.Attributes; @@ -35,6 +36,6 @@ public async Task ListOneInterface() { } private Interface GenerateInterface(IConfigurationManager configuration) { - return new DefaultInterfaceGenerator(configuration, WireguardServiceMock.Object).Generate(); + return new DefaultInterfaceGenerator(configuration, WireguardServiceMock.Object, new SystemWrapper(configuration)).Generate(); } } \ No newline at end of file diff --git a/Linguard/Cli.Test/ShowInterfaceCommandShould.cs b/Linguard/Cli.Test/ShowInterfaceCommandShould.cs index b72a64b..437d6ed 100644 --- a/Linguard/Cli.Test/ShowInterfaceCommandShould.cs +++ b/Linguard/Cli.Test/ShowInterfaceCommandShould.cs @@ -5,6 +5,7 @@ using Linguard.Cli.Commands; using Linguard.Core.Managers; using Linguard.Core.Models.Wireguard; +using Linguard.Core.OS; using Linguard.Core.Services; using Moq; using Typin.Attributes; @@ -36,6 +37,6 @@ public async Task ShowInterface() { private static Interface GenerateInterface(IConfigurationManager configuration) { return new DefaultInterfaceGenerator(configuration, - WireguardServiceMock.Object).Generate(); + WireguardServiceMock.Object, new SystemWrapper(configuration)).Generate(); } } \ No newline at end of file diff --git a/Linguard/Cli/Commands/AddInterfaceCommand.cs b/Linguard/Cli/Commands/AddInterfaceCommand.cs index 308f711..2a35482 100644 --- a/Linguard/Cli/Commands/AddInterfaceCommand.cs +++ b/Linguard/Cli/Commands/AddInterfaceCommand.cs @@ -54,10 +54,10 @@ public AddInterfaceCommand(IConfigurationManager configurationManager, ILogger l public bool? Auto { get; set; } = default; [CommandOption("onUp", Description = "Commands to execute right after the interface is brought up.")] - public ICollection? OnUp { get; set; } = default; + public ICollection? OnUp { get; set; } = default; [CommandOption("onDown", Description = "Commands to execute right after the interface is brought down.")] - public ICollection? OnDown { get; set; } = default; + public ICollection? OnDown { get; set; } = default; [CommandOption("pubkey", Description = "The peer's public key.")] public string? PublicKey { get; set; } = default; diff --git a/Linguard/Core.Test/DefaultClientGeneratorShould.cs b/Linguard/Core.Test/DefaultClientGeneratorShould.cs index 88e3059..0401da0 100644 --- a/Linguard/Core.Test/DefaultClientGeneratorShould.cs +++ b/Linguard/Core.Test/DefaultClientGeneratorShould.cs @@ -4,6 +4,7 @@ using Linguard.Core.Managers; using Linguard.Core.Models.Wireguard; using Linguard.Core.Models.Wireguard.Validators; +using Linguard.Core.OS; using Linguard.Core.Services; using Moq; using Xunit; @@ -14,9 +15,10 @@ public class DefaultClientGeneratorShould { private static readonly Mock ConfigurationManagerMock = new DefaultConfigurationManager(); private static readonly Mock WireguardServiceMock = new(); + private static readonly ISystemWrapper SystemWrapper = new SystemWrapper(ConfigurationManagerMock.Object); private static IInterfaceGenerator InterfaceGenerator => - new DefaultInterfaceGenerator(ConfigurationManagerMock.Object, WireguardServiceMock.Object); + new DefaultInterfaceGenerator(ConfigurationManagerMock.Object, WireguardServiceMock.Object, SystemWrapper); private static IClientGenerator ClientGenerator => new DefaultClientGenerator(WireguardServiceMock.Object, ConfigurationManagerMock.Object); private static AbstractValidator Validator => new ClientValidator(ConfigurationManagerMock.Object); diff --git a/Linguard/Core.Test/DefaultInterfaceGeneratorShould.cs b/Linguard/Core.Test/DefaultInterfaceGeneratorShould.cs index 2dc6aba..8781691 100644 --- a/Linguard/Core.Test/DefaultInterfaceGeneratorShould.cs +++ b/Linguard/Core.Test/DefaultInterfaceGeneratorShould.cs @@ -4,6 +4,7 @@ using Linguard.Core.Managers; using Linguard.Core.Models.Wireguard; using Linguard.Core.Models.Wireguard.Validators; +using Linguard.Core.OS; using Linguard.Core.Services; using Moq; using Xunit; @@ -14,9 +15,12 @@ public class DefaultInterfaceGeneratorShould { private static readonly Mock ConfigurationManagerMock = new DefaultConfigurationManager(); private static readonly Mock WireguardServiceMock = new(); - private static IInterfaceGenerator Generator => - new DefaultInterfaceGenerator(ConfigurationManagerMock.Object, WireguardServiceMock.Object); - private static AbstractValidator Validator => new InterfaceValidator(ConfigurationManagerMock.Object); + private static readonly ISystemWrapper SystemWrapper = new SystemWrapper(ConfigurationManagerMock.Object); + + private static IInterfaceGenerator Generator => + new DefaultInterfaceGenerator(ConfigurationManagerMock.Object, WireguardServiceMock.Object, SystemWrapper); + private static AbstractValidator Validator => + new InterfaceValidator(ConfigurationManagerMock.Object, SystemWrapper); [Fact] public void AlwaysGenerateValidInterfaces() { diff --git a/Linguard/Core.Test/InterfaceShould.cs b/Linguard/Core.Test/InterfaceShould.cs index 482280f..51522ea 100644 --- a/Linguard/Core.Test/InterfaceShould.cs +++ b/Linguard/Core.Test/InterfaceShould.cs @@ -1,15 +1,18 @@ using System; using System.Linq; -using System.Net.NetworkInformation; +using Core.Test.Mocks; using FluentAssertions; using Linguard.Core; using Linguard.Core.Models.Wireguard; +using Linguard.Core.OS; using Linguard.Core.Utils.Wireguard; using Xunit; namespace Core.Test; public class InterfaceShould { + private readonly ISystemWrapper _system = new SystemMock().Object; + [Fact] public void CreateValidWireguardConfiguration() { @@ -42,17 +45,17 @@ public void CreateValidWireguardConfiguration() { IPv6Address = IPAddressCidr.Parse("47cc:ec62:b8b4:d4c0:9c90:4c5c:1df5:a13f/64"), PublicKey = "c892a52a-1fad-4564-af83-641744cd4dc3", PrivateKey = "16116afc-3068-4ff5-88e0-0662ef57641a", - OnUp = new [] { + OnUp = new Rule[] { "/usr/sbin/iptables -I FORWARD -i wg0 -j ACCEPT", "/usr/sbin/iptables -I FORWARD -o wg0 -j ACCEPT", "/usr/sbin/iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE" }, - OnDown = new [] { + OnDown = new Rule[] { "/usr/sbin/iptables -D FORWARD -i wg0 -j ACCEPT", "/usr/sbin/iptables -D FORWARD -o wg0 -j ACCEPT", "/usr/sbin/iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE" }, - Gateway = NetworkInterface.GetAllNetworkInterfaces().First(), + Gateway = _system.NetworkInterfaces.First(), Clients = new[] { new Client { Endpoint = new Uri("vpn.example.com", UriKind.RelativeOrAbsolute), diff --git a/Linguard/WebMock/SystemMock.cs b/Linguard/Core.Test/Mocks/SystemMock.cs similarity index 76% rename from Linguard/WebMock/SystemMock.cs rename to Linguard/Core.Test/Mocks/SystemMock.cs index 40c0940..dc761ad 100644 --- a/Linguard/WebMock/SystemMock.cs +++ b/Linguard/Core.Test/Mocks/SystemMock.cs @@ -1,13 +1,21 @@ -using System.Net.NetworkInformation; -using Core.Test.Mocks; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; using Linguard.Core.Models.Wireguard; using Linguard.Core.OS; using Moq; -namespace WebMock; +namespace Core.Test.Mocks; public class SystemMock : Mock { - private readonly List _networkInterfaces = new(); + + private readonly List _networkInterfaces = new() { + new NetworkInterfaceMock("eth0", OperationalStatus.Up).Object, + new NetworkInterfaceMock("eth1", OperationalStatus.Down).Object, + new NetworkInterfaceMock("wlan0", OperationalStatus.Up).Object, + new NetworkInterfaceMock("wlan1").Object + }; + public SystemMock() { SetupGet(o => o.NetworkInterfaces).Returns(_networkInterfaces); diff --git a/Linguard/Core/Models/Wireguard/Interface.cs b/Linguard/Core/Models/Wireguard/Interface.cs index 0add215..bbb5613 100644 --- a/Linguard/Core/Models/Wireguard/Interface.cs +++ b/Linguard/Core/Models/Wireguard/Interface.cs @@ -7,8 +7,8 @@ public class Interface : WireguardPeerBase, ICloneable { public int Port { get; set; } public bool Auto { get; set; } public ICollection Clients { get; set; } = new List(); - public ICollection OnUp { get; set; } = new List(); - public ICollection OnDown { get; set; } = new List(); + public ICollection OnUp { get; set; } = new List(); + public ICollection OnDown { get; set; } = new List(); /// /// Default primary DNS for all peers if none specified. /// diff --git a/Linguard/Core/Models/Wireguard/Rule.cs b/Linguard/Core/Models/Wireguard/Rule.cs new file mode 100644 index 0000000..9a572f2 --- /dev/null +++ b/Linguard/Core/Models/Wireguard/Rule.cs @@ -0,0 +1,12 @@ +namespace Linguard.Core.Models.Wireguard; + +public class Rule { + public string Command { get; set; } + + public static implicit operator Rule(string command) + { + return new Rule { + Command = command + }; + } +} \ No newline at end of file diff --git a/Linguard/Core/Models/Wireguard/Validators/InterfaceValidator.cs b/Linguard/Core/Models/Wireguard/Validators/InterfaceValidator.cs index 6c0b191..18e0379 100644 --- a/Linguard/Core/Models/Wireguard/Validators/InterfaceValidator.cs +++ b/Linguard/Core/Models/Wireguard/Validators/InterfaceValidator.cs @@ -1,9 +1,9 @@ -using System.Net.NetworkInformation; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using FluentValidation; using FluentValidation.Results; using Linguard.Core.Configuration; using Linguard.Core.Managers; +using Linguard.Core.OS; using Linguard.Core.Utils.Comparers; namespace Linguard.Core.Models.Wireguard.Validators; @@ -15,9 +15,11 @@ public class InterfaceValidator : AbstractValidator { private readonly IConfigurationManager _configurationManager; private IWireguardConfiguration Configuration => _configurationManager.Configuration.Wireguard; + private readonly ISystemWrapper _system; - public InterfaceValidator(IConfigurationManager configurationManager) { + public InterfaceValidator(IConfigurationManager configurationManager, ISystemWrapper system) { _configurationManager = configurationManager; + _system = system; } public override ValidationResult Validate(ValidationContext context) { @@ -51,7 +53,7 @@ private void SetGatewayRules() { .WithMessage($"{field} {Validation.CannotBeEmpty}") .DependentRules(() => { RuleFor(i => i.Gateway) - .Must(i => NetworkInterface.GetAllNetworkInterfaces() + .Must(i => _system.NetworkInterfaces .Contains(i, new NetworkInterfaceNameComparer())) .WithMessage(Validation.InvalidGateway); }); diff --git a/Linguard/Core/Services/DefaultInterfaceGenerator.cs b/Linguard/Core/Services/DefaultInterfaceGenerator.cs index f591232..12d0dae 100644 --- a/Linguard/Core/Services/DefaultInterfaceGenerator.cs +++ b/Linguard/Core/Services/DefaultInterfaceGenerator.cs @@ -4,20 +4,23 @@ using Linguard.Core.Configuration; using Linguard.Core.Managers; using Linguard.Core.Models.Wireguard; +using Linguard.Core.OS; using Linguard.Core.Utils.Wireguard; namespace Linguard.Core.Services; public class DefaultInterfaceGenerator : IInterfaceGenerator { private readonly IWireguardService _wireguardService; + private readonly ISystemWrapper _system; private readonly IConfigurationManager _configurationManager; private const int MaxTries = 100; private IWireguardConfiguration Configuration => _configurationManager.Configuration.Wireguard; public DefaultInterfaceGenerator(IConfigurationManager configurationManager, - IWireguardService wireguardService) { + IWireguardService wireguardService, ISystemWrapper system) { _configurationManager = configurationManager; _wireguardService = wireguardService; + _system = system; } public Interface Generate() { @@ -32,7 +35,7 @@ public Interface Generate() { .RuleFor(i => i.Auto, true) .RuleFor(i => i.Description, f => f.Lorem.Sentence()) .RuleFor(i => i.Gateway, f => { - var gateways = NetworkInterface.GetAllNetworkInterfaces() + var gateways = _system.NetworkInterfaces .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback) .ToArray(); return f.PickRandomParam(gateways); diff --git a/Linguard/Core/Utils/Wireguard/WireguardUtils.cs b/Linguard/Core/Utils/Wireguard/WireguardUtils.cs index fa867b4..28c839e 100644 --- a/Linguard/Core/Utils/Wireguard/WireguardUtils.cs +++ b/Linguard/Core/Utils/Wireguard/WireguardUtils.cs @@ -6,8 +6,8 @@ namespace Linguard.Core.Utils.Wireguard; public static class WireguardUtils { - public static string[] GenerateOnUpRules(string iptablesBin, string interfaceName, NetworkInterface gateway) { - return new[] { + public static ICollection GenerateOnUpRules(string iptablesBin, string interfaceName, NetworkInterface gateway) { + return new List { $"{iptablesBin} -I FORWARD -i {interfaceName} -j ACCEPT", $"{iptablesBin} -I FORWARD -o {interfaceName} -j ACCEPT", $"{iptablesBin} -t nat -I POSTROUTING -o {gateway.Name} -j MASQUERADE", @@ -16,8 +16,8 @@ public static string[] GenerateOnUpRules(string iptablesBin, string interfaceNam }; } - public static string[] GenerateOnDownRules(string iptablesBin, string interfaceName, NetworkInterface gateway) { - return new[] { + public static ICollection GenerateOnDownRules(string iptablesBin, string interfaceName, NetworkInterface gateway) { + return new List { $"{iptablesBin} -D FORWARD -i {interfaceName} -j ACCEPT", $"{iptablesBin} -D FORWARD -o {interfaceName} -j ACCEPT", $"{iptablesBin} -t nat -D POSTROUTING -o {gateway.Name} -j MASQUERADE", diff --git a/Linguard/Web/Pages/Wireguard.razor b/Linguard/Web/Pages/Wireguard.razor index c9dd43b..44aca87 100644 --- a/Linguard/Web/Pages/Wireguard.razor +++ b/Linguard/Web/Pages/Wireguard.razor @@ -1,12 +1,11 @@ @page "/wireguard" -@using Linguard.Core.Utils -@using Linguard.Core.Models.Wireguard @using Linguard.Core.Managers -@using System.Net.NetworkInformation -@using Linguard.Core.Configuration +@using Linguard.Core.Models.Wireguard @using Linguard.Core.OS @using Linguard.Core.Services +@using Linguard.Core.Utils @using Linguard.Web.Services +@using Linguard.Core.Configuration @($"{AssemblyInfo.Product} | {Title}") @@ -124,7 +123,6 @@ bool _stoppingAll; protected override void OnInitialized() { - NetworkInterface.GetAllNetworkInterfaces(); _wireguardInterfaces = Configuration.Wireguard.Interfaces .OrderBy(i => i.Name) .ToList(); diff --git a/Linguard/Web/Services/LifetimeService.cs b/Linguard/Web/Services/LifetimeService.cs index 78a1326..9d79282 100644 --- a/Linguard/Web/Services/LifetimeService.cs +++ b/Linguard/Web/Services/LifetimeService.cs @@ -3,6 +3,7 @@ using Linguard.Core.Configuration.Exceptions; using Linguard.Core.Managers; using Linguard.Core.Models.Wireguard; +using Linguard.Core.OS; using Linguard.Core.Services; using Linguard.Core.Utils; @@ -15,6 +16,7 @@ public class LifetimeService : ILifetimeService { private static readonly string WorkingDirectoryEnvironmentVariable = $"{AssemblyInfo.Product}Workdir"; private readonly Log.ILogger _logger; + private readonly ISystemWrapper _system; private readonly IWireguardService _wireguardService; private readonly IConfigurationManager _configurationManager; private IWireguardConfiguration Configuration => _configurationManager.Configuration.Wireguard; @@ -22,10 +24,11 @@ public class LifetimeService : ILifetimeService { #endregion public LifetimeService(IConfigurationManager configurationManager, IWireguardService wireguardService, - Log.ILogger logger) { + Log.ILogger logger, ISystemWrapper system) { _configurationManager = configurationManager; _wireguardService = wireguardService; _logger = logger; + _system = system; } public void OnAppStarted() { @@ -113,7 +116,7 @@ private void StopInterfaces() { } private IEnumerable GetStartedInterfaces() { - var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces() + var networkInterfaces = _system.NetworkInterfaces .Where(i => i.OperationalStatus == OperationalStatus.Up); return Configuration.Interfaces .Where(i => networkInterfaces.Any(ni => ni.Name.Equals(i.Name))); diff --git a/Linguard/Web/Shared/InterfaceConfigurationForm.razor b/Linguard/Web/Shared/InterfaceConfigurationForm.razor index 04ca848..04ff6b8 100644 --- a/Linguard/Web/Shared/InterfaceConfigurationForm.razor +++ b/Linguard/Web/Shared/InterfaceConfigurationForm.razor @@ -1,6 +1,6 @@ -@using Linguard.Core.Models.Wireguard +@using Linguard.Core.OS +@using Linguard.Core.Models.Wireguard @using Linguard.Core.Models.Wireguard.Validators -@using System.Net.NetworkInformation @namespace Linguard.Web.Shared
@@ -177,22 +177,33 @@
+ TItem="Rule"> - + + + + Total: @Iface.OnUp.Count - + + + + + @@ -200,10 +211,9 @@
- @**@ + ButtonStyle="ButtonStyle.Secondary" + Click="() => AddRow(_onUpGrid)"/>
@@ -215,23 +225,32 @@
- + - + + + + Total: @Iface.OnDown.Count - + + + + + @@ -239,10 +258,9 @@
- @**@ + ButtonStyle="ButtonStyle.Secondary" + Click="() => _onDownGrid.InsertRow(new Rule())"/>
@@ -271,6 +289,8 @@ +@inject ISystemWrapper _system + @code { [Parameter] public Interface Iface { get; set; } @@ -284,17 +304,56 @@ [Parameter] public string SubmitIcon { get; set; } = "save"; - RadzenDataGrid _onUpGrid; - RadzenDataGrid _onDownGrid; + RadzenDataGrid _onUpGrid; + RadzenDataGrid _onDownGrid; + + private string previousRuleCommand = string.Empty; + private bool _appendingRule; - IEnumerable InterfaceNames => NetworkInterface.GetAllNetworkInterfaces() + IEnumerable InterfaceNames => _system.NetworkInterfaces .Select(i => i.Name) //.Concat(Configuration.Interfaces.Select(i => i.Name)) //.Distinct() .OrderBy(n => n); void OnGatewayChanged(object gatewayName) { - Iface.Gateway = NetworkInterface.GetAllNetworkInterfaces() + Iface.Gateway = _system.NetworkInterfaces .SingleOrDefault(i => i.Name.Equals(gatewayName))!; } + + async Task EditRow(Rule data, RadzenDataGrid grid) { + previousRuleCommand = new string(data.Command); + await grid.EditRow(data); + } + + async Task SaveRow(Rule data, RadzenDataGrid grid) { + previousRuleCommand = string.Empty; + await grid.UpdateRow(data); + _appendingRule = false; + } + + async Task CancelEditRow(Rule data, RadzenDataGrid grid) { + data.Command = previousRuleCommand; + previousRuleCommand = string.Empty; + grid.CancelEditRow(data); + if (_appendingRule) { + await DeleteRow(data, grid); + } + _appendingRule = false; + } + + async Task DeleteRow(Rule data, RadzenDataGrid grid) { + if (grid.Equals(_onDownGrid)) Iface.OnDown.Remove(data); + else if (grid.Equals(_onUpGrid)) Iface.OnUp.Remove(data); + await grid.Reload(); + } + + async Task AddRow(RadzenDataGrid grid) { + _appendingRule = true; + var rule = new Rule(); + if (grid.Equals(_onDownGrid)) Iface.OnDown.Add(rule); + else if (grid.Equals(_onUpGrid)) Iface.OnUp.Add(rule); + await grid.Reload(); + await EditRow(rule, grid); + } } \ No newline at end of file diff --git a/Linguard/WebMock/Program.cs b/Linguard/WebMock/Program.cs index 618bc26..1592420 100644 --- a/Linguard/WebMock/Program.cs +++ b/Linguard/WebMock/Program.cs @@ -4,8 +4,8 @@ using Linguard.Core.Models.Wireguard.Validators; using Linguard.Core.Services; using Linguard.Core.Utils; -using Linguard.Log; using Linguard.Web.Services; +using Moq; using QRCoder; using Radzen; using WebMock; @@ -31,7 +31,7 @@ builder.Services.AddSingleton(manager); builder.Services.AddSingleton(systemMock); builder.Services.AddSingleton(wireguardServiceMock); -builder.Services.AddTransient(); +builder.Services.AddSingleton(new Mock().Object); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient, InterfaceValidator>();