@@ -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) | public static void LogUsefulException(Exception e) | ||||
{ | { | ||||
// just log useful exceptions, not all of them | // 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); | Server server = _controller.GetCurrentStrategy().GetAServer(IStrategyCallerType.TCP, (IPEndPoint)socket.RemoteEndPoint); | ||||
handler.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | handler.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | ||||
handler.server = server; | handler.server = server; | ||||
handler.controller = _controller; | |||||
handler.Start(firstPacket, length); | handler.Start(firstPacket, length); | ||||
return true; | return true; | ||||
@@ -48,6 +49,7 @@ namespace Shadowsocks.Controller | |||||
// Client socket. | // Client socket. | ||||
public Socket remote; | public Socket remote; | ||||
public Socket connection; | public Socket connection; | ||||
public ShadowsocksController controller; | |||||
private byte command; | private byte command; | ||||
private byte[] _firstPacket; | private byte[] _firstPacket; | ||||
@@ -55,6 +57,10 @@ namespace Shadowsocks.Controller | |||||
// Size of receive buffer. | // Size of receive buffer. | ||||
public const int RecvSize = 16384; | public const int RecvSize = 16384; | ||||
public const int BufferSize = RecvSize + 32; | public const int BufferSize = RecvSize + 32; | ||||
private int totalRead = 0; | |||||
private int totalWrite = 0; | |||||
// remote receive buffer | // remote receive buffer | ||||
private byte[] remoteRecvBuffer = new byte[RecvSize]; | private byte[] remoteRecvBuffer = new byte[RecvSize]; | ||||
// remote send buffer | // remote send buffer | ||||
@@ -72,6 +78,8 @@ namespace Shadowsocks.Controller | |||||
private object encryptionLock = new object(); | private object encryptionLock = new object(); | ||||
private object decryptionLock = new object(); | private object decryptionLock = new object(); | ||||
private DateTime _startConnectTime; | |||||
public void Start(byte[] firstPacket, int length) | public void Start(byte[] firstPacket, int length) | ||||
{ | { | ||||
this._firstPacket = firstPacket; | this._firstPacket = firstPacket; | ||||
@@ -305,6 +313,8 @@ namespace Shadowsocks.Controller | |||||
SocketType.Stream, ProtocolType.Tcp); | SocketType.Stream, ProtocolType.Tcp); | ||||
remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | ||||
_startConnectTime = DateTime.Now; | |||||
// Connect to the remote endpoint. | // Connect to the remote endpoint. | ||||
remote.BeginConnect(remoteEP, | remote.BeginConnect(remoteEP, | ||||
new AsyncCallback(ConnectCallback), null); | new AsyncCallback(ConnectCallback), null); | ||||
@@ -330,10 +340,14 @@ namespace Shadowsocks.Controller | |||||
//Console.WriteLine("Socket connected to {0}", | //Console.WriteLine("Socket connected to {0}", | ||||
// remote.RemoteEndPoint.ToString()); | // remote.RemoteEndPoint.ToString()); | ||||
var latency = DateTime.Now - _startConnectTime; | |||||
controller.GetCurrentStrategy().UpdateLatency(this.server, latency); | |||||
StartPipe(); | StartPipe(); | ||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
controller.GetCurrentStrategy().SetFailure(this.server); | |||||
Logging.LogUsefulException(e); | Logging.LogUsefulException(e); | ||||
this.Close(); | this.Close(); | ||||
} | } | ||||
@@ -368,6 +382,7 @@ namespace Shadowsocks.Controller | |||||
try | try | ||||
{ | { | ||||
int bytesRead = remote.EndReceive(ar); | int bytesRead = remote.EndReceive(ar); | ||||
totalRead += bytesRead; | |||||
if (bytesRead > 0) | if (bytesRead > 0) | ||||
{ | { | ||||
@@ -381,6 +396,8 @@ namespace Shadowsocks.Controller | |||||
encryptor.Decrypt(remoteRecvBuffer, bytesRead, remoteSendBuffer, out bytesToSend); | encryptor.Decrypt(remoteRecvBuffer, bytesRead, remoteSendBuffer, out bytesToSend); | ||||
} | } | ||||
connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null); | connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null); | ||||
controller.GetCurrentStrategy().UpdateLastRead(this.server); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -388,6 +405,12 @@ namespace Shadowsocks.Controller | |||||
connection.Shutdown(SocketShutdown.Send); | connection.Shutdown(SocketShutdown.Send); | ||||
connectionShutdown = true; | connectionShutdown = true; | ||||
CheckClose(); | CheckClose(); | ||||
if (totalRead == 0) | |||||
{ | |||||
// closed before anything received, reports as failure | |||||
controller.GetCurrentStrategy().SetFailure(this.server); | |||||
} | |||||
} | } | ||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
@@ -406,6 +429,7 @@ namespace Shadowsocks.Controller | |||||
try | try | ||||
{ | { | ||||
int bytesRead = connection.EndReceive(ar); | int bytesRead = connection.EndReceive(ar); | ||||
totalWrite += bytesRead; | |||||
if (bytesRead > 0) | if (bytesRead > 0) | ||||
{ | { | ||||
@@ -419,6 +443,8 @@ namespace Shadowsocks.Controller | |||||
encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); | encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); | ||||
} | } | ||||
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); | remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); | ||||
controller.GetCurrentStrategy().UpdateLastWrite(this.server); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -282,6 +282,11 @@ namespace Shadowsocks.Controller | |||||
polipoRunner.Stop(); | polipoRunner.Stop(); | ||||
try | try | ||||
{ | { | ||||
foreach (var strategy in GetStrategies()) | |||||
{ | |||||
strategy.ReloadServers(); | |||||
} | |||||
polipoRunner.Start(_config); | polipoRunner.Start(_config); | ||||
TCPRelay tcpRelay = new TCPRelay(this); | TCPRelay tcpRelay = new TCPRelay(this); | ||||
@@ -20,20 +20,19 @@ namespace Shadowsocks.Controller.Strategy | |||||
public string Name | public string Name | ||||
{ | { | ||||
get | |||||
{ | |||||
return "Load Balance"; | |||||
} | |||||
get { return I18N.GetString("Load Balance"); } | |||||
} | } | ||||
public string ID | 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) | public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint) | ||||
{ | { | ||||
var configs = _controller.GetCurrentConfiguration().configs; | var configs = _controller.GetCurrentConfiguration().configs; | ||||
@@ -49,7 +48,7 @@ namespace Shadowsocks.Controller.Strategy | |||||
return configs[index % configs.Count]; | return configs[index % configs.Count]; | ||||
} | } | ||||
public void UpdateLatency(Model.Server server) | |||||
public void UpdateLatency(Model.Server server, TimeSpan latency) | |||||
{ | { | ||||
// do nothing | // 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 | UDP | ||||
} | } | ||||
/* | |||||
* IStrategy | |||||
* | |||||
* Subclasses must be thread-safe | |||||
*/ | |||||
public interface IStrategy | public interface IStrategy | ||||
{ | { | ||||
string Name { get; } | string Name { get; } | ||||
string ID { 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); | 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); | void UpdateLastRead(Server server); | ||||
/* | |||||
* TCPRelay will call this when writing to a server | |||||
*/ | |||||
void UpdateLastWrite(Server server); | void UpdateLastWrite(Server server); | ||||
/* | |||||
* TCPRelay will call this when fatal failure detected | |||||
*/ | |||||
void SetFailure(Server server); | void SetFailure(Server server); | ||||
} | } | ||||
} | } |
@@ -12,6 +12,8 @@ namespace Shadowsocks.Controller.Strategy | |||||
{ | { | ||||
_strategies = new List<IStrategy>(); | _strategies = new List<IStrategy>(); | ||||
_strategies.Add(new BalancingStrategy(controller)); | _strategies.Add(new BalancingStrategy(controller)); | ||||
_strategies.Add(new HighAvailabilityStrategy(controller)); | |||||
// TODO: load DLL plugins | |||||
} | } | ||||
public IList<IStrategy> GetStrategies() | public IList<IStrategy> GetStrategies() | ||||
{ | { | ||||
@@ -24,6 +24,7 @@ About...=关于... | |||||
Quit=退出 | Quit=退出 | ||||
Edit Servers=编辑服务器 | Edit Servers=编辑服务器 | ||||
Load Balance=负载均衡 | Load Balance=负载均衡 | ||||
High Availability=高可用 | |||||
# Config Form | # Config Form | ||||
@@ -18,6 +18,17 @@ namespace Shadowsocks.Model | |||||
public string method; | public string method; | ||||
public string remarks; | 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() | public string FriendlyName() | ||||
{ | { | ||||
if (string.IsNullOrEmpty(server)) | if (string.IsNullOrEmpty(server)) | ||||
@@ -136,7 +136,11 @@ namespace Shadowsocks.View | |||||
_modifiedConfiguration = controller.GetConfigurationCopy(); | _modifiedConfiguration = controller.GetConfigurationCopy(); | ||||
LoadConfiguration(_modifiedConfiguration); | LoadConfiguration(_modifiedConfiguration); | ||||
_oldSelectedIndex = _modifiedConfiguration.index; | _oldSelectedIndex = _modifiedConfiguration.index; | ||||
ServersListBox.SelectedIndex = _modifiedConfiguration.index; | |||||
if (_oldSelectedIndex < 0) | |||||
{ | |||||
_oldSelectedIndex = 0; | |||||
} | |||||
ServersListBox.SelectedIndex = _oldSelectedIndex; | |||||
LoadSelectedServer(); | LoadSelectedServer(); | ||||
} | } | ||||
@@ -127,7 +127,7 @@ namespace Shadowsocks.View | |||||
string serverInfo = null; | string serverInfo = null; | ||||
if (config.strategy != null) | if (config.strategy != null) | ||||
{ | { | ||||
serverInfo = I18N.GetString(controller.GetCurrentStrategy().Name); | |||||
serverInfo = controller.GetCurrentStrategy().Name; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -275,17 +275,18 @@ namespace Shadowsocks.View | |||||
int i = 0; | int i = 0; | ||||
foreach (var strategy in controller.GetStrategies()) | 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.Tag = strategy.ID; | ||||
item.Click += AStrategyItem_Click; | item.Click += AStrategyItem_Click; | ||||
items.Add(i, item); | items.Add(i, item); | ||||
i++; | i++; | ||||
} | } | ||||
int strategyCount = i; | |||||
Configuration configuration = controller.GetConfigurationCopy(); | Configuration configuration = controller.GetConfigurationCopy(); | ||||
foreach (var server in configuration.configs) | foreach (var server in configuration.configs) | ||||
{ | { | ||||
MenuItem item = new MenuItem(server.FriendlyName()); | MenuItem item = new MenuItem(server.FriendlyName()); | ||||
item.Tag = i; | |||||
item.Tag = i - strategyCount; | |||||
item.Click += AServerItem_Click; | item.Click += AServerItem_Click; | ||||
items.Add(i, item); | items.Add(i, item); | ||||
i++; | i++; | ||||
@@ -124,6 +124,7 @@ | |||||
<Compile Include="3rd\zxing\ResultPoint.cs" /> | <Compile Include="3rd\zxing\ResultPoint.cs" /> | ||||
<Compile Include="3rd\zxing\ResultPointCallback.cs" /> | <Compile Include="3rd\zxing\ResultPointCallback.cs" /> | ||||
<Compile Include="3rd\zxing\WriterException.cs" /> | <Compile Include="3rd\zxing\WriterException.cs" /> | ||||
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> | |||||
<Compile Include="Controller\System\AutoStartup.cs" /> | <Compile Include="Controller\System\AutoStartup.cs" /> | ||||
<Compile Include="Controller\FileManager.cs" /> | <Compile Include="Controller\FileManager.cs" /> | ||||
<Compile Include="Controller\Service\GFWListUpdater.cs" /> | <Compile Include="Controller\Service\GFWListUpdater.cs" /> | ||||