Browse Source

Refine PAC server

- Split the file operations to new PACDaemon
- More precise matching logic for handling PAC content http request
tags/4.1.8.0
celeron533 5 years ago
parent
commit
7030c380a1
5 changed files with 215 additions and 141 deletions
  1. +7
    -7
      shadowsocks-csharp/Controller/Service/GfwListUpdater.cs
  2. +135
    -0
      shadowsocks-csharp/Controller/Service/PACDaemon.cs
  3. +56
    -126
      shadowsocks-csharp/Controller/Service/PACServer.cs
  4. +16
    -8
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  5. +1
    -0
      shadowsocks-csharp/shadowsocks-csharp.csproj

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

@@ -48,24 +48,24 @@ namespace Shadowsocks.Controller
public static bool MergeAndWritePACFile(string gfwListResult) public static bool MergeAndWritePACFile(string gfwListResult)
{ {
string abpContent = MergePACFile(gfwListResult); string abpContent = MergePACFile(gfwListResult);
if (File.Exists(PACServer.PAC_FILE))
if (File.Exists(PACDaemon.PAC_FILE))
{ {
string original = FileManager.NonExclusiveReadAllText(PACServer.PAC_FILE, Encoding.UTF8);
string original = FileManager.NonExclusiveReadAllText(PACDaemon.PAC_FILE, Encoding.UTF8);
if (original == abpContent) if (original == abpContent)
{ {
return false; return false;
} }
} }
File.WriteAllText(PACServer.PAC_FILE, abpContent, Encoding.UTF8);
File.WriteAllText(PACDaemon.PAC_FILE, abpContent, Encoding.UTF8);
return true; return true;
} }
private static string MergePACFile(string gfwListResult) private static string MergePACFile(string gfwListResult)
{ {
string abpContent; string abpContent;
if (File.Exists(PACServer.USER_ABP_FILE))
if (File.Exists(PACDaemon.USER_ABP_FILE))
{ {
abpContent = FileManager.NonExclusiveReadAllText(PACServer.USER_ABP_FILE, Encoding.UTF8);
abpContent = FileManager.NonExclusiveReadAllText(PACDaemon.USER_ABP_FILE, Encoding.UTF8);
} }
else else
{ {
@@ -73,9 +73,9 @@ namespace Shadowsocks.Controller
} }
List<string> userruleLines = new List<string>(); List<string> userruleLines = new List<string>();
if (File.Exists(PACServer.USER_RULE_FILE))
if (File.Exists(PACDaemon.USER_RULE_FILE))
{ {
string userrulesString = FileManager.NonExclusiveReadAllText(PACServer.USER_RULE_FILE, Encoding.UTF8);
string userrulesString = FileManager.NonExclusiveReadAllText(PACDaemon.USER_RULE_FILE, Encoding.UTF8);
userruleLines = ParseToValidList(userrulesString); userruleLines = ParseToValidList(userrulesString);
} }


+ 135
- 0
shadowsocks-csharp/Controller/Service/PACDaemon.cs View File

@@ -0,0 +1,135 @@
using Shadowsocks.Properties;
using Shadowsocks.Util;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Shadowsocks.Controller
{
/// <summary>
/// Processing the PAC file content
/// </summary>
public class PACDaemon
{
public const string PAC_FILE = "pac.txt";
public const string USER_RULE_FILE = "user-rule.txt";
public const string USER_ABP_FILE = "abp.txt";
FileSystemWatcher PACFileWatcher;
FileSystemWatcher UserRuleFileWatcher;
public event EventHandler PACFileChanged;
public event EventHandler UserRuleFileChanged;
public PACDaemon()
{
this.WatchPacFile();
this.WatchUserRuleFile();
}
public string TouchPACFile()
{
if (File.Exists(PAC_FILE))
{
return PAC_FILE;
}
else
{
FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt);
return PAC_FILE;
}
}
internal string TouchUserRuleFile()
{
if (File.Exists(USER_RULE_FILE))
{
return USER_RULE_FILE;
}
else
{
File.WriteAllText(USER_RULE_FILE, Resources.user_rule);
return USER_RULE_FILE;
}
}
internal string GetPACContent()
{
if (File.Exists(PAC_FILE))
{
return File.ReadAllText(PAC_FILE, Encoding.UTF8);
}
else
{
return Utils.UnGzip(Resources.proxy_pac_txt);
}
}
private void WatchPacFile()
{
PACFileWatcher?.Dispose();
PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory());
PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
PACFileWatcher.Filter = PAC_FILE;
PACFileWatcher.Changed += PACFileWatcher_Changed;
PACFileWatcher.Created += PACFileWatcher_Changed;
PACFileWatcher.Deleted += PACFileWatcher_Changed;
PACFileWatcher.Renamed += PACFileWatcher_Changed;
PACFileWatcher.EnableRaisingEvents = true;
}
private void WatchUserRuleFile()
{
UserRuleFileWatcher?.Dispose();
UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory());
UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
UserRuleFileWatcher.Filter = USER_RULE_FILE;
UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.EnableRaisingEvents = true;
}
#region FileSystemWatcher.OnChanged()
// FileSystemWatcher Changed event is raised twice
// http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice
// Add a short delay to avoid raise event twice in a short period
private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (PACFileChanged != null)
{
Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
Task.Factory.StartNew(() =>
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
System.Threading.Thread.Sleep(10);
PACFileChanged(this, new EventArgs());
((FileSystemWatcher)sender).EnableRaisingEvents = true;
});
}
}
private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (UserRuleFileChanged != null)
{
Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
Task.Factory.StartNew(() =>
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
System.Threading.Thread.Sleep(10);
UserRuleFileChanged(this, new EventArgs());
((FileSystemWatcher)sender).EnableRaisingEvents = true;
});
}
}
#endregion
}
}

