Browse Source

add HA

tags/2.5
clowwindy 9 years ago
parent
commit
ddd46ed8c6
12 changed files with 225 additions and 15 deletions
  1. +8
    -0
      shadowsocks-csharp/Controller/Logging.cs
  2. +26
    -0
      shadowsocks-csharp/Controller/Service/TCPRelay.cs
  3. +5
    -0
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  4. +9
    -10
      shadowsocks-csharp/Controller/Strategy/BalancingStrategy.cs
  5. +126
    -0
      shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs
  6. +27
    -1
      shadowsocks-csharp/Controller/Strategy/IStrategy.cs
  7. +2
    -0
      shadowsocks-csharp/Controller/Strategy/StrategyManager.cs
  8. +1
    -0
      shadowsocks-csharp/Data/cn.txt
  9. +11
    -0
      shadowsocks-csharp/Model/Server.cs
  10. +5
    -1
      shadowsocks-csharp/View/ConfigForm.cs
  11. +4
    -3
      shadowsocks-csharp/View/MenuViewController.cs
  12. +1
    -0
      shadowsocks-csharp/shadowsocks-csharp.csproj

+ 8
- 0
shadowsocks-csharp/Controller/Logging.cs View File

@@ -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


+ 26
- 0
shadowsocks-csharp/Controller/Service/TCPRelay.cs View File

@@ -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
{ {


+ 5
- 0
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -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);


+ 9
- 10
shadowsocks-csharp/Controller/Strategy/BalancingStrategy.cs View File

@@ -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
} }


+ 126
- 0
shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs View File

@@ -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;
}
}
}
}

+ 27
- 1
shadowsocks-csharp/Controller/Strategy/IStrategy.cs View File

@@ -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);
} }
} }

+ 2
- 0
shadowsocks-csharp/Controller/Strategy/StrategyManager.cs View File

@@ -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()
{ {


+ 1
- 0
shadowsocks-csharp/Data/cn.txt View File

@@ -24,6 +24,7 @@ About...=关于...
Quit=退出 Quit=退出
Edit Servers=编辑服务器 Edit Servers=编辑服务器
Load Balance=负载均衡 Load Balance=负载均衡
High Availability=高可用


# Config Form # Config Form




+ 11
- 0
shadowsocks-csharp/Model/Server.cs View File

@@ -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))


+ 5
- 1
shadowsocks-csharp/View/ConfigForm.cs View File

@@ -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();
} }


+ 4
- 3
shadowsocks-csharp/View/MenuViewController.cs View File

@@ -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++;


+ 1
- 0
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -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" />


Loading…
Cancel
Save