Browse Source

✔ Geosite group validation + PAC regeneration on version update

- Validate geosite group configuration
- Reset to default if specified group doesn't exist
- Regenerate pac.txt on version update (can be turned off)
- Cleanup of `Configuration` to separate loading logic and processing logic
tags/4.3.0.0
database64128 3 years ago
parent
commit
d72c2b0429
11 changed files with 151 additions and 162 deletions
  1. +1
    -1
      shadowsocks-csharp/Controller/HotkeyReg.cs
  2. +7
    -0
      shadowsocks-csharp/Controller/Service/GeositeUpdater.cs
  3. +1
    -2
      shadowsocks-csharp/Controller/Service/PACServer.cs
  4. +1
    -1
      shadowsocks-csharp/Controller/Service/UpdateChecker.cs
  5. +26
    -31
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  6. +1
    -0
      shadowsocks-csharp/Data/i18n.csv
  7. +91
    -74
      shadowsocks-csharp/Model/Configuration.cs
  8. +1
    -3
      shadowsocks-csharp/Program.cs
  9. +0
    -36
      shadowsocks-csharp/Util/Util.cs
  10. +21
    -13
      shadowsocks-csharp/View/MenuViewController.cs
  11. +1
    -1
      shadowsocks-csharp/ViewModels/ServerSharingViewModel.cs

+ 1
- 1
shadowsocks-csharp/Controller/HotkeyReg.cs View File

@@ -12,7 +12,7 @@ namespace Shadowsocks.Controller
private static Logger logger = LogManager.GetCurrentClassLogger();
public static void RegAllHotkeys()
{
var hotkeyConfig = Configuration.Load().hotkey;
var hotkeyConfig = Program.MainController.GetCurrentConfiguration().hotkey;

if (hotkeyConfig == null || !hotkeyConfig.RegHotkeysAtStartup)
return;


+ 7
- 0
shadowsocks-csharp/Controller/Service/GeositeUpdater.cs View File

@@ -192,6 +192,13 @@ namespace Shadowsocks.Controller
return true;
}

/// <summary>
/// Checks if the specified group exists in GeoSite database.
/// </summary>
/// <param name="group">The group name to check for.</param>
/// <returns>True if the group exists. False if the group doesn't exist.</returns>
public static bool CheckGeositeGroup(string group) => Geosites.ContainsKey(group);

