Optimization

This commit is contained in:
DHR60 2026-03-27 22:49:37 +08:00
parent bfbfa1ab7b
commit 268c77118f
6 changed files with 86 additions and 122 deletions

View file

@ -4,9 +4,9 @@ namespace ServiceLib.Services.Udp;
public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposable public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposable
{ {
private TcpClient tcpClient; private TcpClient _tcpClient;
private UdpClient udpClient; private UdpClient _udpClient;
private IPEndPoint relayEndPoint; private IPEndPoint _relayEndPoint;
private bool _initialized = false; private bool _initialized = false;
@ -24,7 +24,7 @@ public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposabl
Port = (ushort)remote.Port Port = (ushort)remote.Port
}; };
var packet = BuildSocks5UdpPacket(addrData, data); var packet = BuildSocks5UdpPacket(addrData, data);
await udpClient.SendAsync(packet, packet.Length, relayEndPoint); await _udpClient.SendAsync(packet, packet.Length, _relayEndPoint);
} }
/// <summary> /// <summary>
@ -55,7 +55,7 @@ public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposabl
addrData.Port = port; addrData.Port = port;
var packet = BuildSocks5UdpPacket(addrData, data); var packet = BuildSocks5UdpPacket(addrData, data);
await udpClient.SendAsync(packet, packet.Length, relayEndPoint); await _udpClient.SendAsync(packet, packet.Length, _relayEndPoint);
} }
/// <summary> /// <summary>
@ -66,7 +66,7 @@ public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposabl
public async Task<(Socks5RemoteEndpoint Remote, byte[] Data)> ReceiveAsync( public async Task<(Socks5RemoteEndpoint Remote, byte[] Data)> ReceiveAsync(
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var result = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); var result = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
var (remote, payload) = ParseSocks5UdpPacket(result.Buffer); var (remote, payload) = ParseSocks5UdpPacket(result.Buffer);
return (remote, payload); return (remote, payload);
} }
@ -200,8 +200,8 @@ public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposabl
public void Dispose() public void Dispose()
{ {
tcpClient.Dispose(); _tcpClient.Dispose();
udpClient.Dispose(); _udpClient.Dispose();
} }
#region SOCKS5 Connection Handling #region SOCKS5 Connection Handling
@ -217,20 +217,20 @@ public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposabl
_initialized = false; _initialized = false;
} }
udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); _udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
tcpClient = new TcpClient(); _tcpClient = new TcpClient();
try try
{ {
await tcpClient.ConnectAsync(socks5Host, socks5TcpPort, cancellationToken).ConfigureAwait(false); await _tcpClient.ConnectAsync(socks5Host, socks5TcpPort, cancellationToken).ConfigureAwait(false);
} }
catch (SocketException) catch (SocketException)
{ {
return false; return false;
} }
var tcpControlStream = tcpClient.GetStream(); var tcpControlStream = _tcpClient.GetStream();
byte[] handshakeRequest = { Socks5Version, 0x01, 0x00 }; byte[] handshakeRequest = [Socks5Version, 0x01, 0x00];
await tcpControlStream.WriteAsync(handshakeRequest, cancellationToken).ConfigureAwait(false); await tcpControlStream.WriteAsync(handshakeRequest, cancellationToken).ConfigureAwait(false);
var handshakeResponse = new byte[2]; var handshakeResponse = new byte[2];
if (await tcpControlStream.ReadAsync(handshakeResponse, cancellationToken).ConfigureAwait(false) < 2 || if (await tcpControlStream.ReadAsync(handshakeResponse, cancellationToken).ConfigureAwait(false) < 2 ||
@ -264,7 +264,7 @@ public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposabl
return false; return false;
} }
relayEndPoint = new IPEndPoint(proxyRelayIp, proxyRelaySocksAddr.Port); _relayEndPoint = new IPEndPoint(proxyRelayIp, proxyRelaySocksAddr.Port);
_initialized = true; _initialized = true;
return true; return true;
} }
@ -296,7 +296,7 @@ public class Socks5UdpChannel(string socks5Host, int socks5TcpPort) : IDisposabl
} }
else else
{ {
ms.Write(new byte[] { 0, 0, 0, 0 }); ms.Write([0, 0, 0, 0]);
} }
break; break;

