Internet Proxy Monitor

Wednesday, 09 September 2009 02:12 PM
by Coose

Our company enabled the Internet Explorer Proxy via group policy a while back to “improve performance”.  The improvement is about 10 times slower than it was before.  They insist that it is faster, but they are WRONG (yes Ken and Kevin…I’m talking about you).  Not only is it much slower, but the “bypass proxy on local addresses” doesn’t work right, so our WCF endpoints don’t work correctly without configuring them for the proxy (which we can only use message security if we do this).

[more Read on for my solution…]

Our group policy typically has these options disabled.  So it’s quite frustrating to need to turn that off for WCF.  Obviously FireFox fixed the problem for web browsing, but for WCF, this was still a problem.

So, I wrote a C# program to poll the Internet Options proxy settings, and notify me when the settings are changed, display an icon on the tray to remind me, and gives me a one-click option to toggle the proxy off when needed, then back on.  It’s not a terribly complicated program, but it demonstrates two interesting points:

  1. P/Invoke variable sized arrays
  2. WinForms application with NotifyIcon without having a Form at all by using a custom ApplicationContext.

So, to create a Formless Windows Forms Application, create a new Windows Forms Application, and remove the form.  Then create a new class deriving from ApplicationContext.  Add relevant code.  My proxy code looks like this:

    1 class ProxyMonitorApplicationContext : ApplicationContext

    2 {

    3     private Container _components;

    4     private NotifyIcon _notifyIcon;

    5     private ContextMenu _menu;

    6     private Timer _timer;

    7     private bool? _enabled;

    8 

    9     public ProxyMonitorApplicationContext()

   10     {

   11         this.InitializeContext();   

   12     }

   13 

   14     protected void InitializeContext()

   15     {

   16         _components = new Container();

   17 

   18         _menu = new ContextMenu(new MenuItem[] {

   19             new MenuItem(Resources.ToggleMenu, new EventHandler(this.Menu_ToggleClicked)),

   20             new MenuItem(Resources.ExitMenu, new EventHandler(this.Menu_ExitClicked))

   21             }

   22         );

   23 

   24         _notifyIcon = new NotifyIcon(_components);

   25         _notifyIcon.Visible = true;

   26         _notifyIcon.MouseClick += new MouseEventHandler(NotifyIcon_MouseClick);

   27         _notifyIcon.BalloonTipClicked += new EventHandler(NotifyIcon_BalloonTipClicked);

   28         _notifyIcon.ContextMenu = _menu;

   29 

   30         _timer = new Timer(_components);

   31         _timer.Enabled = true;

   32         _timer.Interval = Settings.Default.QueryInterval;

   33         _timer.Tick += new EventHandler(Timer_Tick);

   34 

   35         UpdateStatus(false);

   36     }

   37 

   38     protected override void Dispose(bool disposing)

   39     {

   40         if (disposing && (this._components != null))

   41         {

   42             this._components.Dispose();

   43             _notifyIcon.Dispose();

   44             _timer.Dispose();

   45             _menu.Dispose();

   46         }

   47         base.Dispose(disposing);

   48     }

   49 

   50     private void Timer_Tick(object sender, EventArgs e)

   51     {

   52         bool enabled = InternetOptionsProxy.IsProxyEnabled();

   53         if (_enabled != enabled)

   54         {

   55             UpdateStatus(true);

   56             ShowNotification();

   57         }

   58         _enabled = enabled;

   59     }

   60 

   61     void NotifyIcon_MouseClick(object sender, MouseEventArgs e)

   62     {

   63         if (e.Button == MouseButtons.Left)

   64         {

   65             UpdateStatus(false);

   66             ShowNotification();

   67         }

   68     }

   69 

   70     void NotifyIcon_BalloonTipClicked(object sender, EventArgs e)

   71     {

   72         ToggleStatus();

   73     }

   74 

   75     void Menu_ToggleClicked(object sender, EventArgs e)

   76     {

   77         ToggleStatus();

   78     }

   79 

   80     void Menu_ExitClicked(object sender, EventArgs e)

   81     {

   82         this.ExitThread();

   83     }

   84 

   85     private void ShowNotification()

   86     {

   87         _notifyIcon.BalloonTipTitle = (this._enabled.HasValue && this._enabled.Value)

   88             ? Resources.EnabledTitle

   89             : Resources.DisabledTitle;

   90         _notifyIcon.BalloonTipText = (this._enabled.HasValue && this._enabled.Value)

   91             ? Resources.EnabledMessage

   92             : Resources.DisabledMessage;

   93         _notifyIcon.BalloonTipIcon = (this._enabled.HasValue && this._enabled.Value)

   94             ? ToolTipIcon.Warning

   95             : ToolTipIcon.Info;

   96         _notifyIcon.ShowBalloonTip(Settings.Default.NotificationDuration);

   97     }

   98 

   99     private void ToggleStatus()

  100     {

  101         InternetOptionsProxy.EnableProxy((_enabled == null || _enabled.Value == false));

  102         UpdateStatus(false);

  103     }

  104 

  105     private void UpdateStatus(bool popupIfChanged)

  106     {

  107         bool enabled = InternetOptionsProxy.IsProxyEnabled();

  108         bool changed = _enabled != enabled;

  109         if (!changed) return;

  110 

  111         _enabled = enabled;

  112         if (changed && popupIfChanged)

  113         {

  114             ShowNotification();

  115         }

  116 

  117         _notifyIcon.Text = (this._enabled.HasValue && this._enabled.Value)

  118             ? Resources.EnabledTitle

  119             : Resources.DisabledTitle;

  120         _notifyIcon.BalloonTipTitle = _notifyIcon.Text;

  121         _notifyIcon.BalloonTipText = (this._enabled.HasValue && this._enabled.Value)

  122             ? Resources.EnabledMessage

  123             : Resources.DisabledMessage;

  124         _notifyIcon.Icon = new Icon(

  125             (this._enabled.HasValue && this._enabled.Value)

  126             ? Resources.ProxyEnabled :

  127             Resources.ProxyDisabled, new Size(16, 16)

  128         );

  129     }

  130 }

