drop sysproxy.exe in net-core versionpull/2895/head
@@ -10,17 +10,12 @@ namespace Shadowsocks.Controller | |||
{ | |||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
private static string GetTimestamp(DateTime value) | |||
{ | |||
return value.ToString("yyyyMMddHHmmssfff"); | |||
} | |||
public static void Update(Configuration config, bool forceDisable, PACServer pacSrv, bool noRetry = false) | |||
{ | |||
bool global = config.global; | |||
bool enabled = config.enabled; | |||
if (forceDisable) | |||
if (forceDisable || WinINet.operational) | |||
{ | |||
enabled = false; | |||
} | |||
@@ -31,7 +26,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
if (global) | |||
{ | |||
Sysproxy.SetIEProxy(true, true, "localhost:" + config.localPort.ToString(), null); | |||
WinINet.ProxyGlobal("localhost:" + config.localPort.ToString(), "<local>"); | |||
} | |||
else | |||
{ | |||
@@ -45,12 +40,12 @@ namespace Shadowsocks.Controller | |||
pacUrl = pacSrv.PacUrl; | |||
} | |||
Sysproxy.SetIEProxy(true, false, null, pacUrl); | |||
WinINet.ProxyPAC(pacUrl); | |||
} | |||
} | |||
else | |||
{ | |||
Sysproxy.SetIEProxy(false, false, null, null); | |||
WinINet.Restore(); | |||
} | |||
} | |||
catch (ProxyException ex) | |||
@@ -61,7 +56,7 @@ namespace Shadowsocks.Controller | |||
var ret = MessageBox.Show(I18N.GetString("Error occured when process proxy setting, do you want reset current setting and retry?"), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo, MessageBoxIcon.Warning); | |||
if (ret == DialogResult.Yes) | |||
{ | |||
Sysproxy.ResetIEProxy(); | |||
WinINet.Reset(); | |||
Update(config, forceDisable, pacSrv, true); | |||
} | |||
} | |||
@@ -151,12 +151,6 @@ | |||
<data name="ssw128" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Resources\ssw128.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | |||
</data> | |||
<data name="sysproxy64_exe" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\sysproxy64.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="sysproxy_exe" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\sysproxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="user_rule" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\user-rule.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | |||
</data> |
@@ -0,0 +1,92 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Runtime.InteropServices; | |||
using System.Text; | |||
namespace Shadowsocks.Util.SystemProxy | |||
{ | |||
enum RasFieldSizeConst | |||
{ | |||
MaxEntryName = 256, | |||
MaxPath = 260, | |||
} | |||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] | |||
struct RasEntryName | |||
{ | |||
public int dwSize; | |||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS.MaxEntryName + 1)] | |||
public string szEntryName; | |||
public int dwFlags; | |||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS.MaxPath + 1)] | |||
public string szPhonebookPath; | |||
} | |||
class RAS | |||
{ | |||
public const int MaxEntryName = 256; | |||
public const int MaxPath = 260; | |||
const int ESuccess = 0; | |||
const int RasBase = 600; | |||
const int EBufferTooSmall = 603; | |||
[DllImport("rasapi32.dll", CharSet = CharSet.Auto)] | |||
private static extern uint RasEnumEntries( | |||
// reserved, must be NULL | |||
string reserved, | |||
// pointer to full path and file name of phone-book file | |||
string lpszPhonebook, | |||
// buffer to receive phone-book entries | |||
[In, Out] RasEntryName[] lprasentryname, | |||
// size in bytes of buffer | |||
ref int lpcb, | |||
// number of entries written to buffer | |||
out int lpcEntries | |||
); | |||
public static string[] GetAllConnections() | |||
{ | |||
int lpNames = 0; | |||
int entryNameSize = 0; | |||
int lpSize = 0; | |||
uint retval = ESuccess; | |||
RasEntryName[] names = null; | |||
entryNameSize = Marshal.SizeOf(typeof(RasEntryName)); | |||
// Windows Vista or later: To determine the required buffer size, call RasEnumEntries | |||
// with lprasentryname set to NULL. The variable pointed to by lpcb should be set to zero. | |||
// The function will return the required buffer size in lpcb and an error code of ERROR_BUFFER_TOO_SMALL. | |||
retval = RasEnumEntries(null, null, null, ref lpSize, out lpNames); | |||
if (retval == EBufferTooSmall) | |||
{ | |||
names = new RasEntryName[lpNames]; | |||
for (int i = 0; i < names.Length; i++) | |||
{ | |||
names[i].dwSize = entryNameSize; | |||
} | |||
retval = RasEnumEntries(null, null, names, ref lpSize, out lpNames); | |||
} | |||
if (retval == ESuccess) | |||
{ | |||
if (lpNames == 0) | |||
{ | |||
// no entries found. | |||
return Array.Empty<string>(); | |||
} | |||
return names.Select(n => n.szEntryName).ToArray(); | |||
} | |||
else | |||
{ | |||
throw new Exception(); | |||
} | |||
} | |||
} | |||
} |
@@ -1,291 +0,0 @@ | |||
using Newtonsoft.Json; | |||
using NLog; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading; | |||
namespace Shadowsocks.Util.SystemProxy | |||
{ | |||
public static class Sysproxy | |||
{ | |||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
private const string _userWininetConfigFile = "user-wininet.json"; | |||
private readonly static string[] _lanIP = { | |||
"<local>", | |||
"localhost", | |||
"127.*", | |||
"10.*", | |||
"172.16.*", | |||
"172.17.*", | |||
"172.18.*", | |||
"172.19.*", | |||
"172.20.*", | |||
"172.21.*", | |||
"172.22.*", | |||
"172.23.*", | |||
"172.24.*", | |||
"172.25.*", | |||
"172.26.*", | |||
"172.27.*", | |||
"172.28.*", | |||
"172.29.*", | |||
"172.30.*", | |||
"172.31.*", | |||
"192.168.*" | |||
}; | |||
private static string _queryStr; | |||
// In general, this won't change | |||
// format: | |||
// <flags><CR-LF> | |||
// <proxy-server><CR-LF> | |||
// <bypass-list><CR-LF> | |||
// <pac-url> | |||
private static SysproxyConfig _userSettings = null; | |||
enum RET_ERRORS : int | |||
{ | |||
RET_NO_ERROR = 0, | |||
INVALID_FORMAT = 1, | |||
NO_PERMISSION = 2, | |||
SYSCALL_FAILED = 3, | |||
NO_MEMORY = 4, | |||
INVAILD_OPTION_COUNT = 5, | |||
}; | |||
static Sysproxy() | |||
{ | |||
try | |||
{ | |||
FileManager.UncompressFile(Utils.GetTempPath("sysproxy.exe"), | |||
Environment.Is64BitOperatingSystem ? Resources.sysproxy64_exe : Resources.sysproxy_exe); | |||
} | |||
catch (IOException e) | |||
{ | |||
logger.LogUsefulException(e); | |||
} | |||
} | |||
public static void SetIEProxy(bool enable, bool global, string proxyServer, string pacURL) | |||
{ | |||
Read(); | |||
if (!_userSettings.UserSettingsRecorded) | |||
{ | |||
// record user settings | |||
ExecSysproxy("query"); | |||
ParseQueryStr(_queryStr); | |||
} | |||
string arguments; | |||
if (enable) | |||
{ | |||
string customBypassString = _userSettings.BypassList ?? ""; | |||
List<string> customBypassList = new List<string>(customBypassString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); | |||
customBypassList.AddRange(_lanIP); | |||
string[] realBypassList = customBypassList.Distinct().ToArray(); | |||
string realBypassString = string.Join(";", realBypassList); | |||
arguments = global | |||
? $"global {proxyServer} {realBypassString}" | |||
: $"pac {pacURL}"; | |||
} | |||
else | |||
{ | |||
// restore user settings | |||
var flags = _userSettings.Flags; | |||
var proxy_server = _userSettings.ProxyServer ?? "-"; | |||
var bypass_list = _userSettings.BypassList ?? "-"; | |||
var pac_url = _userSettings.PacUrl ?? "-"; | |||
arguments = $"set {flags} {proxy_server} {bypass_list} {pac_url}"; | |||
// have to get new settings | |||
_userSettings.UserSettingsRecorded = false; | |||
} | |||
Save(); | |||
ExecSysproxy(arguments); | |||
} | |||
// set system proxy to 1 (null) (null) (null) | |||
public static bool ResetIEProxy() | |||
{ | |||
try | |||
{ | |||
// clear user-wininet.json | |||
_userSettings = new SysproxyConfig(); | |||
Save(); | |||
// clear system setting | |||
ExecSysproxy("set 1 - - -"); | |||
} | |||
catch (Exception) | |||
{ | |||
return false; | |||
} | |||
return true; | |||
} | |||
private static void ExecSysproxy(string arguments) | |||
{ | |||
// using event to avoid hanging when redirect standard output/error | |||
// ref: https://stackoverflow.com/questions/139593/processstartinfo-hanging-on-waitforexit-why | |||
// and http://blog.csdn.net/zhangweixing0/article/details/7356841 | |||
using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false)) | |||
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false)) | |||
{ | |||
using (var process = new Process()) | |||
{ | |||
// Configure the process using the StartInfo properties. | |||
process.StartInfo.FileName = Utils.GetTempPath("sysproxy.exe"); | |||
process.StartInfo.Arguments = arguments; | |||
process.StartInfo.WorkingDirectory = Utils.GetTempPath(); | |||
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; | |||
process.StartInfo.UseShellExecute = false; | |||
process.StartInfo.RedirectStandardError = true; | |||
process.StartInfo.RedirectStandardOutput = true; | |||
// Need to provide encoding info, or output/error strings we got will be wrong. | |||
process.StartInfo.StandardOutputEncoding = Encoding.Unicode; | |||
process.StartInfo.StandardErrorEncoding = Encoding.Unicode; | |||
process.StartInfo.CreateNoWindow = true; | |||
StringBuilder output = new StringBuilder(); | |||
StringBuilder error = new StringBuilder(); | |||
process.OutputDataReceived += (sender, e) => | |||
{ | |||
if (e.Data == null) | |||
{ | |||
outputWaitHandle.Set(); | |||
} | |||
else | |||
{ | |||
output.AppendLine(e.Data); | |||
} | |||
}; | |||
process.ErrorDataReceived += (sender, e) => | |||
{ | |||
if (e.Data == null) | |||
{ | |||
errorWaitHandle.Set(); | |||
} | |||
else | |||
{ | |||
error.AppendLine(e.Data); | |||
} | |||
}; | |||
try | |||
{ | |||
process.Start(); | |||
process.BeginErrorReadLine(); | |||
process.BeginOutputReadLine(); | |||
process.WaitForExit(); | |||
} | |||
catch (System.ComponentModel.Win32Exception e) | |||
{ | |||
// log the arguments | |||
throw new ProxyException(ProxyExceptionType.FailToRun, process.StartInfo.Arguments, e); | |||
} | |||
var stderr = error.ToString(); | |||
var stdout = output.ToString(); | |||
var exitCode = process.ExitCode; | |||
if (exitCode != (int)RET_ERRORS.RET_NO_ERROR) | |||
{ | |||
throw new ProxyException(ProxyExceptionType.SysproxyExitError, stderr); | |||
} | |||
if (arguments == "query") | |||
{ | |||
if (stdout.IsNullOrWhiteSpace() || stdout.IsNullOrEmpty()) | |||
{ | |||
// we cannot get user settings | |||
throw new ProxyException(ProxyExceptionType.QueryReturnEmpty); | |||
} | |||
_queryStr = stdout; | |||
} | |||
} | |||
} | |||
} | |||
private static void Save() | |||
{ | |||
try | |||
{ | |||
using (StreamWriter sw = new StreamWriter(File.Open(Utils.GetTempPath(_userWininetConfigFile), FileMode.Create))) | |||
{ | |||
string jsonString = JsonConvert.SerializeObject(_userSettings, Formatting.Indented); | |||
sw.Write(jsonString); | |||
sw.Flush(); | |||
} | |||
} | |||
catch (IOException e) | |||
{ | |||
logger.LogUsefulException(e); | |||
} | |||
} | |||
private static void Read() | |||
{ | |||
try | |||
{ | |||
string configContent = File.ReadAllText(Utils.GetTempPath(_userWininetConfigFile)); | |||
_userSettings = JsonConvert.DeserializeObject<SysproxyConfig>(configContent); | |||
} | |||
catch (Exception) | |||
{ | |||
// Suppress all exceptions. finally block will initialize new user config settings. | |||
} | |||
finally | |||
{ | |||
if (_userSettings == null) _userSettings = new SysproxyConfig(); | |||
} | |||
} | |||
private static void ParseQueryStr(string str) | |||
{ | |||
string[] userSettingsArr = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); | |||
// sometimes sysproxy output in utf16le instead of ascii | |||
// manually translate it | |||
if (userSettingsArr.Length != 4) | |||
{ | |||
byte[] strByte = Encoding.ASCII.GetBytes(str); | |||
str = Encoding.Unicode.GetString(strByte); | |||
userSettingsArr = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); | |||
// still fail, throw exception with string hexdump | |||
if (userSettingsArr.Length != 4) | |||
{ | |||
throw new ProxyException(ProxyExceptionType.QueryReturnMalformed, BitConverter.ToString(strByte)); | |||
} | |||
} | |||
_userSettings.Flags = userSettingsArr[0]; | |||
// handle output from WinINET | |||
if (userSettingsArr[1] == "(null)") _userSettings.ProxyServer = null; | |||
else _userSettings.ProxyServer = userSettingsArr[1]; | |||
if (userSettingsArr[2] == "(null)") _userSettings.BypassList = null; | |||
else _userSettings.BypassList = userSettingsArr[2]; | |||
if (userSettingsArr[3] == "(null)") _userSettings.PacUrl = null; | |||
else _userSettings.PacUrl = userSettingsArr[3]; | |||
_userSettings.UserSettingsRecorded = true; | |||
} | |||
} | |||
} |
@@ -0,0 +1,447 @@ | |||
using Newtonsoft.Json; | |||
using NLog; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.ComponentModel; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Runtime.InteropServices; | |||
namespace Shadowsocks.Util.SystemProxy | |||
{ | |||
public enum InternetOptions | |||
{ | |||
Refresh = 37, | |||
SettingsChanged = 39, | |||
PerConnectionOption = 75, | |||
ProxySettingChanged = 95, | |||
} | |||
public enum InternetPerConnectionOptionEnum | |||
{ | |||
Flags = 1, | |||
ProxyServer = 2, | |||
ProxyBypass = 3, | |||
AutoConfigUrl = 4, | |||
AutoDiscovery = 5, | |||
AutoConfigSecondaryUrl = 6, | |||
AutoConfigReloadDelay = 7, | |||
AutoConfigLastDetectTime = 8, | |||
AutoConfigLastDetectUrl = 9, | |||
FlagsUI = 10, | |||
} | |||
[Flags] | |||
public enum InternetPerConnectionFlags | |||
{ | |||
Direct = 0x01, | |||
Proxy = 0x02, | |||
AutoProxyUrl = 0x04, | |||
AutoDetect = 0x08, | |||
} | |||
[StructLayout(LayoutKind.Explicit)] | |||
public struct InternetPerConnectionOptionUnion : IDisposable | |||
{ | |||
[FieldOffset(0)] | |||
public int dwValue; | |||
[FieldOffset(0)] | |||
public IntPtr pszValue; | |||
[FieldOffset(0)] | |||
public System.Runtime.InteropServices.ComTypes.FILETIME ftValue; | |||
public void Dispose() | |||
{ | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
private void Dispose(bool disposing) | |||
{ | |||
if (disposing) | |||
{ | |||
if (pszValue != IntPtr.Zero) | |||
{ | |||
Marshal.FreeHGlobal(pszValue); | |||
pszValue = IntPtr.Zero; | |||
} | |||
} | |||
} | |||
} | |||
[StructLayout(LayoutKind.Sequential)] | |||
public struct InternetPerConnectionOption | |||
{ | |||
public int dwOption; | |||
public InternetPerConnectionOptionUnion Value; | |||
} | |||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] | |||
public struct InternetPerConnectionOptionList : IDisposable | |||
{ | |||
public int Size; | |||
// The connection to be set. NULL means LAN. | |||
public IntPtr Connection; | |||
public int OptionCount; | |||
public int OptionError; | |||
// List of INTERNET_PER_CONN_OPTIONs. | |||
public IntPtr pOptions; | |||
public void Dispose() | |||
{ | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
private void Dispose(bool disposing) | |||
{ | |||
if (disposing) | |||
{ | |||
if (Connection != IntPtr.Zero) | |||
{ | |||
Marshal.FreeHGlobal(Connection); | |||
Connection = IntPtr.Zero; | |||
} | |||
if (pOptions != IntPtr.Zero) | |||
{ | |||
Marshal.FreeHGlobal(pOptions); | |||
pOptions = IntPtr.Zero; | |||
} | |||
} | |||
} | |||
} | |||
public class WinINetSetting | |||
{ | |||
public InternetPerConnectionFlags Flags = InternetPerConnectionFlags.Direct; | |||
public string ProxyServer; | |||
public string ProxyBypass; | |||
public string AutoConfigUrl; | |||
} | |||
public class WinINet | |||
{ | |||
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); | |||
private const string SettingFile = "wininet-setting.json"; | |||
private static WinINetSetting initialSetting; | |||
public static bool operational { get; private set; } = true; | |||
static WinINet() | |||
{ | |||
try | |||
{ | |||
Query(); | |||
} | |||
catch (DllNotFoundException) | |||
{ | |||
operational = false; | |||
// Not on windows | |||
logger.Info("You are not running on Windows platform, system proxy will disable"); | |||
} | |||
catch (Win32Exception we) | |||
{ | |||
if (we.NativeErrorCode == 12178) | |||
{ | |||
logger.Warn("WPAD service is not running, system proxy will disable"); | |||
// WPAD not running | |||
} | |||
else | |||
{ | |||
throw we; | |||
} | |||
} | |||
Load(); | |||
} | |||
public static void ProxyGlobal(string server, string bypass) | |||
{ | |||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||
{ | |||
GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.Proxy|InternetPerConnectionFlags.Direct), | |||
GetOption(InternetPerConnectionOptionEnum.ProxyServer,server), | |||
GetOption(InternetPerConnectionOptionEnum.ProxyBypass,bypass), | |||
}; | |||
Exec(options); | |||
} | |||
public static void ProxyPAC(string url) | |||
{ | |||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||
{ | |||
GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.AutoProxyUrl|InternetPerConnectionFlags.Direct), | |||
GetOption(InternetPerConnectionOptionEnum.AutoConfigUrl,url), | |||
}; | |||
Exec(options); | |||
} | |||
public static void Direct() | |||
{ | |||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||
{ | |||
GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.Direct), | |||
}; | |||
Exec(options); | |||
} | |||
private static void Load() | |||
{ | |||
try | |||
{ | |||
string configContent = File.ReadAllText(Utils.GetTempPath(SettingFile)); | |||
initialSetting = JsonConvert.DeserializeObject<WinINetSetting>(configContent); | |||
} | |||
catch (Exception) | |||
{ | |||
// Suppress all exceptions. finally block will initialize new user config settings. | |||
} | |||
finally | |||
{ | |||
initialSetting ??= new WinINetSetting(); | |||
} | |||
} | |||
private static void Save() | |||
{ | |||
try | |||
{ | |||
using (StreamWriter sw = new StreamWriter(File.Open(Utils.GetTempPath(SettingFile), FileMode.Create))) | |||
{ | |||
string jsonString = JsonConvert.SerializeObject(initialSetting, Formatting.Indented); | |||
sw.Write(jsonString); | |||
sw.Flush(); | |||
} | |||
} | |||
catch (IOException) | |||
{ | |||
// logger.LogUsefulException(e); | |||
} | |||
} | |||
private static void Record() | |||
{ | |||
initialSetting ??= Query(); | |||
} | |||
public static void Restore() | |||
{ | |||
Set(initialSetting); | |||
} | |||
public static void Set(WinINetSetting setting) | |||
{ | |||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||
{ | |||
GetOption(InternetPerConnectionOptionEnum.Flags,setting.Flags), | |||
GetOption(InternetPerConnectionOptionEnum.ProxyServer,setting.ProxyServer), | |||
GetOption(InternetPerConnectionOptionEnum.ProxyBypass,setting.ProxyBypass), | |||
GetOption(InternetPerConnectionOptionEnum.AutoConfigUrl,setting.AutoConfigUrl), | |||
}; | |||
Exec(options); | |||
} | |||
public static void Reset() | |||
{ | |||
Set(new WinINetSetting | |||
{ | |||
Flags = InternetPerConnectionFlags.Direct, | |||
ProxyServer = "", | |||
ProxyBypass = "", | |||
AutoConfigUrl = "", | |||
}); | |||
} | |||
public static WinINetSetting Query() | |||
{ | |||
if (!operational) | |||
{ | |||
return new WinINetSetting(); | |||
} | |||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||
{ | |||
new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.FlagsUI}, | |||
new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.ProxyServer}, | |||
new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.ProxyBypass}, | |||
new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.AutoConfigUrl}, | |||
}; | |||
(IntPtr unmanagedList, int listSize) = PrepareOptionList(options, null); | |||
bool ok = InternetQueryOption(IntPtr.Zero, (int)InternetOptions.PerConnectionOption, unmanagedList, ref listSize); | |||
if (!ok) | |||
{ | |||
throw new Win32Exception(Marshal.GetLastWin32Error()); | |||
} | |||
WinINetSetting proxy = new WinINetSetting(); | |||
InternetPerConnectionOptionList ret = Marshal.PtrToStructure<InternetPerConnectionOptionList>(unmanagedList); | |||
IntPtr p = ret.pOptions; | |||
int nOption = ret.OptionCount; | |||
List<InternetPerConnectionOption> outOptions = new List<InternetPerConnectionOption>(); | |||
for (int i = 0; i < nOption; i++) | |||
{ | |||
InternetPerConnectionOption o = Marshal.PtrToStructure<InternetPerConnectionOption>(p); | |||
outOptions.Add(o); | |||
p += Marshal.SizeOf(o); | |||
} | |||
foreach (InternetPerConnectionOption o in outOptions) | |||
{ | |||
switch ((InternetPerConnectionOptionEnum)o.dwOption) | |||
{ | |||
case InternetPerConnectionOptionEnum.FlagsUI: | |||
case InternetPerConnectionOptionEnum.Flags: | |||
proxy.Flags = (InternetPerConnectionFlags)o.Value.dwValue; | |||
break; | |||
case InternetPerConnectionOptionEnum.AutoConfigUrl: | |||
proxy.AutoConfigUrl = Marshal.PtrToStringAuto(o.Value.pszValue); | |||
break; | |||
case InternetPerConnectionOptionEnum.ProxyBypass: | |||
proxy.ProxyBypass = Marshal.PtrToStringAuto(o.Value.pszValue); | |||
break; | |||
case InternetPerConnectionOptionEnum.ProxyServer: | |||
proxy.ProxyServer = Marshal.PtrToStringAuto(o.Value.pszValue); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
return proxy; | |||
} | |||
private static InternetPerConnectionOption GetOption( | |||
InternetPerConnectionOptionEnum option, | |||
InternetPerConnectionFlags flag | |||
) | |||
{ | |||
return new InternetPerConnectionOption | |||
{ | |||
dwOption = (int)option, | |||
Value = | |||
{ | |||
dwValue = (int)flag, | |||
} | |||
}; | |||
} | |||
private static InternetPerConnectionOption GetOption( | |||
InternetPerConnectionOptionEnum option, | |||
string param | |||
) | |||
{ | |||
return new InternetPerConnectionOption | |||
{ | |||
dwOption = (int)option, | |||
Value = | |||
{ | |||
pszValue = Marshal.StringToCoTaskMemAuto(param), | |||
} | |||
}; | |||
} | |||
private static (IntPtr, int) PrepareOptionList(List<InternetPerConnectionOption> options, string connName) | |||
{ | |||
int len = options.Sum(o => Marshal.SizeOf(o)); | |||
IntPtr buf = Marshal.AllocCoTaskMem(len); | |||
IntPtr cur = buf; | |||
foreach (InternetPerConnectionOption o in options) | |||
{ | |||
Marshal.StructureToPtr(o, cur, false); | |||
cur += Marshal.SizeOf(o); | |||
} | |||
InternetPerConnectionOptionList optionList = new InternetPerConnectionOptionList | |||
{ | |||
pOptions = buf, | |||
OptionCount = options.Count, | |||
Connection = string.IsNullOrEmpty(connName) | |||
? IntPtr.Zero | |||
: Marshal.StringToHGlobalAuto(connName), | |||
OptionError = 0, | |||
}; | |||
int listSize = Marshal.SizeOf(optionList); | |||
optionList.Size = listSize; | |||
IntPtr unmanagedList = Marshal.AllocCoTaskMem(listSize); | |||
Marshal.StructureToPtr(optionList, unmanagedList, true); | |||
return (unmanagedList, listSize); | |||
} | |||
private static void ClearOptionList(IntPtr list) | |||
{ | |||
InternetPerConnectionOptionList l = Marshal.PtrToStructure<InternetPerConnectionOptionList>(list); | |||
Marshal.FreeCoTaskMem(l.pOptions); | |||
Marshal.FreeCoTaskMem(list); | |||
} | |||
private static void Exec(List<InternetPerConnectionOption> options) | |||
{ | |||
// TODO: optimize load and save | |||
Load(); | |||
Record(); | |||
Exec(options, null); | |||
foreach (string conn in RAS.GetAllConnections()) | |||
{ | |||
Exec(options, conn); | |||
} | |||
Save(); | |||
} | |||
private static void Exec(List<InternetPerConnectionOption> options, string connName) | |||
{ | |||
if (!operational) | |||
{ | |||
return; | |||
} | |||
(IntPtr unmanagedList, int listSize) = PrepareOptionList(options, connName); | |||
bool ok = InternetSetOption( | |||
IntPtr.Zero, | |||
(int)InternetOptions.PerConnectionOption, | |||
unmanagedList, | |||
listSize | |||
); | |||
if (!ok) | |||
{ | |||
throw new Win32Exception(Marshal.GetLastWin32Error()); | |||
} | |||
ClearOptionList(unmanagedList); | |||
ok = InternetSetOption( | |||
IntPtr.Zero, | |||
(int)InternetOptions.ProxySettingChanged, | |||
IntPtr.Zero, | |||
0 | |||
); | |||
if (!ok) | |||
{ | |||
throw new Win32Exception(Marshal.GetLastWin32Error()); | |||
} | |||
ok = InternetSetOption( | |||
IntPtr.Zero, | |||
(int)InternetOptions.Refresh, | |||
IntPtr.Zero, | |||
0 | |||
); | |||
if (!ok) | |||
{ | |||
throw new Win32Exception(Marshal.GetLastWin32Error()); | |||
} | |||
} | |||
[DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)] | |||
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength); | |||
[DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)] | |||
private static extern bool InternetQueryOption(IntPtr hInternet, uint dwOption, IntPtr lpBuffer, ref int lpdwBufferLength); | |||
} | |||
} |
@@ -3,6 +3,7 @@ using Shadowsocks.Controller; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
using Shadowsocks.Util.SystemProxy; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Drawing; | |||
@@ -38,7 +39,7 @@ namespace Shadowsocks.View | |||
private ToolStripMenuItem disableItem; | |||
private ToolStripMenuItem AutoStartupItem; | |||
private ToolStripMenuItem ShareOverLANItem; | |||
private ToolStripSeparator SeperatorItem; | |||
private ToolStripSeparator SeperatorItem; | |||
private ToolStripMenuItem ConfigItem; | |||
private ToolStripMenuItem ProtocolHandlerItem; | |||
private ToolStripMenuItem ServersItem; | |||
@@ -507,7 +508,8 @@ namespace Shadowsocks.View | |||
) | |||
{ | |||
menuItem.Checked = true; | |||
} else | |||
} | |||
else | |||
{ | |||
menuItem.Checked = false; | |||
} | |||
@@ -671,6 +673,9 @@ namespace Shadowsocks.View | |||
globalModeItem.Checked = config.global; | |||
PACModeItem.Checked = !config.global; | |||
} | |||
globalModeItem.Enabled = WinINet.operational; | |||
PACModeItem.Enabled = WinINet.operational; | |||
} | |||
private void GlobalModeItem_Click(object sender, EventArgs e) | |||
@@ -30,8 +30,6 @@ | |||
<None Remove="Data\NLog.config" /> | |||
<None Remove="Data\privoxy.exe.gz" /> | |||
<None Remove="Data\privoxy_conf.txt" /> | |||
<None Remove="Data\sysproxy.exe.gz" /> | |||
<None Remove="Data\sysproxy64.exe.gz" /> | |||
<None Remove="Data\user-rule.txt" /> | |||
<None Remove="Resources\ss128.pdn" /> | |||
<None Remove="Resources\ss32.pdn" /> | |||
@@ -94,8 +92,6 @@ | |||
<Resource Include="Data\NLog.config" /> | |||
<Resource Include="Data\privoxy.exe.gz" /> | |||
<Resource Include="Data\privoxy_conf.txt" /> | |||
<Resource Include="Data\sysproxy.exe.gz" /> | |||
<Resource Include="Data\sysproxy64.exe.gz" /> | |||
<Resource Include="Data\user-rule.txt" /> | |||
<Resource Include="Resources\ss128.pdn" /> | |||
<Resource Include="Resources\ss32.pdn" /> | |||