Skip to content

Commit ff7024e

Browse files
committed
Feature: Firewall app
1 parent eac4754 commit ff7024e

14 files changed

Lines changed: 1410 additions & 28 deletions
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using SMA = System.Management.Automation;
5+
using System.Threading.Tasks;
6+
using log4net;
7+
8+
namespace NETworkManager.Models.Firewall;
9+
10+
/// <summary>
11+
/// Represents a firewall configuration and management class that provides functionalities
12+
/// for applying and managing firewall rules based on a specified profile.
13+
/// </summary>
14+
public class Firewall
15+
{
16+
#region Variables
17+
18+
/// <summary>
19+
/// The Logger.
20+
/// </summary>
21+
private static readonly ILog Log = LogManager.GetLogger(typeof(Firewall));
22+
23+
private const string RuleIdentifier = "NETworkManager_";
24+
25+
#endregion
26+
27+
#region Methods
28+
29+
/// <summary>
30+
/// Reads all Windows Firewall rules whose display name starts with <see cref="RuleIdentifier"/>
31+
/// and maps them to <see cref="FirewallRule"/> objects.
32+
/// </summary>
33+
/// <returns>A task that resolves to the list of matching firewall rules.</returns>
34+
public static async Task<List<FirewallRule>> GetRulesAsync()
35+
{
36+
return await Task.Run(() =>
37+
{
38+
var rules = new List<FirewallRule>();
39+
40+
using var ps = SMA.PowerShell.Create();
41+
42+
ps.AddScript($@"
43+
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
44+
Import-Module NetSecurity -ErrorAction Stop
45+
Get-NetFirewallRule -DisplayName '{RuleIdentifier}*' | ForEach-Object {{
46+
$rule = $_
47+
$portFilter = $rule | Get-NetFirewallPortFilter
48+
$appFilter = $rule | Get-NetFirewallApplicationFilter
49+
[PSCustomObject]@{{
50+
Id = $rule.ID
51+
DisplayName = $rule.DisplayName
52+
Enabled = ($rule.Enabled -eq 'True')
53+
Description = $rule.Description
54+
Direction = [string]$rule.Direction
55+
Action = [string]$rule.Action
56+
Protocol = $portFilter.Protocol
57+
LocalPort = ($portFilter.LocalPort -join ',')
58+
RemotePort = ($portFilter.RemotePort -join ',')
59+
Profile = [string]$rule.Profile
60+
InterfaceType = [string]$rule.InterfaceType
61+
Program = $appFilter.Program
62+
}}
63+
}}");
64+
65+
var results = ps.Invoke();
66+
67+
if (ps.Streams.Error.Count > 0)
68+
{
69+
foreach (var error in ps.Streams.Error)
70+
Log.Warn($"PowerShell error: {error}");
71+
}
72+
73+
foreach (var result in results)
74+
{
75+
try
76+
{
77+
var rule = new FirewallRule
78+
{
79+
Id = result.Properties["Id"]?.Value?.ToString() ?? string.Empty,
80+
IsEnabled = result.Properties["Enabled"]?.Value as bool? == true,
81+
Name = result.Properties["DisplayName"]?.Value?.ToString() ?? string.Empty,
82+
Description = result.Properties["Description"]?.Value?.ToString() ?? string.Empty,
83+
Direction = ParseDirection(result.Properties["Direction"]?.Value?.ToString()),
84+
Action = ParseAction(result.Properties["Action"]?.Value?.ToString()),
85+
Protocol = ParseProtocol(result.Properties["Protocol"]?.Value?.ToString()),
86+
LocalPorts = ParsePorts(result.Properties["LocalPort"]?.Value?.ToString()),
87+
RemotePorts = ParsePorts(result.Properties["RemotePort"]?.Value?.ToString()),
88+
NetworkProfiles = ParseProfile(result.Properties["Profile"]?.Value?.ToString()),
89+
InterfaceType = ParseInterfaceType(result.Properties["InterfaceType"]?.Value?.ToString()),
90+
};
91+
92+
var program = result.Properties["Program"]?.Value as string;
93+
94+
if (!string.IsNullOrWhiteSpace(program) && !program.Equals("Any", StringComparison.OrdinalIgnoreCase))
95+
rule.Program = new FirewallRuleProgram(program);
96+
97+
rules.Add(rule);
98+
}
99+
catch (Exception ex)
100+
{
101+
Log.Warn($"Failed to parse firewall rule: {ex.Message}");
102+
}
103+
}
104+
105+
return rules;
106+
});
107+
}
108+
109+
/// <summary>Parses a PowerShell direction string to <see cref="FirewallRuleDirection"/>.</summary>
110+
private static FirewallRuleDirection ParseDirection(string value)
111+
{
112+
return value switch
113+
{
114+
"Outbound" => FirewallRuleDirection.Outbound,
115+
_ => FirewallRuleDirection.Inbound,
116+
};
117+
}
118+
119+
/// <summary>Parses a PowerShell action string to <see cref="FirewallRuleAction"/>.</summary>
120+
private static FirewallRuleAction ParseAction(string value)
121+
{
122+
return value switch
123+
{
124+
"Allow" => FirewallRuleAction.Allow,
125+
_ => FirewallRuleAction.Block,
126+
};
127+
}
128+
129+
/// <summary>Parses a PowerShell protocol string to <see cref="FirewallProtocol"/>.</summary>
130+
private static FirewallProtocol ParseProtocol(string value)
131+
{
132+
if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
133+
return FirewallProtocol.Any;
134+
135+
return value.ToUpperInvariant() switch
136+
{
137+
"TCP" => FirewallProtocol.TCP,
138+
"UDP" => FirewallProtocol.UDP,
139+
"ICMPV4" => FirewallProtocol.ICMPv4,
140+
"ICMPV6" => FirewallProtocol.ICMPv6,
141+
"GRE" => FirewallProtocol.GRE,
142+
"L2TP" => FirewallProtocol.L2TP,
143+
_ => int.TryParse(value, out var proto) ? (FirewallProtocol)proto : FirewallProtocol.Any,
144+
};
145+
}
146+
147+
/// <summary>
148+
/// Parses a comma-separated port string (e.g. <c>"80,443,8080-8090"</c>) to a list of
149+
/// <see cref="FirewallPortSpecification"/> objects. Returns an empty list for <c>Any</c> or blank input.
150+
/// </summary>
151+
private static List<FirewallPortSpecification> ParsePorts(string value)
152+
{
153+
var list = new List<FirewallPortSpecification>();
154+
155+
if (string.IsNullOrWhiteSpace(value) || value.Equals("Any", StringComparison.OrdinalIgnoreCase))
156+
return list;
157+
158+
foreach (var token in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
159+
{
160+
var dashIndex = token.IndexOf('-');
161+
162+
if (dashIndex > 0 &&
163+
int.TryParse(token[..dashIndex], out var start) &&
164+
int.TryParse(token[(dashIndex + 1)..], out var end))
165+
{
166+
list.Add(new FirewallPortSpecification(start, end));
167+
}
168+
else if (int.TryParse(token, out var port))
169+
{
170+
list.Add(new FirewallPortSpecification(port));
171+
}
172+
}
173+
174+
return list;
175+
}
176+
177+
/// <summary>
178+
/// Parses a PowerShell profile string (e.g. <c>"Domain, Private"</c>) to a three-element boolean array
179+
/// in the order Domain, Private, Public.
180+
/// </summary>
181+
private static bool[] ParseProfile(string value)
182+
{
183+
var profiles = new bool[3];
184+
185+
if (string.IsNullOrWhiteSpace(value))
186+
return profiles;
187+
188+
if (value.Equals("Any", StringComparison.OrdinalIgnoreCase) ||
189+
value.Equals("All", StringComparison.OrdinalIgnoreCase))
190+
{
191+
profiles[0] = profiles[1] = profiles[2] = true;
192+
return profiles;
193+
}
194+
195+
foreach (var token in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
196+
{
197+
switch (token)
198+
{
199+
case "Domain": profiles[0] = true; break;
200+
case "Private": profiles[1] = true; break;
201+
case "Public": profiles[2] = true; break;
202+
}
203+
}
204+
205+
return profiles;
206+
}
207+
208+
/// <summary>Parses a PowerShell interface-type string to <see cref="FirewallInterfaceType"/>.</summary>
209+
private static FirewallInterfaceType ParseInterfaceType(string value)
210+
{
211+
return value switch
212+
{
213+
"Wired" => FirewallInterfaceType.Wired,
214+
"Wireless" => FirewallInterfaceType.Wireless,
215+
"RemoteAccess" => FirewallInterfaceType.RemoteAccess,
216+
_ => FirewallInterfaceType.Any,
217+
};
218+
}
219+
#endregion
220+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace NETworkManager.Models.Firewall;
2+
3+
/// <summary>
4+
/// Defines the types of network interfaces that can be used in firewall rules.
5+
/// </summary>
6+
public enum FirewallInterfaceType
7+
{
8+
/// <summary>
9+
/// Any interface type.
10+
/// </summary>
11+
Any = -1,
12+
13+
/// <summary>
14+
/// Wired interface types, e.g. Ethernet.
15+
/// </summary>
16+
Wired,
17+
18+
/// <summary>
19+
/// Wireless interface types, e.g. Wi-Fi.
20+
/// </summary>
21+
Wireless,
22+
23+
/// <summary>
24+
/// Remote interface types, e.g. VPN, L2TP, OpenVPN, etc.
25+
/// </summary>
26+
RemoteAccess
27+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace NETworkManager.Models.Firewall;
2+
3+
/// <summary>
4+
/// Ports of local host or remote host.
5+
/// </summary>
6+
public enum FirewallPortLocation
7+
{
8+
/// <summary>
9+
/// Ports of local host.
10+
/// </summary>
11+
LocalPorts,
12+
13+
/// <summary>
14+
/// Ports of remote host.
15+
/// </summary>
16+
RemotePorts
17+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// ReSharper disable MemberCanBePrivate.Global
2+
// Needed for serialization.
3+
namespace NETworkManager.Models.Firewall;
4+
5+
/// <summary>
6+
/// Represents a specification for defining and validating firewall ports.
7+
/// </summary>
8+
/// <remarks>
9+
/// This class is used to encapsulate rules and configurations for
10+
/// managing firewall port restrictions or allowances. It provides
11+
/// properties and methods to define a range of acceptable ports or
12+
/// individual port specifications.
13+
/// </remarks>
14+
public class FirewallPortSpecification
15+
{
16+
/// <summary>
17+
/// Gets or sets the start point or initial value of a process, range, or operation.
18+
/// </summary>
19+
/// <remarks>
20+
/// The <c>Start</c> property typically represents the beginning state or position for sequential
21+
/// processing or iteration. The exact usage of this property may vary depending on the context of
22+
/// the class or object it belongs to.
23+
/// </remarks>
24+
public int Start { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets the endpoint or final state of a process, range, or operation.
28+
/// </summary>
29+
/// <remarks>
30+
/// This property typically represents the termination position, time, or value
31+
/// in a sequence, operation, or any bounded context. Its specific meaning may vary
32+
/// depending on the context in which it is used.
33+
/// </remarks>
34+
public int End { get; set; }
35+
36+
/// <summary>
37+
/// For serializing.
38+
/// </summary>
39+
public FirewallPortSpecification()
40+
{
41+
Start = -1;
42+
End = -1;
43+
}
44+
45+
/// <summary>
46+
/// Represents the specification for a firewall port, detailing its configuration
47+
/// and rules for inbound or outbound network traffic.
48+
/// </summary>
49+
public FirewallPortSpecification(int start, int end = -1)
50+
{
51+
Start = start;
52+
End = end;
53+
}
54+
55+
/// <summary>
56+
/// Returns a string that represents the current object.
57+
/// </summary>
58+
/// <returns>A string that represents the current instance of the object.</returns>
59+
public override string ToString()
60+
{
61+
if (Start is 0)
62+
return string.Empty;
63+
64+
return End is -1 or 0 ? $"{Start}" : $"{Start}-{End}";
65+
}
66+
}

0 commit comments

Comments
 (0)