diff --git a/Shadowsocks.Common/Models/IoCManager.cs b/Shadowsocks.Common/Models/IoCManager.cs deleted file mode 100644 index 5c176290..00000000 --- a/Shadowsocks.Common/Models/IoCManager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using SimpleInjector; - -namespace Shadowsocks.Common.Model -{ - public static class IoCManager - { - public static Container Container { get; } = new Container(); - } -} diff --git a/Shadowsocks.Common/Shadowsocks.Common.csproj b/Shadowsocks.Common/Shadowsocks.Common.csproj deleted file mode 100644 index 5fb972fe..00000000 --- a/Shadowsocks.Common/Shadowsocks.Common.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netstandard2.1 - clowwindy & community 2020 - clowwindy & community 2020 - Shadowsocks Common - - - - - - - - - - diff --git a/Shadowsocks.Crypto/Shadowsocks.Crypto.csproj b/Shadowsocks.Crypto/Shadowsocks.Crypto.csproj deleted file mode 100644 index 4c42fabc..00000000 --- a/Shadowsocks.Crypto/Shadowsocks.Crypto.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netstandard2.1 - clowwindy & community 2020 - Shadowsocks Crypto - - - - - - - - - - - - diff --git a/shadowsocks-csharp/Controller/CachedNetworkStream.cs b/Shadowsocks.Net/CachedNetworkStream.cs similarity index 99% rename from shadowsocks-csharp/Controller/CachedNetworkStream.cs rename to Shadowsocks.Net/CachedNetworkStream.cs index 00738945..468dce7d 100644 --- a/shadowsocks-csharp/Controller/CachedNetworkStream.cs +++ b/Shadowsocks.Net/CachedNetworkStream.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Net.Sockets; @@ -7,7 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Shadowsocks.Controller +namespace Shadowsocks.Net { // cache first packet for duty-chain pattern listener public class CachedNetworkStream : Stream diff --git a/Shadowsocks.Crypto/Crypto/AEAD/AEADAesGcmNativeCrypto.cs b/Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs similarity index 97% rename from Shadowsocks.Crypto/Crypto/AEAD/AEADAesGcmNativeCrypto.cs rename to Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs index b2552479..043f09f1 100644 --- a/Shadowsocks.Crypto/Crypto/AEAD/AEADAesGcmNativeCrypto.cs +++ b/Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Security.Cryptography; -namespace Shadowsocks.Crypto.AEAD +namespace Shadowsocks.Net.Crypto.AEAD { public class AEADAesGcmNativeCrypto : AEADCrypto { diff --git a/Shadowsocks.Crypto/Crypto/AEAD/AEADBouncyCastleCrypto.cs b/Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs similarity index 98% rename from Shadowsocks.Crypto/Crypto/AEAD/AEADBouncyCastleCrypto.cs rename to Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs index 82433bbe..17b6a531 100644 --- a/Shadowsocks.Crypto/Crypto/AEAD/AEADBouncyCastleCrypto.cs +++ b/Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; - using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; +using System; +using System.Collections.Generic; -namespace Shadowsocks.Crypto.AEAD +namespace Shadowsocks.Net.Crypto.AEAD { public class AEADBouncyCastleCrypto : AEADCrypto { diff --git a/Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs b/Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs similarity index 98% rename from Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs rename to Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs index c58b623f..201012ad 100644 --- a/Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs +++ b/Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs @@ -1,15 +1,12 @@ +using Shadowsocks.Net.Crypto.Exception; +using Shadowsocks.Net.Crypto.Stream; using System; using System.Collections.Generic; using System.Net; using System.Runtime.CompilerServices; using System.Text; -using NLog; - -using Shadowsocks.Crypto.Exception; -using Shadowsocks.Crypto.Stream; - -namespace Shadowsocks.Crypto.AEAD +namespace Shadowsocks.Net.Crypto.AEAD { public abstract class AEADCrypto : CryptoBase { diff --git a/Shadowsocks.Crypto/Crypto/AEAD/AEADNaClCrypto.cs b/Shadowsocks.Net/Crypto/AEAD/AEADNaClCrypto.cs similarity index 98% rename from Shadowsocks.Crypto/Crypto/AEAD/AEADNaClCrypto.cs rename to Shadowsocks.Net/Crypto/AEAD/AEADNaClCrypto.cs index f12b229b..6ba1e1c5 100644 --- a/Shadowsocks.Crypto/Crypto/AEAD/AEADNaClCrypto.cs +++ b/Shadowsocks.Net/Crypto/AEAD/AEADNaClCrypto.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections.Generic; - using NaCl.Core; using NaCl.Core.Base; +using System; +using System.Collections.Generic; -namespace Shadowsocks.Crypto.AEAD +namespace Shadowsocks.Net.Crypto.AEAD { public class AEADNaClCrypto : AEADCrypto { diff --git a/Shadowsocks.Crypto/Crypto/CipherInfo.cs b/Shadowsocks.Net/Crypto/CipherInfo.cs similarity index 98% rename from Shadowsocks.Crypto/Crypto/CipherInfo.cs rename to Shadowsocks.Net/Crypto/CipherInfo.cs index 93e1d83c..cb96f1fe 100644 --- a/Shadowsocks.Crypto/Crypto/CipherInfo.cs +++ b/Shadowsocks.Net/Crypto/CipherInfo.cs @@ -1,4 +1,4 @@ -namespace Shadowsocks.Crypto +namespace Shadowsocks.Net.Crypto { public enum CipherFamily { diff --git a/Shadowsocks.Crypto/Crypto/CryptoBase.cs b/Shadowsocks.Net/Crypto/CryptoBase.cs similarity index 97% rename from Shadowsocks.Crypto/Crypto/CryptoBase.cs rename to Shadowsocks.Net/Crypto/CryptoBase.cs index 1fa95af9..f1d945ea 100644 --- a/Shadowsocks.Crypto/Crypto/CryptoBase.cs +++ b/Shadowsocks.Net/Crypto/CryptoBase.cs @@ -1,6 +1,6 @@ using System; -namespace Shadowsocks.Crypto +namespace Shadowsocks.Net.Crypto { public abstract class CryptoBase : ICrypto { diff --git a/Shadowsocks.Crypto/Crypto/CryptoFactory.cs b/Shadowsocks.Net/Crypto/CryptoFactory.cs similarity index 97% rename from Shadowsocks.Crypto/Crypto/CryptoFactory.cs rename to Shadowsocks.Net/Crypto/CryptoFactory.cs index 985e42da..413e5d14 100644 --- a/Shadowsocks.Crypto/Crypto/CryptoFactory.cs +++ b/Shadowsocks.Net/Crypto/CryptoFactory.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Reflection; using System.Text; -using Shadowsocks.Crypto.AEAD; -using Shadowsocks.Crypto.Stream; +using Shadowsocks.Net.Crypto.AEAD; +using Shadowsocks.Net.Crypto.Stream; -namespace Shadowsocks.Crypto +namespace Shadowsocks.Net.Crypto { public static class CryptoFactory { diff --git a/Shadowsocks.Crypto/Util/CryptoUtils.cs b/Shadowsocks.Net/Crypto/CryptoUtils.cs similarity index 98% rename from Shadowsocks.Crypto/Util/CryptoUtils.cs rename to Shadowsocks.Net/Crypto/CryptoUtils.cs index 12c93fa0..8aa75a0c 100644 --- a/Shadowsocks.Crypto/Util/CryptoUtils.cs +++ b/Shadowsocks.Net/Crypto/CryptoUtils.cs @@ -6,7 +6,7 @@ using System; using System.Security.Cryptography; using System.Threading; -namespace Shadowsocks.Crypto +namespace Shadowsocks.Net.Crypto { public static class CryptoUtils { diff --git a/Shadowsocks.Crypto/Crypto/Exception/CryptoException.cs b/Shadowsocks.Net/Crypto/Exception/CryptoException.cs similarity index 89% rename from Shadowsocks.Crypto/Crypto/Exception/CryptoException.cs rename to Shadowsocks.Net/Crypto/Exception/CryptoException.cs index bf05486c..7cd77b7a 100644 --- a/Shadowsocks.Crypto/Crypto/Exception/CryptoException.cs +++ b/Shadowsocks.Net/Crypto/Exception/CryptoException.cs @@ -1,4 +1,4 @@ -namespace Shadowsocks.Crypto.Exception +namespace Shadowsocks.Net.Crypto.Exception { public class CryptoErrorException : System.Exception { diff --git a/Shadowsocks.Crypto/Crypto/ICrypto.cs b/Shadowsocks.Net/Crypto/ICrypto.cs similarity index 90% rename from Shadowsocks.Crypto/Crypto/ICrypto.cs rename to Shadowsocks.Net/Crypto/ICrypto.cs index 04bda0eb..3967fe0c 100644 --- a/Shadowsocks.Crypto/Crypto/ICrypto.cs +++ b/Shadowsocks.Net/Crypto/ICrypto.cs @@ -1,6 +1,6 @@ using System; -namespace Shadowsocks.Crypto +namespace Shadowsocks.Net.Crypto { public interface ICrypto { diff --git a/Shadowsocks.Crypto/Crypto/RNG.cs b/Shadowsocks.Net/Crypto/RNG.cs similarity index 97% rename from Shadowsocks.Crypto/Crypto/RNG.cs rename to Shadowsocks.Net/Crypto/RNG.cs index 5f8711d3..3d9c2ddc 100644 --- a/Shadowsocks.Crypto/Crypto/RNG.cs +++ b/Shadowsocks.Net/Crypto/RNG.cs @@ -1,7 +1,7 @@ using System; using System.Security.Cryptography; -namespace Shadowsocks.Crypto +namespace Shadowsocks.Net.Crypto { public static class RNG { diff --git a/Shadowsocks.Crypto/Crypto/Stream/ExtendedCfbBlockCipher.cs b/Shadowsocks.Net/Crypto/Stream/ExtendedCfbBlockCipher.cs similarity index 100% rename from Shadowsocks.Crypto/Crypto/Stream/ExtendedCfbBlockCipher.cs rename to Shadowsocks.Net/Crypto/Stream/ExtendedCfbBlockCipher.cs diff --git a/Shadowsocks.Crypto/Crypto/Stream/StreamAesBouncyCastleCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs similarity index 98% rename from Shadowsocks.Crypto/Crypto/Stream/StreamAesBouncyCastleCrypto.cs rename to Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs index fde7bb9b..236d5dc9 100644 --- a/Shadowsocks.Crypto/Crypto/Stream/StreamAesBouncyCastleCrypto.cs +++ b/Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Shadowsocks.Crypto.Stream +namespace Shadowsocks.Net.Crypto.Stream { public class StreamAesCfbBouncyCastleCrypto : StreamCrypto diff --git a/Shadowsocks.Crypto/Crypto/Stream/StreamChachaNaClCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamChachaNaClCrypto.cs similarity index 98% rename from Shadowsocks.Crypto/Crypto/Stream/StreamChachaNaClCrypto.cs rename to Shadowsocks.Net/Crypto/Stream/StreamChachaNaClCrypto.cs index 3d007cc2..df65762d 100644 --- a/Shadowsocks.Crypto/Crypto/Stream/StreamChachaNaClCrypto.cs +++ b/Shadowsocks.Net/Crypto/Stream/StreamChachaNaClCrypto.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using NaCl.Core; -namespace Shadowsocks.Crypto.Stream +namespace Shadowsocks.Net.Crypto.Stream { public class StreamChachaNaClCrypto : StreamCrypto { diff --git a/Shadowsocks.Crypto/Crypto/Stream/StreamCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamCrypto.cs similarity index 99% rename from Shadowsocks.Crypto/Crypto/Stream/StreamCrypto.cs rename to Shadowsocks.Net/Crypto/Stream/StreamCrypto.cs index f31485be..385b404a 100644 --- a/Shadowsocks.Crypto/Crypto/Stream/StreamCrypto.cs +++ b/Shadowsocks.Net/Crypto/Stream/StreamCrypto.cs @@ -1,11 +1,9 @@ -using NLog; - using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; -namespace Shadowsocks.Crypto.Stream +namespace Shadowsocks.Net.Crypto.Stream { public abstract class StreamCrypto : CryptoBase { diff --git a/Shadowsocks.Crypto/Crypto/Stream/StreamPlainNativeCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs similarity index 96% rename from Shadowsocks.Crypto/Crypto/Stream/StreamPlainNativeCrypto.cs rename to Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs index c4b02c0c..c09fda2d 100644 --- a/Shadowsocks.Crypto/Crypto/Stream/StreamPlainNativeCrypto.cs +++ b/Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Shadowsocks.Crypto.Stream +namespace Shadowsocks.Net.Crypto.Stream { public class StreamPlainNativeCrypto : StreamCrypto { diff --git a/Shadowsocks.Crypto/Crypto/Stream/StreamRc4NativeCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs similarity index 98% rename from Shadowsocks.Crypto/Crypto/Stream/StreamRc4NativeCrypto.cs rename to Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs index 260a58ae..64b96996 100644 --- a/Shadowsocks.Crypto/Crypto/Stream/StreamRc4NativeCrypto.cs +++ b/Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Shadowsocks.Crypto.Stream +namespace Shadowsocks.Net.Crypto.Stream { public class StreamRc4NativeCrypto : StreamCrypto { diff --git a/Shadowsocks.Crypto/Crypto/TCPInfo.cs b/Shadowsocks.Net/Crypto/TCPInfo.cs similarity index 90% rename from Shadowsocks.Crypto/Crypto/TCPInfo.cs rename to Shadowsocks.Net/Crypto/TCPInfo.cs index 663cd7ef..a542b891 100644 --- a/Shadowsocks.Crypto/Crypto/TCPInfo.cs +++ b/Shadowsocks.Net/Crypto/TCPInfo.cs @@ -1,6 +1,6 @@ -using Shadowsocks.Crypto.AEAD; +using Shadowsocks.Net.Crypto.AEAD; -namespace Shadowsocks.Crypto +namespace Shadowsocks.Net.Crypto { public static class TCPParameter { diff --git a/shadowsocks-csharp/Proxy/DirectConnect.cs b/Shadowsocks.Net/Proxy/DirectConnect.cs similarity index 96% rename from shadowsocks-csharp/Proxy/DirectConnect.cs rename to Shadowsocks.Net/Proxy/DirectConnect.cs index 88506328..02d7fef6 100644 --- a/shadowsocks-csharp/Proxy/DirectConnect.cs +++ b/Shadowsocks.Net/Proxy/DirectConnect.cs @@ -1,11 +1,10 @@ -using System; +using System; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using Shadowsocks.Util.Sockets; -namespace Shadowsocks.Proxy +namespace Shadowsocks.Net.Proxy { public class DirectConnect : IProxy { diff --git a/shadowsocks-csharp/Proxy/HttpProxy.cs b/Shadowsocks.Net/Proxy/HttpProxy.cs similarity index 94% rename from shadowsocks-csharp/Proxy/HttpProxy.cs rename to Shadowsocks.Net/Proxy/HttpProxy.cs index 7ee73ec4..c7e88332 100644 --- a/shadowsocks-csharp/Proxy/HttpProxy.cs +++ b/Shadowsocks.Net/Proxy/HttpProxy.cs @@ -1,117 +1,115 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using NLog; -using Shadowsocks.Controller; -using Shadowsocks.Util.Sockets; - -namespace Shadowsocks.Proxy -{ - public class HttpProxy : IProxy - { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - - public EndPoint LocalEndPoint => _remote.LocalEndPoint; - public EndPoint ProxyEndPoint { get; private set; } - public EndPoint DestEndPoint { get; private set; } - - private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); - - private const string HTTP_CRLF = "\r\n"; - private const string HTTP_CONNECT_TEMPLATE = - "CONNECT {0} HTTP/1.1" + HTTP_CRLF + - "Host: {0}" + HTTP_CRLF + - "Proxy-Connection: keep-alive" + HTTP_CRLF + - "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" + HTTP_CRLF + - "{1}" + // Proxy-Authorization if any - "" + HTTP_CRLF; // End with an empty line - private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF; - - public void Shutdown(SocketShutdown how) - { - _remote.Shutdown(how); - } - - public void Close() - { - _remote.Dispose(); - } - - private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled); - private int _respondLineCount = 0; - private bool _established = false; - - private bool OnLineRead(string line, object state) - { - logger.Trace(line); - - if (_respondLineCount == 0) - { - var m = HttpRespondHeaderRegex.Match(line); - if (m.Success) - { - var resultCode = m.Groups[2].Value; - if ("200" != resultCode) - { - return true; - } - _established = true; - } - } - else - { - if (string.IsNullOrEmpty(line)) - { - return true; - } - } - _respondLineCount++; - - return false; - } - - private NetworkCredential auth; - - public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default) - { - ProxyEndPoint = remoteEP; - this.auth = auth; - await _remote.ConnectAsync(remoteEP); - _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - } - - public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default) - { - DestEndPoint = destEndPoint; - String authInfo = ""; - if (auth != null) - { - string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password)); - authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey); - } - string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo); - - var b = Encoding.UTF8.GetBytes(request); - - await _remote.SendAsync(Encoding.UTF8.GetBytes(request), SocketFlags.None, token); - - // start line read - LineReader reader = new LineReader(_remote, OnLineRead, (e, _) => throw e, (_1, _2, _3, _4) => { }, Encoding.UTF8, HTTP_CRLF, 1024, null); - await reader.Finished; - } - - public async Task SendAsync(ReadOnlyMemory buffer, CancellationToken token = default) - { - return await _remote.SendAsync(buffer, SocketFlags.None, token); - } - - public async Task ReceiveAsync(Memory buffer, CancellationToken token = default) - { - return await _remote.ReceiveAsync(buffer, SocketFlags.None, token); - } - } -} +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using NLog; + +namespace Shadowsocks.Net.Proxy +{ + public class HttpProxy : IProxy + { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + public EndPoint LocalEndPoint => _remote.LocalEndPoint; + public EndPoint ProxyEndPoint { get; private set; } + public EndPoint DestEndPoint { get; private set; } + + private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); + + private const string HTTP_CRLF = "\r\n"; + private const string HTTP_CONNECT_TEMPLATE = + "CONNECT {0} HTTP/1.1" + HTTP_CRLF + + "Host: {0}" + HTTP_CRLF + + "Proxy-Connection: keep-alive" + HTTP_CRLF + + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" + HTTP_CRLF + + "{1}" + // Proxy-Authorization if any + "" + HTTP_CRLF; // End with an empty line + private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF; + + public void Shutdown(SocketShutdown how) + { + _remote.Shutdown(how); + } + + public void Close() + { + _remote.Dispose(); + } + + private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled); + private int _respondLineCount = 0; + private bool _established = false; + + private bool OnLineRead(string line, object state) + { + logger.Trace(line); + + if (_respondLineCount == 0) + { + var m = HttpRespondHeaderRegex.Match(line); + if (m.Success) + { + var resultCode = m.Groups[2].Value; + if ("200" != resultCode) + { + return true; + } + _established = true; + } + } + else + { + if (string.IsNullOrEmpty(line)) + { + return true; + } + } + _respondLineCount++; + + return false; + } + + private NetworkCredential auth; + + public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default) + { + ProxyEndPoint = remoteEP; + this.auth = auth; + await _remote.ConnectAsync(remoteEP); + _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + } + + public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default) + { + DestEndPoint = destEndPoint; + String authInfo = ""; + if (auth != null) + { + string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password)); + authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey); + } + string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo); + + var b = Encoding.UTF8.GetBytes(request); + + await _remote.SendAsync(Encoding.UTF8.GetBytes(request), SocketFlags.None, token); + + // start line read + LineReader reader = new LineReader(_remote, OnLineRead, (e, _) => throw e, (_1, _2, _3, _4) => { }, Encoding.UTF8, HTTP_CRLF, 1024, null); + await reader.Finished; + } + + public async Task SendAsync(ReadOnlyMemory buffer, CancellationToken token = default) + { + return await _remote.SendAsync(buffer, SocketFlags.None, token); + } + + public async Task ReceiveAsync(Memory buffer, CancellationToken token = default) + { + return await _remote.ReceiveAsync(buffer, SocketFlags.None, token); + } + } +} diff --git a/shadowsocks-csharp/Proxy/IProxy.cs b/Shadowsocks.Net/Proxy/IProxy.cs similarity index 94% rename from shadowsocks-csharp/Proxy/IProxy.cs rename to Shadowsocks.Net/Proxy/IProxy.cs index da982744..7c52d47a 100644 --- a/shadowsocks-csharp/Proxy/IProxy.cs +++ b/Shadowsocks.Net/Proxy/IProxy.cs @@ -1,12 +1,11 @@ -using System; +using System; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -namespace Shadowsocks.Proxy +namespace Shadowsocks.Net.Proxy { - public interface IProxy { EndPoint LocalEndPoint { get; } diff --git a/shadowsocks-csharp/Util/Sockets/LineReader.cs b/Shadowsocks.Net/Proxy/LineReader.cs similarity index 99% rename from shadowsocks-csharp/Util/Sockets/LineReader.cs rename to Shadowsocks.Net/Proxy/LineReader.cs index b1bbcc27..93c10751 100644 --- a/shadowsocks-csharp/Util/Sockets/LineReader.cs +++ b/Shadowsocks.Net/Proxy/LineReader.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; -namespace Shadowsocks.Util.Sockets +namespace Shadowsocks.Net.Proxy { public class LineReader { diff --git a/shadowsocks-csharp/Proxy/Socks5Proxy.cs b/Shadowsocks.Net/Proxy/Socks5Proxy.cs similarity index 86% rename from shadowsocks-csharp/Proxy/Socks5Proxy.cs rename to Shadowsocks.Net/Proxy/Socks5Proxy.cs index 828fb83a..7082e0a8 100644 --- a/shadowsocks-csharp/Proxy/Socks5Proxy.cs +++ b/Shadowsocks.Net/Proxy/Socks5Proxy.cs @@ -1,13 +1,11 @@ -using System; +using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using Shadowsocks.Controller; -using Shadowsocks.Util.Sockets; -namespace Shadowsocks.Proxy +namespace Shadowsocks.Net.Proxy { public class Socks5Proxy : IProxy { @@ -37,11 +35,11 @@ namespace Shadowsocks.Proxy await _remote.SendAsync(new byte[] { 5, 1, 0 }, SocketFlags.None); if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 2), SocketFlags.None) != 2) { - throw new Exception(I18N.GetString("Proxy handshake failed")); + throw new Exception("Proxy handshake failed"); } if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0) { - throw new Exception(I18N.GetString("Proxy handshake failed")); + throw new Exception("Proxy handshake failed"); } } @@ -80,14 +78,13 @@ namespace Shadowsocks.Proxy atyp = 4; // IP V6 address break; default: - throw new Exception(I18N.GetString("Proxy request failed")); + throw new Exception("Proxy request failed"); } port = ((IPEndPoint)DestEndPoint).Port; var addr = ((IPEndPoint)DestEndPoint).Address.GetAddressBytes(); Array.Copy(addr, 0, request, 4, request.Length - 4 - 2); } - // 构造request包剩余部分 request[0] = 5; request[1] = 1; request[2] = 0; @@ -99,11 +96,11 @@ namespace Shadowsocks.Proxy if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 4), SocketFlags.None, token) != 4) { - throw new Exception(I18N.GetString("Proxy request failed")); + throw new Exception("Proxy request failed"); }; if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0) { - throw new Exception(I18N.GetString("Proxy request failed")); + throw new Exception("Proxy request failed"); } var addrLen = _receiveBuffer[3] switch { @@ -113,11 +110,8 @@ namespace Shadowsocks.Proxy }; if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, addrLen), SocketFlags.None, token) != addrLen) { - throw new Exception(I18N.GetString("Proxy request failed")); + throw new Exception("Proxy request failed"); } - - - } public async Task SendAsync(ReadOnlyMemory buffer, CancellationToken token = default) diff --git a/Shadowsocks.Net/Shadowsocks.Net.csproj b/Shadowsocks.Net/Shadowsocks.Net.csproj new file mode 100644 index 00000000..7e0a2c6c --- /dev/null +++ b/Shadowsocks.Net/Shadowsocks.Net.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.1 + + + + + + + + diff --git a/Shadowsocks.Common/SystemProxy/ProxyException.cs b/Shadowsocks.Net/SystemProxy/ProxyException.cs similarity index 90% rename from Shadowsocks.Common/SystemProxy/ProxyException.cs rename to Shadowsocks.Net/SystemProxy/ProxyException.cs index ec2866a3..e6494fd0 100644 --- a/Shadowsocks.Common/SystemProxy/ProxyException.cs +++ b/Shadowsocks.Net/SystemProxy/ProxyException.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Runtime.Serialization; -namespace Shadowsocks.Common.SystemProxy +namespace Shadowsocks.Net.SystemProxy { - enum ProxyExceptionType + public enum ProxyExceptionType { Unspecific, FailToRun, @@ -12,7 +12,7 @@ namespace Shadowsocks.Common.SystemProxy QueryReturnMalformed } - class ProxyException : Exception + public class ProxyException : Exception { // provide more specific information about exception public ProxyExceptionType Type { get; } diff --git a/shadowsocks-csharp/Controller/Service/TCPListener.cs b/Shadowsocks.Net/TCPListener.cs similarity index 98% rename from shadowsocks-csharp/Controller/Service/TCPListener.cs rename to Shadowsocks.Net/TCPListener.cs index 6e699184..34e3f69b 100644 --- a/shadowsocks-csharp/Controller/Service/TCPListener.cs +++ b/Shadowsocks.Net/TCPListener.cs @@ -1,5 +1,4 @@ -using NLog; -using Shadowsocks.Model; +using NLog; using System; using System.Collections.Generic; using System.Linq; @@ -7,7 +6,7 @@ using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; -namespace Shadowsocks.Controller +namespace Shadowsocks.Net { public interface IStreamService { diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/Shadowsocks.Net/TCPRelay.cs similarity index 95% rename from shadowsocks-csharp/Controller/Service/TCPRelay.cs rename to Shadowsocks.Net/TCPRelay.cs index 4e93ca2c..c288f9d8 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/Shadowsocks.Net/TCPRelay.cs @@ -1,612 +1,612 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics; -using System.Net; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using NLog; - -using Shadowsocks.Controller.Strategy; -using Shadowsocks.Encryption; -using Shadowsocks.Encryption.AEAD; -using Shadowsocks.Model; -using Shadowsocks.Proxy; -using Shadowsocks.Util.Sockets; - -using static Shadowsocks.Encryption.EncryptorBase; - -namespace Shadowsocks.Controller -{ - class TCPRelay : StreamService - { - public event EventHandler OnConnected; - public event EventHandler OnInbound; - public event EventHandler OnOutbound; - public event EventHandler OnFailed; - - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - private readonly ShadowsocksController _controller; - private DateTime _lastSweepTime; - private readonly Configuration _config; - - public ISet Handlers { get; set; } - - public TCPRelay(ShadowsocksController controller, Configuration conf) - { - _controller = controller; - _config = conf; - Handlers = new HashSet(); - _lastSweepTime = DateTime.Now; - } - - public override bool Handle(CachedNetworkStream stream, object state) - { - - byte[] fp = new byte[256]; - int len = stream.ReadFirstBlock(fp); - - var socket = stream.Socket; - if (socket.ProtocolType != ProtocolType.Tcp - || (len < 2 || fp[0] != 5)) - return false; - - - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - - TCPHandler handler = new TCPHandler(_controller, _config, socket); - - IList handlersToClose = new List(); - lock (Handlers) - { - Handlers.Add(handler); - DateTime now = DateTime.Now; - if (now - _lastSweepTime > TimeSpan.FromSeconds(1)) - { - _lastSweepTime = now; - foreach (TCPHandler handler1 in Handlers) - if (now - handler1.lastActivity > TimeSpan.FromSeconds(900)) - handlersToClose.Add(handler1); - } - } - foreach (TCPHandler handler1 in handlersToClose) - { - logger.Debug("Closing timed out TCP connection."); - handler1.Close(); - } - - /* - * Start after we put it into Handlers set. Otherwise if it failed in handler.Start() - * then it will call handler.Close() before we add it into the set. - * Then the handler will never release until the next Handle call. Sometimes it will - * cause odd problems (especially during memory profiling). - */ - // handler.Start(fp, len); - _ = handler.StartAsync(fp, len); - - return true; - // return Handle(fp, len, stream.Socket, state); - } - - [Obsolete] - public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) - { - if (socket.ProtocolType != ProtocolType.Tcp - || (length < 2 || firstPacket[0] != 5)) - { - return false; - } - - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - TCPHandler handler = new TCPHandler(_controller, _config, socket); - - handler.OnConnected += OnConnected; - handler.OnInbound += OnInbound; - handler.OnOutbound += OnOutbound; - handler.OnFailed += OnFailed; - handler.OnClosed += (h, arg) => - { - lock (Handlers) - { - Handlers.Remove(handler); - } - }; - - IList handlersToClose = new List(); - lock (Handlers) - { - Handlers.Add(handler); - DateTime now = DateTime.Now; - if (now - _lastSweepTime > TimeSpan.FromSeconds(1)) - { - _lastSweepTime = now; - foreach (TCPHandler handler1 in Handlers) - { - if (now - handler1.lastActivity > TimeSpan.FromSeconds(900)) - { - handlersToClose.Add(handler1); - } - } - } - } - foreach (TCPHandler handler1 in handlersToClose) - { - logger.Debug("Closing timed out TCP connection."); - handler1.Close(); - } - - /* - * Start after we put it into Handlers set. Otherwise if it failed in handler.Start() - * then it will call handler.Close() before we add it into the set. - * Then the handler will never release until the next Handle call. Sometimes it will - * cause odd problems (especially during memory profiling). - */ - // handler.Start(firstPacket, length); - _ = handler.StartAsync(firstPacket, length); - - return true; - } - - public override void Stop() - { - List handlersToClose = new List(); - lock (Handlers) - { - handlersToClose.AddRange(Handlers); - } - handlersToClose.ForEach(h => h.Close()); - } - } - - public class SSRelayEventArgs : EventArgs - { - public readonly Server server; - - public SSRelayEventArgs(Server server) - { - this.server = server; - } - } - - public class SSTransmitEventArgs : SSRelayEventArgs - { - public readonly long length; - public SSTransmitEventArgs(Server server, long length) : base(server) - { - this.length = length; - } - } - - public class SSTCPConnectedEventArgs : SSRelayEventArgs - { - public readonly TimeSpan latency; - - public SSTCPConnectedEventArgs(Server server, TimeSpan latency) : base(server) - { - this.latency = latency; - } - } - - internal class TCPHandler - { - public event EventHandler OnConnected; - public event EventHandler OnInbound; - public event EventHandler OnOutbound; - public event EventHandler OnClosed; - public event EventHandler OnFailed; - - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private readonly int _serverTimeout; - private readonly int _proxyTimeout; - - private readonly MemoryPool pool = MemoryPool.Shared; - // each recv size. - public const int RecvSize = 16384; - - // overhead of one chunk, reserved for AEAD ciphers - public const int ChunkOverheadSize = 100;//16 * 2 /* two tags */ + AEADEncryptor.ChunkLengthBytes; - - // In general, the ciphertext length, we should take overhead into account - public const int SendSize = 32768; - - public DateTime lastActivity; - - private readonly ShadowsocksController _controller; - private readonly ForwardProxyConfig _config; - private readonly Socket _connection; - private IProxy _remote; - private IEncryptor encryptor; - // workaround - private IEncryptor decryptor; - private Server _server; - - private byte[] _firstPacket; - private int _firstPacketLength; - - private const int CMD_CONNECT = 0x01; - private const int CMD_BIND = 0x02; - private const int CMD_UDP_ASSOC = 0x03; - - private bool _closed = false; - - // instance-based lock without static - private readonly object _encryptionLock = new object(); - private readonly object _decryptionLock = new object(); - private readonly object _closeConnLock = new object(); - - // TODO: decouple controller - public TCPHandler(ShadowsocksController controller, Configuration config, Socket socket) - { - _controller = controller; - _config = config.proxy; - _connection = socket; - _proxyTimeout = config.proxy.proxyTimeout * 1000; - _serverTimeout = config.GetCurrentServer().timeout * 1000; - - lastActivity = DateTime.Now; - } - - public void CreateRemote(EndPoint destination) - { - Server server = _controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)_connection.RemoteEndPoint, destination); - if (server == null || server.server == "") - { - throw new ArgumentException("No server configured"); - } - - encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); - decryptor = EncryptorFactory.GetEncryptor(server.method, server.password); - _server = server; - } - - public async Task StartAsync(byte[] firstPacket, int length) - { - _firstPacket = firstPacket; - _firstPacketLength = length; - (int cmd, EndPoint dst) = await Socks5Handshake(); - if (cmd == CMD_CONNECT) - { - await ConnectRemote(dst); - await SendAddress(dst); - await Forward(); - } - else if (cmd == CMD_UDP_ASSOC) - { - await DrainConnection(); - } - } - - private void ErrorClose(Exception e) - { - Logger.LogUsefulException(e); - Close(); - } - - public void Close() - { - lock (_closeConnLock) - { - if (_closed) - { - return; - } - - _closed = true; - } - - OnClosed?.Invoke(this, new SSRelayEventArgs(_server)); - - try - { - _connection.Shutdown(SocketShutdown.Both); - _connection.Close(); - } - catch (Exception e) - { - Logger.LogUsefulException(e); - } - } - - async Task<(int cmd, EndPoint destination)> Socks5Handshake() - { - // not so strict here - // 5 2 1 2 should return 5 255 - // 5 1 0 5 / 1 0 1 127 0 0 1 0 80 will cause handshake fail - - int bytesRead = _firstPacketLength; - if (bytesRead <= 1) - { - Close(); - return (0, default); - } - - byte[] response = { 5, 0 }; - if (_firstPacket[0] != 5) - { - // reject socks 4 - response = new byte[] { 0, 91 }; - Logger.Error("socks 5 protocol error"); - } - await _connection.SendAsync(response, SocketFlags.None); - - using var bufOwner = pool.Rent(512); - var buf = bufOwner.Memory; - - if (await _connection.ReceiveAsync(buf.Slice(0, 5), SocketFlags.None) != 5) - { - Close(); - return (0, default); - } - - var cmd = buf.Span[1]; - EndPoint dst = default; - switch (cmd) - { - case CMD_CONNECT: - await _connection.SendAsync(new byte[] { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, SocketFlags.None); - dst = await ReadAddress(buf); - // start forward - break; - case CMD_UDP_ASSOC: - dst = await ReadAddress(buf); - await SendUdpAssociate(); - // drain - break; - default: - Close(); - break; - } - return (cmd, dst); - } - - async Task DrainConnection() - { - if (_closed) - { - return; - } - using var b = pool.Rent(512); - try - { - int l; - do - { - l = await _connection.ReceiveAsync(b.Memory, SocketFlags.None); - } - while (l > 0); - - Close(); - } - catch (Exception e) - { - ErrorClose(e); - } - } - - private async Task ReadAddress(Memory buf) - { - var atyp = buf.Span[3]; - var maybeDomainLength = buf.Span[4]; - buf.Span[0] = atyp; - buf.Span[1] = maybeDomainLength; - - int toRead = atyp switch - { - ATYP_IPv4 => 4, - ATYP_IPv6 => 16, - ATYP_DOMAIN => maybeDomainLength + 1, - _ => throw new NotSupportedException(), - } + 2 - 1; - await _connection.ReceiveAsync(buf.Slice(2, toRead), SocketFlags.None); - - return GetSocks5EndPoint(buf.ToArray()); - } - - private int ReadPort(byte[] arr, long offset) - { - return (arr[offset] << 8) + arr[offset + 1]; - } - - private EndPoint GetSocks5EndPoint(byte[] buf) - { - int maybeDomainLength = buf[1] + 2; - - return (buf[0]) switch - { - ATYP_IPv4 => new IPEndPoint(new IPAddress(buf[1..5]), ReadPort(buf, 5)), - ATYP_IPv6 => new IPEndPoint(new IPAddress(buf[1..17]), ReadPort(buf, 17)), - ATYP_DOMAIN => new DnsEndPoint(Encoding.ASCII.GetString(buf[2..maybeDomainLength]), ReadPort(buf, maybeDomainLength)), - _ => throw new NotSupportedException(), - }; - } - - private async Task SendUdpAssociate() - { - IPEndPoint endPoint = (IPEndPoint)_connection.LocalEndPoint; - byte[] address = endPoint.Address.GetAddressBytes(); - int port = endPoint.Port; - byte[] response = new byte[4 + address.Length + ADDR_PORT_LEN]; - response[0] = 5; - switch (endPoint.AddressFamily) - { - case AddressFamily.InterNetwork: - response[3] = ATYP_IPv4; - break; - case AddressFamily.InterNetworkV6: - response[3] = ATYP_IPv6; - break; - } - address.CopyTo(response, 4); - response[^1] = (byte)(port & 0xFF); - response[^2] = (byte)((port >> 8) & 0xFF); - await _connection.SendAsync(response, SocketFlags.None); - } - - private async Task ConnectRemote(EndPoint destination) - { - CreateRemote(destination); - IProxy remote; - EndPoint proxyEP = null; - EndPoint serverEP = SocketUtil.GetEndPoint(_server.server, _server.server_port); - EndPoint pluginEP = _controller.GetPluginLocalEndPointIfConfigured(_server); - - NetworkCredential auth = null; - if (_config.useAuth) - { - auth = new NetworkCredential(_config.authUser, _config.authPwd); - } - if (pluginEP != null) - { - serverEP = pluginEP; - remote = new DirectConnect(); - } - else if (_config.useProxy) - { - remote = _config.proxyType switch - { - ForwardProxyConfig.PROXY_SOCKS5 => new Socks5Proxy(), - ForwardProxyConfig.PROXY_HTTP => new HttpProxy(), - _ => throw new NotSupportedException("Unknown forward proxy."), - }; - proxyEP = SocketUtil.GetEndPoint(_config.proxyServer, _config.proxyPort); - } - else - { - remote = new DirectConnect(); - } - - - CancellationTokenSource cancelProxy = new CancellationTokenSource(_proxyTimeout * 1000); - - await remote.ConnectProxyAsync(proxyEP, auth, cancelProxy.Token); - _remote = remote; - - if (!(remote is DirectConnect)) - { - Logger.Debug($"Socket connected to proxy {remote.ProxyEndPoint}"); - } - - var _startConnectTime = DateTime.Now; - CancellationTokenSource cancelServer = new CancellationTokenSource(_serverTimeout * 1000); - await remote.ConnectRemoteAsync(serverEP, cancelServer.Token); - Logger.Debug($"Socket connected to ss server: {_server}"); - TimeSpan latency = DateTime.Now - _startConnectTime; - OnConnected?.Invoke(this, new SSTCPConnectedEventArgs(_server, latency)); - - } - - private async Task SendAddress(EndPoint dest) - { - byte[] dstByte = GetSocks5EndPointByte(dest); - using var t = pool.Rent(512); - try - { - int addrlen = encryptor.Encrypt(dstByte, t.Memory.Span); - await _remote.SendAsync(t.Memory.Slice(0, addrlen)); - } - catch (Exception e) - { - ErrorClose(e); - } - } - - private byte[] GetSocks5EndPointByte(EndPoint dest) - { - if (dest is DnsEndPoint d) - { - byte[] r = new byte[d.Host.Length + 4]; - r[0] = 3; - r[1] = (byte)d.Host.Length; - Encoding.ASCII.GetBytes(d.Host, r.AsSpan(2)); - r[^2] = (byte)(d.Port / 256); - r[^1] = (byte)(d.Port % 256); - return r; - } - else if (dest is IPEndPoint i) - { - if (i.AddressFamily == AddressFamily.InterNetwork) - { - byte[] r = new byte[7]; - r[0] = 1; - i.Address.GetAddressBytes().CopyTo(r, 1); - r[^2] = (byte)(i.Port / 256); - r[^1] = (byte)(i.Port % 256); - return r; - } - else if (i.AddressFamily == AddressFamily.InterNetworkV6) - { - byte[] r = new byte[19]; - r[0] = 1; - i.Address.GetAddressBytes().CopyTo(r, 1); - r[^2] = (byte)(i.Port / 256); - r[^1] = (byte)(i.Port % 256); - return r; - } - } - throw new NotImplementedException(); - } - - private async Task Forward() - { - try - { - await Task.WhenAll(ForwardInbound(), ForwardOutbound()); - Close(); - } - catch (Exception e) - { - ErrorClose(e); - } - } - - private async Task ForwardInbound() - { - using var cipherOwner = pool.Rent(RecvSize); - using var plainOwner = pool.Rent(SendSize); - var plain = plainOwner.Memory; - var cipher = cipherOwner.Memory; - try - { - - while (true) - { - int len = await _remote.ReceiveAsync(cipher); - if (len == 0) break; - int plen = decryptor.Decrypt(plain.Span, cipher.Span.Slice(0, len)); - if (plen == 0) continue; - int len2 = await _connection.SendAsync(plain.Slice(0, plen), SocketFlags.None); - if (len2 == 0) break; - OnInbound?.Invoke(this, new SSTransmitEventArgs(_server, plen)); - } - } - catch (Exception e) - { - ErrorClose(e); - } - } - - private async Task ForwardOutbound() - { - using var plainOwner = pool.Rent(RecvSize); - using var cipherOwner = pool.Rent(SendSize); - var plain = plainOwner.Memory; - var cipher = cipherOwner.Memory; - while (true) - { - int len = await _connection.ReceiveAsync(plain, SocketFlags.None); - - if (len == 0) break; - int clen = encryptor.Encrypt(plain.Span.Slice(0, len), cipher.Span); - int len2 = await _remote.SendAsync(cipher.Slice(0, clen)); - if (len2 == 0) break; - OnOutbound?.Invoke(this, new SSTransmitEventArgs(_server, len)); - } - _remote.Shutdown(SocketShutdown.Send); - } - } +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using NLog; + +using Shadowsocks.Controller.Strategy; +using Shadowsocks.Net.Crypto; +using Shadowsocks.Net.Crypto.AEAD; +using Shadowsocks.Model; +using Shadowsocks.Net.Proxy; +using Shadowsocks.Net.Sockets; + +using static Shadowsocks.Net.Crypto.CryptoBase; + +namespace Shadowsocks.Net +{ + class TCPRelay : StreamService + { + public event EventHandler OnConnected; + public event EventHandler OnInbound; + public event EventHandler OnOutbound; + public event EventHandler OnFailed; + + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private readonly ShadowsocksController _controller; + private DateTime _lastSweepTime; + private readonly Configuration _config; + + public ISet Handlers { get; set; } + + public TCPRelay(ShadowsocksController controller, Configuration conf) + { + _controller = controller; + _config = conf; + Handlers = new HashSet(); + _lastSweepTime = DateTime.Now; + } + + public override bool Handle(CachedNetworkStream stream, object state) + { + + byte[] fp = new byte[256]; + int len = stream.ReadFirstBlock(fp); + + var socket = stream.Socket; + if (socket.ProtocolType != ProtocolType.Tcp + || (len < 2 || fp[0] != 5)) + return false; + + + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + + TCPHandler handler = new TCPHandler(_controller, _config, socket); + + IList handlersToClose = new List(); + lock (Handlers) + { + Handlers.Add(handler); + DateTime now = DateTime.Now; + if (now - _lastSweepTime > TimeSpan.FromSeconds(1)) + { + _lastSweepTime = now; + foreach (TCPHandler handler1 in Handlers) + if (now - handler1.lastActivity > TimeSpan.FromSeconds(900)) + handlersToClose.Add(handler1); + } + } + foreach (TCPHandler handler1 in handlersToClose) + { + logger.Debug("Closing timed out TCP connection."); + handler1.Close(); + } + + /* + * Start after we put it into Handlers set. Otherwise if it failed in handler.Start() + * then it will call handler.Close() before we add it into the set. + * Then the handler will never release until the next Handle call. Sometimes it will + * cause odd problems (especially during memory profiling). + */ + // handler.Start(fp, len); + _ = handler.StartAsync(fp, len); + + return true; + // return Handle(fp, len, stream.Socket, state); + } + + [Obsolete] + public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) + { + if (socket.ProtocolType != ProtocolType.Tcp + || (length < 2 || firstPacket[0] != 5)) + { + return false; + } + + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + TCPHandler handler = new TCPHandler(_controller, _config, socket); + + handler.OnConnected += OnConnected; + handler.OnInbound += OnInbound; + handler.OnOutbound += OnOutbound; + handler.OnFailed += OnFailed; + handler.OnClosed += (h, arg) => + { + lock (Handlers) + { + Handlers.Remove(handler); + } + }; + + IList handlersToClose = new List(); + lock (Handlers) + { + Handlers.Add(handler); + DateTime now = DateTime.Now; + if (now - _lastSweepTime > TimeSpan.FromSeconds(1)) + { + _lastSweepTime = now; + foreach (TCPHandler handler1 in Handlers) + { + if (now - handler1.lastActivity > TimeSpan.FromSeconds(900)) + { + handlersToClose.Add(handler1); + } + } + } + } + foreach (TCPHandler handler1 in handlersToClose) + { + logger.Debug("Closing timed out TCP connection."); + handler1.Close(); + } + + /* + * Start after we put it into Handlers set. Otherwise if it failed in handler.Start() + * then it will call handler.Close() before we add it into the set. + * Then the handler will never release until the next Handle call. Sometimes it will + * cause odd problems (especially during memory profiling). + */ + // handler.Start(firstPacket, length); + _ = handler.StartAsync(firstPacket, length); + + return true; + } + + public override void Stop() + { + List handlersToClose = new List(); + lock (Handlers) + { + handlersToClose.AddRange(Handlers); + } + handlersToClose.ForEach(h => h.Close()); + } + } + + public class SSRelayEventArgs : EventArgs + { + public readonly Server server; + + public SSRelayEventArgs(Server server) + { + this.server = server; + } + } + + public class SSTransmitEventArgs : SSRelayEventArgs + { + public readonly long length; + public SSTransmitEventArgs(Server server, long length) : base(server) + { + this.length = length; + } + } + + public class SSTCPConnectedEventArgs : SSRelayEventArgs + { + public readonly TimeSpan latency; + + public SSTCPConnectedEventArgs(Server server, TimeSpan latency) : base(server) + { + this.latency = latency; + } + } + + internal class TCPHandler + { + public event EventHandler OnConnected; + public event EventHandler OnInbound; + public event EventHandler OnOutbound; + public event EventHandler OnClosed; + public event EventHandler OnFailed; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private readonly int _serverTimeout; + private readonly int _proxyTimeout; + + private readonly MemoryPool pool = MemoryPool.Shared; + // each recv size. + public const int RecvSize = 16384; + + // overhead of one chunk, reserved for AEAD ciphers + public const int ChunkOverheadSize = 100;//16 * 2 /* two tags */ + AEADEncryptor.ChunkLengthBytes; + + // In general, the ciphertext length, we should take overhead into account + public const int SendSize = 32768; + + public DateTime lastActivity; + + private readonly ShadowsocksController _controller; + private readonly ForwardProxyConfig _config; + private readonly Socket _connection; + private IProxy _remote; + private IEncryptor encryptor; + // workaround + private IEncryptor decryptor; + private Server _server; + + private byte[] _firstPacket; + private int _firstPacketLength; + + private const int CMD_CONNECT = 0x01; + private const int CMD_BIND = 0x02; + private const int CMD_UDP_ASSOC = 0x03; + + private bool _closed = false; + + // instance-based lock without static + private readonly object _encryptionLock = new object(); + private readonly object _decryptionLock = new object(); + private readonly object _closeConnLock = new object(); + + // TODO: decouple controller + public TCPHandler(ShadowsocksController controller, Configuration config, Socket socket) + { + _controller = controller; + _config = config.proxy; + _connection = socket; + _proxyTimeout = config.proxy.proxyTimeout * 1000; + _serverTimeout = config.GetCurrentServer().timeout * 1000; + + lastActivity = DateTime.Now; + } + + public void CreateRemote(EndPoint destination) + { + Server server = _controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)_connection.RemoteEndPoint, destination); + if (server == null || server.server == "") + { + throw new ArgumentException("No server configured"); + } + + encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); + decryptor = EncryptorFactory.GetEncryptor(server.method, server.password); + _server = server; + } + + public async Task StartAsync(byte[] firstPacket, int length) + { + _firstPacket = firstPacket; + _firstPacketLength = length; + (int cmd, EndPoint dst) = await Socks5Handshake(); + if (cmd == CMD_CONNECT) + { + await ConnectRemote(dst); + await SendAddress(dst); + await Forward(); + } + else if (cmd == CMD_UDP_ASSOC) + { + await DrainConnection(); + } + } + + private void ErrorClose(Exception e) + { + Logger.LogUsefulException(e); + Close(); + } + + public void Close() + { + lock (_closeConnLock) + { + if (_closed) + { + return; + } + + _closed = true; + } + + OnClosed?.Invoke(this, new SSRelayEventArgs(_server)); + + try + { + _connection.Shutdown(SocketShutdown.Both); + _connection.Close(); + } + catch (Exception e) + { + Logger.LogUsefulException(e); + } + } + + async Task<(int cmd, EndPoint destination)> Socks5Handshake() + { + // not so strict here + // 5 2 1 2 should return 5 255 + // 5 1 0 5 / 1 0 1 127 0 0 1 0 80 will cause handshake fail + + int bytesRead = _firstPacketLength; + if (bytesRead <= 1) + { + Close(); + return (0, default); + } + + byte[] response = { 5, 0 }; + if (_firstPacket[0] != 5) + { + // reject socks 4 + response = new byte[] { 0, 91 }; + Logger.Error("socks 5 protocol error"); + } + await _connection.SendAsync(response, SocketFlags.None); + + using var bufOwner = pool.Rent(512); + var buf = bufOwner.Memory; + + if (await _connection.ReceiveAsync(buf.Slice(0, 5), SocketFlags.None) != 5) + { + Close(); + return (0, default); + } + + var cmd = buf.Span[1]; + EndPoint dst = default; + switch (cmd) + { + case CMD_CONNECT: + await _connection.SendAsync(new byte[] { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, SocketFlags.None); + dst = await ReadAddress(buf); + // start forward + break; + case CMD_UDP_ASSOC: + dst = await ReadAddress(buf); + await SendUdpAssociate(); + // drain + break; + default: + Close(); + break; + } + return (cmd, dst); + } + + async Task DrainConnection() + { + if (_closed) + { + return; + } + using var b = pool.Rent(512); + try + { + int l; + do + { + l = await _connection.ReceiveAsync(b.Memory, SocketFlags.None); + } + while (l > 0); + + Close(); + } + catch (Exception e) + { + ErrorClose(e); + } + } + + private async Task ReadAddress(Memory buf) + { + var atyp = buf.Span[3]; + var maybeDomainLength = buf.Span[4]; + buf.Span[0] = atyp; + buf.Span[1] = maybeDomainLength; + + int toRead = atyp switch + { + ATYP_IPv4 => 4, + ATYP_IPv6 => 16, + ATYP_DOMAIN => maybeDomainLength + 1, + _ => throw new NotSupportedException(), + } + 2 - 1; + await _connection.ReceiveAsync(buf.Slice(2, toRead), SocketFlags.None); + + return GetSocks5EndPoint(buf.ToArray()); + } + + private int ReadPort(byte[] arr, long offset) + { + return (arr[offset] << 8) + arr[offset + 1]; + } + + private EndPoint GetSocks5EndPoint(byte[] buf) + { + int maybeDomainLength = buf[1] + 2; + + return (buf[0]) switch + { + ATYP_IPv4 => new IPEndPoint(new IPAddress(buf[1..5]), ReadPort(buf, 5)), + ATYP_IPv6 => new IPEndPoint(new IPAddress(buf[1..17]), ReadPort(buf, 17)), + ATYP_DOMAIN => new DnsEndPoint(Encoding.ASCII.GetString(buf[2..maybeDomainLength]), ReadPort(buf, maybeDomainLength)), + _ => throw new NotSupportedException(), + }; + } + + private async Task SendUdpAssociate() + { + IPEndPoint endPoint = (IPEndPoint)_connection.LocalEndPoint; + byte[] address = endPoint.Address.GetAddressBytes(); + int port = endPoint.Port; + byte[] response = new byte[4 + address.Length + ADDR_PORT_LEN]; + response[0] = 5; + switch (endPoint.AddressFamily) + { + case AddressFamily.InterNetwork: + response[3] = ATYP_IPv4; + break; + case AddressFamily.InterNetworkV6: + response[3] = ATYP_IPv6; + break; + } + address.CopyTo(response, 4); + response[^1] = (byte)(port & 0xFF); + response[^2] = (byte)((port >> 8) & 0xFF); + await _connection.SendAsync(response, SocketFlags.None); + } + + private async Task ConnectRemote(EndPoint destination) + { + CreateRemote(destination); + IProxy remote; + EndPoint proxyEP = null; + EndPoint serverEP = new DnsEndPoint(_server.server, _server.server_port); + EndPoint pluginEP = _controller.GetPluginLocalEndPointIfConfigured(_server); + + NetworkCredential auth = null; + if (_config.useAuth) + { + auth = new NetworkCredential(_config.authUser, _config.authPwd); + } + if (pluginEP != null) + { + serverEP = pluginEP; + remote = new DirectConnect(); + } + else if (_config.useProxy) + { + remote = _config.proxyType switch + { + ForwardProxyConfig.PROXY_SOCKS5 => new Socks5Proxy(), + ForwardProxyConfig.PROXY_HTTP => new HttpProxy(), + _ => throw new NotSupportedException("Unknown forward proxy."), + }; + proxyEP = new DnsEndPoint(_config.proxyServer, _config.proxyPort); + } + else + { + remote = new DirectConnect(); + } + + + CancellationTokenSource cancelProxy = new CancellationTokenSource(_proxyTimeout * 1000); + + await remote.ConnectProxyAsync(proxyEP, auth, cancelProxy.Token); + _remote = remote; + + if (!(remote is DirectConnect)) + { + Logger.Debug($"Socket connected to proxy {remote.ProxyEndPoint}"); + } + + var _startConnectTime = DateTime.Now; + CancellationTokenSource cancelServer = new CancellationTokenSource(_serverTimeout * 1000); + await remote.ConnectRemoteAsync(serverEP, cancelServer.Token); + Logger.Debug($"Socket connected to ss server: {_server}"); + TimeSpan latency = DateTime.Now - _startConnectTime; + OnConnected?.Invoke(this, new SSTCPConnectedEventArgs(_server, latency)); + + } + + private async Task SendAddress(EndPoint dest) + { + byte[] dstByte = GetSocks5EndPointByte(dest); + using var t = pool.Rent(512); + try + { + int addrlen = encryptor.Encrypt(dstByte, t.Memory.Span); + await _remote.SendAsync(t.Memory.Slice(0, addrlen)); + } + catch (Exception e) + { + ErrorClose(e); + } + } + + private byte[] GetSocks5EndPointByte(EndPoint dest) + { + if (dest is DnsEndPoint d) + { + byte[] r = new byte[d.Host.Length + 4]; + r[0] = 3; + r[1] = (byte)d.Host.Length; + Encoding.ASCII.GetBytes(d.Host, r.AsSpan(2)); + r[^2] = (byte)(d.Port / 256); + r[^1] = (byte)(d.Port % 256); + return r; + } + else if (dest is IPEndPoint i) + { + if (i.AddressFamily == AddressFamily.InterNetwork) + { + byte[] r = new byte[7]; + r[0] = 1; + i.Address.GetAddressBytes().CopyTo(r, 1); + r[^2] = (byte)(i.Port / 256); + r[^1] = (byte)(i.Port % 256); + return r; + } + else if (i.AddressFamily == AddressFamily.InterNetworkV6) + { + byte[] r = new byte[19]; + r[0] = 1; + i.Address.GetAddressBytes().CopyTo(r, 1); + r[^2] = (byte)(i.Port / 256); + r[^1] = (byte)(i.Port % 256); + return r; + } + } + throw new NotImplementedException(); + } + + private async Task Forward() + { + try + { + await Task.WhenAll(ForwardInbound(), ForwardOutbound()); + Close(); + } + catch (Exception e) + { + ErrorClose(e); + } + } + + private async Task ForwardInbound() + { + using var cipherOwner = pool.Rent(RecvSize); + using var plainOwner = pool.Rent(SendSize); + var plain = plainOwner.Memory; + var cipher = cipherOwner.Memory; + try + { + + while (true) + { + int len = await _remote.ReceiveAsync(cipher); + if (len == 0) break; + int plen = decryptor.Decrypt(plain.Span, cipher.Span.Slice(0, len)); + if (plen == 0) continue; + int len2 = await _connection.SendAsync(plain.Slice(0, plen), SocketFlags.None); + if (len2 == 0) break; + OnInbound?.Invoke(this, new SSTransmitEventArgs(_server, plen)); + } + } + catch (Exception e) + { + ErrorClose(e); + } + } + + private async Task ForwardOutbound() + { + using var plainOwner = pool.Rent(RecvSize); + using var cipherOwner = pool.Rent(SendSize); + var plain = plainOwner.Memory; + var cipher = cipherOwner.Memory; + while (true) + { + int len = await _connection.ReceiveAsync(plain, SocketFlags.None); + + if (len == 0) break; + int clen = encryptor.Encrypt(plain.Span.Slice(0, len), cipher.Span); + int len2 = await _remote.SendAsync(cipher.Slice(0, clen)); + if (len2 == 0) break; + OnOutbound?.Invoke(this, new SSTransmitEventArgs(_server, len)); + } + _remote.Shutdown(SocketShutdown.Send); + } + } } \ No newline at end of file diff --git a/shadowsocks-csharp/Controller/Service/UDPListener.cs b/Shadowsocks.Net/UDPListener.cs similarity index 100% rename from shadowsocks-csharp/Controller/Service/UDPListener.cs rename to Shadowsocks.Net/UDPListener.cs diff --git a/shadowsocks-csharp/Controller/Service/UDPRelay.cs b/Shadowsocks.Net/UDPRelay.cs similarity index 95% rename from shadowsocks-csharp/Controller/Service/UDPRelay.cs rename to Shadowsocks.Net/UDPRelay.cs index 3f8e6f2c..cd2e317f 100644 --- a/shadowsocks-csharp/Controller/Service/UDPRelay.cs +++ b/Shadowsocks.Net/UDPRelay.cs @@ -1,232 +1,230 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using NLog; -using Shadowsocks.Controller.Strategy; -using Shadowsocks.Encryption; -using Shadowsocks.Model; - -namespace Shadowsocks.Controller -{ - class UDPRelay : DatagramService - { - private ShadowsocksController _controller; - - // TODO: choose a smart number - private LRUCache _cache = new LRUCache(512); - - public long outbound = 0; - public long inbound = 0; - - public UDPRelay(ShadowsocksController controller) - { - this._controller = controller; - } - - public override async Task Handle(Memory packet, Socket socket, EndPoint client) - { - if (socket.ProtocolType != ProtocolType.Udp) - { - return false; - } - if (packet.Length < 4) - { - return false; - } - IPEndPoint remoteEndPoint = (IPEndPoint)client; - UDPHandler handler = _cache.get(remoteEndPoint); - if (handler == null) - { - handler = new UDPHandler(socket, _controller.GetAServer(IStrategyCallerType.UDP, remoteEndPoint, null/*TODO: fix this*/), remoteEndPoint); - handler.Receive(); - _cache.add(remoteEndPoint, handler); - } - await handler.SendAsync(packet); - return true; - } - - public class UDPHandler - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - private static MemoryPool pool = MemoryPool.Shared; - private Socket _local; - private Socket _remote; - - private Server _server; - private byte[] _buffer = new byte[65536]; - - private IPEndPoint _localEndPoint; - private IPEndPoint _remoteEndPoint; - - private IPAddress ListenAddress - { - get - { - return _remote.AddressFamily switch - { - AddressFamily.InterNetwork => IPAddress.Any, - AddressFamily.InterNetworkV6 => IPAddress.IPv6Any, - _ => throw new NotSupportedException(), - }; - } - } - - public UDPHandler(Socket local, Server server, IPEndPoint localEndPoint) - { - _local = local; - _server = server; - _localEndPoint = localEndPoint; - - // TODO async resolving - bool parsed = IPAddress.TryParse(server.server, out IPAddress ipAddress); - if (!parsed) - { - IPHostEntry ipHostInfo = Dns.GetHostEntry(server.server); - ipAddress = ipHostInfo.AddressList[0]; - } - _remoteEndPoint = new IPEndPoint(ipAddress, server.server_port); - _remote = new Socket(_remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); - _remote.Bind(new IPEndPoint(ListenAddress, 0)); - } - - public async Task SendAsync(ReadOnlyMemory data) - { - IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password); - using IMemoryOwner mem = pool.Rent(data.Length + 1000); - - // byte[] dataOut = new byte[slicedData.Length + 1000]; - int outlen = encryptor.EncryptUDP(data.Span[3..], mem.Memory.Span); - logger.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay up"); - if (!MemoryMarshal.TryGetArray(mem.Memory[..outlen], out ArraySegment outData)) - { - throw new InvalidOperationException("Can't extract underly array segment"); - }; - await _remote?.SendToAsync(outData, SocketFlags.None, _remoteEndPoint); - } - - public async Task ReceiveAsync() - { - EndPoint remoteEndPoint = new IPEndPoint(ListenAddress, 0); - logger.Debug($"++++++Receive Server Port, size:" + _buffer.Length); - try - { - while (true) - { - var result = await _remote.ReceiveFromAsync(_buffer, SocketFlags.None, remoteEndPoint); - int bytesRead = result.ReceivedBytes; - - using IMemoryOwner owner = pool.Rent(bytesRead + 3); - Memory o = owner.Memory; - - IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password); - int outlen = encryptor.DecryptUDP(o.Span[3..], _buffer.AsSpan(0, bytesRead)); - logger.Debug(_remoteEndPoint, _localEndPoint, outlen, "UDP Relay down"); - if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment data)) - { - throw new InvalidOperationException("Can't extract underly array segment"); - }; - await _local?.SendToAsync(data, SocketFlags.None, _localEndPoint); - - } - } - catch (Exception e) - { - logger.LogUsefulException(e); - } - } - - public void Receive() - { - _ = ReceiveAsync(); - } - - public void Close() - { - try - { - _remote?.Close(); - } - catch (ObjectDisposedException) - { - // TODO: handle the ObjectDisposedException - } - catch (Exception) - { - // TODO: need more think about handle other Exceptions, or should remove this catch(). - } - } - } - } - - #region LRU cache - - // cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054 - class LRUCache where V : UDPRelay.UDPHandler - { - private int capacity; - private Dictionary>> cacheMap = new Dictionary>>(); - private LinkedList> lruList = new LinkedList>(); - - public LRUCache(int capacity) - { - this.capacity = capacity; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public V get(K key) - { - LinkedListNode> node; - if (cacheMap.TryGetValue(key, out node)) - { - V value = node.Value.value; - lruList.Remove(node); - lruList.AddLast(node); - return value; - } - return default(V); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public void add(K key, V val) - { - if (cacheMap.Count >= capacity) - { - RemoveFirst(); - } - - LRUCacheItem cacheItem = new LRUCacheItem(key, val); - LinkedListNode> node = new LinkedListNode>(cacheItem); - lruList.AddLast(node); - cacheMap.Add(key, node); - } - - private void RemoveFirst() - { - // Remove from LRUPriority - LinkedListNode> node = lruList.First; - lruList.RemoveFirst(); - - // Remove from cache - cacheMap.Remove(node.Value.key); - node.Value.value.Close(); - } - } - - class LRUCacheItem - { - public LRUCacheItem(K k, V v) - { - key = k; - value = v; - } - public K key; - public V value; - } - - #endregion -} +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using NLog; +using Shadowsocks.Net.Crypto; + +namespace Shadowsocks.Controller +{ + class UDPRelay : DatagramService + { + private ShadowsocksController _controller; + + // TODO: choose a smart number + private LRUCache _cache = new LRUCache(512); + + public long outbound = 0; + public long inbound = 0; + + public UDPRelay(ShadowsocksController controller) + { + this._controller = controller; + } + + public override async Task Handle(Memory packet, Socket socket, EndPoint client) + { + if (socket.ProtocolType != ProtocolType.Udp) + { + return false; + } + if (packet.Length < 4) + { + return false; + } + IPEndPoint remoteEndPoint = (IPEndPoint)client; + UDPHandler handler = _cache.get(remoteEndPoint); + if (handler == null) + { + handler = new UDPHandler(socket, _controller.GetAServer(IStrategyCallerType.UDP, remoteEndPoint, null/*TODO: fix this*/), remoteEndPoint); + handler.Receive(); + _cache.add(remoteEndPoint, handler); + } + await handler.SendAsync(packet); + return true; + } + + public class UDPHandler + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + private static MemoryPool pool = MemoryPool.Shared; + private Socket _local; + private Socket _remote; + + private Server _server; + private byte[] _buffer = new byte[65536]; + + private IPEndPoint _localEndPoint; + private IPEndPoint _remoteEndPoint; + + private IPAddress ListenAddress + { + get + { + return _remote.AddressFamily switch + { + AddressFamily.InterNetwork => IPAddress.Any, + AddressFamily.InterNetworkV6 => IPAddress.IPv6Any, + _ => throw new NotSupportedException(), + }; + } + } + + public UDPHandler(Socket local, Server server, IPEndPoint localEndPoint) + { + _local = local; + _server = server; + _localEndPoint = localEndPoint; + + // TODO async resolving + bool parsed = IPAddress.TryParse(server.server, out IPAddress ipAddress); + if (!parsed) + { + IPHostEntry ipHostInfo = Dns.GetHostEntry(server.server); + ipAddress = ipHostInfo.AddressList[0]; + } + _remoteEndPoint = new IPEndPoint(ipAddress, server.server_port); + _remote = new Socket(_remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + _remote.Bind(new IPEndPoint(ListenAddress, 0)); + } + + public async Task SendAsync(ReadOnlyMemory data) + { + IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password); + using IMemoryOwner mem = pool.Rent(data.Length + 1000); + + // byte[] dataOut = new byte[slicedData.Length + 1000]; + int outlen = encryptor.EncryptUDP(data.Span[3..], mem.Memory.Span); + logger.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay up"); + if (!MemoryMarshal.TryGetArray(mem.Memory[..outlen], out ArraySegment outData)) + { + throw new InvalidOperationException("Can't extract underly array segment"); + }; + await _remote?.SendToAsync(outData, SocketFlags.None, _remoteEndPoint); + } + + public async Task ReceiveAsync() + { + EndPoint remoteEndPoint = new IPEndPoint(ListenAddress, 0); + logger.Debug($"++++++Receive Server Port, size:" + _buffer.Length); + try + { + while (true) + { + var result = await _remote.ReceiveFromAsync(_buffer, SocketFlags.None, remoteEndPoint); + int bytesRead = result.ReceivedBytes; + + using IMemoryOwner owner = pool.Rent(bytesRead + 3); + Memory o = owner.Memory; + + IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password); + int outlen = encryptor.DecryptUDP(o.Span[3..], _buffer.AsSpan(0, bytesRead)); + logger.Debug(_remoteEndPoint, _localEndPoint, outlen, "UDP Relay down"); + if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment data)) + { + throw new InvalidOperationException("Can't extract underly array segment"); + }; + await _local?.SendToAsync(data, SocketFlags.None, _localEndPoint); + + } + } + catch (Exception e) + { + logger.LogUsefulException(e); + } + } + + public void Receive() + { + _ = ReceiveAsync(); + } + + public void Close() + { + try + { + _remote?.Close(); + } + catch (ObjectDisposedException) + { + // TODO: handle the ObjectDisposedException + } + catch (Exception) + { + // TODO: need more think about handle other Exceptions, or should remove this catch(). + } + } + } + } + + #region LRU cache + + // cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054 + class LRUCache where V : UDPRelay.UDPHandler + { + private int capacity; + private Dictionary>> cacheMap = new Dictionary>>(); + private LinkedList> lruList = new LinkedList>(); + + public LRUCache(int capacity) + { + this.capacity = capacity; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public V get(K key) + { + LinkedListNode> node; + if (cacheMap.TryGetValue(key, out node)) + { + V value = node.Value.value; + lruList.Remove(node); + lruList.AddLast(node); + return value; + } + return default(V); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void add(K key, V val) + { + if (cacheMap.Count >= capacity) + { + RemoveFirst(); + } + + LRUCacheItem cacheItem = new LRUCacheItem(key, val); + LinkedListNode> node = new LinkedListNode>(cacheItem); + lruList.AddLast(node); + cacheMap.Add(key, node); + } + + private void RemoveFirst() + { + // Remove from LRUPriority + LinkedListNode> node = lruList.First; + lruList.RemoveFirst(); + + // Remove from cache + cacheMap.Remove(node.Value.key); + node.Value.value.Close(); + } + } + + class LRUCacheItem + { + public LRUCacheItem(K k, V v) + { + key = k; + value = v; + } + public K key; + public V value; + } + + #endregion +} diff --git a/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs b/Shadowsocks.PAC/GeositeUpdater.cs similarity index 99% rename from shadowsocks-csharp/Controller/Service/GeositeUpdater.cs rename to Shadowsocks.PAC/GeositeUpdater.cs index 40c64bdc..0a655adc 100644 --- a/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs +++ b/Shadowsocks.PAC/GeositeUpdater.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using Shadowsocks.Properties; using Shadowsocks.Util; using System; @@ -13,7 +13,7 @@ using System.Net.Http; using System.Threading.Tasks; using System.Security.Cryptography; -namespace Shadowsocks.Controller +namespace Shadowsocks.PAC { public class GeositeResultEventArgs : EventArgs { @@ -21,7 +21,7 @@ namespace Shadowsocks.Controller public GeositeResultEventArgs(bool success) { - this.Success = success; + Success = success; } } diff --git a/shadowsocks-csharp/Controller/Service/PACDaemon.cs b/Shadowsocks.PAC/PACDaemon.cs similarity index 96% rename from shadowsocks-csharp/Controller/Service/PACDaemon.cs rename to Shadowsocks.PAC/PACDaemon.cs index 217f0fda..1189e469 100644 --- a/shadowsocks-csharp/Controller/Service/PACDaemon.cs +++ b/Shadowsocks.PAC/PACDaemon.cs @@ -1,133 +1,133 @@ -using NLog; -using Shadowsocks.Model; -using Shadowsocks.Properties; -using Shadowsocks.Util; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Shadowsocks.Controller -{ - - /// - /// Processing the PAC file content - /// - public class PACDaemon - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - public const string PAC_FILE = "pac.txt"; - public const string USER_RULE_FILE = "user-rule.txt"; - public const string USER_ABP_FILE = "abp.txt"; - private Configuration config; - - FileSystemWatcher PACFileWatcher; - FileSystemWatcher UserRuleFileWatcher; - - public event EventHandler PACFileChanged; - public event EventHandler UserRuleFileChanged; - - public PACDaemon(Configuration config) - { - this.config = config; - TouchPACFile(); - TouchUserRuleFile(); - - this.WatchPacFile(); - this.WatchUserRuleFile(); - } - - - public string TouchPACFile() - { - if (!File.Exists(PAC_FILE)) - { - GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect); - } - return PAC_FILE; - } - - internal string TouchUserRuleFile() - { - if (!File.Exists(USER_RULE_FILE)) - { - File.WriteAllText(USER_RULE_FILE, Resources.user_rule); - } - return USER_RULE_FILE; - } - - internal string GetPACContent() - { - if (!File.Exists(PAC_FILE)) - { - GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect); - } - return File.ReadAllText(PAC_FILE, Encoding.UTF8); - } - - - private void WatchPacFile() - { - PACFileWatcher?.Dispose(); - PACFileWatcher = new FileSystemWatcher(Program.WorkingDirectory); - PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; - PACFileWatcher.Filter = PAC_FILE; - PACFileWatcher.Changed += PACFileWatcher_Changed; - PACFileWatcher.Created += PACFileWatcher_Changed; - PACFileWatcher.Deleted += PACFileWatcher_Changed; - PACFileWatcher.Renamed += PACFileWatcher_Changed; - PACFileWatcher.EnableRaisingEvents = true; - } - - private void WatchUserRuleFile() - { - UserRuleFileWatcher?.Dispose(); - UserRuleFileWatcher = new FileSystemWatcher(Program.WorkingDirectory); - UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; - UserRuleFileWatcher.Filter = USER_RULE_FILE; - UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; - UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed; - UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed; - UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed; - UserRuleFileWatcher.EnableRaisingEvents = true; - } - - #region FileSystemWatcher.OnChanged() - // FileSystemWatcher Changed event is raised twice - // http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice - // Add a short delay to avoid raise event twice in a short period - private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e) - { - if (PACFileChanged != null) - { - logger.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); - Task.Factory.StartNew(() => - { - ((FileSystemWatcher)sender).EnableRaisingEvents = false; - System.Threading.Thread.Sleep(10); - PACFileChanged(this, new EventArgs()); - ((FileSystemWatcher)sender).EnableRaisingEvents = true; - }); - } - } - - private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e) - { - if (UserRuleFileChanged != null) - { - logger.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); - Task.Factory.StartNew(() => - { - ((FileSystemWatcher)sender).EnableRaisingEvents = false; - System.Threading.Thread.Sleep(10); - UserRuleFileChanged(this, new EventArgs()); - ((FileSystemWatcher)sender).EnableRaisingEvents = true; - }); - } - } - #endregion - } -} +using NLog; +using Shadowsocks.Model; +using Shadowsocks.Properties; +using Shadowsocks.Util; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Shadowsocks.PAC +{ + + /// + /// Processing the PAC file content + /// + public class PACDaemon + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public const string PAC_FILE = "pac.txt"; + public const string USER_RULE_FILE = "user-rule.txt"; + public const string USER_ABP_FILE = "abp.txt"; + private Configuration config; + + FileSystemWatcher PACFileWatcher; + FileSystemWatcher UserRuleFileWatcher; + + public event EventHandler PACFileChanged; + public event EventHandler UserRuleFileChanged; + + public PACDaemon(Configuration config) + { + this.config = config; + TouchPACFile(); + TouchUserRuleFile(); + + this.WatchPacFile(); + this.WatchUserRuleFile(); + } + + + public string TouchPACFile() + { + if (!File.Exists(PAC_FILE)) + { + GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect); + } + return PAC_FILE; + } + + internal string TouchUserRuleFile() + { + if (!File.Exists(USER_RULE_FILE)) + { + File.WriteAllText(USER_RULE_FILE, Resources.user_rule); + } + return USER_RULE_FILE; + } + + internal string GetPACContent() + { + if (!File.Exists(PAC_FILE)) + { + GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect); + } + return File.ReadAllText(PAC_FILE, Encoding.UTF8); + } + + + private void WatchPacFile() + { + PACFileWatcher?.Dispose(); + PACFileWatcher = new FileSystemWatcher(Program.WorkingDirectory); + PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; + PACFileWatcher.Filter = PAC_FILE; + PACFileWatcher.Changed += PACFileWatcher_Changed; + PACFileWatcher.Created += PACFileWatcher_Changed; + PACFileWatcher.Deleted += PACFileWatcher_Changed; + PACFileWatcher.Renamed += PACFileWatcher_Changed; + PACFileWatcher.EnableRaisingEvents = true; + } + + private void WatchUserRuleFile() + { + UserRuleFileWatcher?.Dispose(); + UserRuleFileWatcher = new FileSystemWatcher(Program.WorkingDirectory); + UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; + UserRuleFileWatcher.Filter = USER_RULE_FILE; + UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.EnableRaisingEvents = true; + } + + #region FileSystemWatcher.OnChanged() + // FileSystemWatcher Changed event is raised twice + // http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice + // Add a short delay to avoid raise event twice in a short period + private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e) + { + if (PACFileChanged != null) + { + logger.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); + Task.Factory.StartNew(() => + { + ((FileSystemWatcher)sender).EnableRaisingEvents = false; + System.Threading.Thread.Sleep(10); + PACFileChanged(this, new EventArgs()); + ((FileSystemWatcher)sender).EnableRaisingEvents = true; + }); + } + } + + private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e) + { + if (UserRuleFileChanged != null) + { + logger.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); + Task.Factory.StartNew(() => + { + ((FileSystemWatcher)sender).EnableRaisingEvents = false; + System.Threading.Thread.Sleep(10); + UserRuleFileChanged(this, new EventArgs()); + ((FileSystemWatcher)sender).EnableRaisingEvents = true; + }); + } + } + #endregion + } +} diff --git a/shadowsocks-csharp/Controller/Service/PACServer.cs b/Shadowsocks.PAC/PACServer.cs similarity index 92% rename from shadowsocks-csharp/Controller/Service/PACServer.cs rename to Shadowsocks.PAC/PACServer.cs index 49585816..4da05095 100644 --- a/shadowsocks-csharp/Controller/Service/PACServer.cs +++ b/Shadowsocks.PAC/PACServer.cs @@ -1,210 +1,209 @@ -using Shadowsocks.Encryption; -using Shadowsocks.Model; -using Shadowsocks.Util; -using System; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Web; -using NLog; - -namespace Shadowsocks.Controller -{ - public class PACServer : StreamService - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - public const string RESOURCE_NAME = "pac"; - - private string PacSecret - { - get - { - if (string.IsNullOrEmpty(_cachedPacSecret)) - { - _cachedPacSecret = HttpServerUtilityUrlToken.Encode(RNG.GetBytes(32)); - } - return _cachedPacSecret; - } - } - private string _cachedPacSecret = ""; - public string PacUrl { get; private set; } = ""; - - private Configuration _config; - private PACDaemon _pacDaemon; - - public PACServer(PACDaemon pacDaemon) - { - _pacDaemon = pacDaemon; - } - - public void UpdatePACURL(Configuration config) - { - _config = config; - string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : ""; - string contentHash = GetHash(_pacDaemon.GetPACContent()); - PacUrl = $"http://{config.LocalHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}"; - logger.Debug("Set PAC URL:" + PacUrl); - } - - private static string GetHash(string content) - { - - return HttpServerUtilityUrlToken.Encode(CryptoUtils.MD5(Encoding.ASCII.GetBytes(content))); - } - - public override bool Handle(CachedNetworkStream stream, object state) - { - byte[] fp = new byte[256]; - int len = stream.ReadFirstBlock(fp); - return Handle(fp, len, stream.Socket, state); - } - - [Obsolete] - public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) - { - if (socket.ProtocolType != ProtocolType.Tcp) - { - return false; - } - - try - { - /* - * RFC 7230 - * - GET /hello.txt HTTP/1.1 - User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 - Host: www.example.com - Accept-Language: en, mi - */ - - string request = Encoding.UTF8.GetString(firstPacket, 0, length); - string[] lines = request.Split('\r', '\n'); - bool hostMatch = false, pathMatch = false, useSocks = false; - bool secretMatch = !_config.secureLocalPac; - - if (lines.Length < 2) // need at lease RequestLine + Host - { - return false; - } - - // parse request line - string requestLine = lines[0]; - // GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1 - string[] requestItems = requestLine.Split(' '); - if (requestItems.Length == 3 && requestItems[0] == "GET") - { - int index = requestItems[1].IndexOf('?'); - if (index < 0) - { - index = requestItems[1].Length; - } - string resourceString = requestItems[1].Substring(0, index).Remove(0, 1); - if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase)) - { - pathMatch = true; - if (!secretMatch) - { - string queryString = requestItems[1].Substring(index); - if (queryString.Contains(PacSecret)) - { - secretMatch = true; - } - } - } - } - - // parse request header - for (int i = 1; i < lines.Length; i++) - { - if (string.IsNullOrEmpty(lines[i])) - continue; - - string[] kv = lines[i].Split(new char[] { ':' }, 2); - if (kv.Length == 2) - { - if (kv[0] == "Host") - { - if (kv[1].Trim() == ((IPEndPoint)socket.LocalEndPoint).ToString()) - { - hostMatch = true; - } - } - //else if (kv[0] == "User-Agent") - //{ - // // we need to drop connections when changing servers - // if (kv[1].IndexOf("Chrome") >= 0) - // { - // useSocks = true; - // } - //} - } - } - - if (hostMatch && pathMatch) - { - if (!secretMatch) - { - socket.Close(); // Close immediately - } - else - { - SendResponse(socket, useSocks); - } - return true; - } - return false; - } - catch (ArgumentException) - { - return false; - } - } - - public void SendResponse(Socket socket, bool useSocks) - { - try - { - IPEndPoint localEndPoint = (IPEndPoint)socket.LocalEndPoint; - - string proxy = GetPACAddress(localEndPoint, useSocks); - - string pacContent = $"var __PROXY__ = '{proxy}';\n" + _pacDaemon.GetPACContent(); - string responseHead = -$@"HTTP/1.1 200 OK -Server: ShadowsocksWindows/{UpdateChecker.Version} -Content-Type: application/x-ns-proxy-autoconfig -Content-Length: { Encoding.UTF8.GetBytes(pacContent).Length} -Connection: Close - -"; - byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent); - socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket); - } - catch (Exception e) - { - logger.LogUsefulException(e); - socket.Close(); - } - } - - private void SendCallback(IAsyncResult ar) - { - Socket conn = (Socket)ar.AsyncState; - try - { - conn.Shutdown(SocketShutdown.Send); - } - catch - { } - } - - private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) - { - return localEndPoint.AddressFamily == AddressFamily.InterNetworkV6 - ? $"{(useSocks ? "SOCKS5" : "PROXY")} [{localEndPoint.Address}]:{_config.localPort};" - : $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint.Address}:{_config.localPort};"; - } - } -} +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using NLog; +using Shadowsocks.Net; +using Shadowsocks.Utilities; +using Shadowsocks.Net.Crypto; + +namespace Shadowsocks.PAC +{ + public class PACServer : StreamService + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public const string RESOURCE_NAME = "pac"; + + private string PacSecret + { + get + { + if (string.IsNullOrEmpty(_cachedPacSecret)) + { + _cachedPacSecret = Base64Url.Encode(RNG.GetBytes(32)); + } + return _cachedPacSecret; + } + } + private string _cachedPacSecret = ""; + public string PacUrl { get; private set; } = ""; + + private Configuration _config; + private PACDaemon _pacDaemon; + + public PACServer(PACDaemon pacDaemon) + { + _pacDaemon = pacDaemon; + } + + public void UpdatePACURL(Configuration config) + { + _config = config; + string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : ""; + string contentHash = GetHash(_pacDaemon.GetPACContent()); + PacUrl = $"http://{config.LocalHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}"; + logger.Debug("Set PAC URL:" + PacUrl); + } + + private static string GetHash(string content) + { + + return Base64Url.Encode(CryptoUtils.MD5(Encoding.ASCII.GetBytes(content))); + } + + public override bool Handle(CachedNetworkStream stream, object state) + { + byte[] fp = new byte[256]; + int len = stream.ReadFirstBlock(fp); + return Handle(fp, len, stream.Socket, state); + } + + [Obsolete] + public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) + { + if (socket.ProtocolType != ProtocolType.Tcp) + { + return false; + } + + try + { + /* + * RFC 7230 + * + GET /hello.txt HTTP/1.1 + User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 + Host: www.example.com + Accept-Language: en, mi + */ + + string request = Encoding.UTF8.GetString(firstPacket, 0, length); + string[] lines = request.Split('\r', '\n'); + bool hostMatch = false, pathMatch = false, useSocks = false; + bool secretMatch = !_config.secureLocalPac; + + if (lines.Length < 2) // need at lease RequestLine + Host + { + return false; + } + + // parse request line + string requestLine = lines[0]; + // GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1 + string[] requestItems = requestLine.Split(' '); + if (requestItems.Length == 3 && requestItems[0] == "GET") + { + int index = requestItems[1].IndexOf('?'); + if (index < 0) + { + index = requestItems[1].Length; + } + string resourceString = requestItems[1].Substring(0, index).Remove(0, 1); + if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase)) + { + pathMatch = true; + if (!secretMatch) + { + string queryString = requestItems[1].Substring(index); + if (queryString.Contains(PacSecret)) + { + secretMatch = true; + } + } + } + } + + // parse request header + for (int i = 1; i < lines.Length; i++) + { + if (string.IsNullOrEmpty(lines[i])) + continue; + + string[] kv = lines[i].Split(new char[] { ':' }, 2); + if (kv.Length == 2) + { + if (kv[0] == "Host") + { + if (kv[1].Trim() == ((IPEndPoint)socket.LocalEndPoint).ToString()) + { + hostMatch = true; + } + } + //else if (kv[0] == "User-Agent") + //{ + // // we need to drop connections when changing servers + // if (kv[1].IndexOf("Chrome") >= 0) + // { + // useSocks = true; + // } + //} + } + } + + if (hostMatch && pathMatch) + { + if (!secretMatch) + { + socket.Close(); // Close immediately + } + else + { + SendResponse(socket, useSocks); + } + return true; + } + return false; + } + catch (ArgumentException) + { + return false; + } + } + + public void SendResponse(Socket socket, bool useSocks) + { + try + { + IPEndPoint localEndPoint = (IPEndPoint)socket.LocalEndPoint; + + string proxy = GetPACAddress(localEndPoint, useSocks); + + string pacContent = $"var __PROXY__ = '{proxy}';\n" + _pacDaemon.GetPACContent(); + string responseHead = +$@"HTTP/1.1 200 OK +Server: ShadowsocksWindows/{UpdateChecker.Version} +Content-Type: application/x-ns-proxy-autoconfig +Content-Length: { Encoding.UTF8.GetBytes(pacContent).Length} +Connection: Close + +"; + byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent); + socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket); + } + catch (Exception e) + { + logger.LogUsefulException(e); + socket.Close(); + } + } + + private void SendCallback(IAsyncResult ar) + { + Socket conn = (Socket)ar.AsyncState; + try + { + conn.Shutdown(SocketShutdown.Send); + } + catch + { } + } + + private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) + { + return localEndPoint.AddressFamily == AddressFamily.InterNetworkV6 + ? $"{(useSocks ? "SOCKS5" : "PROXY")} [{localEndPoint.Address}]:{_config.localPort};" + : $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint.Address}:{_config.localPort};"; + } + } +} diff --git a/Shadowsocks/Assets/abp.js b/Shadowsocks.PAC/Resources/abp.js similarity index 100% rename from Shadowsocks/Assets/abp.js rename to Shadowsocks.PAC/Resources/abp.js diff --git a/Shadowsocks/Assets/dlc.dat b/Shadowsocks.PAC/Resources/dlc.dat similarity index 100% rename from Shadowsocks/Assets/dlc.dat rename to Shadowsocks.PAC/Resources/dlc.dat diff --git a/Shadowsocks/Assets/user-rule.txt b/Shadowsocks.PAC/Resources/user-rule.txt similarity index 100% rename from Shadowsocks/Assets/user-rule.txt rename to Shadowsocks.PAC/Resources/user-rule.txt diff --git a/Shadowsocks.PAC/Shadowsocks.PAC.csproj b/Shadowsocks.PAC/Shadowsocks.PAC.csproj new file mode 100644 index 00000000..9b7805f6 --- /dev/null +++ b/Shadowsocks.PAC/Shadowsocks.PAC.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.1 + + + + + + + + diff --git a/shadowsocks-csharp/Model/Geosite/Geosite.cs b/Shadowsocks.Protobuf/Geosite.cs similarity index 100% rename from shadowsocks-csharp/Model/Geosite/Geosite.cs rename to Shadowsocks.Protobuf/Geosite.cs diff --git a/Shadowsocks.Protobuf/Shadowsocks.Protobuf.csproj b/Shadowsocks.Protobuf/Shadowsocks.Protobuf.csproj new file mode 100644 index 00000000..7ef6635d --- /dev/null +++ b/Shadowsocks.Protobuf/Shadowsocks.Protobuf.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp3.1 + + + + + + + diff --git a/shadowsocks-csharp/Model/Geosite/geosite.proto b/Shadowsocks.Protobuf/geosite.proto similarity index 100% rename from shadowsocks-csharp/Model/Geosite/geosite.proto rename to Shadowsocks.Protobuf/geosite.proto diff --git a/Shadowsocks.WPF/App.xaml b/Shadowsocks.WPF/App.xaml index b283b871..a3501c68 100644 --- a/Shadowsocks.WPF/App.xaml +++ b/Shadowsocks.WPF/App.xaml @@ -2,12 +2,12 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Shadowsocks.WPF" - xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" StartupUri="Views/MainWindow.xaml"> - + diff --git a/shadowsocks-csharp/Controller/System/AutoStartup.cs b/Shadowsocks.WPF/Behaviors/AutoStartup.cs similarity index 96% rename from shadowsocks-csharp/Controller/System/AutoStartup.cs rename to Shadowsocks.WPF/Behaviors/AutoStartup.cs index 97127b66..a1ee8301 100644 --- a/shadowsocks-csharp/Controller/System/AutoStartup.cs +++ b/Shadowsocks.WPF/Behaviors/AutoStartup.cs @@ -1,153 +1,153 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using Microsoft.Win32; -using NLog; -using Shadowsocks.Util; - -namespace Shadowsocks.Controller -{ - static class AutoStartup - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - // Don't use Application.ExecutablePath - // see https://stackoverflow.com/questions/12945805/odd-c-sharp-path-issue - - private static string Key = "Shadowsocks_" + Program.ExecutablePath.GetHashCode(); - - public static bool Set(bool enabled) - { - RegistryKey runKey = null; - try - { - runKey = Utils.OpenRegKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); - if (runKey == null) - { - logger.Error(@"Cannot find HKCU\Software\Microsoft\Windows\CurrentVersion\Run"); - return false; - } - if (enabled) - { - runKey.SetValue(Key, Program.ExecutablePath); - } - else - { - runKey.DeleteValue(Key); - } - // When autostartup setting change, change RegisterForRestart state to avoid start 2 times - RegisterForRestart(!enabled); - return true; - } - catch (Exception e) - { - logger.LogUsefulException(e); - return false; - } - finally - { - if (runKey != null) - { - try - { - runKey.Close(); - runKey.Dispose(); - } - catch (Exception e) - { logger.LogUsefulException(e); } - } - } - } - - public static bool Check() - { - RegistryKey runKey = null; - try - { - runKey = Utils.OpenRegKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); - if (runKey == null) - { - logger.Error(@"Cannot find HKCU\Software\Microsoft\Windows\CurrentVersion\Run"); - return false; - } - string[] runList = runKey.GetValueNames(); - foreach (string item in runList) - { - if (item.Equals(Key, StringComparison.OrdinalIgnoreCase)) - return true; - else if (item.Equals("Shadowsocks", StringComparison.OrdinalIgnoreCase)) // Compatibility with older versions - { - string value = Convert.ToString(runKey.GetValue(item)); - if (Program.ExecutablePath.Equals(value, StringComparison.OrdinalIgnoreCase)) - { - runKey.DeleteValue(item); - runKey.SetValue(Key, Program.ExecutablePath); - return true; - } - } - } - return false; - } - catch (Exception e) - { - logger.LogUsefulException(e); - return false; - } - finally - { - if (runKey != null) - { - try - { - runKey.Close(); - runKey.Dispose(); - } - catch (Exception e) - { logger.LogUsefulException(e); } - } - } - } - - [DllImport("kernel32.dll", SetLastError = true)] - static extern int RegisterApplicationRestart([MarshalAs(UnmanagedType.LPWStr)] string commandLineArgs, int Flags); - - [DllImport("kernel32.dll", SetLastError = true)] - static extern int UnregisterApplicationRestart(); - - [Flags] - enum ApplicationRestartFlags - { - RESTART_ALWAYS = 0, - RESTART_NO_CRASH = 1, - RESTART_NO_HANG = 2, - RESTART_NO_PATCH = 4, - RESTART_NO_REBOOT = 8, - } - - // register restart after system reboot/update - public static void RegisterForRestart(bool register) - { - // requested register and not autostartup - if (register && !Check()) - { - // escape command line parameter - string[] args = new List(Program.Args) - .Select(p => p.Replace("\"", "\\\"")) // escape " to \" - .Select(p => p.IndexOf(" ") >= 0 ? "\"" + p + "\"" : p) // encapsule with " - .ToArray(); - string cmdline = string.Join(" ", args); - // first parameter is process command line parameter - // needn't include the name of the executable in the command line - RegisterApplicationRestart(cmdline, (int)(ApplicationRestartFlags.RESTART_NO_CRASH | ApplicationRestartFlags.RESTART_NO_HANG)); - logger.Debug("Register restart after system reboot, command line:" + cmdline); - } - // requested unregister, which has no side effect - else if (!register) - { - UnregisterApplicationRestart(); - logger.Debug("Unregister restart after system reboot"); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.Win32; +using NLog; +using Shadowsocks.Util; + +namespace Shadowsocks.WPF.Behaviors +{ + static class AutoStartup + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + // Don't use Application.ExecutablePath + // see https://stackoverflow.com/questions/12945805/odd-c-sharp-path-issue + + private static string Key = "Shadowsocks_" + Program.ExecutablePath.GetHashCode(); + + public static bool Set(bool enabled) + { + RegistryKey runKey = null; + try + { + runKey = Utils.OpenRegKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); + if (runKey == null) + { + logger.Error(@"Cannot find HKCU\Software\Microsoft\Windows\CurrentVersion\Run"); + return false; + } + if (enabled) + { + runKey.SetValue(Key, Program.ExecutablePath); + } + else + { + runKey.DeleteValue(Key); + } + // When autostartup setting change, change RegisterForRestart state to avoid start 2 times + RegisterForRestart(!enabled); + return true; + } + catch (Exception e) + { + logger.LogUsefulException(e); + return false; + } + finally + { + if (runKey != null) + { + try + { + runKey.Close(); + runKey.Dispose(); + } + catch (Exception e) + { logger.LogUsefulException(e); } + } + } + } + + public static bool Check() + { + RegistryKey runKey = null; + try + { + runKey = Utils.OpenRegKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); + if (runKey == null) + { + logger.Error(@"Cannot find HKCU\Software\Microsoft\Windows\CurrentVersion\Run"); + return false; + } + string[] runList = runKey.GetValueNames(); + foreach (string item in runList) + { + if (item.Equals(Key, StringComparison.OrdinalIgnoreCase)) + return true; + else if (item.Equals("Shadowsocks", StringComparison.OrdinalIgnoreCase)) // Compatibility with older versions + { + string value = Convert.ToString(runKey.GetValue(item)); + if (Program.ExecutablePath.Equals(value, StringComparison.OrdinalIgnoreCase)) + { + runKey.DeleteValue(item); + runKey.SetValue(Key, Program.ExecutablePath); + return true; + } + } + } + return false; + } + catch (Exception e) + { + logger.LogUsefulException(e); + return false; + } + finally + { + if (runKey != null) + { + try + { + runKey.Close(); + runKey.Dispose(); + } + catch (Exception e) + { logger.LogUsefulException(e); } + } + } + } + + [DllImport("kernel32.dll", SetLastError = true)] + static extern int RegisterApplicationRestart([MarshalAs(UnmanagedType.LPWStr)] string commandLineArgs, int Flags); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern int UnregisterApplicationRestart(); + + [Flags] + enum ApplicationRestartFlags + { + RESTART_ALWAYS = 0, + RESTART_NO_CRASH = 1, + RESTART_NO_HANG = 2, + RESTART_NO_PATCH = 4, + RESTART_NO_REBOOT = 8, + } + + // register restart after system reboot/update + public static void RegisterForRestart(bool register) + { + // requested register and not autostartup + if (register && !Check()) + { + // escape command line parameter + string[] args = new List(Program.Args) + .Select(p => p.Replace("\"", "\\\"")) // escape " to \" + .Select(p => p.IndexOf(" ") >= 0 ? "\"" + p + "\"" : p) // encapsule with " + .ToArray(); + string cmdline = string.Join(" ", args); + // first parameter is process command line parameter + // needn't include the name of the executable in the command line + RegisterApplicationRestart(cmdline, (int)(ApplicationRestartFlags.RESTART_NO_CRASH | ApplicationRestartFlags.RESTART_NO_HANG)); + logger.Debug("Register restart after system reboot, command line:" + cmdline); + } + // requested unregister, which has no side effect + else if (!register) + { + UnregisterApplicationRestart(); + logger.Debug("Unregister restart after system reboot"); + } + } + } +} diff --git a/shadowsocks-csharp/Controller/FileManager.cs b/Shadowsocks.WPF/Behaviors/FileManager.cs old mode 100755 new mode 100644 similarity index 94% rename from shadowsocks-csharp/Controller/FileManager.cs rename to Shadowsocks.WPF/Behaviors/FileManager.cs index ef30470d..477fd167 --- a/shadowsocks-csharp/Controller/FileManager.cs +++ b/Shadowsocks.WPF/Behaviors/FileManager.cs @@ -1,68 +1,68 @@ -using NLog; -using System; -using System.IO; -using System.IO.Compression; -using System.Text; - -namespace Shadowsocks.Controller -{ - public static class FileManager - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - public static bool ByteArrayToFile(string fileName, byte[] content) - { - try - { - using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) - fs.Write(content, 0, content.Length); - return true; - } - catch (Exception ex) - { - logger.Error(ex); - } - return false; - } - - public static void UncompressFile(string fileName, byte[] content) - { - // Because the uncompressed size of the file is unknown, - // we are using an arbitrary buffer size. - byte[] buffer = new byte[4096]; - int n; - - using(var fs = File.Create(fileName)) - using (var input = new GZipStream(new MemoryStream(content), - CompressionMode.Decompress, false)) - { - while ((n = input.Read(buffer, 0, buffer.Length)) > 0) - { - fs.Write(buffer, 0, n); - } - } - } - - public static string NonExclusiveReadAllText(string path) - { - return NonExclusiveReadAllText(path, Encoding.Default); - } - - public static string NonExclusiveReadAllText(string path, Encoding encoding) - { - try - { - using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (var sr = new StreamReader(fs, encoding)) - { - return sr.ReadToEnd(); - } - } - catch (Exception ex) - { - logger.Error(ex); - throw ex; - } - } - } -} +using NLog; +using System; +using System.IO; +using System.IO.Compression; +using System.Text; + +namespace Shadowsocks.WPF.Behaviors +{ + public static class FileManager + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public static bool ByteArrayToFile(string fileName, byte[] content) + { + try + { + using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + fs.Write(content, 0, content.Length); + return true; + } + catch (Exception ex) + { + logger.Error(ex); + } + return false; + } + + public static void UncompressFile(string fileName, byte[] content) + { + // Because the uncompressed size of the file is unknown, + // we are using an arbitrary buffer size. + byte[] buffer = new byte[4096]; + int n; + + using(var fs = File.Create(fileName)) + using (var input = new GZipStream(new MemoryStream(content), + CompressionMode.Decompress, false)) + { + while ((n = input.Read(buffer, 0, buffer.Length)) > 0) + { + fs.Write(buffer, 0, n); + } + } + } + + public static string NonExclusiveReadAllText(string path) + { + return NonExclusiveReadAllText(path, Encoding.Default); + } + + public static string NonExclusiveReadAllText(string path, Encoding encoding) + { + try + { + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var sr = new StreamReader(fs, encoding)) + { + return sr.ReadToEnd(); + } + } + catch (Exception ex) + { + logger.Error(ex); + throw ex; + } + } + } +} diff --git a/shadowsocks-csharp/Controller/HotkeyReg.cs b/Shadowsocks.WPF/Behaviors/HotkeyReg.cs similarity index 96% rename from shadowsocks-csharp/Controller/HotkeyReg.cs rename to Shadowsocks.WPF/Behaviors/HotkeyReg.cs index 25b1dd93..7611ee16 100644 --- a/shadowsocks-csharp/Controller/HotkeyReg.cs +++ b/Shadowsocks.WPF/Behaviors/HotkeyReg.cs @@ -1,11 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Windows.Forms; using NLog; using Shadowsocks.Controller.Hotkeys; -using Shadowsocks.Model; +using System; +using System.Windows.Forms; -namespace Shadowsocks.Controller +namespace Shadowsocks.WPF.Behaviors { static class HotkeyReg { diff --git a/shadowsocks-csharp/Controller/System/Hotkeys/HotkeyCallbacks.cs b/Shadowsocks.WPF/Behaviors/Hotkeys/HotkeyCallbacks.cs similarity index 100% rename from shadowsocks-csharp/Controller/System/Hotkeys/HotkeyCallbacks.cs rename to Shadowsocks.WPF/Behaviors/Hotkeys/HotkeyCallbacks.cs diff --git a/shadowsocks-csharp/Controller/System/Hotkeys/Hotkeys.cs b/Shadowsocks.WPF/Behaviors/Hotkeys/Hotkeys.cs similarity index 100% rename from shadowsocks-csharp/Controller/System/Hotkeys/Hotkeys.cs rename to Shadowsocks.WPF/Behaviors/Hotkeys/Hotkeys.cs diff --git a/shadowsocks-csharp/Controller/Service/IPCService.cs b/Shadowsocks.WPF/Behaviors/IPCService.cs similarity index 97% rename from shadowsocks-csharp/Controller/Service/IPCService.cs rename to Shadowsocks.WPF/Behaviors/IPCService.cs index 5e32f4aa..2fe2d2d8 100644 --- a/shadowsocks-csharp/Controller/Service/IPCService.cs +++ b/Shadowsocks.WPF/Behaviors/IPCService.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.IO.Pipes; using System.Net; using System.Text; -namespace Shadowsocks.Controller +namespace Shadowsocks.WPF.Behaviors { class RequestAddUrlEventArgs : EventArgs { @@ -11,7 +11,7 @@ namespace Shadowsocks.Controller public RequestAddUrlEventArgs(string url) { - this.Url = url; + Url = url; } } diff --git a/Shadowsocks.Common/Utilities/LoggerExtension.cs b/Shadowsocks.WPF/Behaviors/LoggerExtension.cs similarity index 99% rename from Shadowsocks.Common/Utilities/LoggerExtension.cs rename to Shadowsocks.WPF/Behaviors/LoggerExtension.cs index 9d6e1013..3f2c99aa 100644 --- a/Shadowsocks.Common/Utilities/LoggerExtension.cs +++ b/Shadowsocks.WPF/Behaviors/LoggerExtension.cs @@ -1,4 +1,4 @@ -using Shadowsocks.Common.SystemProxy; +using Shadowsocks.Net.SystemProxy; using System; using System.ComponentModel; diff --git a/shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs b/Shadowsocks.WPF/Behaviors/OnlineConfigResolver.cs similarity index 94% rename from shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs rename to Shadowsocks.WPF/Behaviors/OnlineConfigResolver.cs index 31059b73..750f43da 100644 --- a/shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs +++ b/Shadowsocks.WPF/Behaviors/OnlineConfigResolver.cs @@ -1,13 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json.Linq; -using Shadowsocks.Model; +using Shadowsocks.Models; -namespace Shadowsocks.Controller.Service +namespace Shadowsocks.WPF.Behaviors { public class OnlineConfigResolver { diff --git a/shadowsocks-csharp/Controller/System/ProtocolHandler.cs b/Shadowsocks.WPF/Behaviors/ProtocolHandler.cs similarity index 94% rename from shadowsocks-csharp/Controller/System/ProtocolHandler.cs rename to Shadowsocks.WPF/Behaviors/ProtocolHandler.cs index c04d1855..595ab1fa 100644 --- a/shadowsocks-csharp/Controller/System/ProtocolHandler.cs +++ b/Shadowsocks.WPF/Behaviors/ProtocolHandler.cs @@ -1,13 +1,8 @@ -using Microsoft.Win32; +using Microsoft.Win32; using NLog; -using Shadowsocks.Util; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace Shadowsocks.Controller +namespace Shadowsocks.WPF.Behaviors { static class ProtocolHandler { diff --git a/shadowsocks-csharp/Controller/System/SystemProxy.cs b/Shadowsocks.WPF/Behaviors/SystemProxy.cs similarity index 91% rename from shadowsocks-csharp/Controller/System/SystemProxy.cs rename to Shadowsocks.WPF/Behaviors/SystemProxy.cs index 826ee9c7..d003eb86 100644 --- a/shadowsocks-csharp/Controller/System/SystemProxy.cs +++ b/Shadowsocks.WPF/Behaviors/SystemProxy.cs @@ -1,70 +1,69 @@ -using System; -using System.Windows.Forms; -using NLog; -using Shadowsocks.Model; -using Shadowsocks.Util.SystemProxy; - -namespace Shadowsocks.Controller -{ - public static class SystemProxy - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - public static void Update(Configuration config, bool forceDisable, PACServer pacSrv, bool noRetry = false) - { - bool global = config.global; - bool enabled = config.enabled; - - if (forceDisable || !WinINet.operational) - { - enabled = false; - } - - try - { - if (enabled) - { - if (global) - { - WinINet.ProxyGlobal("localhost:" + config.localPort.ToString(), ""); - } - else - { - string pacUrl; - if (config.useOnlinePac && !string.IsNullOrEmpty(config.pacUrl)) - { - pacUrl = config.pacUrl; - } - else - { - - pacUrl = pacSrv.PacUrl; - } - WinINet.ProxyPAC(pacUrl); - } - } - else - { - WinINet.Restore(); - } - } - catch (ProxyException ex) - { - logger.LogUsefulException(ex); - if (ex.Type != ProxyExceptionType.Unspecific && !noRetry) - { - var ret = MessageBox.Show(I18N.GetString("Error occured when process proxy setting, do you want reset current setting and retry?"), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo, MessageBoxIcon.Warning); - if (ret == DialogResult.Yes) - { - WinINet.Reset(); - Update(config, forceDisable, pacSrv, true); - } - } - else - { - MessageBox.Show(I18N.GetString("Unrecoverable proxy setting error occured, see log for detail"), I18N.GetString("Shadowsocks"), MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } - } +using NLog; +using Shadowsocks.Net.SystemProxy; +using Shadowsocks.WPF.Services.SystemProxy; +using System.Windows; + +namespace Shadowsocks.WPF.Behaviors +{ + public static class SystemProxy + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public static void Update(Configuration config, bool forceDisable, PACServer pacSrv, bool noRetry = false) + { + bool global = config.global; + bool enabled = config.enabled; + + if (forceDisable || !WinINet.operational) + { + enabled = false; + } + + try + { + if (enabled) + { + if (global) + { + WinINet.ProxyGlobal("localhost:" + config.localPort.ToString(), ""); + } + else + { + string pacUrl; + if (config.useOnlinePac && !string.IsNullOrEmpty(config.pacUrl)) + { + pacUrl = config.pacUrl; + } + else + { + + pacUrl = pacSrv.PacUrl; + } + WinINet.ProxyPAC(pacUrl); + } + } + else + { + WinINet.Restore(); + } + } + catch (ProxyException ex) + { + logger.LogUsefulException(ex); + if (ex.Type != ProxyExceptionType.Unspecific && !noRetry) + { + var ret = MessageBox.Show(I18N.GetString("Error occured when process proxy setting, do you want reset current setting and retry?"), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo, MessageBoxIcon.Warning); + if (ret == DialogResult.Yes) + { + WinINet.Reset(); + Update(config, forceDisable, pacSrv, true); + } + } + else + { + MessageBox.Show(I18N.GetString("Unrecoverable proxy setting error occured, see log for detail"), I18N.GetString("Shadowsocks"), MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } } \ No newline at end of file diff --git a/Shadowsocks.WPF/Behaviors/Utilities.cs b/Shadowsocks.WPF/Behaviors/Utilities.cs new file mode 100644 index 00000000..45c13110 --- /dev/null +++ b/Shadowsocks.WPF/Behaviors/Utilities.cs @@ -0,0 +1,125 @@ +using Microsoft.Win32; +using NLog; +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using ZXing; +using ZXing.Common; +using ZXing.QrCode; + +namespace Shadowsocks.WPF.Behaviors +{ + public static class Utilities + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + private static string _tempPath = null; + + // return path to store temporary files + public static string GetTempPath() + { + if (_tempPath == null) + { + bool isPortableMode = Configuration.Load().portableMode; + try + { + if (isPortableMode) + { + _tempPath = Directory.CreateDirectory("ss_win_temp").FullName; + // don't use "/", it will fail when we call explorer /select xxx/ss_win_temp\xxx.log + } + else + { + _tempPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), @"Shadowsocks\ss_win_temp_" + Program.ExecutablePath.GetHashCode())).FullName; + } + } + catch (Exception e) + { + logger.Error(e); + throw; + } + } + return _tempPath; + } + + // return a full path with filename combined which pointed to the temporary directory + public static string GetTempPath(string filename) => Path.Combine(GetTempPath(), filename); + + public static string ScanQRCodeFromScreen() + { + foreach (Screen screen in Screen.AllScreens) + { + using (Bitmap fullImage = new Bitmap(screen.Bounds.Width, + screen.Bounds.Height)) + { + using (Graphics g = Graphics.FromImage(fullImage)) + { + g.CopyFromScreen(screen.Bounds.X, + screen.Bounds.Y, + 0, 0, + fullImage.Size, + CopyPixelOperation.SourceCopy); + } + int maxTry = 10; + for (int i = 0; i < maxTry; i++) + { + int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry); + int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry); + Rectangle cropRect = new Rectangle(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2); + Bitmap target = new Bitmap(screen.Bounds.Width, screen.Bounds.Height); + + double imageScale = (double)screen.Bounds.Width / (double)cropRect.Width; + using (Graphics g = Graphics.FromImage(target)) + { + g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height), + cropRect, + GraphicsUnit.Pixel); + } + var source = new BitmapLuminanceSource(target); + var bitmap = new BinaryBitmap(new HybridBinarizer(source)); + QRCodeReader reader = new QRCodeReader(); + var result = reader.decode(bitmap); + if (result != null) + return result.Text; + } + } + } + return null; + } + + public static void OpenInBrowser(string url) + { + try + { + Process.Start(url); + } + catch + { + // hack because of this: https://github.com/dotnet/corefx/issues/10361 + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Process.Start(new ProcessStartInfo(url) + { + UseShellExecute = true, + Verb = "open" + }); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", url); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", url); + } + else + { + throw; + } + } + } + } +} diff --git a/Shadowsocks.WPF/Localization/LocalizationProvider.cs b/Shadowsocks.WPF/Localization/LocalizationProvider.cs index a2625b92..986e6c84 100644 --- a/Shadowsocks.WPF/Localization/LocalizationProvider.cs +++ b/Shadowsocks.WPF/Localization/LocalizationProvider.cs @@ -1,12 +1,9 @@ -using Shadowsocks.Common.Model; - using System.Reflection; - using WPFLocalizeExtension.Extensions; namespace Shadowsocks.WPF.Localization { - public class LocalizationProvider : ILocalizationProvider + public class LocalizationProvider { private static readonly string CallingAssemblyName = Assembly.GetCallingAssembly().GetName().Name; diff --git a/shadowsocks-csharp/Model/ForwardProxyConfig.cs b/Shadowsocks.WPF/Models/ForwardProxyConfig.cs similarity index 95% rename from shadowsocks-csharp/Model/ForwardProxyConfig.cs rename to Shadowsocks.WPF/Models/ForwardProxyConfig.cs index 7a1d4ec5..d5210fac 100644 --- a/shadowsocks-csharp/Model/ForwardProxyConfig.cs +++ b/Shadowsocks.WPF/Models/ForwardProxyConfig.cs @@ -1,6 +1,6 @@ -using System; +using System; -namespace Shadowsocks.Model +namespace Shadowsocks.WPF.Models { [Serializable] public class ForwardProxyConfig diff --git a/shadowsocks-csharp/Model/HotKeyConfig.cs b/Shadowsocks.WPF/Models/HotKeyConfig.cs similarity index 90% rename from shadowsocks-csharp/Model/HotKeyConfig.cs rename to Shadowsocks.WPF/Models/HotKeyConfig.cs index 4e27a08b..36c728af 100644 --- a/shadowsocks-csharp/Model/HotKeyConfig.cs +++ b/Shadowsocks.WPF/Models/HotKeyConfig.cs @@ -1,33 +1,33 @@ -using System; - -namespace Shadowsocks.Model -{ - /* - * Format: - * + - * - */ - - [Serializable] - public class HotkeyConfig - { - public string SwitchSystemProxy; - public string SwitchSystemProxyMode; - public string SwitchAllowLan; - public string ShowLogs; - public string ServerMoveUp; - public string ServerMoveDown; - public bool RegHotkeysAtStartup; - - public HotkeyConfig() - { - SwitchSystemProxy = ""; - SwitchSystemProxyMode = ""; - SwitchAllowLan = ""; - ShowLogs = ""; - ServerMoveUp = ""; - ServerMoveDown = ""; - RegHotkeysAtStartup = false; - } - } +using System; + +namespace Shadowsocks.WPF.Models +{ + /* + * Format: + * + + * + */ + + [Serializable] + public class HotkeyConfig + { + public string SwitchSystemProxy; + public string SwitchSystemProxyMode; + public string SwitchAllowLan; + public string ShowLogs; + public string ServerMoveUp; + public string ServerMoveDown; + public bool RegHotkeysAtStartup; + + public HotkeyConfig() + { + SwitchSystemProxy = ""; + SwitchSystemProxyMode = ""; + SwitchAllowLan = ""; + ShowLogs = ""; + ServerMoveUp = ""; + ServerMoveDown = ""; + RegHotkeysAtStartup = false; + } + } } \ No newline at end of file diff --git a/shadowsocks-csharp/Model/NlogConfig.cs b/Shadowsocks.WPF/Models/NlogConfig.cs similarity index 96% rename from shadowsocks-csharp/Model/NlogConfig.cs rename to Shadowsocks.WPF/Models/NlogConfig.cs index 65384b10..c2ef4b10 100644 --- a/shadowsocks-csharp/Model/NlogConfig.cs +++ b/Shadowsocks.WPF/Models/NlogConfig.cs @@ -1,137 +1,137 @@ -using NLog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml; - -namespace Shadowsocks.Model -{ - public class NLogConfig - { - public enum LogLevel - { - Fatal, - Error, - Warn, - Info, - Debug, - Trace, - } - private static string _NLOG_CONFIG_FILE_NAME=string.Empty; - public static string NLOG_CONFIG_FILE_NAME - { - get - { - if (string.IsNullOrEmpty(_NLOG_CONFIG_FILE_NAME)) - { - _NLOG_CONFIG_FILE_NAME = Path.Combine(Environment.CurrentDirectory, "NLog.config"); - } - return _NLOG_CONFIG_FILE_NAME; - } - } - const string TARGET_MIN_LEVEL_ATTRIBUTE = "minlevel"; - const string LOGGER_FILE_NAME_ATTRIBUTE = "fileName"; - - XmlDocument doc = new XmlDocument(); - XmlElement logFileNameElement; - XmlElement logLevelElement; - - /// - /// Load the NLog config xml file content - /// - public static NLogConfig LoadXML() - { - NLogConfig config = new NLogConfig(); - config.doc.Load(NLOG_CONFIG_FILE_NAME); - config.logLevelElement = (XmlElement)SelectSingleNode(config.doc, "//nlog:logger[@name='*']"); - config.logFileNameElement = (XmlElement)SelectSingleNode(config.doc, "//nlog:target[@name='file']"); - return config; - } - - /// - /// Save the content to NLog config xml file - /// - public static void SaveXML(NLogConfig nLogConfig) - { - nLogConfig.doc.Save(NLOG_CONFIG_FILE_NAME); - } - - - /// - /// Get the current minLogLevel from xml file - /// - /// - public LogLevel GetLogLevel() - { - LogLevel level = LogLevel.Warn; - string levelStr = logLevelElement.GetAttribute(TARGET_MIN_LEVEL_ATTRIBUTE); - Enum.TryParse(levelStr, out level); - return level; - } - - /// - /// Get the target fileName from xml file - /// - /// - public string GetLogFileName() - { - return logFileNameElement.GetAttribute(LOGGER_FILE_NAME_ATTRIBUTE); - } - - /// - /// Set the minLogLevel to xml file - /// - /// - public void SetLogLevel(LogLevel logLevel) - { - logLevelElement.SetAttribute(TARGET_MIN_LEVEL_ATTRIBUTE, logLevel.ToString("G")); - } - - /// - /// Set the target fileName to xml file - /// - /// - public void SetLogFileName(string fileName) - { - logFileNameElement.SetAttribute(LOGGER_FILE_NAME_ATTRIBUTE, fileName); - } - - /// - /// Select a single XML node/elemant - /// - /// - /// - /// - private static XmlNode SelectSingleNode(XmlDocument doc, string xpath) - { - XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); - manager.AddNamespace("nlog", "http://www.nlog-project.org/schemas/NLog.xsd"); - //return doc.SelectSingleNode("//nlog:logger[(@shadowsocks='managed') and (@name='*')]", manager); - return doc.SelectSingleNode(xpath, manager); - } - - /// - /// Extract the pre-defined NLog configuration file is does not exist. Then reload the Nlog configuration. - /// - public static void TouchAndApplyNLogConfig() - { - - if (!File.Exists(NLOG_CONFIG_FILE_NAME)) - { - File.WriteAllText(NLOG_CONFIG_FILE_NAME, Properties.Resources.NLog_config); - LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME); - } - } - - /// - /// NLog reload the config file and apply to current LogManager - /// - public static void LoadConfiguration() - { - LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME); - } - } -} +using NLog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace Shadowsocks.WPF.Models +{ + public class NLogConfig + { + public enum LogLevel + { + Fatal, + Error, + Warn, + Info, + Debug, + Trace, + } + private static string _NLOG_CONFIG_FILE_NAME=string.Empty; + public static string NLOG_CONFIG_FILE_NAME + { + get + { + if (string.IsNullOrEmpty(_NLOG_CONFIG_FILE_NAME)) + { + _NLOG_CONFIG_FILE_NAME = Path.Combine(Environment.CurrentDirectory, "NLog.config"); + } + return _NLOG_CONFIG_FILE_NAME; + } + } + const string TARGET_MIN_LEVEL_ATTRIBUTE = "minlevel"; + const string LOGGER_FILE_NAME_ATTRIBUTE = "fileName"; + + XmlDocument doc = new XmlDocument(); + XmlElement logFileNameElement; + XmlElement logLevelElement; + + /// + /// Load the NLog config xml file content + /// + public static NLogConfig LoadXML() + { + NLogConfig config = new NLogConfig(); + config.doc.Load(NLOG_CONFIG_FILE_NAME); + config.logLevelElement = (XmlElement)SelectSingleNode(config.doc, "//nlog:logger[@name='*']"); + config.logFileNameElement = (XmlElement)SelectSingleNode(config.doc, "//nlog:target[@name='file']"); + return config; + } + + /// + /// Save the content to NLog config xml file + /// + public static void SaveXML(NLogConfig nLogConfig) + { + nLogConfig.doc.Save(NLOG_CONFIG_FILE_NAME); + } + + + /// + /// Get the current minLogLevel from xml file + /// + /// + public LogLevel GetLogLevel() + { + LogLevel level = LogLevel.Warn; + string levelStr = logLevelElement.GetAttribute(TARGET_MIN_LEVEL_ATTRIBUTE); + Enum.TryParse(levelStr, out level); + return level; + } + + /// + /// Get the target fileName from xml file + /// + /// + public string GetLogFileName() + { + return logFileNameElement.GetAttribute(LOGGER_FILE_NAME_ATTRIBUTE); + } + + /// + /// Set the minLogLevel to xml file + /// + /// + public void SetLogLevel(LogLevel logLevel) + { + logLevelElement.SetAttribute(TARGET_MIN_LEVEL_ATTRIBUTE, logLevel.ToString("G")); + } + + /// + /// Set the target fileName to xml file + /// + /// + public void SetLogFileName(string fileName) + { + logFileNameElement.SetAttribute(LOGGER_FILE_NAME_ATTRIBUTE, fileName); + } + + /// + /// Select a single XML node/elemant + /// + /// + /// + /// + private static XmlNode SelectSingleNode(XmlDocument doc, string xpath) + { + XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); + manager.AddNamespace("nlog", "http://www.nlog-project.org/schemas/NLog.xsd"); + //return doc.SelectSingleNode("//nlog:logger[(@shadowsocks='managed') and (@name='*')]", manager); + return doc.SelectSingleNode(xpath, manager); + } + + /// + /// Extract the pre-defined NLog configuration file is does not exist. Then reload the Nlog configuration. + /// + public static void TouchAndApplyNLogConfig() + { + + if (!File.Exists(NLOG_CONFIG_FILE_NAME)) + { + File.WriteAllText(NLOG_CONFIG_FILE_NAME, Properties.Resources.NLog_config); + LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME); + } + } + + /// + /// NLog reload the config file and apply to current LogManager + /// + public static void LoadConfiguration() + { + LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME); + } + } +} diff --git a/shadowsocks-csharp/Model/Server.cs b/Shadowsocks.WPF/Models/Server.cs old mode 100755 new mode 100644 similarity index 96% rename from shadowsocks-csharp/Model/Server.cs rename to Shadowsocks.WPF/Models/Server.cs index d72f1c8b..5456c84a --- a/shadowsocks-csharp/Model/Server.cs +++ b/Shadowsocks.WPF/Models/Server.cs @@ -1,252 +1,252 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Text; -using System.Web; -using Shadowsocks.Controller; -using System.Text.RegularExpressions; -using System.Linq; -using Newtonsoft.Json; -using System.ComponentModel; - -namespace Shadowsocks.Model -{ - [Serializable] - public class Server - { - public const string DefaultMethod = "chacha20-ietf-poly1305"; - public const int DefaultPort = 8388; - - #region ParseLegacyURL - private static readonly Regex UrlFinder = new Regex(@"ss://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase); - private static readonly Regex DetailsParser = new Regex(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase); - #endregion ParseLegacyURL - - private const int DefaultServerTimeoutSec = 5; - public const int MaxServerTimeoutSec = 20; - - public string server; - public int server_port; - public string password; - public string method; - - // optional fields - [DefaultValue("")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - public string plugin; - [DefaultValue("")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - public string plugin_opts; - [DefaultValue("")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - public string plugin_args; - [DefaultValue("")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - public string remarks; - - [DefaultValue("")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - public string group; - - public int timeout; - - public override int GetHashCode() - { - return server.GetHashCode() ^ server_port; - } - - public override bool Equals(object obj) => obj is Server o2 && server == o2.server && server_port == o2.server_port; - - public override string ToString() - { - if (string.IsNullOrEmpty(server)) - { - return I18N.GetString("New server"); - } - - string serverStr = $"{FormalHostName}:{server_port}"; - return string.IsNullOrEmpty(remarks) - ? serverStr - : $"{remarks} ({serverStr})"; - } - - public string GetURL(bool legacyUrl = false) - { - if (legacyUrl && string.IsNullOrWhiteSpace(plugin)) - { - // For backwards compatiblity, if no plugin, use old url format - string p = $"{method}:{password}@{server}:{server_port}"; - string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(p)); - return string.IsNullOrEmpty(remarks) - ? $"ss://{base64}" - : $"ss://{base64}#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}"; - } - - UriBuilder u = new UriBuilder("ss", null); - string b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{method}:{password}")); - u.UserName = b64.Replace('+', '-').Replace('/', '_').TrimEnd('='); - u.Host = server; - u.Port = server_port; - u.Fragment = HttpUtility.UrlEncode(remarks, Encoding.UTF8); - - if (!string.IsNullOrWhiteSpace(plugin)) - { - NameValueCollection param = HttpUtility.ParseQueryString(""); - - string pluginPart = plugin; - if (!string.IsNullOrWhiteSpace(plugin_opts)) - { - pluginPart += ";" + plugin_opts; - } - param["plugin"] = pluginPart; - u.Query = param.ToString(); - } - - return u.ToString(); - } - - [JsonIgnore] - public string FormalHostName - { - get - { - // CheckHostName() won't do a real DNS lookup - return (Uri.CheckHostName(server)) switch - { - // Add square bracket when IPv6 (RFC3986) - UriHostNameType.IPv6 => $"[{server}]", - // IPv4 or domain name - _ => server, - }; - } - } - - public Server() - { - server = ""; - server_port = DefaultPort; - method = DefaultMethod; - plugin = ""; - plugin_opts = ""; - plugin_args = ""; - password = ""; - remarks = ""; - timeout = DefaultServerTimeoutSec; - } - - private static Server ParseLegacyURL(string ssURL) - { - var match = UrlFinder.Match(ssURL); - if (!match.Success) - return null; - - Server server = new Server(); - var base64 = match.Groups["base64"].Value.TrimEnd('/'); - var tag = match.Groups["tag"].Value; - if (!string.IsNullOrEmpty(tag)) - { - server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8); - } - Match details; - try - { - details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String( - base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '=')))); - } - catch (FormatException) - { - return null; - } - if (!details.Success) - return null; - server.method = details.Groups["method"].Value; - server.password = details.Groups["password"].Value; - server.server = details.Groups["hostname"].Value; - server.server_port = int.Parse(details.Groups["port"].Value); - return server; - } - - public static Server ParseURL(string serverUrl) - { - string _serverUrl = serverUrl.Trim(); - if (!_serverUrl.StartsWith("ss://", StringComparison.InvariantCultureIgnoreCase)) - { - return null; - } - - Server legacyServer = ParseLegacyURL(serverUrl); - if (legacyServer != null) //legacy - { - return legacyServer; - } - else //SIP002 - { - Uri parsedUrl; - try - { - parsedUrl = new Uri(serverUrl); - } - catch (UriFormatException) - { - return null; - } - Server server = new Server - { - remarks = HttpUtility.UrlDecode(parsedUrl.GetComponents( - UriComponents.Fragment, UriFormat.Unescaped), Encoding.UTF8), - server = parsedUrl.IdnHost, - server_port = parsedUrl.Port, - }; - - // parse base64 UserInfo - string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); - string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64 - string userInfo; - try - { - userInfo = Encoding.UTF8.GetString(Convert.FromBase64String( - base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))); - } - catch (FormatException) - { - return null; - } - string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2); - if (userInfoParts.Length != 2) - { - return null; - } - server.method = userInfoParts[0]; - server.password = userInfoParts[1]; - - NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); - string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2); - if (pluginParts.Length > 0) - { - server.plugin = pluginParts[0] ?? ""; - } - - if (pluginParts.Length > 1) - { - server.plugin_opts = pluginParts[1] ?? ""; - } - - return server; - } - } - - public static List GetServers(string ssURL) - { - return ssURL - .Split('\r', '\n', ' ') - .Select(u => ParseURL(u)) - .Where(s => s != null) - .ToList(); - } - - public string Identifier() - { - return server + ':' + server_port; - } - } -} +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using System.Web; +using Shadowsocks.Controller; +using System.Text.RegularExpressions; +using System.Linq; +using Newtonsoft.Json; +using System.ComponentModel; + +namespace Shadowsocks.WPF.Models +{ + [Serializable] + public class Server + { + public const string DefaultMethod = "chacha20-ietf-poly1305"; + public const int DefaultPort = 8388; + + #region ParseLegacyURL + private static readonly Regex UrlFinder = new Regex(@"ss://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase); + private static readonly Regex DetailsParser = new Regex(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase); + #endregion ParseLegacyURL + + private const int DefaultServerTimeoutSec = 5; + public const int MaxServerTimeoutSec = 20; + + public string server; + public int server_port; + public string password; + public string method; + + // optional fields + [DefaultValue("")] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string plugin; + [DefaultValue("")] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string plugin_opts; + [DefaultValue("")] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string plugin_args; + [DefaultValue("")] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string remarks; + + [DefaultValue("")] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string group; + + public int timeout; + + public override int GetHashCode() + { + return server.GetHashCode() ^ server_port; + } + + public override bool Equals(object obj) => obj is Server o2 && server == o2.server && server_port == o2.server_port; + + public override string ToString() + { + if (string.IsNullOrEmpty(server)) + { + return I18N.GetString("New server"); + } + + string serverStr = $"{FormalHostName}:{server_port}"; + return string.IsNullOrEmpty(remarks) + ? serverStr + : $"{remarks} ({serverStr})"; + } + + public string GetURL(bool legacyUrl = false) + { + if (legacyUrl && string.IsNullOrWhiteSpace(plugin)) + { + // For backwards compatiblity, if no plugin, use old url format + string p = $"{method}:{password}@{server}:{server_port}"; + string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(p)); + return string.IsNullOrEmpty(remarks) + ? $"ss://{base64}" + : $"ss://{base64}#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}"; + } + + UriBuilder u = new UriBuilder("ss", null); + string b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{method}:{password}")); + u.UserName = b64.Replace('+', '-').Replace('/', '_').TrimEnd('='); + u.Host = server; + u.Port = server_port; + u.Fragment = HttpUtility.UrlEncode(remarks, Encoding.UTF8); + + if (!string.IsNullOrWhiteSpace(plugin)) + { + NameValueCollection param = HttpUtility.ParseQueryString(""); + + string pluginPart = plugin; + if (!string.IsNullOrWhiteSpace(plugin_opts)) + { + pluginPart += ";" + plugin_opts; + } + param["plugin"] = pluginPart; + u.Query = param.ToString(); + } + + return u.ToString(); + } + + [JsonIgnore] + public string FormalHostName + { + get + { + // CheckHostName() won't do a real DNS lookup + return (Uri.CheckHostName(server)) switch + { + // Add square bracket when IPv6 (RFC3986) + UriHostNameType.IPv6 => $"[{server}]", + // IPv4 or domain name + _ => server, + }; + } + } + + public Server() + { + server = ""; + server_port = DefaultPort; + method = DefaultMethod; + plugin = ""; + plugin_opts = ""; + plugin_args = ""; + password = ""; + remarks = ""; + timeout = DefaultServerTimeoutSec; + } + + private static Server ParseLegacyURL(string ssURL) + { + var match = UrlFinder.Match(ssURL); + if (!match.Success) + return null; + + Server server = new Server(); + var base64 = match.Groups["base64"].Value.TrimEnd('/'); + var tag = match.Groups["tag"].Value; + if (!string.IsNullOrEmpty(tag)) + { + server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8); + } + Match details; + try + { + details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String( + base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '=')))); + } + catch (FormatException) + { + return null; + } + if (!details.Success) + return null; + server.method = details.Groups["method"].Value; + server.password = details.Groups["password"].Value; + server.server = details.Groups["hostname"].Value; + server.server_port = int.Parse(details.Groups["port"].Value); + return server; + } + + public static Server ParseURL(string serverUrl) + { + string _serverUrl = serverUrl.Trim(); + if (!_serverUrl.StartsWith("ss://", StringComparison.InvariantCultureIgnoreCase)) + { + return null; + } + + Server legacyServer = ParseLegacyURL(serverUrl); + if (legacyServer != null) //legacy + { + return legacyServer; + } + else //SIP002 + { + Uri parsedUrl; + try + { + parsedUrl = new Uri(serverUrl); + } + catch (UriFormatException) + { + return null; + } + Server server = new Server + { + remarks = HttpUtility.UrlDecode(parsedUrl.GetComponents( + UriComponents.Fragment, UriFormat.Unescaped), Encoding.UTF8), + server = parsedUrl.IdnHost, + server_port = parsedUrl.Port, + }; + + // parse base64 UserInfo + string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); + string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64 + string userInfo; + try + { + userInfo = Encoding.UTF8.GetString(Convert.FromBase64String( + base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))); + } + catch (FormatException) + { + return null; + } + string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2); + if (userInfoParts.Length != 2) + { + return null; + } + server.method = userInfoParts[0]; + server.password = userInfoParts[1]; + + NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); + string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2); + if (pluginParts.Length > 0) + { + server.plugin = pluginParts[0] ?? ""; + } + + if (pluginParts.Length > 1) + { + server.plugin_opts = pluginParts[1] ?? ""; + } + + return server; + } + } + + public static List GetServers(string ssURL) + { + return ssURL + .Split('\r', '\n', ' ') + .Select(u => ParseURL(u)) + .Where(s => s != null) + .ToList(); + } + + public string Identifier() + { + return server + ':' + server_port; + } + } +} diff --git a/Shadowsocks.WPF/Models/Settings.cs b/Shadowsocks.WPF/Models/Settings.cs new file mode 100644 index 00000000..4e7d06a7 --- /dev/null +++ b/Shadowsocks.WPF/Models/Settings.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shadowsocks.WPF.Models +{ + public class Settings + { + public Settings() + { + + } + + + } +} diff --git a/Shadowsocks/Assets/NLog.config b/Shadowsocks.WPF/Resources/NLog.config similarity index 100% rename from Shadowsocks/Assets/NLog.config rename to Shadowsocks.WPF/Resources/NLog.config diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Bold.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Bold.ttf new file mode 100644 index 00000000..900fce68 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Bold.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-BoldItalic.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-BoldItalic.ttf new file mode 100644 index 00000000..4bfe29ae Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-BoldItalic.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLight.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLight.ttf new file mode 100644 index 00000000..d5358845 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLight.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLightItalic.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLightItalic.ttf new file mode 100644 index 00000000..b28960a0 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLightItalic.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Italic.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Italic.ttf new file mode 100644 index 00000000..4ee4dc49 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Italic.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Light.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Light.ttf new file mode 100644 index 00000000..276af4c5 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Light.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-LightItalic.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-LightItalic.ttf new file mode 100644 index 00000000..a2801c21 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-LightItalic.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Medium.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Medium.ttf new file mode 100644 index 00000000..8461be77 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Medium.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-MediumItalic.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-MediumItalic.ttf new file mode 100644 index 00000000..a3bfaa11 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-MediumItalic.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Regular.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Regular.ttf new file mode 100644 index 00000000..7c4ce36a Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Regular.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBold.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBold.ttf new file mode 100644 index 00000000..15ee6c6e Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBold.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBoldItalic.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBoldItalic.ttf new file mode 100644 index 00000000..8e214977 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBoldItalic.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Thin.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Thin.ttf new file mode 100644 index 00000000..ee8a3fd4 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Thin.ttf differ diff --git a/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ThinItalic.ttf b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ThinItalic.ttf new file mode 100644 index 00000000..40b01e40 Binary files /dev/null and b/Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ThinItalic.ttf differ diff --git a/Shadowsocks/Assets/privoxy.exe.gz b/Shadowsocks.WPF/Resources/privoxy.exe.gz similarity index 100% rename from Shadowsocks/Assets/privoxy.exe.gz rename to Shadowsocks.WPF/Resources/privoxy.exe.gz diff --git a/Shadowsocks/Assets/privoxy_conf.txt b/Shadowsocks.WPF/Resources/privoxy_conf.txt similarity index 100% rename from Shadowsocks/Assets/privoxy_conf.txt rename to Shadowsocks.WPF/Resources/privoxy_conf.txt diff --git a/Shadowsocks.WPF/Assets/shadowsocks.ico b/Shadowsocks.WPF/Resources/shadowsocks.ico similarity index 100% rename from Shadowsocks.WPF/Assets/shadowsocks.ico rename to Shadowsocks.WPF/Resources/shadowsocks.ico diff --git a/Shadowsocks.WPF/Assets/ss128.pdn b/Shadowsocks.WPF/Resources/ss128.pdn similarity index 100% rename from Shadowsocks.WPF/Assets/ss128.pdn rename to Shadowsocks.WPF/Resources/ss128.pdn diff --git a/Shadowsocks.WPF/Assets/ss32.pdn b/Shadowsocks.WPF/Resources/ss32.pdn similarity index 100% rename from Shadowsocks.WPF/Assets/ss32.pdn rename to Shadowsocks.WPF/Resources/ss32.pdn diff --git a/Shadowsocks.WPF/Assets/ss32Fill.png b/Shadowsocks.WPF/Resources/ss32Fill.png similarity index 100% rename from Shadowsocks.WPF/Assets/ss32Fill.png rename to Shadowsocks.WPF/Resources/ss32Fill.png diff --git a/Shadowsocks.WPF/Assets/ss32In.png b/Shadowsocks.WPF/Resources/ss32In.png similarity index 100% rename from Shadowsocks.WPF/Assets/ss32In.png rename to Shadowsocks.WPF/Resources/ss32In.png diff --git a/Shadowsocks.WPF/Assets/ss32Out.png b/Shadowsocks.WPF/Resources/ss32Out.png similarity index 100% rename from Shadowsocks.WPF/Assets/ss32Out.png rename to Shadowsocks.WPF/Resources/ss32Out.png diff --git a/Shadowsocks.WPF/Assets/ss32Outline.png b/Shadowsocks.WPF/Resources/ss32Outline.png similarity index 100% rename from Shadowsocks.WPF/Assets/ss32Outline.png rename to Shadowsocks.WPF/Resources/ss32Outline.png diff --git a/Shadowsocks.WPF/Assets/ssw128.png b/Shadowsocks.WPF/Resources/ssw128.png similarity index 100% rename from Shadowsocks.WPF/Assets/ssw128.png rename to Shadowsocks.WPF/Resources/ssw128.png diff --git a/shadowsocks-csharp/Controller/Service/PortForwarder.cs b/Shadowsocks.WPF/Services/PortForwarder.cs similarity index 93% rename from shadowsocks-csharp/Controller/Service/PortForwarder.cs rename to Shadowsocks.WPF/Services/PortForwarder.cs index 47914da5..966d86fd 100644 --- a/shadowsocks-csharp/Controller/Service/PortForwarder.cs +++ b/Shadowsocks.WPF/Services/PortForwarder.cs @@ -1,277 +1,276 @@ -using System; -using System.Net; -using System.Net.Sockets; -using NLog; -using Shadowsocks.Util.Sockets; - -namespace Shadowsocks.Controller -{ - class PortForwarder : StreamService - { - private readonly int _targetPort; - - public PortForwarder(int targetPort) - { - _targetPort = targetPort; - } - - public override bool Handle(CachedNetworkStream stream, object state) - { - byte[] fp = new byte[256]; - int len = stream.ReadFirstBlock(fp); - - if (stream.Socket.ProtocolType != ProtocolType.Tcp) - { - return false; - } - new Handler().Start(fp, len, stream.Socket, _targetPort); - return true; - } - - [Obsolete] - public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) - { - if (socket.ProtocolType != ProtocolType.Tcp) - { - return false; - } - new Handler().Start(firstPacket, length, socket, _targetPort); - return true; - } - - private class Handler - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - private byte[] _firstPacket; - private int _firstPacketLength; - private Socket _local; - private Socket _remote; - private bool _closed = false; - private bool _localShutdown = false; - private bool _remoteShutdown = false; - private const int RecvSize = 2048; - // remote receive buffer - private byte[] remoteRecvBuffer = new byte[RecvSize]; - // connection receive buffer - private byte[] connetionRecvBuffer = new byte[RecvSize]; - - // instance-based lock - private readonly object _Lock = new object(); - - public void Start(byte[] firstPacket, int length, Socket socket, int targetPort) - { - _firstPacket = firstPacket; - _firstPacketLength = length; - _local = socket; - try - { - // Local Port Forward use IP as is - EndPoint remoteEP = SocketUtil.GetEndPoint(_local.AddressFamily == AddressFamily.InterNetworkV6 ? "[::1]" : "127.0.0.1", targetPort); - - // Connect to the remote endpoint. - _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); - _remote.BeginConnect(remoteEP, ConnectCallback, null); - } - catch (Exception e) - { - logger.LogUsefulException(e); - Close(); - } - } - - private void ConnectCallback(IAsyncResult ar) - { - if (_closed) - { - return; - } - try - { - _remote.EndConnect(ar); - _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - HandshakeReceive(); - } - catch (Exception e) - { - logger.LogUsefulException(e); - Close(); - } - } - - private void HandshakeReceive() - { - if (_closed) - { - return; - } - try - { - _remote.BeginSend(_firstPacket, 0, _firstPacketLength, 0, StartPipe, null); - } - catch (Exception e) - { - logger.LogUsefulException(e); - Close(); - } - } - - private void StartPipe(IAsyncResult ar) - { - if (_closed) - { - return; - } - try - { - _remote.EndSend(ar); - _remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, - PipeRemoteReceiveCallback, null); - _local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, - PipeConnectionReceiveCallback, null); - } - catch (Exception e) - { - logger.LogUsefulException(e); - Close(); - } - } - - private void PipeRemoteReceiveCallback(IAsyncResult ar) - { - if (_closed) - { - return; - } - try - { - int bytesRead = _remote.EndReceive(ar); - if (bytesRead > 0) - { - _local.BeginSend(remoteRecvBuffer, 0, bytesRead, 0, PipeConnectionSendCallback, null); - } - else - { - _local.Shutdown(SocketShutdown.Send); - _localShutdown = true; - CheckClose(); - } - } - catch (Exception e) - { - logger.LogUsefulException(e); - Close(); - } - } - - private void PipeConnectionReceiveCallback(IAsyncResult ar) - { - if (_closed) - { - return; - } - try - { - int bytesRead = _local.EndReceive(ar); - if (bytesRead > 0) - { - _remote.BeginSend(connetionRecvBuffer, 0, bytesRead, 0, PipeRemoteSendCallback, null); - } - else - { - _remote.Shutdown(SocketShutdown.Send); - _remoteShutdown = true; - CheckClose(); - } - } - catch (Exception e) - { - logger.LogUsefulException(e); - Close(); - } - } - - private void PipeRemoteSendCallback(IAsyncResult ar) - { - if (_closed) - { - return; - } - try - { - _remote.EndSend(ar); - _local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, - PipeConnectionReceiveCallback, null); - } - catch (Exception e) - { - logger.LogUsefulException(e); - Close(); - } - } - - private void PipeConnectionSendCallback(IAsyncResult ar) - { - if (_closed) - { - return; - } - try - { - _local.EndSend(ar); - _remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, - PipeRemoteReceiveCallback, null); - } - catch (Exception e) - { - logger.LogUsefulException(e); - Close(); - } - } - - private void CheckClose() - { - if (_localShutdown && _remoteShutdown) - { - Close(); - } - } - - public void Close() - { - lock (_Lock) - { - if (_closed) - { - return; - } - _closed = true; - } - if (_local != null) - { - try - { - _local.Shutdown(SocketShutdown.Both); - _local.Close(); - } - catch (Exception e) - { - logger.LogUsefulException(e); - } - } - if (_remote != null) - { - try - { - _remote.Shutdown(SocketShutdown.Both); - _remote.Dispose(); - } - catch (SocketException e) - { - logger.LogUsefulException(e); - } - } - } - } - } -} +using System; +using System.Net; +using System.Net.Sockets; +using NLog; +using Shadowsocks.Net; + +namespace Shadowsocks.WPF.Services +{ + public class PortForwarder : StreamService + { + private readonly int _targetPort; + + public PortForwarder(int targetPort) + { + _targetPort = targetPort; + } + + public override bool Handle(CachedNetworkStream stream, object state) + { + byte[] fp = new byte[256]; + int len = stream.ReadFirstBlock(fp); + + if (stream.Socket.ProtocolType != ProtocolType.Tcp) + { + return false; + } + new Handler().Start(fp, len, stream.Socket, _targetPort); + return true; + } + + [Obsolete] + public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) + { + if (socket.ProtocolType != ProtocolType.Tcp) + { + return false; + } + new Handler().Start(firstPacket, length, socket, _targetPort); + return true; + } + + private class Handler + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + private byte[] _firstPacket; + private int _firstPacketLength; + private Socket _local; + private Socket _remote; + private bool _closed = false; + private bool _localShutdown = false; + private bool _remoteShutdown = false; + private const int RecvSize = 2048; + // remote receive buffer + private byte[] remoteRecvBuffer = new byte[RecvSize]; + // connection receive buffer + private byte[] connetionRecvBuffer = new byte[RecvSize]; + + // instance-based lock + private readonly object _Lock = new object(); + + public void Start(byte[] firstPacket, int length, Socket socket, int targetPort) + { + _firstPacket = firstPacket; + _firstPacketLength = length; + _local = socket; + try + { + // Local Port Forward use IP as is + EndPoint remoteEP = new IPEndPoint(_local.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, targetPort); + // Connect to the remote endpoint. + _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); + _remote.BeginConnect(remoteEP, ConnectCallback, null); + } + catch (Exception e) + { + logger.LogUsefulException(e); + Close(); + } + } + + private void ConnectCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + _remote.EndConnect(ar); + _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + HandshakeReceive(); + } + catch (Exception e) + { + logger.LogUsefulException(e); + Close(); + } + } + + private void HandshakeReceive() + { + if (_closed) + { + return; + } + try + { + _remote.BeginSend(_firstPacket, 0, _firstPacketLength, 0, StartPipe, null); + } + catch (Exception e) + { + logger.LogUsefulException(e); + Close(); + } + } + + private void StartPipe(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + _remote.EndSend(ar); + _remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, + PipeRemoteReceiveCallback, null); + _local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, + PipeConnectionReceiveCallback, null); + } + catch (Exception e) + { + logger.LogUsefulException(e); + Close(); + } + } + + private void PipeRemoteReceiveCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + int bytesRead = _remote.EndReceive(ar); + if (bytesRead > 0) + { + _local.BeginSend(remoteRecvBuffer, 0, bytesRead, 0, PipeConnectionSendCallback, null); + } + else + { + _local.Shutdown(SocketShutdown.Send); + _localShutdown = true; + CheckClose(); + } + } + catch (Exception e) + { + logger.LogUsefulException(e); + Close(); + } + } + + private void PipeConnectionReceiveCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + int bytesRead = _local.EndReceive(ar); + if (bytesRead > 0) + { + _remote.BeginSend(connetionRecvBuffer, 0, bytesRead, 0, PipeRemoteSendCallback, null); + } + else + { + _remote.Shutdown(SocketShutdown.Send); + _remoteShutdown = true; + CheckClose(); + } + } + catch (Exception e) + { + logger.LogUsefulException(e); + Close(); + } + } + + private void PipeRemoteSendCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + _remote.EndSend(ar); + _local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, + PipeConnectionReceiveCallback, null); + } + catch (Exception e) + { + logger.LogUsefulException(e); + Close(); + } + } + + private void PipeConnectionSendCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + _local.EndSend(ar); + _remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, + PipeRemoteReceiveCallback, null); + } + catch (Exception e) + { + logger.LogUsefulException(e); + Close(); + } + } + + private void CheckClose() + { + if (_localShutdown && _remoteShutdown) + { + Close(); + } + } + + public void Close() + { + lock (_Lock) + { + if (_closed) + { + return; + } + _closed = true; + } + if (_local != null) + { + try + { + _local.Shutdown(SocketShutdown.Both); + _local.Close(); + } + catch (Exception e) + { + logger.LogUsefulException(e); + } + } + if (_remote != null) + { + try + { + _remote.Shutdown(SocketShutdown.Both); + _remote.Dispose(); + } + catch (SocketException e) + { + logger.LogUsefulException(e); + } + } + } + } + } +} diff --git a/shadowsocks-csharp/Controller/Service/PrivoxyRunner.cs b/Shadowsocks.WPF/Services/PrivoxyRunner.cs similarity index 90% rename from shadowsocks-csharp/Controller/Service/PrivoxyRunner.cs rename to Shadowsocks.WPF/Services/PrivoxyRunner.cs index 1a7aaebf..676b04c4 100644 --- a/shadowsocks-csharp/Controller/Service/PrivoxyRunner.cs +++ b/Shadowsocks.WPF/Services/PrivoxyRunner.cs @@ -1,170 +1,162 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Windows.Forms; -using NLog; -using Shadowsocks.Model; -using Shadowsocks.Properties; -using Shadowsocks.Util; -using Shadowsocks.Util.ProcessManagement; - -namespace Shadowsocks.Controller -{ - class PrivoxyRunner - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - private static int _uid; - private static string _uniqueConfigFile; - private static Job _privoxyJob; - private Process _process; - private int _runningPort; - - static PrivoxyRunner() - { - try - { - _uid = Program.WorkingDirectory.GetHashCode(); // Currently we use ss's StartupPath to identify different Privoxy instance. - _uniqueConfigFile = $"privoxy_{_uid}.conf"; - _privoxyJob = new Job(); - - FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe); - } - catch (IOException e) - { - logger.LogUsefulException(e); - } - } - - public int RunningPort => _runningPort; - - public void Start(Configuration configuration) - { - if (_process == null) - { - Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy"); - foreach (Process p in existingPrivoxy.Where(IsChildProcess)) - { - KillProcess(p); - } - string privoxyConfig = Resources.privoxy_conf; - _runningPort = GetFreePort(configuration.isIPv6Enabled); - privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); - privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString()); - privoxyConfig = configuration.isIPv6Enabled - ? privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "[::]" : "[::1]") - .Replace("__SOCKS_HOST__", "[::1]") - : privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1") - .Replace("__SOCKS_HOST__", "127.0.0.1"); - FileManager.ByteArrayToFile(Utils.GetTempPath(_uniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig)); - - _process = new Process - { - // Configure the process using the StartInfo properties. - StartInfo = - { - FileName = "ss_privoxy.exe", - Arguments = _uniqueConfigFile, - WorkingDirectory = Utils.GetTempPath(), - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = true, - CreateNoWindow = true - } - }; - _process.Start(); - - /* - * Add this process to job obj associated with this ss process, so that - * when ss exit unexpectedly, this process will be forced killed by system. - */ - _privoxyJob.AddProcess(_process.Handle); - } - } - - public void Stop() - { - if (_process != null) - { - KillProcess(_process); - _process.Dispose(); - _process = null; - } - } - - private static void KillProcess(Process p) - { - try - { - p.CloseMainWindow(); - p.WaitForExit(100); - if (!p.HasExited) - { - p.Kill(); - p.WaitForExit(); - } - } - catch (Exception e) - { - logger.LogUsefulException(e); - } - } - - /* - * We won't like to kill other ss instances' ss_privoxy.exe. - * This function will check whether the given process is created - * by this process by checking the module path or command line. - * - * Since it's required to put ss in different dirs to run muti instances, - * different instance will create their unique "privoxy_UID.conf" where - * UID is hash of ss's location. - */ - - private static bool IsChildProcess(Process process) - { - try - { - /* - * Under PortableMode, we could identify it by the path of ss_privoxy.exe. - */ - var path = process.MainModule.FileName; - - return Utils.GetTempPath("ss_privoxy.exe").Equals(path); - - } - catch (Exception ex) - { - /* - * Sometimes Process.GetProcessesByName will return some processes that - * are already dead, and that will cause exceptions here. - * We could simply ignore those exceptions. - */ - logger.LogUsefulException(ex); - return false; - } - } - - private int GetFreePort(bool isIPv6 = false) - { - int defaultPort = 8123; - try - { - // TCP stack please do me a favor - TcpListener l = new TcpListener(isIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0); - l.Start(); - var port = ((IPEndPoint)l.LocalEndpoint).Port; - l.Stop(); - return port; - } - catch (Exception e) - { - // in case access denied - logger.LogUsefulException(e); - return defaultPort; - } - } - } -} +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Windows.Forms; +using NLog; +using Shadowsocks.Model; +using Shadowsocks.Properties; +using Shadowsocks.Util; +using Shadowsocks.Util.ProcessManagement; + +namespace Shadowsocks.WPF.Services +{ + class PrivoxyRunner + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + private static int _uid; + private static string _uniqueConfigFile; + private Process _process; + private int _runningPort; + + static PrivoxyRunner() + { + try + { + _uid = Program.WorkingDirectory.GetHashCode(); // Currently we use ss's StartupPath to identify different Privoxy instance. + _uniqueConfigFile = $"privoxy_{_uid}.conf"; + + FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe); + } + catch (IOException e) + { + logger.LogUsefulException(e); + } + } + + public int RunningPort => _runningPort; + + public void Start(Configuration configuration) + { + if (_process == null) + { + Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy"); + foreach (Process p in existingPrivoxy.Where(IsChildProcess)) + { + KillProcess(p); + } + string privoxyConfig = Resources.privoxy_conf; + _runningPort = GetFreePort(configuration.isIPv6Enabled); + privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); + privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString()); + privoxyConfig = configuration.isIPv6Enabled + ? privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "[::]" : "[::1]") + .Replace("__SOCKS_HOST__", "[::1]") + : privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1") + .Replace("__SOCKS_HOST__", "127.0.0.1"); + FileManager.ByteArrayToFile(Utils.GetTempPath(_uniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig)); + + _process = new Process + { + // Configure the process using the StartInfo properties. + StartInfo = + { + FileName = "ss_privoxy.exe", + Arguments = _uniqueConfigFile, + WorkingDirectory = Utils.GetTempPath(), + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true, + CreateNoWindow = true + } + }; + _process.Start(); + } + } + + public void Stop() + { + if (_process != null) + { + KillProcess(_process); + _process.Dispose(); + _process = null; + } + } + + private static void KillProcess(Process p) + { + try + { + p.CloseMainWindow(); + p.WaitForExit(100); + if (!p.HasExited) + { + p.Kill(); + p.WaitForExit(); + } + } + catch (Exception e) + { + logger.LogUsefulException(e); + } + } + + /* + * We won't like to kill other ss instances' ss_privoxy.exe. + * This function will check whether the given process is created + * by this process by checking the module path or command line. + * + * Since it's required to put ss in different dirs to run muti instances, + * different instance will create their unique "privoxy_UID.conf" where + * UID is hash of ss's location. + */ + + private static bool IsChildProcess(Process process) + { + try + { + /* + * Under PortableMode, we could identify it by the path of ss_privoxy.exe. + */ + var path = process.MainModule.FileName; + + return Utils.GetTempPath("ss_privoxy.exe").Equals(path); + + } + catch (Exception ex) + { + /* + * Sometimes Process.GetProcessesByName will return some processes that + * are already dead, and that will cause exceptions here. + * We could simply ignore those exceptions. + */ + logger.LogUsefulException(ex); + return false; + } + } + + private int GetFreePort(bool isIPv6 = false) + { + int defaultPort = 8123; + try + { + // TCP stack please do me a favor + TcpListener l = new TcpListener(isIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0); + l.Start(); + var port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } + catch (Exception e) + { + // in case access denied + logger.LogUsefulException(e); + return defaultPort; + } + } + } +} diff --git a/shadowsocks-csharp/Controller/Service/Sip003Plugin.cs b/Shadowsocks.WPF/Services/Sip003Plugin.cs similarity index 95% rename from shadowsocks-csharp/Controller/Service/Sip003Plugin.cs rename to Shadowsocks.WPF/Services/Sip003Plugin.cs index 7024c648..9c91ce25 100644 --- a/shadowsocks-csharp/Controller/Service/Sip003Plugin.cs +++ b/Shadowsocks.WPF/Services/Sip003Plugin.cs @@ -1,177 +1,174 @@ -using System; -using System.Collections.Specialized; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Net.Sockets; -using Shadowsocks.Model; -using Shadowsocks.Util.ProcessManagement; - -namespace Shadowsocks.Controller.Service -{ - // https://github.com/shadowsocks/shadowsocks-org/wiki/Plugin - public sealed class Sip003Plugin : IDisposable - { - public IPEndPoint LocalEndPoint { get; private set; } - public int ProcessId => _started ? _pluginProcess.Id : 0; - - private readonly object _startProcessLock = new object(); - private readonly Job _pluginJob; - private readonly Process _pluginProcess; - private bool _started; - private bool _disposed; - - public static Sip003Plugin CreateIfConfigured(Server server, bool showPluginOutput) - { - if (server == null) - { - throw new ArgumentNullException(nameof(server)); - } - - if (string.IsNullOrWhiteSpace(server.plugin)) - { - return null; - } - - return new Sip003Plugin( - server.plugin, - server.plugin_opts, - server.plugin_args, - server.server, - server.server_port, - showPluginOutput); - } - - private Sip003Plugin(string plugin, string pluginOpts, string pluginArgs, string serverAddress, int serverPort, bool showPluginOutput) - { - if (plugin == null) throw new ArgumentNullException(nameof(plugin)); - if (string.IsNullOrWhiteSpace(serverAddress)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(serverAddress)); - } - if (serverPort <= 0 || serverPort > 65535) - { - throw new ArgumentOutOfRangeException("serverPort"); - } - - _pluginProcess = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = plugin, - Arguments = pluginArgs, - UseShellExecute = false, - CreateNoWindow = !showPluginOutput, - ErrorDialog = false, - WindowStyle = ProcessWindowStyle.Hidden, - WorkingDirectory = Program.WorkingDirectory ?? Environment.CurrentDirectory, - Environment = - { - ["SS_REMOTE_HOST"] = serverAddress, - ["SS_REMOTE_PORT"] = serverPort.ToString(), - ["SS_PLUGIN_OPTIONS"] = pluginOpts - } - } - }; - - _pluginJob = new Job(); - } - - public bool StartIfNeeded() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().FullName); - } - - lock (_startProcessLock) - { - if (_started && !_pluginProcess.HasExited) - { - return false; - } - - var localPort = GetNextFreeTcpPort(); - LocalEndPoint = new IPEndPoint(IPAddress.Loopback, localPort); - - _pluginProcess.StartInfo.Environment["SS_LOCAL_HOST"] = LocalEndPoint.Address.ToString(); - _pluginProcess.StartInfo.Environment["SS_LOCAL_PORT"] = LocalEndPoint.Port.ToString(); - _pluginProcess.StartInfo.Arguments = ExpandEnvironmentVariables(_pluginProcess.StartInfo.Arguments, _pluginProcess.StartInfo.EnvironmentVariables); - try - { - _pluginProcess.Start(); - } - catch (System.ComponentModel.Win32Exception ex) - { - // do not use File.Exists(...), it can not handle the scenarios when the plugin file is in system environment path. - // https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values - //if ((uint)ex.ErrorCode == 0x80004005) - // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d - if (ex.NativeErrorCode == 0x00000002) - { - throw new FileNotFoundException(I18N.GetString("Cannot find the plugin program file"), _pluginProcess.StartInfo.FileName, ex); - } - throw new ApplicationException(I18N.GetString("Plugin Program"), ex); - } - _pluginJob.AddProcess(_pluginProcess.Handle); - _started = true; - } - - return true; - } - - public string ExpandEnvironmentVariables(string name, StringDictionary environmentVariables = null) - { - name = name.ToLower(); - // Expand the environment variables from the new process itself - if (environmentVariables != null) - { - foreach(string key in environmentVariables.Keys) - { - name = name.Replace($"%{key.ToLower()}%", environmentVariables[key]); - } - } - // Also expand the environment variables from current main process (system) - name = Environment.ExpandEnvironmentVariables(name); - return name; - } - - static int GetNextFreeTcpPort() - { - var l = new TcpListener(IPAddress.Loopback, 0); - l.Start(); - int port = ((IPEndPoint)l.LocalEndpoint).Port; - l.Stop(); - return port; - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - try - { - if (!_pluginProcess.HasExited) - { - _pluginProcess.Kill(); - _pluginProcess.WaitForExit(); - } - } - catch (Exception) { } - finally - { - try - { - _pluginProcess.Dispose(); - _pluginJob.Dispose(); - } - catch (Exception) { } - - _disposed = true; - } - } - } +using System; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using Shadowsocks.Model; +using Shadowsocks.Util.ProcessManagement; + +namespace Shadowsocks.WPF.Services +{ + // https://github.com/shadowsocks/shadowsocks-org/wiki/Plugin + public sealed class Sip003Plugin : IDisposable + { + public IPEndPoint LocalEndPoint { get; private set; } + public int ProcessId => _started ? _pluginProcess.Id : 0; + + private readonly object _startProcessLock = new object(); + private readonly Process _pluginProcess; + private bool _started; + private bool _disposed; + + public static Sip003Plugin CreateIfConfigured(Server server, bool showPluginOutput) + { + if (server == null) + { + throw new ArgumentNullException(nameof(server)); + } + + if (string.IsNullOrWhiteSpace(server.plugin)) + { + return null; + } + + return new Sip003Plugin( + server.plugin, + server.plugin_opts, + server.plugin_args, + server.server, + server.server_port, + showPluginOutput); + } + + private Sip003Plugin(string plugin, string pluginOpts, string pluginArgs, string serverAddress, int serverPort, bool showPluginOutput) + { + if (plugin == null) throw new ArgumentNullException(nameof(plugin)); + if (string.IsNullOrWhiteSpace(serverAddress)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(serverAddress)); + } + if (serverPort <= 0 || serverPort > 65535) + { + throw new ArgumentOutOfRangeException("serverPort"); + } + + _pluginProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = plugin, + Arguments = pluginArgs, + UseShellExecute = false, + CreateNoWindow = !showPluginOutput, + ErrorDialog = false, + WindowStyle = ProcessWindowStyle.Hidden, + WorkingDirectory = Program.WorkingDirectory ?? Environment.CurrentDirectory, + Environment = + { + ["SS_REMOTE_HOST"] = serverAddress, + ["SS_REMOTE_PORT"] = serverPort.ToString(), + ["SS_PLUGIN_OPTIONS"] = pluginOpts + } + } + }; + } + + public bool StartIfNeeded() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().FullName); + } + + lock (_startProcessLock) + { + if (_started && !_pluginProcess.HasExited) + { + return false; + } + + var localPort = GetNextFreeTcpPort(); + LocalEndPoint = new IPEndPoint(IPAddress.Loopback, localPort); + + _pluginProcess.StartInfo.Environment["SS_LOCAL_HOST"] = LocalEndPoint.Address.ToString(); + _pluginProcess.StartInfo.Environment["SS_LOCAL_PORT"] = LocalEndPoint.Port.ToString(); + _pluginProcess.StartInfo.Arguments = ExpandEnvironmentVariables(_pluginProcess.StartInfo.Arguments, _pluginProcess.StartInfo.EnvironmentVariables); + try + { + _pluginProcess.Start(); + } + catch (System.ComponentModel.Win32Exception ex) + { + // do not use File.Exists(...), it can not handle the scenarios when the plugin file is in system environment path. + // https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values + //if ((uint)ex.ErrorCode == 0x80004005) + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d + if (ex.NativeErrorCode == 0x00000002) + { + throw new FileNotFoundException(I18N.GetString("Cannot find the plugin program file"), _pluginProcess.StartInfo.FileName, ex); + } + throw new ApplicationException(I18N.GetString("Plugin Program"), ex); + } + _pluginJob.AddProcess(_pluginProcess.Handle); + _started = true; + } + + return true; + } + + public string ExpandEnvironmentVariables(string name, StringDictionary environmentVariables = null) + { + name = name.ToLower(); + // Expand the environment variables from the new process itself + if (environmentVariables != null) + { + foreach(string key in environmentVariables.Keys) + { + name = name.Replace($"%{key.ToLower()}%", environmentVariables[key]); + } + } + // Also expand the environment variables from current main process (system) + name = Environment.ExpandEnvironmentVariables(name); + return name; + } + + static int GetNextFreeTcpPort() + { + var l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + int port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + try + { + if (!_pluginProcess.HasExited) + { + _pluginProcess.Kill(); + _pluginProcess.WaitForExit(); + } + } + catch (Exception) { } + finally + { + try + { + _pluginProcess.Dispose(); + _pluginJob.Dispose(); + } + catch (Exception) { } + + _disposed = true; + } + } + } } \ No newline at end of file diff --git a/shadowsocks-csharp/Util/SystemProxy/RAS.cs b/Shadowsocks.WPF/Services/SystemProxy/RAS.cs similarity index 94% rename from shadowsocks-csharp/Util/SystemProxy/RAS.cs rename to Shadowsocks.WPF/Services/SystemProxy/RAS.cs index 3dd3ef9a..b60b56e9 100644 --- a/shadowsocks-csharp/Util/SystemProxy/RAS.cs +++ b/Shadowsocks.WPF/Services/SystemProxy/RAS.cs @@ -1,20 +1,19 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; -namespace Shadowsocks.Util.SystemProxy +namespace Shadowsocks.WPF.Services.SystemProxy { - - enum RasFieldSizeConst + public enum RasFieldSizeConst { MaxEntryName = 256, MaxPath = 260, } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - struct RasEntryName + public struct RasEntryName { public int dwSize; @@ -26,7 +25,8 @@ namespace Shadowsocks.Util.SystemProxy [MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS.MaxPath + 1)] public string szPhonebookPath; } - class RAS + + public class RAS { public const int MaxEntryName = 256; public const int MaxPath = 260; diff --git a/shadowsocks-csharp/Util/SystemProxy/WinINet.cs b/Shadowsocks.WPF/Services/SystemProxy/WinINet.cs similarity index 99% rename from shadowsocks-csharp/Util/SystemProxy/WinINet.cs rename to Shadowsocks.WPF/Services/SystemProxy/WinINet.cs index b5b275f3..de90a6f8 100644 --- a/shadowsocks-csharp/Util/SystemProxy/WinINet.cs +++ b/Shadowsocks.WPF/Services/SystemProxy/WinINet.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using NLog; -namespace Shadowsocks.Util.SystemProxy +namespace Shadowsocks.WPF.Services.SystemProxy { #region Windows API data structure definition public enum InternetOptions diff --git a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs b/Shadowsocks.WPF/Services/UpdateChecker.cs similarity index 96% rename from shadowsocks-csharp/Controller/Service/UpdateChecker.cs rename to Shadowsocks.WPF/Services/UpdateChecker.cs index 8ece7da9..0ec99435 100644 --- a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs +++ b/Shadowsocks.WPF/Services/UpdateChecker.cs @@ -1,178 +1,178 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Windows; -using Newtonsoft.Json.Linq; -using NLog; -using Shadowsocks.Localization; -using Shadowsocks.Model; -using Shadowsocks.Util; -using Shadowsocks.Views; - -namespace Shadowsocks.Controller -{ - public class UpdateChecker - { - private readonly Logger logger; - private readonly HttpClient httpClient; - - // https://developer.github.com/v3/repos/releases/ - private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases"; - - private Configuration _config; - private Window versionUpdatePromptWindow; - private JToken _releaseObject; - - public string NewReleaseVersion { get; private set; } - public string NewReleaseZipFilename { get; private set; } - - public event EventHandler CheckUpdateCompleted; - - public static readonly string Version = Assembly.GetEntryAssembly().GetName().Version.ToString(); - private readonly Version _version; - - public UpdateChecker() - { - logger = LogManager.GetCurrentClassLogger(); - httpClient = Program.MainController.GetHttpClient(); - _version = new Version(Version); - _config = Program.MainController.GetCurrentConfiguration(); - } - - /// - /// Checks for updates and asks the user if updates are found. - /// - /// A delay in milliseconds before checking. - /// - public async Task CheckForVersionUpdate(int millisecondsDelay = 0) - { - // delay - logger.Info($"Waiting for {millisecondsDelay}ms before checking for version update."); - await Task.Delay(millisecondsDelay); - // update _config so we would know if the user checked or unchecked pre-release checks - _config = Program.MainController.GetCurrentConfiguration(); - // start - logger.Info($"Checking for version update."); - try - { - // list releases via API - var releasesListJsonString = await httpClient.GetStringAsync(UpdateURL); - // parse - var releasesJArray = JArray.Parse(releasesListJsonString); - foreach (var releaseObject in releasesJArray) - { - var releaseTagName = (string)releaseObject["tag_name"]; - var releaseVersion = new Version(releaseTagName); - if (releaseTagName == _config.skippedUpdateVersion) // finished checking - break; - if (releaseVersion.CompareTo(_version) > 0 && - (!(bool)releaseObject["prerelease"] || _config.checkPreRelease && (bool)releaseObject["prerelease"])) // selected - { - logger.Info($"Found new version {releaseTagName}."); - _releaseObject = releaseObject; - NewReleaseVersion = releaseTagName; - AskToUpdate(releaseObject); - return; - } - } - logger.Info($"No new versions found."); - CheckUpdateCompleted?.Invoke(this, new EventArgs()); - } - catch (Exception e) - { - logger.LogUsefulException(e); - } - } - - /// - /// Opens a window to show the update's information. - /// - /// The update release object. - private void AskToUpdate(JToken releaseObject) - { - if (versionUpdatePromptWindow == null) - { - versionUpdatePromptWindow = new Window() - { - Title = LocalizationProvider.GetLocalizedValue("VersionUpdate"), - Height = 480, - Width = 640, - MinHeight = 480, - MinWidth = 640, - Content = new VersionUpdatePromptView(releaseObject) - }; - versionUpdatePromptWindow.Closed += VersionUpdatePromptWindow_Closed; - versionUpdatePromptWindow.Show(); - } - versionUpdatePromptWindow.Activate(); - } - - private void VersionUpdatePromptWindow_Closed(object sender, EventArgs e) - { - versionUpdatePromptWindow = null; - } - - /// - /// Downloads the selected update and notifies the user. - /// - /// - public async Task DoUpdate() - { - try - { - var assets = (JArray)_releaseObject["assets"]; - // download all assets - foreach (JObject asset in assets) - { - var filename = (string)asset["name"]; - var browser_download_url = (string)asset["browser_download_url"]; - var response = await httpClient.GetAsync(browser_download_url); - using (var downloadedFileStream = File.Create(Utils.GetTempPath(filename))) - await response.Content.CopyToAsync(downloadedFileStream); - logger.Info($"Downloaded {filename}."); - // store .zip filename - if (filename.EndsWith(".zip")) - NewReleaseZipFilename = filename; - } - logger.Info("Finished downloading."); - // notify user - CloseVersionUpdatePromptWindow(); - Process.Start("explorer.exe", $"/select, \"{Utils.GetTempPath(NewReleaseZipFilename)}\""); - } - catch (Exception e) - { - logger.LogUsefulException(e); - } - } - - /// - /// Saves the skipped update version. - /// - public void SkipUpdate() - { - var version = (string)_releaseObject["tag_name"] ?? ""; - _config.skippedUpdateVersion = version; - Program.MainController.SaveSkippedUpdateVerion(version); - logger.Info($"The update {version} has been skipped and will be ignored next time."); - CloseVersionUpdatePromptWindow(); - } - - /// - /// Closes the update prompt window. - /// - public void CloseVersionUpdatePromptWindow() - { - if (versionUpdatePromptWindow != null) - { - versionUpdatePromptWindow.Close(); - versionUpdatePromptWindow = null; - } - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows; +using Newtonsoft.Json.Linq; +using NLog; +using Shadowsocks.Localization; +using Shadowsocks.Model; +using Shadowsocks.Util; +using Shadowsocks.Views; + +namespace Shadowsocks.WPF.Services +{ + public class UpdateChecker + { + private readonly Logger logger; + private readonly HttpClient httpClient; + + // https://developer.github.com/v3/repos/releases/ + private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases"; + + private Configuration _config; + private Window versionUpdatePromptWindow; + private JToken _releaseObject; + + public string NewReleaseVersion { get; private set; } + public string NewReleaseZipFilename { get; private set; } + + public event EventHandler CheckUpdateCompleted; + + public static readonly string Version = Assembly.GetEntryAssembly().GetName().Version.ToString(); + private readonly Version _version; + + public UpdateChecker() + { + logger = LogManager.GetCurrentClassLogger(); + httpClient = Program.MainController.GetHttpClient(); + _version = new Version(Version); + _config = Program.MainController.GetCurrentConfiguration(); + } + + /// + /// Checks for updates and asks the user if updates are found. + /// + /// A delay in milliseconds before checking. + /// + public async Task CheckForVersionUpdate(int millisecondsDelay = 0) + { + // delay + logger.Info($"Waiting for {millisecondsDelay}ms before checking for version update."); + await Task.Delay(millisecondsDelay); + // update _config so we would know if the user checked or unchecked pre-release checks + _config = Program.MainController.GetCurrentConfiguration(); + // start + logger.Info($"Checking for version update."); + try + { + // list releases via API + var releasesListJsonString = await httpClient.GetStringAsync(UpdateURL); + // parse + var releasesJArray = JArray.Parse(releasesListJsonString); + foreach (var releaseObject in releasesJArray) + { + var releaseTagName = (string)releaseObject["tag_name"]; + var releaseVersion = new Version(releaseTagName); + if (releaseTagName == _config.skippedUpdateVersion) // finished checking + break; + if (releaseVersion.CompareTo(_version) > 0 && + (!(bool)releaseObject["prerelease"] || _config.checkPreRelease && (bool)releaseObject["prerelease"])) // selected + { + logger.Info($"Found new version {releaseTagName}."); + _releaseObject = releaseObject; + NewReleaseVersion = releaseTagName; + AskToUpdate(releaseObject); + return; + } + } + logger.Info($"No new versions found."); + CheckUpdateCompleted?.Invoke(this, new EventArgs()); + } + catch (Exception e) + { + logger.LogUsefulException(e); + } + } + + /// + /// Opens a window to show the update's information. + /// + /// The update release object. + private void AskToUpdate(JToken releaseObject) + { + if (versionUpdatePromptWindow == null) + { + versionUpdatePromptWindow = new Window() + { + Title = LocalizationProvider.GetLocalizedValue("VersionUpdate"), + Height = 480, + Width = 640, + MinHeight = 480, + MinWidth = 640, + Content = new VersionUpdatePromptView(releaseObject) + }; + versionUpdatePromptWindow.Closed += VersionUpdatePromptWindow_Closed; + versionUpdatePromptWindow.Show(); + } + versionUpdatePromptWindow.Activate(); + } + + private void VersionUpdatePromptWindow_Closed(object sender, EventArgs e) + { + versionUpdatePromptWindow = null; + } + + /// + /// Downloads the selected update and notifies the user. + /// + /// + public async Task DoUpdate() + { + try + { + var assets = (JArray)_releaseObject["assets"]; + // download all assets + foreach (JObject asset in assets) + { + var filename = (string)asset["name"]; + var browser_download_url = (string)asset["browser_download_url"]; + var response = await httpClient.GetAsync(browser_download_url); + using (var downloadedFileStream = File.Create(Utils.GetTempPath(filename))) + await response.Content.CopyToAsync(downloadedFileStream); + logger.Info($"Downloaded {filename}."); + // store .zip filename + if (filename.EndsWith(".zip")) + NewReleaseZipFilename = filename; + } + logger.Info("Finished downloading."); + // notify user + CloseVersionUpdatePromptWindow(); + Process.Start("explorer.exe", $"/select, \"{Utils.GetTempPath(NewReleaseZipFilename)}\""); + } + catch (Exception e) + { + logger.LogUsefulException(e); + } + } + + /// + /// Saves the skipped update version. + /// + public void SkipUpdate() + { + var version = (string)_releaseObject["tag_name"] ?? ""; + _config.skippedUpdateVersion = version; + Program.MainController.SaveSkippedUpdateVerion(version); + logger.Info($"The update {version} has been skipped and will be ignored next time."); + CloseVersionUpdatePromptWindow(); + } + + /// + /// Closes the update prompt window. + /// + public void CloseVersionUpdatePromptWindow() + { + if (versionUpdatePromptWindow != null) + { + versionUpdatePromptWindow.Close(); + versionUpdatePromptWindow = null; + } + } + } +} diff --git a/Shadowsocks.WPF/Shadowsocks.WPF.csproj b/Shadowsocks.WPF/Shadowsocks.WPF.csproj index 95613db6..ad7152f3 100644 --- a/Shadowsocks.WPF/Shadowsocks.WPF.csproj +++ b/Shadowsocks.WPF/Shadowsocks.WPF.csproj @@ -20,25 +20,11 @@ + - - - - - - - - - - - - - - - True @@ -58,4 +44,9 @@ + + + + + \ No newline at end of file diff --git a/Shadowsocks.WPF/ViewModels/DashboardViewModel.cs b/Shadowsocks.WPF/ViewModels/DashboardViewModel.cs new file mode 100644 index 00000000..4c96b970 --- /dev/null +++ b/Shadowsocks.WPF/ViewModels/DashboardViewModel.cs @@ -0,0 +1,15 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shadowsocks.WPF.ViewModels +{ + public class DashboardViewModel : ReactiveObject + { + public DashboardViewModel() + { + + } + } +} diff --git a/Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs b/Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs index 042a5d34..c024f127 100644 --- a/Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs +++ b/Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs @@ -1,7 +1,4 @@ using ReactiveUI; -using Shadowsocks.Controller.Service; - -using System; namespace Shadowsocks.WPF.ViewModels { diff --git a/Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs b/Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs index 2c4d1b5d..24b40e31 100644 --- a/Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs +++ b/Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs @@ -1,9 +1,9 @@ -using ReactiveUI; +using ReactiveUI; using ReactiveUI.Fody.Helpers; using ReactiveUI.Validation.Extensions; using ReactiveUI.Validation.Helpers; using Shadowsocks.Controller; -using Shadowsocks.Localization; +using Shadowsocks.WPF.Localization; using Shadowsocks.Model; using Shadowsocks.View; using System; diff --git a/Shadowsocks.WPF/ViewModels/RoutingViewModel.cs b/Shadowsocks.WPF/ViewModels/RoutingViewModel.cs new file mode 100644 index 00000000..52b28fc6 --- /dev/null +++ b/Shadowsocks.WPF/ViewModels/RoutingViewModel.cs @@ -0,0 +1,15 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shadowsocks.WPF.ViewModels +{ + public class RoutingViewModel : ReactiveObject + { + public RoutingViewModel() + { + + } + } +} diff --git a/Shadowsocks.WPF/ViewModels/ServersViewModel.cs b/Shadowsocks.WPF/ViewModels/ServersViewModel.cs new file mode 100644 index 00000000..89ee6be6 --- /dev/null +++ b/Shadowsocks.WPF/ViewModels/ServersViewModel.cs @@ -0,0 +1,15 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shadowsocks.WPF.ViewModels +{ + public class ServersViewModel : ReactiveObject + { + public ServersViewModel() + { + + } + } +} diff --git a/Shadowsocks.WPF/ViewModels/SettingsViewModel.cs b/Shadowsocks.WPF/ViewModels/SettingsViewModel.cs new file mode 100644 index 00000000..e6b5412a --- /dev/null +++ b/Shadowsocks.WPF/ViewModels/SettingsViewModel.cs @@ -0,0 +1,15 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shadowsocks.WPF.ViewModels +{ + public class SettingsViewModel : ReactiveObject + { + public SettingsViewModel() + { + + } + } +} diff --git a/Shadowsocks.WPF/Views/DashboardView.xaml b/Shadowsocks.WPF/Views/DashboardView.xaml new file mode 100644 index 00000000..4614e373 --- /dev/null +++ b/Shadowsocks.WPF/Views/DashboardView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/Shadowsocks.WPF/Views/DashboardView.xaml.cs b/Shadowsocks.WPF/Views/DashboardView.xaml.cs new file mode 100644 index 00000000..f7188688 --- /dev/null +++ b/Shadowsocks.WPF/Views/DashboardView.xaml.cs @@ -0,0 +1,32 @@ +using ReactiveUI; +using Shadowsocks.WPF.ViewModels; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Shadowsocks.WPF.Views +{ + /// + /// Interaction logic for DashboardView.xaml + /// + public partial class DashboardView : ReactiveUserControl + { + public DashboardView() + { + InitializeComponent(); + this.WhenActivated(disposables => + { + + }); + } + } +} diff --git a/Shadowsocks.WPF/Views/MainWindow.xaml b/Shadowsocks.WPF/Views/MainWindow.xaml index 110aa42a..455e7e4b 100644 --- a/Shadowsocks.WPF/Views/MainWindow.xaml +++ b/Shadowsocks.WPF/Views/MainWindow.xaml @@ -1,15 +1,19 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shadowsocks.WPF/Views/MainWindow.xaml.cs b/Shadowsocks.WPF/Views/MainWindow.xaml.cs index 2bf713d8..36950be7 100644 --- a/Shadowsocks.WPF/Views/MainWindow.xaml.cs +++ b/Shadowsocks.WPF/Views/MainWindow.xaml.cs @@ -12,6 +12,11 @@ namespace Shadowsocks.WPF.Views public MainWindow() { InitializeComponent(); + ViewModel = new MainWindowViewModel(); + this.WhenActivated(disposables => + { + + }); } } } diff --git a/Shadowsocks.WPF/Views/RoutingView.xaml b/Shadowsocks.WPF/Views/RoutingView.xaml new file mode 100644 index 00000000..01766672 --- /dev/null +++ b/Shadowsocks.WPF/Views/RoutingView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/Shadowsocks.WPF/Views/RoutingView.xaml.cs b/Shadowsocks.WPF/Views/RoutingView.xaml.cs new file mode 100644 index 00000000..987811b2 --- /dev/null +++ b/Shadowsocks.WPF/Views/RoutingView.xaml.cs @@ -0,0 +1,32 @@ +using ReactiveUI; +using Shadowsocks.WPF.ViewModels; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Shadowsocks.WPF.Views +{ + /// + /// Interaction logic for RoutingView.xaml + /// + public partial class RoutingView : ReactiveUserControl + { + public RoutingView() + { + InitializeComponent(); + this.WhenActivated(disposables => + { + + }); + } + } +} diff --git a/Shadowsocks.WPF/Views/ServersView.xaml b/Shadowsocks.WPF/Views/ServersView.xaml new file mode 100644 index 00000000..d2cbd701 --- /dev/null +++ b/Shadowsocks.WPF/Views/ServersView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/Shadowsocks.WPF/Views/ServersView.xaml.cs b/Shadowsocks.WPF/Views/ServersView.xaml.cs new file mode 100644 index 00000000..0d2fa9f2 --- /dev/null +++ b/Shadowsocks.WPF/Views/ServersView.xaml.cs @@ -0,0 +1,32 @@ +using ReactiveUI; +using Shadowsocks.WPF.ViewModels; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Shadowsocks.WPF.Views +{ + /// + /// Interaction logic for ServersView.xaml + /// + public partial class ServersView : ReactiveUserControl + { + public ServersView() + { + InitializeComponent(); + this.WhenActivated(disposables => + { + + }); + } + } +} diff --git a/Shadowsocks.WPF/Views/SettingsView.xaml b/Shadowsocks.WPF/Views/SettingsView.xaml new file mode 100644 index 00000000..471ef2c7 --- /dev/null +++ b/Shadowsocks.WPF/Views/SettingsView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/Shadowsocks.WPF/Views/SettingsView.xaml.cs b/Shadowsocks.WPF/Views/SettingsView.xaml.cs new file mode 100644 index 00000000..798d9520 --- /dev/null +++ b/Shadowsocks.WPF/Views/SettingsView.xaml.cs @@ -0,0 +1,32 @@ +using ReactiveUI; +using Shadowsocks.WPF.ViewModels; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Shadowsocks.WPF.Views +{ + /// + /// Interaction logic for SettingsView.xaml + /// + public partial class SettingsView : ReactiveUserControl + { + public SettingsView() + { + InitializeComponent(); + this.WhenActivated(disposables => + { + + }); + } + } +} diff --git a/Shadowsocks/Models/Server.cs b/Shadowsocks/Models/Server.cs new file mode 100644 index 00000000..4eab0cd6 --- /dev/null +++ b/Shadowsocks/Models/Server.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace Shadowsocks.Models +{ + public class Server + { + [JsonPropertyName("server")] + public string Host { get; set; } + [JsonPropertyName("server_port")] + public int Port { get; set; } + public string Password { get; set; } + public string Method { get; set; } + public string Plugin { get; set; } + public string PluginOpts { get; set; } + [JsonPropertyName("remarks")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Uuid { get; set; } + + public Server() + { + Host = ""; + Password = ""; + Method = ""; + Plugin = ""; + PluginOpts = ""; + Name = ""; + Uuid = ""; + } + + public Server( + string name, + string uuid, + string host, + int port, + string password, + string method, + string plugin = "", + string pluginOpts = "") + { + Host = host; + Port = port; + Password = password; + Method = method; + Plugin = plugin; + PluginOpts = pluginOpts; + Name = name; + Uuid = uuid; + } + } +} diff --git a/Shadowsocks/Shadowsocks.csproj b/Shadowsocks/Shadowsocks.csproj index f43b6b00..cb631906 100644 --- a/Shadowsocks/Shadowsocks.csproj +++ b/Shadowsocks/Shadowsocks.csproj @@ -1,20 +1,7 @@ - netstandard2.1 - clowwindy & community 2020 + netcoreapp3.1 - - - - - - - - - - - - diff --git a/Shadowsocks/Utilities/Base64Url.cs b/Shadowsocks/Utilities/Base64Url.cs new file mode 100644 index 00000000..16b993ac --- /dev/null +++ b/Shadowsocks/Utilities/Base64Url.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shadowsocks.Utilities +{ + public static class Base64Url + { + public static string Encode(string data) => Encode(Encoding.UTF8.GetBytes(data)); + + public static string Encode(byte[] bytes) => Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_'); + + public static string DecodeToString(string base64url) => Encoding.UTF8.GetString(DecodeToBytes(base64url)); + + public static byte[] DecodeToBytes(string base64url) + { + string base64string = base64url.Replace('_', '/').Replace('-', '+'); + base64string = base64string.PadRight(base64string.Length + (4 - base64string.Length % 4) % 4, '='); + return Convert.FromBase64String(base64string); + } + } +} diff --git a/shadowsocks-csharp/Controller/I18N.cs b/shadowsocks-csharp/Controller/I18N.cs deleted file mode 100755 index 5111a777..00000000 --- a/shadowsocks-csharp/Controller/I18N.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Microsoft.VisualBasic.FileIO; -using NLog; -using Shadowsocks.Properties; -using Shadowsocks.Util; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; -using System.Windows.Forms; - -namespace Shadowsocks.Controller -{ - public static class I18N - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - public const string I18N_FILE = "i18n.csv"; - - private static Dictionary _strings = new Dictionary(); - - private static void Init(string res, string locale) - { - using (TextFieldParser csvParser = new TextFieldParser(new StringReader(res))) - { - csvParser.SetDelimiters(","); - - // search language index - string[] localeNames = csvParser.ReadFields(); - - int enIndex = 0; - int targetIndex = -1; - - for (int i = 0; i < localeNames.Length; i++) - { - if (localeNames[i] == "en") - enIndex = i; - if (localeNames[i] == locale) - targetIndex = i; - } - - // Fallback to same language with different region - if (targetIndex == -1) - { - string localeNoRegion = locale.Split('-')[0]; - for (int i = 0; i < localeNames.Length; i++) - { - if (localeNames[i].Split('-')[0] == localeNoRegion) - targetIndex = i; - } - if (targetIndex != -1 && enIndex != targetIndex) - { - logger.Info($"Using {localeNames[targetIndex]} translation for {locale}"); - } - else - { - // Still not found, exit - logger.Info($"Translation for {locale} not found"); - return; - } - } - - // read translation lines - while (!csvParser.EndOfData) - { - string[] translations = csvParser.ReadFields(); - string source = translations[enIndex]; - string translation = translations[targetIndex]; - - // source string or translation empty - if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(translation)) continue; - // line start with comment - if (translations[0].TrimStart(' ')[0] == '#') continue; - - _strings[source] = translation; - } - } - } - - static I18N() - { - string i18n; - string locale = CultureInfo.CurrentCulture.Name; - if (!File.Exists(I18N_FILE)) - { - i18n = Resources.i18n_csv; - //File.WriteAllText(I18N_FILE, i18n, Encoding.UTF8); - } - else - { - logger.Info("Using external translation"); - i18n = File.ReadAllText(I18N_FILE, Encoding.UTF8); - } - logger.Info("Current language is: " + locale); - Init(i18n, locale); - } - - public static string GetString(string key, params object[] args) - { - return string.Format(_strings.TryGetValue(key.Trim(), out var value) ? value : key, args); - } - - public static void TranslateForm(Form c) - { - if (c == null) return; - c.Text = GetString(c.Text); - foreach (var item in ViewUtils.GetChildControls(c)) - { - if (item == null) continue; - item.Text = GetString(item.Text); - } - TranslateMenu(c.MainMenuStrip); - } - public static void TranslateMenu(MenuStrip m) - { - if (m == null) return; - foreach (var item in ViewUtils.GetToolStripMenuItems(m)) - { - if (item == null) continue; - item.Text = GetString(item.Text); - } - } - } -} diff --git a/shadowsocks-csharp/Controller/Strategy/BalancingStrategy.cs b/shadowsocks-csharp/Controller/Strategy/BalancingStrategy.cs deleted file mode 100644 index 82e30eb8..00000000 --- a/shadowsocks-csharp/Controller/Strategy/BalancingStrategy.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Shadowsocks.Controller; -using Shadowsocks.Model; -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; - -namespace Shadowsocks.Controller.Strategy -{ - class BalancingStrategy : IStrategy - { - ShadowsocksController _controller; - Random _random; - - public BalancingStrategy(ShadowsocksController controller) - { - _controller = controller; - _random = new Random(); - } - - public string Name - { - get { return I18N.GetString("Load Balance"); } - } - - public string ID - { - get { return "com.shadowsocks.strategy.balancing"; } - } - - public void ReloadServers() - { - // do nothing - } - - public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint) - { - var configs = _controller.GetCurrentConfiguration().configs; - int index; - if (type == IStrategyCallerType.TCP) - { - index = _random.Next(); - } - else - { - index = localIPEndPoint.GetHashCode(); - } - return configs[index % configs.Count]; - } - - public void UpdateLatency(Model.Server server, TimeSpan latency) - { - // do nothing - } - - public void UpdateLastRead(Model.Server server) - { - // do nothing - } - - public void UpdateLastWrite(Model.Server server) - { - // do nothing - } - - public void SetFailure(Model.Server server) - { - // do nothing - } - } -} diff --git a/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs b/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs deleted file mode 100644 index d19ea24a..00000000 --- a/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs +++ /dev/null @@ -1,189 +0,0 @@ -using NLog; -using Shadowsocks.Model; -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; - -namespace Shadowsocks.Controller.Strategy -{ - class HighAvailabilityStrategy : IStrategy - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - protected ServerStatus _currentServer; - protected Dictionary _serverStatus; - ShadowsocksController _controller; - Random _random; - - public class ServerStatus - { - // time interval between SYN and SYN+ACK - public TimeSpan latency; - public DateTime lastTimeDetectLatency; - - // last time anything received - public DateTime lastRead; - - // last time anything sent - public DateTime lastWrite; - - // connection refused or closed before anything received - public DateTime lastFailure; - - public Server server; - - public double score; - } - - public HighAvailabilityStrategy(ShadowsocksController controller) - { - _controller = controller; - _random = new Random(); - _serverStatus = new Dictionary(); - } - - public string Name - { - get { return I18N.GetString("High Availability"); } - } - - public string ID - { - get { return "com.shadowsocks.strategy.ha"; } - } - - public void ReloadServers() - { - // make a copy to avoid locking - var newServerStatus = new Dictionary(_serverStatus); - - foreach (var server in _controller.GetCurrentConfiguration().configs) - { - if (!newServerStatus.ContainsKey(server)) - { - var status = new ServerStatus(); - status.server = server; - status.lastFailure = DateTime.MinValue; - status.lastRead = DateTime.Now; - status.lastWrite = DateTime.Now; - status.latency = new TimeSpan(0, 0, 0, 0, 10); - status.lastTimeDetectLatency = DateTime.Now; - newServerStatus[server] = status; - } - else - { - // update settings for existing server - newServerStatus[server].server = server; - } - } - _serverStatus = newServerStatus; - - ChooseNewServer(); - } - - public Server GetAServer(IStrategyCallerType type, System.Net.IPEndPoint localIPEndPoint, EndPoint destEndPoint) - { - if (type == IStrategyCallerType.TCP) - { - ChooseNewServer(); - } - if (_currentServer == null) - { - return null; - } - return _currentServer.server; - } - - /** - * once failed, try after 5 min - * and (last write - last read) < 5s - * and (now - last read) < 5s // means not stuck - * and latency < 200ms, try after 30s - */ - public void ChooseNewServer() - { - ServerStatus oldServer = _currentServer; - List servers = new List(_serverStatus.Values); - DateTime now = DateTime.Now; - foreach (var status in servers) - { - // all of failure, latency, (lastread - lastwrite) normalized to 1000, then - // 100 * failure - 2 * latency - 0.5 * (lastread - lastwrite) - status.score = - 100 * 1000 * Math.Min(5 * 60, (now - status.lastFailure).TotalSeconds) - -2 * 5 * (Math.Min(2000, status.latency.TotalMilliseconds) / (1 + (now - status.lastTimeDetectLatency).TotalSeconds / 30 / 10) + - -0.5 * 200 * Math.Min(5, (status.lastRead - status.lastWrite).TotalSeconds)); - logger.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.ToString(), status.latency, status.score)); - } - ServerStatus max = null; - foreach (var status in servers) - { - if (max == null) - { - max = status; - } - else - { - if (status.score >= max.score) - { - max = status; - } - } - } - if (max != null) - { - if (_currentServer == null || max.score - _currentServer.score > 200) - { - _currentServer = max; - logger.Info($"HA switching to server: {_currentServer.server.ToString()}"); - } - } - } - - public void UpdateLatency(Model.Server server, TimeSpan latency) - { - logger.Debug($"latency: {server.ToString()} {latency}"); - - ServerStatus status; - if (_serverStatus.TryGetValue(server, out status)) - { - status.latency = latency; - status.lastTimeDetectLatency = DateTime.Now; - } - } - - public void UpdateLastRead(Model.Server server) - { - logger.Debug($"last read: {server.ToString()}"); - - ServerStatus status; - if (_serverStatus.TryGetValue(server, out status)) - { - status.lastRead = DateTime.Now; - } - } - - public void UpdateLastWrite(Model.Server server) - { - logger.Debug($"last write: {server.ToString()}"); - - ServerStatus status; - if (_serverStatus.TryGetValue(server, out status)) - { - status.lastWrite = DateTime.Now; - } - } - - public void SetFailure(Model.Server server) - { - logger.Debug($"failure: {server.ToString()}"); - - ServerStatus status; - if (_serverStatus.TryGetValue(server, out status)) - { - status.lastFailure = DateTime.Now; - } - } - } -} diff --git a/shadowsocks-csharp/Controller/Strategy/IStrategy.cs b/shadowsocks-csharp/Controller/Strategy/IStrategy.cs deleted file mode 100644 index 48bd3aca..00000000 --- a/shadowsocks-csharp/Controller/Strategy/IStrategy.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Shadowsocks.Model; -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; - -namespace Shadowsocks.Controller.Strategy -{ - public enum IStrategyCallerType - { - TCP, - UDP - } - - /* - * IStrategy - * - * Subclasses must be thread-safe - */ - public interface IStrategy - { - string Name { get; } - - string ID { get; } - - /* - * Called when servers need to be reloaded, i.e. new configuration saved - */ - void ReloadServers(); - - /* - * Get a new server to use in TCPRelay or UDPRelay - */ - Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint); - - /* - * TCPRelay will call this when latency of a server detected - */ - void UpdateLatency(Server server, TimeSpan latency); - - /* - * TCPRelay will call this when reading from a server - */ - void UpdateLastRead(Server server); - - /* - * TCPRelay will call this when writing to a server - */ - void UpdateLastWrite(Server server); - - /* - * TCPRelay will call this when fatal failure detected - */ - void SetFailure(Server server); - } -} diff --git a/shadowsocks-csharp/Controller/Strategy/StrategyManager.cs b/shadowsocks-csharp/Controller/Strategy/StrategyManager.cs deleted file mode 100644 index be8869da..00000000 --- a/shadowsocks-csharp/Controller/Strategy/StrategyManager.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Shadowsocks.Controller; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Shadowsocks.Controller.Strategy -{ - class StrategyManager - { - List _strategies; - public StrategyManager(ShadowsocksController controller) - { - _strategies = new List(); - _strategies.Add(new BalancingStrategy(controller)); - _strategies.Add(new HighAvailabilityStrategy(controller)); - // TODO: load DLL plugins - } - public IList GetStrategies() - { - return _strategies; - } - } -} diff --git a/shadowsocks-csharp/HttpServerUtilityUrlToken.cs b/shadowsocks-csharp/HttpServerUtilityUrlToken.cs deleted file mode 100644 index 617e74e9..00000000 --- a/shadowsocks-csharp/HttpServerUtilityUrlToken.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Shadowsocks -{ - /// - /// HttpServerUtility URL Token のエンコード及びデコードを行うクラス。 - /// https://docs.microsoft.com/ja-jp/dotnet/api/system.web.httpserverutility.urltokenencode - /// https://docs.microsoft.com/ja-jp/dotnet/api/system.web.httpserverutility.urltokendecode - /// - /// - /// HttpServerUtility URL Token 形式は、パディング無し base64url にパディング数を文字として追記した文字列です。 - /// 例えば、0x00AA2 になります。 - /// - public static class HttpServerUtilityUrlToken - { -#if NETSTANDARD2_0 - private static readonly byte[] EmptyBytes = Array.Empty(); -#else - private static readonly byte[] EmptyBytes = new byte[0]; -#endif - - /// - /// 配列を HttpServerUtility URL Token にエンコードします。 - /// - /// エンコード対象の 配列。 - /// HttpServerUtility URL Token エンコード文字列。 の長さが 0 の場合は空文字列を返します。 - /// is null. - public static string Encode(byte[] bytes) - { - if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); } - - return Encode(bytes, 0, bytes.Length); - } - - /// - /// 配列を HttpServerUtility URL Token にエンコードします。 - /// - /// エンコード対象の 配列。 - /// エンコードの開始位置を示すオフセット。 - /// エンコード対象の要素の数。 - /// HttpServerUtility URL Token エンコード文字列。0 の場合は空文字列を返します。 - /// is null. - /// - /// または が負の値です。 - /// または を加算した値が の長さを超えています。 - /// - public static string Encode(byte[] bytes, int offset, int length) - { - if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); } - - var encoded = Encode(bytes, offset, length, padding: false); - if (encoded.Length == 0) { return ""; } - - var paddingLen = unchecked(~encoded.Length + 1) & 0b11; - encoded += paddingLen; - - return encoded; - } - /// - /// 配列を base64url にエンコードします。 - /// - /// エンコード対象の 配列。 - /// エンコードの開始位置を示すオフセット。 - /// エンコード対象の要素の数。 - /// パディングをする場合は true、それ以外は false。既定値は false。 - /// base64url エンコード文字列。 - /// is null. - /// - /// または が負の値です。 - /// または を加算した値が の長さを超えています。 - /// - public static string Encode(byte[] bytes, int offset, int length, bool padding = false) - { - var encoded = Convert.ToBase64String(bytes, offset, length); - - if (!padding) - { - encoded = encoded.TrimEnd('='); - } - - return encoded - .Replace('+', '-') - .Replace('/', '_') - ; - } - - /// - /// HttpServerUtility URL Token 文字列を 配列にデコードします。 - /// - /// HttpServerUtility URL Token にエンコードされた文字列。 - /// デコード後の 配列。 が空文字列の場合は の空配列を返します。 - /// is null. - /// が HttpServerUtility URL Token 文字列ではありません。 - public static byte[] Decode(string encoded) - { - if (encoded == null) { throw new ArgumentNullException(nameof(encoded)); } - - if (!TryDecode(encoded, out var result)) { throw new FormatException("HttpServerUtility URL Token 文字列ではありません。"); } - return result; - } - - /// - /// HttpServerUtility URL Token でエンコードされた文字列をデコードします。 - /// - /// HttpServerUtility URL Token エンコードされた文字列。 - /// デコード後の 配列。 が空文字列の場合は の空配列が設定されます。失敗した場合は null。 - /// デコードに成功した場合は true、それ以外は false - public static bool TryDecode(string encoded, out byte[] result) - { - if (encoded == null) { goto Failure; } - if (encoded.Length == 0) - { - result = EmptyBytes; - return true; - } - - var paddingLen = encoded[encoded.Length - 1] - '0'; - if (paddingLen < 0 || paddingLen > 3) { goto Failure; } - - var base64Str = encoded - .Substring(0, encoded.Length - 1) - .Replace('-', '+') - .Replace('_', '/'); - - if (paddingLen > 0) - { - base64Str += new string('=', paddingLen); - } - - try - { - result = Convert.FromBase64String(base64Str); - return true; - } - catch (FormatException) { goto Failure; } - - Failure: - result = null; - return false; - } - } -} diff --git a/shadowsocks-csharp/Model/LogViewerConfig.cs b/shadowsocks-csharp/Model/LogViewerConfig.cs deleted file mode 100644 index 6a2fc862..00000000 --- a/shadowsocks-csharp/Model/LogViewerConfig.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Drawing; -using System.Windows.Forms; -using Newtonsoft.Json; - -namespace Shadowsocks.Model -{ - [Serializable] - public class LogViewerConfig - { - public bool topMost; - public bool wrapText; - public bool toolbarShown; - - public Font Font { get; set; } = new Font("Consolas", 8F); - - public Color BackgroundColor { get; set; } = Color.Black; - - public Color TextColor { get; set; } = Color.White; - - public LogViewerConfig() - { - topMost = false; - wrapText = false; - toolbarShown = false; - } - - - #region Size - - public void SaveSize() - { - Properties.Settings.Default.Save(); - } - - [JsonIgnore] - public int Width - { - get { return Properties.Settings.Default.LogViewerWidth; } - set { Properties.Settings.Default.LogViewerWidth = value; } - } - - [JsonIgnore] - public int Height - { - get { return Properties.Settings.Default.LogViewerHeight; } - set { Properties.Settings.Default.LogViewerHeight = value; } - } - [JsonIgnore] - public int Top - { - get { return Properties.Settings.Default.LogViewerTop; } - set { Properties.Settings.Default.LogViewerTop = value; } - } - [JsonIgnore] - public int Left - { - get { return Properties.Settings.Default.LogViewerLeft; } - set { Properties.Settings.Default.LogViewerLeft = value; } - } - [JsonIgnore] - public bool Maximized - { - get { return Properties.Settings.Default.LogViewerMaximized; } - set { Properties.Settings.Default.LogViewerMaximized = value; } - } - - [JsonIgnore] - // Use GetBestTop() and GetBestLeft() to ensure the log viwer form can be always display IN screen. - public int BestLeft - { - get - { - int width = Width; - width = (width >= 400) ? width : 400; // set up the minimum size - return Screen.PrimaryScreen.WorkingArea.Width - width; - } - } - - [JsonIgnore] - public int BestTop - { - get - { - int height = Height; - height = (height >= 200) ? height : 200; // set up the minimum size - return Screen.PrimaryScreen.WorkingArea.Height - height; - } - } - - #endregion - - } -} diff --git a/shadowsocks-csharp/Model/SysproxyConfig.cs b/shadowsocks-csharp/Model/SysproxyConfig.cs deleted file mode 100644 index 53e78a6e..00000000 --- a/shadowsocks-csharp/Model/SysproxyConfig.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Shadowsocks.Model -{ - /* - * Data come from WinINET - */ - - [Serializable] - public class SysproxyConfig - { - public bool UserSettingsRecorded; - public string Flags; - public string ProxyServer; - public string BypassList; - public string PacUrl; - - public SysproxyConfig() - { - UserSettingsRecorded = false; - Flags = "1"; - // Watchout, Nullable! See #2100 - ProxyServer = ""; - BypassList = ""; - PacUrl = ""; - } - } -} \ No newline at end of file diff --git a/shadowsocks-csharp/Settings.cs b/shadowsocks-csharp/Settings.cs deleted file mode 100644 index a1663c6d..00000000 --- a/shadowsocks-csharp/Settings.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Shadowsocks.Properties { - - - // 通过此类可以处理设置类的特定事件: - // 在更改某个设置的值之前将引发 SettingChanging 事件。 - // 在更改某个设置的值之后将引发 PropertyChanged 事件。 - // 在加载设置值之后将引发 SettingsLoaded 事件。 - // 在保存设置值之前将引发 SettingsSaving 事件。 - internal sealed partial class Settings { - - public Settings() { - // // 若要为保存和更改设置添加事件处理程序,请取消注释下列行: - // - // this.SettingChanging += this.SettingChangingEventHandler; - // - // this.SettingsSaving += this.SettingsSavingEventHandler; - // - } - - private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { - // 在此处添加用于处理 SettingChangingEvent 事件的代码。 - } - - private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { - // 在此处添加用于处理 SettingsSaving 事件的代码。 - } - } -} diff --git a/shadowsocks-csharp/Util/ProcessManagement/Job.cs b/shadowsocks-csharp/Util/ProcessManagement/Job.cs deleted file mode 100644 index 0dadbdbc..00000000 --- a/shadowsocks-csharp/Util/ProcessManagement/Job.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using NLog; -using Shadowsocks.Controller; - -namespace Shadowsocks.Util.ProcessManagement -{ - /* - * See: - * http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net - */ - public class Job : IDisposable - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - private IntPtr handle = IntPtr.Zero; - - public Job() - { - handle = CreateJobObject(IntPtr.Zero, null); - var extendedInfoPtr = IntPtr.Zero; - var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION - { - LimitFlags = 0x2000 - }; - - var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION - { - BasicLimitInformation = info - }; - - try - { - int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); - extendedInfoPtr = Marshal.AllocHGlobal(length); - Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); - - if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, - (uint) length)) - throw new Exception(string.Format("Unable to set information. Error: {0}", - Marshal.GetLastWin32Error())); - } - finally - { - if (extendedInfoPtr != IntPtr.Zero) - { - Marshal.FreeHGlobal(extendedInfoPtr); - extendedInfoPtr = IntPtr.Zero; - } - } - } - - public bool AddProcess(IntPtr processHandle) - { - var succ = AssignProcessToJobObject(handle, processHandle); - - if (!succ) - { - logger.Error("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error()); - } - - return succ; - } - - public bool AddProcess(int processId) - { - return AddProcess(Process.GetProcessById(processId).Handle); - } - - #region IDisposable - - private bool disposed; - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposed) return; - disposed = true; - - if (disposing) - { - // no managed objects to free - } - - if (handle != IntPtr.Zero) - { - CloseHandle(handle); - handle = IntPtr.Zero; - } - } - - ~Job() - { - Dispose(false); - } - - #endregion - - #region Interop - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - private static extern IntPtr CreateJobObject(IntPtr a, string lpName); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool CloseHandle(IntPtr hObject); - - #endregion - } - - #region Helper classes - - [StructLayout(LayoutKind.Sequential)] - struct IO_COUNTERS - { - public UInt64 ReadOperationCount; - public UInt64 WriteOperationCount; - public UInt64 OtherOperationCount; - public UInt64 ReadTransferCount; - public UInt64 WriteTransferCount; - public UInt64 OtherTransferCount; - } - - - [StructLayout(LayoutKind.Sequential)] - struct JOBOBJECT_BASIC_LIMIT_INFORMATION - { - public Int64 PerProcessUserTimeLimit; - public Int64 PerJobUserTimeLimit; - public UInt32 LimitFlags; - public UIntPtr MinimumWorkingSetSize; - public UIntPtr MaximumWorkingSetSize; - public UInt32 ActiveProcessLimit; - public UIntPtr Affinity; - public UInt32 PriorityClass; - public UInt32 SchedulingClass; - } - - [StructLayout(LayoutKind.Sequential)] - public struct SECURITY_ATTRIBUTES - { - public UInt32 nLength; - public IntPtr lpSecurityDescriptor; - public Int32 bInheritHandle; - } - - [StructLayout(LayoutKind.Sequential)] - struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION - { - public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; - public IO_COUNTERS IoInfo; - public UIntPtr ProcessMemoryLimit; - public UIntPtr JobMemoryLimit; - public UIntPtr PeakProcessMemoryUsed; - public UIntPtr PeakJobMemoryUsed; - } - - public enum JobObjectInfoType - { - AssociateCompletionPortInformation = 7, - BasicLimitInformation = 2, - BasicUIRestrictions = 4, - EndOfJobTimeInformation = 6, - ExtendedLimitInformation = 9, - SecurityLimitInformation = 5, - GroupInformation = 11 - } - - #endregion -} diff --git a/shadowsocks-csharp/Util/ProcessManagement/ThreadUtil.cs b/shadowsocks-csharp/Util/ProcessManagement/ThreadUtil.cs deleted file mode 100644 index 9421638f..00000000 --- a/shadowsocks-csharp/Util/ProcessManagement/ThreadUtil.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics; -using System.Management; -using System.Text; - -namespace Shadowsocks.Util.ProcessManagement -{ - static class ThreadUtil - { - - /* - * See: - * http://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c - */ - public static string GetCommandLine(this Process process) - { - var commandLine = new StringBuilder(process.MainModule.FileName); - - commandLine.Append(" "); - using (var searcher = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + process.Id)) - { - foreach (var @object in searcher.Get()) - { - commandLine.Append(@object["CommandLine"]); - commandLine.Append(" "); - } - } - - return commandLine.ToString(); - } - } -} diff --git a/shadowsocks-csharp/Util/Sockets/SocketUtil.cs b/shadowsocks-csharp/Util/Sockets/SocketUtil.cs deleted file mode 100644 index e8035676..00000000 --- a/shadowsocks-csharp/Util/Sockets/SocketUtil.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; - -namespace Shadowsocks.Util.Sockets -{ - public static class SocketUtil - { - private class DnsEndPoint2 : DnsEndPoint - { - public DnsEndPoint2(string host, int port) : base(host, port) - { - } - - public DnsEndPoint2(string host, int port, AddressFamily addressFamily) : base(host, port, addressFamily) - { - } - - public override string ToString() - { - return this.Host + ":" + this.Port; - } - } - - public static EndPoint GetEndPoint(string host, int port) - { - IPAddress ipAddress; - bool parsed = IPAddress.TryParse(host, out ipAddress); - if (parsed) - { - return new IPEndPoint(ipAddress, port); - } - - // maybe is a domain name - return new DnsEndPoint2(host, port); - } - - - public static void FullClose(this System.Net.Sockets.Socket s) - { - try - { - s.Shutdown(SocketShutdown.Both); - } - catch (Exception) - { - } - try - { - s.Disconnect(false); - } - catch (Exception) - { - } - try - { - s.Close(); - } - catch (Exception) - { - } - } - - } -} diff --git a/shadowsocks-csharp/Util/Util.cs b/shadowsocks-csharp/Util/Util.cs deleted file mode 100755 index 5c7560ba..00000000 --- a/shadowsocks-csharp/Util/Util.cs +++ /dev/null @@ -1,289 +0,0 @@ -using NLog; -using System; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Runtime.InteropServices; -using System.Windows.Forms; -using Microsoft.Win32; -using Shadowsocks.Controller; -using Shadowsocks.Model; -using System.Drawing; -using ZXing; -using ZXing.QrCode; -using ZXing.Common; - -namespace Shadowsocks.Util -{ - public struct BandwidthScaleInfo - { - public float value; - public string unitName; - public long unit; - - public BandwidthScaleInfo(float value, string unitName, long unit) - { - this.value = value; - this.unitName = unitName; - this.unit = unit; - } - } - - public static class Utils - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - private static string _tempPath = null; - - // return path to store temporary files - public static string GetTempPath() - { - if (_tempPath == null) - { - bool isPortableMode = Configuration.Load().portableMode; - try - { - if (isPortableMode) - { - _tempPath = Directory.CreateDirectory("ss_win_temp").FullName; - // don't use "/", it will fail when we call explorer /select xxx/ss_win_temp\xxx.log - } - else - { - _tempPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), @"Shadowsocks\ss_win_temp_" + Program.ExecutablePath.GetHashCode())).FullName; - } - } - catch (Exception e) - { - logger.Error(e); - throw; - } - } - return _tempPath; - } - - public enum WindowsThemeMode { Dark, Light } - - // Support on Windows 10 1903+ - public static WindowsThemeMode GetWindows10SystemThemeSetting() - { - WindowsThemeMode themeMode = WindowsThemeMode.Dark; - try - { - RegistryKey reg_ThemesPersonalize = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", false); - if (reg_ThemesPersonalize.GetValue("SystemUsesLightTheme") != null) - { - if ((int)(reg_ThemesPersonalize.GetValue("SystemUsesLightTheme")) == 0) // 0:dark mode, 1:light mode - themeMode = WindowsThemeMode.Dark; - else - themeMode = WindowsThemeMode.Light; - } - else - { - throw new Exception("Reg-Value SystemUsesLightTheme not found."); - } - } - catch - { - - logger.Debug( - $"Cannot get Windows 10 system theme mode, return default value 0 (dark mode)."); - - } - return themeMode; - } - - // return a full path with filename combined which pointed to the temporary directory - public static string GetTempPath(string filename) - { - return Path.Combine(GetTempPath(), filename); - } - - public static string FormatBandwidth(long n) - { - var result = GetBandwidthScale(n); - return $"{result.value:0.##}{result.unitName}"; - } - - public static string FormatBytes(long bytes) - { - const long K = 1024L; - const long M = K * 1024L; - const long G = M * 1024L; - const long T = G * 1024L; - const long P = T * 1024L; - const long E = P * 1024L; - - if (bytes >= P * 990) - return (bytes / (double)E).ToString("F5") + "EiB"; - if (bytes >= T * 990) - return (bytes / (double)P).ToString("F5") + "PiB"; - if (bytes >= G * 990) - return (bytes / (double)T).ToString("F5") + "TiB"; - if (bytes >= M * 990) - { - return (bytes / (double)G).ToString("F4") + "GiB"; - } - if (bytes >= M * 100) - { - return (bytes / (double)M).ToString("F1") + "MiB"; - } - if (bytes >= M * 10) - { - return (bytes / (double)M).ToString("F2") + "MiB"; - } - if (bytes >= K * 990) - { - return (bytes / (double)M).ToString("F3") + "MiB"; - } - if (bytes > K * 2) - { - return (bytes / (double)K).ToString("F1") + "KiB"; - } - return bytes.ToString() + "B"; - } - - /// - /// Return scaled bandwidth - /// - /// Raw bandwidth - /// - /// The BandwidthScaleInfo struct - /// - public static BandwidthScaleInfo GetBandwidthScale(long n) - { - long scale = 1; - float f = n; - string unit = "B"; - if (f > 1024) - { - f = f / 1024; - scale <<= 10; - unit = "KiB"; - } - if (f > 1024) - { - f = f / 1024; - scale <<= 10; - unit = "MiB"; - } - if (f > 1024) - { - f = f / 1024; - scale <<= 10; - unit = "GiB"; - } - if (f > 1024) - { - f = f / 1024; - scale <<= 10; - unit = "TiB"; - } - return new BandwidthScaleInfo(f, unit, scale); - } - - public static RegistryKey OpenRegKey(string name, bool writable, RegistryHive hive = RegistryHive.CurrentUser) - { - // we are building x86 binary for both x86 and x64, which will - // cause problem when opening registry key - // detect operating system instead of CPU - if (string.IsNullOrEmpty(name)) throw new ArgumentException(nameof(name)); - try - { - RegistryKey userKey = RegistryKey.OpenBaseKey(hive, - Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32) - .OpenSubKey(name, writable); - return userKey; - } - catch (ArgumentException ae) - { - MessageBox.Show("OpenRegKey: " + ae.ToString()); - return null; - } - catch (Exception e) - { - logger.LogUsefulException(e); - return null; - } - } - - public static bool IsWinVistaOrHigher() - { - return Environment.OSVersion.Version.Major > 5; - } - - public static string ScanQRCodeFromScreen() - { - foreach (Screen screen in Screen.AllScreens) - { - using (Bitmap fullImage = new Bitmap(screen.Bounds.Width, - screen.Bounds.Height)) - { - using (Graphics g = Graphics.FromImage(fullImage)) - { - g.CopyFromScreen(screen.Bounds.X, - screen.Bounds.Y, - 0, 0, - fullImage.Size, - CopyPixelOperation.SourceCopy); - } - int maxTry = 10; - for (int i = 0; i < maxTry; i++) - { - int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry); - int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry); - Rectangle cropRect = new Rectangle(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2); - Bitmap target = new Bitmap(screen.Bounds.Width, screen.Bounds.Height); - - double imageScale = (double)screen.Bounds.Width / (double)cropRect.Width; - using (Graphics g = Graphics.FromImage(target)) - { - g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height), - cropRect, - GraphicsUnit.Pixel); - } - var source = new BitmapLuminanceSource(target); - var bitmap = new BinaryBitmap(new HybridBinarizer(source)); - QRCodeReader reader = new QRCodeReader(); - var result = reader.decode(bitmap); - if (result != null) - return result.Text; - } - } - } - return null; - } - - public static void OpenInBrowser(string url) - { - try - { - Process.Start(url); - } - catch - { - // hack because of this: https://github.com/dotnet/corefx/issues/10361 - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Process.Start(new ProcessStartInfo(url) - { - UseShellExecute = true, - Verb = "open" - }); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - Process.Start("xdg-open", url); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - Process.Start("open", url); - } - else - { - throw; - } - } - } - } -} diff --git a/shadowsocks-csharp/Util/ViewUtils.cs b/shadowsocks-csharp/Util/ViewUtils.cs deleted file mode 100644 index 84fc6dca..00000000 --- a/shadowsocks-csharp/Util/ViewUtils.cs +++ /dev/null @@ -1,137 +0,0 @@ -using Shadowsocks.Controller; -using Shadowsocks.View; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.Linq; -using System.Reflection; -using System.Windows.Forms; - -namespace Shadowsocks.Util -{ - public static class ViewUtils - { - public static IEnumerable GetChildControls(this Control control) where TControl : Control - { - if (control.Controls.Count == 0) - { - return Enumerable.Empty(); - } - var children = control.Controls.OfType().ToList(); - return children.SelectMany(GetChildControls).Concat(children); - } - - public static IEnumerable GetToolStripMenuItems(MenuStrip m) - { - if (m?.Items == null || m.Items.Count == 0) return Enumerable.Empty(); - var children = new List(); - foreach (var item in m.Items) - { - children.Add((ToolStripMenuItem)item); - } - return children.SelectMany(GetToolStripMenuItems).Concat(children); - } - public static IEnumerable GetToolStripMenuItems(ToolStripMenuItem m) - { - if (m?.DropDownItems == null || m.DropDownItems.Count == 0) return Enumerable.Empty(); - var children = new List(); - foreach (var item in m.DropDownItems) - { - children.Add((ToolStripMenuItem)item); - } - return children.SelectMany(GetToolStripMenuItems).Concat(children); - } - - // Workaround NotifyIcon's 63 chars limit - // https://stackoverflow.com/questions/579665/how-can-i-show-a-systray-tooltip-longer-than-63-chars - public static void SetNotifyIconText(NotifyIcon ni, string text) - { - if (text.Length >= 128) - throw new ArgumentOutOfRangeException("Text limited to 127 characters"); - Type t = typeof(NotifyIcon); - BindingFlags hidden = BindingFlags.NonPublic | BindingFlags.Instance; - t.GetField("text", hidden).SetValue(ni, text); - if ((bool)t.GetField("added", hidden).GetValue(ni)) - t.GetMethod("UpdateIcon", hidden).Invoke(ni, new object[] { true }); - } - - public static Bitmap AddBitmapOverlay(Bitmap original, params Bitmap[] overlays) - { - Bitmap bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format64bppArgb); - Graphics canvas = Graphics.FromImage(bitmap); - canvas.DrawImage(original, new Point(0, 0)); - foreach (Bitmap overlay in overlays) - { - canvas.DrawImage(new Bitmap(overlay, original.Size), new Point(0, 0)); - } - canvas.Save(); - return bitmap; - } - - public static Bitmap ChangeBitmapColor(Bitmap original, Color colorMask) - { - Bitmap newBitmap = new Bitmap(original); - - for (int x = 0; x < newBitmap.Width; x++) - { - for (int y = 0; y < newBitmap.Height; y++) - { - Color color = original.GetPixel(x, y); - if (color.A != 0) - { - int red = color.R * colorMask.R / 255; - int green = color.G * colorMask.G / 255; - int blue = color.B * colorMask.B / 255; - int alpha = color.A * colorMask.A / 255; - newBitmap.SetPixel(x, y, Color.FromArgb(alpha, red, green, blue)); - } - else - { - newBitmap.SetPixel(x, y, color); - } - } - } - return newBitmap; - } - - public static Bitmap ResizeBitmap(Bitmap original, int width, int height) - { - Bitmap newBitmap = new Bitmap(width, height); - using (Graphics g = Graphics.FromImage(newBitmap)) - { - g.SmoothingMode = SmoothingMode.HighQuality; - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - g.DrawImage(original, new Rectangle(0, 0, width, height)); - } - return newBitmap; - } - - public static int GetScreenDpi() - { - Graphics graphics = Graphics.FromHwnd(IntPtr.Zero); - int dpi = (int)graphics.DpiX; - graphics.Dispose(); - return dpi; - } - - public static string InputBox(string Prompt, string Title = "", string DefaultResponse = "", int XPos = -1, int YPos = -1) - { - var result = DefaultResponse; - var box = new InputBox() - { - Prompt = Prompt, - Text = Title ?? "Input", - Response = DefaultResponse - }; - if (box.ShowDialog()== DialogResult.OK) - { - result = box.Response; - } - return result; - } - } -} diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index b9716dcd..f5f7f62e 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -20,22 +20,7 @@ - - - - - - - - - - - - - - - @@ -71,21 +56,11 @@ - - + + + + - - - - - - - - - - - - diff --git a/shadowsocks-windows.sln b/shadowsocks-windows.sln index 5f4391ec..577a7b65 100644 --- a/shadowsocks-windows.sln +++ b/shadowsocks-windows.sln @@ -15,20 +15,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig .gitignore = .gitignore appveyor.yml = appveyor.yml - appveyor.yml.obsolete = appveyor.yml.obsolete - appveyor.yml.sample = appveyor.yml.sample CHANGES = CHANGES - CHANGES-NETCORE.txt = CHANGES-NETCORE.txt CONTRIBUTING.md = CONTRIBUTING.md LICENSE.txt = LICENSE.txt README.md = README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.Common", "Shadowsocks.Common\Shadowsocks.Common.csproj", "{61AF0616-8E90-46C4-908A-E182A5001AB5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks", "Shadowsocks\Shadowsocks.csproj", "{DFE11C77-62FA-4011-8398-38626C02E382}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.Crypto", "Shadowsocks.Crypto\Shadowsocks.Crypto.csproj", "{AD8974F4-EFDC-4EC5-A147-54CD4934D295}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.Net", "Shadowsocks.Net\Shadowsocks.Net.csproj", "{F60CD6D5-4B1C-4293-829E-9C10D21AE8A3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks", "Shadowsocks\Shadowsocks.csproj", "{85C2FC93-91F0-454F-AF0A-522FCD20827A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.PAC", "Shadowsocks.PAC\Shadowsocks.PAC.csproj", "{AE81B416-FBC4-4F88-9EFC-D07D8789355F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.Protobuf", "Shadowsocks.Protobuf\Shadowsocks.Protobuf.csproj", "{99142A50-E046-4F18-9C52-9855ABADA9B3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shadowsocks.WPF", "Shadowsocks.WPF\Shadowsocks.WPF.csproj", "{EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}" EndProject @@ -39,25 +38,25 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8C02D2F7-7CDB-4D55-9F25-CD03EF4AA062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8C02D2F7-7CDB-4D55-9F25-CD03EF4AA062}.Debug|Any CPU.Build.0 = Debug|Any CPU {8C02D2F7-7CDB-4D55-9F25-CD03EF4AA062}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8C02D2F7-7CDB-4D55-9F25-CD03EF4AA062}.Release|Any CPU.Build.0 = Release|Any CPU {45913187-0685-4903-B250-DCEF0479CD86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45913187-0685-4903-B250-DCEF0479CD86}.Debug|Any CPU.Build.0 = Debug|Any CPU {45913187-0685-4903-B250-DCEF0479CD86}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45913187-0685-4903-B250-DCEF0479CD86}.Release|Any CPU.Build.0 = Release|Any CPU - {61AF0616-8E90-46C4-908A-E182A5001AB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {61AF0616-8E90-46C4-908A-E182A5001AB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {61AF0616-8E90-46C4-908A-E182A5001AB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {61AF0616-8E90-46C4-908A-E182A5001AB5}.Release|Any CPU.Build.0 = Release|Any CPU - {AD8974F4-EFDC-4EC5-A147-54CD4934D295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD8974F4-EFDC-4EC5-A147-54CD4934D295}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD8974F4-EFDC-4EC5-A147-54CD4934D295}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD8974F4-EFDC-4EC5-A147-54CD4934D295}.Release|Any CPU.Build.0 = Release|Any CPU - {85C2FC93-91F0-454F-AF0A-522FCD20827A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85C2FC93-91F0-454F-AF0A-522FCD20827A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85C2FC93-91F0-454F-AF0A-522FCD20827A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85C2FC93-91F0-454F-AF0A-522FCD20827A}.Release|Any CPU.Build.0 = Release|Any CPU + {DFE11C77-62FA-4011-8398-38626C02E382}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFE11C77-62FA-4011-8398-38626C02E382}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFE11C77-62FA-4011-8398-38626C02E382}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFE11C77-62FA-4011-8398-38626C02E382}.Release|Any CPU.Build.0 = Release|Any CPU + {F60CD6D5-4B1C-4293-829E-9C10D21AE8A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F60CD6D5-4B1C-4293-829E-9C10D21AE8A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F60CD6D5-4B1C-4293-829E-9C10D21AE8A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F60CD6D5-4B1C-4293-829E-9C10D21AE8A3}.Release|Any CPU.Build.0 = Release|Any CPU + {AE81B416-FBC4-4F88-9EFC-D07D8789355F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE81B416-FBC4-4F88-9EFC-D07D8789355F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE81B416-FBC4-4F88-9EFC-D07D8789355F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE81B416-FBC4-4F88-9EFC-D07D8789355F}.Release|Any CPU.Build.0 = Release|Any CPU + {99142A50-E046-4F18-9C52-9855ABADA9B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99142A50-E046-4F18-9C52-9855ABADA9B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99142A50-E046-4F18-9C52-9855ABADA9B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99142A50-E046-4F18-9C52-9855ABADA9B3}.Release|Any CPU.Build.0 = Release|Any CPU {EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}.Debug|Any CPU.Build.0 = Debug|Any CPU {EA1FB2D4-B5A7-47A6-B097-2F4D29E23010}.Release|Any CPU.ActiveCfg = Release|Any CPU