Skip to content

Commit 39d3778

Browse files
committed
feat: hostname dual support
1 parent 3032223 commit 39d3778

File tree

1 file changed

+61
-49
lines changed

1 file changed

+61
-49
lines changed

ThingConnect.Pulse.Server/Services/Monitoring/DiscoveryService.cs

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -174,62 +174,37 @@ public enum IPv6Format
174174
Full
175175
}
176176

177-
private IEnumerable<string> ExpandIPv6Host(string host, IPv6Format format = IPv6Format.Compressed)
178-
{
179-
string hostPart = host;
180-
string? zoneIndex = null;
181-
182-
if (host.Contains('%'))
183-
{
184-
var split = host.Split('%');
185-
hostPart = split[0];
186-
zoneIndex = split[1];
187-
}
188-
189-
if (!IPAddress.TryParse(hostPart, out var ip)) yield break;
190-
if (ip.AddressFamily != AddressFamily.InterNetworkV6) yield break;
191-
192-
string compressed = ip.ToString();
193-
194-
// Full form (always 8 groups of 4 hex digits)
195-
string full = string.Join(":", Enumerable.Range(0, 8)
196-
.Select(i => ((ushort)((ip.GetAddressBytes()[i * 2] << 8) |
197-
ip.GetAddressBytes()[i * 2 + 1])).ToString("x4")));
198-
199-
string ApplyZone(string addr) => zoneIndex != null ? $"{addr}%{zoneIndex}" : addr;
200-
201-
if (format == IPv6Format.Compressed)
202-
{
203-
yield return ApplyZone(compressed);
204-
}
205-
else if (format == IPv6Format.Full)
206-
{
207-
yield return ApplyZone(full);
208-
}
209-
}
210-
211177
public async Task<IEnumerable<string>> ResolveHostnameAsync(string hostname, CancellationToken cancellationToken = default)
212178
{
179+
var ipv4Addresses = new List<string>();
180+
var ipv6Addresses = new List<string>();
181+
213182
try
214183
{
215184
IPAddress[] addresses = await Dns.GetHostAddressesAsync(hostname);
216-
return addresses
217-
.Where(addr => addr.AddressFamily == AddressFamily.InterNetwork || addr.AddressFamily == AddressFamily.InterNetworkV6)
218-
.Select(addr =>
219-
{
220-
// Include zone index for IPv6 link-local
221-
if (addr.AddressFamily == AddressFamily.InterNetworkV6 && addr.IsIPv6LinkLocal && addr.ScopeId != 0)
222-
return addr + "%" + addr.ScopeId;
223185

224-
return addr.ToString();
225-
})
226-
.ToList();
186+
foreach (var addr in addresses)
187+
{
188+
if (addr.AddressFamily == AddressFamily.InterNetwork) // IPv4
189+
{
190+
ipv4Addresses.Add(addr.ToString());
191+
}
192+
else if (addr.AddressFamily == AddressFamily.InterNetworkV6) // IPv6
193+
{
194+
string address = addr.IsIPv6LinkLocal && addr.ScopeId != 0
195+
? addr + "%" + addr.ScopeId
196+
: addr.ToString();
197+
ipv6Addresses.Add(address);
198+
}
199+
}
227200
}
228201
catch (Exception ex)
229202
{
230203
_logger.LogWarning(ex, "Failed to resolve hostname: {Hostname}", hostname);
231-
return Enumerable.Empty<string>();
232204
}
205+
206+
// Always return both IPv4 and IPv6 if available
207+
return ipv6Addresses.Concat(ipv4Addresses);
233208
}
234209

235210
public async Task<IEnumerable<Data.Endpoint>> ExpandTargetsAsync(IEnumerable<dynamic> configTargets,
@@ -257,6 +232,40 @@ public async Task<IEnumerable<string>> ResolveHostnameAsync(string hostname, Can
257232
return endpoints;
258233
}
259234

235+
private IEnumerable<string> ExpandIPv6Host(string host, IPv6Format format = IPv6Format.Compressed)
236+
{
237+
string hostPart = host;
238+
string? zoneIndex = null;
239+
240+
if (host.Contains('%'))
241+
{
242+
var split = host.Split('%');
243+
hostPart = split[0];
244+
zoneIndex = split[1];
245+
}
246+
247+
if (!IPAddress.TryParse(hostPart, out var ip)) yield break;
248+
if (ip.AddressFamily != AddressFamily.InterNetworkV6) yield break;
249+
250+
string compressed = ip.ToString();
251+
252+
// Full form (always 8 groups of 4 hex digits)
253+
string full = string.Join(":", Enumerable.Range(0, 8)
254+
.Select(i => ((ushort)((ip.GetAddressBytes()[i * 2] << 8) |
255+
ip.GetAddressBytes()[i * 2 + 1])).ToString("x4")));
256+
257+
string ApplyZone(string addr) => zoneIndex != null ? $"{addr}%{zoneIndex}" : addr;
258+
259+
if (format == IPv6Format.Compressed)
260+
{
261+
yield return ApplyZone(compressed);
262+
}
263+
else if (format == IPv6Format.Full)
264+
{
265+
yield return ApplyZone(full);
266+
}
267+
}
268+
260269
private async Task<IEnumerable<Data.Endpoint>> ExpandSingleTargetAsync(dynamic target,
261270
CancellationToken cancellationToken)
262271
{
@@ -270,25 +279,28 @@ public async Task<IEnumerable<string>> ResolveHostnameAsync(string hostname, Can
270279

271280
if (IPAddress.TryParse(host, out var ip))
272281
{
273-
// Expand IPv6 host into compressed and full forms
274282
if (ip.AddressFamily == AddressFamily.InterNetworkV6)
283+
{
275284
hosts.AddRange(ExpandIPv6Host(host));
285+
}
276286
else
287+
{
277288
hosts.Add(host); // IPv4
289+
}
278290
}
279291
else
280292
{
281293
// Hostname
282294
IEnumerable<string> resolvedHosts = await ResolveHostnameAsync(host, cancellationToken);
283-
hosts.AddRange(resolvedHosts.SelectMany(resolvedHost =>
295+
hosts.AddRange(resolvedHosts.Select(resolvedHost =>
284296
{
285297
if (IPAddress.TryParse(resolvedHost, out var resolvedIp) &&
286298
resolvedIp.AddressFamily == AddressFamily.InterNetworkV6)
287299
{
288-
return ExpandIPv6Host(resolvedHost);
300+
return ExpandIPv6Host(resolvedHost, IPv6Format.Compressed);
289301
}
290302
return new[] { resolvedHost };
291-
}));
303+
}).SelectMany(x => x));
292304
}
293305
}
294306
else if (target.cidr != null)

0 commit comments

Comments
 (0)