ردود 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 مع القراءات الفردية الأقصر ويرمي استثناءً عندما ينتهي التدفق قبل الطول المطلوب. عند الفشل، تخلّص من الاتصال الفاشل عبر مسار التنظيف الخاص بمالكه.
عند النجاح توقّف عند حدود الرد. البايتات التي بعده تنتمي إلى بروتوكول الوجهة. وأي قراءة استباقية أكبر يجب أن تحتفظ بالبايتات الفائضة بدلًا من إلقاء بداية تدفق التطبيق.