Add DNS Lookup functionality to vmPing#113
Conversation
brunocarne
commented
Apr 13, 2026
- Introduced a new menu item for DNS Lookup in the main window.
- Added localized string for DNS Lookup in Strings.resx and Strings.Designer.cs.
- Created DnsLookupService class to handle DNS queries and responses.
- Implemented DnsLookupWindow for user interaction, including input for hostname/IP and displaying results.
- Integrated DNS Lookup functionality into the main application flow, allowing users to add DNS probes.
- Updated project file to include new DnsLookupWindow and DnsLookupService classes.
- Added icons and UI elements for DNS Lookup in the application.
- Introduced a new menu item for DNS Lookup in the main window. - Added localized string for DNS Lookup in Strings.resx and Strings.Designer.cs. - Created DnsLookupService class to handle DNS queries and responses. - Implemented DnsLookupWindow for user interaction, including input for hostname/IP and displaying results. - Integrated DNS Lookup functionality into the main application flow, allowing users to add DNS probes. - Updated project file to include new DnsLookupWindow and DnsLookupService classes. - Added icons and UI elements for DNS Lookup in the application.
There was a problem hiding this comment.
Pull request overview
Adds a DNS Lookup feature to vmPing, including a dedicated lookup window and a shared DNS lookup implementation used by both the UI and DNS probes.
Changes:
- Added a DNS Lookup command/menu item (Ctrl+D) and a new
DnsLookupWindowUI. - Introduced
DnsLookupServiceto perform forward/reverse lookups (multiple record types, multiple resolvers) and updated DNS probes to use it. - Updated resources (strings, icons) and project configuration (new files, target framework bump).
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| vmPing/vmPing.csproj | Bumps target framework and includes new DNS lookup UI/service files. |
| vmPing/Views/MainWindow.xaml | Adds DNS Lookup menu entry with icon. |
| vmPing/Views/MainWindow.xaml.cs | Wires command/binding and adds helper to start DNS probes from the lookup window. |
| vmPing/Views/DnsLookupWindow.xaml | New DNS Lookup window layout and controls. |
| vmPing/Views/DnsLookupWindow.xaml.cs | Implements lookup/cancel/copy/add-probe behavior. |
| vmPing/Classes/DnsLookupService.cs | New DNS-over-UDP resolver/formatter used by UI and probe. |
| vmPing/Classes/Probe-Dns.cs | Switches DNS probe execution to use DnsLookupService. |
| vmPing/ResourceDictionaries/Icons.xaml | Adds DNS icon resource. |
| vmPing/Properties/Strings.resx | Adds localized menu string for DNS Lookup. |
| vmPing/Properties/Strings.Designer.cs | Adds generated accessor for Menu_DnsLookup (doc comment issue noted). |
| README.md | Updates documentation to mention the new DNS Lookup entry point and enhanced output. |
Files not reviewed (1)
- vmPing/Properties/Strings.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var receiveTask = client.ReceiveAsync(); | ||
| var timeoutTask = Task.Delay(DefaultTimeoutMilliseconds, token); | ||
| var completed = await Task.WhenAny(receiveTask, timeoutTask).ConfigureAwait(false); | ||
|
|
||
| if (completed != receiveTask) | ||
| throw new TimeoutException(); |
There was a problem hiding this comment.
Cancellation is implemented via Task.Delay(..., token), but if token is canceled while waiting, timeoutTask completes as canceled and this code throws TimeoutException (so UI shows a timeout instead of a cancellation). After WhenAny, check token.IsCancellationRequested/token.ThrowIfCancellationRequested() before throwing TimeoutException, and consider actively closing the UdpClient on cancellation to stop ReceiveAsync promptly.
| var id = GenerateMessageId(); | ||
| var payload = BuildQuery(queryName, recordType, id); | ||
| var endpoint = new IPEndPoint(resolverAddress, 53); | ||
|
|
||
| using (var client = new UdpClient(resolverAddress.AddressFamily)) | ||
| { | ||
| client.Client.SendTimeout = DefaultTimeoutMilliseconds; | ||
| client.Client.ReceiveTimeout = DefaultTimeoutMilliseconds; | ||
|
|
||
| await client.SendAsync(payload, payload.Length, endpoint).ConfigureAwait(false); | ||
|
|
||
| var receiveTask = client.ReceiveAsync(); | ||
| var timeoutTask = Task.Delay(DefaultTimeoutMilliseconds, token); | ||
| var completed = await Task.WhenAny(receiveTask, timeoutTask).ConfigureAwait(false); | ||
|
|
||
| if (completed != receiveTask) | ||
| throw new TimeoutException(); | ||
|
|
||
| var result = await receiveTask.ConfigureAwait(false); | ||
| return ParseResponse(result.Buffer); |
There was a problem hiding this comment.
The generated DNS message ID (id) is never validated against the received response. Because UdpClient.ReceiveAsync() can return any datagram sent to the socket, this can misassociate responses (and also leaves ushort id = ... in ParseResponse unused). Parse the response ID and verify it matches the request ID before accepting the packet (otherwise keep waiting / treat as invalid).
| var reports = new List<DnsLookupReport>(); | ||
| foreach (var resolver in resolvers) | ||
| { | ||
| token.ThrowIfCancellationRequested(); | ||
| reports.Add(await QueryResolverAsync(trimmedInput, queryName, resolver, recordTypes, token).ConfigureAwait(false)); | ||
| } |
There was a problem hiding this comment.
DNS queries are executed sequentially across resolvers (and then sequentially per record type inside QueryResolverAsync). With 3+ resolvers and a 4s timeout, worst-case lookups can take a long time and make the UI feel hung. Consider running resolver queries in parallel (e.g., Task.WhenAll with a small concurrency limit) and aggregating results when all complete or cancellation occurs.
| if (resolvers.Count == 0) | ||
| resolvers.AddRange(PublicResolvers); |
There was a problem hiding this comment.
GetResolvers() always yields PublicResolvers (after local resolvers), so resolvers.Count will never be 0 here and this fallback block is effectively dead code. Either remove the if (resolvers.Count == 0) branch, or change GetResolvers() to only return local resolvers and keep the fallback logic here.
| if (resolvers.Count == 0) | |
| resolvers.AddRange(PublicResolvers); |
| private static async Task<DnsLookupReport> QueryResolverAsync(string originalInput, string queryName, DnsResolver resolver, IReadOnlyList<DnsRecordType> recordTypes, CancellationToken token) | ||
| { |
There was a problem hiding this comment.
originalInput is passed into QueryResolverAsync but never used. This makes the API misleading and suggests missing functionality (e.g., reporting the original input). Remove the parameter or use it in the report output.
|
|
||
| private enum DnsRecordType : ushort | ||
| { | ||
| A = 1, |
There was a problem hiding this comment.
There appears to be an embedded control character at the end of the private enum DnsRecordType : ushort line (shows up as ushort followed by a stray character). This can cause noisy diffs and can break some tooling/linters—please remove the non-printable character and ensure the file uses consistent line endings/encoding.
| existingProbe.Hostname = probeHostname; | ||
|
|
||
| var aliasKey = normalizedHost.ToLowerInvariant(); | ||
| existingProbe.Alias = null; | ||
| if (_Aliases.TryGetValue(aliasKey, out var aliasValue)) |
There was a problem hiding this comment.
aliasKey is derived from normalizedHost, which may still include the D/ prefix if the caller passes it. Since Probe.StartStop() strips the D/ prefix (and aliases are typically keyed by the raw host), this path will fail to apply an alias when host starts with D/. Consider deriving aliasKey from the unprefixed hostname (e.g., strip D/ before the lookup).
| cancellation?.Cancel(); | ||
| cancellation = new CancellationTokenSource(); | ||
| isLookupInProgress = true; |
There was a problem hiding this comment.
When starting a new lookup, the previous CancellationTokenSource is canceled but not disposed before being replaced. Over multiple lookups this can leak wait handles. Dispose the old CTS (e.g., capture to a local, cancel+dispose) before assigning a new one.
| /// <summary> | ||
| /// Looks up a localized string similar to Traceroute. | ||
| /// </summary> | ||
| /// <summary> | ||
| /// Looks up a localized string similar to DNS Lookup. |
There was a problem hiding this comment.
The XML doc comments here are malformed: there are two consecutive <summary> blocks ("Traceroute" then "DNS Lookup"), which leaves Menu_Traceroute without its own summary and can confuse doc generation tooling. Regenerate Strings.Designer.cs from the .resx or fix the comment blocks so each property has exactly one <summary>.
|
vmPing does already have a DNS lookup feature, unfortunately the docs are lacking. For the hostname, do I'll look over your commits next week to see what's added. Thanks! |