View file

@ -7,16 +7,15 @@ public class DnsService : IUdpTest
private const int DnsDefaultPort = 53; private const int DnsDefaultPort = 53;
private const string DnsDefaultServer = "8.8.8.8"; // Google Public DNS private const string DnsDefaultServer = "8.8.8.8"; // Google Public DNS
private static readonly byte[] DnsQueryPacket = private static readonly byte[] DnsQueryPacket =
new byte[] [
{
// Header: ID=0x1234, Standard query with RD set, QDCOUNT=1 // Header: ID=0x1234, Standard query with RD set, QDCOUNT=1
0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Question: www.google.com, Type A, Class IN // Question: www.google.com, Type A, Class IN
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F,
0x67, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x67, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00,
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01
}; ];
public byte[] BuildUdpRequestPacket() public byte[] BuildUdpRequestPacket()
{ {
@ -25,7 +24,7 @@ public class DnsService : IUdpTest
public bool VerifyAndExtractUdpResponse(byte[] dnsResponseBytes) public bool VerifyAndExtractUdpResponse(byte[] dnsResponseBytes)
{ {
if (dnsResponseBytes == null || dnsResponseBytes.Length < 12) if (dnsResponseBytes.Length < 12)
{ {
return false; return false;
} }
@ -76,4 +75,4 @@ public class DnsService : IUdpTest
{ {
return DnsDefaultServer; return DnsDefaultServer;
} }
} }

View file

@ -6,8 +6,7 @@ public class McBeService : IUdpTest
private const string McBeDefaultServer = "pms.mc-complex.com"; private const string McBeDefaultServer = "pms.mc-complex.com";
// 0x01 | client alive time in ms (unsigned long long) | magic | client GUID // 0x01 | client alive time in ms (unsigned long long) | magic | client GUID
private static readonly byte[] McBeQueryPacket = private static readonly byte[] McBeQueryPacket =
new byte[] [
{
// 0x01 // 0x01
0x01, 0x01,
// Client alive time (1000 ms) // Client alive time (1000 ms)
@ -18,19 +17,19 @@ public class McBeService : IUdpTest
// Client GUID (random 16 bytes) // Client GUID (random 16 bytes)
0x66, 0x0E, 0xAB, 0xBC, 0x61, 0x0D, 0x1F, 0x4E, 0x66, 0x0E, 0xAB, 0xBC, 0x61, 0x0D, 0x1F, 0x4E,
0xA4, 0x40, 0x8C, 0x65, 0xC1, 0xBE, 0xF5, 0x4B 0xA4, 0x40, 0x8C, 0x65, 0xC1, 0xBE, 0xF5, 0x4B
}; ];
private static readonly byte[] McBeMagicBytes = new byte[] private static readonly byte[] McBeMagicBytes =
{ [
0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE,
0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78 0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78
}; ];
private static readonly List<string> ValidGameModes = new List<string> private static readonly List<string> ValidGameModes =
{ [
"Survival", "Survival",
"Creative", "Creative",
"Adventure", "Adventure",
"Spectator" "Spectator"
}; ];
public byte[] BuildUdpRequestPacket() public byte[] BuildUdpRequestPacket()
{ {
@ -45,7 +44,7 @@ public class McBeService : IUdpTest
// Edition Example: // Edition Example:
// //
// MCPE;Dedicated Server;527;1.19.1;0;10;13253860892328930865;Bedrock level;Survival;1;19132;19133; // MCPE;Dedicated Server;527;1.19.1;0;10;13253860892328930865;Bedrock level;Survival;1;19132;19133;
if (mcbeResponseBytes == null || mcbeResponseBytes.Length < 48) if (mcbeResponseBytes.Length < 48)
{ {
return false; return false;
} }

View file

@ -1,5 +1,3 @@
using System.Buffers.Binary;
namespace ServiceLib.Services.Udp.Test; namespace ServiceLib.Services.Udp.Test;
public class NtpService : IUdpTest public class NtpService : IUdpTest
@ -16,7 +14,7 @@ public class NtpService : IUdpTest
public bool VerifyAndExtractUdpResponse(byte[] ntpResponseBytes) public bool VerifyAndExtractUdpResponse(byte[] ntpResponseBytes)
{ {
if (ntpResponseBytes == null || ntpResponseBytes.Length < 48) if (ntpResponseBytes.Length < 48)
{ {
return false; return false;
} }
@ -24,17 +22,7 @@ public class NtpService : IUdpTest
{ {
return false; return false;
} }
try return true;
{
var secsSince1900 = BinaryPrimitives.ReadUInt32BigEndian(ntpResponseBytes.AsSpan(40, 4));
const long ntpToUnixEpochOffsetSeconds = 2208988800L;
var unixSecs = (long)secsSince1900 - ntpToUnixEpochOffsetSeconds;
return true;
}
catch
{
return false;
}
} }
public ushort GetDefaultTargetPort() public ushort GetDefaultTargetPort()
@ -46,4 +34,4 @@ public class NtpService : IUdpTest
{ {
return NtpDefaultServer; return NtpDefaultServer;
} }
} }

View file

@ -4,47 +4,32 @@ public class StunService : IUdpTest
{ {
private const int StunDefaultPort = 3478; private const int StunDefaultPort = 3478;
private const string StunDefaultServer = "stun.voztovoice.org"; private const string StunDefaultServer = "stun.voztovoice.org";
private byte[] _transactionId; private static readonly byte[] StunBindingRequestPacket =
[
// STUN Binding Request
0x00, 0x01, // Message Type: Binding Request (0x0001)
0x00, 0x00, // Message Length: 0 (no attributes)
0x21, 0x12, 0xA4, 0x42, // Magic Cookie: 0x2112A442
// Transaction ID: 96 bits (12 bytes) random
0x66, 0x0E, 0xAB, 0xBC, 0x61, 0x0D,
0xA4, 0x40, 0x8C, 0x65, 0xC1, 0xBE,
];
public byte[] BuildUdpRequestPacket() public byte[] BuildUdpRequestPacket()
{ {
// STUN Binding Request return (byte[])StunBindingRequestPacket.Clone();
var packet = new byte[20];
// Message Type: Binding Request (0x0001)
packet[0] = 0x00;
packet[1] = 0x01;
// Message Length: 0 (no attributes)
packet[2] = 0x00;
packet[3] = 0x00;
// Magic Cookie: 0x2112A442
packet[4] = 0x21;
packet[5] = 0x12;
packet[6] = 0xA4;
packet[7] = 0x42;
// Transaction ID: 96 bits (12 bytes) random
_transactionId = new byte[12];
RandomNumberGenerator.Fill(_transactionId);
Array.Copy(_transactionId, 0, packet, 8, 12);
return packet;
} }
public bool VerifyAndExtractUdpResponse(byte[] stunResponseBytes) public bool VerifyAndExtractUdpResponse(byte[] stunResponseBytes)
{ {
if (stunResponseBytes == null || stunResponseBytes.Length < 20) if (stunResponseBytes.Length < 20)
{ {
return false; return false;
} }
// Message Type: Binding Success Response (0x0101) 或 Binding Error Response (0x0111)
if (stunResponseBytes.Length >= 2) if (stunResponseBytes.Length >= 2)
{ {
var messageType = (stunResponseBytes[0] << 8) | stunResponseBytes[1]; var messageType = (stunResponseBytes[0] << 8) | stunResponseBytes[1];
// 0x0101 = Success Response, 0x0111 = Error Response
if (messageType is 0x0101 or 0x0111) if (messageType is 0x0101 or 0x0111)
{ {
return true; return true;
@ -63,4 +48,4 @@ public class StunService : IUdpTest
{ {
return StunDefaultServer; return StunDefaultServer;
} }
} }

View file

@ -52,7 +52,7 @@ public class UdpService
} }
// Handle IPv6 format: [::1]:port or [2001:db8::1]:port // Handle IPv6 format: [::1]:port or [2001:db8::1]:port
if (targetServerHost.StartsWith("[")) if (targetServerHost.StartsWith('['))
{ {
var closeBracketIndex = targetServerHost.IndexOf(']'); var closeBracketIndex = targetServerHost.IndexOf(']');
if (closeBracketIndex > 0) if (closeBracketIndex > 0)
@ -96,64 +96,57 @@ public class UdpService
throw new InvalidOperationException("Failed to build UDP request packet."); throw new InvalidOperationException("Failed to build UDP request packet.");
} }
using var channel = new Socks5UdpChannel(Global.Loopback, socks5Port); using var channel = new Socks5UdpChannel(Global.Loopback, socks5Port);
try if (!await channel.EstablishUdpAssociationAsync(cancellationToken).ConfigureAwait(false))
{ {
if (!await channel.EstablishUdpAssociationAsync(cancellationToken).ConfigureAwait(false)) throw new Exception("Failed to establish UDP association with SOCKS5 proxy.");
}
var (targetHost, targetPort) = ParseHostAndPort(targetServerHost);
byte[] udpReceiveResult = null;
// Get minimum round trip time from two attempts
var roundTripTime = TimeSpan.MaxValue;
for (var attempt = 0; attempt < 2; attempt++)
{
try
{ {
throw new Exception("Failed to establish UDP association with SOCKS5 proxy."); var stopwatch = new Stopwatch();
} stopwatch.Start();
await channel.SendAsync(targetHost, targetPort, udpRequestPacket).ConfigureAwait(false);
var (_, receiveResult) = await channel.ReceiveAsync(cancellationToken).ConfigureAwait(false);
stopwatch.Stop();
var (targetHost, targetPort) = ParseHostAndPort(targetServerHost); udpReceiveResult = receiveResult;
byte[] udpReceiveResult = null; var currentRoundTripTime = stopwatch.Elapsed;
if (currentRoundTripTime < roundTripTime)
// Get minimum round trip time from two attempts
var roundTripTime = TimeSpan.MaxValue;
for (var attempt = 0; attempt < 2; attempt++)
{
try
{ {
var stopwatch = new Stopwatch(); roundTripTime = currentRoundTripTime;
stopwatch.Start();
await channel.SendAsync(targetHost, targetPort, udpRequestPacket).ConfigureAwait(false);
var (_, receiveResult) = await channel.ReceiveAsync(cancellationToken).ConfigureAwait(false);
stopwatch.Stop();
udpReceiveResult = receiveResult;
var currentRoundTripTime = stopwatch.Elapsed;
if (currentRoundTripTime < roundTripTime)
{
roundTripTime = currentRoundTripTime;
}
}
catch
{
if (attempt == 1 && roundTripTime == TimeSpan.MaxValue)
{
throw;
}
} }
} }
catch
if ((udpReceiveResult?.Length ?? 0) < 4 + 1 + 4 + 2)
{ {
throw new Exception("Received NTP response is too short."); if (attempt == 1 && roundTripTime == TimeSpan.MaxValue)
} {
throw;
if (_udpTest.VerifyAndExtractUdpResponse(udpReceiveResult)) }
{
return roundTripTime;
}
else
{
throw new Exception("Failed to verify and extract UDP response.");
} }
} }
catch
if ((udpReceiveResult?.Length ?? 0) < 4 + 1 + 4 + 2)
{ {
throw; throw new Exception("Received NTP response is too short.");
}
if (udpReceiveResult != null && _udpTest.VerifyAndExtractUdpResponse(udpReceiveResult))
{
return roundTripTime;
}
else
{
throw new Exception("Failed to verify and extract UDP response.");
} }
} }
} }