diff --git a/Source/NETworkManager.Converters/FirewallInterfaceTypeToStringConverter.cs b/Source/NETworkManager.Converters/FirewallInterfaceTypeToStringConverter.cs
new file mode 100644
index 0000000000..f15e2ac52b
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallInterfaceTypeToStringConverter.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization;
+using NETworkManager.Models.Firewall;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert to translated or vice versa.
+///
+public sealed class FirewallInterfaceTypeToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to translated .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Translated .
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not FirewallInterfaceType interfaceType
+ ? "-/-"
+ : ResourceTranslator.Translate(ResourceIdentifier.FirewallInterfaceType, interfaceType);
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs b/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
new file mode 100644
index 0000000000..10ce69e7e3
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization.Resources;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert a array (Domain, Private, Public) representing firewall network
+/// profiles to a localized , or vice versa.
+///
+public sealed class FirewallNetworkProfilesToStringConverter : IValueConverter
+{
+ ///
+ /// Convert a array (Domain, Private, Public) to a localized .
+ /// Returns null when all three profiles are active so that a TargetNullValue binding
+ /// can supply the translated "Any" label.
+ ///
+ /// A array with exactly three elements.
+ ///
+ ///
+ ///
+ /// Localized, comma-separated profile list (e.g. "Domain, Private, Public").
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is not bool[] { Length: 3 } profiles)
+ return "-/-";
+
+ var names = new List(3);
+ if (profiles[0]) names.Add(Strings.Domain);
+ if (profiles[1]) names.Add(Strings.Private);
+ if (profiles[2]) names.Add(Strings.Public);
+
+ return names.Count == 0 ? "-" : string.Join(", ", names);
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/Source/NETworkManager.Converters/FirewallProtocolToStringConverter.cs b/Source/NETworkManager.Converters/FirewallProtocolToStringConverter.cs
new file mode 100644
index 0000000000..daced724af
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallProtocolToStringConverter.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization;
+using NETworkManager.Models.Firewall;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert to translated or vice versa.
+///
+public sealed class FirewallProtocolToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to translated .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Translated .
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not FirewallProtocol protocol
+ ? "-/-"
+ : ResourceTranslator.Translate(ResourceIdentifier.FirewallProtocol, protocol);
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs b/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs
new file mode 100644
index 0000000000..841e3e4177
--- /dev/null
+++ b/Source/NETworkManager.Converters/NetworkProfileToStringConverter.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using NETworkManager.Localization.Resources;
+using NETworkManager.Models.Network;
+
+namespace NETworkManager.Converters;
+
+///
+/// Convert to a localized or vice versa.
+///
+public sealed class NetworkProfileToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to a localized .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Localized representing the network profile.
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not NetworkProfile profile
+ ? "-/-"
+ : profile switch
+ {
+ NetworkProfile.Domain => Strings.Domain,
+ NetworkProfile.Private => Strings.Private,
+ NetworkProfile.Public => Strings.Public,
+ _ => "-/-"
+ };
+ }
+
+ ///
+ /// !!! Method not implemented !!!
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/Source/NETworkManager.Localization/ResourceIdentifier.cs b/Source/NETworkManager.Localization/ResourceIdentifier.cs
index 0cecf42520..803af9f1ec 100644
--- a/Source/NETworkManager.Localization/ResourceIdentifier.cs
+++ b/Source/NETworkManager.Localization/ResourceIdentifier.cs
@@ -22,5 +22,7 @@ public enum ResourceIdentifier
TcpState,
Theme,
TimeUnit,
- WiFiConnectionStatus
+ WiFiConnectionStatus,
+ FirewallProtocol,
+ FirewallInterfaceType
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 68086ee533..e3a4e842f5 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -7343,7 +7343,25 @@ public static string Program {
return ResourceManager.GetString("Program", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to Private.
+ ///
+ public static string Private {
+ get {
+ return ResourceManager.GetString("Private", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Public.
+ ///
+ public static string Public {
+ get {
+ return ResourceManager.GetString("Public", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Protocol.
///
@@ -11553,6 +11571,15 @@ public static string Block {
}
}
+ ///
+ /// Looks up a localized string similar to Network profile.
+ ///
+ public static string NetworkProfile {
+ get {
+ return ResourceManager.GetString("NetworkProfile", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Network profiles.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 789507cf64..21fc7e296c 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -2151,6 +2151,12 @@ is disabled!
Program
+
+ Private
+
+
+ Public
+
The selected custom command will be deleted permanently.
@@ -4037,6 +4043,9 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Block
+
+ Network profile
+
Network profiles
@@ -4046,4 +4055,61 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Failed to load firewall rules. {0}
+
+ Any
+
+
+ TCP
+
+
+ UDP
+
+
+ ICMPv4
+
+
+ ICMPv6
+
+
+ HOPOPT
+
+
+ GRE
+
+
+ IPv6
+
+
+ IPv6-Route
+
+
+ IPv6-Frag
+
+
+ IPv6-NoNxt
+
+
+ IPv6-Opts
+
+
+ VRRP
+
+
+ PGM
+
+
+ L2TP
+
+
+ Any
+
+
+ Wired
+
+
+ Wireless
+
+
+ Remote access
+
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Export/ExportManager.FirewallRule.cs b/Source/NETworkManager.Models/Export/ExportManager.FirewallRule.cs
new file mode 100644
index 0000000000..ec8cb81f8f
--- /dev/null
+++ b/Source/NETworkManager.Models/Export/ExportManager.FirewallRule.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using NETworkManager.Models.Firewall;
+using NETworkManager.Utilities;
+using Newtonsoft.Json;
+
+namespace NETworkManager.Models.Export;
+
+public static partial class ExportManager
+{
+ ///
+ /// Method to export objects from type to a file.
+ ///
+ /// Path to the export file.
+ /// Allowed are CSV, XML or JSON.
+ /// Objects as to export.
+ public static void Export(string filePath, ExportFileType fileType, IReadOnlyList collection)
+ {
+ switch (fileType)
+ {
+ case ExportFileType.Csv:
+ CreateCsv(collection, filePath);
+ break;
+ case ExportFileType.Xml:
+ CreateXml(collection, filePath);
+ break;
+ case ExportFileType.Json:
+ CreateJson(collection, filePath);
+ break;
+ case ExportFileType.Txt:
+ default:
+ throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null);
+ }
+ }
+
+ ///
+ /// Creates a CSV file from the given collection.
+ ///
+ private static void CreateCsv(IEnumerable collection, string filePath)
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine(
+ $"{nameof(FirewallRule.Name)}," +
+ $"{nameof(FirewallRule.IsEnabled)}," +
+ $"{nameof(FirewallRule.Direction)}," +
+ $"{nameof(FirewallRule.Action)}," +
+ $"{nameof(FirewallRule.Protocol)}," +
+ $"{nameof(FirewallRule.LocalPorts)}," +
+ $"{nameof(FirewallRule.RemotePorts)}," +
+ $"{nameof(FirewallRule.LocalAddresses)}," +
+ $"{nameof(FirewallRule.RemoteAddresses)}," +
+ $"{nameof(FirewallRule.NetworkProfiles)}," +
+ $"{nameof(FirewallRule.InterfaceType)}," +
+ $"{nameof(FirewallRule.Program)}," +
+ $"{nameof(FirewallRule.Description)}");
+
+ foreach (var rule in collection)
+ sb.AppendLine(
+ $"{CsvHelper.QuoteString(rule.Name)}," +
+ $"{rule.IsEnabled}," +
+ $"{rule.Direction}," +
+ $"{rule.Action}," +
+ $"{rule.Protocol}," +
+ $"{CsvHelper.QuoteString(rule.LocalPortsDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.RemotePortsDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.LocalAddressesDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.RemoteAddressesDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.NetworkProfilesDisplay)}," +
+ $"{rule.InterfaceType}," +
+ $"{CsvHelper.QuoteString(rule.ProgramDisplay)}," +
+ $"{CsvHelper.QuoteString(rule.Description)}");
+
+ File.WriteAllText(filePath, sb.ToString());
+ }
+
+ ///
+ /// Creates an XML file from the given collection.
+ ///
+ private static void CreateXml(IEnumerable collection, string filePath)
+ {
+ var document = new XDocument(DefaultXDeclaration,
+ new XElement(nameof(ApplicationName.Firewall),
+ new XElement(nameof(FirewallRule) + "s",
+ from rule in collection
+ select new XElement(nameof(FirewallRule),
+ new XElement(nameof(FirewallRule.Name), rule.Name),
+ new XElement(nameof(FirewallRule.IsEnabled), rule.IsEnabled),
+ new XElement(nameof(FirewallRule.Direction), rule.Direction),
+ new XElement(nameof(FirewallRule.Action), rule.Action),
+ new XElement(nameof(FirewallRule.Protocol), rule.Protocol),
+ new XElement(nameof(FirewallRule.LocalPorts), rule.LocalPortsDisplay),
+ new XElement(nameof(FirewallRule.RemotePorts), rule.RemotePortsDisplay),
+ new XElement(nameof(FirewallRule.LocalAddresses), rule.LocalAddressesDisplay),
+ new XElement(nameof(FirewallRule.RemoteAddresses), rule.RemoteAddressesDisplay),
+ new XElement(nameof(FirewallRule.NetworkProfiles), rule.NetworkProfilesDisplay),
+ new XElement(nameof(FirewallRule.InterfaceType), rule.InterfaceType),
+ new XElement(nameof(FirewallRule.Program), rule.ProgramDisplay),
+ new XElement(nameof(FirewallRule.Description), rule.Description)))));
+
+ document.Save(filePath);
+ }
+
+ ///
+ /// Creates a JSON file from the given collection.
+ ///
+ private static void CreateJson(IReadOnlyList collection, string filePath)
+ {
+ var jsonData = new object[collection.Count];
+
+ for (var i = 0; i < collection.Count; i++)
+ {
+ var rule = collection[i];
+ jsonData[i] = new
+ {
+ rule.Name,
+ rule.IsEnabled,
+ Direction = rule.Direction.ToString(),
+ Action = rule.Action.ToString(),
+ Protocol = rule.Protocol.ToString(),
+ LocalPorts = rule.LocalPortsDisplay,
+ RemotePorts = rule.RemotePortsDisplay,
+ LocalAddresses = rule.LocalAddressesDisplay,
+ RemoteAddresses = rule.RemoteAddressesDisplay,
+ NetworkProfiles = rule.NetworkProfilesDisplay,
+ InterfaceType = rule.InterfaceType.ToString(),
+ Program = rule.ProgramDisplay,
+ rule.Description
+ };
+ }
+
+ File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented));
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/Firewall.cs b/Source/NETworkManager.Models/Firewall/Firewall.cs
new file mode 100644
index 0000000000..5222e34eda
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/Firewall.cs
@@ -0,0 +1,242 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using SMA = System.Management.Automation;
+using System.Threading.Tasks;
+using log4net;
+
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a firewall configuration and management class that provides functionalities
+/// for applying and managing firewall rules based on a specified profile.
+///
+public class Firewall
+{
+ #region Variables
+
+ ///
+ /// The Logger.
+ ///
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Firewall));
+
+ private const string RuleIdentifier = "NETworkManager_";
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Reads all Windows Firewall rules whose display name starts with
+ /// and maps them to objects.
+ ///
+ /// A task that resolves to the list of matching firewall rules.
+ public static async Task> GetRulesAsync()
+ {
+ return await Task.Run(() =>
+ {
+ var rules = new List();
+
+ using var ps = SMA.PowerShell.Create();
+
+ ps.AddScript($@"
+Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
+Import-Module NetSecurity -ErrorAction Stop
+Get-NetFirewallRule -DisplayName '{RuleIdentifier}*' | ForEach-Object {{
+ $rule = $_
+ $portFilter = $rule | Get-NetFirewallPortFilter
+ $addressFilter = $rule | Get-NetFirewallAddressFilter
+ $appFilter = $rule | Get-NetFirewallApplicationFilter
+ $ifTypeFilter = $rule | Get-NetFirewallInterfaceTypeFilter
+ [PSCustomObject]@{{
+ Id = $rule.ID
+ DisplayName = $rule.DisplayName
+ Enabled = ($rule.Enabled -eq 'True')
+ Description = $rule.Description
+ Direction = [string]$rule.Direction
+ Action = [string]$rule.Action
+ Protocol = $portFilter.Protocol
+ LocalPort = ($portFilter.LocalPort -join ',')
+ RemotePort = ($portFilter.RemotePort -join ',')
+ LocalAddress = ($addressFilter.LocalAddress -join ',')
+ RemoteAddress = ($addressFilter.RemoteAddress -join ',')
+ Profile = [string]$rule.Profile
+ InterfaceType = [string]$ifTypeFilter.InterfaceType
+ Program = $appFilter.Program
+ }}
+}}");
+
+ var results = ps.Invoke();
+
+ if (ps.Streams.Error.Count > 0)
+ {
+ foreach (var error in ps.Streams.Error)
+ Log.Warn($"PowerShell error: {error}");
+ }
+
+ foreach (var result in results)
+ {
+ try
+ {
+ var displayName = result.Properties["DisplayName"]?.Value?.ToString() ?? string.Empty;
+
+ var rule = new FirewallRule
+ {
+ Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
+ IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
+ Name = displayName.StartsWith(RuleIdentifier, StringComparison.Ordinal)
+ ? displayName[RuleIdentifier.Length..]
+ : displayName,
+ Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
+ Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
+ Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
+ Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
+ LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
+ RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
+ LocalAddresses = ParseAddresses(result.Properties["LocalAddress"]?.Value?.ToString()),
+ RemoteAddresses = ParseAddresses(result.Properties["RemoteAddress"]?.Value?.ToString()),
+ NetworkProfiles = ParseProfile(result.Properties["Profile"]?.Value?.ToString()),
+ InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
+ };
+
+ var program = result.Properties["Program"]?.Value as string;
+
+ if (!string.IsNullOrWhiteSpace(program) && !program.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ rule.Program = new FirewallRuleProgram(program);
+
+ rules.Add(rule);
+ }
+ catch (Exception ex)
+ {
+ Log.Warn($"Failed to parse firewall rule: {ex.Message}");
+ }
+ }
+
+ return rules;
+ });
+ }
+
+ /// Parses a PowerShell direction string to .
+ private static FirewallRuleDirection ParseDirection(string value)
+ {
+ return value switch
+ {
+ "Outbound" => FirewallRuleDirection.Outbound,
+ _ => FirewallRuleDirection.Inbound,
+ };
+ }
+
+ /// Parses a PowerShell action string to .
+ private static FirewallRuleAction ParseAction(string value)
+ {
+ return value switch
+ {
+ "Allow" => FirewallRuleAction.Allow,
+ _ => FirewallRuleAction.Block,
+ };
+ }
+
+ /// Parses a PowerShell protocol string to .
+ private static FirewallProtocol ParseProtocol(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ return FirewallProtocol.Any;
+
+ return value.ToUpperInvariant() switch
+ {
+ "TCP" => FirewallProtocol.TCP,
+ "UDP" => FirewallProtocol.UDP,
+ "ICMPV4" => FirewallProtocol.ICMPv4,
+ "ICMPV6" => FirewallProtocol.ICMPv6,
+ "GRE" => FirewallProtocol.GRE,
+ "L2TP" => FirewallProtocol.L2TP,
+ _ => int.TryParse(value, out var proto) ? (FirewallProtocol)proto : FirewallProtocol.Any,
+ };
+ }
+
+ ///
+ /// Parses a comma-separated port string (e.g. "80,443,8080-8090") to a list of
+ /// objects. Returns an empty list for Any or blank input.
+ ///
+ private static List ParsePorts(string value)
+ {
+ var list = new List();
+
+ if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ return list;
+
+ foreach (var token in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
+ {
+ var dashIndex = token.IndexOf('-');
+
+ if (dashIndex > 0 &&
+ int.TryParse(token[..dashIndex], out var start) &&
+ int.TryParse(token[(dashIndex + 1)..], out var end))
+ {
+ list.Add(new FirewallPortSpecification(start, end));
+ }
+ else if (int.TryParse(token, out var port))
+ {
+ list.Add(new FirewallPortSpecification(port));
+ }
+ }
+
+ return list;
+ }
+
+ ///
+ /// Parses a PowerShell profile string (e.g. "Domain, Private") to a three-element boolean array
+ /// in the order Domain, Private, Public.
+ ///
+ private static bool[] ParseProfile(string value)
+ {
+ var profiles = new bool[3];
+
+ if (string.IsNullOrWhiteSpace(value))
+ return profiles;
+
+ if (value.Equals("Any", StringComparison.OrdinalIgnoreCase) ||
+ value.Equals("All", StringComparison.OrdinalIgnoreCase))
+ {
+ profiles[0] = profiles[1] = profiles[2] = true;
+ return profiles;
+ }
+
+ foreach (var token in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
+ {
+ switch (token)
+ {
+ case "Domain": profiles[0] = true; break;
+ case "Private": profiles[1] = true; break;
+ case "Public": profiles[2] = true; break;
+ }
+ }
+
+ return profiles;
+ }
+
+ ///
+ /// Parses a comma-separated address string (e.g. "192.168.1.0/24,LocalSubnet") to a list of
+ /// address strings. Returns an empty list for Any or blank input.
+ ///
+ private static List ParseAddresses(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
+ return [];
+
+ return [.. value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
+ }
+
+ /// Parses a PowerShell interface-type string to .
+ private static FirewallInterfaceType ParseInterfaceType(string value)
+ {
+ return value switch
+ {
+ "Wired" => FirewallInterfaceType.Wired,
+ "Wireless" => FirewallInterfaceType.Wireless,
+ "RemoteAccess" => FirewallInterfaceType.RemoteAccess,
+ _ => FirewallInterfaceType.Any,
+ };
+ }
+ #endregion
+}
diff --git a/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs b/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
new file mode 100644
index 0000000000..438291b6d9
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
@@ -0,0 +1,27 @@
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Defines the types of network interfaces that can be used in firewall rules.
+///
+public enum FirewallInterfaceType
+{
+ ///
+ /// Any interface type.
+ ///
+ Any = -1,
+
+ ///
+ /// Wired interface types, e.g. Ethernet.
+ ///
+ Wired,
+
+ ///
+ /// Wireless interface types, e.g. Wi-Fi.
+ ///
+ Wireless,
+
+ ///
+ /// Remote interface types, e.g. VPN, L2TP, OpenVPN, etc.
+ ///
+ RemoteAccess
+}
diff --git a/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs b/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs
new file mode 100644
index 0000000000..549cb35744
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallPortLocation.cs
@@ -0,0 +1,17 @@
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Ports of local host or remote host.
+///
+public enum FirewallPortLocation
+{
+ ///
+ /// Ports of local host.
+ ///
+ LocalPorts,
+
+ ///
+ /// Ports of remote host.
+ ///
+ RemotePorts
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs b/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs
new file mode 100644
index 0000000000..ae26b65d61
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallPortSpecification.cs
@@ -0,0 +1,66 @@
+// ReSharper disable MemberCanBePrivate.Global
+// Needed for serialization.
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a specification for defining and validating firewall ports.
+///
+///
+/// This class is used to encapsulate rules and configurations for
+/// managing firewall port restrictions or allowances. It provides
+/// properties and methods to define a range of acceptable ports or
+/// individual port specifications.
+///
+public class FirewallPortSpecification
+{
+ ///
+ /// Gets or sets the start point or initial value of a process, range, or operation.
+ ///
+ ///
+ /// The Start property typically represents the beginning state or position for sequential
+ /// processing or iteration. The exact usage of this property may vary depending on the context of
+ /// the class or object it belongs to.
+ ///
+ public int Start { get; set; }
+
+ ///
+ /// Gets or sets the endpoint or final state of a process, range, or operation.
+ ///
+ ///
+ /// This property typically represents the termination position, time, or value
+ /// in a sequence, operation, or any bounded context. Its specific meaning may vary
+ /// depending on the context in which it is used.
+ ///
+ public int End { get; set; }
+
+ ///
+ /// For serializing.
+ ///
+ public FirewallPortSpecification()
+ {
+ Start = -1;
+ End = -1;
+ }
+
+ ///
+ /// Represents the specification for a firewall port, detailing its configuration
+ /// and rules for inbound or outbound network traffic.
+ ///
+ public FirewallPortSpecification(int start, int end = -1)
+ {
+ Start = start;
+ End = end;
+ }
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current instance of the object.
+ public override string ToString()
+ {
+ if (Start is 0)
+ return string.Empty;
+
+ return End is -1 or 0 ? $"{Start}" : $"{Start}-{End}";
+ }
+}
diff --git a/Source/NETworkManager.Models/Firewall/FirewallProtocol.cs b/Source/NETworkManager.Models/Firewall/FirewallProtocol.cs
new file mode 100644
index 0000000000..da54f95e6e
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallProtocol.cs
@@ -0,0 +1,122 @@
+// ReSharper disable InconsistentNaming
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Specifies the network protocols supported by the firewall configuration.
+/// Each protocol is represented by its respective protocol number as defined in
+/// the Internet Assigned Numbers Authority (IANA) protocol registry.
+/// This enumeration is used to identify traffic based on its protocol type
+/// for filtering or access control purposes in the firewall.
+///
+public enum FirewallProtocol
+{
+ ///
+ /// Denotes the Transmission Control Protocol (TCP) used in firewall configurations.
+ /// TCP is a fundamental protocol within the Internet Protocol Suite, ensuring reliable
+ /// communication by delivering a stream of data packets in sequence with error checking
+ /// between networked devices.
+ ///
+ TCP = 6,
+
+ ///
+ /// Represents the User Datagram Protocol (UDP) in the context of firewall rules.
+ /// UDP is a connectionless protocol within the Internet Protocol (IP) suite that
+ /// allows for minimal latency by transmitting datagrams without guaranteeing delivery,
+ /// order, or error recovery.
+ ///
+ UDP = 17,
+
+ ///
+ /// Represents the Internet Control Message Protocol (ICMPv4) in the context of firewall rules.
+ /// ICMP is used by network devices, such as routers, to send error messages and operational
+ /// information, indicating issues like unreachable network destinations.
+ ///
+ ICMPv4 = 1,
+
+ ///
+ /// Represents the Internet Control Message Protocol for IPv6 (ICMPv6) in the context of firewall rules.
+ /// ICMPv6 is a supporting protocol in the Internet Protocol version 6 (IPv6) suite and is used for
+ /// diagnostic and error-reporting purposes, as well as for functions such as Neighbor Discovery Protocol (NDP).
+ ///
+ ICMPv6 = 58,
+
+ ///
+ /// Represents the IPv6 Hop-by-Hop Option (HOPOPT) protocol in the context of firewall rules.
+ /// HOPOPT is a special protocol used in IPv6 for carrying optional information that must be examined
+ /// by each node along the packet's delivery path.
+ ///
+ HOPOPT = 0,
+
+ ///
+ /// Represents the Generic Routing Encapsulation (GRE) protocol in the context of firewall rules.
+ /// GRE is a tunneling protocol developed to encapsulate a wide variety of network layer protocols
+ /// inside virtual point-to-point links. It is commonly used in creating VPNs and enabling the
+ /// transport of multicast traffic and non-IP protocols across IP networks.
+ ///
+ GRE = 47,
+
+ ///
+ /// Represents the Internet Protocol Version 6 (IPv6) in the context of firewall rules.
+ /// IPv6 is the most recent version of the Internet Protocol (IP) and provides identification
+ /// and location addressing for devices across networks, enabling communication over the internet.
+ ///
+ IPv6 = 41,
+
+ ///
+ /// Represents the IPv6-Route protocol in the context of firewall rules.
+ /// IPv6-Route is used for routing header information in IPv6 packets, which
+ /// specifies the list of one or more intermediate nodes a packet should pass
+ /// through before reaching its destination.
+ ///
+ IPv6_Route = 43,
+
+ ///
+ /// Represents the IPv6 Fragmentation Header (IPv6_Frag) in the context of firewall rules.
+ /// The IPv6 Fragmentation Header is used to support fragmentation and reassembly of
+ /// packets in IPv6 networks. It facilitates handling packets that are too large to
+ /// fit in the path MTU (Maximum Transmission Unit) of the network segment.
+ ///
+ IPv6_Frag = 44,
+
+ ///
+ /// Represents the IPv6 No Next Header protocol in the context of firewall rules.
+ /// This protocol indicates that there is no next header following the current header in the IPv6 packet.
+ /// It is primarily used in cases where the payload does not require a specific transport protocol header.
+ ///
+ IPv6_NoNxt = 59,
+
+ ///
+ /// Represents the IPv6 Options (IPv6_Opts) protocol in the context of firewall rules.
+ /// IPv6 Options is a part of the IPv6 suite used for carrying optional internet-layer information
+ /// and additional headers for specific purposes, providing extensibility in IPv6 communication.
+ ///
+ IPv6_Opts = 60,
+
+ ///
+ /// Represents the Virtual Router Redundancy Protocol (VRRP) in the context of firewall rules.
+ /// VRRP is a network protocol that provides automatic assignment of available routers to
+ /// participating hosts, ensuring redundancy and high availability of router services.
+ ///
+ VRRP = 112,
+
+ ///
+ /// Represents the Pragmatic General Multicast (PGM) protocol in the context of firewall rules.
+ /// PGM is a reliable multicast transport protocol that ensures ordered, duplicate-free,
+ /// and scalable delivery of data in multicast-enabled networks.
+ ///
+ PGM = 113,
+
+ ///
+ /// Represents the Layer 2 Tunneling Protocol (L2TP) in the context of firewall rules.
+ /// L2TP is a tunneling protocol used to support virtual private networks (VPNs) or
+ /// as part of the delivery of services by Internet Service Providers (ISPs).
+ ///
+ L2TP = 115,
+
+ ///
+ /// Represents a wildcard protocol option to match any protocol in the context of firewall rules.
+ /// The "Any" value can be used to specify that the rule applies to all network protocols
+ /// without restriction or specificity.
+ ///
+ Any = 255
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRule.cs b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
new file mode 100644
index 0000000000..1af34b849e
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRule.cs
@@ -0,0 +1,216 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a security rule used within a firewall to control network traffic based on
+/// specific conditions such as IP addresses, ports, and protocols.
+///
+public class FirewallRule
+{
+ #region Variables
+
+ ///
+ /// Internal unique identifier of the rule (i.e. the value of $rule.Id in PowerShell).
+ /// Used to target the rule in Set-NetFirewallRule / Remove-NetFirewallRule calls.
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// Human-readable display name of the firewall rule. Used for display purposes
+ /// or as an identifier in various contexts.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Indicates whether the firewall rule is enabled.
+ ///
+ public bool IsEnabled { get; set; } = true;
+
+ ///
+ /// Represents a text-based explanation or information associated with an object.
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Represents the communication protocol to be used in the network configuration.
+ ///
+ public FirewallProtocol Protocol { get; set; } = FirewallProtocol.TCP;
+
+ ///
+ /// Defines the direction of traffic impacted by the rule or configuration.
+ ///
+ public FirewallRuleDirection Direction { get; set; } = FirewallRuleDirection.Inbound;
+
+ ///
+ /// Represents the entry point and core execution logic for an application.
+ ///
+ public FirewallRuleProgram Program { get; set; }
+
+ ///
+ /// Local IP addresses or address specifiers (e.g. "192.168.1.0/24", "LocalSubnet").
+ /// An empty list means "Any".
+ ///
+ public List LocalAddresses
+ {
+ get;
+ set
+ {
+ if (value is null)
+ {
+ field = [];
+ return;
+ }
+ field = value;
+ }
+ } = [];
+
+ ///
+ /// Remote IP addresses or address specifiers (e.g. "10.0.0.0/8", "Internet").
+ /// An empty list means "Any".
+ ///
+ public List RemoteAddresses
+ {
+ get;
+ set
+ {
+ if (value is null)
+ {
+ field = [];
+ return;
+ }
+ field = value;
+ }
+ } = [];
+
+ ///
+ /// Defines the local ports associated with the firewall rule.
+ ///
+ public List LocalPorts
+ {
+ get;
+ set
+ {
+ if (value is null)
+ {
+ field = [];
+ return;
+ }
+ field = value;
+ }
+ } = [];
+
+ ///
+ /// Defines the range of remote ports associated with the firewall rule.
+ ///
+ public List RemotePorts
+ {
+ get;
+ set
+ {
+ if (value is null)
+ {
+ field = [];
+ return;
+ }
+ field = value;
+ }
+ } = [];
+
+ ///
+ /// Network profiles in order Domain, Private, Public.
+ ///
+ public bool[] NetworkProfiles
+ {
+ get;
+ set
+ {
+ if (value?.Length is not 3)
+ return;
+ field = value;
+ }
+ } = new bool[3];
+
+ public FirewallInterfaceType InterfaceType { get; set; } = FirewallInterfaceType.Any;
+
+ ///
+ /// Represents the operation to be performed or executed.
+ ///
+ public FirewallRuleAction Action { get; set; } = FirewallRuleAction.Block;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Represents a rule within the firewall configuration.
+ /// Used to control network traffic based on specified criteria, such as
+ /// ports, protocols, the interface type, network profiles, and the used programs.
+ ///
+ public FirewallRule()
+ {
+
+ }
+ #endregion
+
+ #region Display properties
+
+ /// Program path, or null when no program restriction is set.
+ public string ProgramDisplay => Program?.ToString();
+
+ /// Local addresses as a human-readable string (e.g. "192.168.1.0/24; LocalSubnet"). Returns null when unrestricted.
+ public string LocalAddressesDisplay => LocalAddresses.Count == 0 ? null : string.Join("; ", LocalAddresses);
+
+ /// Remote addresses as a human-readable string (e.g. "10.0.0.0/8"). Returns null when unrestricted.
+ public string RemoteAddressesDisplay => RemoteAddresses.Count == 0 ? null : string.Join("; ", RemoteAddresses);
+
+ /// Local ports as a human-readable string (e.g. "80; 443; 8080-8090"). Returns null when unrestricted.
+ public string LocalPortsDisplay => LocalPorts.Count == 0 ? null : PortsToString(LocalPorts);
+
+ /// Remote ports as a human-readable string (e.g. "80; 443"). Returns null when unrestricted.
+ public string RemotePortsDisplay => RemotePorts.Count == 0 ? null : PortsToString(RemotePorts);
+
+ ///
+ /// Network profiles (Domain / Private / Public) as a comma-separated string.
+ /// Returns "Any" when all three are set.
+ ///
+ public string NetworkProfilesDisplay
+ {
+ get
+ {
+ if (NetworkProfiles.Length == 3 && NetworkProfiles.All(x => x))
+ return "Any";
+
+ var names = new List(3);
+ if (NetworkProfiles.Length > 0 && NetworkProfiles[0]) names.Add("Domain");
+ if (NetworkProfiles.Length > 1 && NetworkProfiles[1]) names.Add("Private");
+ if (NetworkProfiles.Length > 2 && NetworkProfiles[2]) names.Add("Public");
+
+ return names.Count == 0 ? "-" : string.Join(", ", names);
+ }
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Converts a collection of port numbers to a single, comma-separated string representation.
+ ///
+ /// A collection of integers representing port numbers.
+ /// Separator character to use
+ /// Separate entries with a space.
+ /// A separated string containing all the port numbers from the input collection.
+ public static string PortsToString(List ports, char separator = ';', bool spacing = true)
+ {
+ if (ports.Count is 0)
+ return string.Empty;
+
+ var delimiter = spacing ? $"{separator} " : separator.ToString();
+
+ return string.Join(delimiter, ports.Select(port => port.ToString()));
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs b/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs
new file mode 100644
index 0000000000..eaaa63cfc4
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleAction.cs
@@ -0,0 +1,20 @@
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents the action, if the rule filter applies.
+///
+public enum FirewallRuleAction
+{
+ ///
+ /// Represents the action to block network traffic in a firewall rule.
+ ///
+ Block,
+
+ ///
+ /// Represents the action to allow network traffic.
+ ///
+ Allow,
+
+ // Unsupported for now
+ //AllowIPsec
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRuleDirection.cs b/Source/NETworkManager.Models/Firewall/FirewallRuleDirection.cs
new file mode 100644
index 0000000000..ddd72c116f
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleDirection.cs
@@ -0,0 +1,18 @@
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a firewall rule direction that allows or processes network traffic
+/// incoming to the system or network from external sources.
+///
+public enum FirewallRuleDirection
+{
+ ///
+ /// Inbound packets.
+ ///
+ Inbound,
+
+ ///
+ /// Outbound packets.
+ ///
+ Outbound
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs b/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
new file mode 100644
index 0000000000..12133b1344
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
@@ -0,0 +1,100 @@
+using System;
+using System.IO;
+using System.Text.Json.Serialization;
+using System.Xml.Serialization;
+
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Represents a program associated with a firewall rule.
+///
+public class FirewallRuleProgram : ICloneable
+{
+ #region Variables
+ ///
+ /// Program to apply rule to.
+ ///
+ [JsonIgnore]
+ [XmlIgnore]
+ public FileInfo Executable {
+ private set;
+ get
+ {
+ if (field is null && Name is not null)
+ field = new FileInfo(Name);
+
+ return field;
+ }
+ }
+
+ ///
+ /// Represents the name associated with the object.
+ ///
+ public string Name
+ {
+ get;
+ // Public modifier required for deserialization
+ // ReSharper disable once MemberCanBePrivate.Global
+ // ReSharper disable once PropertyCanBeMadeInitOnly.Global
+ set
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return;
+
+ Executable = new FileInfo(value);
+ field = value;
+ }
+ }
+ #endregion
+
+ #region Constructor
+ ///
+ /// Public empty constructor is required for de-/serialization.
+ ///
+ // ReSharper disable once MemberCanBePrivate.Global
+ public FirewallRuleProgram()
+ {
+ }
+
+ ///
+ /// Construct program reference for firewall rule.
+ ///
+ ///
+ public FirewallRuleProgram(string pathToExe)
+ {
+ ArgumentNullException.ThrowIfNull(pathToExe);
+ var exe = new FileInfo(pathToExe);
+ Executable = exe;
+ Name = exe.FullName;
+ }
+ #endregion
+
+ #region Methods
+ ///
+ /// Convert the full file path to string.
+ ///
+ ///
+ public override string ToString()
+ {
+ return Executable?.FullName;
+ }
+
+ ///
+ /// Clone instance.
+ ///
+ /// An instance clone.
+ public object Clone()
+ {
+ try
+ {
+ return new FirewallRuleProgram(Executable?.FullName);
+ }
+ catch (ArgumentNullException)
+ {
+ return new FirewallRuleProgram();
+ }
+
+ }
+
+ #endregion
+}
diff --git a/Source/NETworkManager.Models/NETworkManager.Models.csproj b/Source/NETworkManager.Models/NETworkManager.Models.csproj
index c913dd5488..d5123a011d 100644
--- a/Source/NETworkManager.Models/NETworkManager.Models.csproj
+++ b/Source/NETworkManager.Models/NETworkManager.Models.csproj
@@ -61,4 +61,8 @@
PreserveNewest
+
+
+
+
diff --git a/Source/NETworkManager.Models/Network/NetworkInterface.cs b/Source/NETworkManager.Models/Network/NetworkInterface.cs
index 6fbc000e2b..ae641531d2 100644
--- a/Source/NETworkManager.Models/Network/NetworkInterface.cs
+++ b/Source/NETworkManager.Models/Network/NetworkInterface.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Win32;
using NETworkManager.Utilities;
+using SMA = System.Management.Automation;
namespace NETworkManager.Models.Network;
@@ -72,6 +73,37 @@ public static List GetNetworkInterfaces()
{
List listNetworkInterfaceInfo = [];
+ // Query network profiles (Domain/Private/Public) for all connected interfaces via PowerShell.
+ // Keyed by InterfaceAlias which matches networkInterface.Name in the .NET API.
+ var profileByAlias = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ try
+ {
+ using var ps = SMA.PowerShell.Create();
+ ps.AddScript("Get-NetConnectionProfile | Select-Object InterfaceAlias, NetworkCategory");
+
+ foreach (var result in ps.Invoke())
+ {
+ var alias = result.Properties["InterfaceAlias"]?.Value?.ToString();
+ var category = result.Properties["NetworkCategory"]?.Value?.ToString();
+
+ if (string.IsNullOrEmpty(alias))
+ continue;
+
+ profileByAlias[alias] = category switch
+ {
+ "DomainAuthenticated" => NetworkProfile.Domain,
+ "Private" => NetworkProfile.Private,
+ "Public" => NetworkProfile.Public,
+ _ => NetworkProfile.NotConfigured
+ };
+ }
+ }
+ catch
+ {
+ // Profile lookup is best-effort; proceed without profile information on error.
+ }
+
foreach (var networkInterface in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces())
{
// NetworkInterfaceType 53 is proprietary virtual/internal interface
@@ -194,7 +226,10 @@ public static List GetNetworkInterfaces()
IPv6Gateway = [.. listIPv6Gateway],
DNSAutoconfigurationEnabled = dnsAutoconfigurationEnabled,
DNSSuffix = ipProperties.DnsSuffix,
- DNSServer = [.. ipProperties.DnsAddresses]
+ DNSServer = [.. ipProperties.DnsAddresses],
+ Profile = profileByAlias.TryGetValue(networkInterface.Name, out var profile)
+ ? profile
+ : NetworkProfile.NotConfigured
});
}
diff --git a/Source/NETworkManager.Models/Network/NetworkInterfaceInfo.cs b/Source/NETworkManager.Models/Network/NetworkInterfaceInfo.cs
index ced21944f1..f5130f49a5 100644
--- a/Source/NETworkManager.Models/Network/NetworkInterfaceInfo.cs
+++ b/Source/NETworkManager.Models/Network/NetworkInterfaceInfo.cs
@@ -120,8 +120,8 @@ public class NetworkInterfaceInfo
public IPAddress[] DNSServer { get; set; }
///
- /// Firewall network category (Private, Public, Domain)
+ /// Network category assigned by Windows (Domain, Private, Public).
+ /// when the interface has no active connection profile.
///
- // NOT IMPLEMENTED YET
- //public NetworkProfiles Profiles { get; set; }
+ public NetworkProfile Profile { get; set; } = NetworkProfile.NotConfigured;
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Network/NetworkProfiles.cs b/Source/NETworkManager.Models/Network/NetworkProfiles.cs
new file mode 100644
index 0000000000..00dd4d9124
--- /dev/null
+++ b/Source/NETworkManager.Models/Network/NetworkProfiles.cs
@@ -0,0 +1,27 @@
+namespace NETworkManager.Models.Network;
+
+///
+/// Defines the network profile detected by Windows.
+///
+public enum NetworkProfile
+{
+ ///
+ /// Network profile is not configured.
+ ///
+ NotConfigured = -1,
+
+ ///
+ /// Network has an Active Directory (AD) controller and you are authenticated.
+ ///
+ Domain,
+
+ ///
+ /// Network is private. Firewall will allow most connections.
+ ///
+ Private,
+
+ ///
+ /// Network is public. Firewall will block most connections.
+ ///
+ Public
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
index 61260da4e5..ac6442df1e 100644
--- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
+++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
@@ -234,6 +234,9 @@ public static class GlobalStaticConfiguration
// Application: Hosts File Editor
public static ExportFileType HostsFileEditor_ExportFileType => ExportFileType.Csv;
+ // Application: Firewall
+ public static ExportFileType Firewall_ExportFileType => ExportFileType.Csv;
+
// Application: Discovery Protocol
public static DiscoveryProtocol DiscoveryProtocol_Protocol => DiscoveryProtocol.LldpCdp;
public static int DiscoveryProtocol_Duration => 60;
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index bbe4f5a968..930fd23061 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -3255,6 +3255,32 @@ public double Firewall_ProfileWidth
}
} = GlobalStaticConfiguration.Profile_DefaultWidthExpanded;
+ public string Firewall_ExportFilePath
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public ExportFileType Firewall_ExportFileType
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = GlobalStaticConfiguration.Firewall_ExportFileType;
+
#endregion
#region Discovery Protocol
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index a11d89993d..4a5173afe6 100644
--- a/Source/NETworkManager/ViewModels/FirewallViewModel.cs
+++ b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
@@ -1,22 +1,29 @@
-using System.Threading.Tasks;
+using log4net;
+using MahApps.Metro.Controls;
+using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
-
-namespace NETworkManager.ViewModels;
-
-using System.Windows;
-using System.Windows.Data;
-using System.Windows.Threading;
+using NETworkManager.Models.Export;
+using NETworkManager.Models.Firewall;
+using NETworkManager.Settings;
+using NETworkManager.Utilities;
+using NETworkManager.Views;
+using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
-using System;
using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
using System.Windows.Input;
+using System.Windows.Threading;
+
+namespace NETworkManager.ViewModels;
+
using Controls;
using Profiles;
using Models;
-using Settings;
-using Utilities;
///
/// ViewModel for the Firewall application.
@@ -24,12 +31,125 @@ namespace NETworkManager.ViewModels;
public class FirewallViewModel : ViewModelBase, IProfileManager
{
#region Variables
-
+
+ private static readonly ILog Log = LogManager.GetLogger(typeof(FirewallViewModel));
+
private readonly DispatcherTimer _searchDispatcherTimer = new();
private bool _searchDisabled;
private readonly bool _isLoading;
private bool _isViewActive = true;
+ #region Rules
+
+ ///
+ /// Gets the loaded firewall rules.
+ ///
+ public ObservableCollection Results { get; } = [];
+
+ ///
+ /// Gets the filtered/sorted view over .
+ ///
+ public ICollectionView ResultsView { get; }
+
+ ///
+ /// Gets or sets the currently selected firewall rule.
+ ///
+ public FirewallRule SelectedResult
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets the list of selected firewall rules (multi-select).
+ ///
+ public IList SelectedResults
+ {
+ get;
+ set
+ {
+ if (Equals(value, field))
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = new ArrayList();
+
+ ///
+ /// Gets or sets the search text for filtering rules.
+ ///
+ public string RulesSearch
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ ResultsView.Refresh();
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets whether a refresh is currently running.
+ ///
+ public bool IsRefreshing
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets whether the status message bar is shown.
+ ///
+ public bool IsStatusMessageDisplayed
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets the status message text.
+ ///
+ public string StatusMessage
+ {
+ get;
+ private set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ #endregion
+
#region Profiles
///
@@ -243,6 +363,25 @@ public FirewallViewModel()
{
_isLoading = true;
+ // Rules
+ ResultsView = CollectionViewSource.GetDefaultView(Results);
+ ResultsView.Filter = o =>
+ {
+ if (string.IsNullOrEmpty(RulesSearch))
+ return true;
+
+ if (o is not FirewallRule rule)
+ return false;
+
+ return rule.Name.IndexOf(RulesSearch, StringComparison.OrdinalIgnoreCase) > -1 ||
+ rule.Protocol.ToString().IndexOf(RulesSearch, StringComparison.OrdinalIgnoreCase) > -1 ||
+ rule.Action.ToString().IndexOf(RulesSearch, StringComparison.OrdinalIgnoreCase) > -1 ||
+ rule.Direction.ToString().IndexOf(RulesSearch, StringComparison.OrdinalIgnoreCase) > -1;
+ };
+
+ // Load firewall rules
+ Refresh(true).ConfigureAwait(false);
+
// Profiles
CreateTags();
@@ -280,17 +419,98 @@ private void LoadSettings()
#region ICommand & Actions
- ///
- /// Gets the command to add a new firewall entry.
- ///
+ /// Gets the command to refresh the firewall rules.
+ public ICommand RefreshCommand => new RelayCommand(_ => RefreshAction().ConfigureAwait(false), Refresh_CanExecute);
+
+ private bool Refresh_CanExecute(object _) => !IsRefreshing;
+
+ private async Task RefreshAction() => await Refresh();
+
+ /// Gets the command to add a new firewall entry.
public ICommand AddEntryCommand => new RelayCommand(_ => AddEntryAction());
- ///
- /// Action to add a new firewall entry.
- ///
private void AddEntryAction()
{
- MessageBox.Show("Not implemented");
+ // TODO: open AddFirewallRuleDialog
+ }
+
+ /// Gets the command to enable the selected firewall entry.
+ public ICommand EnableEntryCommand => new RelayCommand(_ => EnableEntryAction(), _ => SelectedResult is { IsEnabled: false });
+
+ private void EnableEntryAction()
+ {
+ // TODO: call Firewall.SetRuleEnabledAsync(SelectedResult, true)
+ }
+
+ /// Gets the command to disable the selected firewall entry.
+ public ICommand DisableEntryCommand => new RelayCommand(_ => DisableEntryAction(), _ => SelectedResult is { IsEnabled: true });
+
+ private void DisableEntryAction()
+ {
+ // TODO: call Firewall.SetRuleEnabledAsync(SelectedResult, false)
+ }
+
+ /// Gets the command to edit the selected firewall entry.
+ public ICommand EditEntryCommand => new RelayCommand(_ => EditEntryAction(), _ => SelectedResult != null);
+
+ private void EditEntryAction()
+ {
+ // TODO: open EditFirewallRuleDialog
+ }
+
+ /// Gets the command to delete the selected firewall entry.
+ public ICommand DeleteEntryCommand => new RelayCommand(_ => DeleteEntryAction(), _ => SelectedResult != null);
+
+ private void DeleteEntryAction()
+ {
+ // TODO: confirm and call Firewall.DeleteRuleAsync
+ }
+
+ /// Gets the command to export the firewall rules.
+ public ICommand ExportCommand => new RelayCommand(_ => ExportAction().ConfigureAwait(false));
+
+ private Task ExportAction()
+ {
+ var childWindow = new ExportChildWindow();
+
+ var childWindowViewModel = new ExportViewModel(async instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+
+ try
+ {
+ ExportManager.Export(instance.FilePath, instance.FileType,
+ instance.ExportAll
+ ? Results
+ : new ObservableCollection(SelectedResults.Cast().ToArray()));
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while exporting data as " + instance.FileType, ex);
+
+ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Error,
+ Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine +
+ Environment.NewLine + ex.Message, ChildWindowIcon.Error);
+ }
+
+ SettingsManager.Current.Firewall_ExportFileType = instance.FileType;
+ SettingsManager.Current.Firewall_ExportFilePath = instance.FilePath;
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ }, [
+ ExportFileType.Csv, ExportFileType.Xml, ExportFileType.Json
+ ], true, SettingsManager.Current.Firewall_ExportFileType,
+ SettingsManager.Current.Firewall_ExportFilePath);
+
+ childWindow.Title = Strings.Export;
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
+
+ return Application.Current.MainWindow.ShowChildWindowAsync(childWindow);
}
///
@@ -493,6 +713,48 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
#endregion
#region Methods
+
+ ///
+ /// Loads firewall rules from Windows via PowerShell and populates .
+ ///
+ private async Task Refresh(bool init = false)
+ {
+ if (IsRefreshing)
+ return;
+
+ IsRefreshing = true;
+ StatusMessage = Strings.RefreshingDots;
+ IsStatusMessageDisplayed = true;
+
+ if (!init)
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
+
+ try
+ {
+ var rules = await Firewall.GetRulesAsync();
+
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ Results.Clear();
+
+ foreach (var rule in rules)
+ Results.Add(rule);
+ });
+
+ StatusMessage = string.Format(Strings.ReloadedAtX, DateTime.Now.ToShortTimeString());
+ IsStatusMessageDisplayed = true;
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while loading firewall rules", ex);
+
+ StatusMessage = string.Format(Strings.FailedToLoadFirewallRulesMessage, ex.Message);
+ IsStatusMessageDisplayed = true;
+ }
+
+ IsRefreshing = false;
+ }
+
///
/// Sets the IsExpanded property for all profile groups.
///
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index 656a84922b..db23e12392 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml
+++ b/Source/NETworkManager/Views/FirewallView.xaml
@@ -12,6 +12,7 @@
xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization"
xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings"
xmlns:controls="clr-namespace:NETworkManager.Controls;assembly=NETworkManager.Controls"
+ xmlns:firewall="clr-namespace:NETworkManager.Models.Firewall;assembly=NETworkManager.Models"
xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF"
xmlns:networkManager="clr-namespace:NETworkManager"
@@ -22,6 +23,9 @@
+
+
+
@@ -37,32 +41,461 @@
+
+
+
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
@@ -102,7 +538,7 @@
-
+ /// Toggles the row details visibility when the expand/collapse chevron is clicked.
+ ///
+ private void ExpandRowDetails_OnClick(object sender, RoutedEventArgs e)
+ {
+ for (var visual = sender as Visual; visual != null; visual = VisualTreeHelper.GetParent(visual) as Visual)
+ {
+ if (visual is not DataGridRow row)
+ continue;
+
+ row.DetailsVisibility =
+ row.DetailsVisibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
+
+ break;
+ }
+ }
private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
diff --git a/Source/NETworkManager/Views/NetworkInterfaceView.xaml b/Source/NETworkManager/Views/NetworkInterfaceView.xaml
index d65bc0e61a..2230bcfebb 100644
--- a/Source/NETworkManager/Views/NetworkInterfaceView.xaml
+++ b/Source/NETworkManager/Views/NetworkInterfaceView.xaml
@@ -37,6 +37,7 @@
x:Key="IPAddressSubnetmaskTupleArrayToStringConverter" />
+
@@ -222,8 +223,9 @@
+
-
@@ -252,6 +254,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -1527,7 +1545,7 @@
IsChecked="{Binding Path=ProfileFilterTagsMatchAll}"
Margin="10,0,0,0" />
-