Skip to content

Commit 98b4071

Browse files
nagilsonCopilot
andcommitted
Fix isOnline() to fall back to HTTP HEAD when DNS fails behind proxy
In enterprise proxy environments, the client machine may not resolve external DNS directly — DNS resolution is handled by the proxy server. The existing DNS-only check in isOnline() would incorrectly report the machine as offline, causing unnecessary fallback to cached/offline installs. When DNS resolution fails and the axios client is available, isOnline() now attempts a lightweight HEAD request to www.microsoft.com through the auto-detected (or manually configured) proxy. This ensures connectivity is correctly detected in proxy environments while keeping DNS as the fast-path for non-proxy setups. Refactors GetProxyAgentIfNeeded by extracting proxy resolution into a standalone getProxyAgent method that does not depend on IAcquisitionWorkerContext. This allows isOnline and other callers without a full context to reuse the same proxy detection logic without duplication. Fixes #2594 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6805569 commit 98b4071

1 file changed

Lines changed: 57 additions & 12 deletions

File tree

vscode-dotnet-runtime-library/src/Utils/WebRequestWorkerSingleton.ts

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export class WebRequestWorkerSingleton
281281
// ... 100 ms is there as a default to prevent the dns resolver from throwing a runtime error if the user sets timeoutSeconds to 0.
282282

283283
const dnsResolver = new dns.promises.Resolver({ timeout: expectedDNSResolutionTimeMs });
284-
const couldConnect = await dnsResolver.resolve(microsoftServerHostName).then(() =>
284+
const dnsOnline = await dnsResolver.resolve(microsoftServerHostName).then(() =>
285285
{
286286
return true;
287287
}).catch((error: any) =>
@@ -290,7 +290,46 @@ export class WebRequestWorkerSingleton
290290
return false;
291291
});
292292

293-
return couldConnect;
293+
if (dnsOnline)
294+
{
295+
return true;
296+
}
297+
298+
// DNS failed — but in proxy environments, the proxy handles DNS resolution, so direct DNS lookups may fail
299+
// even though HTTP connectivity works fine. Fall back to a lightweight HEAD request through the proxy-configured client.
300+
if (this.client)
301+
{
302+
const httpFallbackTimeoutMs = Math.max(timeoutSec * 1000, 2000);
303+
const proxyAgent = await this.getProxyAgent(undefined, eventStream);
304+
305+
const headOptions: object = {
306+
timeout: httpFallbackTimeoutMs,
307+
cache: false,
308+
...(proxyAgent !== null && { proxy: false }),
309+
...(proxyAgent !== null && { httpsAgent: proxyAgent }),
310+
};
311+
312+
const headOnline = await this.client.head(`https://${microsoftServerHostName}`, headOptions)
313+
.then((response) =>
314+
{
315+
return response.status >= 200 && response.status < 500; // Any server response means we're online
316+
})
317+
.catch(() =>
318+
{
319+
return false;
320+
});
321+
322+
if (headOnline)
323+
{
324+
eventStream.post(new OfflineDetectionLogicTriggered(new EventCancellationError('DnsFailedButHttpSucceeded',
325+
`DNS resolution failed but HTTP HEAD request succeeded. This may indicate a proxy is handling DNS.`),
326+
`DNS failed but HTTP connectivity confirmed via HEAD request to ${microsoftServerHostName}.`));
327+
}
328+
329+
return headOnline;
330+
}
331+
332+
return false;
294333
}
295334
/**
296335
*
@@ -321,11 +360,22 @@ export class WebRequestWorkerSingleton
321360
}
322361

323362
private async GetProxyAgentIfNeeded(ctx: IAcquisitionWorkerContext): Promise<HttpsProxyAgent<string> | null>
363+
{
364+
return this.getProxyAgent(ctx.proxyUrl, ctx.eventStream);
365+
}
366+
367+
/**
368+
* Resolves a proxy agent from the manual proxy URL or auto-detected system proxy settings.
369+
* Decoupled from IAcquisitionWorkerContext so it can be used by isOnline and other callers that don't have a full context.
370+
*/
371+
private async getProxyAgent(manualProxyUrl?: string, eventStream?: IEventStream): Promise<HttpsProxyAgent<string> | null>
324372
{
325373
try
326374
{
375+
const hasManualProxy = manualProxyUrl ? manualProxyUrl !== '""' : false;
376+
327377
let discoveredProxy = '';
328-
if (!this.proxySettingConfiguredManually(ctx))
378+
if (!hasManualProxy)
329379
{
330380
const autoDetectProxies = await getProxySettings();
331381
if (autoDetectProxies?.https)
@@ -338,17 +388,17 @@ export class WebRequestWorkerSingleton
338388
}
339389
}
340390

341-
if (this.proxySettingConfiguredManually(ctx) || discoveredProxy)
391+
if (hasManualProxy || discoveredProxy)
342392
{
343-
const finalProxy = ctx?.proxyUrl && ctx?.proxyUrl !== '""' && ctx?.proxyUrl !== '' ? ctx.proxyUrl : discoveredProxy;
344-
ctx.eventStream.post(new ProxyUsed(`Utilizing the Proxy : Manual ? ${ctx?.proxyUrl}, Automatic: ${discoveredProxy}, Decision : ${finalProxy}`))
393+
const finalProxy = manualProxyUrl && manualProxyUrl !== '""' && manualProxyUrl !== '' ? manualProxyUrl : discoveredProxy;
394+
eventStream?.post(new ProxyUsed(`Utilizing the Proxy : Manual ? ${manualProxyUrl}, Automatic: ${discoveredProxy}, Decision : ${finalProxy}`))
345395
const proxyAgent = new HttpsProxyAgent(finalProxy);
346396
return proxyAgent;
347397
}
348398
}
349399
catch (error: any)
350400
{
351-
ctx.eventStream.post(new SuppressedAcquisitionError(error, `The proxy lookup failed, most likely due to limited registry access. Skipping automatic proxy lookup.`));
401+
eventStream?.post(new SuppressedAcquisitionError(error, `The proxy lookup failed, most likely due to limited registry access. Skipping automatic proxy lookup.`));
352402
}
353403

354404
return null;
@@ -485,11 +535,6 @@ If you're on a proxy and disable registry access, you must set the proxy in our
485535
}
486536
}
487537

488-
private proxySettingConfiguredManually(ctx: IAcquisitionWorkerContext): boolean
489-
{
490-
return ctx?.proxyUrl ? ctx?.proxyUrl !== '""' : false;
491-
}
492-
493538
private timeoutMsFromCtx(ctx: IAcquisitionWorkerContext): number
494539
{
495540
return ctx?.timeoutSeconds * 1000;

0 commit comments

Comments
 (0)