Відповіді SOCKS5 у .NET 7+: обробка часткових читань TCP
Читайте фіксований префікс відповіді команди SOCKS5 і суфікс, що залежить від ATYP, не покладаючись на межі запису TCP на сервері.
Сервер SOCKS5 записує чотири байти окремо, і клієнт працює лише після того, як ці записи об'єднано. Об'єднання може змінити таймінг, але TCP досі не обіцяє одне читання на один запис.
Клієнту потрібен аналізатор відповіді. Після вибору методу й будь-якої потрібної автентифікації відповідь команди SOCKS5 починається з чотирьох байтів: версія, результат, зарезервований байт і тип адреси. Довжина решти залежить від цього типу адреси.
Аналізатор для .NET 7+, якому треба спожити лише успішну відповідь CONNECT, може використати:
static async Task ReadConnectReply(Stream stream, CancellationToken ct)
{
byte[] prefix = new byte[4];
await stream.ReadExactlyAsync(prefix, ct);
if (prefix[0] != 5 || prefix[2] != 0)
throw new IOException("Invalid SOCKS5 reply");
if (prefix[1] != 0)
throw new IOException($"SOCKS5 request failed: {prefix[1]}");
int remaining;
switch (prefix[3])
{
case 1: remaining = 4 + 2; break;
case 4: remaining = 16 + 2; break;
case 3:
byte[] length = new byte[1];
await stream.ReadExactlyAsync(length, ct);
remaining = length[0] + 2;
break;
default:
throw new IOException("Unknown SOCKS5 address type");
}
await stream.ReadExactlyAsync(new byte[remaining], ct);
}
Останні два байти — це прив'язаний порт. IPv4 потребує чотирьох байтів; IPv6 — шістнадцяти; доменна адреса несе байт довжини перед іменем.
Передайте токен скасування з передбаченим терміном узгодження. ReadExactlyAsync обробляє коротші окремі читання й кидає виняток, коли потік закінчується раніше за запитану довжину. У разі збою відкиньте невдале з'єднання через шлях очищення його власника.
У разі успіху зупиніться на межі відповіді. Байти після неї належать протоколу призначення. Більше випереджальне читання має зберігати зайві байти, а не викидати початок потоку застосунку.