diff --git a/v2rayN/v2rayN/Handler/ProxySetting.cs b/v2rayN/v2rayN/Handler/ProxySetting.cs index 67541e15..fe0ea689 100644 --- a/v2rayN/v2rayN/Handler/ProxySetting.cs +++ b/v2rayN/v2rayN/Handler/ProxySetting.cs @@ -1,37 +1,68 @@ using Microsoft.Win32; using System.Runtime.InteropServices; +using static v2rayN.Handler.ProxySetting.InternetConnectionOption; namespace v2rayN.Handler { internal class ProxySetting { + /// + // set to use no proxy + /// + /// Error message with win32 error code public static bool UnsetProxy() { return SetProxy(null, null, 1); } + /// + /// Set system proxy settings + /// + /// proxy address + /// exception addresses that do not use proxy + /// type of proxy defined in PerConnFlags + /// PROXY_TYPE_DIRECT = 0x00000001, // direct connection (no proxy) + /// PROXY_TYPE_PROXY = 0x00000002, // via named proxy + /// PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy script URL + /// PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection + /// + /// Error message with win32 error code + /// true: one of connnection is successfully updated proxy settings public static bool SetProxy(string? strProxy, string? exceptions, int type) + { + // set proxy for LAN + bool result = SetConnectionProxy(null, strProxy, exceptions, type); + // set proxy for dial up connections + var connections = EnumerateRasEntries(); + foreach (var connection in connections) + { + result |= SetConnectionProxy(connection, strProxy, exceptions, type); + } + return result; + } + + private static bool SetConnectionProxy(string? connectionName, string? strProxy, string? exceptions, int type) { InternetPerConnOptionList list = new(); int optionCount = 1; - if (type == 1) + if (type == 1) // No proxy { optionCount = 1; } - else if (type is 2 or 4) + else if (type is 2 or 4) // named proxy or autoproxy script URL { optionCount = Utils.IsNullOrEmpty(exceptions) ? 2 : 3; } int m_Int = (int)PerConnFlags.PROXY_TYPE_DIRECT; PerConnOption m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS; - if (type == 2) + if (type == 2) // named proxy { m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY); m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_SERVER; } - else if (type == 4) + else if (type == 4) // autoproxy script url { m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_AUTO_PROXY_URL); m_Option = PerConnOption.INTERNET_PER_CONN_AUTOCONFIG_URL; @@ -47,24 +78,31 @@ namespace v2rayN.Handler if (optionCount > 1) { options[1].m_Option = m_Option; - options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy); + options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy); // !! remember to deallocate memory 1 // except for these addresses ... if (optionCount > 2) { options[2].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_BYPASS; - options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions); + options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions); // !! remember to deallocate memory 2 } } // default stuff list.dwSize = Marshal.SizeOf(list); - list.szConnection = IntPtr.Zero; + if (connectionName != null) + { + list.szConnection = Marshal.StringToHGlobalAuto(connectionName); // !! remember to deallocate memory 3 + } + else + { + list.szConnection = IntPtr.Zero; + } list.dwOptionCount = options.Length; list.dwOptionError = 0; int optSize = Marshal.SizeOf(typeof(InternetConnectionOption)); // make a pointer out of all that ... - IntPtr optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length); + IntPtr optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length); // !! remember to deallocate memory 4 // copy the array over into that spot in memory ... for (int i = 0; i < options.Length; ++i) { @@ -83,26 +121,82 @@ namespace v2rayN.Handler list.options = optionsPtr; // and then make a pointer out of the whole list - IntPtr ipcoListPtr = Marshal.AllocCoTaskMem(list.dwSize); + IntPtr ipcoListPtr = Marshal.AllocCoTaskMem(list.dwSize); // !! remember to deallocate memory 5 Marshal.StructureToPtr(list, ipcoListPtr, false); // and finally, call the API method! - int returnvalue = NativeMethods.InternetSetOption(IntPtr.Zero, + bool isSuccess = NativeMethods.InternetSetOption(IntPtr.Zero, InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION, - ipcoListPtr, list.dwSize) ? -1 : 0; - if (returnvalue == 0) + ipcoListPtr, list.dwSize); + int returnvalue = 0; // ERROR_SUCCESS + if (!isSuccess) { // get the error codes, they might be helpful - returnvalue = Marshal.GetLastWin32Error(); + returnvalue = Marshal.GetLastPInvokeError(); } - // FREE the data ASAP - Marshal.FreeCoTaskMem(optionsPtr); - Marshal.FreeCoTaskMem(ipcoListPtr); - if (returnvalue > 0) - { // throw the error codes, they might be helpful - //throw new Win32Exception(Marshal.GetLastWin32Error()); + else + { + // Notify the system that the registry settings have been changed and cause them to be refreshed + NativeMethods.InternetSetOption(IntPtr.Zero, InternetOption.INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0); + NativeMethods.InternetSetOption(IntPtr.Zero, InternetOption.INTERNET_OPTION_REFRESH, IntPtr.Zero, 0); } - return (returnvalue < 0); + // FREE the data ASAP + if(list.szConnection != IntPtr.Zero) Marshal.FreeHGlobal(list.szConnection); // release mem 3 + if (optionCount > 1) + { + Marshal.FreeHGlobal(options[1].m_Value.m_StringPtr); // release mem 1 + if (optionCount > 2) + { + Marshal.FreeHGlobal(options[2].m_Value.m_StringPtr); // release mem 2 + } + } + Marshal.FreeCoTaskMem(optionsPtr); // release mem 4 + Marshal.FreeCoTaskMem(ipcoListPtr); // release mem 5 + if (returnvalue != 0) + { + // throw the error codes, they might be helpful + throw new ApplicationException($"Set Internet Proxy failed with error code: {Marshal.GetLastWin32Error()}" ); + } + + return true; + } + + /// + /// Retrieve list of connections including LAN and WAN to support PPPoE connection + /// + /// A list of RAS connection names. May be empty list if no dial up connection. + /// Error message with win32 error code + private static IEnumerable EnumerateRasEntries() + { + int entries = 0; + // attempt to query with 1 entry buffer + RASENTRYNAME[] rasEntryNames = new RASENTRYNAME[1]; + int bufferSize = Marshal.SizeOf(typeof(RASENTRYNAME)); + rasEntryNames[0].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME)); + + uint result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries); + // increase buffer if the buffer is not large enough + if (result == (uint)ErrorCode.ERROR_BUFFER_TOO_SMALL) + { + rasEntryNames = new RASENTRYNAME[bufferSize / Marshal.SizeOf(typeof(RASENTRYNAME))]; + for (int i = 0; i < rasEntryNames.Length; i++) + { + rasEntryNames[i].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME)); + } + + result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries); + } + if (result == 0) + { + var entryNames = new List(); + for (int i = 0; i < entries; i++) + { + entryNames.Add(rasEntryNames[i].szEntryName); + } + + return entryNames; + } + throw new ApplicationException($"RasEnumEntries failed with error code: {result}"); } #region WinInet structures @@ -145,6 +239,25 @@ namespace v2rayN.Handler [FieldOffset(0)] public IntPtr m_StringPtr; } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct RASENTRYNAME + { + public int dwSize; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS_MaxEntryName + 1)] + public string szEntryName; + + public int dwFlags; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)] + public string szPhonebookPath; + } + + // Constants + public const int RAS_MaxEntryName = 256; + public const int MAX_PATH = 260; // Standard MAX_PATH value in Windows + } #endregion WinInet structures @@ -156,7 +269,9 @@ namespace v2rayN.Handler // public enum InternetOption : uint { - INTERNET_OPTION_PER_CONNECTION_OPTION = 75 + INTERNET_OPTION_PER_CONNECTION_OPTION = 75, + INTERNET_OPTION_REFRESH = 37, + INTERNET_OPTION_SETTINGS_CHANGED = 39 } // @@ -182,6 +297,12 @@ namespace v2rayN.Handler PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection } + public enum ErrorCode : uint + { + ERROR_BUFFER_TOO_SMALL = 603, + ERROR_INVALID_SIZE = 632 + } + #endregion WinInet enums internal static class NativeMethods @@ -189,6 +310,14 @@ namespace v2rayN.Handler [DllImport("WinInet.dll", SetLastError = true, CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool InternetSetOption(IntPtr hInternet, InternetOption dwOption, IntPtr lpBuffer, int dwBufferLength); + [DllImport("Rasapi32.dll", CharSet = CharSet.Auto)] + public static extern uint RasEnumEntries( + string? reserved, // Reserved, must be null + string? lpszPhonebook, // Pointer to full path and filename of phone-book file. If this parameter is NULL, the entries are enumerated from all the remote access phone-book files + [In, Out] RASENTRYNAME[]? lprasentryname, // Buffer to receive RAS entry names + ref int lpcb, // Size of the buffer + ref int lpcEntries // Number of entries written to the buffer + ); } //判断是否使用代理 diff --git a/v2rayN/v2rayN/Handler/SysProxyHandle.cs b/v2rayN/v2rayN/Handler/SysProxyHandle.cs index 44a25aec..9bce9058 100644 --- a/v2rayN/v2rayN/Handler/SysProxyHandle.cs +++ b/v2rayN/v2rayN/Handler/SysProxyHandle.cs @@ -20,7 +20,6 @@ namespace v2rayN.Handler // // // - private static SysproxyConfig? _userSettings = null; private enum RET_ERRORS : int { @@ -34,15 +33,7 @@ namespace v2rayN.Handler static SysProxyHandle() { - try - { - FileManager.UncompressFile(Utils.GetTempPath("sysproxy.exe"), - Environment.Is64BitOperatingSystem ? Resources.sysproxy64_exe : Resources.sysproxy_exe); - } - catch (IOException ex) - { - Utils.SaveLog(ex.Message, ex); - } + } public static bool UpdateSysProxy(Config config, bool forceDisable) @@ -79,13 +70,11 @@ namespace v2rayN.Handler .Replace("{http_port}", port.ToString()) .Replace("{socks_port}", portSocks.ToString()); } - ProxySetting.SetProxy(strProxy, strExceptions, 2); - SetIEProxy(true, strProxy, strExceptions); + ProxySetting.SetProxy(strProxy, strExceptions, 2); // set a named proxy } else if (type == ESysProxyType.ForcedClear) { - ProxySetting.UnsetProxy(); - ResetIEProxy(); + ProxySetting.UnsetProxy(); // set to no proxy } else if (type == ESysProxyType.Unchanged) { @@ -94,8 +83,7 @@ namespace v2rayN.Handler { PacHandler.Start(Utils.GetConfigPath(), port, portPac); var strProxy = $"{Global.httpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}"; - ProxySetting.SetProxy(strProxy, "", 4); - SetIEProxy(false, strProxy, ""); + ProxySetting.SetProxy(strProxy, "", 4); // use pac script url for auto-config proxy } if (type != ESysProxyType.Pac) @@ -121,102 +109,5 @@ namespace v2rayN.Handler { } } - - public static void SetIEProxy(bool global, string strProxy, string strExceptions) - { - string arguments = global - ? $"global {strProxy} {strExceptions}" - : $"pac {strProxy}"; - - ExecSysproxy(arguments); - } - - // set system proxy to 1 (null) (null) (null) - public static void ResetIEProxy() - { - ExecSysproxy("set 1 - - -"); - } - - 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(false); - using AutoResetEvent errorWaitHandle = new(false); - using Process process = new(); - - // 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(1024); - StringBuilder error = new(1024); - - 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 Exception(process.StartInfo.Arguments); - } - string stderr = error.ToString(); - string stdout = output.ToString(); - - int exitCode = process.ExitCode; - if (exitCode != (int)RET_ERRORS.RET_NO_ERROR) - { - throw new Exception(stderr); - } - - //if (arguments == "query") - //{ - // if (stdout.IsNullOrWhiteSpace() || stdout.IsNullOrEmpty()) - // { - // throw new Exception("failed to query wininet settings"); - // } - // _queryStr = stdout; - //} - } } } \ No newline at end of file diff --git a/v2rayN/v2rayN/Properties/Resources.Designer.cs b/v2rayN/v2rayN/Properties/Resources.Designer.cs index f422c042..24b5f9dd 100644 --- a/v2rayN/v2rayN/Properties/Resources.Designer.cs +++ b/v2rayN/v2rayN/Properties/Resources.Designer.cs @@ -219,25 +219,5 @@ namespace v2rayN.Properties { return ((System.Drawing.Bitmap)(obj)); } } - - /// - /// 查找 System.Byte[] 类型的本地化资源。 - /// - internal static byte[] sysproxy_exe { - get { - object obj = ResourceManager.GetObject("sysproxy_exe", resourceCulture); - return ((byte[])(obj)); - } - } - - /// - /// 查找 System.Byte[] 类型的本地化资源。 - /// - internal static byte[] sysproxy64_exe { - get { - object obj = ResourceManager.GetObject("sysproxy64_exe", resourceCulture); - return ((byte[])(obj)); - } - } } } diff --git a/v2rayN/v2rayN/Properties/Resources.resx b/v2rayN/v2rayN/Properties/Resources.resx index bebcc3df..60899900 100644 --- a/v2rayN/v2rayN/Properties/Resources.resx +++ b/v2rayN/v2rayN/Properties/Resources.resx @@ -118,12 +118,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\resources\sysproxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ..\resources\sysproxy64.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ..\Resources\NotifyIcon1.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/v2rayN/v2rayN/Resources/sysproxy.exe.gz b/v2rayN/v2rayN/Resources/sysproxy.exe.gz deleted file mode 100644 index 980d304b..00000000 Binary files a/v2rayN/v2rayN/Resources/sysproxy.exe.gz and /dev/null differ diff --git a/v2rayN/v2rayN/Resources/sysproxy64.exe.gz b/v2rayN/v2rayN/Resources/sysproxy64.exe.gz deleted file mode 100644 index c5ff36af..00000000 Binary files a/v2rayN/v2rayN/Resources/sysproxy64.exe.gz and /dev/null differ diff --git a/v2rayN/v2rayUpgrade/Program.cs b/v2rayN/v2rayUpgrade/Program.cs index ed454bd0..bc4303de 100644 --- a/v2rayN/v2rayUpgrade/Program.cs +++ b/v2rayN/v2rayUpgrade/Program.cs @@ -13,6 +13,7 @@ namespace v2rayUpgrade { Application.EnableVisualStyles(); Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm(args)); } diff --git a/v2rayN/v2rayUpgrade/app.manifest b/v2rayN/v2rayUpgrade/app.manifest index c7239fd8..3081161b 100644 --- a/v2rayN/v2rayUpgrade/app.manifest +++ b/v2rayN/v2rayUpgrade/app.manifest @@ -2,8 +2,6 @@ - true - PerMonitorV2 \ No newline at end of file