Add upstream proxy challenge logging

This commit is contained in:
2026-04-11 12:01:09 +03:00
parent e1734f82ef
commit 406e6e2d92

View File

@@ -479,8 +479,10 @@ class Program
return resp;
var challenges = ParseProxyAuthenticateValues(resp.Raw);
Log.Info($"[conn:{connectionId}] upstream challenges: {string.Join(", ", challenges)}");
if (code != 407 || challenges.Count == 0)
return resp;
await DrainResponseBodyIfAny(upstreamStream, resp.Raw, connectionId, "initial-407");
// 2) Basic fallback.
if (!string.IsNullOrEmpty(UPSTREAM_PROXY_AUTH_HEADER))
@@ -491,6 +493,8 @@ class Program
return basicResp;
resp = basicResp ?? resp;
challenges = ParseProxyAuthenticateValues(resp.Raw);
Log.Info($"[conn:{connectionId}] upstream challenges after basic: {string.Join(", ", challenges)}");
await DrainResponseBodyIfAny(upstreamStream, resp.Raw, connectionId, "basic-407");
}
// 3) Negotiate/NTLM over SSPI (as browsers do for AD proxies).
@@ -523,7 +527,9 @@ class Program
return authResp ?? resp;
challenges = ParseProxyAuthenticateValues(authResp.Raw);
Log.Info($"[conn:{connectionId}] upstream challenges after {scheme} step {step + 1}: {string.Join(", ", challenges)}");
inToken = ExtractSchemeToken(challenges, scheme);
await DrainResponseBodyIfAny(upstreamStream, authResp.Raw, connectionId, $"{scheme}-{step + 1}-407");
resp = authResp;
}
@@ -574,6 +580,111 @@ class Program
}
return null;
}
static async Task DrainResponseBodyIfAny(NetworkStream stream, string rawHeaders, long connectionId, string stage)
{
if (string.IsNullOrWhiteSpace(rawHeaders))
return;
int contentLength = 0;
bool chunked = false;
foreach (var line in rawHeaders.Split("\r\n", StringSplitOptions.None))
{
if (line.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase))
{
var value = line.Substring("Content-Length:".Length).Trim();
if (int.TryParse(value, out var len) && len > 0)
contentLength = len;
}
if (line.StartsWith("Transfer-Encoding:", StringComparison.OrdinalIgnoreCase)
&& line.IndexOf("chunked", StringComparison.OrdinalIgnoreCase) >= 0)
{
chunked = true;
}
}
if (contentLength > 0)
{
await ReadExactly(stream, contentLength);
Log.Info($"[conn:{connectionId}] drained {contentLength} response bytes ({stage})");
return;
}
if (chunked)
{
int drained = await DrainChunkedBody(stream);
Log.Info($"[conn:{connectionId}] drained chunked body bytes={drained} ({stage})");
}
}
static async Task ReadExactly(Stream stream, int count)
{
var buffer = ArrayPool<byte>.Shared.Rent(2048);
try
{
int remain = count;
while (remain > 0)
{
int read = await stream.ReadAsync(buffer, 0, Math.Min(buffer.Length, remain));
if (read <= 0)
break;
remain -= read;
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
static async Task<int> DrainChunkedBody(Stream stream)
{
int total = 0;
while (true)
{
string sizeLine = await ReadAsciiLine(stream);
if (string.IsNullOrWhiteSpace(sizeLine))
continue;
string hex = sizeLine.Split(';', 2)[0].Trim();
if (!int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out int size))
break;
if (size == 0)
{
await ReadAsciiLine(stream); // trailing CRLF after last chunk/trailers simplified
break;
}
await ReadExactly(stream, size);
total += size;
await ReadExactly(stream, 2); // CRLF
}
return total;
}
static async Task<string> ReadAsciiLine(Stream stream)
{
var bytes = new List<byte>(64);
var one = new byte[1];
while (bytes.Count < 8192)
{
int read = await stream.ReadAsync(one, 0, 1);
if (read <= 0)
break;
bytes.Add(one[0]);
int n = bytes.Count;
if (n >= 2 && bytes[n - 2] == '\r' && bytes[n - 1] == '\n')
break;
}
if (bytes.Count == 0)
return string.Empty;
return Encoding.ASCII.GetString(bytes.ToArray()).TrimEnd('\r', '\n');
}
}
sealed class SspiProxyAuthenticator : IDisposable