@@ -18,66 +18,67 @@ public class DNSClient : SingletonBase<DNSClient>
1818 private const string NotConfiguredMessage = "DNS client is not configured. Call Configure() first." ;
1919
2020 /// <summary>
21- /// Hold the current instance of the LookupClient.
21+ /// Immutable snapshot of everything a resolve call needs (lookup client + settings it was configured
22+ /// with), published as a single reference so concurrent resolves during a <see cref="Configure" />
23+ /// call never observe a torn combination (e.g. the old suffix flag with the new lookup client).
24+ /// Relies on <see cref="DNSClientSettings" /> not being mutated after being passed to
25+ /// <see cref="Configure" /> - see the note on <see cref="DNSClientSettings" /> itself.
2226 /// </summary>
23- private LookupClient _client ;
27+ private sealed record ResolverState ( LookupClient Client , bool AddSuffix , DNSClientSettings Settings ) ;
2428
2529 /// <summary>
2630 /// Indicates if the DNS client is configured.
2731 /// </summary>
2832 private bool _isConfigured ;
2933
3034 /// <summary>
31- /// Store the current DNS settings .
35+ /// Current resolver state (lookup client + DNS suffix behavior), swapped atomically on configure .
3236 /// </summary>
33- private DNSClientSettings _settings ;
37+ private ResolverState _state ;
3438
3539 /// <summary>
3640 /// Method to configure the DNS client.
3741 /// </summary>
3842 /// <param name="settings"></param>
3943 public void Configure ( DNSClientSettings settings )
4044 {
41- _settings = settings ;
42-
4345 Log . Debug ( "Configure - Configuring DNS client..." ) ;
4446
45- if ( _settings . UseCustomDNSServers )
47+ LookupClient client ;
48+
49+ if ( settings . UseCustomDNSServers )
4650 {
4751 Log . Debug ( "Configure - Using custom DNS servers..." ) ;
4852
4953 // Setup custom DNS servers
5054 List < NameServer > servers = [ ] ;
5155
52- foreach ( var ( server , port ) in _settings . DNSServers )
56+ foreach ( var ( server , port ) in settings . DNSServers )
5357 {
5458 Log . Debug ( $ "Configure - Adding custom DNS server: { server } :{ port } ") ;
5559 servers . Add ( new IPEndPoint ( IPAddress . Parse ( server ) , port ) ) ;
5660 }
5761
5862 Log . Debug ( "Configure - Creating LookupClient with custom DNS servers..." ) ;
59- _client = new LookupClient ( new LookupClientOptions ( servers . ToArray ( ) ) ) ;
63+ client = new LookupClient ( new LookupClientOptions ( [ .. servers ] ) ) ;
6064 }
6165 else
6266 {
6367 Log . Debug ( "Configure - Creating LookupClient with Windows default DNS servers..." ) ;
64- _client = new LookupClient ( ) ;
68+ client = new LookupClient ( ) ;
6569 }
6670
71+ var addSuffix = settings . AddDNSSuffix && ! string . IsNullOrEmpty ( settings . DNSSuffix ) ;
72+ Log . Debug ( addSuffix
73+ ? $ "Configure - DNS suffix will be added to hostnames without a dot: { settings . DNSSuffix } "
74+ : "Configure - DNS suffix will NOT be added to hostnames without a dot." ) ;
75+
76+ _state = new ResolverState ( client , addSuffix , settings ) ;
77+
6778 Log . Debug ( "Configure - DNS client configured." ) ;
6879 _isConfigured = true ;
6980 }
7081
71- /// <summary>
72- /// Method to update the (Windows) name servers of the DNS client
73- /// when they may have changed due to a network update.
74- /// </summary>
75- public void UpdateWindowsDNSSever ( )
76- {
77- Log . Debug ( "UpdateWindowsDNSSever - Recreating LookupClient with with Windows default DNS servers..." ) ;
78- _client = new LookupClient ( ) ;
79- }
80-
8182 /// <summary>
8283 /// Resolve an IPv4 address from a hostname or FQDN.
8384 /// </summary>
@@ -88,9 +89,13 @@ public async Task<DNSClientResultIPAddress> ResolveAAsync(string query)
8889 if ( ! _isConfigured )
8990 throw new DNSClientNotConfiguredException ( NotConfiguredMessage ) ;
9091
92+ var state = _state ;
93+
94+ query = AddDNSSuffixIfConfigured ( query , state ) ;
95+
9196 try
9297 {
93- var result = await _client . QueryAsync ( query , QueryType . A ) ;
98+ var result = await state . Client . QueryAsync ( query , QueryType . A ) ;
9499
95100 // Pass the error we got from the lookup client (dns server).
96101 // NXDOMAIN is not a real failure like a timeout - flag it via IsNotFound so callers can
@@ -131,9 +136,13 @@ public async Task<DNSClientResultIPAddress> ResolveAaaaAsync(string query)
131136 if ( ! _isConfigured )
132137 throw new DNSClientNotConfiguredException ( NotConfiguredMessage ) ;
133138
139+ var state = _state ;
140+
141+ query = AddDNSSuffixIfConfigured ( query , state ) ;
142+
134143 try
135144 {
136- var result = await _client . QueryAsync ( query , QueryType . AAAA ) ;
145+ var result = await state . Client . QueryAsync ( query , QueryType . AAAA ) ;
137146
138147 // Pass the error we got from the lookup client (dns server).
139148 // NXDOMAIN is not a real failure like a timeout - flag it via IsNotFound so callers can
@@ -176,7 +185,7 @@ public async Task<DNSClientResultString> ResolveCnameAsync(string query)
176185
177186 try
178187 {
179- var result = await _client . QueryAsync ( query , QueryType . CNAME ) ;
188+ var result = await _state . Client . QueryAsync ( query , QueryType . CNAME ) ;
180189
181190 // Pass the error we got from the lookup client (dns server).
182191 // NXDOMAIN is not a real failure like a timeout - flag it via IsNotFound so callers can
@@ -219,7 +228,7 @@ public async Task<DNSClientResultString> ResolvePtrAsync(IPAddress ipAddress)
219228
220229 try
221230 {
222- var result = await _client . QueryReverseAsync ( ipAddress ) ;
231+ var result = await _state . Client . QueryReverseAsync ( ipAddress ) ;
223232
224233 // Pass the error we got from the lookup client (dns server).
225234 // NXDOMAIN is always a clean "no record". For private/ULA IP ranges (the common case for
@@ -255,6 +264,20 @@ public async Task<DNSClientResultString> ResolvePtrAsync(IPAddress ipAddress)
255264 }
256265 }
257266
267+ /// <summary>
268+ /// Appends the configured DNS suffix to a bare hostname (forward lookups only).
269+ /// FQDNs and IP literals are returned unchanged.
270+ /// </summary>
271+ /// <param name="query">Hostname, FQDN, or IP address as string like "example.com".</param>
272+ /// <param name="state">Resolver state snapshot captured at the start of the resolve call.</param>
273+ /// <returns>Query with the DNS suffix appended, if configured and applicable.</returns>
274+ private static string AddDNSSuffixIfConfigured ( string query , ResolverState state )
275+ {
276+ return state . AddSuffix && DNSClientHelper . IsBareHostname ( query )
277+ ? $ "{ query } .{ state . Settings . DNSSuffix } "
278+ : query ;
279+ }
280+
258281 /// <summary>
259282 /// Determines whether a DNS response code means "no record" rather than a real failure.
260283 /// NXDOMAIN is always a clean "does not exist". SERVFAIL and REFUSED are only treated as
0 commit comments