From 1c1cc622356fa598251d2726f27a90c3a1ebc38b Mon Sep 17 00:00:00 2001 From: Syrone Wong Date: Fri, 14 Oct 2016 16:10:58 +0800 Subject: [PATCH] System proxy support for Remote Access Service e.g. Dial-up connection and VPN - use INTERNET_OPTION_PROXY_SETTINGS_CHANGED instead of INTERNET_OPTION_SETTINGS_CHANGED Signed-off-by: Syrone Wong --- .../Util/SystemProxy/INTERNET_OPTION.cs | 8 +- .../SystemProxy/INTERNET_PER_CONN_OPTION_LIST.cs | 2 +- .../Util/SystemProxy/NativeMethods.cs | 2 +- shadowsocks-csharp/Util/SystemProxy/RAS.cs | 151 +++++++++++++++++++++ shadowsocks-csharp/Util/SystemProxy/WinINet.cs | 67 +++++++-- shadowsocks-csharp/shadowsocks-csharp.csproj | 1 + 6 files changed, 217 insertions(+), 14 deletions(-) create mode 100644 shadowsocks-csharp/Util/SystemProxy/RAS.cs diff --git a/shadowsocks-csharp/Util/SystemProxy/INTERNET_OPTION.cs b/shadowsocks-csharp/Util/SystemProxy/INTERNET_OPTION.cs index 584b8e97..169d7ebf 100644 --- a/shadowsocks-csharp/Util/SystemProxy/INTERNET_OPTION.cs +++ b/shadowsocks-csharp/Util/SystemProxy/INTERNET_OPTION.cs @@ -30,7 +30,13 @@ namespace Shadowsocks.Util.SystemProxy INTERNET_OPTION_SETTINGS_CHANGED = 39, // Causes the proxy data to be reread from the registry for a handle. - INTERNET_OPTION_REFRESH = 37 + INTERNET_OPTION_REFRESH = 37, + + // Alerts the current WinInet instance that proxy settings have changed + // and that they must update with the new settings. + // To alert all available WinInet instances, set the Buffer parameter of + // InternetSetOption to NULL and BufferLength to 0 when passing this option. + INTERNET_OPTION_PROXY_SETTINGS_CHANGED = 95 } } diff --git a/shadowsocks-csharp/Util/SystemProxy/INTERNET_PER_CONN_OPTION_LIST.cs b/shadowsocks-csharp/Util/SystemProxy/INTERNET_PER_CONN_OPTION_LIST.cs index 601cde38..8541e0b7 100644 --- a/shadowsocks-csharp/Util/SystemProxy/INTERNET_PER_CONN_OPTION_LIST.cs +++ b/shadowsocks-csharp/Util/SystemProxy/INTERNET_PER_CONN_OPTION_LIST.cs @@ -22,7 +22,7 @@ using System.Runtime.InteropServices; namespace Shadowsocks.Util.SystemProxy { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct INTERNET_PER_CONN_OPTION_LIST : IDisposable { public int Size; diff --git a/shadowsocks-csharp/Util/SystemProxy/NativeMethods.cs b/shadowsocks-csharp/Util/SystemProxy/NativeMethods.cs index ab90c569..e5dd0578 100644 --- a/shadowsocks-csharp/Util/SystemProxy/NativeMethods.cs +++ b/shadowsocks-csharp/Util/SystemProxy/NativeMethods.cs @@ -26,7 +26,7 @@ namespace Shadowsocks.Util.SystemProxy /// /// Sets an Internet option. /// - [DllImport("wininet.dll", CharSet = CharSet.Ansi, SetLastError = true)] + [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool InternetSetOption( IntPtr hInternet, INTERNET_OPTION dwOption, diff --git a/shadowsocks-csharp/Util/SystemProxy/RAS.cs b/shadowsocks-csharp/Util/SystemProxy/RAS.cs new file mode 100644 index 00000000..8192a860 --- /dev/null +++ b/shadowsocks-csharp/Util/SystemProxy/RAS.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Shadowsocks.Util.SystemProxy +{ + public static class RAS + { + private enum RasFieldSizeConstants + { + #region original header + + //#if (WINVER >= 0x400) + //#define RAS_MaxEntryName 256 + //#define RAS_MaxDeviceName 128 + //#define RAS_MaxCallbackNumber RAS_MaxPhoneNumber + //#else + //#define RAS_MaxEntryName 20 + //#define RAS_MaxDeviceName 32 + //#define RAS_MaxCallbackNumber 48 + //#endif + + #endregion + + RAS_MaxEntryName = 256, + RAS_MaxPath = 260 + } + + private const int ERROR_SUCCESS = 0; + private const int RASBASE = 600; + private const int ERROR_BUFFER_TOO_SMALL = RASBASE + 3; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct RasEntryName + { + #region original header + + //#define RASENTRYNAMEW struct tagRASENTRYNAMEW + //RASENTRYNAMEW + //{ + // DWORD dwSize; + // WCHAR szEntryName[RAS_MaxEntryName + 1]; + // + //#if (WINVER >= 0x500) + // // + // // If this flag is REN_AllUsers then its a + // // system phonebook. + // // + // DWORD dwFlags; + // WCHAR szPhonebookPath[MAX_PATH + 1]; + //#endif + //}; + // + //#define RASENTRYNAMEA struct tagRASENTRYNAMEA + //RASENTRYNAMEA + //{ + // DWORD dwSize; + // CHAR szEntryName[RAS_MaxEntryName + 1]; + // + //#if (WINVER >= 0x500) + // DWORD dwFlags; + // CHAR szPhonebookPath[MAX_PATH + 1]; + //#endif + //}; + + #endregion + + public int dwSize; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst=(int)RasFieldSizeConstants.RAS_MaxEntryName + 1)] + public string szEntryName; + + public int dwFlags; + + [MarshalAs(UnmanagedType.ByValTStr,SizeConst=(int)RasFieldSizeConstants.RAS_MaxPath + 1)] + public string szPhonebookPath; + } + + [DllImport("rasapi32.dll", CharSet = CharSet.Auto)] + private static extern uint RasEnumEntries( + string reserved, // reserved, must be NULL + string lpszPhonebook, // pointer to full path and file name of phone-book file + [In, Out] RasEntryName[] lprasentryname, // buffer to receive phone-book entries + ref int lpcb, // size in bytes of buffer + out int lpcEntries // number of entries written to buffer + ); + + /// + /// Get all entries from RAS + /// + /// + /// + /// 0: success with entries + /// 1: success but no entries found + /// 2: failed + /// + public static uint GetAllConns(ref string[] allConns) + { + int lpNames = 1; + int entryNameSize = 0; + int lpSize = 0; + uint retval = ERROR_SUCCESS; + RasEntryName[] names = null; + + entryNameSize = Marshal.SizeOf(typeof(RasEntryName)); + lpSize = lpNames * entryNameSize; + + names = new RasEntryName[lpNames]; + names[0].dwSize = entryNameSize; + + retval = RAS.RasEnumEntries(null, null, names, ref lpSize, out lpNames); + + //if we have more than one connection, we need to resize + if (retval == ERROR_BUFFER_TOO_SMALL) + { + names = new RasEntryName[lpNames]; + for (int i = 0; i < names.Length; i++) + { + names[i].dwSize = entryNameSize; + } + + retval = RAS.RasEnumEntries(null, null, names, ref lpSize, out lpNames); + + } + + if (retval == ERROR_SUCCESS) + { + if (lpNames == 0) + { + // no entries found. + return 1; + } + + allConns = new string[names.Length]; + + for (int i = 0; i < names.Length; i++) + { + allConns[i] = names[i].szEntryName; + } + return 0; + } + else + { + return 2; + } + + } + } +} \ No newline at end of file diff --git a/shadowsocks-csharp/Util/SystemProxy/WinINet.cs b/shadowsocks-csharp/Util/SystemProxy/WinINet.cs index a95a3b5d..a24d1197 100644 --- a/shadowsocks-csharp/Util/SystemProxy/WinINet.cs +++ b/shadowsocks-csharp/Util/SystemProxy/WinINet.cs @@ -19,17 +19,19 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using Shadowsocks.Controller; namespace Shadowsocks.Util.SystemProxy { public static class WinINet { /// - /// Set IE settings for LAN connection. + /// Set IE settings. /// - public static void SetIEProxy(bool enable, bool global, string proxyServer, string pacURL) + private static void SetIEProxy(bool enable, bool global, string proxyServer, string pacURL, string connName) { List _optionlist = new List(); + if (enable) { if (global) @@ -38,17 +40,18 @@ namespace Shadowsocks.Util.SystemProxy _optionlist.Add(new INTERNET_PER_CONN_OPTION { dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_FLAGS_UI, - Value = { dwValue = (int)INTERNET_OPTION_PER_CONN_FLAGS_UI.PROXY_TYPE_PROXY } + Value = { dwValue = (int)(INTERNET_OPTION_PER_CONN_FLAGS_UI.PROXY_TYPE_PROXY + | INTERNET_OPTION_PER_CONN_FLAGS_UI.PROXY_TYPE_DIRECT) } }); _optionlist.Add(new INTERNET_PER_CONN_OPTION { dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_SERVER, - Value = { pszValue = Marshal.StringToHGlobalAnsi(proxyServer) } + Value = { pszValue = Marshal.StringToHGlobalAuto(proxyServer) } }); _optionlist.Add(new INTERNET_PER_CONN_OPTION { dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_BYPASS, - Value = { pszValue = Marshal.StringToHGlobalAnsi("") } + Value = { pszValue = Marshal.StringToHGlobalAuto("") } }); } else @@ -62,7 +65,7 @@ namespace Shadowsocks.Util.SystemProxy _optionlist.Add(new INTERNET_PER_CONN_OPTION { dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_AUTOCONFIG_URL, - Value = { pszValue = Marshal.StringToHGlobalAnsi(pacURL) } + Value = { pszValue = Marshal.StringToHGlobalAuto(pacURL) } }); } } @@ -101,8 +104,9 @@ namespace Shadowsocks.Util.SystemProxy // Return the unmanaged size of an object in bytes. optionList.Size = Marshal.SizeOf(optionList); - // IntPtr.Zero means LAN connection. - optionList.Connection = IntPtr.Zero; + optionList.Connection = connName.IsNullOrEmpty() + ? IntPtr.Zero // NULL means LAN + : Marshal.StringToHGlobalAuto(connName); // TODO: not working if contains Chinese optionList.OptionCount = _optionlist.Count; optionList.OptionError = 0; @@ -132,15 +136,56 @@ namespace Shadowsocks.Util.SystemProxy // Notify the system that the registry settings have been changed and cause // the proxy data to be reread from the registry for a handle. - NativeMethods.InternetSetOption( +// bReturn = NativeMethods.InternetSetOption( +// IntPtr.Zero, +// INTERNET_OPTION.INTERNET_OPTION_SETTINGS_CHANGED, +// IntPtr.Zero, 0); +// if ( ! bReturn ) +// { +// Logging.Error("InternetSetOption:INTERNET_OPTION_SETTINGS_CHANGED"); +// } + + bReturn = NativeMethods.InternetSetOption( IntPtr.Zero, - INTERNET_OPTION.INTERNET_OPTION_SETTINGS_CHANGED, + INTERNET_OPTION.INTERNET_OPTION_PROXY_SETTINGS_CHANGED, IntPtr.Zero, 0); + if (!bReturn) + { + Logging.Error("InternetSetOption:INTERNET_OPTION_PROXY_SETTINGS_CHANGED"); + } - NativeMethods.InternetSetOption( + bReturn = NativeMethods.InternetSetOption( IntPtr.Zero, INTERNET_OPTION.INTERNET_OPTION_REFRESH, IntPtr.Zero, 0); + if (!bReturn) + { + Logging.Error("InternetSetOption:INTERNET_OPTION_REFRESH"); + } + } + + public static void SetIEProxy(bool enable, bool global, string proxyServer, string pacURL) + { + string[] allConnections = null; + var ret = RAS.GetAllConns(ref allConnections); + + if (ret == 2) + throw new Exception("Cannot get all connections"); + + if (ret == 1) + { + // no entries, only set LAN + SetIEProxy(enable, global, proxyServer, pacURL, null); + } + else if (ret == 0) + { + // found entries, set LAN and each connection + SetIEProxy(enable, global, proxyServer, pacURL, null); + foreach (string connName in allConnections) + { + SetIEProxy(enable, global, proxyServer, pacURL, connName); + } + } } } } diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index f4ce3ff0..77460a90 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -190,6 +190,7 @@ +