diff --git a/shadowsocks-csharp/Util/SystemProxy/RAS.cs b/shadowsocks-csharp/Util/SystemProxy/RAS.cs new file mode 100644 index 00000000..3dd3ef9a --- /dev/null +++ b/shadowsocks-csharp/Util/SystemProxy/RAS.cs @@ -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(); + } + return names.Select(n => n.szEntryName).ToArray(); + } + else + { + throw new Exception(); + } + } + } +} diff --git a/shadowsocks-csharp/Util/SystemProxy/WinINet.cs b/shadowsocks-csharp/Util/SystemProxy/WinINet.cs new file mode 100644 index 00000000..0c8bc700 --- /dev/null +++ b/shadowsocks-csharp/Util/SystemProxy/WinINet.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +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 System.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 WinINet + { + // TODO: Save, Restore, + // TODO: Query, Set, + + public static void ProxyGlobal(string server, string bypass) + { + List options = new List + { + GetOption(InternetPerConnectionOptionEnum.FlagsUI,InternetPerConnectionFlags.Proxy), + GetOption(InternetPerConnectionOptionEnum.ProxyServer,server), + GetOption(InternetPerConnectionOptionEnum.ProxyBypass,bypass), + }; + Exec(options); + } + public static void ProxyPAC(string url) + { + List options = new List + { + GetOption(InternetPerConnectionOptionEnum.FlagsUI,InternetPerConnectionFlags.AutoProxyUrl), + GetOption(InternetPerConnectionOptionEnum.ProxyServer,url), + }; + Exec(options); + } + public static void Direct() + { + List options = new List + { + GetOption(InternetPerConnectionOptionEnum.FlagsUI,InternetPerConnectionFlags.Direct), + }; + Exec(options); + } + + 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 void Exec(List options) + { + Exec(options, null); + foreach (var conn in RAS.GetAllConnections()) + { + Exec(options, conn); + } + } + + private static void Exec(List options, string connName) + { + int len = options.Sum(o => Marshal.SizeOf(o)); + + IntPtr buf = Marshal.AllocCoTaskMem(len); + IntPtr cur = buf; + + foreach (var o in options) + { + Marshal.StructureToPtr(o, cur, false); + cur += Marshal.SizeOf(o); + } + InternetPerConnectionOptionList optionList = new InternetPerConnectionOptionList + { + pOptions = buf, + Connection = string.IsNullOrEmpty(connName) + ? IntPtr.Zero + : Marshal.StringToHGlobalAuto(connName), + OptionCount = options.Count, + OptionError = 0, + }; + int listSize = Marshal.SizeOf(optionList); + optionList.Size = listSize; + + IntPtr unmanagedList = Marshal.AllocCoTaskMem(listSize); + Marshal.StructureToPtr(optionList, unmanagedList, true); + + bool ok = InternetSetOption( + IntPtr.Zero, + (int)InternetOptions.PerConnectionOption, + unmanagedList, + listSize + ); + + Marshal.FreeCoTaskMem(buf); + Marshal.FreeCoTaskMem(unmanagedList); + + if (!ok) throw new Exception(); + ok = InternetSetOption( + IntPtr.Zero, + (int)InternetOptions.ProxySettingChanged, + IntPtr.Zero, + 0 + ); + if (!ok) throw new Exception(); + ok = InternetSetOption( + IntPtr.Zero, + (int)InternetOptions.Refresh, + IntPtr.Zero, + 0 + ); + if (!ok) throw new Exception(); + + } + + [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength); + } + +}