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..5e5225bad9
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallNetworkProfilesToStringConverter.cs
@@ -0,0 +1,48 @@
+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 the localized "Any" label when all three profiles are active or none are active,
+ /// since both cases are treated as "Any" by the Windows Firewall PowerShell layer.
+ ///
+ /// A array with exactly three elements.
+ ///
+ ///
+ ///
+ /// Localized, comma-separated profile list (e.g. "Domain, Private, Public"), or "Any".
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is not bool[] { Length: 3 } profiles)
+ return "-/-";
+
+ if ((profiles[0] && profiles[1] && profiles[2]) || (!profiles[0] && !profiles[1] && !profiles[2]))
+ return Strings.Any;
+
+ 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 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/FirewallRuleActionToStringConverter.cs b/Source/NETworkManager.Converters/FirewallRuleActionToStringConverter.cs
new file mode 100644
index 0000000000..052932ab47
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallRuleActionToStringConverter.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 FirewallRuleActionToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to translated .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Translated .
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not FirewallRuleAction action
+ ? "-/-"
+ : ResourceTranslator.Translate(ResourceIdentifier.FirewallRuleAction, action);
+ }
+
+ ///
+ /// !!! 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/FirewallRuleDirectionToStringConverter.cs b/Source/NETworkManager.Converters/FirewallRuleDirectionToStringConverter.cs
new file mode 100644
index 0000000000..a180ff65c7
--- /dev/null
+++ b/Source/NETworkManager.Converters/FirewallRuleDirectionToStringConverter.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 FirewallRuleDirectionToStringConverter : IValueConverter
+{
+ ///
+ /// Convert to translated .
+ ///
+ /// Object from type .
+ ///
+ ///
+ ///
+ /// Translated .
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is not FirewallRuleDirection direction
+ ? "-/-"
+ : ResourceTranslator.Translate(ResourceIdentifier.FirewallRuleDirection, direction);
+ }
+
+ ///
+ /// !!! 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..fe16a6b95a
--- /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.Documentation/DocumentationManager.cs b/Source/NETworkManager.Documentation/DocumentationManager.cs
index 9e0fa88860..3907c3b97b 100644
--- a/Source/NETworkManager.Documentation/DocumentationManager.cs
+++ b/Source/NETworkManager.Documentation/DocumentationManager.cs
@@ -267,7 +267,6 @@ public static DocumentationIdentifier GetIdentifierBySettingsName(SettingsName n
SettingsName.WebConsole => GetIdentifierByApplicationName(ApplicationName.WebConsole),
SettingsName.SNMP => GetIdentifierByApplicationName(ApplicationName.SNMP),
SettingsName.SNTPLookup => GetIdentifierByApplicationName(ApplicationName.SNTPLookup),
- SettingsName.Firewall => GetIdentifierByApplicationName(ApplicationName.Firewall),
SettingsName.WakeOnLAN => GetIdentifierByApplicationName(ApplicationName.WakeOnLAN),
SettingsName.BitCalculator => GetIdentifierByApplicationName(ApplicationName.BitCalculator),
_ => DocumentationIdentifier.Default
diff --git a/Source/NETworkManager.Localization/ResourceIdentifier.cs b/Source/NETworkManager.Localization/ResourceIdentifier.cs
index 0cecf42520..1f36bc0209 100644
--- a/Source/NETworkManager.Localization/ResourceIdentifier.cs
+++ b/Source/NETworkManager.Localization/ResourceIdentifier.cs
@@ -22,5 +22,9 @@ public enum ResourceIdentifier
TcpState,
Theme,
TimeUnit,
- WiFiConnectionStatus
+ WiFiConnectionStatus,
+ FirewallProtocol,
+ FirewallInterfaceType,
+ FirewallRuleDirection,
+ FirewallRuleAction
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs b/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
index 35c35d1075..533505c677 100644
--- a/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs
@@ -734,5 +734,23 @@ public static string XML {
return ResourceManager.GetString("XML", resourceCulture);
}
}
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die MyApp - HTTP ähnelt.
+ ///
+ public static string ExampleFirewallRuleName {
+ get {
+ return ResourceManager.GetString("ExampleFirewallRuleName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die 10.0.0.0/8; LocalSubnet ähnelt.
+ ///
+ public static string ExampleFirewallAddresses {
+ get {
+ return ResourceManager.GetString("ExampleFirewallAddresses", resourceCulture);
+ }
+ }
}
}
diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.resx b/Source/NETworkManager.Localization/Resources/StaticStrings.resx
index 957880db82..006de73953 100644
--- a/Source/NETworkManager.Localization/Resources/StaticStrings.resx
+++ b/Source/NETworkManager.Localization/Resources/StaticStrings.resx
@@ -342,4 +342,10 @@
borntoberoot.net or 1.1.1.1
+
+ MyApp - HTTP
+
+
+ 10.0.0.0/8; LocalSubnet
+
\ 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..8a56008b17 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -4128,7 +4128,16 @@ public static string Firewall {
return ResourceManager.GetString("Firewall", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to Read-only mode. Modifying firewall rules requires elevated rights!
+ ///
+ public static string FirewallAdminMessage {
+ get {
+ return ResourceManager.GetString("FirewallAdminMessage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Firewall rules.
///
@@ -4813,6 +4822,15 @@ public static string HostsFileEditorAdminMessage {
return ResourceManager.GetString("HostsFileEditorAdminMessage", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Open hosts file.
+ ///
+ public static string OpenHostsFile {
+ get {
+ return ResourceManager.GetString("OpenHostsFile", resourceCulture);
+ }
+ }
///
/// Looks up a localized string similar to The entry was not found in the "hosts" file! Maybe the file has been modified..
@@ -7343,7 +7361,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 +11589,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.
///
@@ -11579,7 +11624,16 @@ public static string FailedToLoadFirewallRulesMessage {
return ResourceManager.GetString("FailedToLoadFirewallRulesMessage", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to The selected firewall rule is permanently deleted: {0}.
+ ///
+ public static string DeleteFirewallRuleMessage {
+ get {
+ return ResourceManager.GetString("DeleteFirewallRuleMessage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to WPS.
///
@@ -11714,5 +11768,50 @@ public static string ZipCode {
return ResourceManager.GetString("ZipCode", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Local ports.
+ ///
+ public static string LocalPorts {
+ get {
+ return ResourceManager.GetString("LocalPorts", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remote ports.
+ ///
+ public static string RemotePorts {
+ get {
+ return ResourceManager.GetString("RemotePorts", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Local addresses.
+ ///
+ public static string LocalAddresses {
+ get {
+ return ResourceManager.GetString("LocalAddresses", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remote addresses.
+ ///
+ public static string RemoteAddresses {
+ get {
+ return ResourceManager.GetString("RemoteAddresses", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enter a valid IP address, subnet (e.g. 10.0.0.0/8) or keyword (e.g. LocalSubnet, Internet).
+ ///
+ public static string EnterValidFirewallAddress {
+ get {
+ return ResourceManager.GetString("EnterValidFirewallAddress", resourceCulture);
+ }
+ }
}
}
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 789507cf64..be263b510f 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.
@@ -3796,6 +3802,9 @@ Right-click for more options.
Read-only mode. Modifying the hosts file requires elevated rights!
+
+ Open hosts file
+
Comment
@@ -4037,6 +4046,9 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Block
+
+ Network profile
+
Network profiles
@@ -4046,4 +4058,96 @@ You can copy your profile files from “{0}” to “{1}” to migrate your exis
Failed to load firewall rules. {0}
+
+ The selected firewall rule is permanently deleted:
+
+{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
+
+
+ Read-only mode. Modifying firewall rules requires elevated rights!
+
+
+ Inbound
+
+
+ Outbound
+
+
+ Block
+
+
+ Allow
+
+
+ Local ports
+
+
+ Remote ports
+
+
+ Local addresses
+
+
+ Remote addresses
+
+
+ Enter a valid IP address, subnet (e.g. 10.0.0.0/8) or keyword (e.g. LocalSubnet, Internet)
+
\ 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..4151d08be4
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/Firewall.cs
@@ -0,0 +1,533 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation.Runspaces;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using SMA = System.Management.Automation;
+using log4net;
+
+namespace NETworkManager.Models.Firewall;
+
+///
+/// Provides static methods to read and modify Windows Firewall rules via PowerShell.
+/// All operations share a single that is initialized once with
+/// the required execution policy and the NetSecurity module, reducing per-call overhead.
+/// A serializes access so the runspace is never used concurrently.
+///
+public class Firewall
+{
+ #region Variables
+
+ ///
+ /// The logger for this class.
+ ///
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Firewall));
+
+ ///
+ /// Prefix applied to the DisplayName of every rule managed by NETworkManager.
+ /// Used to scope Get-NetFirewallRule queries to only our own rules.
+ ///
+ private const string RuleIdentifier = "NETworkManager_";
+
+ ///
+ /// Ensures that only one PowerShell pipeline runs on at a time.
+ ///
+ private static readonly SemaphoreSlim Lock = new(1, 1);
+
+ ///
+ /// Shared PowerShell runspace, initialized once in the static constructor with
+ /// Set-ExecutionPolicy Bypass and Import-Module NetSecurity.
+ ///
+ private static readonly Runspace SharedRunspace;
+
+ ///
+ /// Opens and runs the one-time initialization script
+ /// so that subsequent operations do not need to repeat the module import.
+ ///
+ static Firewall()
+ {
+ SharedRunspace = RunspaceFactory.CreateRunspace();
+ SharedRunspace.Open();
+
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+ ps.AddScript(@"
+Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
+Import-Module NetSecurity -ErrorAction Stop").Invoke();
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Retrieves all Windows Firewall rules whose display name starts with
+ /// and maps each one to a object.
+ /// PowerShell errors during the query are logged as warnings; errors for individual
+ /// rules are caught so a single malformed rule does not abort the entire load.
+ ///
+ ///
+ /// A list of objects representing the matching rules.
+ ///
+ public static async Task> GetRulesAsync()
+ {
+ await Lock.WaitAsync();
+ try
+ {
+ return await Task.Run(() =>
+ {
+ var rules = new List();
+
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+
+ ps.AddScript($@"
+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;
+ });
+ }
+ finally
+ {
+ Lock.Release();
+ }
+ }
+
+ ///
+ /// Enables or disables the given by running
+ /// Enable-NetFirewallRule or Disable-NetFirewallRule against
+ /// the rule's internal .
+ ///
+ ///
+ /// The firewall rule to modify.
+ ///
+ ///
+ /// to enable the rule; to disable it.
+ ///
+ ///
+ /// Thrown when the PowerShell pipeline reports one or more errors.
+ ///
+ public static async Task SetRuleEnabledAsync(FirewallRule rule, bool enabled)
+ {
+ await Lock.WaitAsync();
+ try
+ {
+ await Task.Run(() =>
+ {
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+
+ ps.AddScript($@"{(enabled ? "Enable" : "Disable")}-NetFirewallRule -Name '{rule.Id}'");
+ ps.Invoke();
+
+ if (ps.Streams.Error.Count > 0)
+ throw new Exception(string.Join("; ", ps.Streams.Error));
+ });
+ }
+ finally
+ {
+ Lock.Release();
+ }
+ }
+
+ ///
+ /// Permanently removes the given by running
+ /// Remove-NetFirewallRule against the rule's internal .
+ ///
+ ///
+ /// The firewall rule to delete.
+ ///
+ ///
+ /// Thrown when the PowerShell pipeline reports one or more errors.
+ ///
+ public static async Task DeleteRuleAsync(FirewallRule rule)
+ {
+ await Lock.WaitAsync();
+ try
+ {
+ await Task.Run(() =>
+ {
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+
+ ps.AddScript($@"Remove-NetFirewallRule -Name '{rule.Id}'");
+ ps.Invoke();
+
+ if (ps.Streams.Error.Count > 0)
+ throw new Exception(string.Join("; ", ps.Streams.Error));
+ });
+ }
+ finally
+ {
+ Lock.Release();
+ }
+ }
+
+ ///
+ /// Creates a new Windows Firewall rule with the properties specified in .
+ /// The rule's is prefixed with so
+ /// it is picked up by on the next refresh.
+ ///
+ ///
+ /// The firewall rule to create.
+ ///
+ ///
+ /// Thrown when the PowerShell pipeline reports one or more errors.
+ ///
+ public static async Task AddRuleAsync(FirewallRule rule)
+ {
+ await Lock.WaitAsync();
+ try
+ {
+ await Task.Run(() =>
+ {
+ using var ps = SMA.PowerShell.Create();
+ ps.Runspace = SharedRunspace;
+
+ ps.AddScript(BuildAddScript(rule));
+ ps.Invoke();
+
+ if (ps.Streams.Error.Count > 0)
+ throw new Exception(string.Join("; ", ps.Streams.Error));
+ });
+ }
+ finally
+ {
+ Lock.Release();
+ }
+ }
+
+ ///
+ /// Builds the PowerShell script that calls New-NetFirewallRule with all
+ /// properties from .
+ ///
+ ///
+ /// The firewall rule whose properties are used to build the script.
+ ///
+ private static string BuildAddScript(FirewallRule rule)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("$params = @{");
+ sb.AppendLine($" DisplayName = '{RuleIdentifier}{EscapePs(rule.Name)}'");
+ sb.AppendLine($" Enabled = '{(rule.IsEnabled ? "True" : "False")}'");
+ sb.AppendLine($" Direction = '{rule.Direction}'");
+ sb.AppendLine($" Action = '{rule.Action}'");
+ sb.AppendLine($" Protocol = '{GetProtocolString(rule.Protocol)}'");
+ sb.AppendLine($" InterfaceType = '{GetInterfaceTypeString(rule.InterfaceType)}'");
+ sb.AppendLine($" Profile = '{GetProfileString(rule.NetworkProfiles)}'");
+ sb.AppendLine("}");
+
+ if (!string.IsNullOrWhiteSpace(rule.Description))
+ sb.AppendLine($"$params['Description'] = '{EscapePs(rule.Description)}'");
+
+ if (rule.Protocol is FirewallProtocol.TCP or FirewallProtocol.UDP)
+ {
+ if (rule.LocalPorts.Count > 0)
+ sb.AppendLine($"$params['LocalPort'] = '{FirewallRule.PortsToString(rule.LocalPorts, ',', false)}'");
+
+ if (rule.RemotePorts.Count > 0)
+ sb.AppendLine($"$params['RemotePort'] = '{FirewallRule.PortsToString(rule.RemotePorts, ',', false)}'");
+ }
+
+ if (rule.LocalAddresses.Count > 0)
+ sb.AppendLine($"$params['LocalAddress'] = '{string.Join(',', rule.LocalAddresses.Select(EscapePs))}'");
+
+ if (rule.RemoteAddresses.Count > 0)
+ sb.AppendLine($"$params['RemoteAddress'] = '{string.Join(',', rule.RemoteAddresses.Select(EscapePs))}'");
+
+ if (rule.Program != null && !string.IsNullOrWhiteSpace(rule.Program.Name))
+ sb.AppendLine($"$params['Program'] = '{EscapePs(rule.Program.Name)}'");
+
+ sb.AppendLine("New-NetFirewallRule @params");
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Escapes a string for embedding inside a PowerShell single-quoted string by
+ /// doubling any single-quote characters.
+ ///
+ /// The raw string value to escape.
+ private static string EscapePs(string value) => value.Replace("'", "''");
+
+ ///
+ /// Maps a value to the string accepted by
+ /// New-NetFirewallRule -Protocol.
+ ///
+ /// The protocol to convert.
+ private static string GetProtocolString(FirewallProtocol protocol) => protocol switch
+ {
+ FirewallProtocol.Any => "Any",
+ FirewallProtocol.TCP => "TCP",
+ FirewallProtocol.UDP => "UDP",
+ FirewallProtocol.ICMPv4 => "ICMPv4",
+ FirewallProtocol.ICMPv6 => "ICMPv6",
+ FirewallProtocol.GRE => "GRE",
+ FirewallProtocol.L2TP => "L2TP",
+ _ => ((int)protocol).ToString()
+ };
+
+ ///
+ /// Maps a value to the string accepted by
+ /// New-NetFirewallRule -InterfaceType.
+ ///
+ /// The interface type to convert.
+ private static string GetInterfaceTypeString(FirewallInterfaceType interfaceType) => interfaceType switch
+ {
+ FirewallInterfaceType.Wired => "Wired",
+ FirewallInterfaceType.Wireless => "Wireless",
+ FirewallInterfaceType.RemoteAccess => "RemoteAccess",
+ _ => "Any"
+ };
+
+ ///
+ /// Converts the three-element network-profile boolean array (Domain, Private, Public)
+ /// to the comma-separated profile string accepted by New-NetFirewallRule -Profile.
+ /// All-false or all-true both map to "Any".
+ ///
+ /// Three-element boolean array (Domain=0, Private=1, Public=2).
+ private static string GetProfileString(bool[] profiles)
+ {
+ if (profiles == null || profiles.Length < 3 || profiles.All(p => p) || profiles.All(p => !p))
+ return "Any";
+
+ var parts = new List(3);
+ if (profiles[0]) parts.Add("Domain");
+ if (profiles[1]) parts.Add("Private");
+ if (profiles[2]) parts.Add("Public");
+
+ return parts.Count == 0 ? "Any" : string.Join(",", parts);
+ }
+
+ ///
+ /// Parses a PowerShell direction string (e.g. "Outbound") to a
+ /// value. Defaults to .
+ ///
+ ///
+ /// The raw string value returned by PowerShell.
+ ///
+ private static FirewallRuleDirection ParseDirection(string value)
+ {
+ return value switch
+ {
+ "Outbound" => FirewallRuleDirection.Outbound,
+ _ => FirewallRuleDirection.Inbound,
+ };
+ }
+
+ ///
+ /// Parses a PowerShell action string (e.g. "Allow") to a
+ /// value. Defaults to .
+ ///
+ ///
+ /// The raw string value returned by PowerShell.
+ ///
+ private static FirewallRuleAction ParseAction(string value)
+ {
+ return value switch
+ {
+ "Allow" => FirewallRuleAction.Allow,
+ _ => FirewallRuleAction.Block,
+ };
+ }
+
+ ///
+ /// Parses a PowerShell protocol string (e.g. "TCP", "Any") to a
+ /// value. Numeric protocol numbers are also accepted.
+ /// Defaults to for unrecognized values.
+ ///
+ ///
+ /// The raw string value returned by PowerShell.
+ ///
+ 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 when the value is blank or "Any".
+ ///
+ ///
+ /// The raw comma-separated port string returned by PowerShell.
+ ///
+ 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.
+ /// "Any" and "All" set all three entries to .
+ ///
+ ///
+ /// The raw profile string returned by PowerShell.
+ ///
+ 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 when the value is blank or "Any".
+ ///
+ ///
+ /// The raw comma-separated address string returned by PowerShell.
+ ///
+ 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 (e.g. "Wired") to a
+ /// value. Defaults to .
+ ///
+ ///
+ /// The raw string value returned by PowerShell.
+ ///
+ private static FirewallInterfaceType ParseInterfaceType(string value)
+ {
+ return value switch
+ {
+ "Wired" => FirewallInterfaceType.Wired,
+ "Wireless" => FirewallInterfaceType.Wireless,
+ "RemoteAccess" => FirewallInterfaceType.RemoteAccess,
+ _ => FirewallInterfaceType.Any,
+ };
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs b/Source/NETworkManager.Models/Firewall/FirewallInterfaceType.cs
new file mode 100644
index 0000000000..c607dc3524
--- /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..874cabec2e
--- /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..f15d818c57
--- /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..bb3ea76d36
--- /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);
+
+ /// 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 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);
+
+ /// 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 ? "Any" : 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..cbb9d91887
--- /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..46cb44d6c3
--- /dev/null
+++ b/Source/NETworkManager.Models/Firewall/FirewallRuleProgram.cs
@@ -0,0 +1,101 @@
+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/HostsFileEditor/HostsFileEditor.cs b/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs
index 82e1817247..2a211c638c 100644
--- a/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs
+++ b/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs
@@ -37,7 +37,7 @@ private static void OnHostsFileChanged()
///
/// Path to the hosts file.
///
- private static string HostsFilePath => Path.Combine(HostsFolderPath, "hosts");
+ public static string HostsFilePath => Path.Combine(HostsFolderPath, "hosts");
///
/// Identifier for the hosts file backup.
diff --git a/Source/NETworkManager.Models/NETworkManager.Models.csproj b/Source/NETworkManager.Models/NETworkManager.Models.csproj
index 6ba6c74b39..ae85cf3cb6 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/DiscoveryProtocol.cs b/Source/NETworkManager.Models/Network/DiscoveryProtocol.cs
index 3ff78c5fb8..81badd3f5c 100644
--- a/Source/NETworkManager.Models/Network/DiscoveryProtocol.cs
+++ b/Source/NETworkManager.Models/Network/DiscoveryProtocol.cs
@@ -100,7 +100,7 @@ public void CaptureAsync(int duration, DiscoveryProtocol protocol)
Task.Run(() =>
{
using var ps = System.Management.Automation.PowerShell.Create();
-
+
var typeParam = protocol != DiscoveryProtocol.LldpCdp ? $" -Type {protocol.ToString().ToUpper()}" : "";
ps.AddScript($@"
diff --git a/Source/NETworkManager.Models/Network/NetworkInterface.cs b/Source/NETworkManager.Models/Network/NetworkInterface.cs
index 6fbc000e2b..bca63e1205 100644
--- a/Source/NETworkManager.Models/Network/NetworkInterface.cs
+++ b/Source/NETworkManager.Models/Network/NetworkInterface.cs
@@ -6,8 +6,10 @@
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
+using log4net;
using Microsoft.Win32;
using NETworkManager.Utilities;
+using SMA = System.Management.Automation;
namespace NETworkManager.Models.Network;
@@ -31,6 +33,8 @@ public sealed class NetworkInterface
/// drivers, filters, or extensions attached to real network interfaces.
/// See: https://github.com/dotnet/runtime/issues/122751
///
+ private static readonly ILog Log = LogManager.GetLogger(typeof(NetworkInterface));
+
private static readonly List NetworkInterfaceFilteredPatterns =
[
"Hyper-V Virtual Switch Extension Filter",
@@ -72,6 +76,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 (Exception ex)
+ {
+ Log.Warn("Failed to query network connection profiles via Get-NetConnectionProfile.", ex);
+ }
+
foreach (var networkInterface in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces())
{
// NetworkInterfaceType 53 is proprietary virtual/internal interface
@@ -194,7 +229,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.Profiles/ProfileInfo.cs b/Source/NETworkManager.Profiles/ProfileInfo.cs
index 26ed49397e..d2c2edf234 100644
--- a/Source/NETworkManager.Profiles/ProfileInfo.cs
+++ b/Source/NETworkManager.Profiles/ProfileInfo.cs
@@ -221,7 +221,7 @@ public ProfileInfo(ProfileInfo profile)
// Firewall
Firewall_Enabled = profile.Firewall_Enabled;
//Firewall_Rules = profile.Firewall_Rules;
-
+
// Wake on LAN
WakeOnLAN_Enabled = profile.WakeOnLAN_Enabled;
WakeOnLAN_MACAddress = profile.WakeOnLAN_MACAddress;
@@ -470,7 +470,7 @@ public ProfileInfo(ProfileInfo profile)
public bool Firewall_Enabled { get; set; }
//public List Firewall_Rules { get; set; }
-
+
public bool WakeOnLAN_Enabled { get; set; }
public string WakeOnLAN_MACAddress { get; set; }
public string WakeOnLAN_Broadcast { get; set; }
diff --git a/Source/NETworkManager.Profiles/ProfileName.cs b/Source/NETworkManager.Profiles/ProfileName.cs
index 0ded53ef0b..7db47b880f 100644
--- a/Source/NETworkManager.Profiles/ProfileName.cs
+++ b/Source/NETworkManager.Profiles/ProfileName.cs
@@ -18,5 +18,5 @@ public enum ProfileName
Firewall,
WakeOnLAN,
Whois,
- IPGeolocation,
+ IPGeolocation,
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Profiles/ProfileViewManager.cs b/Source/NETworkManager.Profiles/ProfileViewManager.cs
index 0435fec045..606041e5b2 100644
--- a/Source/NETworkManager.Profiles/ProfileViewManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileViewManager.cs
@@ -22,41 +22,43 @@ public static class ProfileViewManager
public static List List =>
[
// General
- new ProfileViewInfo(ProfileName.General, new PackIconModern { Kind = PackIconModernKind.Box },
+ new(ProfileName.General, new PackIconModern { Kind = PackIconModernKind.Box },
ProfileGroup.General),
// Applications
- new ProfileViewInfo(ProfileName.NetworkInterface, ApplicationManager.GetIcon(ApplicationName.NetworkInterface),
+ new(ProfileName.NetworkInterface, ApplicationManager.GetIcon(ApplicationName.NetworkInterface),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.IPScanner, ApplicationManager.GetIcon(ApplicationName.IPScanner),
+ new(ProfileName.IPScanner, ApplicationManager.GetIcon(ApplicationName.IPScanner),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.PortScanner, ApplicationManager.GetIcon(ApplicationName.PortScanner),
+ new(ProfileName.PortScanner, ApplicationManager.GetIcon(ApplicationName.PortScanner),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.PingMonitor, ApplicationManager.GetIcon(ApplicationName.PingMonitor),
+ new(ProfileName.PingMonitor, ApplicationManager.GetIcon(ApplicationName.PingMonitor),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.Traceroute, ApplicationManager.GetIcon(ApplicationName.Traceroute),
+ new(ProfileName.Traceroute, ApplicationManager.GetIcon(ApplicationName.Traceroute),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.DNSLookup, ApplicationManager.GetIcon(ApplicationName.DNSLookup),
+ new(ProfileName.DNSLookup, ApplicationManager.GetIcon(ApplicationName.DNSLookup),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.RemoteDesktop, ApplicationManager.GetIcon(ApplicationName.RemoteDesktop),
+ new(ProfileName.RemoteDesktop, ApplicationManager.GetIcon(ApplicationName.RemoteDesktop),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.PowerShell, ApplicationManager.GetIcon(ApplicationName.PowerShell),
+ new(ProfileName.PowerShell, ApplicationManager.GetIcon(ApplicationName.PowerShell),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.PuTTY, ApplicationManager.GetIcon(ApplicationName.PuTTY),
+ new(ProfileName.PuTTY, ApplicationManager.GetIcon(ApplicationName.PuTTY),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.TigerVNC, ApplicationManager.GetIcon(ApplicationName.TigerVNC),
+ new(ProfileName.TigerVNC, ApplicationManager.GetIcon(ApplicationName.TigerVNC),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.WebConsole, ApplicationManager.GetIcon(ApplicationName.WebConsole),
+ new(ProfileName.WebConsole, ApplicationManager.GetIcon(ApplicationName.WebConsole),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.SNMP, ApplicationManager.GetIcon(ApplicationName.SNMP),
+ new(ProfileName.SNMP, ApplicationManager.GetIcon(ApplicationName.SNMP),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.Firewall, ApplicationManager.GetIcon(ApplicationName.Firewall),
+ /*
+ new(ProfileName.Firewall, ApplicationManager.GetIcon(ApplicationName.Firewall),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.WakeOnLAN, ApplicationManager.GetIcon(ApplicationName.WakeOnLAN),
+ */
+ new(ProfileName.WakeOnLAN, ApplicationManager.GetIcon(ApplicationName.WakeOnLAN),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.Whois, ApplicationManager.GetIcon(ApplicationName.Whois),
+ new(ProfileName.Whois, ApplicationManager.GetIcon(ApplicationName.Whois),
+ ProfileGroup.Application),
+ new(ProfileName.IPGeolocation, ApplicationManager.GetIcon(ApplicationName.IPGeolocation),
ProfileGroup.Application),
- new ProfileViewInfo(ProfileName.IPGeolocation, ApplicationManager.GetIcon(ApplicationName.IPGeolocation),
- ProfileGroup.Application),
];
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
index 61260da4e5..51623e151f 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..7d0caae60e 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -3255,8 +3255,34 @@ 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
public DiscoveryProtocol DiscoveryProtocol_Protocol
diff --git a/Source/NETworkManager.Settings/SettingsName.cs b/Source/NETworkManager.Settings/SettingsName.cs
index 415c308718..949de02c75 100644
--- a/Source/NETworkManager.Settings/SettingsName.cs
+++ b/Source/NETworkManager.Settings/SettingsName.cs
@@ -28,5 +28,5 @@ public enum SettingsName
SNTPLookup,
Firewall,
WakeOnLAN,
- BitCalculator,
+ BitCalculator,
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Settings/SettingsViewManager.cs b/Source/NETworkManager.Settings/SettingsViewManager.cs
index 0e050bf4f4..08950281ac 100644
--- a/Source/NETworkManager.Settings/SettingsViewManager.cs
+++ b/Source/NETworkManager.Settings/SettingsViewManager.cs
@@ -21,62 +21,60 @@ public static class SettingsViewManager
public static List List =>
[
// General
- new SettingsViewInfo(SettingsName.General, new PackIconModern { Kind = PackIconModernKind.Layer },
+ new(SettingsName.General, new PackIconModern { Kind = PackIconModernKind.Layer },
SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Window, new PackIconOcticons { Kind = PackIconOcticonsKind.Browser },
+ new(SettingsName.Window, new PackIconOcticons { Kind = PackIconOcticonsKind.Browser },
SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Appearance, new PackIconMaterial { Kind = PackIconMaterialKind.Palette },
+ new(SettingsName.Appearance, new PackIconMaterial { Kind = PackIconMaterialKind.Palette },
SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Language, new PackIconMaterial { Kind = PackIconMaterialKind.Translate },
+ new(SettingsName.Language, new PackIconMaterial { Kind = PackIconMaterialKind.Translate },
SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Network, new PackIconModern { Kind = PackIconModernKind.Network },
+ new(SettingsName.Network, new PackIconModern { Kind = PackIconModernKind.Network },
SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Status, new PackIconMaterial { Kind = PackIconMaterialKind.Pulse },
+ new(SettingsName.Status, new PackIconMaterial { Kind = PackIconMaterialKind.Pulse },
SettingsGroup.General),
- new SettingsViewInfo(SettingsName.HotKeys,
+ new(SettingsName.HotKeys,
new PackIconFontAwesome { Kind = PackIconFontAwesomeKind.KeyboardRegular },
SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Autostart, new PackIconMaterial { Kind = PackIconMaterialKind.Power },
+ new(SettingsName.Autostart, new PackIconMaterial { Kind = PackIconMaterialKind.Power },
SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Update,
+ new(SettingsName.Update,
new PackIconMaterial { Kind = PackIconMaterialKind.RocketLaunchOutline }, SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Profiles,
+ new(SettingsName.Profiles,
new PackIconFontAwesome { Kind = PackIconFontAwesomeKind.ServerSolid }, SettingsGroup.General),
- new SettingsViewInfo(SettingsName.Settings, new PackIconMaterialLight { Kind = PackIconMaterialLightKind.Cog },
+ new(SettingsName.Settings, new PackIconMaterialLight { Kind = PackIconMaterialLightKind.Cog },
SettingsGroup.General),
// Applications
- new SettingsViewInfo(SettingsName.Dashboard, ApplicationManager.GetIcon(ApplicationName.Dashboard),
+ new(SettingsName.Dashboard, ApplicationManager.GetIcon(ApplicationName.Dashboard),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.IPScanner, ApplicationManager.GetIcon(ApplicationName.IPScanner),
+ new(SettingsName.IPScanner, ApplicationManager.GetIcon(ApplicationName.IPScanner),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.PortScanner, ApplicationManager.GetIcon(ApplicationName.PortScanner),
+ new(SettingsName.PortScanner, ApplicationManager.GetIcon(ApplicationName.PortScanner),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.PingMonitor, ApplicationManager.GetIcon(ApplicationName.PingMonitor),
+ new(SettingsName.PingMonitor, ApplicationManager.GetIcon(ApplicationName.PingMonitor),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.Traceroute, ApplicationManager.GetIcon(ApplicationName.Traceroute),
+ new(SettingsName.Traceroute, ApplicationManager.GetIcon(ApplicationName.Traceroute),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.DNSLookup, ApplicationManager.GetIcon(ApplicationName.DNSLookup),
+ new(SettingsName.DNSLookup, ApplicationManager.GetIcon(ApplicationName.DNSLookup),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.RemoteDesktop, ApplicationManager.GetIcon(ApplicationName.RemoteDesktop),
+ new(SettingsName.RemoteDesktop, ApplicationManager.GetIcon(ApplicationName.RemoteDesktop),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.PowerShell, ApplicationManager.GetIcon(ApplicationName.PowerShell),
+ new(SettingsName.PowerShell, ApplicationManager.GetIcon(ApplicationName.PowerShell),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.PuTTY, ApplicationManager.GetIcon(ApplicationName.PuTTY),
+ new(SettingsName.PuTTY, ApplicationManager.GetIcon(ApplicationName.PuTTY),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.TigerVNC, ApplicationManager.GetIcon(ApplicationName.TigerVNC),
+ new(SettingsName.TigerVNC, ApplicationManager.GetIcon(ApplicationName.TigerVNC),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.WebConsole, ApplicationManager.GetIcon(ApplicationName.WebConsole),
+ new(SettingsName.WebConsole, ApplicationManager.GetIcon(ApplicationName.WebConsole),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.SNMP, ApplicationManager.GetIcon(ApplicationName.SNMP),
+ new(SettingsName.SNMP, ApplicationManager.GetIcon(ApplicationName.SNMP),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.SNTPLookup, ApplicationManager.GetIcon(ApplicationName.SNTPLookup),
+ new(SettingsName.SNTPLookup, ApplicationManager.GetIcon(ApplicationName.SNTPLookup),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.Firewall, ApplicationManager.GetIcon(ApplicationName.Firewall),
+ new(SettingsName.WakeOnLAN, ApplicationManager.GetIcon(ApplicationName.WakeOnLAN),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.WakeOnLAN, ApplicationManager.GetIcon(ApplicationName.WakeOnLAN),
+ new(SettingsName.BitCalculator, ApplicationManager.GetIcon(ApplicationName.BitCalculator),
SettingsGroup.Application),
- new SettingsViewInfo(SettingsName.BitCalculator, ApplicationManager.GetIcon(ApplicationName.BitCalculator),
- SettingsGroup.Application),
];
}
diff --git a/Source/NETworkManager.Utilities.WPF/ReloadAnimationHelper.cs b/Source/NETworkManager.Utilities.WPF/ReloadAnimationHelper.cs
index bc77172974..28e6a31f31 100644
--- a/Source/NETworkManager.Utilities.WPF/ReloadAnimationHelper.cs
+++ b/Source/NETworkManager.Utilities.WPF/ReloadAnimationHelper.cs
@@ -23,21 +23,21 @@ public static bool GetIsReloading(UIElement element) =>
private static void OnIsReloadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- if (d is not Rectangle rect || e.NewValue is not bool isReloading)
+ if (d is not Rectangle rect || e.NewValue is not bool isReloading)
return;
-
+
if (isReloading)
{
var rotate = new RotateTransform
{
- CenterX = rect.Width / 2,
+ CenterX = rect.Width / 2,
CenterY = rect.Height / 2
};
-
+
rect.RenderTransform = rotate;
var animation = new DoubleAnimation(0, 720, new Duration(TimeSpan.FromSeconds(2)));
-
+
rotate.BeginAnimation(RotateTransform.AngleProperty, animation);
}
else
diff --git a/Source/NETworkManager.Utilities/PowerShellHelper.cs b/Source/NETworkManager.Utilities/PowerShellHelper.cs
index 2303719bd0..a888690556 100644
--- a/Source/NETworkManager.Utilities/PowerShellHelper.cs
+++ b/Source/NETworkManager.Utilities/PowerShellHelper.cs
@@ -38,9 +38,9 @@ public static void ExecuteCommand(string command, bool asAdmin = false, ProcessW
if (Powershell.Length + BaseOpts.Length + commandOpts.Length > 32767)
{
scriptPath = Path.Combine(Path.GetTempPath(), $"NETworkManager_{Guid.NewGuid()}.ps1");
-
+
File.WriteAllText(scriptPath, command);
-
+
commandOpts = $" -ExecutionPolicy Bypass -File \"{scriptPath}\"";
}
@@ -66,7 +66,7 @@ public static void ExecuteCommand(string command, bool asAdmin = false, ProcessW
{
if (e.NativeErrorCode != 1223)
throw;
-
+
// Nothing to handle on UAC cancellation
}
finally
diff --git a/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs b/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs
new file mode 100644
index 0000000000..c13ed9eaf1
--- /dev/null
+++ b/Source/NETworkManager.Validators/EmptyOrFirewallAddressValidator.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Windows.Controls;
+using NETworkManager.Localization.Resources;
+
+namespace NETworkManager.Validators;
+
+///
+/// Validates that the input is empty (meaning "Any") or contains semicolon-separated
+/// valid IPv4/IPv6 addresses, CIDR subnets, or recognized Windows Firewall keywords
+/// (e.g. LocalSubnet, Internet, Intranet, DNS, DHCP, WINS, DefaultGateway).
+///
+public class EmptyOrFirewallAddressValidator : ValidationRule
+{
+ private static readonly string[] Keywords =
+ [
+ "Any", "LocalSubnet", "Internet", "Intranet", "DNS", "DHCP", "WINS", "DefaultGateway"
+ ];
+
+ ///
+ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
+ {
+ if (string.IsNullOrEmpty(value as string))
+ return ValidationResult.ValidResult;
+
+ foreach (var entry in ((string)value).Split(';'))
+ {
+ var token = entry.Trim();
+
+ if (string.IsNullOrEmpty(token))
+ continue;
+
+ if (Array.Exists(Keywords, k => k.Equals(token, StringComparison.OrdinalIgnoreCase)))
+ continue;
+
+ var slashIndex = token.IndexOf('/');
+ var addressPart = slashIndex > 0 ? token[..slashIndex] : token;
+
+ if (!IPAddress.TryParse(addressPart, out var ip))
+ return new ValidationResult(false, Strings.EnterValidFirewallAddress);
+
+ if (slashIndex > 0)
+ {
+ var prefixStr = token[(slashIndex + 1)..];
+ var maxPrefix = ip.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32;
+
+ if (!int.TryParse(prefixStr, out var prefix) || prefix < 0 || prefix > maxPrefix)
+ return new ValidationResult(false, Strings.EnterValidFirewallAddress);
+ }
+ }
+
+ return ValidationResult.ValidResult;
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Validators/EmptyOrPortRangeValidator.cs b/Source/NETworkManager.Validators/EmptyOrPortRangeValidator.cs
index df90512554..888a9e101b 100644
--- a/Source/NETworkManager.Validators/EmptyOrPortRangeValidator.cs
+++ b/Source/NETworkManager.Validators/EmptyOrPortRangeValidator.cs
@@ -13,9 +13,6 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
var isValid = true;
- if (value == null)
- return new ValidationResult(false, Strings.EnterValidPortOrPortRange);
-
foreach (var portOrRange in ((string)value).Replace(" ", "").Split(';'))
if (portOrRange.Contains('-'))
{
diff --git a/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs b/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
index 6b57463e7d..7b917a8e3d 100644
--- a/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
@@ -295,7 +295,9 @@ private async Task RestartAsAdminAction()
///
/// Gets the command to start the capture.
///
- public ICommand CaptureCommand => new RelayCommand(_ => CaptureAction().ConfigureAwait(false));
+ public ICommand CaptureCommand => new RelayCommand(_ => CaptureAction().ConfigureAwait(false), Capture_CanExecute);
+
+ private bool Capture_CanExecute(object _) => ConfigurationManager.Current.IsAdmin && !IsCapturing;
///
/// Action to start the capture.
diff --git a/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs b/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs
new file mode 100644
index 0000000000..32fe089a06
--- /dev/null
+++ b/Source/NETworkManager/ViewModels/FirewallRuleViewModel.cs
@@ -0,0 +1,373 @@
+using NETworkManager.Models.Firewall;
+using NETworkManager.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Windows.Input;
+
+namespace NETworkManager.ViewModels;
+
+///
+/// ViewModel for adding or editing a firewall rule in the FirewallRule dialog.
+///
+public class FirewallRuleViewModel : ViewModelBase
+{
+ ///
+ /// Creates a new instance of for adding or
+ /// editing a firewall rule.
+ ///
+ /// OK command to save the rule.
+ /// Cancel command to discard changes.
+ /// Existing rule to edit; to add a new rule.
+ public FirewallRuleViewModel(Action okCommand,
+ Action cancelHandler, FirewallRule entry = null)
+ {
+ OKCommand = new RelayCommand(_ => okCommand(this));
+ CancelCommand = new RelayCommand(_ => cancelHandler(this));
+
+ Entry = entry;
+
+ if (entry == null)
+ {
+ IsEnabled = true;
+ Direction = FirewallRuleDirection.Inbound;
+ Action = FirewallRuleAction.Allow;
+ Protocol = FirewallProtocol.Any;
+ InterfaceType = FirewallInterfaceType.Any;
+ NetworkProfileDomain = true;
+ NetworkProfilePrivate = true;
+ NetworkProfilePublic = true;
+ }
+ else
+ {
+ Name = entry.Name;
+ IsEnabled = entry.IsEnabled;
+ Description = entry.Description ?? string.Empty;
+ Direction = entry.Direction;
+ Action = entry.Action;
+ Protocol = entry.Protocol;
+ LocalPorts = FirewallRule.PortsToString(entry.LocalPorts);
+ RemotePorts = FirewallRule.PortsToString(entry.RemotePorts);
+ LocalAddresses = entry.LocalAddresses.Count > 0 ? string.Join("; ", entry.LocalAddresses) : string.Empty;
+ RemoteAddresses = entry.RemoteAddresses.Count > 0 ? string.Join("; ", entry.RemoteAddresses) : string.Empty;
+ Program = entry.Program?.Name ?? string.Empty;
+ InterfaceType = entry.InterfaceType;
+ NetworkProfileDomain = entry.NetworkProfiles.Length > 0 && entry.NetworkProfiles[0];
+ NetworkProfilePrivate = entry.NetworkProfiles.Length > 1 && entry.NetworkProfiles[1];
+ NetworkProfilePublic = entry.NetworkProfiles.Length > 2 && entry.NetworkProfiles[2];
+ }
+ }
+
+ ///
+ /// OK command to save the rule.
+ ///
+ public ICommand OKCommand { get; }
+
+ ///
+ /// Cancel command to discard changes.
+ ///
+ public ICommand CancelCommand { get; }
+
+ ///
+ /// The original firewall rule being edited, or when adding a new rule.
+ ///
+ public FirewallRule Entry { get; }
+
+ ///
+ /// Protocols available in the protocol drop-down.
+ ///
+ public IEnumerable Protocols { get; } =
+ [
+ FirewallProtocol.Any,
+ FirewallProtocol.TCP,
+ FirewallProtocol.UDP,
+ FirewallProtocol.ICMPv4,
+ FirewallProtocol.ICMPv6,
+ FirewallProtocol.GRE,
+ FirewallProtocol.L2TP
+ ];
+
+ ///
+ /// Directions available in the direction drop-down.
+ ///
+ public IEnumerable Directions { get; } = Enum.GetValues();
+
+ ///
+ /// Actions available in the action drop-down.
+ ///
+ public IEnumerable Actions { get; } = Enum.GetValues();
+
+ ///
+ /// Interface types available in the interface type drop-down.
+ ///
+ public IEnumerable InterfaceTypes { get; } = Enum.GetValues();
+
+ ///
+ /// Human-readable display name of the rule (without the NETworkManager_ prefix).
+ ///
+ public string Name
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Indicates whether the rule is enabled.
+ ///
+ public bool IsEnabled
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Optional description of the rule.
+ ///
+ public string Description
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Traffic direction (Inbound or Outbound).
+ ///
+ public FirewallRuleDirection Direction
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Rule action (Allow or Block).
+ ///
+ public FirewallRuleAction Action
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Network protocol. When changed away from TCP/UDP, local and remote port fields are cleared.
+ ///
+ public FirewallProtocol Protocol
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+
+ if (value is not (FirewallProtocol.TCP or FirewallProtocol.UDP))
+ {
+ LocalPorts = string.Empty;
+ RemotePorts = string.Empty;
+ }
+
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(PortsVisible));
+ }
+ }
+
+ ///
+ /// when the current protocol supports port filtering (TCP or UDP).
+ ///
+ public bool PortsVisible => Protocol is FirewallProtocol.TCP or FirewallProtocol.UDP;
+
+ ///
+ /// Semicolon-separated local port numbers or ranges (e.g. "80; 443; 8080-8090").
+ /// Only relevant when is TCP or UDP.
+ ///
+ public string LocalPorts
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Semicolon-separated remote port numbers or ranges.
+ /// Only relevant when is TCP or UDP.
+ ///
+ public string RemotePorts
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Semicolon-separated local addresses (IPs, CIDR subnets, or keywords such as LocalSubnet).
+ /// Empty means "Any".
+ ///
+ public string LocalAddresses
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Semicolon-separated remote addresses.
+ /// Empty means "Any".
+ ///
+ public string RemoteAddresses
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Full path to the executable this rule applies to. Empty means "Any program".
+ ///
+ public string Program
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ } = string.Empty;
+
+ ///
+ /// Network interface type filter.
+ ///
+ public FirewallInterfaceType InterfaceType
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Whether the rule applies to the Domain network profile.
+ ///
+ public bool NetworkProfileDomain
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(IsNetworkProfileDomainEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePrivateEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePublicEnabled));
+ }
+ }
+
+ ///
+ /// Whether the rule applies to the Private network profile.
+ ///
+ public bool NetworkProfilePrivate
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(IsNetworkProfileDomainEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePrivateEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePublicEnabled));
+ }
+ }
+
+ ///
+ /// Whether the rule applies to the Public network profile.
+ ///
+ public bool NetworkProfilePublic
+ {
+ get;
+ set
+ {
+ if (value == field)
+ return;
+
+ field = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(IsNetworkProfileDomainEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePrivateEnabled));
+ OnPropertyChanged(nameof(IsNetworkProfilePublicEnabled));
+ }
+ }
+
+ public bool IsNetworkProfileDomainEnabled => NetworkProfilePrivate || NetworkProfilePublic;
+ public bool IsNetworkProfilePrivateEnabled => NetworkProfileDomain || NetworkProfilePublic;
+ public bool IsNetworkProfilePublicEnabled => NetworkProfileDomain || NetworkProfilePrivate;
+}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/FirewallSettingsViewModel.cs b/Source/NETworkManager/ViewModels/FirewallSettingsViewModel.cs
deleted file mode 100644
index 37f018e832..0000000000
--- a/Source/NETworkManager/ViewModels/FirewallSettingsViewModel.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using NETworkManager.Settings;
-
-namespace NETworkManager.ViewModels;
-
-public class FirewallSettingsViewModel : ViewModelBase
-{
- #region Variables
- ///
- /// Indicates whether the view model is loading.
- ///
- private readonly bool _isLoading;
- #endregion
-
- #region Constructor, load settings
- ///
- /// Construct the view model and load settings.
- ///
- public FirewallSettingsViewModel()
- {
- _isLoading = true;
-
- LoadSettings();
-
- _isLoading = false;
- }
-
- ///
- /// Load the settings via .
- ///
- private void LoadSettings()
- {
-
- }
- #endregion
-}
diff --git a/Source/NETworkManager/ViewModels/FirewallViewModel.cs b/Source/NETworkManager/ViewModels/FirewallViewModel.cs
index a11d89993d..c0c8347d41 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
///
@@ -120,7 +240,7 @@ public bool ProfileFilterIsOpen
}
}
- ///
+ ///
/// Gets the collection view for profile filter tags.
///
public ICollectionView ProfileFilterTagsView { get; }
@@ -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();
@@ -258,10 +397,10 @@ public FirewallViewModel()
_searchDispatcherTimer.Tick += SearchDispatcherTimer_Tick;
LoadSettings();
-
+
_isLoading = false;
}
-
+
///
/// Loads the settings.
///
@@ -281,29 +420,273 @@ private void LoadSettings()
#region ICommand & Actions
///
- /// Gets the command to add a new firewall entry.
+ /// Gets the command to refresh the list of firewall rules from the system.
+ /// Disabled while a refresh is already in progress.
+ ///
+ public ICommand RefreshCommand => new RelayCommand(_ => RefreshAction().ConfigureAwait(false), Refresh_CanExecute);
+
+ ///
+ /// Returns when no refresh is currently running.
+ ///
+ private bool Refresh_CanExecute(object _) => !IsRefreshing;
+
+ ///
+ /// Delegates to to reload the firewall rules.
+ ///
+ private async Task RefreshAction() => await Refresh();
+
+ ///
+ /// Gets the command to open the dialog for adding a new firewall rule.
+ /// Only enabled when the application is running as administrator.
+ ///
+ public ICommand AddEntryCommand => new RelayCommand(_ => AddEntry().ConfigureAwait(false), _ => ModifyEntry_CanExecute());
+
+ ///
+ /// Opens the add-firewall-rule dialog. On confirmation, creates the rule via PowerShell
+ /// and refreshes the rule list.
+ ///
+ private async Task AddEntry()
+ {
+ var childWindow = new FirewallRuleChildWindow();
+
+ var childWindowViewModel = new FirewallRuleViewModel(async instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+
+ try
+ {
+ await Firewall.AddRuleAsync(BuildRule(instance));
+ await Refresh();
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while adding firewall rule", ex);
+
+ StatusMessage = ex.Message;
+ IsStatusMessageDisplayed = true;
+ }
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ });
+
+ childWindow.Title = Strings.AddEntry;
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
+
+ await Application.Current.MainWindow.ShowChildWindowAsync(childWindow);
+ }
+
+ ///
+ /// Gets the command to enable the selected firewall rule.
+ /// Only executable when the rule is currently disabled and modification is allowed.
+ ///
+ public ICommand EnableEntryCommand => new RelayCommand(_ => SetRuleEnabled(SelectedResult, true).ConfigureAwait(false), _ => ModifyEntry_CanExecute() && SelectedResult is { IsEnabled: false });
+
+ ///
+ /// Gets the command to disable the selected firewall rule.
+ /// Only executable when the rule is currently enabled and modification is allowed.
+ ///
+ public ICommand DisableEntryCommand => new RelayCommand(_ => SetRuleEnabled(SelectedResult, false).ConfigureAwait(false), _ => ModifyEntry_CanExecute() && SelectedResult is { IsEnabled: true });
+
+ ///
+ /// Enables or disables the given via PowerShell,
+ /// then reloads the rule list to reflect the updated state.
+ /// Any PowerShell error is written to the log and shown in the status bar.
+ ///
+ ///
+ /// The firewall rule to modify.
+ ///
+ ///
+ /// to enable the rule; to disable it.
+ ///
+ private async Task SetRuleEnabled(FirewallRule rule, bool enabled)
+ {
+ try
+ {
+ await Firewall.SetRuleEnabledAsync(rule, enabled);
+ await Refresh();
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"Error while {(enabled ? "enabling" : "disabling")} firewall rule", ex);
+
+ StatusMessage = ex.Message;
+ IsStatusMessageDisplayed = true;
+ }
+ }
+
+ ///
+ /// Gets the command to open the dialog for editing the selected firewall rule.
+ /// Only executable when a rule is selected and modification is allowed.
///
- public ICommand AddEntryCommand => new RelayCommand(_ => AddEntryAction());
+ public ICommand EditEntryCommand => new RelayCommand(_ => EditEntry().ConfigureAwait(false), _ => ModifyEntry_CanExecute() && SelectedResult != null);
///
- /// Action to add a new firewall entry.
+ /// Opens the edit-firewall-rule dialog pre-filled with the selected rule's properties.
+ /// On confirmation, deletes the old rule, creates the updated rule via PowerShell,
+ /// and refreshes the rule list.
///
- private void AddEntryAction()
+ private async Task EditEntry()
{
- MessageBox.Show("Not implemented");
+ var childWindow = new FirewallRuleChildWindow();
+
+ var childWindowViewModel = new FirewallRuleViewModel(async instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+
+ try
+ {
+ await Firewall.DeleteRuleAsync(instance.Entry);
+ await Firewall.AddRuleAsync(BuildRule(instance));
+ await Refresh();
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while editing firewall rule", ex);
+
+ StatusMessage = ex.Message;
+ IsStatusMessageDisplayed = true;
+ }
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ }, SelectedResult);
+
+ childWindow.Title = Strings.EditEntry;
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
+
+ await Application.Current.MainWindow.ShowChildWindowAsync(childWindow);
+ }
+
+ ///
+ /// Gets the command to permanently delete the selected firewall rule.
+ /// Only executable when a rule is selected and modification is allowed.
+ ///
+ public ICommand DeleteEntryCommand => new RelayCommand(_ => DeleteEntry().ConfigureAwait(false), _ => ModifyEntry_CanExecute() && SelectedResult != null);
+
+ ///
+ /// Shows a confirmation dialog and, if confirmed, deletes the selected firewall rule
+ /// via PowerShell and reloads the rule list.
+ /// Any PowerShell error is written to the log and shown in the status bar.
+ ///
+ private async Task DeleteEntry()
+ {
+ var result = await DialogHelper.ShowConfirmationMessageAsync(
+ Application.Current.MainWindow,
+ Strings.DeleteEntry,
+ string.Format(Strings.DeleteFirewallRuleMessage, SelectedResult.Name),
+ ChildWindowIcon.Info,
+ Strings.Delete);
+
+ if (!result)
+ return;
+
+ try
+ {
+ await Firewall.DeleteRuleAsync(SelectedResult);
+ await Refresh();
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while deleting firewall rule", ex);
+
+ StatusMessage = ex.Message;
+ IsStatusMessageDisplayed = true;
+ }
+ }
+
+ ///
+ /// Returns when the application is running as administrator,
+ /// no dialog is open, and no child window is open — i.e. it is safe to modify a rule.
+ ///
+ private static bool ModifyEntry_CanExecute()
+ {
+ return ConfigurationManager.Current.IsAdmin &&
+ Application.Current.MainWindow != null &&
+ !((MetroWindow)Application.Current.MainWindow).IsAnyDialogOpen &&
+ !ConfigurationManager.Current.IsChildWindowOpen;
+ }
+
+ ///
+ /// Gets the command to restart the application with administrator privileges.
+ ///
+ public ICommand RestartAsAdminCommand => new RelayCommand(_ => RestartAsAdminAction().ConfigureAwait(false));
+
+ ///
+ /// Restarts the application elevated. Shows an error dialog if the restart fails.
+ ///
+ private async Task RestartAsAdminAction()
+ {
+ try
+ {
+ (Application.Current.MainWindow as MainWindow)?.RestartApplication(true);
+ }
+ catch (Exception ex)
+ {
+ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Error, ex.Message,
+ ChildWindowIcon.Error);
+ }
}
///
- /// Gets the command to apply the selected profile configuration.
+ /// Gets the command to export the current firewall rule list to a file.
///
- public ICommand ApplyProfileCommand => new RelayCommand(_ => ApplyProfileAction());
+ public ICommand ExportCommand => new RelayCommand(_ => ExportAction().ConfigureAwait(false));
///
- /// Action to apply the selected profile configuration.
+ /// Opens the export child window and writes the selected or all firewall rules to the
+ /// chosen file format (CSV, XML, or JSON). Shows an error dialog if the export fails.
///
- private void ApplyProfileAction()
+ private Task ExportAction()
{
- MessageBox.Show("Not implemented");
+ 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);
}
///
@@ -371,7 +754,7 @@ private void DeleteProfileAction()
.ShowDeleteProfileDialog(Application.Current.MainWindow, this, new List { SelectedProfile })
.ConfigureAwait(false);
}
-
+
///
/// Gets the command to edit a profile group.
///
@@ -463,9 +846,9 @@ private void CollapseAllProfileGroupsAction()
{
SetIsExpandedForAllProfileGroups(false);
}
-
+
#region Additional commands
-
+
///
/// Gets the command to open the Windows Firewall management console (WF.msc).
///
@@ -489,10 +872,123 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
}
#endregion
-
+
#endregion
#region Methods
+
+ ///
+ /// Loads all NETworkManager firewall rules from the system via PowerShell and
+ /// replaces the contents of with the new list.
+ /// Updates throughout to reflect loading progress.
+ ///
+ ///
+ /// When the initial UI delay is skipped so the first load
+ /// on startup feels immediate.
+ ///
+ 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;
+ }
+
+ ///
+ /// Builds a from the values the user entered in the dialog.
+ ///
+ /// The dialog ViewModel containing the user's input.
+ private static FirewallRule BuildRule(FirewallRuleViewModel vm) => new()
+ {
+ Name = vm.Name,
+ IsEnabled = vm.IsEnabled,
+ Description = vm.Description ?? string.Empty,
+ Direction = vm.Direction,
+ Action = vm.Action,
+ Protocol = vm.Protocol,
+ LocalPorts = ParsePortsString(vm.LocalPorts),
+ RemotePorts = ParsePortsString(vm.RemotePorts),
+ LocalAddresses = ParseAddressesString(vm.LocalAddresses),
+ RemoteAddresses = ParseAddressesString(vm.RemoteAddresses),
+ Program = string.IsNullOrWhiteSpace(vm.Program) ? null : new FirewallRuleProgram(vm.Program),
+ InterfaceType = vm.InterfaceType,
+ NetworkProfiles = [vm.NetworkProfileDomain, vm.NetworkProfilePrivate, vm.NetworkProfilePublic]
+ };
+
+ ///
+ /// Parses a semicolon-separated port string (e.g. "80; 443; 8080-8090") into a
+ /// list of objects.
+ ///
+ /// The semicolon-separated port string from the dialog.
+ private static List ParsePortsString(string value)
+ {
+ var list = new List();
+
+ if (string.IsNullOrWhiteSpace(value))
+ return list;
+
+ foreach (var token in value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
+ {
+ var dash = token.IndexOf('-');
+
+ if (dash > 0 &&
+ int.TryParse(token[..dash], out var start) &&
+ int.TryParse(token[(dash + 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 semicolon-separated address string (e.g. "192.168.1.0/24; LocalSubnet")
+ /// into a list of address strings.
+ ///
+ /// The semicolon-separated address string from the dialog.
+ private static List ParseAddressesString(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return [];
+
+ return [.. value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
+ }
+
///
/// Sets the IsExpanded property for all profile groups.
///
@@ -502,7 +998,7 @@ private void SetIsExpandedForAllProfileGroups(bool isExpanded)
foreach (var group in Profiles.Groups.Cast())
GroupExpanderStateStore[group.Name.ToString()] = isExpanded;
}
-
+
///
/// Resizes the profile view.
///
@@ -535,7 +1031,7 @@ private void ResizeProfile(bool dueToChangedSize)
_canProfileWidthChange = true;
}
-
+
///
/// Called when the view becomes visible.
///
@@ -553,7 +1049,7 @@ public void OnViewHide()
{
_isViewActive = false;
}
-
+
///
/// Creates the profile filter tags.
///
@@ -633,9 +1129,9 @@ private void RefreshProfiles()
IsProfileFilterSet = !string.IsNullOrEmpty(filter.Search) || filter.Tags.Any();
}
#endregion
-
+
#region Events
-
+
///
/// Handles the OnProfilesUpdated event of the ProfileManager.
///
@@ -645,7 +1141,7 @@ private void ProfileManager_OnProfilesUpdated(object sender, EventArgs e)
RefreshProfiles();
}
-
+
///
/// Handles the Tick event of the search dispatcher timer.
///
@@ -657,6 +1153,6 @@ private void SearchDispatcherTimer_Tick(object sender, EventArgs e)
IsSearching = false;
}
-
+
#endregion
}
diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
index 2502a4da06..3abb776105 100644
--- a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
+++ b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
@@ -454,14 +454,13 @@ private async Task DeleteEntryAction()
{
IsModifying = true;
- var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
+ var result = await DialogHelper.ShowConfirmationMessageAsync(
+ Application.Current.MainWindow,
Strings.DeleteEntry,
string.Format(Strings.DeleteHostsFileEntryMessage, SelectedResult.IPAddress, SelectedResult.Hostname,
string.IsNullOrEmpty(SelectedResult.Comment) ? "" : $"# {SelectedResult.Comment}"),
ChildWindowIcon.Info,
- Strings.Delete
- );
-
+ Strings.Delete);
if (!result)
{
@@ -531,6 +530,28 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Erro
}
}
+ ///
+ /// Gets the command to open the hosts file with the system default editor.
+ ///
+ public ICommand OpenHostsFileCommand => new RelayCommand(_ => OpenHostsFileAction().ConfigureAwait(false));
+
+ ///
+ /// Opens the hosts file with the system default editor.
+ /// Shows an error dialog if the process cannot be started.
+ ///
+ private async Task OpenHostsFileAction()
+ {
+ try
+ {
+ ExternalProcessStarter.RunProcess(HostsFileEditor.HostsFilePath);
+ }
+ catch (Exception ex)
+ {
+ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, Strings.Error, ex.Message,
+ ChildWindowIcon.Error);
+ }
+ }
+
#endregion
#region Methods
diff --git a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
index 2aba68f586..cd16c741fd 100644
--- a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
+++ b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
@@ -900,7 +900,7 @@ private void ApplyConfigurationAction()
/// Gets the command to apply the profile configuration.
///
public ICommand ApplyProfileCommand => new RelayCommand(_ => ApplyProfileAction());
-
+
private void ApplyProfileAction()
{
ApplyConfigurationFromProfile().ConfigureAwait(false);
diff --git a/Source/NETworkManager/ViewModels/ProfileViewModel.cs b/Source/NETworkManager/ViewModels/ProfileViewModel.cs
index 5fe23f5577..dacde82a25 100644
--- a/Source/NETworkManager/ViewModels/ProfileViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ProfileViewModel.cs
@@ -310,7 +310,7 @@ public ProfileViewModel(Action saveCommand, Action
-
-
+
+
+
+
+
-
-
+
diff --git a/Source/NETworkManager/Views/DiscoveryProtocolView.xaml b/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
index 47de679080..ffe9eb0929 100644
--- a/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
+++ b/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
@@ -16,21 +16,24 @@
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -61,16 +64,8 @@
Text="{x:Static localization:Strings.DurationS}" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml
new file mode 100644
index 0000000000..3b97546304
--- /dev/null
+++ b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml.cs b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml.cs
new file mode 100644
index 0000000000..7e6b1d5685
--- /dev/null
+++ b/Source/NETworkManager/Views/FirewallRuleChildWindow.xaml.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Windows;
+using System.Windows.Threading;
+
+namespace NETworkManager.Views;
+
+public partial class FirewallRuleChildWindow
+{
+ public FirewallRuleChildWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void ChildWindow_OnLoaded(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(delegate
+ {
+ TextBoxName.Focus();
+ }));
+ }
+}
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/FirewallSettingsView.xaml b/Source/NETworkManager/Views/FirewallSettingsView.xaml
deleted file mode 100644
index 0790a6eead..0000000000
--- a/Source/NETworkManager/Views/FirewallSettingsView.xaml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
diff --git a/Source/NETworkManager/Views/FirewallSettingsView.xaml.cs b/Source/NETworkManager/Views/FirewallSettingsView.xaml.cs
deleted file mode 100644
index 94fcc3445c..0000000000
--- a/Source/NETworkManager/Views/FirewallSettingsView.xaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using NETworkManager.ViewModels;
-
-namespace NETworkManager.Views;
-
-///
-/// View for the firewall settings.
-///
-public partial class FirewallSettingsView
-{
- private readonly FirewallSettingsViewModel _viewModel = new();
-
- public FirewallSettingsView()
- {
- InitializeComponent();
- DataContext = _viewModel;
- }
-}
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/FirewallView.xaml b/Source/NETworkManager/Views/FirewallView.xaml
index 656a84922b..1c37d4917a 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"
@@ -20,49 +21,502 @@
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
-
@@ -74,13 +528,15 @@
-
-
@@ -93,608 +549,647 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/NETworkManager/Views/FirewallView.xaml.cs b/Source/NETworkManager/Views/FirewallView.xaml.cs
index bc108f1bf9..4565a26677 100644
--- a/Source/NETworkManager/Views/FirewallView.xaml.cs
+++ b/Source/NETworkManager/Views/FirewallView.xaml.cs
@@ -1,6 +1,7 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
+using System.Windows.Media;
using NETworkManager.ViewModels;
namespace NETworkManager.Views;
@@ -35,11 +36,22 @@ private void ContextMenu_Opened(object sender, RoutedEventArgs e)
if (sender is ContextMenu menu)
menu.DataContext = _viewModel;
}
-
- private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+
+ ///
+ /// Toggles the row details visibility when the expand/collapse chevron is clicked.
+ ///
+ private void ExpandRowDetails_OnClick(object sender, RoutedEventArgs e)
{
- if (e.ChangedButton == MouseButton.Left)
- _viewModel.ApplyProfileCommand.Execute(null);
+ 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;
+ }
}
///
diff --git a/Source/NETworkManager/Views/HostsFileEditorView.xaml b/Source/NETworkManager/Views/HostsFileEditorView.xaml
index afdd720a2b..f70d7c1830 100644
--- a/Source/NETworkManager/Views/HostsFileEditorView.xaml
+++ b/Source/NETworkManager/Views/HostsFileEditorView.xaml
@@ -30,9 +30,12 @@
-
+
+
+
+
@@ -75,6 +78,7 @@
Style="{StaticResource ResourceKey=SearchTextBox}" />
+
-
+
+
+
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -247,13 +277,14 @@
-
-
-
+
+
diff --git a/Source/NETworkManager/Views/IPScannerView.xaml b/Source/NETworkManager/Views/IPScannerView.xaml
index 93591d34e8..5ec9d99894 100644
--- a/Source/NETworkManager/Views/IPScannerView.xaml
+++ b/Source/NETworkManager/Views/IPScannerView.xaml
@@ -606,7 +606,8 @@
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
@@ -696,7 +697,8 @@
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
+ Style="{StaticResource BoldTextBlock}"
+ Foreground="{DynamicResource MahApps.Brushes.Gray5}" />
-
-
+
+
+
+
+
-
-
+
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" />
-
+
+:::note
+
+In addition, further actions can be performed using the buttons below:
+
+- **Add entry...** - Opens a dialog to create a new firewall rule.
+- **Windows Firewall Settings** - Opens the Windows Firewall management console (`WF.msc`).
+
+:::
+
+:::note
+
+With `F5` you can refresh the firewall rules.
+
+Right-click on a rule to `enable`, `disable`, `edit`, or `delete` it, or to `copy` or `export` the information.
+
+You can also use the Hotkeys `F2` (`edit`) or `Del` (`delete`) on a selected rule.
+
+:::
\ No newline at end of file
diff --git a/Website/docs/application/hosts-file-editor.md b/Website/docs/application/hosts-file-editor.md
index 72ac0142cb..8f81e04ce7 100644
--- a/Website/docs/application/hosts-file-editor.md
+++ b/Website/docs/application/hosts-file-editor.md
@@ -8,8 +8,6 @@ keywords: [NETworkManager, hosts file editor, Windows hosts file, DNS override,
The **Hosts File Editor** allows you to view, add, edit, enable, disable, or remove entries in the local computer's `hosts` file.
-Editing the `hosts` file requires administrator privileges. The application automatically creates a daily backup of the `hosts` file, retaining up to 5 backups in the same directory (Syntax: `hosts_backup_NETworkManager_YYYYMMDD`).
-
:::info
The hosts file is a plain text file that maps hostnames to IP addresses and is checked by the operating system before querying DNS servers. It's commonly used to override DNS settings for testing websites, redirecting domains, or blocking access to certain sites. On Windows, the file is located at `C:\Windows\System32\drivers\etc\hosts` and requires administrator privileges to edit.
@@ -24,6 +22,14 @@ Example of a hosts file entry:
:::
+:::note
+
+Editing the `hosts` file requires administrator privileges. If the application is not running as administrator, the view is in read-only mode. Use the **Restart as administrator** button to relaunch the application with elevated rights.
+
+The application automatically creates a daily backup of the `hosts` file, retaining up to 5 backups in the same directory (Syntax: `hosts_backup_NETworkManager_YYYYMMDD`).
+
+:::
+

:::note
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 8a3bc8fae0..08b76c6bd2 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -29,6 +29,10 @@ Release date: **xx.xx.2025**
## What's new?
+**Firewall**
+
+- New feature to quickly add, edit, enable, disable and delete NETworkManager-owned firewall rules. Managed rules are prefixed with `NETworkManager_` in the Windows Firewall. (See the [documentation](https://borntoberoot.net/NETworkManager/docs/application/firewall) for more details) [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+
**PowerShell**
- DPI scaling is now applied correctly when NETworkManager is moved to a monitor with a different DPI scaling factor. The embedded PowerShell (conhost) window now rescales its font automatically using the Windows Console API (`AttachConsole` + `SetCurrentConsoleFontEx`), bypassing the OS limitation that prevents `WM_DPICHANGED` from being forwarded to cross-process child windows. [#3352](https://github.com/BornToBeRoot/NETworkManager/pull/3352)
@@ -39,8 +43,23 @@ Release date: **xx.xx.2025**
## Improvements
+**Dashboard**
+
- Redesign Status Window to make it more compact. [#3359](https://github.com/BornToBeRoot/NETworkManager/pull/3359)
+**Network Interface**
+
+- Added Network Profile (domain, private, public) information to the Network Interface details view, if available. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+
+**Discovery Protocol**
+
+- Added support for `F5` and `Enter` keys to start capturing network packets. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+- Redesigned the "restart as admin" note to be more compact and visually consistent. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+
+**Hosts File Editor**
+
+- Button to open the hosts file in the default text editor added. [#3383](https://github.com/BornToBeRoot/NETworkManager/pull/3383)
+
## Bug Fixes
**Port Scanner**
diff --git a/Website/docs/introduction.mdx b/Website/docs/introduction.mdx
index 669760bd60..5117d4ec48 100644
--- a/Website/docs/introduction.mdx
+++ b/Website/docs/introduction.mdx
@@ -29,6 +29,7 @@ NETworkManager comes with various built-in tools. See the documentation for deta
- [Web Console](./application/web-console)
- [SNMP](./application/snmp) - Get, Walk, Set
- [Hosts File Editor](./application/hosts-file-editor)
+- [Firewall](./application/firewall)
- [SNTP Lookup](./application/sntp-lookup)
- [Discovery Protocol](./application/discovery-protocol) - LLDP, CDP
- [Wake on LAN](./application/wake-on-lan)