Update
Andrei Ignat pointed out there is a class in the .Net Framework that deals exactly with this issue: NetworkChange Class. That is definitely a better approach than leveraging the native API through p-Invoke. The MSDN sample also shows how to check for network availability on each interface, when notified of a change.
Original post:
A common problem for applications that use the network stack is to detect when they are connected to the network. It would be ideal if the application was notified when you plugged in your network cable into your laptop, or when it came in range of a WiFi station and has joined a wireless network.
The Windows API has a function for registering for exactly such notifications: NotifyAddrChange. An application can register a wait handle with this function and the handle will be signaled by the operating system every time a change occurs in the IP address to interface mapping table. Technically is true that ‘IP address table change’ does mean strictly that ‘network connectivity has changed’, but in reality the overwhelming majority of IP address table changes can be associated with the network connectivity being gained or lost.
To leverage this API from managed code we have to use p-Invoke. I use a helper class that wraps the registration for notification and handling the event notification in a simple to use package:
/// <summary>
/// Raises an event each time a change occurs
/// on the system IP address table.
/// </summary>
public class NotifyAddressChanges: IDisposable
{
#region State fields
/// <summary>
/// The OVERLAPPED structure passed to NotifyAddrChange
/// </summary>
private NativeOverlapped _overlapped;
/// <summary>
/// File handle created by NotifyAddrChange
/// </summary>
private IntPtr _handle;
/// <summary>
/// The event signaled by NotifyAddrChange
/// </summary>
private AutoResetEvent _eventChanged;
/// <summary>
/// The thread pool notification registration
/// </summary>
private RegisteredWaitHandle _registered;
/// <summary>
/// Submitted state tracking
/// </summary>
private volatile int _submitted;
#endregion
#region Win32 API declarations
[DllImport("Iphlpapi.dll", SetLastError = true)]
private static extern UInt32 NotifyAddrChange(
ref IntPtr Handle,
ref NativeOverlapped overlapped);
[DllImport("Iphlpapi.dll", SetLastError = true)]
private static extern bool CancelIPChangeNotify(
ref NativeOverlapped overlapped);
#endregion
#region Public usable methods and events
/// <summary>
/// The event raised when a change in the system
/// IP address table occurs.
/// </summary>
public event EventHandler AddressChanged;
/// <summary>
/// CTOR. Initializes the notifications event and submits it
/// to the ThreadPool. Caller need to call Submit() to enable
/// system IP address notifications to raise the AddressChange event.
/// </summary>
public NotifyAddressChanges()
{
_submitted = 0;
_eventChanged = new AutoResetEvent(false);
_overlapped.EventHandle = _eventChanged.SafeWaitHandle.DangerousGetHandle();
_registered = ThreadPool.RegisterWaitForSingleObject(
_eventChanged,
new WaitOrTimerCallback(AddressChangedNotification),
null,
Timeout.Infinite,
false);
}
/// <summary>
/// Enables the system IP address notifications to raise the AddressChange event.
/// </summary>
public void Submit()
{
if (0 == Interlocked.CompareExchange(
ref _submitted, 1, 0))
{
NotifyAddrChange(ref _handle, ref _overlapped);
}
}
#endregion
#region Callback and event raising
/// <summary>
/// IP address change notification callback.
/// </summary>
private void AddressChangedNotification(object state, bool timedOut)
{
if (1 == Interlocked.CompareExchange(
ref _submitted, 0, 1))
{
EventHandler handler = AddressChanged;
if (null != handler)
{
try
{
handler(this, EventArgs.Empty);
}
catch (Exception)
{
// silent ignore
}
}
Submit();
}
}
#endregion
#region Dispose Logic
private void Dispose(bool userInvoked)
{
if (null != _registered)
{
_registered.Unregister(_eventChanged);
}
if (1 == Interlocked.CompareExchange(
ref _submitted, 0, 1))
{
CancelIPChangeNotify(ref _overlapped);
}
}
~NotifyAddressChanges()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
To use this class, simply create an instance, add an event handler to the AddressChanged event and then call the Submit() method to start getting notifications. The AddressChanged event will be raised each time the system changes an IP address on any of its network interfaces. For example consider a simple form named Form1 to which I added a TextBox named textbox1 (in other words I created a new Windows forms application and I dropped a multiline text box on its surface, without bothering to rename either). Here is how I can hook up the IP address change event to display ‘changed’ on a new line in the text box:
public partial class Form1 : Form
{
private NotifyAddressChanges _notification;
public Form1()
{
InitializeComponent();
_notification = new NotifyAddressChanges();
_notification.AddressChanged +=
new EventHandler(_notification_AddressChanged);
_notification.Submit();
}
void _notification_AddressChanged(object sender, EventArgs e)
{
Invoke(new MethodInvoker(delegate ()
{
textBox1.AppendText("changed\r\n");
}));
}
}
I can now start my test application and play with the network cable. As I unplug and plug back the cable, the system looses and re-gains the IP address from my base station DHCP server and it signals the NotifyAddrChange registered events. My form gets notified and it adds a “changed” line to the text box. I can also release and renew my DHCP license from the command line running ipconfig /release and ipconfig /renew.
Note that this notification occurs with any change, both on IP4 and IPv6 interfaces. You will be notified if a VPN is connected or disconnected, if a dial-up connection is established and so on and so forth. Being notified does not mean that network is available, nor that is unavailable. It simply means a change occurred. While I could add support to the AddressChanged event to indicate what change, that would needlessly complicate this simple class, because the number of possible changes is actually much bigger than one would expect (think again at all those VPN, dial-up, point-to-point and other connections). My goal with this class was to eliminate the need to periodically wake the application and check if the network is available every minute, based on a timer. I can simply wait for the AddressChanged notification and then I can try to see if I can establish a connection.