Адказы 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 апрацоўвае кароткія асобныя чытанні і кідае выключэнне, калі паток заканчваецца раней за запытаную даўжыню. Пры збоі адкідвайце няўдалае злучэнне праз шлях ачысткі яго ўладальніка.
Пры поспеху спыніцеся на мяжы адказу. Байты пасля яе належаць пратаколу прызначэння. Большае спекулятыўнае чытанне павінна захаваць лішкі байтаў, а не выкідваць пачатак патоку праграмы.