@@ -9,6 +9,7 @@ using Shadowsocks.Model; | |||||
namespace Shadowsocks.Controller | namespace Shadowsocks.Controller | ||||
{ | { | ||||
// TODO: Stream/Dgram Listener, so we needn't put TCP/UDP service together | |||||
public class Listener | public class Listener | ||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | private static Logger logger = LogManager.GetCurrentClassLogger(); | ||||
@@ -181,7 +181,6 @@ Connection: Close | |||||
"; | "; | ||||
byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent); | byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent); | ||||
socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket); | socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket); | ||||
Utils.ReleaseMemory(true); | |||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
@@ -25,6 +25,7 @@ namespace Shadowsocks.Controller | |||||
this._controller = controller; | this._controller = controller; | ||||
} | } | ||||
// TODO: UDP is datagram protocol not stream protocol | |||||
public override bool Handle(CachedNetworkStream stream, object state) | public override bool Handle(CachedNetworkStream stream, object state) | ||||
{ | { | ||||
byte[] fp = new byte[256]; | byte[] fp = new byte[256]; | ||||
@@ -95,7 +95,6 @@ namespace Shadowsocks.Controller | |||||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | ||||
_strategyManager = new StrategyManager(this); | _strategyManager = new StrategyManager(this); | ||||
_pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>(); | _pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>(); | ||||
StartReleasingMemory(); | |||||
StartTrafficStatistics(61); | StartTrafficStatistics(61); | ||||
ProgramUpdated += (o, e) => | ProgramUpdated += (o, e) => | ||||
@@ -568,7 +567,6 @@ namespace Shadowsocks.Controller | |||||
ConfigChanged?.Invoke(this, new EventArgs()); | ConfigChanged?.Invoke(this, new EventArgs()); | ||||
UpdateSystemProxy(); | UpdateSystemProxy(); | ||||
Utils.ReleaseMemory(true); | |||||
} | } | ||||
private void StartPlugin() | private void StartPlugin() | ||||
@@ -622,28 +620,6 @@ namespace Shadowsocks.Controller | |||||
Clipboard.SetDataObject(_pacServer.PacUrl); | Clipboard.SetDataObject(_pacServer.PacUrl); | ||||
} | } | ||||
#region Memory Management | |||||
private void StartReleasingMemory() | |||||
{ | |||||
_ramThread = new Thread(new ThreadStart(ReleaseMemory)) | |||||
{ | |||||
IsBackground = true | |||||
}; | |||||
_ramThread.Start(); | |||||
} | |||||
private void ReleaseMemory() | |||||
{ | |||||
while (true) | |||||
{ | |||||
Utils.ReleaseMemory(false); | |||||
Thread.Sleep(30 * 1000); | |||||
} | |||||
} | |||||
#endregion | |||||
#region Traffic Statistics | #region Traffic Statistics | ||||
private void StartTrafficStatistics(int queueMaxSize) | private void StartTrafficStatistics(int queueMaxSize) | ||||
@@ -29,9 +29,15 @@ namespace Shadowsocks.Encryption.AEAD | |||||
} | } | ||||
#endregion | #endregion | ||||
AesGcm aes; | |||||
public override void InitCipher(byte[] salt, bool isEncrypt) | |||||
{ | |||||
base.InitCipher(salt, isEncrypt); | |||||
aes = new AesGcm(sessionKey); | |||||
} | |||||
public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | ||||
{ | { | ||||
using AesGcm aes = new AesGcm(sessionKey); | |||||
aes.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen)); | aes.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen)); | ||||
return plain.Length + tagLen; | return plain.Length + tagLen; | ||||
} | } | ||||
@@ -39,7 +45,6 @@ namespace Shadowsocks.Encryption.AEAD | |||||
public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | ||||
{ | { | ||||
int clen = cipher.Length - tagLen; | int clen = cipher.Length - tagLen; | ||||
using AesGcm aes = new AesGcm(sessionKey); | |||||
ReadOnlySpan<byte> ciphertxt = cipher.Slice(0, clen); | ReadOnlySpan<byte> ciphertxt = cipher.Slice(0, clen); | ||||
ReadOnlySpan<byte> tag = cipher.Slice(clen); | ReadOnlySpan<byte> tag = cipher.Slice(clen); | ||||
aes.Decrypt(nonce, ciphertxt, tag, plain.Slice(0, clen)); | aes.Decrypt(nonce, ciphertxt, tag, plain.Slice(0, clen)); | ||||
@@ -43,14 +43,14 @@ namespace Shadowsocks.Encryption.AEAD | |||||
public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | ||||
{ | { | ||||
byte[] ct = enc!.Encrypt(plain, null, nonce); | |||||
byte[] ct = enc.Encrypt(plain, null, nonce); | |||||
ct.CopyTo(cipher); | ct.CopyTo(cipher); | ||||
return ct.Length; | return ct.Length; | ||||
} | } | ||||
public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | ||||
{ | { | ||||
byte[] pt = enc!.Decrypt(cipher, null, nonce); | |||||
byte[] pt = enc.Decrypt(cipher, null, nonce); | |||||
pt.CopyTo(plain); | pt.CopyTo(plain); | ||||
return pt.Length; | return pt.Length; | ||||
} | } | ||||
@@ -41,7 +41,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
{ | { | ||||
Span<byte> ob = new byte[o.Length + 128]; | Span<byte> ob = new byte[o.Length + 128]; | ||||
i.CopyTo(cfbBuf.AsSpan(ptr)); | i.CopyTo(cfbBuf.AsSpan(ptr)); | ||||
// TODO: standard CFB | |||||
// TODO: standard CFB, maybe with native aes | |||||
int total = i.Length + ptr; | int total = i.Length + ptr; | ||||
int blkSize = b.GetBlockSize(); | int blkSize = b.GetBlockSize(); | ||||
@@ -39,6 +39,12 @@ namespace Shadowsocks.Encryption.Stream | |||||
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] | ||||
private int CipherUpdate(ReadOnlySpan<byte> i, Span<byte> o, bool enc) | private int CipherUpdate(ReadOnlySpan<byte> i, Span<byte> o, bool enc) | ||||
{ | { | ||||
// about performance problem: | |||||
// as a part of hacking for streaming, we need manually increase IC | |||||
// so we need new Chacha20 | |||||
// and to get correct position, copy paste array everytime is required | |||||
// NaCl.Core has no int Encrypt(ReadOnlySpan<byte>,Span<byte>)... | |||||
int len = i.Length; | int len = i.Length; | ||||
int pad = remain; | int pad = remain; | ||||
i.CopyTo(chachaBuf.AsSpan(pad)); | i.CopyTo(chachaBuf.AsSpan(pad)); | ||||
@@ -46,7 +46,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
// don't know why we need third array, but it works... | // don't know why we need third array, but it works... | ||||
Span<byte> t = new byte[i.Length]; | Span<byte> t = new byte[i.Length]; | ||||
i.CopyTo(t); | i.CopyTo(t); | ||||
RC4(ctx, sbox, t, t.Length); | |||||
RC4(sbox, t, t.Length); | |||||
t.CopyTo(o); | t.CopyTo(o); | ||||
return t.Length; | return t.Length; | ||||
} | } | ||||
@@ -71,13 +71,9 @@ namespace Shadowsocks.Encryption.Stream | |||||
#endregion | #endregion | ||||
#region RC4 | #region RC4 | ||||
class Context | |||||
{ | |||||
public int index1 = 0; | |||||
public int index2 = 0; | |||||
} | |||||
private readonly Context ctx = new Context(); | |||||
int index1 = 0; | |||||
int index2 = 0; | |||||
private byte[] SBox(byte[] key) | private byte[] SBox(byte[] key) | ||||
{ | { | ||||
@@ -99,17 +95,17 @@ namespace Shadowsocks.Encryption.Stream | |||||
} | } | ||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)] | [MethodImpl(MethodImplOptions.AggressiveOptimization)] | ||||
private void RC4(Context ctx, Span<byte> s, Span<byte> data, int length) | |||||
private void RC4(Span<byte> s, Span<byte> data, int length) | |||||
{ | { | ||||
for (int n = 0; n < length; n++) | for (int n = 0; n < length; n++) | ||||
{ | { | ||||
byte b = data[n]; | byte b = data[n]; | ||||
ctx.index1 = (ctx.index1 + 1) & 255; | |||||
ctx.index2 = (ctx.index2 + s[ctx.index1]) & 255; | |||||
index1 = (index1 + 1) & 255; | |||||
index2 = (index2 + s[index1]) & 255; | |||||
Swap(s, ctx.index1, ctx.index2); | |||||
data[n] = (byte)(b ^ s[(s[ctx.index1] + s[ctx.index2]) & 255]); | |||||
Swap(s, index1, index2); | |||||
data[n] = (byte)(b ^ s[(s[index1] + s[index2]) & 255]); | |||||
} | } | ||||
} | } | ||||
@@ -46,23 +46,8 @@ namespace Shadowsocks | |||||
"Shadowsocks Error", MessageBoxButtons.OK, MessageBoxIcon.Error); | "Shadowsocks Error", MessageBoxButtons.OK, MessageBoxIcon.Error); | ||||
return; | return; | ||||
} | } | ||||
#if NET472 | |||||
// Check .NET Framework version | |||||
if (!Utils.IsSupportedRuntimeVersion()) | |||||
{ | |||||
if (DialogResult.OK == MessageBox.Show(I18N.GetString("Unsupported .NET Framework, please update to {0} or later.", "4.7.2"), | |||||
"Shadowsocks Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Error)) | |||||
{ | |||||
//Process.Start("https://www.microsoft.com/download/details.aspx?id=53344"); // 4.6.2 | |||||
Process.Start("https://dotnet.microsoft.com/download/dotnet-framework/net472"); | |||||
} | |||||
return; | |||||
} | |||||
if (Environment.OSVersion.Version.Major >= 6) SetProcessDPIAware(); | |||||
#else | |||||
Application.SetHighDpiMode(HighDpiMode.SystemAware); | Application.SetHighDpiMode(HighDpiMode.SystemAware); | ||||
#endif | |||||
Utils.ReleaseMemory(true); | |||||
using (Mutex mutex = new Mutex(false, $"Global\\Shadowsocks_{Application.StartupPath.GetHashCode()}")) | using (Mutex mutex = new Mutex(false, $"Global\\Shadowsocks_{Application.StartupPath.GetHashCode()}")) | ||||
{ | { | ||||
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); | Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); | ||||
@@ -95,61 +95,6 @@ namespace Shadowsocks.Util | |||||
return Path.Combine(GetTempPath(), filename); | return Path.Combine(GetTempPath(), filename); | ||||
} | } | ||||
public static void ReleaseMemory(bool removePages) | |||||
{ | |||||
// release any unused pages | |||||
// making the numbers look good in task manager | |||||
// this is totally nonsense in programming | |||||
// but good for those users who care | |||||
// making them happier with their everyday life | |||||
// which is part of user experience | |||||
GC.Collect(GC.MaxGeneration); | |||||
GC.WaitForPendingFinalizers(); | |||||
if (removePages) | |||||
{ | |||||
// as some users have pointed out | |||||
// removing pages from working set will cause some IO | |||||
// which lowered user experience for another group of users | |||||
// | |||||
// so we do 2 more things here to satisfy them: | |||||
// 1. only remove pages once when configuration is changed | |||||
// 2. add more comments here to tell users that calling | |||||
// this function will not be more frequent than | |||||
// IM apps writing chat logs, or web browsers writing cache files | |||||
// if they're so concerned about their disk, they should | |||||
// uninstall all IM apps and web browsers | |||||
// | |||||
// please open an issue if you're worried about anything else in your computer | |||||
// no matter it's GPU performance, monitor contrast, audio fidelity | |||||
// or anything else in the task manager | |||||
// we'll do as much as we can to help you | |||||
// | |||||
// just kidding | |||||
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, | |||||
(UIntPtr)0xFFFFFFFF, | |||||
(UIntPtr)0xFFFFFFFF); | |||||
} | |||||
} | |||||
public static string UnGzip(byte[] buf) | |||||
{ | |||||
byte[] buffer = new byte[1024]; | |||||
int n; | |||||
using (MemoryStream sb = new MemoryStream()) | |||||
{ | |||||
using (GZipStream input = new GZipStream(new MemoryStream(buf), | |||||
CompressionMode.Decompress, | |||||
false)) | |||||
{ | |||||
while ((n = input.Read(buffer, 0, buffer.Length)) > 0) | |||||
{ | |||||
sb.Write(buffer, 0, n); | |||||
} | |||||
} | |||||
return System.Text.Encoding.UTF8.GetString(sb.ToArray()); | |||||
} | |||||
} | |||||
public static string FormatBandwidth(long n) | public static string FormatBandwidth(long n) | ||||
{ | { | ||||
var result = GetBandwidthScale(n); | var result = GetBandwidthScale(n); | ||||
@@ -575,14 +575,12 @@ namespace Shadowsocks.View | |||||
{ | { | ||||
logForm.Dispose(); | logForm.Dispose(); | ||||
logForm = null; | logForm = null; | ||||
Utils.ReleaseMemory(true); | |||||
} | } | ||||
void configForm_FormClosed(object sender, FormClosedEventArgs e) | void configForm_FormClosed(object sender, FormClosedEventArgs e) | ||||
{ | { | ||||
configForm.Dispose(); | configForm.Dispose(); | ||||
configForm = null; | configForm = null; | ||||
Utils.ReleaseMemory(true); | |||||
if (_isFirstRun) | if (_isFirstRun) | ||||
{ | { | ||||
CheckUpdateForFirstRun(); | CheckUpdateForFirstRun(); | ||||
@@ -600,14 +598,12 @@ namespace Shadowsocks.View | |||||
{ | { | ||||
proxyForm.Dispose(); | proxyForm.Dispose(); | ||||
proxyForm = null; | proxyForm = null; | ||||
Utils.ReleaseMemory(true); | |||||
} | } | ||||
void hotkeySettingsForm_FormClosed(object sender, FormClosedEventArgs e) | void hotkeySettingsForm_FormClosed(object sender, FormClosedEventArgs e) | ||||
{ | { | ||||
hotkeySettingsForm.Dispose(); | hotkeySettingsForm.Dispose(); | ||||
hotkeySettingsForm = null; | hotkeySettingsForm = null; | ||||
Utils.ReleaseMemory(true); | |||||
} | } | ||||
private void Config_Click(object sender, EventArgs e) | private void Config_Click(object sender, EventArgs e) | ||||