Phản hồi SOCKS5 trong .NET 7+: xử lý các lần đọc TCP một phần
Đọc tiền tố cố định của phản hồi lệnh SOCKS5 và hậu tố phụ thuộc ATYP mà không dựa vào ranh giới ghi TCP của máy chủ.
Máy chủ SOCKS5 ghi bốn byte riêng lẻ, và máy khách chỉ hoạt động sau khi các lần ghi đó được kết hợp. Việc kết hợp có thể thay đổi thời điểm, nhưng TCP vẫn không hứa hẹn một lần đọc cho mỗi lần ghi.
Máy khách cần một bộ phân tích phản hồi. Sau khi chọn phương thức và mọi xác thực cần thiết, phản hồi lệnh SOCKS5 bắt đầu bằng bốn byte: phiên bản, kết quả, byte dự trữ và loại địa chỉ. Độ dài còn lại phụ thuộc vào loại địa chỉ đó.
Một bộ phân tích .NET 7+ chỉ cần tiêu thụ phản hồi CONNECT thành công có thể dùng:
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);
}
Hai byte cuối là cổng được liên kết. IPv4 cần bốn byte; IPv6 cần mười sáu; địa chỉ miền mang một byte độ dài trước tên.
Cung cấp mã thông báo hủy với thời hạn thương lượng dự kiến. ReadExactlyAsync xử lý các lần đọc riêng lẻ ngắn hơn và ném ngoại lệ khi luồng kết thúc trước độ dài được yêu cầu. Khi thất bại, hãy loại bỏ kết nối thất bại qua đường dọn dẹp của chủ sở hữu nó.
Khi thành công, dừng ở ranh giới phản hồi. Các byte sau đó thuộc về giao thức đích. Một lần đọc suy đoán lớn hơn phải giữ lại các byte dư thay vì vứt bỏ phần đầu của luồng ứng dụng.