Skip to content

Commit dccfb8a

Browse files
BornToBeRootclaude
andauthored
Feature: More improvements (#3667)
* Chore: Port Scanner: Clarify MaxRowChangesOneByOne comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Feature: IP Scanner: More default ports to detect if a host is up Add 554 (RTSP), 631 (IPP), 8080, 8443 and 62078 (iPhone / iPad) to the default ports (16 ports, all scanned at the same time with the default 16 port threads). Settings with the old defaults (release and pre-releases) are migrated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Feature: IP Scanner: Add FTP, Telnet, VNC, WinRM, AirPlay and Chromecast to the default ports Add 21, 23, 5900, 5985, 7000 and 8009 (22 ports, 2 rounds with the default 16 port threads, ~4 s for an unreachable host like 2 ICMP attempts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Feature: Port Scanner: Own attempts, timeout and buffer for the ping before the scan Ping hosts before the scan used the timeout / attempts of the port scan and a fixed 32-byte buffer. New settings like the IP Scanner (default 2 attempts, 2000 ms, 32 bytes), disabled while the option is off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix: Group dialog: Feature names shown without spaces The group views were only translated with ProfileGroup_* keys (only General / Application exist), so e.g. "RemoteDesktop" was shown. Fall back to the application names like the profile dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Feature: Port Scanner: Ports in groups Groups get Ports (ports, port ranges and port profiles) for the Port Scanner, used by the profiles of the group that don't override them. Profiles get a Ports checkbox under "Override default settings", like the other settings that can be set in a group. The ports of a profile are resolved in one place (Profiles.Application.PortScanner.GetPorts). Profile file schema version 2 (first use of the version): profiles of older files with ports get the override enabled when loaded, so they keep their ports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix: Firewall: Feature name not translatable ApplicationName_Firewall was missing, so the name fell back to the enum name wherever application names are translated (list of features, settings, run command). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix: DNS Lookup: Clear message for PTR with a hostname A hostname looked up with PTR showed the exception text "An invalid IP address was specified". Hostnames are skipped with a message now, the other hosts are looked up. The IP check of the DNS Lookup (PTR for IP addresses) moves to IPAddressHelper to be shared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Feature: DNS Lookup: DNS server in groups, record type in profiles - Groups get DNS server (like the profile), used by the profiles of the group that don't override it: profile -> group -> selected in DNS Lookup. - Profiles get Type (record type). Without it, a profile is looked up with PTR for IP addresses and A for hostnames instead of the record type last selected in DNS Lookup. PTR with a hostname is an error in the profile (can't be saved). - DNS server and type are under "Override default settings" in the profile, like the other settings that can be set in a group. - DNS server and type of a profile are resolved in one place (Profiles.Application.DNSLookup). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix: Profiles: Group renamed only by case The profiles of a group renamed only by case (e.g. "servers" -> "Servers") were not updated (case-insensitive comparison), but groups are looked up case-sensitive: opening such a profile in a feature with group settings threw "Group not found", editing it could create a second group with the old name. Profiles are updated on any rename now, and profiles of existing files get the name of the group they are saved in when loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: Profiles: Shared saving / loading of port profiles and DNS servers Profiles and groups saved and loaded the ports (port profiles) and DNS servers with copies of the same code (dialog manager, profile and group view model, Port Scanner / DNS Lookup helper). New PortProfile.Encode / Decode and DNSServer.Encode / Decode, with tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: Group dialog: Select the tab by the name of the view The tab was selected by the index of the view in the list, so the order of GroupViewName and the order of the tabs in the XAML had to match. Tabs have their GroupViewName as Tag and are selected by it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix: Ping buffer from the settings file is limited IP Scanner, Port Scanner, Ping Monitor and Traceroute created the ping buffer from the setting without a limit: a value edited in the settings file threw (negative) or made every ping fail (more than 65500 bytes). Limited to the input range (1 - 65500) when the buffer is created, with one constant for the maximum (also used by the settings upgrade). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 459354a commit dccfb8a

40 files changed

Lines changed: 1215 additions & 145 deletions

‎Source/NETworkManager.Converters/ProfileGroupToTranslatedStringConverter.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
2323
{
2424
return value is not GroupViewName name
2525
? "-/-"
26-
: ResourceTranslator.Translate(ResourceIdentifier.ProfileGroup, name);
26+
: ResourceTranslator.Translate(new[] { ResourceIdentifier.ProfileGroup, ResourceIdentifier.ApplicationName },
27+
name);
2728
}
2829

2930
/// <summary>

‎Source/NETworkManager.Localization/Resources/Strings.Designer.cs‎

Lines changed: 30 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Source/NETworkManager.Localization/Resources/Strings.resx‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5123,13 +5123,13 @@ Do you want to start the scan?</value>
51235123
<value>The name must not contain ";" and must not be a port or port range.</value>
51245124
</data>
51255125
<data name="PortProfileOfProfileNotFoundMessage" xml:space="preserve">
5126-
<value>A port profile of this profile was not found, the ports saved with the profile are used. Port profiles are saved in the settings and the profile refers to them by an internal ID - if the profile file was shared from another computer, create the port profile and then select it in the profile again (edit the profile).</value>
5126+
<value>A port profile of this profile or its group was not found, the ports saved with the profile or group are used. Port profiles are saved in the settings and the profile or group refers to them by an internal ID - if the profile file was shared from another computer, create the port profile and then select it in the profile or group again (edit it).</value>
51275127
</data>
51285128
<data name="PingHostsBeforeScan" xml:space="preserve">
51295129
<value>Ping hosts before the scan</value>
51305130
</data>
51315131
<data name="HelpMessage_PingHostsBeforeScan" xml:space="preserve">
5132-
<value>Only hosts that answer a ping (ICMP echo, with the timeout and attempts above) are scanned. Faster for ranges with many unused IP addresses. Hosts that block ICMP (e.g. Windows with the firewall enabled) are not scanned.</value>
5132+
<value>Only hosts that answer a ping (ICMP echo, with the attempts, timeout and buffer below) are scanned. Faster for ranges with many unused IP addresses. Hosts that block ICMP (e.g. Windows with the firewall enabled) are not scanned.</value>
51335133
</data>
51345134
<data name="XHostsDidNotAnswerThePingAndWereNotScanned" xml:space="preserve">
51355135
<value>{0} host(s) did not answer the ping and were not scanned.</value>
@@ -5141,6 +5141,15 @@ Do you want to start the scan?</value>
51415141
<value>The name must not contain ";".</value>
51425142
</data>
51435143
<data name="DNSServerOfProfileNotFoundMessage" xml:space="preserve">
5144-
<value>A DNS server of this profile was not found, the DNS servers saved with the profile are used. DNS servers are saved in the settings and the profile refers to them by an internal ID - if the profile file was shared from another computer, create the DNS server and then select it in the profile again (edit the profile).</value>
5144+
<value>A DNS server of this profile or its group was not found, the DNS servers saved with the profile or group are used. DNS servers are saved in the settings and the profile or group refers to them by an internal ID - if the profile file was shared from another computer, create the DNS server and then select it in the profile or group again (edit it).</value>
5145+
</data>
5146+
<data name="ApplicationName_Firewall" xml:space="preserve">
5147+
<value>Firewall</value>
5148+
</data>
5149+
<data name="PTRRequiresIPAddressX" xml:space="preserve">
5150+
<value>PTR records can only be looked up for IP addresses, "{0}" is skipped.</value>
5151+
</data>
5152+
<data name="ErrorMessage_PTRRequiresIPAddresses" xml:space="preserve">
5153+
<value>PTR records can only be looked up for IP addresses.</value>
51455154
</data>
51465155
</root>

‎Source/NETworkManager.Models/Network/DNSServer.cs‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,48 @@ public static bool TryDecodeReferences(string inputWithReferences,
153153
return NamedReferenceInput.TryDecodeReferences(inputWithReferences, profiles, out input);
154154
}
155155

156+
/// <summary>
157+
/// Converts a DNS server input (e.g. of a profile or group) to the values that are saved: the servers (DNS
158+
/// server profiles replaced by their servers, see <see cref="ReplaceProfilesByServers" />, used if a DNS server
159+
/// profile can't be found later, e.g. a profile file from another computer) and the input with references (see
160+
/// <see cref="EncodeReferences" />).
161+
/// </summary>
162+
/// <param name="input">DNS server input.</param>
163+
/// <param name="profiles">DNS server profiles.</param>
164+
/// <returns>Servers and DNS server input with references (null if it contains no DNS server profile).</returns>
165+
public static (string DNSServer, string DNSServerInput) Encode(string input,
166+
IEnumerable<DNSServerConnectionInfoProfile> profiles)
167+
{
168+
var profileList = profiles.ToList();
169+
170+
return (ReplaceProfilesByServers(input?.Trim(), profileList), EncodeReferences(input, profileList));
171+
}
172+
173+
/// <summary>
174+
/// Converts the saved values (see <see cref="Encode" />) back to the DNS server input: the DNS server profiles
175+
/// with their current name (also after a rename), or the saved servers if a DNS server profile can't be found.
176+
/// </summary>
177+
/// <param name="dnsServer">Saved servers.</param>
178+
/// <param name="dnsServerInput">Saved DNS server input with references.</param>
179+
/// <param name="profiles">DNS server profiles.</param>
180+
/// <param name="dnsServerNotFound">True if a DNS server profile can't be found.</param>
181+
/// <returns>DNS server input.</returns>
182+
public static string Decode(string dnsServer, string dnsServerInput,
183+
IEnumerable<DNSServerConnectionInfoProfile> profiles, out bool dnsServerNotFound)
184+
{
185+
dnsServerNotFound = false;
186+
187+
if (string.IsNullOrEmpty(dnsServerInput))
188+
return dnsServer;
189+
190+
if (TryDecodeReferences(dnsServerInput, profiles, out var input))
191+
return input;
192+
193+
dnsServerNotFound = true;
194+
195+
return dnsServer;
196+
}
197+
156198
/// <summary>
157199
/// Replaces the names of DNS server profiles in a DNS server input by their servers (e.g. "Cloudflare; 9.9.9.9"
158200
/// -> "1.1.1.1:53; 1.0.0.1:53; 9.9.9.9"), so the input still works without the DNS server profiles (e.g. on another

‎Source/NETworkManager.Models/Network/PortProfile.cs‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,47 @@ public static bool TryDecodeReferences(string inputWithReferences, IEnumerable<P
104104
return NamedReferenceInput.TryDecodeReferences(inputWithReferences, portProfiles, out input);
105105
}
106106

107+
/// <summary>
108+
/// Converts a ports input (e.g. of a profile or group) to the values that are saved: the ports (port profiles
109+
/// replaced by their ports, used if a port profile can't be found later, e.g. a profile file from another
110+
/// computer) and the input with references to the port profiles (see <see cref="EncodeReferences" />).
111+
/// </summary>
112+
/// <param name="input">Ports input.</param>
113+
/// <param name="portProfiles">Port profiles.</param>
114+
/// <returns>Ports and ports input with references (null if it contains no port profile).</returns>
115+
public static (string Ports, string PortsInput) Encode(string input, IEnumerable<PortProfileInfo> portProfiles)
116+
{
117+
var portProfileList = portProfiles.ToList();
118+
119+
return (TryResolvePorts(input, portProfileList, out var ports) ? ports : input?.Trim(),
120+
EncodeReferences(input, portProfileList));
121+
}
122+
123+
/// <summary>
124+
/// Converts the saved values (see <see cref="Encode" />) back to the ports input: the port profiles with their
125+
/// current name (also after a rename), or the saved ports if a port profile can't be found.
126+
/// </summary>
127+
/// <param name="ports">Saved ports.</param>
128+
/// <param name="portsInput">Saved ports input with references.</param>
129+
/// <param name="portProfiles">Port profiles.</param>
130+
/// <param name="portProfileNotFound">True if a port profile can't be found.</param>
131+
/// <returns>Ports input.</returns>
132+
public static string Decode(string ports, string portsInput, IEnumerable<PortProfileInfo> portProfiles,
133+
out bool portProfileNotFound)
134+
{
135+
portProfileNotFound = false;
136+
137+
if (string.IsNullOrEmpty(portsInput))
138+
return ports;
139+
140+
if (TryDecodeReferences(portsInput, portProfiles, out var input))
141+
return input;
142+
143+
portProfileNotFound = true;
144+
145+
return ports;
146+
}
147+
107148
public static List<PortProfileInfo> GetDefaultList()
108149
{
109150
return new List<PortProfileInfo>

‎Source/NETworkManager.Models/Network/PortScanner.cs‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,6 @@ public PortScanner(PortScannerOptions options)
6363
private readonly InterlockedCounter _portsTimedOut = new();
6464
private readonly InterlockedCounter _hostsSkipped = new();
6565

66-
/// <summary>
67-
/// Data of the ping before the scan (see <see cref="PortScannerOptions.PingHostsBeforeScan" />), like the
68-
/// default buffer of the IP Scanner.
69-
/// </summary>
70-
private static readonly byte[] PingBuffer = new byte[32];
71-
7266
// Closed and timed out ports per host, see GetClosedAndTimedOutPortsPerHost
7367
private readonly ConcurrentDictionary<IPAddress, (InterlockedCounter closed, InterlockedCounter timedOut)>
7468
_portsPerHost = new();
@@ -399,8 +393,8 @@ await Parallel.ForEachAsync(hosts, pingParallelOptions, async (host, ct) =>
399393
/// <returns>True if the host answered the ping.</returns>
400394
private async Task<bool> IsReachableAsync(IPAddress ipAddress, int portCount, CancellationToken cancellationToken)
401395
{
402-
var pingInfo = await PingProbe.PingAsync(ipAddress, _options.Timeout, _options.Attempts, PingBuffer,
403-
cancellationToken).ConfigureAwait(false);
396+
var pingInfo = await PingProbe.PingAsync(ipAddress, _options.ICMPTimeout, _options.ICMPAttempts,
397+
_options.ICMPBuffer, cancellationToken).ConfigureAwait(false);
404398

405399
if (pingInfo.Status == IPStatus.Success)
406400
return true;

‎Source/NETworkManager.Models/Network/PortScannerOptions.cs‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,27 @@ public record PortScannerOptions
3232
public required int Attempts { get; init; }
3333

3434
/// <summary>
35-
/// Ping every host before its ports are scanned (with <see cref="Timeout" /> and <see cref="Attempts" />), a host
36-
/// that doesn't answer is not scanned.
35+
/// Ping every host before its ports are scanned (with <see cref="ICMPAttempts" />, <see cref="ICMPTimeout" /> and
36+
/// <see cref="ICMPBuffer" />), a host that doesn't answer is not scanned.
3737
/// </summary>
3838
public required bool PingHostsBeforeScan { get; init; }
3939

40+
/// <summary>
41+
/// Number of pings before the scan if there was no answer (see <see cref="PingHostsBeforeScan" />).
42+
/// </summary>
43+
public required int ICMPAttempts { get; init; }
44+
45+
/// <summary>
46+
/// Timeout in milliseconds after which a ping before the scan is considered lost (see
47+
/// <see cref="PingHostsBeforeScan" />).
48+
/// </summary>
49+
public required int ICMPTimeout { get; init; }
50+
51+
/// <summary>
52+
/// Data sent with the ping before the scan (see <see cref="PingHostsBeforeScan" />).
53+
/// </summary>
54+
public required byte[] ICMPBuffer { get; init; }
55+
4056
/// <summary>
4157
/// Resolve the hostname for an IP address.
4258
/// </summary>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using DnsClient;
2+
using NETworkManager.Models.Network;
3+
using NETworkManager.Settings;
4+
using NETworkManager.Utilities;
5+
6+
namespace NETworkManager.Profiles.Application;
7+
8+
public static class DNSLookup
9+
{
10+
/// <summary>
11+
/// Gets the DNS servers for a profile: its own DNS servers (if overridden), otherwise the DNS servers of its
12+
/// group (if overridden), otherwise none (the DNS server selected in the DNS Lookup is used). DNS servers by name
13+
/// with their current name (also after a rename); if a DNS server can't be found (deleted, or a profile file
14+
/// shared from another computer - DNS servers are saved in the settings), the servers saved with the profile or
15+
/// group.
16+
/// </summary>
17+
/// <param name="profile">Profile to get the DNS servers for.</param>
18+
/// <param name="dnsServerNotFound">True if a DNS server can't be found.</param>
19+
/// <returns>DNS servers, null if neither the profile nor its group has DNS servers.</returns>
20+
public static string GetDNSServer(ProfileInfo profile, out bool dnsServerNotFound)
21+
{
22+
// Get group info
23+
var group = ProfileManager.GetGroupByName(profile.Group);
24+
25+
var (dnsServer, dnsServerInput) = profile.DNSLookup_OverrideDNSServer
26+
? (profile.DNSLookup_DNSServer, profile.DNSLookup_DNSServerInput)
27+
: group.DNSLookup_OverrideDNSServer
28+
? (group.DNSLookup_DNSServer, group.DNSLookup_DNSServerInput)
29+
: (null, null);
30+
31+
if (string.IsNullOrWhiteSpace(dnsServer))
32+
{
33+
dnsServerNotFound = false;
34+
35+
return null;
36+
}
37+
38+
return DNSServer.Decode(dnsServer, dnsServerInput, SettingsManager.Current.DNSLookup_DNSServers,
39+
out dnsServerNotFound);
40+
}
41+
42+
/// <summary>
43+
/// Gets the query type for a profile: its own type (if overridden), otherwise PTR for IP addresses and A for
44+
/// hostnames.
45+
/// </summary>
46+
/// <param name="profile">Profile to get the query type for.</param>
47+
/// <returns>Query type.</returns>
48+
public static QueryType GetQueryType(ProfileInfo profile)
49+
{
50+
return profile.DNSLookup_OverrideQueryType
51+
? profile.DNSLookup_QueryType
52+
: IPAddressHelper.AreIPAddresses(profile.DNSLookup_Host)
53+
? QueryType.PTR
54+
: QueryType.A;
55+
}
56+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using NETworkManager.Models.Network;
2+
using NETworkManager.Settings;
3+
4+
namespace NETworkManager.Profiles.Application;
5+
6+
public static class PortScanner
7+
{
8+
/// <summary>
9+
/// Gets the ports to scan for a profile: its own ports (if overridden), otherwise the ports of its group (if
10+
/// overridden), otherwise none. Port profiles with their current name (also after a rename); if a port profile
11+
/// can't be found (deleted, or a profile file shared from another computer - port profiles are saved in the
12+
/// settings), the ports saved with the profile or group.
13+
/// </summary>
14+
/// <param name="profile">Profile to get the ports for.</param>
15+
/// <param name="portProfileNotFound">True if a port profile can't be found.</param>
16+
/// <returns>Ports to scan, empty if neither the profile nor its group has ports.</returns>
17+
public static string GetPorts(ProfileInfo profile, out bool portProfileNotFound)
18+
{
19+
// Get group info
20+
var group = ProfileManager.GetGroupByName(profile.Group);
21+
22+
var (ports, portsInput) = profile.PortScanner_OverridePorts
23+
? (profile.PortScanner_Ports, profile.PortScanner_PortsInput)
24+
: group.PortScanner_OverridePorts
25+
? (group.PortScanner_Ports, group.PortScanner_PortsInput)
26+
: (string.Empty, null);
27+
28+
return PortProfile.Decode(ports, portsInput, SettingsManager.Current.PortScanner_PortProfiles,
29+
out portProfileNotFound);
30+
}
31+
}

0 commit comments

Comments
 (0)