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); + }); + } } }