@@ -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