SOCKS5-Antworten in .NET 7+: teilweise TCP-Lesevorgänge behandeln
Lies das feste Präfix der SOCKS5-Befehlsantwort und das ATYP-abhängige Suffix, ohne dich auf die TCP-Schreibgrenzen des Servers zu verlassen.
Ein SOCKS5-Server schreibt vier Bytes getrennt, und der Client funktioniert erst, wenn diese Schreibvorgänge zusammengeführt sind. Das Zusammenführen kann das Timing ändern, aber TCP verspricht weiterhin nicht ein Lesen pro Schreiben.
Der Client braucht einen Antwortparser. Nach der Methodenauswahl und einer eventuell nötigen Authentifizierung beginnt eine SOCKS5-Befehlsantwort mit vier Bytes: Version, Ergebnis, reserviertes Byte und Adresstyp. Die verbleibende Länge hängt von diesem Adresstyp ab.
Ein Parser für .NET 7+, der nur eine erfolgreiche CONNECT-Antwort verarbeiten muss, kann Folgendes verwenden:
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);
}
Die letzten zwei Bytes sind der gebundene Port. IPv4 braucht vier Bytes; IPv6 braucht sechzehn; eine Domainadresse trägt ein Längenbyte vor dem Namen.
Übergib ein Abbruchtoken mit der vorgesehenen Aushandlungsfrist. ReadExactlyAsync behandelt kürzere einzelne Lesevorgänge und löst eine Ausnahme aus, wenn der Stream vor der angeforderten Länge endet. Bei einem Fehler verwirf die fehlgeschlagene Verbindung über den Aufräumpfad ihres Eigentümers.
Bei Erfolg stoppe an der Antwortgrenze. Bytes danach gehören zum Zielprotokoll. Ein größerer spekulativer Lesevorgang muss überschüssige Bytes bewahren, statt den Anfang des Anwendungsstreams wegzuwerfen.