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 にパディング数を文字として追記した文字列です。
- /// 例えば、0x00 は AA2 になります。
- ///
- 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