+ 56
- 126
shadowsocks-csharp/Controller/Service/PACServer.cs View File

@@ -15,28 +15,21 @@ namespace Shadowsocks.Controller
{ {
public class PACServer : Listener.Service public class PACServer : Listener.Service
{ {
public const string PAC_FILE = "pac.txt";
public const string USER_RULE_FILE = "user-rule.txt";
public const string USER_ABP_FILE = "abp.txt";
public const string RESOURCE_NAME = "pac";
private string PacSecret { get; set; } = ""; private string PacSecret { get; set; } = "";
public string PacUrl { get; private set; } = ""; public string PacUrl { get; private set; } = "";
FileSystemWatcher PACFileWatcher;
FileSystemWatcher UserRuleFileWatcher;
private Configuration _config; private Configuration _config;
private PACDaemon _pacDaemon;
public event EventHandler PACFileChanged;
public event EventHandler UserRuleFileChanged;
public PACServer()
public PACServer(PACDaemon pacDaemon)
{ {
this.WatchPacFile();
this.WatchUserRuleFile();
_pacDaemon = pacDaemon;
} }
public void UpdateConfiguration(Configuration config)
public void UpdatePACURL(Configuration config)
{ {
this._config = config; this._config = config;
@@ -51,7 +44,7 @@ namespace Shadowsocks.Controller
PacSecret = ""; PacSecret = "";
} }
PacUrl = $"http://{config.localHost}:{config.localPort}/pac?t={GetTimestamp(DateTime.Now)}{PacSecret}";
PacUrl = $"http://{config.localHost}:{config.localPort}/{RESOURCE_NAME}?t={GetTimestamp(DateTime.Now)}{PacSecret}";
} }
@@ -66,15 +59,61 @@ namespace Shadowsocks.Controller
{ {
return false; return false;
} }
try try
{ {
/*
* RFC 7230
*
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
*/
string request = Encoding.UTF8.GetString(firstPacket, 0, length); string request = Encoding.UTF8.GetString(firstPacket, 0, length);
string[] lines = request.Split('\r', '\n'); string[] lines = request.Split('\r', '\n');
bool hostMatch = false, pathMatch = false, useSocks = false; bool hostMatch = false, pathMatch = false, useSocks = false;
bool secretMatch = PacSecret.IsNullOrEmpty(); bool secretMatch = PacSecret.IsNullOrEmpty();
foreach (string line in lines)
if (lines.Length < 2) // need at lease RequestLine + Host
{ {
string[] kv = line.Split(new char[] { ':' }, 2);
return false;
}
// parse request line
string requestLine = lines[0];
// GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1
string[] requestItems = requestLine.Split(' ');
if (requestItems.Length == 3 && requestItems[0] == "GET")
{
int index = requestItems[1].IndexOf('?');
if (index < 0)
{
index = requestItems[1].Length;
}
string resourceString = requestItems[1].Substring(0, index).Remove(0, 1);
if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase))
{
pathMatch = true;
if (!secretMatch)
{
string queryString = requestItems[1].Substring(index);
if (queryString.Contains(PacSecret))
{
secretMatch = true;
}
}
}
}
// parse request header
for (int i = 1; i < lines.Length; i++)
{
if (string.IsNullOrEmpty(lines[i]))
continue;
string[] kv = lines[i].Split(new char[] { ':' }, 2);
if (kv.Length == 2) if (kv.Length == 2)
{ {
if (kv[0] == "Host") if (kv[0] == "Host")
@@ -93,21 +132,8 @@ namespace Shadowsocks.Controller
// } // }
//} //}
} }
else if (kv.Length == 1)
{
if (line.IndexOf("pac", StringComparison.Ordinal) >= 0)
{
pathMatch = true;
}
if (!secretMatch)
{
if (line.IndexOf(PacSecret, StringComparison.Ordinal) >= 0)
{
secretMatch = true;
}
}
}
} }
if (hostMatch && pathMatch) if (hostMatch && pathMatch)
{ {
if (!secretMatch) if (!secretMatch)
@@ -128,43 +154,7 @@ namespace Shadowsocks.Controller
} }
} }
public string TouchPACFile()
{
if (File.Exists(PAC_FILE))
{
return PAC_FILE;
}
else
{
FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt);
return PAC_FILE;
}
}
internal string TouchUserRuleFile()
{
if (File.Exists(USER_RULE_FILE))
{
return USER_RULE_FILE;
}
else
{
File.WriteAllText(USER_RULE_FILE, Resources.user_rule);
return USER_RULE_FILE;
}
}
private string GetPACContent()
{
if (File.Exists(PAC_FILE))
{
return File.ReadAllText(PAC_FILE, Encoding.UTF8);
}
else
{
return Utils.UnGzip(Resources.proxy_pac_txt);
}
}
public void SendResponse(Socket socket, bool useSocks) public void SendResponse(Socket socket, bool useSocks)
{ {
@@ -174,7 +164,7 @@ namespace Shadowsocks.Controller
string proxy = GetPACAddress(localEndPoint, useSocks); string proxy = GetPACAddress(localEndPoint, useSocks);
string pacContent = GetPACContent().Replace("__PROXY__", proxy);
string pacContent = _pacDaemon.GetPACContent().Replace("__PROXY__", proxy);
string responseHead = String.Format(@"HTTP/1.1 200 OK string responseHead = String.Format(@"HTTP/1.1 200 OK
Server: Shadowsocks Server: Shadowsocks
@@ -205,66 +195,6 @@ Connection: Close
{ } { }
} }
private void WatchPacFile()
{
PACFileWatcher?.Dispose();
PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory());
PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
PACFileWatcher.Filter = PAC_FILE;
PACFileWatcher.Changed += PACFileWatcher_Changed;
PACFileWatcher.Created += PACFileWatcher_Changed;
PACFileWatcher.Deleted += PACFileWatcher_Changed;
PACFileWatcher.Renamed += PACFileWatcher_Changed;
PACFileWatcher.EnableRaisingEvents = true;
}
private void WatchUserRuleFile()
{
UserRuleFileWatcher?.Dispose();
UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory());
UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
UserRuleFileWatcher.Filter = USER_RULE_FILE;
UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.EnableRaisingEvents = true;
}
#region FileSystemWatcher.OnChanged()
// FileSystemWatcher Changed event is raised twice
// http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice
// Add a short delay to avoid raise event twice in a short period
private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (PACFileChanged != null)
{
Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
Task.Factory.StartNew(() =>
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
System.Threading.Thread.Sleep(10);
PACFileChanged(this, new EventArgs());
((FileSystemWatcher)sender).EnableRaisingEvents = true;
});
}
}
private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (UserRuleFileChanged != null)
{
Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
Task.Factory.StartNew(() =>
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
System.Threading.Thread.Sleep(10);
UserRuleFileChanged(this, new EventArgs());
((FileSystemWatcher)sender).EnableRaisingEvents = true;
});
}
}
#endregion
private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks)
{ {


+ 16
- 8
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -28,6 +28,7 @@ namespace Shadowsocks.Controller
private Thread _trafficThread; private Thread _trafficThread;
private Listener _listener; private Listener _listener;
private PACDaemon _pacDaemon;
private PACServer _pacServer; private PACServer _pacServer;
private Configuration _config; private Configuration _config;
private StrategyManager _strategyManager; private StrategyManager _strategyManager;
@@ -299,14 +300,14 @@ namespace Shadowsocks.Controller
public void TouchPACFile() public void TouchPACFile()
{ {
string pacFilename = _pacServer.TouchPACFile();
string pacFilename = _pacDaemon.TouchPACFile();
PACFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = pacFilename }); PACFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = pacFilename });
} }
public void TouchUserRuleFile() public void TouchUserRuleFile()
{ {
string userRuleFilename = _pacServer.TouchUserRuleFile();
string userRuleFilename = _pacDaemon.TouchUserRuleFile();
UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename }); UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename });
} }
@@ -468,13 +469,20 @@ namespace Shadowsocks.Controller
{ {
privoxyRunner = new PrivoxyRunner(); privoxyRunner = new PrivoxyRunner();
} }
if (_pacDaemon == null)
{
_pacDaemon = new PACDaemon();
_pacDaemon.PACFileChanged += PacDaemon_PACFileChanged;
_pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged;
}
if (_pacServer == null) if (_pacServer == null)
{ {
_pacServer = new PACServer();
_pacServer.PACFileChanged += PacServer_PACFileChanged;
_pacServer.UserRuleFileChanged += PacServer_UserRuleFileChanged;
_pacServer = new PACServer(_pacDaemon);
} }
_pacServer.UpdateConfiguration(_config);
_pacServer.UpdatePACURL(_config);
if (gfwListUpdater == null) if (gfwListUpdater == null)
{ {
gfwListUpdater = new GFWListUpdater(); gfwListUpdater = new GFWListUpdater();
@@ -561,7 +569,7 @@ namespace Shadowsocks.Controller
SystemProxy.Update(_config, false, _pacServer); SystemProxy.Update(_config, false, _pacServer);
} }
private void PacServer_PACFileChanged(object sender, EventArgs e)
private void PacDaemon_PACFileChanged(object sender, EventArgs e)
{ {
UpdateSystemProxy(); UpdateSystemProxy();
} }
@@ -577,7 +585,7 @@ namespace Shadowsocks.Controller
} }
private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' }; private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' };
private void PacServer_UserRuleFileChanged(object sender, EventArgs e)
private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e)
{ {
if (!File.Exists(Utils.GetTempPath("gfwlist.txt"))) if (!File.Exists(Utils.GetTempPath("gfwlist.txt")))
{ {


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

@@ -103,6 +103,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Controller\HotkeyReg.cs" /> <Compile Include="Controller\HotkeyReg.cs" />
<Compile Include="Controller\Service\PACDaemon.cs" />
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" /> <Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" />
<Compile Include="Encryption\AEAD\AEADEncryptor.cs" /> <Compile Include="Encryption\AEAD\AEADEncryptor.cs" />
<Compile Include="Encryption\AEAD\AEADMbedTLSEncryptor.cs" /> <Compile Include="Encryption\AEAD\AEADMbedTLSEncryptor.cs" />


Loading…
Cancel
Save