Сервер 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 обробляє коротші окремі читання й кидає виняток, коли потік закінчується раніше за запитану довжину. У разі збою відкиньте невдале з'єднання через шлях очищення його власника.

У разі успіху зупиніться на межі відповіді. Байти після неї належать протоколу призначення. Більше випереджальне читання має зберігати зайві байти, а не викидати початок потоку застосунку.

Схожі статті