پاسخهای 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 خواندنهای کوتاهتر را مدیریت میکند و وقتی جریان پیش از طول درخواستی پایان یابد استثنا پرتاب میکند. در صورت شکست، اتصال ناموفق را از مسیر پاکسازی صاحبش دور بیندازید.
در صورت موفقیت، در مرز پاسخ توقف کنید. بایتهای بعد از آن به پروتکل مقصد تعلق دارند. خواندن حدسی بزرگتر باید بایتهای مازاد را نگه دارد، نه اینکه آغاز جریان برنامه را دور بریزد.