Skip to content

Commit 409853c

Browse files
BornToBeRootclaude
andauthored
Fix/review 2026 10 28 (#3674)
* Fix: Web Console: Close the temporary WebView2 controller if deleting the browsing data fails (C1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: Feature review conventions: Context menu DataContext via BindingProxy in XAML (C3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: SNMP / Server profiles dialog: Remove nested BindingProxy (C6) The top-level BindingProxy (added for the context menus) has the same key and the same view model, the nested copies are no longer needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: Firewall: Outdated comment of ContextMenu_Opened (C7) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix: Port Scanner: Profile tooltip shows the ports to scan (F1) The tooltip of a profile showed its own saved ports: nothing or old ports if the ports are inherited from the group, and old names of renamed port profiles. It now shows the ports the scan uses (own ports, otherwise the ports of the group, port profiles with their current name). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: Changelog: Link the entries of #3667 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix: Port Scanner: Scan options compare by value (F2) The options contained the ping buffer (byte[]), records compare arrays by reference: two options were never equal and every settings change rebuilt the options of every Port Scanner tab without results (allocating the buffer and re-evaluating the bindings). The options hold the buffer size, the scanner creates the buffer once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix: Profiles: PTR check of DNS Lookup accepts the same addresses as the lookup (F3) The DNS Lookup looks up every host IPAddress.TryParse accepts with PTR (also shortened IPv4 forms like "127.1"), the profile dialog only accepted the full form: a profile with "127.1" and PTR couldn't be saved. The automatic record type (PTR vs A) keeps the strict check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: Ping Monitor / IP Scanner / Port Scanner: Shared PingInfo from a successful reply PingProbe (IP Scanner, Port Scanner) and Ping (Ping Monitor) created the PingInfo of a successful reply the same way (TTL only for IPv4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: IP Scanner / Port Scanner / Ping Monitor / Traceroute: Shared limit of the ping buffer The same limit (1 to 65500 bytes) was written in four view models. GlobalStaticConfiguration.LimitICMPBuffer is used instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chore: IP Scanner / Ping Monitor / Traceroute: Buffer size instead of the buffer in the options Like the Port Scanner (F2): the options hold the buffer size, the scanner / ping / traceroute creates the buffer once. IPScannerOptions is a record and compared arrays by reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 4c77eb5 commit 409853c

23 files changed

Lines changed: 149 additions & 84 deletions

‎.claude/skills/feature-review/references/conventions.md‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,19 @@ Reference: `HostsFileEditorView.xaml`, `FirewallView.xaml`.
8484
`SelectedResults` is changed in place (no change notification): selection-dependent properties are raised
8585
in `OnContextMenuOpened()` called from the view's `ContextMenu_Opened`.
8686
- Two context menus: `RowContextMenu` (one row) and `MultiRowContextMenu` (several rows), switched by a
87-
`DataTrigger` on `SelectedItems.Count`. `Opened="ContextMenu_Opened"` sets the menu's `DataContext`.
87+
`DataTrigger` on `SelectedItems.Count`.
88+
- A context menu is not part of the visual tree and doesn't inherit the `DataContext`: the menu gets
89+
`DataContext="{Binding Data, Source={StaticResource BindingProxy}}"` in XAML, with
90+
`<wpfHelpers:BindingProxy x:Key="BindingProxy" Data="{Binding}" />` in the view's own resources, declared before
91+
the menus. The bindings inside the menu are plain bindings (`{Binding EditEntryCommand}`). Don't set the
92+
`DataContext` in `ContextMenu_Opened` (until the menu is opened, its bindings fail and are logged as XAML binding
93+
errors). `Opened` is only for extra work, e.g. `OnContextMenuOpened()` or the custom commands of the IP Scanner.
94+
- No bindings that have no element to resolve against: no `VisualBrush` / `BitmapImage` with a binding in a style
95+
setter or as a freezable resource. Bind the property of the element with a converter instead
96+
(`VisualToVisualBrushConverter`, `ApplicationNameToIconBrushConverter`, `Image Source="{Binding Uri}"`). A static
97+
`VisualBrush` (`Visual="{iconPacks:Material Kind=...}"`) is fine. `MenuItem`s created outside a menu (e.g. extra
98+
items in an `x:Array`) are fine: the implicit `DefaultMenuItem` style sets the content alignment directly instead
99+
of the MahApps binding to the parent. New `MenuItem` styles are `BasedOn="{StaticResource DefaultMenuItem}"`.
88100
- Order: Enable / Disable (only the applicable one) → Edit... → Copy as... → Delete → separator → Copy
89101
(submenu, `CopyMenuItem` style) → Export...
90102
- Icons (16×16 `Rectangle`, `MahApps.Brushes.Gray3`, `VisualBrush`): Enable `Material ToggleSwitchOutline`,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Windows.Data;
4+
using NETworkManager.Profiles;
5+
6+
namespace NETworkManager.Converters;
7+
8+
/// <summary>
9+
/// Convert a <see cref="ProfileInfo" /> to the ports the Port Scanner scans for it (own ports, otherwise the ports
10+
/// of its group, port profiles with their current name).
11+
/// </summary>
12+
public sealed class ProfileToPortScannerPortsConverter : IValueConverter
13+
{
14+
/// <summary>
15+
/// Convert a <see cref="ProfileInfo" /> to the ports to scan (see
16+
/// <see cref="Profiles.Application.PortScanner.GetPorts" />).
17+
/// </summary>
18+
/// <param name="value">Object from type <see cref="ProfileInfo" />.</param>
19+
/// <param name="targetType"></param>
20+
/// <param name="parameter"></param>
21+
/// <param name="culture"></param>
22+
/// <returns>Ports to scan, empty if neither the profile nor its group has ports.</returns>
23+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
24+
{
25+
return value is not ProfileInfo profile
26+
? string.Empty
27+
: Profiles.Application.PortScanner.GetPorts(profile, out _);
28+
}
29+
30+
/// <summary>
31+
/// !!! Method not implemented !!!
32+
/// </summary>
33+
/// <param name="value"></param>
34+
/// <param name="targetType"></param>
35+
/// <param name="parameter"></param>
36+
/// <param name="culture"></param>
37+
/// <returns></returns>
38+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
39+
{
40+
throw new NotImplementedException();
41+
}
42+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public sealed class IPScanner(IPScannerOptions options)
2828
/// </summary>
2929
private static readonly TimeSpan MaxHostStartInterval = TimeSpan.FromMilliseconds(10);
3030

31+
// Data sent with the ping, created once (see IPScannerOptions.ICMPBufferSize)
32+
private readonly byte[] _icmpBuffer = new byte[options.ICMPBufferSize];
33+
3134
private int _progressValue;
3235
private readonly InterlockedCounter _hostsUp = new();
3336
private readonly InterlockedCounter _hostsDown = new();
@@ -211,7 +214,7 @@ private async Task ScanHostAsync(IPAddress ipAddress, ParallelOptions portScanPa
211214
var portAnswered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
212215

213216
var pingTask = PingProbe.PingAsync(ipAddress, options.ICMPTimeout, options.ICMPAttempts,
214-
options.ICMPBuffer, cancellationToken);
217+
_icmpBuffer, cancellationToken);
215218

216219
var portScanTask = options.PortScanEnabled
217220
? PortScanAsync(ipAddress, portScanParallelOptions, () => portAnswered.TrySetResult(), cancellationToken)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ public record IPScannerOptions
2727
public required int ICMPTimeout { get; init; }
2828

2929
/// <summary>
30-
/// Size of the buffer used in the ping in bytes.
30+
/// Size of the buffer used in the ping in bytes. A size, not the buffer, so options with the same values are
31+
/// equal (records compare arrays by reference).
3132
/// </summary>
32-
public required byte[] ICMPBuffer { get; init; }
33+
public required int ICMPBufferSize { get; init; }
3334

3435
/// <summary>
3536
/// Resolve the hostname for an IP address.

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

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Net;
33
using System.Net.NetworkInformation;
4-
using System.Net.Sockets;
54
using System.Threading;
65
using System.Threading.Tasks;
76
using NETworkManager.Utilities;
@@ -26,9 +25,9 @@ public sealed class Ping
2625
public int Timeout = 4000;
2726

2827
/// <summary>
29-
/// The buffer to send with the ping request. Default is 32 bytes.
28+
/// The size in bytes of the buffer to send with the ping request. Default is 32 bytes.
3029
/// </summary>
31-
public byte[] Buffer = new byte[32];
30+
public int BufferSize = 32;
3231

3332
/// <summary>
3433
/// The Time to Live (TTL) value for the ping request. Default is 64.
@@ -123,6 +122,8 @@ public void SendAsync(IPAddress ipAddress, CancellationToken cancellationToken)
123122

124123
var errorCount = 0;
125124

125+
var buffer = new byte[BufferSize];
126+
126127
var options = new PingOptions
127128
{
128129
Ttl = TTL,
@@ -139,7 +140,7 @@ public void SendAsync(IPAddress ipAddress, CancellationToken cancellationToken)
139140
var timestamp = DateTime.Now;
140141

141142
// Send ping
142-
var pingReply = ping.Send(ipAddress, Timeout, Buffer, options);
143+
var pingReply = ping.Send(ipAddress, Timeout, buffer, options);
143144

144145
// Reset the error count (if no exception was thrown)
145146
errorCount = 0;
@@ -152,15 +153,8 @@ public void SendAsync(IPAddress ipAddress, CancellationToken cancellationToken)
152153
}
153154
else
154155
{
155-
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
156-
OnPingReceived(new PingReceivedArgs(
157-
new PingInfo(timestamp, pingReply.Address, hostname,
158-
pingReply.Buffer.Length, pingReply.RoundtripTime, pingReply.Options!.Ttl,
159-
pingReply.Status)));
160-
else
161-
OnPingReceived(new PingReceivedArgs(
162-
new PingInfo(timestamp, pingReply.Address, hostname,
163-
pingReply.Buffer.Length, pingReply.RoundtripTime, pingReply.Status)));
156+
OnPingReceived(new PingReceivedArgs(
157+
PingInfo.FromSuccessfulReply(timestamp, pingReply, hostname)));
164158
}
165159
}
166160
catch (PingException ex)

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,22 @@ public PingInfo(DateTime timestamp, IPAddress ipAddress, string hostname, int by
122122
Status = status;
123123
}
124124

125+
/// <summary>
126+
/// Creates a <see cref="PingInfo" /> from a successful ping reply. The TTL is only available for IPv4.
127+
/// </summary>
128+
/// <param name="timestamp">Timestamp when the ping was sent.</param>
129+
/// <param name="pingReply">Successful reply of the ping.</param>
130+
/// <param name="hostname">Hostname of the host.</param>
131+
/// <returns>Information of the ping.</returns>
132+
internal static PingInfo FromSuccessfulReply(DateTime timestamp, PingReply pingReply, string hostname = null)
133+
{
134+
return pingReply.Address.AddressFamily == AddressFamily.InterNetwork
135+
? new PingInfo(timestamp, pingReply.Address, hostname, pingReply.Buffer.Length, pingReply.RoundtripTime,
136+
pingReply.Options!.Ttl, pingReply.Status)
137+
: new PingInfo(timestamp, pingReply.Address, hostname, pingReply.Buffer.Length, pingReply.RoundtripTime,
138+
pingReply.Status);
139+
}
140+
125141
/// <summary>
126142
/// Timestamp when the ping was sent.
127143
/// </summary>

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

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Net;
33
using System.Net.NetworkInformation;
4-
using System.Net.Sockets;
54
using System.Threading;
65
using System.Threading.Tasks;
76

@@ -43,26 +42,7 @@ public static async Task<PingInfo> PingAsync(IPAddress ipAddress, int timeoutMs,
4342

4443
// Success
4544
if (pingReply is { Status: IPStatus.Success })
46-
{
47-
switch (ipAddress.AddressFamily)
48-
{
49-
case AddressFamily.InterNetwork:
50-
return new PingInfo(
51-
timestamp,
52-
pingReply.Address,
53-
pingReply.Buffer.Length,
54-
pingReply.RoundtripTime,
55-
pingReply.Options!.Ttl,
56-
pingReply.Status);
57-
case AddressFamily.InterNetworkV6:
58-
return new PingInfo(
59-
timestamp,
60-
pingReply.Address,
61-
pingReply.Buffer.Length,
62-
pingReply.RoundtripTime,
63-
pingReply.Status);
64-
}
65-
}
45+
return PingInfo.FromSuccessfulReply(timestamp, pingReply);
6646

6747
// Failed
6848
pingInfo = new PingInfo(timestamp, ipAddress, pingReply.Status);

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public sealed class PortScanner
1919
public PortScanner(PortScannerOptions options)
2020
{
2121
_options = options;
22+
_icmpBuffer = new byte[options.ICMPBufferSize];
2223
}
2324

2425
#endregion
@@ -69,6 +70,9 @@ public PortScanner(PortScannerOptions options)
6970

7071
private readonly PortScannerOptions _options;
7172

73+
// Data sent with the ping before the scan, created once (see PortScannerOptions.ICMPBufferSize)
74+
private readonly byte[] _icmpBuffer;
75+
7276
/// <summary>
7377
/// Gets the number of ports scanned so far. Thread-safe; may be read from any thread while the scan is
7478
/// running.
@@ -394,7 +398,7 @@ await Parallel.ForEachAsync(hosts, pingParallelOptions, async (host, ct) =>
394398
private async Task<bool> IsReachableAsync(IPAddress ipAddress, int portCount, CancellationToken cancellationToken)
395399
{
396400
var pingInfo = await PingProbe.PingAsync(ipAddress, _options.ICMPTimeout, _options.ICMPAttempts,
397-
_options.ICMPBuffer, cancellationToken).ConfigureAwait(false);
401+
_icmpBuffer, cancellationToken).ConfigureAwait(false);
398402

399403
if (pingInfo.Status == IPStatus.Success)
400404
return true;

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public record PortScannerOptions
3333

3434
/// <summary>
3535
/// 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.
36+
/// <see cref="ICMPBufferSize" />), a host that doesn't answer is not scanned.
3737
/// </summary>
3838
public required bool PingHostsBeforeScan { get; init; }
3939

@@ -49,9 +49,10 @@ public record PortScannerOptions
4949
public required int ICMPTimeout { get; init; }
5050

5151
/// <summary>
52-
/// Data sent with the ping before the scan (see <see cref="PingHostsBeforeScan" />).
52+
/// Size in bytes of the data sent with the ping before the scan (see <see cref="PingHostsBeforeScan" />). A size,
53+
/// not the buffer, so options with the same values are equal (records compare arrays by reference).
5354
/// </summary>
54-
public required byte[] ICMPBuffer { get; init; }
55+
public required int ICMPBufferSize { get; init; }
5556

5657
/// <summary>
5758
/// Resolve the hostname for an IP address.

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ public sealed class Traceroute
1717

1818
private readonly TracerouteOptions _options;
1919

20+
// Data sent with the ping, created once (see TracerouteOptions.BufferSize)
21+
private readonly byte[] _buffer;
22+
2023
#endregion
2124

2225
#region Constructor
2326

2427
public Traceroute(TracerouteOptions options)
2528
{
2629
_options = options;
30+
_buffer = new byte[options.BufferSize];
2731
}
2832

2933
#endregion
@@ -55,7 +59,7 @@ public void TraceAsync(IPAddress ipAddress, CancellationToken cancellationToken)
5559
{
5660
stopwatch.Start();
5761

58-
pingReply = ping.Send(ipAddress, _options.Timeout, _options.Buffer,
62+
pingReply = ping.Send(ipAddress, _options.Timeout, _buffer,
5963
new PingOptions { Ttl = i1, DontFragment = _options.DontFragment });
6064

6165
stopwatch.Stop();

0 commit comments

Comments
 (0)