Compare commits

..
18 Commits
Author SHA1 Message Date
clowwindy 60dc9e7d71 bump 2.5.1 2015-07-26 12:32:35 +08:00
clowwindy 02f75f899f update CHANGES 2015-07-26 12:32:17 +08:00
clowwindy eda92b80e1 refine logs 2015-07-26 12:27:34 +08:00
clowwindy ccec6be210 prevent jittering and fix server settings update in HA 2015-07-26 12:08:28 +08:00
clowwindy 01c059c4e3 fix server index problem 2015-07-26 11:37:09 +08:00
clowwindy a0e79f37bc fix #235 2015-07-26 10:44:44 +08:00
clowwindy a2cd2a5408 update CHANGES 2015-07-25 19:20:17 +08:00
clowwindy 9332741e18 update README 2015-07-25 19:19:20 +08:00
clowwindy 1a68544f7f bump 2015-07-25 19:06:03 +08:00
clowwindy dafe33f7ed fix a crash 2015-07-25 19:05:37 +08:00
clowwindy 008e3544f2 refine HA 2015-07-25 18:39:14 +08:00
clowwindy 449df1022d add connect timeout 2015-07-25 17:40:08 +08:00
clowwindy cb5fece8b3 HA almost works 2015-07-25 16:22:28 +08:00
clowwindy 8c869ca38e add retry 2015-07-25 14:16:29 +08:00
clowwindy ddd46ed8c6 add HA 2015-07-24 04:17:33 +08:00
clowwindy 4afd5faf3f add load balancing 2015-07-22 15:58:39 +08:00
clowwindy da41514839 fix zero IP 2015-07-22 14:32:32 +08:00
clowwindy f6cbf7b73a Update README.md 2015-07-11 19:58:43 +08:00
24 changed files with 695 additions and 66 deletions
+10
View File
@@ -1,3 +1,13 @@
2.5.1 2015-07-26
- Prevent HA from switching servers too frequently
- Fix a problem that server settings can not be updated when using HA
- Fix server port can't be 8123
- Other minor fixes
2.5 2015-07-25
- Support load balance
- Support high availability
2.4 2015-07-11
- Support UDP relay
- Support online PAC
+21 -11
View File
@@ -6,10 +6,11 @@ Shadowsocks for Windows
#### Features
1. System proxy configuration
2. Fast profile switching
3. PAC mode and global mode
4. GFWList and user rules
5. Supports HTTP proxy
2. PAC mode and global mode
3. GFWList and user rules
4. Supports HTTP proxy
5. Supports server auto switching
6. Supports UDP relay (see Usage)
#### Download
@@ -19,7 +20,7 @@ For >= Windows 8 or with .Net 4.0, download Shadowsocks-win-dotnet4.0-x.x.x.zip.
For <= Windows 7 or with .Net 2.0, download Shadowsocks-win-x.x.x.zip.
#### Usage
#### Basic
1. Find Shadowsocks icon in the notification tray
2. You can add multiple servers in servers menu
@@ -28,15 +29,24 @@ proxy addons in your browser, or set them to use system proxy
4. You can also configure your browser proxy manually if you don't want to enable
system proxy. Set Socks5 or HTTP proxy to 127.0.0.1:1080. You can change this
port in Server -> Edit Servers
5. You can change PAC rules by editing the PAC file. When you save the PAC file
#### PAC
1. You can change PAC rules by editing the PAC file. When you save the PAC file
with any editor, Shadowsocks will notify browsers about the change automatically
6. You can also update the PAC file from GFWList. Note your modifications to the PAC
file will be lost. However you can put your rules in the user rule file for GFWList.
Don't forget to update from GFWList again after you've edited the user rule
2. You can also update PAC file from GFWList (maintained by 3rd party)
3. You can also use online PAC URL
### Develop
#### Server Auto Switching
Visual Studio Express 2012 is recommended.
1. Load balance: choosing server randomly
2. High availability: choosing the best server (low latency and packet loss)
3. Write your own strategy by implement IStrategy interface and send us a pull request!
#### UDP
For UDP, you need to use SocksCap or ProxyCap to force programs you want
to proxy to tunnel over Shadowsocks
#### License
+11
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)
{
// just log useful exceptions, not all of them
@@ -55,6 +63,9 @@ namespace Shadowsocks.Controller
Console.WriteLine(e);
}
}
else if (e is ObjectDisposedException)
{
}
else
{
Console.WriteLine(e);
@@ -18,7 +18,7 @@ namespace Shadowsocks.Controller
public class UDPState
{
public byte[] buffer = new byte[4096];
public EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 1);
public EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
}
Configuration _config;
@@ -5,16 +5,18 @@ using System.Net.Sockets;
using System.Net;
using Shadowsocks.Encryption;
using Shadowsocks.Model;
using Shadowsocks.Controller.Strategy;
using System.Timers;
namespace Shadowsocks.Controller
{
class TCPRelay : Listener.Service
{
private Configuration _config;
public TCPRelay(Configuration config)
private ShadowsocksController _controller;
public TCPRelay(ShadowsocksController controller)
{
this._config = config;
this._controller = controller;
}
public bool Handle(byte[] firstPacket, int length, Socket socket, object state)
@@ -30,9 +32,7 @@ namespace Shadowsocks.Controller
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
Handler handler = new Handler();
handler.connection = socket;
Server server = _config.GetCurrentServer();
handler.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password);
handler.server = server;
handler.controller = _controller;
handler.Start(firstPacket, length);
return true;
@@ -47,6 +47,9 @@ namespace Shadowsocks.Controller
// Client socket.
public Socket remote;
public Socket connection;
public ShadowsocksController controller;
private int retryCount = 0;
private bool connected;
private byte command;
private byte[] _firstPacket;
@@ -54,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
@@ -71,6 +78,19 @@ namespace Shadowsocks.Controller
private object encryptionLock = new object();
private object decryptionLock = new object();
private DateTime _startConnectTime;
public void CreateRemote()
{
Server server = controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)connection.RemoteEndPoint);
if (server == null || server.server == "")
{
throw new ArgumentException("No server configured");
}
this.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password);
this.server = server;
}
public void Start(byte[] firstPacket, int length)
{
this._firstPacket = firstPacket;
@@ -115,7 +135,7 @@ namespace Shadowsocks.Controller
remote.Shutdown(SocketShutdown.Both);
remote.Close();
}
catch (SocketException e)
catch (Exception e)
{
Logging.LogUsefulException(e);
}
@@ -124,7 +144,10 @@ namespace Shadowsocks.Controller
{
lock (decryptionLock)
{
((IDisposable)encryptor).Dispose();
if (encryptor != null)
{
((IDisposable)encryptor).Dispose();
}
}
}
}
@@ -206,7 +229,7 @@ namespace Shadowsocks.Controller
if (command == 1)
{
byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(StartConnect), null);
connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(ResponseCallback), null);
}
else if (command == 3)
{
@@ -283,12 +306,37 @@ namespace Shadowsocks.Controller
}
}
private void StartConnect(IAsyncResult ar)
private void ResponseCallback(IAsyncResult ar)
{
try
{
connection.EndSend(ar);
StartConnect();
}
catch (Exception e)
{
Logging.LogUsefulException(e);
this.Close();
}
}
private class ServerTimer : Timer
{
public Server Server;
public ServerTimer(int p) :base(p)
{
}
}
private void StartConnect()
{
try
{
CreateRemote();
// TODO async resolving
IPAddress ipAddress;
bool parsed = IPAddress.TryParse(server.server, out ipAddress);
@@ -304,9 +352,17 @@ namespace Shadowsocks.Controller
SocketType.Stream, ProtocolType.Tcp);
remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
_startConnectTime = DateTime.Now;
ServerTimer connectTimer = new ServerTimer(3000);
connectTimer.AutoReset = false;
connectTimer.Elapsed += connectTimer_Elapsed;
connectTimer.Enabled = true;
connectTimer.Server = server;
connected = false;
// Connect to the remote endpoint.
remote.BeginConnect(remoteEP,
new AsyncCallback(ConnectCallback), null);
new AsyncCallback(ConnectCallback), connectTimer);
}
catch (Exception e)
{
@@ -315,26 +371,84 @@ namespace Shadowsocks.Controller
}
}
private void connectTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (connected)
{
return;
}
Server server = ((ServerTimer)sender).Server;
IStrategy strategy = controller.GetCurrentStrategy();
if (strategy != null)
{
strategy.SetFailure(server);
}
Console.WriteLine(String.Format("{0} timed out", server.FriendlyName()));
remote.Close();
RetryConnect();
}
private void RetryConnect()
{
if (retryCount < 4)
{
Logging.Debug("Connection failed, retrying");
StartConnect();
retryCount++;
}
else
{
this.Close();
}
}
private void ConnectCallback(IAsyncResult ar)
{
Server server = null;
if (closed)
{
return;
}
try
{
ServerTimer timer = (ServerTimer)ar.AsyncState;
server = timer.Server;
timer.Elapsed -= connectTimer_Elapsed;
timer.Enabled = false;
timer.Dispose();
// Complete the connection.
remote.EndConnect(ar);
connected = true;
//Console.WriteLine("Socket connected to {0}",
// remote.RemoteEndPoint.ToString());
var latency = DateTime.Now - _startConnectTime;
IStrategy strategy = controller.GetCurrentStrategy();
if (strategy != null)
{
strategy.UpdateLatency(server, latency);
}
StartPipe();
}
catch (ArgumentException e)
{
}
catch (Exception e)
{
if (server != null)
{
IStrategy strategy = controller.GetCurrentStrategy();
if (strategy != null)
{
strategy.SetFailure(server);
}
}
Logging.LogUsefulException(e);
this.Close();
RetryConnect();
}
}
@@ -367,6 +481,7 @@ namespace Shadowsocks.Controller
try
{
int bytesRead = remote.EndReceive(ar);
totalRead += bytesRead;
if (bytesRead > 0)
{
@@ -380,6 +495,12 @@ namespace Shadowsocks.Controller
encryptor.Decrypt(remoteRecvBuffer, bytesRead, remoteSendBuffer, out bytesToSend);
}
connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null);
IStrategy strategy = controller.GetCurrentStrategy();
if (strategy != null)
{
strategy.UpdateLastRead(this.server);
}
}
else
{
@@ -387,6 +508,13 @@ namespace Shadowsocks.Controller
connection.Shutdown(SocketShutdown.Send);
connectionShutdown = true;
CheckClose();
if (totalRead == 0)
{
// closed before anything received, reports as failure
// disable this feature
// controller.GetCurrentStrategy().SetFailure(this.server);
}
}
}
catch (Exception e)
@@ -405,6 +533,7 @@ namespace Shadowsocks.Controller
try
{
int bytesRead = connection.EndReceive(ar);
totalWrite += bytesRead;
if (bytesRead > 0)
{
@@ -418,6 +547,13 @@ namespace Shadowsocks.Controller
encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend);
}
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null);
IStrategy strategy = controller.GetCurrentStrategy();
if (strategy != null)
{
strategy.UpdateLastWrite(this.server);
}
}
else
{
@@ -6,16 +6,17 @@ using Shadowsocks.Model;
using System.Net.Sockets;
using System.Net;
using System.Runtime.CompilerServices;
using Shadowsocks.Controller.Strategy;
namespace Shadowsocks.Controller
{
class UDPRelay : Listener.Service
{
private Configuration _config;
private ShadowsocksController _controller;
private LRUCache<IPEndPoint, UDPHandler> _cache;
public UDPRelay(Configuration config)
public UDPRelay(ShadowsocksController controller)
{
this._config = config;
this._controller = controller;
this._cache = new LRUCache<IPEndPoint, UDPHandler>(512); // todo: choose a smart number
}
@@ -34,7 +35,7 @@ namespace Shadowsocks.Controller
UDPHandler handler = _cache.get(remoteEndPoint);
if (handler == null)
{
handler = new UDPHandler(socket, _config.GetCurrentServer(), remoteEndPoint);
handler = new UDPHandler(socket, _controller.GetAServer(IStrategyCallerType.UDP, remoteEndPoint), remoteEndPoint);
_cache.add(remoteEndPoint, handler);
}
handler.Send(firstPacket, length);
@@ -18,7 +18,7 @@ namespace Shadowsocks.Controller
public string LatestVersionURL;
public event EventHandler NewVersionFound;
public const string Version = "2.4";
public const string Version = "2.5.1";
public void CheckUpdate(Configuration config)
{
@@ -154,7 +154,7 @@ namespace Shadowsocks.Controller
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Logging.Debug(ex.ToString());
return;
}
}
@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using Shadowsocks.Controller.Strategy;
using System.Net;
namespace Shadowsocks.Controller
{
@@ -20,6 +22,7 @@ namespace Shadowsocks.Controller
private Listener _listener;
private PACServer _pacServer;
private Configuration _config;
private StrategyManager _strategyManager;
private PolipoRunner polipoRunner;
private GFWListUpdater gfwListUpdater;
private bool stopped = false;
@@ -49,6 +52,7 @@ namespace Shadowsocks.Controller
public ShadowsocksController()
{
_config = Configuration.Load();
_strategyManager = new StrategyManager(this);
}
public void Start()
@@ -70,11 +74,48 @@ namespace Shadowsocks.Controller
}
// always return copy
public Configuration GetConfiguration()
public Configuration GetConfigurationCopy()
{
return Configuration.Load();
}
// always return current instance
public Configuration GetCurrentConfiguration()
{
return _config;
}
public IList<IStrategy> GetStrategies()
{
return _strategyManager.GetStrategies();
}
public IStrategy GetCurrentStrategy()
{
foreach (var strategy in _strategyManager.GetStrategies())
{
if (strategy.ID == this._config.strategy)
{
return strategy;
}
}
return null;
}
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint)
{
IStrategy strategy = GetCurrentStrategy();
if (strategy != null)
{
return strategy.GetAServer(type, localIPEndPoint);
}
if (_config.index < 0)
{
_config.index = 0;
}
return GetCurrentServer();
}
public void SaveServers(List<Server> servers, int localPort)
{
_config.configs = servers;
@@ -134,6 +175,14 @@ namespace Shadowsocks.Controller
public void SelectServerIndex(int index)
{
_config.index = index;
_config.strategy = null;
SaveConfig(_config);
}
public void SelectStrategy(string strategyID)
{
_config.index = -1;
_config.strategy = strategyID;
SaveConfig(_config);
}
@@ -248,10 +297,16 @@ namespace Shadowsocks.Controller
polipoRunner.Stop();
try
{
var strategy = GetCurrentStrategy();
if (strategy != null)
{
strategy.ReloadServers();
}
polipoRunner.Start(_config);
TCPRelay tcpRelay = new TCPRelay(_config);
UDPRelay udpRelay = new UDPRelay(_config);
TCPRelay tcpRelay = new TCPRelay(this);
UDPRelay udpRelay = new UDPRelay(this);
List<Listener.Service> services = new List<Listener.Service>();
services.Add(tcpRelay);
services.Add(udpRelay);
@@ -0,0 +1,71 @@
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)
{
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
}
}
}
@@ -0,0 +1,185 @@
using Shadowsocks.Model;
using System;
using System.Collections.Generic;
using System.Text;
namespace Shadowsocks.Controller.Strategy
{
class HighAvailabilityStrategy : IStrategy
{
protected ServerStatus _currentServer;
protected Dictionary<Server, ServerStatus> _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<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;
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)
{
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<ServerStatus> servers = new List<ServerStatus>(_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));
Logging.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.FriendlyName(), 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;
Console.WriteLine("HA switching to server: {0}", _currentServer.server.FriendlyName());
}
}
}
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;
status.lastTimeDetectLatency = DateTime.Now;
}
}
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;
}
}
}
}
@@ -0,0 +1,56 @@
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);
/*
* 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);
}
}
@@ -0,0 +1,23 @@
using Shadowsocks.Controller;
using System;
using System.Collections.Generic;
using System.Text;
namespace Shadowsocks.Controller.Strategy
{
class StrategyManager
{
List<IStrategy> _strategies;
public StrategyManager(ShadowsocksController controller)
{
_strategies = new List<IStrategy>();
_strategies.Add(new BalancingStrategy(controller));
_strategies.Add(new HighAvailabilityStrategy(controller));
// TODO: load DLL plugins
}
public IList<IStrategy> GetStrategies()
{
return _strategies;
}
}
}
+2
View File
@@ -23,6 +23,8 @@ Show Logs...=显示日志...
About...=关于...
Quit=退出
Edit Servers=编辑服务器
Load Balance=负载均衡
High Availability=高可用
# Config Form
+24 -2
View File
@@ -11,6 +11,9 @@ namespace Shadowsocks.Model
public class Configuration
{
public List<Server> configs;
// when strategy is set, index is ignored
public string strategy;
public int index;
public bool global;
public bool enabled;
@@ -52,6 +55,13 @@ namespace Shadowsocks.Model
{
config.localPort = 1080;
}
if (config.index == -1)
{
if (config.strategy == null)
{
config.index = 0;
}
}
return config;
}
catch (Exception e)
@@ -79,9 +89,16 @@ namespace Shadowsocks.Model
{
config.index = config.configs.Count - 1;
}
if (config.index < 0)
if (config.index < -1)
{
config.index = 0;
config.index = -1;
}
if (config.index == -1)
{
if (config.strategy == null)
{
config.index = 0;
}
}
config.isDefault = false;
try
@@ -118,6 +135,11 @@ namespace Shadowsocks.Model
{
throw new ArgumentException(I18N.GetString("Port out of range"));
}
}
public static void CheckLocalPort(int port)
{
CheckPort(port);
if (port == 8123)
{
throw new ArgumentException(I18N.GetString("Port can't be 8123"));
+11
View File
@@ -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))
+7 -3
View File
@@ -84,7 +84,7 @@ namespace Shadowsocks.View
};
int localPort = int.Parse(ProxyPortTextBox.Text);
Configuration.CheckServer(server);
Configuration.CheckPort(localPort);
Configuration.CheckLocalPort(localPort);
_modifiedConfiguration.configs[_oldSelectedIndex] = server;
_modifiedConfiguration.localPort = localPort;
@@ -133,10 +133,14 @@ namespace Shadowsocks.View
private void LoadCurrentConfiguration()
{
_modifiedConfiguration = controller.GetConfiguration();
_modifiedConfiguration = controller.GetConfigurationCopy();
LoadConfiguration(_modifiedConfiguration);
_oldSelectedIndex = _modifiedConfiguration.index;
ServersListBox.SelectedIndex = _modifiedConfiguration.index;
if (_oldSelectedIndex < 0)
{
_oldSelectedIndex = 0;
}
ServersListBox.SelectedIndex = _oldSelectedIndex;
LoadSelectedServer();
}
+46 -18
View File
@@ -71,9 +71,9 @@ namespace Shadowsocks.View
LoadCurrentConfiguration();
updateChecker.CheckUpdate(controller.GetConfiguration());
updateChecker.CheckUpdate(controller.GetConfigurationCopy());
if (controller.GetConfiguration().isDefault)
if (controller.GetConfigurationCopy().isDefault)
{
_isFirstRun = true;
ShowConfigForm();
@@ -106,7 +106,7 @@ namespace Shadowsocks.View
{
icon = Resources.ss24;
}
Configuration config = controller.GetConfiguration();
Configuration config = controller.GetConfigurationCopy();
bool enabled = config.enabled;
bool global = config.global;
if (!enabled)
@@ -124,12 +124,21 @@ namespace Shadowsocks.View
}
_notifyIcon.Icon = Icon.FromHandle(icon.GetHicon());
string serverInfo = null;
if (controller.GetCurrentStrategy() != null)
{
serverInfo = controller.GetCurrentStrategy().Name;
}
else
{
serverInfo = config.GetCurrentServer().FriendlyName();
}
// we want to show more details but notify icon title is limited to 63 characters
string text = I18N.GetString("Shadowsocks") + " " + UpdateChecker.Version + "\n" +
(enabled ?
I18N.GetString("System Proxy On: ") + (global ? I18N.GetString("Global") : I18N.GetString("PAC")) :
String.Format(I18N.GetString("Running: Port {0}"), config.localPort)) // this feedback is very important because they need to know Shadowsocks is running
+ "\n" + config.GetCurrentServer().FriendlyName();
+ "\n" + serverInfo;
_notifyIcon.Text = text.Substring(0, Math.Min(63, text.Length));
}
@@ -185,18 +194,18 @@ namespace Shadowsocks.View
private void controller_EnableStatusChanged(object sender, EventArgs e)
{
enableItem.Checked = controller.GetConfiguration().enabled;
enableItem.Checked = controller.GetConfigurationCopy().enabled;
modeItem.Enabled = enableItem.Checked;
}
void controller_ShareOverLANStatusChanged(object sender, EventArgs e)
{
ShareOverLANItem.Checked = controller.GetConfiguration().shareOverLan;
ShareOverLANItem.Checked = controller.GetConfigurationCopy().shareOverLan;
}
void controller_EnableGlobalChanged(object sender, EventArgs e)
{
globalModeItem.Checked = controller.GetConfiguration().global;
globalModeItem.Checked = controller.GetConfigurationCopy().global;
PACModeItem.Checked = !globalModeItem.Checked;
}
@@ -243,7 +252,7 @@ namespace Shadowsocks.View
private void LoadCurrentConfiguration()
{
Configuration config = controller.GetConfiguration();
Configuration config = controller.GetConfigurationCopy();
UpdateServersMenu();
enableItem.Checked = config.enabled;
modeItem.Enabled = config.enabled;
@@ -263,20 +272,33 @@ namespace Shadowsocks.View
{
items.RemoveAt(0);
}
Configuration configuration = controller.GetConfiguration();
for (int i = 0; i < configuration.configs.Count; i++)
int i = 0;
foreach (var strategy in controller.GetStrategies())
{
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)
{
Server server = configuration.configs[i];
MenuItem item = new MenuItem(server.FriendlyName());
item.Tag = i;
item.Tag = i - strategyCount;
item.Click += AServerItem_Click;
items.Add(i, item);
i++;
}
if (configuration.index >= 0 && configuration.index < configuration.configs.Count)
foreach (MenuItem item in items)
{
items[configuration.index].Checked = true;
if (item.Tag != null && (item.Tag.ToString() == configuration.index.ToString() || item.Tag.ToString() == configuration.strategy))
{
item.Checked = true;
}
}
}
@@ -380,6 +402,12 @@ namespace Shadowsocks.View
controller.SelectServerIndex((int)item.Tag);
}
private void AStrategyItem_Click(object sender, EventArgs e)
{
MenuItem item = (MenuItem)sender;
controller.SelectStrategy((string)item.Tag);
}
private void ShowLogItem_Click(object sender, EventArgs e)
{
string argument = Logging.LogFile;
@@ -511,11 +539,11 @@ namespace Shadowsocks.View
{
if (!onlinePACItem.Checked)
{
if (String.IsNullOrEmpty(controller.GetConfiguration().pacUrl))
if (String.IsNullOrEmpty(controller.GetConfigurationCopy().pacUrl))
{
UpdateOnlinePACURLItem_Click(sender, e);
}
if (!String.IsNullOrEmpty(controller.GetConfiguration().pacUrl))
if (!String.IsNullOrEmpty(controller.GetConfigurationCopy().pacUrl))
{
localPACItem.Checked = false;
onlinePACItem.Checked = true;
@@ -527,7 +555,7 @@ namespace Shadowsocks.View
private void UpdateOnlinePACURLItem_Click(object sender, EventArgs e)
{
string origPacUrl = controller.GetConfiguration().pacUrl;
string origPacUrl = controller.GetConfigurationCopy().pacUrl;
string pacUrl = Microsoft.VisualBasic.Interaction.InputBox(
I18N.GetString("Please input PAC Url"),
I18N.GetString("Edit Online PAC URL"),
+14 -10
View File
@@ -124,15 +124,16 @@
<Compile Include="3rd\zxing\ResultPoint.cs" />
<Compile Include="3rd\zxing\ResultPointCallback.cs" />
<Compile Include="3rd\zxing\WriterException.cs" />
<Compile Include="Controller\AutoStartup.cs" />
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" />
<Compile Include="Controller\System\AutoStartup.cs" />
<Compile Include="Controller\FileManager.cs" />
<Compile Include="Controller\GFWListUpdater.cs" />
<Compile Include="Controller\Service\GFWListUpdater.cs" />
<Compile Include="Controller\I18N.cs" />
<Compile Include="Controller\Listener.cs" />
<Compile Include="Controller\Service\Listener.cs" />
<Compile Include="Controller\Logging.cs" />
<Compile Include="Controller\PortForwarder.cs" />
<Compile Include="Controller\UDPRelay.cs" />
<Compile Include="Controller\UpdateChecker.cs" />
<Compile Include="Controller\Service\PortForwarder.cs" />
<Compile Include="Controller\Service\UDPRelay.cs" />
<Compile Include="Controller\Service\UpdateChecker.cs" />
<Compile Include="Encryption\EncryptorBase.cs" />
<Compile Include="Encryption\EncryptorFactory.cs" />
<Compile Include="Encryption\IVEncryptor.cs" />
@@ -142,7 +143,7 @@
<Compile Include="Encryption\SodiumEncryptor.cs" />
<Compile Include="Encryption\TableEncryptor.cs" />
<Compile Include="Encryption\IEncryptor.cs" />
<Compile Include="Controller\PACServer.cs" />
<Compile Include="Controller\Service\PACServer.cs" />
<Compile Include="Model\Server.cs" />
<Compile Include="Model\Configuration.cs" />
<Compile Include="Properties\Resources.Designer.cs">
@@ -150,6 +151,9 @@
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Controller\Strategy\BalancingStrategy.cs" />
<Compile Include="Controller\Strategy\StrategyManager.cs" />
<Compile Include="Controller\Strategy\IStrategy.cs" />
<Compile Include="Util\Util.cs" />
<Compile Include="View\ConfigForm.cs">
<SubType>Form</SubType>
@@ -157,12 +161,12 @@
<Compile Include="View\ConfigForm.Designer.cs">
<DependentUpon>ConfigForm.cs</DependentUpon>
</Compile>
<Compile Include="Controller\TCPRelay.cs" />
<Compile Include="Controller\PolipoRunner.cs" />
<Compile Include="Controller\Service\TCPRelay.cs" />
<Compile Include="Controller\Service\PolipoRunner.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Controller\ShadowsocksController.cs" />
<Compile Include="Controller\SystemProxy.cs" />
<Compile Include="Controller\System\SystemProxy.cs" />
<Compile Include="View\MenuViewController.cs" />
<Compile Include="View\QRCodeForm.cs">
<SubType>Form</SubType>