Essentially, just make an InitializeComponent method, to create an initialize the components that are typically sited on a Form, and be sure to override the Dispose method.

Then create a Program.cs or whatever class you like for the static Main method.  In the Application.Run method, pass a new instance of your context instead of the default form.

    1 [STAThread]

    2 static void Main()

    3 {

    4     Application.EnableVisualStyles();

    5     Application.SetCompatibleTextRenderingDefault(false);

    6     Application.Run(new ProxyMonitorApplicationContext());

    7 }

 

Now, we will P/Invoke methods to actually turn on and off the Internet proxy server.  So here’s the P/Invoke signatures that I used:

    1 internal static class NativeMethods

    2 {

    3     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

    4     public class INTERNET_PER_CONN_OPTION_LIST

    5     {

    6         public int dwSize;

    7         [MarshalAs(UnmanagedType.LPWStr)]

    8         public string pszConnection;

    9         public int dwOptionCount;

   10         public int dwOptionError;

   11         public IntPtr pOptions;

   12     }

   13 

   14     [StructLayout(LayoutKind.Explicit, Size = 12)]

   15     public struct INTERNET_PER_CONN_OPTION

   16     {

   17         // Fields

   18         [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]

   19         [FieldOffset(0)]

   20         public int dwOption;

   21         [FieldOffset(4)]

   22         public IntPtr ptrValue;

   23     }

   24 

   25     public const int PROXY_TYPE_AUTO_DETECT = 8;

   26     public const int PROXY_TYPE_AUTO_PROXY_URL = 4;

   27     public const int PROXY_TYPE_DIRECT = 1;

   28     public const int PROXY_TYPE_PROXY = 2;

   29     public const int INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME = 8;

   30     public const int INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL = 9;

   31     public const int INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS = 7;

   32     public const int INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL = 6;

   33     public const int INTERNET_PER_CONN_AUTOCONFIG_URL = 4;

   34     public const int INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5;

   35     public const int INTERNET_PER_CONN_FLAGS = 1;

   36     public const int INTERNET_PER_CONN_PROXY_BYPASS = 3;

   37     public const int INTERNET_PER_CONN_PROXY_SERVER = 2;

   38     public const int INTERNET_OPTION_PER_CONNECTION_OPTION = 0x4b;

   39     public const int INTERNET_OPTION_REFRESH = 0x25;

   40 

   41     [DllImport("kernel32.dll")]

   42     public static extern IntPtr GlobalFree(IntPtr hMem);

   43     [DllImport("wininet.dll", SetLastError = true)]

   44     [return: MarshalAs(UnmanagedType.Bool)]

   45     public static extern bool InternetQueryOption(IntPtr hInternet, uint dwOption, INTERNET_PER_CONN_OPTION_LIST lpBuffer, ref int lpdwBufferLength);

   46     [DllImport("wininet.dll")]

   47     [return: MarshalAs(UnmanagedType.Bool)]

   48     public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, INTERNET_PER_CONN_OPTION_LIST lpBuffer, int dwBufferLength);

   49 }

