|
|
@@ -25,7 +25,7 @@ namespace Shadowsocks.Controller |
|
|
|
// handle user actions
|
|
|
|
// manipulates UI
|
|
|
|
// interacts with low level logic
|
|
|
|
|
|
|
|
#region Members definition
|
|
|
|
private Thread _ramThread;
|
|
|
|
private Thread _trafficThread;
|
|
|
|
|
|
|
@@ -87,6 +87,7 @@ namespace Shadowsocks.Controller |
|
|
|
|
|
|
|
// Invoked when controller.Start();
|
|
|
|
public event EventHandler<UpdatedEventArgs> ProgramUpdated;
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
public ShadowsocksController()
|
|
|
|
{
|
|
|
@@ -103,6 +104,8 @@ namespace Shadowsocks.Controller |
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#region Basic
|
|
|
|
|
|
|
|
public void Start(bool regHotkeys = true)
|
|
|
|
{
|
|
|
|
if (_config.updated && regHotkeys)
|
|
|
@@ -122,6 +125,115 @@ namespace Shadowsocks.Controller |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Stop()
|
|
|
|
{
|
|
|
|
if (stopped)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
stopped = true;
|
|
|
|
if (_listener != null)
|
|
|
|
{
|
|
|
|
_listener.Stop();
|
|
|
|
}
|
|
|
|
StopPlugins();
|
|
|
|
if (privoxyRunner != null)
|
|
|
|
{
|
|
|
|
privoxyRunner.Stop();
|
|
|
|
}
|
|
|
|
if (_config.enabled)
|
|
|
|
{
|
|
|
|
SystemProxy.Update(_config, true, null);
|
|
|
|
}
|
|
|
|
Encryption.RNG.Close();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void Reload()
|
|
|
|
{
|
|
|
|
Encryption.RNG.Reload();
|
|
|
|
// some logic in configuration updated the config when saving, we need to read it again
|
|
|
|
_config = Configuration.Load();
|
|
|
|
|
|
|
|
NLogConfig.LoadConfiguration();
|
|
|
|
|
|
|
|
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
|
|
|
|
|
|
|
|
privoxyRunner = privoxyRunner ?? new PrivoxyRunner();
|
|
|
|
|
|
|
|
_pacDaemon = _pacDaemon ?? new PACDaemon(_config);
|
|
|
|
_pacDaemon.PACFileChanged += PacDaemon_PACFileChanged;
|
|
|
|
_pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged;
|
|
|
|
_pacServer = _pacServer ?? new PACServer(_pacDaemon);
|
|
|
|
_pacServer.UpdatePACURL(_config); // So PACServer works when system proxy disabled.
|
|
|
|
|
|
|
|
GeositeUpdater.ResetEvent();
|
|
|
|
GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted;
|
|
|
|
GeositeUpdater.Error += PacServer_PACUpdateError;
|
|
|
|
|
|
|
|
availabilityStatistics.UpdateConfiguration(this);
|
|
|
|
_listener?.Stop();
|
|
|
|
StopPlugins();
|
|
|
|
|
|
|
|
// don't put PrivoxyRunner.Start() before pacServer.Stop()
|
|
|
|
// or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1
|
|
|
|
// though UseShellExecute is set to true now
|
|
|
|
// http://stackoverflow.com/questions/10235093/socket-doesnt-close-after-application-exits-if-a-launched-process-is-open
|
|
|
|
privoxyRunner.Stop();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
var strategy = GetCurrentStrategy();
|
|
|
|
strategy?.ReloadServers();
|
|
|
|
|
|
|
|
StartPlugin();
|
|
|
|
privoxyRunner.Start(_config);
|
|
|
|
|
|
|
|
TCPRelay tcpRelay = new TCPRelay(this, _config);
|
|
|
|
tcpRelay.OnConnected += UpdateLatency;
|
|
|
|
tcpRelay.OnInbound += UpdateInboundCounter;
|
|
|
|
tcpRelay.OnOutbound += UpdateOutboundCounter;
|
|
|
|
tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server);
|
|
|
|
|
|
|
|
UDPRelay udpRelay = new UDPRelay(this);
|
|
|
|
List<Listener.IService> services = new List<Listener.IService>
|
|
|
|
{
|
|
|
|
tcpRelay,
|
|
|
|
udpRelay,
|
|
|
|
_pacServer,
|
|
|
|
new PortForwarder(privoxyRunner.RunningPort)
|
|
|
|
};
|
|
|
|
_listener = new Listener(services);
|
|
|
|
_listener.Start(_config);
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
// translate Microsoft language into human language
|
|
|
|
// i.e. An attempt was made to access a socket in a way forbidden by its access permissions => Port already in use
|
|
|
|
if (e is SocketException se)
|
|
|
|
{
|
|
|
|
if (se.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
|
|
|
{
|
|
|
|
e = new Exception(I18N.GetString("Port {0} already in use", _config.localPort), e);
|
|
|
|
}
|
|
|
|
else if (se.SocketErrorCode == SocketError.AccessDenied)
|
|
|
|
{
|
|
|
|
e = new Exception(I18N.GetString("Port {0} is reserved by system", _config.localPort), e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
logger.LogUsefulException(e);
|
|
|
|
ReportError(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
UpdateSystemProxy();
|
|
|
|
Utils.ReleaseMemory(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void SaveConfig(Configuration newConfig)
|
|
|
|
{
|
|
|
|
Configuration.Save(newConfig);
|
|
|
|
Reload();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void ReportError(Exception e)
|
|
|
|
{
|
|
|
|
Errored?.Invoke(this, new ErrorEventArgs(e));
|
|
|
@@ -144,23 +256,6 @@ namespace Shadowsocks.Controller |
|
|
|
return _config;
|
|
|
|
}
|
|
|
|
|
|
|
|
public IList<IStrategy> GetStrategies()
|
|
|
|
{
|
|
|
|
return _strategyManager.GetStrategies();
|
|
|
|
}
|
|
|
|
|
|
|
|
public IStrategy GetCurrentStrategy()
|
|
|
|
{
|
|
|
|
foreach (var strategy in _strategyManager.GetStrategies())
|
|
|
|
{
|
|
|
|
if (strategy.ID == _config.strategy)
|
|
|
|
{
|
|
|
|
return strategy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint)
|
|
|
|
{
|
|
|
|
IStrategy strategy = GetCurrentStrategy();
|
|
|
@@ -175,34 +270,6 @@ namespace Shadowsocks.Controller |
|
|
|
return GetCurrentServer();
|
|
|
|
}
|
|
|
|
|
|
|
|
public EndPoint GetPluginLocalEndPointIfConfigured(Server server)
|
|
|
|
{
|
|
|
|
var plugin = _pluginsByServer.GetOrAdd(
|
|
|
|
server,
|
|
|
|
x => Sip003Plugin.CreateIfConfigured(x, _config.showPluginOutput));
|
|
|
|
|
|
|
|
if (plugin == null)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (plugin.StartIfNeeded())
|
|
|
|
{
|
|
|
|
logger.Info(
|
|
|
|
$"Started SIP003 plugin for {server.Identifier()} on {plugin.LocalEndPoint} - PID: {plugin.ProcessId}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
logger.Error("Failed to start SIP003 plugin: " + ex.Message);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
return plugin.LocalEndPoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SaveServers(List<Server> servers, int localPort, bool portableMode)
|
|
|
|
{
|
|
|
|
_config.configs = servers;
|
|
|
@@ -211,56 +278,24 @@ namespace Shadowsocks.Controller |
|
|
|
Configuration.Save(_config);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration)
|
|
|
|
public void SelectServerIndex(int index)
|
|
|
|
{
|
|
|
|
StatisticsConfiguration = configuration;
|
|
|
|
StatisticsStrategyConfiguration.Save(configuration);
|
|
|
|
_config.index = index;
|
|
|
|
_config.strategy = null;
|
|
|
|
SaveConfig(_config);
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool AskAddServerBySSURL(string ssURL)
|
|
|
|
public void ToggleShareOverLAN(bool enabled)
|
|
|
|
{
|
|
|
|
var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo);
|
|
|
|
if (dr == DialogResult.Yes)
|
|
|
|
{
|
|
|
|
if (AddServerBySSURL(ssURL))
|
|
|
|
{
|
|
|
|
MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid."));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_config.shareOverLan = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
|
|
|
|
public bool AddServerBySSURL(string ssURL)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (ssURL.IsNullOrEmpty() || ssURL.IsWhiteSpace())
|
|
|
|
return false;
|
|
|
|
ShareOverLANStatusChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
var servers = Server.GetServers(ssURL);
|
|
|
|
if (servers == null || servers.Count == 0)
|
|
|
|
return false;
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
foreach (var server in servers)
|
|
|
|
{
|
|
|
|
_config.configs.Add(server);
|
|
|
|
}
|
|
|
|
_config.index = _config.configs.Count - 1;
|
|
|
|
SaveConfig(_config);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
logger.LogUsefulException(e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#region OS Proxy
|
|
|
|
|
|
|
|
public void ToggleEnable(bool enabled)
|
|
|
|
{
|
|
|
@@ -278,81 +313,62 @@ namespace Shadowsocks.Controller |
|
|
|
EnableGlobalChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ToggleShareOverLAN(bool enabled)
|
|
|
|
{
|
|
|
|
_config.shareOverLan = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
|
|
|
|
ShareOverLANStatusChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SaveProxy(ProxyConfig proxyConfig)
|
|
|
|
{
|
|
|
|
_config.proxy = proxyConfig;
|
|
|
|
SaveConfig(_config);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ToggleVerboseLogging(bool enabled)
|
|
|
|
private void UpdateSystemProxy()
|
|
|
|
{
|
|
|
|
_config.isVerboseLogging = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
NLogConfig.LoadConfiguration(); // reload nlog
|
|
|
|
|
|
|
|
VerboseLoggingStatusChanged?.Invoke(this, new EventArgs());
|
|
|
|
SystemProxy.Update(_config, false, _pacServer);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ToggleShowPluginOutput(bool enabled)
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region PAC
|
|
|
|
|
|
|
|
private void PacDaemon_PACFileChanged(object sender, EventArgs e)
|
|
|
|
{
|
|
|
|
_config.showPluginOutput = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
UpdateSystemProxy();
|
|
|
|
}
|
|
|
|
|
|
|
|
ShowPluginOutputChanged?.Invoke(this, new EventArgs());
|
|
|
|
private void PacServer_PACUpdateCompleted(object sender, GeositeResultEventArgs e)
|
|
|
|
{
|
|
|
|
UpdatePACFromGeositeCompleted?.Invoke(this, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SelectServerIndex(int index)
|
|
|
|
private void PacServer_PACUpdateError(object sender, ErrorEventArgs e)
|
|
|
|
{
|
|
|
|
_config.index = index;
|
|
|
|
_config.strategy = null;
|
|
|
|
SaveConfig(_config);
|
|
|
|
UpdatePACFromGeositeError?.Invoke(this, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SelectStrategy(string strategyID)
|
|
|
|
private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' };
|
|
|
|
private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e)
|
|
|
|
{
|
|
|
|
_config.index = -1;
|
|
|
|
_config.strategy = strategyID;
|
|
|
|
SaveConfig(_config);
|
|
|
|
GeositeUpdater.MergeAndWritePACFile(_config.geositeGroup, _config.geositeBlacklistMode);
|
|
|
|
UpdateSystemProxy();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Stop()
|
|
|
|
public void CopyPacUrl()
|
|
|
|
{
|
|
|
|
if (stopped)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
stopped = true;
|
|
|
|
if (_listener != null)
|
|
|
|
{
|
|
|
|
_listener.Stop();
|
|
|
|
}
|
|
|
|
StopPlugins();
|
|
|
|
if (privoxyRunner != null)
|
|
|
|
{
|
|
|
|
privoxyRunner.Stop();
|
|
|
|
}
|
|
|
|
if (_config.enabled)
|
|
|
|
{
|
|
|
|
SystemProxy.Update(_config, true, null);
|
|
|
|
}
|
|
|
|
Encryption.RNG.Close();
|
|
|
|
Clipboard.SetDataObject(_pacServer.PacUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void StopPlugins()
|
|
|
|
public void SavePACUrl(string pacUrl)
|
|
|
|
{
|
|
|
|
foreach (var serverAndPlugin in _pluginsByServer)
|
|
|
|
{
|
|
|
|
serverAndPlugin.Value?.Dispose();
|
|
|
|
}
|
|
|
|
_pluginsByServer.Clear();
|
|
|
|
_config.pacUrl = pacUrl;
|
|
|
|
SaveConfig(_config);
|
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UseOnlinePAC(bool useOnlinePac)
|
|
|
|
{
|
|
|
|
_config.useOnlinePac = useOnlinePac;
|
|
|
|
SaveConfig(_config);
|
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void TouchPACFile()
|
|
|
@@ -369,43 +385,78 @@ namespace Shadowsocks.Controller |
|
|
|
UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename });
|
|
|
|
}
|
|
|
|
|
|
|
|
public string GetServerURLForCurrentServer()
|
|
|
|
public void ToggleSecureLocalPac(bool enabled)
|
|
|
|
{
|
|
|
|
return GetCurrentServer().GetURL(_config.generateLegacyUrl);
|
|
|
|
_config.secureLocalPac = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UpdateStatisticsConfiguration(bool enabled)
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region SIP002
|
|
|
|
|
|
|
|
public bool AskAddServerBySSURL(string ssURL)
|
|
|
|
{
|
|
|
|
if (availabilityStatistics != null)
|
|
|
|
var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo);
|
|
|
|
if (dr == DialogResult.Yes)
|
|
|
|
{
|
|
|
|
availabilityStatistics.UpdateConfiguration(this);
|
|
|
|
_config.availabilityStatistics = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
if (AddServerBySSURL(ssURL))
|
|
|
|
{
|
|
|
|
MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid."));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SavePACUrl(string pacUrl)
|
|
|
|
public bool AddServerBySSURL(string ssURL)
|
|
|
|
{
|
|
|
|
_config.pacUrl = pacUrl;
|
|
|
|
SaveConfig(_config);
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (ssURL.IsNullOrEmpty() || ssURL.IsWhiteSpace())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
var servers = Server.GetServers(ssURL);
|
|
|
|
if (servers == null || servers.Count == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
foreach (var server in servers)
|
|
|
|
{
|
|
|
|
_config.configs.Add(server);
|
|
|
|
}
|
|
|
|
_config.index = _config.configs.Count - 1;
|
|
|
|
SaveConfig(_config);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
logger.LogUsefulException(e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UseOnlinePAC(bool useOnlinePac)
|
|
|
|
public string GetServerURLForCurrentServer()
|
|
|
|
{
|
|
|
|
_config.useOnlinePac = useOnlinePac;
|
|
|
|
SaveConfig(_config);
|
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
return GetCurrentServer().GetURL(_config.generateLegacyUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ToggleSecureLocalPac(bool enabled)
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Misc
|
|
|
|
|
|
|
|
public void ToggleVerboseLogging(bool enabled)
|
|
|
|
{
|
|
|
|
_config.secureLocalPac = enabled;
|
|
|
|
_config.isVerboseLogging = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
NLogConfig.LoadConfiguration(); // reload nlog
|
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
VerboseLoggingStatusChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ToggleCheckingUpdate(bool enabled)
|
|
|
@@ -439,6 +490,50 @@ namespace Shadowsocks.Controller |
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Statistic
|
|
|
|
|
|
|
|
public void SelectStrategy(string strategyID)
|
|
|
|
{
|
|
|
|
_config.index = -1;
|
|
|
|
_config.strategy = strategyID;
|
|
|
|
SaveConfig(_config);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration)
|
|
|
|
{
|
|
|
|
StatisticsConfiguration = configuration;
|
|
|
|
StatisticsStrategyConfiguration.Save(configuration);
|
|
|
|
}
|
|
|
|
|
|
|
|
public IList<IStrategy> GetStrategies()
|
|
|
|
{
|
|
|
|
return _strategyManager.GetStrategies();
|
|
|
|
}
|
|
|
|
|
|
|
|
public IStrategy GetCurrentStrategy()
|
|
|
|
{
|
|
|
|
foreach (var strategy in _strategyManager.GetStrategies())
|
|
|
|
{
|
|
|
|
if (strategy.ID == _config.strategy)
|
|
|
|
{
|
|
|
|
return strategy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UpdateStatisticsConfiguration(bool enabled)
|
|
|
|
{
|
|
|
|
if (availabilityStatistics != null)
|
|
|
|
{
|
|
|
|
availabilityStatistics.UpdateConfiguration(this);
|
|
|
|
_config.availabilityStatistics = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UpdateLatency(object sender, SSTCPConnectedEventArgs args)
|
|
|
|
{
|
|
|
@@ -469,130 +564,63 @@ namespace Shadowsocks.Controller |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void Reload()
|
|
|
|
{
|
|
|
|
Encryption.RNG.Reload();
|
|
|
|
// some logic in configuration updated the config when saving, we need to read it again
|
|
|
|
_config = Configuration.Load();
|
|
|
|
|
|
|
|
NLogConfig.LoadConfiguration();
|
|
|
|
|
|
|
|
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
|
|
|
|
|
|
|
|
privoxyRunner = privoxyRunner ?? new PrivoxyRunner();
|
|
|
|
|
|
|
|
_pacDaemon = _pacDaemon ?? new PACDaemon(_config);
|
|
|
|
_pacDaemon.PACFileChanged += PacDaemon_PACFileChanged;
|
|
|
|
_pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged;
|
|
|
|
_pacServer = _pacServer ?? new PACServer(_pacDaemon);
|
|
|
|
_pacServer.UpdatePACURL(_config); // So PACServer works when system proxy disabled.
|
|
|
|
|
|
|
|
GeositeUpdater.ResetEvent();
|
|
|
|
GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted;
|
|
|
|
GeositeUpdater.Error += PacServer_PACUpdateError;
|
|
|
|
|
|
|
|
availabilityStatistics.UpdateConfiguration(this);
|
|
|
|
_listener?.Stop();
|
|
|
|
StopPlugins();
|
|
|
|
|
|
|
|
// don't put PrivoxyRunner.Start() before pacServer.Stop()
|
|
|
|
// or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1
|
|
|
|
// though UseShellExecute is set to true now
|
|
|
|
// http://stackoverflow.com/questions/10235093/socket-doesnt-close-after-application-exits-if-a-launched-process-is-open
|
|
|
|
privoxyRunner.Stop();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
var strategy = GetCurrentStrategy();
|
|
|
|
strategy?.ReloadServers();
|
|
|
|
|
|
|
|
StartPlugin();
|
|
|
|
privoxyRunner.Start(_config);
|
|
|
|
|
|
|
|
TCPRelay tcpRelay = new TCPRelay(this, _config);
|
|
|
|
tcpRelay.OnConnected += UpdateLatency;
|
|
|
|
tcpRelay.OnInbound += UpdateInboundCounter;
|
|
|
|
tcpRelay.OnOutbound += UpdateOutboundCounter;
|
|
|
|
tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server);
|
|
|
|
|
|
|
|
UDPRelay udpRelay = new UDPRelay(this);
|
|
|
|
List<Listener.IService> services = new List<Listener.IService>
|
|
|
|
{
|
|
|
|
tcpRelay,
|
|
|
|
udpRelay,
|
|
|
|
_pacServer,
|
|
|
|
new PortForwarder(privoxyRunner.RunningPort)
|
|
|
|
};
|
|
|
|
_listener = new Listener(services);
|
|
|
|
_listener.Start(_config);
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
// translate Microsoft language into human language
|
|
|
|
// i.e. An attempt was made to access a socket in a way forbidden by its access permissions => Port already in use
|
|
|
|
if (e is SocketException se)
|
|
|
|
{
|
|
|
|
if (se.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
|
|
|
{
|
|
|
|
e = new Exception(I18N.GetString("Port {0} already in use", _config.localPort), e);
|
|
|
|
}
|
|
|
|
else if (se.SocketErrorCode == SocketError.AccessDenied)
|
|
|
|
{
|
|
|
|
e = new Exception(I18N.GetString("Port {0} is reserved by system", _config.localPort), e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
logger.LogUsefulException(e);
|
|
|
|
ReportError(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
ConfigChanged?.Invoke(this, new EventArgs());
|
|
|
|
UpdateSystemProxy();
|
|
|
|
Utils.ReleaseMemory(true);
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region SIP003
|
|
|
|
|
|
|
|
private void StartPlugin()
|
|
|
|
{
|
|
|
|
var server = _config.GetCurrentServer();
|
|
|
|
GetPluginLocalEndPointIfConfigured(server);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void SaveConfig(Configuration newConfig)
|
|
|
|
private void StopPlugins()
|
|
|
|
{
|
|
|
|
Configuration.Save(newConfig);
|
|
|
|
Reload();
|
|
|
|
foreach (var serverAndPlugin in _pluginsByServer)
|
|
|
|
{
|
|
|
|
serverAndPlugin.Value?.Dispose();
|
|
|
|
}
|
|
|
|
_pluginsByServer.Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void UpdateSystemProxy()
|
|
|
|
public EndPoint GetPluginLocalEndPointIfConfigured(Server server)
|
|
|
|
{
|
|
|
|
SystemProxy.Update(_config, false, _pacServer);
|
|
|
|
}
|
|
|
|
var plugin = _pluginsByServer.GetOrAdd(
|
|
|
|
server,
|
|
|
|
x => Sip003Plugin.CreateIfConfigured(x, _config.showPluginOutput));
|
|
|
|
|
|
|
|
private void PacDaemon_PACFileChanged(object sender, EventArgs e)
|
|
|
|
{
|
|
|
|
UpdateSystemProxy();
|
|
|
|
}
|
|
|
|
if (plugin == null)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void PacServer_PACUpdateCompleted(object sender, GeositeResultEventArgs e)
|
|
|
|
{
|
|
|
|
UpdatePACFromGeositeCompleted?.Invoke(this, e);
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (plugin.StartIfNeeded())
|
|
|
|
{
|
|
|
|
logger.Info(
|
|
|
|
$"Started SIP003 plugin for {server.Identifier()} on {plugin.LocalEndPoint} - PID: {plugin.ProcessId}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
logger.Error("Failed to start SIP003 plugin: " + ex.Message);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void PacServer_PACUpdateError(object sender, ErrorEventArgs e)
|
|
|
|
{
|
|
|
|
UpdatePACFromGeositeError?.Invoke(this, e);
|
|
|
|
return plugin.LocalEndPoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' };
|
|
|
|
private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e)
|
|
|
|
public void ToggleShowPluginOutput(bool enabled)
|
|
|
|
{
|
|
|
|
GeositeUpdater.MergeAndWritePACFile(_config.geositeGroup, _config.geositeBlacklistMode);
|
|
|
|
UpdateSystemProxy();
|
|
|
|
}
|
|
|
|
_config.showPluginOutput = enabled;
|
|
|
|
SaveConfig(_config);
|
|
|
|
|
|
|
|
public void CopyPacUrl()
|
|
|
|
{
|
|
|
|
Clipboard.SetDataObject(_pacServer.PacUrl);
|
|
|
|
ShowPluginOutputChanged?.Invoke(this, new EventArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Memory Management
|
|
|
|
|
|
|
|
private void StartReleasingMemory()
|
|
|
|