private static string MergePACFile(IList<DomainObject> domains, bool blacklist)
{
string abpContent;


+ 1
- 2
shadowsocks-csharp/Controller/Service/PACServer.cs View File

@@ -45,7 +45,7 @@ namespace Shadowsocks.Controller
_config = config;
string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : "";
string contentHash = GetHash(_pacDaemon.GetPACContent());
PacUrl = $"http://{config.localHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}";
PacUrl = $"http://{config.LocalHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}";
logger.Debug("Set PAC URL:" + PacUrl);
}
@@ -176,7 +176,6 @@ Connection: Close
";
byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent);
socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket);
Utils.ReleaseMemory(true);
}
catch (Exception e)
{


+ 1
- 1
shadowsocks-csharp/Controller/Service/UpdateChecker.cs View File

@@ -172,7 +172,7 @@ namespace Shadowsocks.Controller
{
WebClient http = new WebClient();
http.Headers.Add("User-Agent", UserAgent);
http.Proxy = new WebProxy(config.localHost, config.localPort);
http.Proxy = new WebProxy(config.LocalHost, config.localPort);
return http;
}


+ 26
- 31
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -27,7 +27,6 @@ namespace Shadowsocks.Controller
// manipulates UI
// interacts with low level logic
#region Members definition
private Thread _ramThread;
private Thread _trafficThread;
private Listener _listener;
@@ -93,10 +92,10 @@ namespace Shadowsocks.Controller
public ShadowsocksController()
{
_config = Configuration.Load();
Configuration.Process(ref _config);
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
_strategyManager = new StrategyManager(this);
_pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>();
StartReleasingMemory();
StartTrafficStatistics(61);
ProgramUpdated += (o, e) =>
@@ -107,23 +106,34 @@ namespace Shadowsocks.Controller
#region Basic
public void Start(bool regHotkeys = true)
public void Start(bool systemWakeUp = false)
{
if (_config.updated && regHotkeys)
if (_config.firstRunOnNewVersion && !systemWakeUp)
{
_config.updated = false;
ProgramUpdated.Invoke(this, new UpdatedEventArgs()
{
OldVersion = _config.version,
NewVersion = UpdateChecker.Version,
});
// delete pac.txt when regeneratePacOnUpdate is true
if (_config.regeneratePacOnUpdate)
try
{
File.Delete(PACDaemon.PAC_FILE);
logger.Info("Deleted pac.txt from previous version.");
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
// finish up first run of new version
_config.firstRunOnNewVersion = false;
_config.version = UpdateChecker.Version;
Configuration.Save(_config);
}
Reload();
if (regHotkeys)
{
if (!systemWakeUp)
HotkeyReg.RegAllHotkeys();
}
}
public void Stop()
@@ -154,6 +164,7 @@ namespace Shadowsocks.Controller
Encryption.RNG.Reload();
// some logic in configuration updated the config when saving, we need to read it again
_config = Configuration.Load();
Configuration.Process(ref _config);
NLogConfig.LoadConfiguration();
@@ -226,7 +237,6 @@ namespace Shadowsocks.Controller
ConfigChanged?.Invoke(this, new EventArgs());
UpdateSystemProxy();
Utils.ReleaseMemory(true);
}
protected void SaveConfig(Configuration newConfig)
@@ -394,6 +404,13 @@ namespace Shadowsocks.Controller
ConfigChanged?.Invoke(this, new EventArgs());
}
public void ToggleRegeneratePacOnUpdate(bool enabled)
{
_config.regeneratePacOnUpdate = enabled;
SaveConfig(_config);
ConfigChanged?.Invoke(this, new EventArgs());
}
#endregion
#region SIP002
@@ -622,28 +639,6 @@ namespace Shadowsocks.Controller
#endregion
#region Memory Management
private void StartReleasingMemory()
{
_ramThread = new Thread(new ThreadStart(ReleaseMemory))
{
IsBackground = true
};
_ramThread.Start();
}
private void ReleaseMemory()
{
while (true)
{
Utils.ReleaseMemory(false);
Thread.Sleep(30 * 1000);
}
}
#endregion
#region Traffic Statistics
private void StartTrafficStatistics(int queueMaxSize)


+ 1
- 0
shadowsocks-csharp/Data/i18n.csv View File

@@ -28,6 +28,7 @@ Edit Local PAC File...,Редактировать локальный PAC…,编
Update Local PAC from Geosite,Обновить локальный PAC из Geosite,从 Geosite 更新本地 PAC,從 Geosite 更新本機 PAC,Geosite からローカル PAC を更新,Geosite에서 로컬 프록시 자동 구성 파일 업데이트,Mettre à jour le PAC local à partir de Geosite
Edit User Rule for Geosite...,Редактировать свои правила для Geosite,编辑 Geosite 的用户规则...,編輯 Geosite 的使用者規則...,ユーザールールの編集...,Geosite 사용자 수정,Modifier la règle utilisateur pour Geosite ...
Secure Local PAC,Безопасный URL локального PAC,保护本地 PAC,安全本機 PAC,ローカル PAC を保護,로컬 프록시 자동 구성 파일 암호화,Sécuriser PAC local
Regenerate local PAC on version update,,版本更新后重新生成本地 PAC,版本更新後重新生成本地 PAC,,,
Copy Local PAC URL,Копировать URL локального PAC,复制本地 PAC 网址,複製本機 PAC 網址,ローカル PAC URL をコピー,로컬 프록시 자동 구성 파일 URL 복사,Copier l'URL du PAC local
Share Server Config...,Поделиться конфигурацией сервера…,分享服务器配置...,分享伺服器設定檔...,サーバーの設定を共有...,서버 설정 공유,Partager la configuration du serveur ...
Scan QRCode from Screen...,Сканировать QRCode с экрана…,扫描屏幕上的二维码...,掃描螢幕上的 QR 碼...,画面から QR コードをスキャン...,화면에서 QR코드 스캔,Scanner le QRCode à partir de l'écran ...


+ 91
- 74
shadowsocks-csharp/Model/Configuration.cs View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows;
using Newtonsoft.Json;
using NLog;
using Shadowsocks.Controller;
@@ -27,14 +28,15 @@ namespace Shadowsocks.Model
public bool global;
public bool enabled;
public bool shareOverLan;
public bool isDefault;
public bool firstRun;
public int localPort;
public bool portableMode;
public bool showPluginOutput;
public string pacUrl;
public bool useOnlinePac;
public bool secureLocalPac;
public bool secureLocalPac; // enable secret for PAC server
public bool regeneratePacOnUpdate; // regenerate pac.txt on version update
public bool availabilityStatistics;
public bool autoCheckUpdate;
public bool checkPreRelease;
@@ -54,7 +56,7 @@ namespace Shadowsocks.Model
public HotkeyConfig hotkey;
[JsonIgnore]
public bool updated;
public bool firstRunOnNewVersion;
public Configuration()
{
@@ -64,13 +66,14 @@ namespace Shadowsocks.Model
global = false;
enabled = false;
shareOverLan = false;
isDefault = true;
firstRun = true;
localPort = 1080;
portableMode = true;
showPluginOutput = false;
pacUrl = "";
useOnlinePac = false;
secureLocalPac = true;
regeneratePacOnUpdate = true;
availabilityStatistics = false;
autoCheckUpdate = false;
checkPreRelease = false;
@@ -88,7 +91,7 @@ namespace Shadowsocks.Model
proxy = new ProxyConfig();
hotkey = new HotkeyConfig();
updated = false;
firstRunOnNewVersion = false;
configs = new List<Server>();
onlineConfigSource = new List<string>();
@@ -108,11 +111,8 @@ namespace Shadowsocks.Model
#endif
[JsonIgnore]
public string localHost => GetLocalHost();
private string GetLocalHost()
{
return isIPv6Enabled ? "[::1]" : "127.0.0.1";
}
public string LocalHost => isIPv6Enabled ? "[::1]" : "127.0.0.1";
public Server GetCurrentServer()
{
if (index >= 0 && index < configs.Count)
@@ -129,6 +129,11 @@ namespace Shadowsocks.Model
localPort)
: null;
/// <summary>
/// Used by multiple forms to validate a server.
/// Communication is done by throwing exceptions.
/// </summary>
/// <param name="server"></param>
public static void CheckServer(Server server)
{
CheckServer(server.server);
@@ -137,53 +142,71 @@ namespace Shadowsocks.Model
CheckTimeout(server.timeout, Server.MaxServerTimeoutSec);
}
public static bool ChecksServer(Server server)
{
try
{
CheckServer(server);
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Loads the configuration from file.
/// </summary>
/// <returns>An Configuration object.</returns>
public static Configuration Load()
{
Configuration config;
try
if (File.Exists(CONFIG_FILE))
{
string configContent = File.ReadAllText(CONFIG_FILE);
config = JsonConvert.DeserializeObject<Configuration>(configContent);
config.isDefault = false;
config.version = UpdateChecker.Version;
if (UpdateChecker.Asset.CompareVersion(UpdateChecker.Version, config.version ?? "0") > 0)
try
{
config.updated = true;
string configContent = File.ReadAllText(CONFIG_FILE);
config = JsonConvert.DeserializeObject<Configuration>(configContent);
return config;
}
if (config.configs.Count == 0)
config.configs.Add(GetDefaultServer());
if (config.index == -1 && string.IsNullOrEmpty(config.strategy))
config.index = 0;
if (!System.Net.Sockets.Socket.OSSupportsIPv6)
catch (Exception e)
{
config.isIPv6Enabled = false; // disable IPv6 if os not support
if (!(e is FileNotFoundException))
logger.LogUsefulException(e);
}
//TODO if remote host(server) do not support IPv6 (or DNS resolve AAAA TYPE record) disable IPv6?
}
config = new Configuration();
return config;
}
config.proxy.CheckConfig();
/// <summary>
/// Process the loaded configurations and set up things.
/// </summary>
/// <param name="config">A reference of Configuration object.</param>
public static void Process(ref Configuration config)
{
// Verify if the configured geosite group exists.
// Reset to default if no such group.
if (!GeositeUpdater.CheckGeositeGroup(config.geositeGroup))
{
#if DEBUG
logger.Debug($"Current group: {config.geositeGroup}");
foreach (var group in GeositeUpdater.Geosites.Keys)
logger.Debug($"{group}");
#endif
config.geositeGroup = "geolocation-!cn";
logger.Warn("The specified Geosite group doesn't exist. Using default group.");
}
catch (Exception e)
// Mark the first run of a new version.
if (UpdateChecker.Asset.CompareVersion(UpdateChecker.Version, config.version ?? "0") > 0)
{
if (!(e is FileNotFoundException))
logger.LogUsefulException(e);
config = new Configuration();
config.configs.Add(GetDefaultServer());
config.firstRunOnNewVersion = true;
}
// Add an empty server configuration
if (config.configs.Count == 0)
config.configs.Add(GetDefaultServer());
// Selected server
if (config.index == -1 && string.IsNullOrEmpty(config.strategy))
config.index = 0;
if (config.index >= config.configs.Count)
config.index = config.configs.Count - 1;
// Check OS IPv6 support
if (!System.Net.Sockets.Socket.OSSupportsIPv6)
config.isIPv6Enabled = false;
config.proxy.CheckConfig();
// Replace $version with the version number.
config.userAgentString = config.userAgent.Replace("$version", config.version);
// NLog log level
try
{
config.nLogConfig = NLogConfig.LoadXML();
@@ -203,48 +226,42 @@ namespace Shadowsocks.Model
}
catch (Exception e)
{
// todo: route the error to UI since there is no log file in this scenario
logger.Error(e, "Cannot get the log level from NLog config file. Please check if the nlog config file exists with corresponding XML nodes.");
MessageBox.Show($"Cannot get the log level from NLog config file. Please check if the nlog config file exists with corresponding XML nodes.\n{e.Message}");
}
config.userAgentString = config.userAgent.Replace("$version", config.version);
return config;
}
/// <summary>
/// Saves the Configuration object to file.
/// </summary>
/// <param name="config">A Configuration object.</param>
public static void Save(Configuration config)
{
config.configs = SortByOnlineConfig(config.configs);
if (config.index >= config.configs.Count)
config.index = config.configs.Count - 1;
if (config.index < -1)
config.index = -1;
if (config.index == -1 && string.IsNullOrEmpty(config.strategy))
config.index = 0;
config.isDefault = false;
FileStream configFileStream = null;
StreamWriter configStreamWriter = null;
try
{
using (StreamWriter sw = new StreamWriter(File.Open(CONFIG_FILE, FileMode.Create)))
{
string jsonString = JsonConvert.SerializeObject(config, Formatting.Indented);
sw.Write(jsonString);
sw.Flush();
}
try
{
// apply changes to NLog.config
config.nLogConfig.SetLogLevel(config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info);
NLogConfig.SaveXML(config.nLogConfig);
}
catch (Exception e)
{
logger.Error(e, "Cannot set the log level to NLog config file. Please check if the nlog config file exists with corresponding XML nodes.");
}
configFileStream = File.Open(CONFIG_FILE, FileMode.Create);
configStreamWriter = new StreamWriter(configFileStream);
var jsonString = JsonConvert.SerializeObject(config, Formatting.Indented);
configStreamWriter.Write(jsonString);
configStreamWriter.Flush();
// NLog
config.nLogConfig.SetLogLevel(config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info);
NLogConfig.SaveXML(config.nLogConfig);
}
catch (IOException e)
catch (Exception e)
{
logger.LogUsefulException(e);
}
finally
{
if (configStreamWriter != null)
configStreamWriter.Dispose();
if (configFileStream != null)
configFileStream.Dispose();
}
}
public static List<Server> SortByOnlineConfig(IEnumerable<Server> servers)


+ 1
- 3
shadowsocks-csharp/Program.cs View File

@@ -97,8 +97,6 @@ namespace Shadowsocks
#endregion
#region Event Handlers Setup
Utils.ReleaseMemory(true);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// handle UI exceptions
Application.ThreadException += Application_ThreadException;
@@ -185,7 +183,7 @@ namespace Shadowsocks
Thread.Sleep(10 * 1000);
try
{
MainController.Start(false);
MainController.Start(true);
logger.Info("controller started");
}
catch (Exception ex)


+ 0
- 36
shadowsocks-csharp/Util/Util.cs View File

@@ -95,42 +95,6 @@ namespace Shadowsocks.Util
return Path.Combine(GetTempPath(), filename);
}
public static void ReleaseMemory(bool removePages)
{
// release any unused pages
// making the numbers look good in task manager
// this is totally nonsense in programming
// but good for those users who care
// making them happier with their everyday life
// which is part of user experience
GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
if (removePages)
{
// as some users have pointed out
// removing pages from working set will cause some IO
// which lowered user experience for another group of users
//
// so we do 2 more things here to satisfy them:
// 1. only remove pages once when configuration is changed
// 2. add more comments here to tell users that calling
// this function will not be more frequent than
// IM apps writing chat logs, or web browsers writing cache files
// if they're so concerned about their disk, they should
// uninstall all IM apps and web browsers
//
// please open an issue if you're worried about anything else in your computer
// no matter it's GPU performance, monitor contrast, audio fidelity
// or anything else in the task manager
// we'll do as much as we can to help you
//
// just kidding
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle,
(UIntPtr)0xFFFFFFFF,
(UIntPtr)0xFFFFFFFF);
}
}
public static string UnGzip(byte[] buf)
{
byte[] buffer = new byte[1024];


+ 21
- 13
shadowsocks-csharp/View/MenuViewController.cs View File

@@ -29,7 +29,6 @@ namespace Shadowsocks.View
private NotifyIcon _notifyIcon;
private Icon icon, icon_in, icon_out, icon_both, previousIcon;
private bool _isFirstRun;
private bool _isStartupChecking;
private string _urlToOpen;
@@ -50,6 +49,7 @@ namespace Shadowsocks.View
private MenuItem editGFWUserRuleItem;
private MenuItem editOnlinePACItem;
private MenuItem secureLocalPacUrlToggleItem;
private MenuItem regenerateLocalPacOnUpdateItem;
private MenuItem autoCheckUpdatesToggleItem;
private MenuItem checkPreReleaseToggleItem;
private MenuItem proxyItem;
@@ -108,9 +108,8 @@ namespace Shadowsocks.View
Configuration config = controller.GetConfigurationCopy();
if (config.isDefault)
if (config.firstRun)
{
_isFirstRun = true;
ShowConfigForm();
}
else if (config.autoCheckUpdate)
@@ -279,6 +278,7 @@ namespace Shadowsocks.View
this.updateFromGeositeItem = CreateMenuItem("Update Local PAC from Geosite", new EventHandler(this.UpdatePACFromGeositeItem_Click)),
this.editGFWUserRuleItem = CreateMenuItem("Edit User Rule for Geosite...", new EventHandler(this.EditUserRuleFileForGeositeItem_Click)),
this.secureLocalPacUrlToggleItem = CreateMenuItem("Secure Local PAC", new EventHandler(this.SecureLocalPacUrlToggleItem_Click)),
this.regenerateLocalPacOnUpdateItem = CreateMenuItem("Regenerate local PAC on version update", new EventHandler(this.RegenerateLocalPacOnUpdateItem_Click)),
CreateMenuItem("Copy Local PAC URL", new EventHandler(this.CopyLocalPacUrlItem_Click)),
this.editOnlinePACItem = CreateMenuItem("Edit Online PAC URL...", new EventHandler(this.UpdateOnlinePACURLItem_Click)),
}),
@@ -336,7 +336,7 @@ namespace Shadowsocks.View
}
}
void controller_Errored(object sender, System.IO.ErrorEventArgs e)
void controller_Errored(object sender, ErrorEventArgs e)
{
MessageBox.Show(e.GetException().ToString(), I18N.GetString("Shadowsocks Error: {0}", e.GetException().Message));
}
@@ -360,6 +360,7 @@ namespace Shadowsocks.View
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac;
localPACItem.Checked = !onlinePACItem.Checked;
secureLocalPacUrlToggleItem.Checked = config.secureLocalPac;
regenerateLocalPacOnUpdateItem.Checked = config.regeneratePacOnUpdate;
UpdatePACItemsEnabledStatus();
UpdateUpdateMenu();
}
@@ -446,15 +447,14 @@ namespace Shadowsocks.View
{
logForm.Dispose();
logForm = null;
Utils.ReleaseMemory(true);
}
void configForm_FormClosed(object sender, FormClosedEventArgs e)
{
configForm.Dispose();
configForm = null;
Utils.ReleaseMemory(true);
if (_isFirstRun)
var config = controller.GetCurrentConfiguration();
if (config.firstRun)
{
CheckUpdateForFirstRun();
ShowBalloonTip(
@@ -463,7 +463,7 @@ namespace Shadowsocks.View
ToolTipIcon.Info,
0
);
_isFirstRun = false;
config.firstRun = false;
}
}
@@ -471,21 +471,18 @@ namespace Shadowsocks.View
{
proxyForm.Dispose();
proxyForm = null;
Utils.ReleaseMemory(true);
}
void hotkeySettingsForm_FormClosed(object sender, FormClosedEventArgs e)
{
hotkeySettingsForm.Dispose();
hotkeySettingsForm = null;
Utils.ReleaseMemory(true);
}
void onlineConfigForm_FormClosed(object sender, FormClosedEventArgs e)
{
onlineConfigForm.Dispose();
onlineConfigForm = null;
Utils.ReleaseMemory(true);
}
#endregion
@@ -541,7 +538,8 @@ namespace Shadowsocks.View
private void CheckUpdateForFirstRun()
{
Configuration config = controller.GetConfigurationCopy();
if (config.isDefault) return;
if (config.firstRun)
return;
_isStartupChecking = true;
updateChecker.CheckUpdate(config, 3000);
}
@@ -689,14 +687,18 @@ namespace Shadowsocks.View
Configuration configuration = controller.GetConfigurationCopy();
foreach (var server in configuration.configs)
{
if (Configuration.ChecksServer(server))
try
{
Configuration.CheckServer(server);
MenuItem item = new MenuItem(server.ToString());
item.Tag = configuration.configs.FindIndex(s => s == server);
item.Click += AServerItem_Click;
items.Add(strategyCount + serverCount, item);
serverCount++;
}
catch
{
}
}
foreach (MenuItem item in items)
@@ -901,6 +903,12 @@ namespace Shadowsocks.View
controller.ToggleSecureLocalPac(!configuration.secureLocalPac);
}
private void RegenerateLocalPacOnUpdateItem_Click(object sender, EventArgs e)
{
var config = controller.GetConfigurationCopy();
controller.ToggleRegeneratePacOnUpdate(!config.regeneratePacOnUpdate);
}
private void CopyLocalPacUrlItem_Click(object sender, EventArgs e)
{
controller.CopyPacUrl();


+ 1
- 1
shadowsocks-csharp/ViewModels/ServerSharingViewModel.cs View File

@@ -16,7 +16,7 @@ namespace Shadowsocks.ViewModels
/// </summary>
public ServerSharingViewModel()
{
_config = Configuration.Load();
_config = Program.MainController.GetCurrentConfiguration();
_servers = _config.configs;
_selectedServer = _servers.First();
//_selectedServerUrlImage = new BitmapImage();


Loading…
Cancel
Save