And a helper class to call the methods from a nicer .NET friendly API:

    1 internal static class InternetOptionsProxy

    2 {

    3     public static bool IsProxyEnabled()

    4     {

    5         NativeMethods.INTERNET_PER_CONN_OPTION_LIST list = new NativeMethods.INTERNET_PER_CONN_OPTION_LIST();

    6         NativeMethods.INTERNET_PER_CONN_OPTION[] options = new NativeMethods.INTERNET_PER_CONN_OPTION[5];

    7         options[0].dwOption = NativeMethods.INTERNET_PER_CONN_AUTOCONFIG_URL;

    8         options[1].dwOption = NativeMethods.INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;

    9         options[2].dwOption = NativeMethods.INTERNET_PER_CONN_FLAGS;

   10         options[3].dwOption = NativeMethods.INTERNET_PER_CONN_PROXY_BYPASS;

   11         options[4].dwOption = NativeMethods.INTERNET_PER_CONN_PROXY_SERVER;

   12 

   13         list.dwSize = Marshal.SizeOf(list);

   14         list.pszConnection = null;

   15         list.dwOptionCount = options.Length;

   16         list.dwOptionError = 0;

   17 

   18         int len = Marshal.SizeOf(options[0]);

   19         IntPtr buffer = Marshal.AllocHGlobal((int)(len * options.Length));

   20         for (int i = 0; i < options.Length; i++)

   21         {

   22             Marshal.StructureToPtr(options[i], new IntPtr(buffer.ToInt64() + (i * len)), false);

   23         }

   24         list.pOptions = buffer;

   25         int lpdwBufferLength = Marshal.SizeOf(list);

   26 

   27         NativeMethods.InternetQueryOption(IntPtr.Zero, NativeMethods.INTERNET_OPTION_PER_CONNECTION_OPTION,

   28             list, ref lpdwBufferLength);

   29         for (int i = 0; i < options.Length; i++)

   30         {

   31             options[i] = (NativeMethods.INTERNET_PER_CONN_OPTION)Marshal.PtrToStructure(

   32                 new IntPtr(buffer.ToInt64() + (i * len)), typeof(NativeMethods.INTERNET_PER_CONN_OPTION));

   33         }

   34 

   35         Marshal.PtrToStringAnsi(options[0].ptrValue);

   36         Marshal.PtrToStringAnsi(options[3].ptrValue);

   37         Marshal.PtrToStringAnsi(options[4].ptrValue);

   38 

   39         int flags = options[2].ptrValue.ToInt32();

   40         NativeMethods.GlobalFree(options[0].ptrValue);

   41         NativeMethods.GlobalFree(options[3].ptrValue);

   42         NativeMethods.GlobalFree(options[4].ptrValue);

   43         Marshal.FreeHGlobal(buffer);

   44 

   45         bool enabled = (((flags & NativeMethods.PROXY_TYPE_AUTO_DETECT) > 0) ||

   46             (((flags & NativeMethods.PROXY_TYPE_AUTO_PROXY_URL) > 0) ||

   47             ((flags & NativeMethods.PROXY_TYPE_PROXY) > 0)));

   48 

   49         return enabled;

   50     }

   51 

   52     public static void EnableProxy(bool enable)

   53     {

   54         NativeMethods.INTERNET_PER_CONN_OPTION_LIST list = new NativeMethods.INTERNET_PER_CONN_OPTION_LIST();

   55         NativeMethods.INTERNET_PER_CONN_OPTION[] options = new NativeMethods.INTERNET_PER_CONN_OPTION[1];

   56         options[0].dwOption = NativeMethods.INTERNET_PER_CONN_FLAGS;

   57         options[0].ptrValue = enable ?

   58             new IntPtr(NativeMethods.PROXY_TYPE_AUTO_PROXY_URL |

   59                 NativeMethods.PROXY_TYPE_PROXY |

   60                 NativeMethods.PROXY_TYPE_DIRECT

   61             )

   62             : new IntPtr(NativeMethods.PROXY_TYPE_DIRECT);

   63 

   64         list.dwSize = Marshal.SizeOf(list);

   65         list.pszConnection = null;

   66         list.dwOptionCount = options.Length;

   67         list.dwOptionError = 0;

   68 

   69         int len = Marshal.SizeOf(options[0]);

   70         IntPtr hglobal = Marshal.AllocHGlobal((int)(len * options.Length));

   71         for (int i = 0; i < options.Length; i++)

   72         {

   73             Marshal.StructureToPtr(options[i], new IntPtr(hglobal.ToInt64() + (i * len)), false);

   74         }

   75         list.pOptions = hglobal;

   76 

   77         int dwBufferLength = Marshal.SizeOf(list);

   78         NativeMethods.InternetSetOption(IntPtr.Zero, NativeMethods.INTERNET_OPTION_PER_CONNECTION_OPTION,

   79             list, dwBufferLength);

   80         NativeMethods.InternetSetOption(IntPtr.Zero, NativeMethods.INTERNET_OPTION_REFRESH, null, 0);

   81         Marshal.FreeHGlobal(hglobal);

   82     }

   83 }

This class does the P/Invoke work.

When the application is running, right clicking the tray icon allows toggling from a “proxy enabled” to “proxy disabled” state.

proxy3

When the proxy is enabled and traffic is going through the proxy, the icon shows red monitors.

proxy4

When the proxy is disabled and traffic is not going through the proxy, the icon shows blue monitors.

proxy2

Left-clicking the icon shows the status in a balloon tip.  Clicking the balloon will toggle the proxy status.

proxy1

Note: When enabling the proxy, it only turns on the currently configured proxy.  It will not configure or autoconfigure a proxy.  If there is no proxy configured, I don’t know what will happen.  I haven’t tried it.

Download the full project here or run a ClickOnce application directly here.

Comment on this
Development
| |

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading