@@ -60,18 +60,32 @@ public async Task<CheckResult> PingAsync(Guid endpointId, string host, int timeo
6060
6161            try 
6262            { 
63-                 // Use overload that accepts cancellation token 
64-                 PingReply  reply  =  await  ping . SendPingAsync ( host ,  TimeSpan . FromMilliseconds ( timeoutMs ) ,  cancellationToken :  combinedCts . Token ) ; 
65-                 stopwatch . Stop ( ) ; 
63+                 PingReply  reply ; 
6664
67-                 if  ( reply . Status  ==  IPStatus . Success ) 
65+                 // Handle IPv6 link-local with scope 
66+                 if  ( IPAddress . TryParse ( host ,  out  var  ip ) ) 
6867                { 
69-                     return  CheckResult . Success ( endpointId ,  timestamp ,  reply . RoundtripTime ) ; 
68+                     if  ( ip . AddressFamily  ==  AddressFamily . InterNetworkV6  &&  host . Contains ( '%' ) ) 
69+                     { 
70+                         var  parts  =  host . Split ( '%' ) ; 
71+                         ip  =  IPAddress . Parse ( parts [ 0 ] ) ; 
72+                         ip . ScopeId  =  long . Parse ( parts [ 1 ] ) ; 
73+                     } 
74+ 
75+                     reply  =  await  ping . SendPingAsync ( ip ,  timeoutMs ) ; 
7076                } 
7177                else 
7278                { 
73-                     return  CheckResult . Failure ( endpointId ,  timestamp ,  $ "Ping failed: { reply . Status } ") ; 
79+                     // Hostname fallback 
80+                     reply  =  await  ping . SendPingAsync ( host ,  timeoutMs ) ; 
7481                } 
82+ 
83+                 stopwatch . Stop ( ) ; 
84+ 
85+                 if  ( reply . Status  ==  IPStatus . Success ) 
86+                     return  CheckResult . Success ( endpointId ,  timestamp ,  reply . RoundtripTime ) ; 
87+                 else 
88+                     return  CheckResult . Failure ( endpointId ,  timestamp ,  $ "Ping failed: { reply . Status } ") ; 
7589            } 
7690            catch  ( OperationCanceledException )  when  ( timeoutCts . Token . IsCancellationRequested  &&  ! cancellationToken . IsCancellationRequested ) 
7791            { 
@@ -105,12 +119,25 @@ public async Task<CheckResult> TcpConnectAsync(Guid endpointId, string host, int
105119            using  var  timeoutCts  =  new  CancellationTokenSource ( timeoutMs ) ; 
106120            using  var  combinedCts  =  CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ,  timeoutCts . Token ) ; 
107121
108-             // Use the combined cancellation token for the connection 
109-             Task  connectTask  =  tcpClient . ConnectAsync ( host ,  port ,  combinedCts . Token ) . AsTask ( ) ; 
110- 
111122            try 
112123            { 
113-                 await  connectTask ; 
124+                 // Handle IPv6 link-local with scope 
125+                 if  ( IPAddress . TryParse ( host ,  out  var  ip ) ) 
126+                 { 
127+                     if  ( ip . AddressFamily  ==  AddressFamily . InterNetworkV6  &&  host . Contains ( '%' ) ) 
128+                     { 
129+                         var  parts  =  host . Split ( '%' ) ; 
130+                         ip  =  IPAddress . Parse ( parts [ 0 ] ) ; 
131+                         ip . ScopeId  =  long . Parse ( parts [ 1 ] ) ; 
132+                     } 
133+ 
134+                     await  tcpClient . ConnectAsync ( ip ,  port ,  combinedCts . Token ) ; 
135+                 } 
136+                 else 
137+                 { 
138+                     await  tcpClient . ConnectAsync ( host ,  port ,  combinedCts . Token ) ; 
139+                 } 
140+ 
114141                stopwatch . Stop ( ) ; 
115142                return  CheckResult . Success ( endpointId ,  timestamp ,  stopwatch . ElapsedMilliseconds ) ; 
116143            } 
@@ -196,10 +223,28 @@ public async Task<CheckResult> HttpCheckAsync(Guid endpointId, string host, int
196223
197224    /// <summary> 
198225    /// Builds HTTP URL with proper protocol detection and validation. 
199-     /// Handles cases where host already contains protocol or needs port-based detection . 
226+     /// Handles IPv6 addresses, scope IDs, existing protocols, ports, and optional path . 
200227    /// </summary> 
201228    private  static string  BuildHttpUrl ( string  host ,  int  port ,  string ?  path ) 
202229    { 
230+         // Wrap IPv6 in brackets and escape scope IDs for URLs 
231+         if  ( IPAddress . TryParse ( host ,  out  var  ip )  &&  ip . AddressFamily  ==  AddressFamily . InterNetworkV6 ) 
232+         { 
233+             // Handle scope index (zone) 
234+             if  ( host . Contains ( '%' ) ) 
235+             { 
236+                 var  parts  =  host . Split ( '%' ) ; 
237+                 string  baseHost  =  parts [ 0 ] ; 
238+                 string  scope  =  parts [ 1 ] ; 
239+                 // Per RFC 6874: must escape "%" as "%25" in URLs 
240+                 host  =  $ "[{ baseHost } %25{ scope } ]"; 
241+             } 
242+             else 
243+             { 
244+                 host  =  $ "[{ host } ]"; 
245+             } 
246+         } 
247+ 
203248        string  url ; 
204249
205250        // Check if host already contains a protocol 
@@ -229,49 +274,32 @@ private static string BuildHttpUrl(string host, int port, string? path)
229274        else 
230275        { 
231276            // Host doesn't contain protocol, determine from port and context 
232-             string  scheme ; 
233- 
234277            // Use smart defaults: 443 and common HTTPS ports default to HTTPS, others to HTTP 
235-             if  ( port  ==  443  ||  IsCommonHttpsPort ( port ) ) 
236-             { 
237-                 scheme  =  "https" ; 
238-             } 
239-             else 
240-             { 
241-                 scheme  =  "http" ; 
242-             } 
243- 
244-             url  =  $ "{ scheme } ://{ host } "; 
278+             string  scheme  =  ( port  ==  443  ||  IsCommonHttpsPort ( port ) )  ?  "https"  :  "http" ; 
245279
246280            // Add port if not standard for the chosen protocol 
247281            int  standardPort  =  scheme  ==  "https"  ?  443  :  80 ; 
282+ 
283+             url  =  $ "{ scheme } ://{ host } "; 
248284            if  ( port  !=  standardPort ) 
249-             { 
250285                url  +=  $ ":{ port } "; 
251-             } 
252286        } 
253- 
254287        // Add path if specified 
255288        if  ( ! string . IsNullOrEmpty ( path ) ) 
256289        { 
257290            // Ensure URL ends with host/port and path starts with / 
258291            if  ( ! url . EndsWith ( "/" )  &&  ! path . StartsWith ( "/" ) ) 
259-             { 
260292                url  +=  "/" ; 
261-             } 
262293            else  if  ( url . EndsWith ( "/" )  &&  path . StartsWith ( "/" ) ) 
263-             { 
264-                 // Remove duplicate slash 
265-                 path  =  path . Substring ( 1 ) ; 
266-             } 
294+                 path  =  path . Substring ( 1 ) ;  // Remove duplicate slash 
267295
268296            url  +=  path ; 
269297        } 
270298
271299        // Validate the final URL 
272300        try 
273301        { 
274-             var   validationUri  =  new  Uri ( url ) ; 
302+             _  =  new  Uri ( url ) ; 
275303            return  url ; 
276304        } 
277305        catch  ( UriFormatException  ex ) 
0 commit comments