Repository navigation
Feature: Improve IP / Port Scanner and various fixes / improvements - #3660
Open
BornToBeRoot wants to merge 53 commits into
Open
BornToBeRoot wants to merge 53 commits into
BornToBeRoot wants to merge 53 commits into
Conversation
Replace the positional constructors (14 / 6 parameters) of IPScannerOptions and PortScannerOptions with init-only properties set via object initializer. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The per-host lambda in ScanAsync is split into DiscoverAsync (ping, port scan and NetBIOS concurrently -> reachable) and EnrichAsync (DNS PTR, MAC address and vendor -> result), with ResolveHostnameAsync / ResolveMACAddressAsync as own methods. Prepares progressive results (enrich as soon as a host answers). No behaviour change. Port Scanner: nothing to split (per port probe -> result, PTR already runs concurrently). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PTR record is fully qualified (e.g. "server.example.com."). The IP Scanner only trimmed it for custom commands and "Add profile", so the grid, the export and other features (DNS Lookup, Remote Desktop, ...) got the hostname with the trailing dot. It is now trimmed once when the result is created, in the IP Scanner and the Port Scanner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DNS hostname (without the NetBIOS fallback of Hostname) was only exported to JSON. Add it to CSV and XML, right after Hostname (JSON moved to the same position). Port Scanner: no separate DNS hostname, nothing to change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n scan (C5) Columns, Copy menu entries and the row details (IP Scanner) were bound to the current settings. After changing a setting the shown results got columns without data (e.g. MAC address enabled after the scan) or lost columns with data, the row details showed "Port scan is disabled" for scanned ports. The view models keep the options of the shown scan (ScanOptions, until the first scan the settings when the tab was opened) and the views bind to them via the BindingProxy (DataGrid columns are not in the visual tree). Port Scanner: Hostname column and Copy > Hostname. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ner) Split the combined "IP Scanner / Port Scanner" section and the "also in the ..." / "the same applies to ..." remarks into separate entries in the section of each tool (IP Scanner, Port Scanner, Ping Monitor, Traceroute). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trailing dot was only trimmed in the IP Scanner and Port Scanner (C2). Ping Monitor, Traceroute, Connections and the Network Connection widget still showed "server.example.com.". Trim it once in DNSClient.ResolvePtrAsync instead and remove the per-tool trims (IP Scanner, Port Scanner, Ping Monitor title / notification). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trimming the trailing dot turned a single-label PTR name like "gateway." into "gateway". DNSClientHelper.IsBareHostname treats that as a bare hostname, so with "Add DNS suffix" enabled, DNS Lookup and every other tool that resolves it (e.g. when opened from the IP Scanner) queried "gateway.<suffix>" instead. New DNSClientHelper.TrimTrailingDot only trims fully qualified names with more than one label, used by DNSClient.ResolvePtrAsync. Unit tests added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since C5 the columns, Copy menu and row details were a snapshot of the settings when the tab was opened until the first scan - before, they followed the settings live. While no results are shown (and no scan is running), the view models now refresh ScanOptions when a setting that affects them changes (IP Scanner: resolve hostname, port scan, NetBIOS, MAC address; Port Scanner: resolve hostname). The snapshot no longer allocates the ICMP buffer (up to 65500 bytes per tab), it is only created for a scan. The settings handler is removed when the tab is closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With init properties (R1) a forgotten option silently became 0 / false (e.g. MaxHostThreads = 0 -> Parallel.ForEachAsync throws), the positional constructor caught this at compile time. Mark all options of IPScannerOptions and PortScannerOptions as required. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C4 inserted the DNSHostname column after Hostname, which moved every following column by one - scripts / spreadsheets that read the CSV by position would read the wrong values. Add it as last column instead (XML and JSON are read by name and keep it after Hostname). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IsReachable was a computed property that went through the port list again on every read (about 4 times per host). Compute it once in the initializer like IsAnyPortOpen. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ently (review #7) EnrichAsync ran the PTR lookup and the MAC address lookup (neighbor table, local interfaces, OUI) one after the other, although they don't depend on each other. Start both and await them together - a host is finished after the slower one instead of after both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iew A) Since the PTR name keeps the dot of a single-label name (review #1, so other tools don't append the DNS suffix), the removed per-tool trims (C2, review #3) made custom commands ({{Hostname}}), the profile name of "Add profile" and the Ping Monitor title / notification use "gateway." instead of "gateway" (main). For custom commands this is more than cosmetic: Windows resolves a name with a trailing dot via DNS only (no suffix search, no LLMNR / NetBIOS). Trim the dot again in these places (display / external programs); the value passed to other NETworkManager tools keeps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScanOptions kept the full IPScannerOptions of the last scan for the lifetime of the tab (ports array, ICMP buffer - up to ~320 KB for 1-65535 / 65500 bytes) only to read four flags, and CreateScanOptions(null) created options for display that claimed a port scan without ports. New IPScannerResultOptions record with the four flags the view binds to (hostname, port scan, NetBIOS, MAC address), created from the options of the scan or from the settings. CreateScanOptions is only used for a scan again. Port Scanner unchanged: PortScannerOptions only holds six scalar values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScanOptions only followed the settings if a setting changed while no results were shown. An empty result could still keep the columns of the previous scan: Results.Clear() ran long before the options of the new scan were set, and a scan that was canceled during the preparation or found nothing didn't update them either. The handler also listed the relevant settings by hand (easy to miss when a column is added). UpdateScanOptionsIfNoResults is called after the results are cleared, when a scan is complete and on any settings change (IP Scanner + Port Scanner). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew C) IPScanner and PortScanner had an identical private ResolveHostnameAsync (PTR lookup, stop waiting on cancellation, empty string on error). Replace both with DNSClient.ResolvePtrOrEmptyAsync. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HostDiscovery caches IsAnyPortOpen / IsReachable at construction. As a positional record, a "with" expression would copy these cached values instead of recomputing them, and the generated equality / ToString members are unused. Use a private sealed class with read-only properties. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EnrichAsync checked discovery.NetBIOSInfo with ?. three times, but it is never null (an empty NetBIOSInfo if NetBIOS is disabled or there was no answer) and HostDiscovery.IsReachable already dereferences it. Keep the ?? fallbacks for its values, which may be null. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CSV export got DNSHostname as last column (review #5), but the XML export still inserted it after Hostname (moving the following elements) and the JSON export moved the existing property next to Hostname. XML: last element, JSON: back to its previous position - no existing column / element / property changes its position compared to the last release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3, step 23) IPScannerHostInfo is observable (PropertyChangedBase, setters internal to the model). Hostname / MACAddress / Vendor are computed from their sources (DNS -> NetBIOS, neighbor table -> NetBIOS), so a value that arrives later updates them. The scanner raises HostFound right after the discovery (ping, port scan, NetBIOS) and the DNS hostname and MAC address / vendor as HostUpdated when they arrive (replaces HostScanned). The scanner never changes a host itself: the view model buffers HostFound and HostUpdated in one list (order kept, an update always after its host) and applies them on the UI thread in the existing 150 ms flush. A host is only finished (progress) when all its lookups are finished, and ScanComplete is raised only after every host body returned - the view model flushes the remaining updates before the scan ends. Events of a scanner other than the running one are ignored and logged (guard only, can't happen). Export copies the rows on the UI thread (Clone), since they may still change during a scan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…4, P9) ScanHostAsync replaces DiscoverAsync / HostDiscovery: ping, port scan and NetBIOS run concurrently and are processed as they finish (Task.WhenAny). The port scan signals the first answering port (open or refused) right away. On the first answer the host is raised in HostFound - with placeholders for the probes that are still running (IsPingPending / IsPortScanPending / IsNetBIOSPending) - counted as up, and the DNS hostname and MAC address lookups start (P9). Every probe that finishes afterwards is raised as HostUpdated. Unreachable hosts are counted / raised (show unreachable) when all probes are finished. A host still only finishes (progress, and the scan with it) when every probe and lookup is finished: a finally waits for all of them, also when a probe was canceled or failed. View: Ping status, Port status and NetBIOS reachable show "..." while their probe is running (new PendingToStringConverter); the ping details and "No open ports found" are hidden until the ping / port scan is finished. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The view models stored the progress value of the ProgressChanged event args. Events of different hosts / ports raised at the same time can arrive in a different order, so an older (smaller) value could overwrite a newer one - also the last one, so the result could end with e.g. 255 of 256 hosts scanned. Step 24 raises ProgressChanged also when a host is found, which makes this more likely. The scanners expose HostsScanned / PortsScanned; the view models read all counters from the scanner on every event and once more in ScanComplete (final values). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New Time column after Ping status with the round trip time of the ping reply
("-/-" without a reply, "..." while the ping is running - optional third value
of PingTimeToStringConverter, existing users unchanged). Sorting by time uses a
custom comparer: hosts without a ping reply (time 0) are always last, in both
directions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tools (phase 3, step 27, C1)
The hostname shown in the result falls back to the NetBIOS computer name. That
name was passed to other tools (Port Scanner, Remote Desktop, PuTTY, ...) and to
DNS Lookup / RDAP > Hostname, where it may not be resolvable via DNS. Other
tools now get the DNS hostname, otherwise the IP address; DNS lookup / RDAP >
Hostname are only enabled with a DNS hostname. Custom commands keep
{{Hostname}} = shown hostname (documented).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ep 28, C3) The context menu for multiple selected results only had Export. New Copy submenu (same strings and style as the single-row Copy menu), one value per line in the order shown in the grid, without duplicates: - IP Scanner: IP address, Hostname, MAC address (visibility like the single-row menu / the columns of the shown scan) - Port Scanner: IP address, Hostname, Port, IP address:Port (IPEndPoint format, IPv6 in brackets) The grid order helper is shared with the export (GetSelectedResultsInGridOrder). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Search box right of the Result header (SearchTextBox, width 250, delayed filter like the Firewall - GlobalStaticConfiguration.SearchDispatcherTimerTimeSpan): - IP Scanner: IP address, hostname, MAC address, vendor, shown ports (number or service). Live filtering on Hostname / MACAddress / Vendor / Ports, so a host found before its hostname or MAC address arrived is filtered again. - Port Scanner: IP address, hostname, port, protocol, service, description, status (also translated). Results don't change after they were added. Enter in the search box filters right away (ApplySearch) and is handled, so it doesn't trigger the scan button (IsDefault) and clear the results. Counters always count all hosts / ports; Export > all exports the filtered results (documented). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le (phase 3, step 30, M2) IP Scanner context menu: - Web Console: opens the host (DNS hostname, otherwise the IP address - C1) via https if 443 is open, otherwise the first open of 80 / 8443 / 8080, http on the default port if none is open or the port scan is still running/disabled. - Wake on LAN: sends the magic packet to the MAC address (WakeOnLANRequest), only enabled with a MAC address. Port Scanner: Web Console for an open web port (80, 443, 8080, 8443). New WebConsoleUrlHelper (URL from host + ports: scheme, default ports left out, IPv6 in brackets with an encoded zone id) with unit tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ep 31, M4) Context menu Rescan (single and multiple rows, disabled while a scan runs), new string "Rescan". IP Scanner: scans the IP addresses of the selected rows again with the options of the shown scan (kept as _lastScanOptions, so the columns stay valid) and new option ReportAllHosts (every host is raised, also if unreachable now, without showing all ports like ShowAllResults). The scanner creates new results; their values are copied into the existing rows (IPScannerHostInfo.CopyFrom, selection and expanded details stay) and their updates are applied to these rows (IPScannerHostUpdatedArgs.ApplyTo). Rows are matched by the new scanned IPAddress of IPScannerHostInfo (PingInfo has the reply address). Up / down are counted from the rows during the rescan, the progress of the last scan is restored afterwards. Port Scanner: the rows of the selected hosts are removed and scanned again with the ports and options of the last scan; open / closed = counters of the other hosts + the ones of the rescan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g Monitor (phase 3, step 33, T1)
The counters were built from the number and a separate word ("{0} {1}" with
Strings.Up / Down / Open / Closed / Paused), so translations couldn't change
the word order. New strings XUp / XDown / XOpen / XClosed / XPaused ("{0} Up"
etc., same English text) used as StringFormat in the IP Scanner and Port
Scanner and as format of PingMonitorGroupSummaryConverter (Ping Monitor group
header). The old strings stay (used elsewhere).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Starting a scan crashed with ArgumentNullException (Parameter 'key') in
PropertyChangedEventManager.AddListener. Since step 23 IPScannerHostInfo
implements INotifyPropertyChanged, so WPF registers a PropertyChanged listener
for every binding path part on it - with the name the path parser stored
(SourceValueInfo.propertyName). For a typed path part like
"(network:IPScannerHostInfo.IsPingPending)" that name is only set if WPF can
resolve the owner type early while parsing the path; in templates / child
bindings of a MultiBinding it is resolved later, the stored name stays null
(verified in the decompiled PropertyPath / PropertyPathWorker of WPF 10.0.13).
Before, the source wasn't INotifyPropertyChanged and the name was never used.
Use plain paths for bindings on IPScannerHostInfo (e.g. "IsPingPending",
"PingInfo.Status") and for the view model via the BindingProxy
("Data.ScanOptions.ResolveHostname", "Data.SelectedResult.DNSHostname",
"Data.IsRunning") in the IP Scanner and Port Scanner views. Typed paths on
PortScannerPortInfo (not INotifyPropertyChanged) are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the rule behind the IP Scanner crash fix to the feature-review conventions: typed binding paths "(ns:Type.Property)" on items that raise PropertyChanged can crash (null property name for the PropertyChanged listener). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New attached properties (NETworkManager.Utilities.WPF): HandleEnter handles Enter in a search box (PreviewKeyDown), so it doesn't trigger the default button of the view / dialog / window (e.g. start a scan, close a dialog with OK - IsDefault is window wide, so also the application search of the main window could trigger the default button of the shown tool). EnterCommand runs a command first, e.g. to apply a delayed search right away. Used per tool in the next commits; convention added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the PreviewKeyDown handler in the view (step 29) with SearchTextBoxHelper.EnterCommand bound to the new ApplySearchCommand (filter right away). Same behaviour: Enter filters and doesn't start a scan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the PreviewKeyDown handler in the view (step 29) with SearchTextBoxHelper.EnterCommand bound to the new ApplySearchCommand (filter right away). Same behaviour: Enter filters and doesn't start a scan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Enter in the search box applies the delayed search right away (SearchTextBoxHelper.EnterCommand -> ApplySearchCommand) and is handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Profiles: Enter in the search box applies the delayed search right away (SearchTextBoxHelper.EnterCommand -> ApplySearchCommand) and is handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Enter in the search box of the import result dialog triggered the default button (import). It now applies the delayed search right away (SearchTextBoxHelper.EnterCommand -> ApplySearchCommand) and is handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The search filtered on every key press (the connection list can be long and is refreshed automatically). Filter after the shared delay while typing (GlobalStaticConfiguration.SearchDispatcherTimerTimeSpan, like the Firewall); Enter applies it right away (SearchTextBoxHelper.EnterCommand -> ApplySearchCommand) and is handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The search filtered on every key press. Filter after the shared delay while typing (GlobalStaticConfiguration.SearchDispatcherTimerTimeSpan, like the Firewall); Enter applies it right away (SearchTextBoxHelper.EnterCommand -> ApplySearchCommand) and is handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The search filtered on every key press. Filter after the shared delay while typing (GlobalStaticConfiguration.SearchDispatcherTimerTimeSpan, like the Firewall); Enter applies it right away (SearchTextBoxHelper.EnterCommand -> ApplySearchCommand) and is handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… away The search filtered on every key press. Filter after the shared delay while typing (GlobalStaticConfiguration.SearchDispatcherTimerTimeSpan, like the Firewall); Enter applies it right away (SearchTextBoxHelper.EnterCommand -> ApplySearchCommand) and is handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The search filtered on every key press. Filter after the shared delay while typing (GlobalStaticConfiguration.SearchDispatcherTimerTimeSpan, like the Firewall); Enter applies it right away (SearchTextBoxHelper.EnterCommand -> ApplySearchCommand) and is handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A default button (IsDefault) is window wide: Enter in the application search of the main window could trigger the default button of the shown tool (e.g. start a scan). SearchTextBoxHelper.HandleEnter handles it, the list is filtered while typing as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SearchTextBoxHelper.HandleEnter on the settings search, so Enter never triggers a default button there (consistent with the other search boxes). No visible change, the list is filtered while typing as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SearchTextBoxHelper.HandleEnter on the search of Settings > Language, so Enter never triggers a default button there (consistent with the other search boxes). No visible change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Enter in the search box triggered the default button (OK) and closed the dialog with the current selection. SearchTextBoxHelper.HandleEnter handles it, the list is filtered while typing as before. Enter in the list still selects (KeyBinding on the DataGrid). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Enter in the search box triggered the default button (OK) and closed the dialog with the current selection. SearchTextBoxHelper.HandleEnter handles it, the list is filtered while typing as before. Enter in the list still selects (KeyBinding on the DataGrid). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Align the SearchTextBoxHelper attribute with the other attributes of the TextBox (formatting of deffdb7). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…otype) With a visible star column (Vendor, Width="*") the DataGrid only lets a column grow by taking the width from the columns to its right (down to their MinWidth, DataGridColumnCollection.RecomputeColumnWidthsOnColumnPositiveResize); the rest is dropped, so a column couldn't be made wider than the grid (also not with a double-click on the gripper) unless there was free space. New DataGridHelper.FillLastColumn: the last visible column fills the remaining width with a pixel width (max(base width, available width - other visible columns); base = MinWidth or the width the user resized it to), updated on size / viewport / column width / visibility changes, previous fill column restored when another one becomes the last. No star column -> columns grow freely, the grid scrolls horizontally. Used in the IP Scanner instead of Width="*" and RefreshStarColumnsOnFirstRow (not needed without a star column). Prototype - roll out to the other tools after testing on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Roll out DataGridHelper.FillRemainingWidth (prototype in the IP Scanner, renamed from FillLastColumn) to every DataGrid with a star column: the star width is removed, the grid fills the remaining width with a pixel width - the column marked with the new DataGridHelper.IsFillColumn (if the star column was not the last one, e.g. Traceroute hostname, Firewall, DNS Lookup, About) or the last visible column. Grids with several star columns (port / OID profiles): the widest one fills, the others get a MinWidth. Fill columns without MinWidth get MinWidth 150. RefreshStarColumnsOnFirstRow removed (no star columns left). Convention added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Tick the box to add this pull request to the merge queue (same as
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes proposed in this pull request
Related issue(s)
To-Do
Contributing
By submitting this pull request, I confirm the following: