diff --git a/shadowsocks-csharp/Data/cn.txt b/shadowsocks-csharp/Data/cn.txt
index b1f73b516a3..44e3f348b9c 100644
--- a/shadowsocks-csharp/Data/cn.txt
+++ b/shadowsocks-csharp/Data/cn.txt
@@ -167,6 +167,8 @@ Empty Response=空连
Error Percent=出错比例
Continuous=连错
Version=版本
+Experimental=实验性功能
+Test selected server=测试选定的服务器
# Global Log Form
diff --git a/shadowsocks-csharp/Data/zh-tw.txt b/shadowsocks-csharp/Data/zh-tw.txt
index d3ea5a7da2f..81fe20edef6 100644
--- a/shadowsocks-csharp/Data/zh-tw.txt
+++ b/shadowsocks-csharp/Data/zh-tw.txt
@@ -167,6 +167,8 @@ Empty Response=空連
Error Percent=出錯比例
Continuous=連錯
Version=版本
+Experimental=實驗性功能
+Test selected server=測試選定的伺服器
# Global Log Form
diff --git a/shadowsocks-csharp/Model/ServerSpeedLog.cs b/shadowsocks-csharp/Model/ServerSpeedLog.cs
index 1ff5d786107..c82598eee96 100644
--- a/shadowsocks-csharp/Model/ServerSpeedLog.cs
+++ b/shadowsocks-csharp/Model/ServerSpeedLog.cs
@@ -177,7 +177,7 @@ public long AvgUploadBytes
#region 延迟
- public long AvgConnectTime => avgConnectTime;
+ public int AvgConnectTime => avgConnectTime;
public long AvgConnectTimeText
{
diff --git a/shadowsocks-csharp/Proxy/HttpProxyRunner.cs b/shadowsocks-csharp/Proxy/HttpProxyRunner.cs
index c350af7717c..e70b2282851 100644
--- a/shadowsocks-csharp/Proxy/HttpProxyRunner.cs
+++ b/shadowsocks-csharp/Proxy/HttpProxyRunner.cs
@@ -1,18 +1,18 @@
-using System;
+using Shadowsocks.Controller;
+using Shadowsocks.Model;
+using Shadowsocks.Properties;
+using Shadowsocks.Util;
+using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
-using Shadowsocks.Controller;
-using Shadowsocks.Model;
-using Shadowsocks.Properties;
-using Shadowsocks.Util;
namespace Shadowsocks.Proxy
{
- class HttpProxyRunner
+ public class HttpProxyRunner
{
private static readonly string UNIQUE_CONFIG_FILE;
private static readonly Job PRIVOXY_JOB;
@@ -126,7 +126,7 @@ private static bool IsChildProcess(Process process)
}
}
- private static int GetFreePort()
+ public static int GetFreePort()
{
const int defaultPort = 60000;
try
diff --git a/shadowsocks-csharp/Proxy/ProxyAuth.cs b/shadowsocks-csharp/Proxy/ProxyAuth.cs
index f74037c810e..da403ed3839 100644
--- a/shadowsocks-csharp/Proxy/ProxyAuth.cs
+++ b/shadowsocks-csharp/Proxy/ProxyAuth.cs
@@ -1,10 +1,10 @@
-using System;
+using Shadowsocks.Controller;
+using Shadowsocks.Model;
+using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
-using Shadowsocks.Controller;
-using Shadowsocks.Model;
namespace Shadowsocks.Proxy
{
@@ -517,19 +517,29 @@ private bool HttpHandshakeRecv()
private void Connect()
{
- Handler.GetCurrentServer getCurrentServer = delegate (int localPort, ServerSelectStrategy.FilterFunc filter, string targetURI, bool cfgRandom, bool usingRandom, bool forceRandom) { return _config.GetCurrentServer(localPort, filter, targetURI, cfgRandom, usingRandom, forceRandom); };
- Handler.KeepCurrentServer keepCurrentServer = delegate (int localPort, string targetURI, string id) { _config.KeepCurrentServer(localPort, targetURI, id); };
+ Server GetCurrentServer(int localPort, ServerSelectStrategy.FilterFunc filter, string targetURI, bool cfgRandom, bool usingRandom, bool forceRandom)
+ => _config.GetCurrentServer(localPort, filter, targetURI, cfgRandom, usingRandom, forceRandom);
+
+ void KeepCurrentServer(int localPort, string targetURI, string id)
+ {
+ _config.KeepCurrentServer(localPort, targetURI, id);
+ }
int local_port = ((IPEndPoint)_connection.LocalEndPoint).Port;
- Handler handler = new Handler();
-
- handler.getCurrentServer = getCurrentServer;
- handler.keepCurrentServer = keepCurrentServer;
- handler.connection = new ProxySocketTunLocal(_connection);
- handler.connectionUDP = _connectionUDP;
- handler.cfg.ReconnectTimesRemain = _config.reconnectTimes;
- handler.cfg.Random = _config.random;
- handler.cfg.ForceRandom = _config.random;
+ Handler handler = new Handler
+ {
+ getCurrentServer = GetCurrentServer,
+ keepCurrentServer = KeepCurrentServer,
+ connection = new ProxySocketTunLocal(_connection),
+ connectionUDP = _connectionUDP,
+ cfg =
+ {
+ ReconnectTimesRemain = _config.reconnectTimes,
+ Random = _config.random,
+ ForceRandom = _config.random
+ }
+ };
+
handler.setServerTransferTotal(_transfer);
if (_config.proxyEnable)
{
diff --git a/shadowsocks-csharp/Util/SimpleSocksProxy.cs b/shadowsocks-csharp/Util/SimpleSocksProxy.cs
new file mode 100644
index 00000000000..ed6092c7e66
--- /dev/null
+++ b/shadowsocks-csharp/Util/SimpleSocksProxy.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Shadowsocks.Util
+{
+ public class ConnectionException : ApplicationException
+ {
+ public ConnectionException(string message) : base(message)
+ { }
+ }
+ public static class SimpleSocksProxy
+ {
+ #region ErrorMessages
+ private static readonly string[] ErrorMessages = {
+ @"Operation completed successfully.",
+ @"General SOCKS server failure.",
+ @"connection not allowed by ruleset.",
+ @"Network unreachable.",
+ @"Host unreachable.",
+ @"Connection refused.",
+ @"TTL expired.",
+ @"Command not supported.",
+ @"Address type not supported.",
+ @"Unknown error."
+ };
+ #endregion
+
+ private static Socket ConnectToSocks5Proxy(string proxyAddress, ushort proxyPort,
+ string destAddress, ushort destPort,
+ string userName, string password)
+ {
+ if (userName == null)
+ {
+ userName = string.Empty;
+ }
+ if (password == null)
+ {
+ password = string.Empty;
+ }
+
+ var request = new byte[257];
+ var response = new byte[257];
+
+ if (IPAddress.TryParse(proxyAddress, out var proxyIp))
+ {
+ proxyIp = Dns.GetHostAddresses(proxyAddress)[0];
+ }
+
+ IPAddress.TryParse(destAddress, out var destIp);
+
+ var proxyEndPoint = new IPEndPoint(proxyIp, proxyPort);
+
+ // open a TCP connection to SOCKS server...
+ var s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ s.Connect(proxyEndPoint);
+
+ ushort nIndex = 0;
+ request[nIndex++] = 0x05; // Version 5.
+ request[nIndex++] = 0x02; // 2 Authentication methods are in packet...
+ request[nIndex++] = 0x00; // NO AUTHENTICATION REQUIRED
+ request[nIndex++] = 0x02; // USERNAME/PASSWORD
+ // Send the authentication negotiation request...
+ s.Send(request, nIndex, SocketFlags.None);
+
+ // Receive 2 byte response...
+ var nGot = s.Receive(response, 2, SocketFlags.None);
+ if (nGot != 2)
+ throw new ConnectionException("Bad response received from proxy server.");
+
+ if (response[1] == 0xFF)
+ { // No authentication method was accepted close the socket.
+ s.Close();
+ throw new ConnectionException("None of the authentication method was accepted by proxy server.");
+ }
+
+ byte[] rawBytes;
+
+ if (/*response[1]==0x02*/true)
+ {//Username/Password Authentication protocol
+ nIndex = 0;
+ request[nIndex++] = 0x05; // Version 5.
+
+ // add user name
+ request[nIndex++] = (byte)userName.Length;
+ rawBytes = Encoding.Default.GetBytes(userName);
+ rawBytes.CopyTo(request, nIndex);
+ nIndex += (ushort)rawBytes.Length;
+
+ // add password
+ request[nIndex++] = (byte)password.Length;
+ rawBytes = Encoding.Default.GetBytes(password);
+ rawBytes.CopyTo(request, nIndex);
+ nIndex += (ushort)rawBytes.Length;
+
+ // Send the Username/Password request
+ s.Send(request, nIndex, SocketFlags.None);
+ // Receive 2 byte response...
+ nGot = s.Receive(response, 2, SocketFlags.None);
+ if (nGot != 2)
+ throw new ConnectionException("Bad response received from proxy server.");
+ if (response[1] != 0x00)
+ throw new ConnectionException("Bad Username/Password.");
+ }
+ // This version only supports connect command.
+ // UDP and Bind are not supported.
+
+ // Send connect request now...
+ nIndex = 0;
+ request[nIndex++] = 0x05; // version 5.
+ request[nIndex++] = 0x01; // command = connect.
+ request[nIndex++] = 0x00; // Reserve = must be 0x00
+
+ if (destIp != null)
+ {
+ if (destIp.AddressFamily == AddressFamily.InterNetwork)
+ {
+ // Address is IPV4 format
+ request[nIndex++] = 0x01;
+ rawBytes = destIp.GetAddressBytes();
+ rawBytes.CopyTo(request, nIndex);
+ nIndex += (ushort)rawBytes.Length;
+ }
+ else if (destIp.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ // Address is IPV6 format
+ request[nIndex++] = 0x04;
+ rawBytes = destIp.GetAddressBytes();
+ rawBytes.CopyTo(request, nIndex);
+ nIndex += (ushort)rawBytes.Length;
+ }
+ }
+ else
+ {
+ // Address is full-qualified domain name.
+ request[nIndex++] = 0x03;
+ // length of address.
+ request[nIndex++] = Convert.ToByte(destAddress.Length);
+ rawBytes = Encoding.Default.GetBytes(destAddress);
+ rawBytes.CopyTo(request, nIndex);
+ nIndex += (ushort)rawBytes.Length;
+ }
+
+ // using big-endian byte order
+ var portBytes = BitConverter.GetBytes(destPort);
+ Array.Reverse(portBytes);
+ foreach (var b in portBytes)
+ {
+ request[nIndex++] = b;
+ }
+
+ // send connect request.
+ s.Send(request, nIndex, SocketFlags.None);
+ // Get variable length response...
+ s.ReceiveTimeout = 3000;
+ s.Receive(response);
+ if (response[1] != 0x00)
+ {
+ throw new ConnectionException(ErrorMessages[response[1]]);
+ }
+ // Success Connected...
+ return s;
+ }
+
+ public static bool TestLocalSocks5(ushort port, string pass, string user)
+ {
+ const string strGet = "GET / HTTP/1.1\n\r\n";
+ var bytesReceived = new byte[1024];
+ Socket client = null;
+ try
+ {
+ client = ConnectToSocks5Proxy(
+ IPAddress.Loopback.ToString(), port,
+ @"www.google.com",
+ 80, pass, user);
+ client.Send(Encoding.UTF8.GetBytes(strGet));
+ var bytes = client.Receive(bytesReceived, bytesReceived.Length, 0);
+ var page = Encoding.UTF8.GetString(bytesReceived, 0, bytes);
+ if (page.StartsWith(@"HTTP/1.1 200 OK"))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ finally
+ {
+ client?.Close();
+ client?.Dispose();
+ }
+ }
+ }
+}
diff --git a/shadowsocks-csharp/View/ServerLogWindow.xaml b/shadowsocks-csharp/View/ServerLogWindow.xaml
index c30397bba4d..5ec618d46f5 100644
--- a/shadowsocks-csharp/View/ServerLogWindow.xaml
+++ b/shadowsocks-csharp/View/ServerLogWindow.xaml
@@ -195,6 +195,9 @@
+
current + $@"{t.SsrLink}{Environment.NewLine}");
Clipboard.SetDataObject(link);
}
+
+ private void TestSelectedMenuItem_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (ServerDataGrid.SelectedCells.Count > 0 && ServerDataGrid.SelectedCells[0].Column != null)
+ {
+ if (ServerDataGrid.SelectedCells[0].Item is Server serverObject)
+ {
+ var id = serverObject.Index - 1;
+ TestOne(id);
+ }
+ }
+ }
+
+ private async void TestOne(int id)
+ {
+ await Task.Run(() =>
+ {
+ var port = HttpProxyRunner.GetFreePort();
+ while (_controller.GetCurrentConfiguration().portMap.ContainsKey(port.ToString()))
+ {
+ port = HttpProxyRunner.GetFreePort();
+ }
+ var server = _controller.GetCurrentConfiguration().configs[id];
+
+ var portMap = _controller.GetCurrentConfiguration().portMap;
+ var portMapConfig = new PortMapConfig
+ {
+ enable = true,
+ id = server.Id,
+ remarks = $@"Test {server.FriendlyName} by HMBSbige",
+ type = PortMapType.ForceProxy,
+ server_addr = @"",
+ server_port = 0
+ };
+ portMap[port.ToString()] = portMapConfig;
+ Dispatcher.Invoke(_controller.Save);
+ _controller.GetCurrentConfiguration().FlushPortMapCache();
+
+ var config = _controller.GetCurrentConfiguration();
+ server = _controller.GetCurrentConfiguration().configs[id];
+ var pass = config.authPass;
+ var user = config.authUser;
+ if (SimpleSocksProxy.TestLocalSocks5(Convert.ToUInt16(port), pass, user))
+ {
+ server.Enable = true;
+ }
+ else
+ {
+ server.Enable = false;
+ server.GetConnections().CloseAll();
+ }
+ _controller.GetCurrentConfiguration().portMap.Remove(port.ToString());
+ Dispatcher.Invoke(_controller.Save);
+ });
+ }
}
}