@@ -31,6 +31,14 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
public static void Debug(object o) | |||
{ | |||
#if DEBUG | |||
Console.WriteLine(o); | |||
#endif | |||
} | |||
public static void LogUsefulException(Exception e) | |||
{ | |||
// just log useful exceptions, not all of them | |||
@@ -34,6 +34,7 @@ namespace Shadowsocks.Controller | |||
Server server = _controller.GetCurrentStrategy().GetAServer(IStrategyCallerType.TCP, (IPEndPoint)socket.RemoteEndPoint); | |||
handler.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||
handler.server = server; | |||
handler.controller = _controller; | |||
handler.Start(firstPacket, length); | |||
return true; | |||
@@ -48,6 +49,7 @@ namespace Shadowsocks.Controller | |||
// Client socket. | |||
public Socket remote; | |||
public Socket connection; | |||
public ShadowsocksController controller; | |||
private byte command; | |||
private byte[] _firstPacket; | |||
@@ -55,6 +57,10 @@ namespace Shadowsocks.Controller | |||
// Size of receive buffer. | |||
public const int RecvSize = 16384; | |||
public const int BufferSize = RecvSize + 32; | |||
private int totalRead = 0; | |||
private int totalWrite = 0; | |||
// remote receive buffer | |||
private byte[] remoteRecvBuffer = new byte[RecvSize]; | |||
// remote send buffer | |||
@@ -72,6 +78,8 @@ namespace Shadowsocks.Controller | |||
private object encryptionLock = new object(); | |||
private object decryptionLock = new object(); | |||
private DateTime _startConnectTime; | |||
public void Start(byte[] firstPacket, int length) | |||
{ | |||
this._firstPacket = firstPacket; | |||
@@ -305,6 +313,8 @@ namespace Shadowsocks.Controller | |||
SocketType.Stream, ProtocolType.Tcp); | |||
remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
_startConnectTime = DateTime.Now; | |||
// Connect to the remote endpoint. | |||
remote.BeginConnect(remoteEP, | |||
new AsyncCallback(ConnectCallback), null); | |||
@@ -330,10 +340,14 @@ namespace Shadowsocks.Controller | |||
//Console.WriteLine("Socket connected to {0}", | |||
// remote.RemoteEndPoint.ToString()); | |||
var latency = DateTime.Now - _startConnectTime; | |||
controller.GetCurrentStrategy().UpdateLatency(this.server, latency); | |||
StartPipe(); | |||
} | |||
catch (Exception e) | |||
{ | |||
controller.GetCurrentStrategy().SetFailure(this.server); | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
} | |||
@@ -368,6 +382,7 @@ namespace Shadowsocks.Controller | |||
try | |||
{ | |||
int bytesRead = remote.EndReceive(ar); | |||
totalRead += bytesRead; | |||
if (bytesRead > 0) | |||
{ | |||
@@ -381,6 +396,8 @@ namespace Shadowsocks.Controller | |||
encryptor.Decrypt(remoteRecvBuffer, bytesRead, remoteSendBuffer, out bytesToSend); | |||
} | |||
connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null); | |||
controller.GetCurrentStrategy().UpdateLastRead(this.server); | |||
} | |||
else | |||
{ | |||
@@ -388,6 +405,12 @@ namespace Shadowsocks.Controller | |||
connection.Shutdown(SocketShutdown.Send); | |||
connectionShutdown = true; | |||
CheckClose(); | |||
if (totalRead == 0) | |||
{ | |||
// closed before anything received, reports as failure | |||
controller.GetCurrentStrategy().SetFailure(this.server); | |||
} | |||
} | |||
} | |||
catch (Exception e) | |||
@@ -406,6 +429,7 @@ namespace Shadowsocks.Controller | |||
try | |||
{ | |||
int bytesRead = connection.EndReceive(ar); | |||
totalWrite += bytesRead; | |||
if (bytesRead > 0) | |||
{ | |||
@@ -419,6 +443,8 @@ namespace Shadowsocks.Controller | |||
encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); | |||
} | |||
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); | |||
controller.GetCurrentStrategy().UpdateLastWrite(this.server); | |||
} | |||
else | |||
{ | |||
@@ -282,6 +282,11 @@ namespace Shadowsocks.Controller | |||
polipoRunner.Stop(); | |||
try | |||
{ | |||
foreach (var strategy in GetStrategies()) | |||
{ | |||
strategy.ReloadServers(); | |||
} | |||
polipoRunner.Start(_config); | |||
TCPRelay tcpRelay = new TCPRelay(this); | |||
@@ -20,20 +20,19 @@ namespace Shadowsocks.Controller.Strategy | |||
public string Name | |||
{ | |||
get | |||
{ | |||
return "Load Balance"; | |||
} | |||
get { return I18N.GetString("Load Balance"); } | |||
} | |||
public string ID | |||
{ | |||
get | |||
{ | |||
return "com.shadowsocks.strategy.balancing"; | |||
} | |||
get { return "com.shadowsocks.strategy.balancing"; } | |||
} | |||
public void ReloadServers() | |||
{ | |||
// do nothing | |||
} | |||
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint) | |||
{ | |||
var configs = _controller.GetCurrentConfiguration().configs; | |||
@@ -49,7 +48,7 @@ namespace Shadowsocks.Controller.Strategy | |||
return configs[index % configs.Count]; | |||
} | |||
public void UpdateLatency(Model.Server server) | |||
public void UpdateLatency(Model.Server server, TimeSpan latency) | |||
{ | |||
// do nothing | |||
} | |||
@@ -0,0 +1,126 @@ | |||
using Shadowsocks.Model; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace Shadowsocks.Controller.Strategy | |||
{ | |||
class HighAvailabilityStrategy : IStrategy | |||
{ | |||
protected Server _currentServer; | |||
protected Dictionary<Server, ServerStatus> _serverStatus; | |||
ShadowsocksController _controller; | |||
Random _random; | |||
public class ServerStatus | |||
{ | |||
// time interval between SYN and SYN+ACK | |||
public TimeSpan latency; | |||
// 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; | |||
} | |||
/** | |||
* if last failure is > 10 min | |||
* and (last write > last read) and (now - last read < 5s) // means not stuck | |||
* choose the lowest latency | |||
*/ | |||
public HighAvailabilityStrategy(ShadowsocksController controller) | |||
{ | |||
_controller = controller; | |||
_random = new Random(); | |||
_serverStatus = new Dictionary<Server, ServerStatus>(); | |||
} | |||
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<Server, ServerStatus>(_serverStatus); | |||
foreach (var server in _controller.GetCurrentConfiguration().configs) | |||
{ | |||
if (!newServerStatus.ContainsKey(server)) | |||
{ | |||
var status = new ServerStatus(); | |||
status.server = server; | |||
newServerStatus[server] = status; | |||
} | |||
} | |||
_serverStatus = newServerStatus; | |||
// just leave removed servers there | |||
// TODO | |||
_currentServer = _controller.GetCurrentConfiguration().configs[0]; | |||
} | |||
public Server GetAServer(IStrategyCallerType type, System.Net.IPEndPoint localIPEndPoint) | |||
{ | |||
return _currentServer; | |||
} | |||
public void UpdateLatency(Model.Server server, TimeSpan latency) | |||
{ | |||
Logging.Debug(String.Format("latency: {0} {1}", server.FriendlyName(), latency)); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
{ | |||
status.latency = latency; | |||
} | |||
} | |||
public void UpdateLastRead(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("last read: {0}", server.FriendlyName())); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
{ | |||
status.lastRead = DateTime.Now; | |||
} | |||
} | |||
public void UpdateLastWrite(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("last write: {0}", server.FriendlyName())); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
{ | |||
status.lastWrite = DateTime.Now; | |||
} | |||
} | |||
public void SetFailure(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("failure: {0}", server.FriendlyName())); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
{ | |||
status.lastFailure = DateTime.Now; | |||
} | |||
} | |||
} | |||
} |
@@ -12,19 +12,45 @@ namespace Shadowsocks.Controller.Strategy | |||
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); | |||
void UpdateLatency(Server server); | |||
/* | |||
* 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); | |||
} | |||
} |
@@ -12,6 +12,8 @@ namespace Shadowsocks.Controller.Strategy | |||
{ | |||
_strategies = new List<IStrategy>(); | |||
_strategies.Add(new BalancingStrategy(controller)); | |||
_strategies.Add(new HighAvailabilityStrategy(controller)); | |||
// TODO: load DLL plugins | |||
} | |||
public IList<IStrategy> GetStrategies() | |||
{ | |||
@@ -24,6 +24,7 @@ About...=关于... | |||
Quit=退出 | |||
Edit Servers=编辑服务器 | |||
Load Balance=负载均衡 | |||
High Availability=高可用 | |||
# Config Form | |||
@@ -18,6 +18,17 @@ namespace Shadowsocks.Model | |||
public string method; | |||
public string remarks; | |||
public override int GetHashCode() | |||
{ | |||
return server.GetHashCode() ^ server_port; | |||
} | |||
public override bool Equals(object obj) | |||
{ | |||
Server o2 = (Server)obj; | |||
return this.server == o2.server && this.server_port == o2.server_port; | |||
} | |||
public string FriendlyName() | |||
{ | |||
if (string.IsNullOrEmpty(server)) | |||
@@ -136,7 +136,11 @@ namespace Shadowsocks.View | |||
_modifiedConfiguration = controller.GetConfigurationCopy(); | |||
LoadConfiguration(_modifiedConfiguration); | |||
_oldSelectedIndex = _modifiedConfiguration.index; | |||
ServersListBox.SelectedIndex = _modifiedConfiguration.index; | |||
if (_oldSelectedIndex < 0) | |||
{ | |||
_oldSelectedIndex = 0; | |||
} | |||
ServersListBox.SelectedIndex = _oldSelectedIndex; | |||
LoadSelectedServer(); | |||
} | |||
@@ -127,7 +127,7 @@ namespace Shadowsocks.View | |||
string serverInfo = null; | |||
if (config.strategy != null) | |||
{ | |||
serverInfo = I18N.GetString(controller.GetCurrentStrategy().Name); | |||
serverInfo = controller.GetCurrentStrategy().Name; | |||
} | |||
else | |||
{ | |||
@@ -275,17 +275,18 @@ namespace Shadowsocks.View | |||
int i = 0; | |||
foreach (var strategy in controller.GetStrategies()) | |||
{ | |||
MenuItem item = new MenuItem(I18N.GetString(strategy.Name)); | |||
MenuItem item = new MenuItem(strategy.Name); | |||
item.Tag = strategy.ID; | |||
item.Click += AStrategyItem_Click; | |||
items.Add(i, item); | |||
i++; | |||
} | |||
int strategyCount = i; | |||
Configuration configuration = controller.GetConfigurationCopy(); | |||
foreach (var server in configuration.configs) | |||
{ | |||
MenuItem item = new MenuItem(server.FriendlyName()); | |||
item.Tag = i; | |||
item.Tag = i - strategyCount; | |||
item.Click += AServerItem_Click; | |||
items.Add(i, item); | |||
i++; | |||
@@ -124,6 +124,7 @@ | |||
<Compile Include="3rd\zxing\ResultPoint.cs" /> | |||
<Compile Include="3rd\zxing\ResultPointCallback.cs" /> | |||
<Compile Include="3rd\zxing\WriterException.cs" /> | |||
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> | |||
<Compile Include="Controller\System\AutoStartup.cs" /> | |||
<Compile Include="Controller\FileManager.cs" /> | |||
<Compile Include="Controller\Service\GFWListUpdater.cs" /> | |||