การตอบกลับ 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 จัดการการอ่านทีละครั้งที่สั้นกว่าและโยนข้อยกเว้นเมื่อสตรีมสิ้นสุดก่อนความยาวที่ร้องขอ หากล้มเหลว ให้ทิ้งการเชื่อมต่อที่ล้มเหลวผ่านเส้นทางการทำความสะอาดของเจ้าของ
เมื่อสำเร็จ ให้หยุดที่ขอบเขตของการตอบกลับ ไบต์หลังจากนั้นเป็นของโปรโตคอลปลายทาง การอ่านล่วงหน้าที่ใหญ่กว่าต้องเก็บไบต์ส่วนเกินไว้ ไม่ใช่ทิ้งจุดเริ่มต้นของสตรีมแอปพลิเคชัน