Export (0) Print
Expand All
Add Support for Digital Ink to Your Windows Applications
Power to the Pen: The Pen is Mightier with GDI+ and the Tablet PC Real-Time Stylus
Expand Minimize

Mobile PC Network Location Awareness

 

Dr. Neil Roodyn

May 2005

Applies to:
   Microsoft Windows XP Tablet PC Edition
   Network Location Awareness (NLA) API
   Microsoft .NET Framework

Summary: This paper introduces using the Network Location Awareness (NLA) API from managed code. NLA allows applications to identify current logical network connections and be notified of changes to the current network connections. (34 printed pages)

Contents

Introduction
The "Sometimes Connected" Way of Working
Querying the Currently Connected Networks
Receiving Notifications When Things Change
Validating the Application
Putting It All Together
The Data Structures
The Network Class
Forcing a Deeper Search
Putting It All Together
Conclusion
Biography

Introduction

This paper introduces using the Network Location Awareness (NLA) API from managed code. NLA allows applications to identify current logical network connections and be notified of changes to the current network connections.

At the end of this paper you will have all the information you need to enable your Microsoft .NET Framework application to detect if there is one or more network connections present, and how you can find out more about the connected networks, such as the names and speed of connection, or whether the network supports internet connectivity. This paper also provides you with the techniques you'll need to enable your application to receive alerts of changes to the current network status.

To support this paper there is also a sample application that you can download. The sample application is provided in two projects. One of the projects contains the sample application with unit tests, the other contains the same code files but without the test files.

The unit tests I employ have been developed using NUnit. This is not a recommended way for you to develop code with unit tests, but it enables you to run the sample application without NUnit if it is not installed on your computer.

The "Sometimes Connected" Way of Working

Portable computers—whether laptops, notebooks, or Microsoft Windows XP Tablet PC Editions—are now considered essential to modern-day life and business. Applications running on these mobile devices must take into account the fact that they will not always have a network connection available to them. Many users now use networks at home as well as at work, and your application may want to distinguish between the two and behave differently. Many mobile PCs support multiple modes of network access, including wireless network connectivity, Ethernet ports, and even Bluetooth capability. This fact, in combination with connections to Virtual Private Networks (VPNs), means that a mobile PC may have multiple network connections open at the same time. Your application may need to know this and connect through the most appropriate available network, or otherwise respond intelligently. For example, you don't want your application repeatedly attempting to connect to the internet if no current network connection supports internet connectivity.

The advent of wireless networks requires that laptop and Tablet PC applications support being sometimes connected (also called "occasionally connected" or "mobilized"). As your users walk around their office with a Tablet PC, you want your application to seamlessly carry on working for them as they enter and leave networks.

There are three states that a mobilized application must operate within:

  • There is no network connection.
  • There is a single network connection.
  • There are multiple network connections.

A well-behaved software application needs to be able to transition smoothly between these three states. These transitions can be seen as four possible scenarios:

  • A network connection becomes available when there was not one previously available.
  • A new network connection becomes available when one (or more) was already available.
  • The network connection disappears (drops out), leaving no connections.
  • A network connection disappears, leaving one or more connections.

Ideally, handling these scenarios should not involve any user interaction. It may, however, be important to provide some visual indication to the user that a connectivity event has occurred.

Certain features of your application may only be available when a network connection is present. You may only want some features available when a particular network connection (such as a VPN) is available.

Let's consider a couple of situations where an application can benefit from being aware of network location.

In the first case, imagine Bob, a tech-support person in a large company. Bob has a laptop and he is constantly moving between floors and between office buildings in the same corporate park. Bob has a couple of applications open on his laptop the whole time: his e-mail application and an issue reporting tool to report the fixes he has made. Both applications require access to the company network. When Bob moves throughout the company's offices, sometimes he can't get network access; for example, in the warehouse there is no wireless network access. Even so, Bob needs to be able to answer e-mail messages and add reports to the system on fixes he has made.

Bob's e-mail application should download and upload messages between the mail server and his laptop whenever Bob is connected, but a message that hasn't completely transferred when connectivity disappears should not be lost. The e-mail application needs to cope with aborted message transfers.

Similarly, Bob's issue reporting tool connects to a database on a company server. The database contains all the records of technical issues and fixes made by Bob and his colleagues on the tech-support team. This system is the way most of Bob's work is routed to him. He collects an open issue from the issue reporting tool and gets the details from the database; he then goes to the suspected location of the fault to fix it. When Bob leaves a location that has network connectivity, the reporting application keeps the last fifty records Bob was looking at in a local cache. Bob can view and update these records locally on his laptop. The next time the application detects a network connection, it attempts to synchronize all of Bob's changes with the database. If the network connection drops out, any records that didn't get fully synchronized are cached until a connection is detected again.

Bob wants his application to:

  • Roam between wireless networks in his company seamlessly.
  • Gracefully handle network disconnections and reconnections.
  • Not interrupt his work when the network status changes.

The second situation we'll explore is that of Jenny. Jenny works for an insurance company and spends most of her time out of the office, usually only popping into the office for Monday morning meetings. She sells insurance and visits claim sites to help her customers make claim reports. Jenny has a Tablet PC and is more often disconnected than she is connected. Jenny relies on two applications for her day to day work: an e-mail application, and an application that contains all the forms her insurance company uses. The e-mail application will connect and upload or download e-mail messages whenever it detects an internet connection. Jenny will often stop at a café at lunch time to pick up and answer e-mail messages. The company's forms application will only connect to the company server when Jenny is connected through a VPN or directly on the company network. Jenny has VPN access set up at home where she has a fast broadband connection, but the connection speed in the café's she visits for lunch tend to be slow with the company VPN, so she doesn't even bother trying to connect during the day.

In this second situation, the insurance company application detects when it has a connection to a known network before attempting to synchronize the forms Jenny has filled in for her customers. The e-mail application, on the other hand, is less fussy and, as soon as it detects any network connection, it attempts to synchronize Jenny's e-mail with the company mail server.

Jenny wants her application to:

  • Hide the features that are unavailable when she is not connected to the corporate network.
  • Use the best connection available to download and upload e-mail messages.
  • Not interrupt her work.

In this paper, you will discover how you can build applications that are aware of the connected networks and operate in similar ways to those previously presented. An application that is only connected sometimes should attempt to provide the user with continuous operation of the application as the mobile PC connects and disconnects from networks. Features of the application that require network access could be hidden from the user or work on a local cache of the data. Either way, the network features should not degrade the user's experience with the application.

Querying the Currently Connected Networks

As a first step to providing the aforementioned support in your application, it is useful to know which networks the application can currently access.

The .NET Framework does not provide this information inherently. In order to get this information, your code must call native Win32 methods.

Version 2 of the Microsoft Windows Sockets Library that is shipped with the Win32 Platform SDK has a number of functions that you can use to detect the currently connected networks. They are:

Before these methods can be successfully invoked, a call must be made to the WSAStartup method to initialize the process for using the Windows Sockets Library. You should also remember to call the WSACleanup method when you have finished using the library.

You can create a simple loop to iterate through the currently available networks using these functions. The pseudo code looks like this:

WSAStartup
WSALookupServiceBegin;
Do
   WSALookupServiceNext
While there are more connected networks 
WSALookupServiceEnd
WSACleanup

To do this in C#, you use Platform Invoke (commonly known as P/Invoke) and two structures. The WSAData structure is used by the WSAStartup method to receive information about the implementation of the Windows Sockets library. You also must define the WSAQUERYSET structure that is used by both the WSALookupServiceBegin and the WSALookupServiceNext functions. In the example code that follows, you can see these structures defined as a C# classes; this is done because a C# class is always passed by reference, making the call to the methods simpler. Note that in the WSAStartup method, the second parameter doesn't have to be marked as an [out] parameter or use the ref keyword—the second parameter is simply an instance of a WSAData class:

    [StructLayout(LayoutKind.Sequential)]
    public class WSAData 
    {
        public Int16 wVersion;
        public Int16 wHighVersion;  
        public String szDescription;  
        public String szSystemStatus;  
        public Int16 iMaxSockets;  
        public Int16 iMaxUdpDg;  
        public IntPtr lpVendorInfo;
    }

    [ StructLayout( LayoutKind.Sequential, CharSet=CharSet.Auto )]
    public class WSAQUERYSET
    {
        public Int32 dwSize = 0;  
        public String szServiceInstanceName = null;  
        public IntPtr lpServiceClassId;  
        public IntPtr lpVersion;  
        public String lpszComment;  
        public Int32 dwNameSpace;  
        public IntPtr lpNSProviderId;  
        public String lpszContext;  
        public Int32 dwNumberOfProtocols;  
        public IntPtr lpafpProtocols;  
        public String lpszQueryString;  
        public Int32 dwNumberOfCsAddrs;  
        public IntPtr lpcsaBuffer;  
        public Int32 dwOutputFlags;  
        public IntPtr lpBlob;
    } 
    
    [DllImport("Ws2_32.DLL", CharSet = CharSet.Auto, 
    SetLastError=true)]
    private extern static
        Int32 WSAStartup( Int16 wVersionRequested, WSAData wsaData);

    [DllImport("Ws2_32.DLL", CharSet = CharSet.Auto, 
    SetLastError=true)]
    private extern static
        Int32 WSACleanup();

    [DllImport("Ws2_32.dll", CharSet = CharSet.Auto, 
    SetLastError=true)]
    private extern static
        Int32 WSALookupServiceBegin(WSAQUERYSET qsRestrictions,
            Int32 dwControlFlags, ref Int32 lphLookup);


    [DllImport("Ws2_32.dll", CharSet = CharSet.Auto, 
    SetLastError=true)]
    private extern static
        Int32 WSALookupServiceNext(Int32 hLookup,
            Int32 dwControlFlags,
            ref Int32 lpdwBufferLength,
            IntPtr pqsResults);

    [DllImport("Ws2_32.dll", CharSet = CharSet.Auto, 
    SetLastError=true)]
    private extern static
        Int32 WSALookupServiceEnd(Int32 hLookup);

With these methods defined, you call them as shown in the following example code. This code snippet gets the name of each network and places it in a list:

    public virtual ArrayList GetConnectedNetworks()
    {
        ArrayList networkConnections = new ArrayList();
        WSAQUERYSET qsRestrictions;
        Int32 dwControlFlags;
        Int32 valHandle = 0;
            
        qsRestrictions = new WSAQUERYSET();
        qsRestrictions.dwSize = Marshal.SizeOf(typeof(WSAQUERYSET));
        qsRestrictions.dwNameSpace = 0; //NS_ALL;
        dwControlFlags = 0x0FF0; //LUP_RETURN_ALL;

        int result = WSALookupServiceBegin(qsRestrictions,
            dwControlFlags, ref valHandle);

        CheckResult(result);
        
        while (0 == result)
        {
            Int32 dwBufferLength = 0x10000; 
            IntPtr pBuffer = Marshal.AllocHGlobal(dwBufferLength);
                
            WSAQUERYSET qsResult = new WSAQUERYSET() ;
                    
            result = WSALookupServiceNext(valHandle, dwControlFlags, 
            ref dwBufferLength, pBuffer);
        
            if (0==result)
            {
                Marshal.PtrToStructure(pBuffer, qsResult);
                networkConnections.Add(
                    qsResult.szServiceInstanceName);
            }
                
            Marshal.FreeHGlobal(pBuffer);
        }       

        result = WSALookupServiceEnd(valHandle);

        return networkConnections;
    }

You will notice that in the previous GetConnectedNetworks code, a large buffer of memory is allocated to read in the WSAQUERYSET structure (a class in the managed code). This is not overly efficient, and in later in this article, you will discover how to allocate only the amount of memory required.

The data in the allocated memory buffer is then marshalled into the WSAQUERYSET class. This allows for the variation in the amount of data being received. Later in this article, you will discover how to use the extra data that is returned. For the moment, only the name of the connected network is utilized.

Your mobilized application should get a list of the currently connected networks when it first starts up. The application can then enable or disable the features that require network access, along with other behavioral modifications the application should make based on connectivity, such as accessing a local data cache.

You can then write the code that will change the application's behavior when the current network connections change. In order to make the changes, your code must be notified of changes to the available networks. The following topic shows how to receive notifications when a change occurs to the currently connected networks.

Receiving Notifications When Things Change

The previous code shows you how to determine the networks that are currently available. You can then adjust your application to behave in an appropriate way. Of course, you will want to be notified of any changes to the state of network connectivity. You do this by polling for the currently connected networks. One way to do this is to create a worker thread that rebuilds the list of currently connected networks, but there is a better way of doing this.

There is another function in the Windows Sockets Library that can be called to find out if anything has changed since the last call to WSAServiceLookupBegin. The WSANSPIoctl function is used to return immediately and provide information as to whether there has been a change to the connected networks. This method can be polled, and you can act upon any changes in the currently connected networks from within your code.

A better solution exists by using the same WSANAPIoctl function as a blocking method that only returns when a change has occurred. You can use this in a worker thread that then calls a delegate method in your code.

The sample code that follows demonstrates this by creating an event that gets fired whenever there is a change in the network connections:

    [DllImport("Ws2_32.dll", CharSet = CharSet.Auto, 
    SetLastError=true)]
    private extern static Int32 WSANSPIoctl(
        Int32 hLookup,
        UInt32 dwControlCode,
        IntPtr lpvInBuffer,
        Int32 cbInBuffer,
        IntPtr lpvOutBuffer,
        Int32 cbOutBuffer,
        ref Int32 lpcbBytesReturned,
        IntPtr lpCompletion );

    public delegate void NetworkChangedEventHandler(object sender, 
        NetworkChangedEventArgs e);

    private Int32 monitorLookup = 0;
    
    protected void WaitForNetworkChanges()
    {
        WSAQUERYSET qsRestrictions = new WSAQUERYSET();
        Int32 dwControlFlags;
            
        qsRestrictions.dwSize = Marshal.SizeOf(typeof(WSAQUERYSET));
        qsRestrictions.dwNameSpace = 0; //NS_ALL;

        dwControlFlags = 0x0FF0; //LUP_RETURN_ALL;

        int nResult = WSALookupServiceBegin(qsRestrictions,
                dwControlFlags, ref monitorLookup);

        Int32 dwBytesReturned = 0;
        UInt32 cCode = 0x88000019; //SIO_NSP_NOTIFY_CHANGE
        nResult = WSANSPIoctl(monitorLookup, cCode, 
                new IntPtr(), 0, new IntPtr(), 0,
                ref dwBytesReturned,
                new IntPtr());
            
        if (0 != nResult)
        {
            CheckResult(nResult);
        }
            
        nResult = WSALookupServiceEnd(monitorLookup);
        monitorLookup=0;
    }
        
    public event NetworkChangedEventHandler NetworkChanged;

    protected void NetworkMonitor()
    {
        int nResult = 0;
    
        while (0 == nResult)
        {
            WaitForNetworkChanges();

            ArrayList networks = GetConnectedNetworks();
                
            NetworkChangedEventArgs eventArgs = 
                new NetworkChangedEventArgs(networks);
         
            try
            {
                if (null != NetworkChanged)
                {
                    NetworkChanged(this, eventArgs);  
                }            
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine(ex.ToString());
            }
        }
    }

    public virtual void StartNetworkMonitor()
    {
        monitorThread = new Thread(new 
            ThreadStart(this.NetworkMonitor) );
        monitorThread.Name = "Network Monitor";
        monitorThread.IsBackground = true;
        monitorThread.Start();
    }

To stop the thread from running, this code calls Abort on the thread. In your application, you may wish to call Interrupt and use an exception handler to catch a ThreadInterruptedException. The call to WSALookupServiceEnd forces the WSANAPIoctl function to return.

    public virtual void StopNetworkMonitor()
    {
        if (monitorThreadRunning.WaitOne(0, true))
        {
            monitorThreadRunning.Reset();
            monitorThread.Abort();
            WSALookupServiceEnd(monitorLookup);
        }
    }

The NetworkChangedEventArgs used in the previous code is a simple class that contains an ArrayList of the networks that are currently connected.

public class NetworkChangedEventArgs : EventArgs
   {
        private ArrayList networkList;
        public NetworkChangedEventArgs(ArrayList networks)
        {
            networkList = networks;
        }

        public ArrayList Networks
        {
            get{ return networkList; }
        }
   }

The NetworkMonitor method in the example code shown has an endless loop. In production code, you would want to use a thread synchronization flag to end the thread when the application is closing down. This use of a thread is demonstrated in the accompanying sample application.

Validating the Application

One of the issues to address when writing network awareness code is how to test the methods. Manually, it is quite easy to pull the cable out of your PC and then plug it back in again. A good test technique is to have a secondary network connection through a USB adaptor such as the SMC Ez Connect USB. You can then set this up so it is on your desk and you can easily unplug and plug it in to test your implementation of the methods discussed so far in this paper.

Most modern applications require that there be some form of automated and unit testing. It is recommended that you encapsulate the NLA components into a class. You can then create a mock class that presents the same interface as the NLA class and use the mock NLA class in your unit tests.

In the full sample, the NLA methods are encapsulated within an NLA class. A mock version of this NLA class can then be defined as follows. Notice the extra two methods, Setup and ChangeNetworks, which exist to allow the test code to manipulate the networks:

    public class NLAMock : NLA
    {
        private ArrayList networkConnections;

        public NLAMock()
        {
            networkConnections = new ArrayList();
        }
        
        public void Setup(ArrayList networks)
        {
            networkConnections = networks;
        }

        public void ChangeNetworks(ArrayList networks)
        {
            networkConnections = networks;
            
            NetworkChangedEventArgs eventArgs = 
                new NetworkChangedEventArgs(networks);
         
            try
            {
                NetworkChanged(this, eventArgs);
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine(ex.ToString());
            }
        }
        
        public override ArrayList GetConnectedNetworks()
        {
            return networkConnections;
        }

        public override void StartNetworkMonitor()
        {}

        public override void StopNetworkMonitor()
        {}

        public override event 
            NetworkChangedEventHandler NetworkChanged;
    }

Putting It All Together

Using the knowledge gained so far, it is possible to write an application that can perform network operations when a network is present and can also cope with a disconnected state. Such an application must handle the four scenarios mentioned at the beginning:

  • A network connection becomes available when there was not one previously available.
  • A new network connection becomes available when one (or more) was already available.
  • The network connection disappears (drops out), leaving no connections.
  • A network connection disappears, leaving one or more connections.

The remainder of this article takes you through a test-driven approach using some sample code. The testing framework used in the sample application is NUnit.

To create the tests, you first create a class called NetworkOperationTests that is marked with the TestFixture attribute. This attribute defines the class as containing unit tests. This test class will work with the NLAMock class in order to test the functionality of a class that performs a network-state detection.

The Setup method will run before each test and initialize a new NLAMock object and ArrayList for use in the tests.

The first test method, CompletesOnNetworkConnected, tests that, when a valid network connection is present, the network operation completes successfully.

    [TestFixture]
    public class NetworkOperationTests
    {
        private NLAMock nla;
        private ArrayList networks;

        [SetUp]
        public void Setup()
        {
            nla = new NLAMock();
            networks = new ArrayList();
        }

        [Test]
        public void CompletesOnNetworkConnected()
        {
            networks.Add("Valid Network");
            nla.Setup(networks);
            NetworkOperation operation = new NetworkOperation(nla);
            operation.Start();
            // You may want to use a timeout here.
            while (operation.Status == 
                NetworkOperationStatus.InProgress)  
            {
                Thread.Sleep(100);   
            }
            Assert.AreEqual(NetworkOperationStatus.Completed, 
                operation.Status, 
                "Should be completed when network connected");
        }
    }

In order to make this test pass, you will need to create a NetworkOperationStatus enumeration and a NetworkOperation class:

    public enum NetworkOperationStatus
    {
        InProgress,
        Completed,
    }

    public class NetworkOperation
    {
        public NetworkOperation(NLA nla)
        {
        }
        
        public void Start()
        {
            
        }

        public NetworkOperationStatus Status
        {
            get{return NetworkOperationStatus.Completed;}
        }
    }

This code is enough to allow the first test to pass. The next test in the NetworkOperationTests class requires more code to be added to the NetworkOperation class:

    [Test]
    public void NotCompletedWhenNoNetwork()
    {
        networks.Clear();
        nla.Setup(networks);
        NetworkOperation operation = new NetworkOperation(nla);
        operation.Start();
        while (operation.Status ==
            NetworkOperationStatus.InProgress)
        {
            Thread.Sleep(100);   
        }
        Assert.AreEqual(NetworkOperationStatus.NotCompleted, 
            operation.Status, 
            "Should not be completed when no network present");
    }

Next, to make this test pass you will need to change the NetworkOperation class to actually carry out some operations:

    public enum NetworkOperationStatus
    {
        InProgress,
        Completed,
        NotCompleted
    }

    public class NetworkOperation
    {
        private NetworkOperationStatus status;
        private NLA nla;
        
        public NetworkOperation(NLA nla)
        {
            this.nla = nla;
        }
        
        public void Start()
        {
            status = NetworkOperationStatus.NotCompleted;
            ArrayList networks = nla.GetConnectedNetworks();

            if (networks.Count > 0)
            {
                status = NetworkOperationStatus.Completed;
            }

        }

        public NetworkOperationStatus Status
        {
            get{return status;}
        }
    }

This example provides you with a good start in your testing, but the code does not yet handle transitions between states. The next test validates that a network connection cannot be completed if the network is disconnected after the operation has started:

    [Test]
    public void NotCompletedWhenNetworkDisconnected()
    {
        networks.Add("Valid Network");
        nla.Setup(networks);
        NetworkOperation operation = new NetworkOperation(nla);
        operation.Start();
        while (operation.Status == 
            NetworkOperationStatus.InProgress)
        {
            Thread.Sleep(50);   
            networks.Clear();
            nla.ChangeNetworks(networks);
        }
        Assert.AreEqual(NetworkOperationStatus.NotCompleted, 
            operation.Status, 
            "Should not be completed when network disconnected");
    }

In order to make this test pass, the NetworkOperation class creates a new thread that carries out the operation. You will also need to handle the event that gets raised by the NLA object when the connected networks change:

    public class NetworkOperation
    {
        private NetworkOperationStatus status;
        private NLA nla;
        private Thread operationThread;
        private ManualResetEvent networkAvailable;
        
        public NetworkOperation(NLA nla)
        {
            // Initialize networkAvailable according 
            // to network count
            ArrayList networks = 
                nla.GetConnectedNetworks();
            networkAvailable = 
                new ManualResetEvent(networks.Count > 0);

            this.nla = nla;
      nla.NetworkChanged += 
          new NetworkChangedEventHandler(nla_NetworkChanged);
            nla.StartNetworkMonitor();
        }
        
        public void Start()
        {
            status = NetworkOperationStatus.NotCompleted;
            ArrayList networks = nla.GetConnectedNetworks();

            if (networks.Count > 0)
            {
                StartNetworkOperationThread();
            }

        }

        protected virtual void PerformNetworkOperation()
        {
            bool bCompleted = false;
            int nPercentComplete = 0;
            
            while (networkAvailable.WaitOne(0,true)
                && !bCompleted )
            {
                nPercentComplete ++;
                Thread.Sleep(10); //perform a network operation here
                if (nPercentComplete > 100)
                {
                    bCompleted = true;
                }

            }
            if (bCompleted)
            {
                status = NetworkOperationStatus.Completed;
            }
            else
            {
                status = NetworkOperationStatus.NotCompleted;
            }
        }

        private void StartNetworkOperationThread()
        {
            networkAvailable.Set();
            status = NetworkOperationStatus.InProgress;
    
            Thread operationThread = new Thread(
                new ThreadStart(this.PerformNetworkOperation) );
            operationThread.Start();
        }


        public NetworkOperationStatus Status
        {
            get{return status;}
        }

        private void nla_NetworkChanged(object sender, 
            NetworkChangedEventArgs e)
        {
            if (0 == e.Networks.Count )
            {
                networkAvailable.Reset();
            }
        }
    }

There are two other cases that your code should test for; these are both related to a new network connection becoming available. The first test validates the behavior when a network becomes available and there wasn't a network connection previously available. The second test validates the behavior when a network gets added and there was previously a network connection available.

    [Test]
    public void CompletedWhenNetworkAdded()
    {
        nla.Setup(networks);
        NetworkOperation operation = new NetworkOperation(nla);
        operation.Start();
        Assert.AreEqual(NetworkOperationStatus.NotCompleted, 
            operation.Status, "Should not be completed");
        networks.Add("Valid Network");
        nla.ChangeNetworks(networks);
        while (operation.Status == 
            NetworkOperationStatus.InProgress)
        {
            Thread.Sleep(100);   
        }
        Assert.AreEqual(NetworkOperationStatus.Completed, 
            operation.Status, "Should be completed");
    }

    [Test]
    public void CompletedWhenMultipleNetworksAdded()
    {
        networks.Add("Valid Network");
        nla.Setup(networks);
        NetworkOperation operation = new NetworkOperation(nla);
        operation.Start();
        while (operation.Status == 
            NetworkOperationStatus.InProgress)
        {
            Thread.Sleep(100);   
            networks.Add("Valid Network");
            nla.ChangeNetworks(networks);
        }
        Assert.AreEqual(NetworkOperationStatus.Completed, 
            operation.Status, "Should be completed");
    }

These two test scenarios can be handled in the NetworkOperation class with an else clause in the event handler that will start the network operation thread. The new else clause allows the networkAvailable ManualResetEvent to be set in the same method. The StartNetworkOperationThread method can also check that the network operation is not already running or completed:

    private void nla_NetworkChanged(object sender, 
        NetworkChangedEventArgs e)
    {
        if (0 == e.Networks.Count )
        {
            networkAvailable.Reset();
        }
        else
        {
            networkAvailable.Set();
            StartNetworkOperationThread();    
        }
    }

    private void StartNetworkOperationThread()
    {
        if (status != NetworkOperationStatus.InProgress
            && status != NetworkOperationStatus.Completed)
        {
            status = NetworkOperationStatus.InProgress;
    
            Thread operationThread = new Thread(
                new ThreadStart(this.PerformNetworkOperation) );
            operationThread.Priority = ThreadPriority.BelowNormal;
            operationThread.IsBackground = true;
            operationThread.Start();
        }
    }

Notice that the thread is only started if it hadn't already been started.

You now have five tests that validate the behavior of the NetworkOperation class in all the scenarios discussed at the beginning of this article.

The accompanying sample application uses the NetworkOperation class to update a progress bar indicating the percentage of the operation that is complete. You may find it useful in your applications to notify the user of network operations that are taking place in the background.

The Data Structures

In the beginning of this article, you discovered that a WSAQUERYSET object that returns from the WSALookupServiceNext method contains the name of the network. This WSAQUERYSET object also contains other information, and we will now explore how to recover and utilize that other information.

The Windows Sockets Library presents most of the interesting information in variable sized data blobs. This is not particularly helpful for managed code developers. Even less helpful is the use of complex structures nested in other structures and in union with other complex structures. Looking inside the MSWSock.h, file you will discover that the NLA_BLOB structure is represented as follows:

typedef struct _NLA_BLOB {
    struct {
        NLA_BLOB_DATA_TYPE type;
        DWORD dwSize;
        DWORD nextOffset;
    } header;
    union {
        // header.type -> NLA_RAW_DATA
        CHAR rawData[1];
        // header.type -> NLA_INTERFACE
        struct {
            DWORD dwType;
            DWORD dwSpeed;
            CHAR adapterName[1];
        } interfaceData;
        // header.type -> NLA_802_1X_LOCATION
        struct {
            CHAR information[1];
        } locationData;
        // header.type -> NLA_CONNECTIVITY
        struct {
            NLA_CONNECTIVITY_TYPE type;
            NLA_INTERNET internet;
        } connectivity;
        // header.type -> NLA_ICS
        struct {
            struct {
                DWORD speed;
                DWORD type;
                DWORD state;
                WCHAR machineName[256];
                WCHAR sharedAdapterName[256];
            } remote;
        } ICS;
    } data;
}

The first nested structure is the header, which contains an NLA_BLOB_DATA_TYPE that identifies the structures in the union that contain data in this instance of a BLOB. The NLA_BLOB_DATA_TYPE is an enumerated type:

typedef enum _NLA_BLOB_DATA_TYPE {
    NLA_RAW_DATA          = 0,
    NLA_INTERFACE         = 1,
    NLA_802_1X_LOCATION   = 2,
    NLA_CONNECTIVITY      = 3,
    NLA_ICS               = 4,
}

For the sake of brevity, this article will focus on the NLA_INTERFACE and NLA_CONNECTIVITY types. Exploring these two nested structures will teach you everything you need to know in order to use the other structures. These two structures include value data types, strings, and enumerated data types.

Before learning how to decode this structure in C#, we have included a definition of the enumerated types we will want to use, as found in the MSWSock.h file:

typedef enum _NLA_CONNECTIVITY_TYPE {
    NLA_NETWORK_AD_HOC    = 0,
    NLA_NETWORK_MANAGED   = 1,
    NLA_NETWORK_UNMANAGED = 2,
    NLA_NETWORK_UNKNOWN   = 3,
} 

typedef enum _NLA_INTERNET {
    NLA_INTERNET_UNKNOWN  = 0,
    NLA_INTERNET_NO       = 1,
    NLA_INTERNET_YES      = 2,
} 

You have probably spotted that the NLA_BLOB structure is not a fixed size, so the different nested structures in the union may take up different amounts of memory. There are two further complications to work with here. First, there can be a number of these NLA_BLOB structures linked together in memory, with the nextOffset value in the header providing the only mechanism to get to the next structure. Secondly, the types in the union do not match; the managed code-default marshalling system has an issue with handling unions that contain both value types and reference types. You can find out more about this topic in the Unions Sample on MSDN.

In order to start extending the example given earlier in this article, you must define managed code structures and classes that map to the structures in the Windows Sockets Library. In order to clarify the naming of the classes, the unmanaged BLOB maps to the BLOB class and the NLA_BLOB maps to the NLA_Info class. The nested structures used in this example are then each explicitly defined. In C#, the way to define a union of these nested structures in the NLA_Info class is to use the LayoutKind.Explicit and the FieldOffset keywords:

    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public class BLOB 
    {
        public UInt32 cbSize;
        public IntPtr pInfo;
    }

    [ StructLayout( LayoutKind.Explicit, Pack=4 )]
    public class NLA_Info
    {
        [ FieldOffset( 0 )]
        public NLA_Header header;

        [ FieldOffset( 12 )]
        public NLA_InterfaceData interfaceData;
        [ FieldOffset( 12 )]
        public NLA_Connectivity connectivity;
        
    }    
    
    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public struct NLA_Header
    {
        public UInt32 type;
        public UInt32 size;
        public UInt32 nextOffset;
    } 

    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public struct NLA_InterfaceData
    {
        public UInt32 type;
        public UInt32 speed;
        public IntPtr adapterName;
    } 

    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public struct NLA_Connectivity
    {
        public UInt32 type;
        public UInt32 internet;
    } 

To use these structures in C#, you must mark your project to Allow Unsafe Code Blocks. This can be done from the Build Configuration properties tab in the Project Properties window. Unsafe code blocks will be required to walk through the memory.

If you are not comfortable with how pointers work and memory is allocated, then consider finding another developer who can work through this with you and ensure that the logic is correct and no memory leaks have been introduced.

The Network Class

It is a good idea to create a class that will encapsulate the additional information about the network connections. Let's call this class Network. In the accompanying sample, this class is called Network.

Within the Network class, three enumerations are defined that map to enumerated types in the Windows Sockets Library. You can use these to discover features of the network:

  • The NLA_CONNECTIVITY_TYPE maps to the Windows Sockets enumerated type of the same name, and contains information on the type of network.
  • The NLA_INTERNET enumeration will be used to determine if the network connection supports internet connectivity. NLA_INTERNET also maps to the enumerated structure of the same name in the Windows Sockets library.
  • The NLA_DATA_TYPE also maps to the unmanaged enumerated type in the Windows Socket library, and is used to determine the type of data available in the structure received from the Windows Sockets Library.
    public enum NLA_CONNECTIVITY_TYPE 
    {
        NLA_NETWORK_AD_HOC    = 0,
        NLA_NETWORK_MANAGED   = 1,
        NLA_NETWORK_UNMANAGED = 2,
        NLA_NETWORK_UNKNOWN   = 3,
    } 

    public enum NLA_INTERNET 
    {
        NLA_INTERNET_UNKNOWN  = 0,
        NLA_INTERNET_NO       = 1,
        NLA_INTERNET_YES      = 2,
    }
    
    public enum NLA_DATA_TYPE 
    {
        NLA_RAW_DATA          = 0,
        NLA_INTERFACE         = 1,
        NLA_802_1X_LOCATION   = 2,
        NLA_CONNECTIVITY      = 3,
        NLA_ICS               = 4,
    }

The Network class has two methods that do most of the work in extracting the extra information about the network connection. The first, the AddInfo method, extracts information from an instance of an NLA_Info class, as shown here:

        protected void AddInfo(NLA_Info info)
        {
            NLA_DATA_TYPE type = (NLA_DATA_TYPE)info.header.type;
            switch (type)
            {
                case NLA_DATA_TYPE.NLA_INTERFACE:
                    this.speed = info.interfaceData.speed;
                    break;
                case NLA_DATA_TYPE.NLA_CONNECTIVITY:
                    this.internet = 
                        (NLA_INTERNET)info.connectivity.internet;
                    this.connectionType = 
                        (NLA_CONNECTIVITY_TYPE)info.connectivity.type;
                    break;
            }
        }

Next, the SetNLAInfo method creates an NLA_Info object from a memory pointer and uses the previous method to extract the information. Notice that this code is marked as unsafe and uses a pointer offset to extract the adaptor name from the IntPtr within the NLA_INTERFACE structure. You do this because it is not possible to overlap value types and reference types in a managed class. The union you implemented in the NLA_Info class cannot contain reference types and so you can only retrieve a pointer to the string containing the adaptor name:

        unsafe public NLA_Info SetNLAInfo(IntPtr pInfo)
        {
            NLA_Info info = new NLA_Info();
            try
            {
                Marshal.PtrToStructure(pInfo, info);
                AddInfo(info);
                if (NLA_DATA_TYPE.NLA_INTERFACE ==
 (NLA_DATA_TYPE)info.header.type )
                {
                    byte* p = (byte*)pInfo;
                    p += 20;
                    adaptor = Marshal.PtrToStringAnsi(new IntPtr(p));
                }
            }
            catch(Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return info;
        }

To provide the Network class with the pInfo IntPtr to the NLA_Info (an unmanaged NLA_BLOB), you must add some code to extract the pointer from the WSAQUERYSET data structure returned. First, get the BLOB structure from the WSAQUERYSET. In the sample code this is done in the GetBlob method that is a member of the NLA class:

    protected BLOB GetBlob(WSAQUERYSET qsResult)
    {
        BLOB blob = new BLOB();
        Marshal.PtrToStructure(qsResult.lpBlob, blob);
        return blob;
    }

This GetBlob method can then be used to retrieve the pointer to the NLA_Info information and fill an instance of a Network class. The GetNetworkDetails method in the NLA class uses the WSAQUERYSET object to extract the BLOB. The pInfo pointer of the BLOB is then used to set the information in the network. The NLA_Info information is contained as a linked list in memory so the do-while loop iterates through each of the structures in the linked list:

    unsafe protected Network GetNetworkDetails(WSAQUERYSET qsResult)
    {
        Network network = 
            new Network(qsResult.szServiceInstanceName ) ;
            
        BLOB blob = GetBlob(qsResult);
        byte* pInfo = (byte*)blob.pInfo;
        NLA_Info info;
        do
        {
            info = network.SetNLAInfo(new IntPtr(pInfo));
                
            pInfo += info.header.nextOffset;
        }while (0 != info.header.nextOffset);

        return network;

    }

In the first part of this article, the code allocated a large fixed block of memory to receive the WSAQUERYSET data that describes the current network connection. This is extremely inefficient and there is no guarantee that the amount of memory allocated is enough for the data being received.

So, let's change the way this method is called. In the following code, the first call to WSALookupServiceNext provides the amount of memory required in the bufferSize variable. The bufferSize is then used to allocate an unmanaged memory block. The call can then be made again with a pointer to the newly allocated memory. This newly allocated memory can then be marshalled into a WSAQUERYSET object.

The first sample was simply allocating a large amount of memory (64K!) in order to ensure that there was enough space to store the network details. This new code is only allocating what is required.

A call is then made to the GetNetworkDetails method, previously discussed. That method returns a Network object with the required information on the network:

    while (0 == result)
    {
        Int32 bufferSize = 0;
        WSAQUERYSET qsResult = new WSAQUERYSET() ;
        IntPtr pqsResult = new IntPtr();
        //get the required buffer size for the network information
        result = WSALookupServiceNext(valHandle, dwControlFlags, 
            ref bufferSize, pqsResult);
        
        if (0 != result && bufferSize > 0 ) 
        {
            pqsResult = Marshal.AllocHGlobal(bufferSize);
            // find the next network in the data set
            result = WSALookupServiceNext(valHandle, dwControlFlags, 
                ref bufferSize, pqsResult);
            // if a valid network is returned
            if (0==result)
            {
                Marshal.PtrToStructure(pqsResult, qsResult);
                //get network details
                Network network = GetNetworkDetails(qsResult);
                networkConnections.Add(network);
            }
                    
            Marshal.FreeHGlobal(pqsResult);
        }
    }
          

Forcing a Deeper Search

At this point, you have code that is capable of reading a set of information of interest about each network connection. It is also quite likely that the code doesn't actually retrieve all of the information you are looking for.

Earlier in this article you discovered that when calling WSALookupServiceBegin you pass a set of control flags. In the previous example the controls flags were set to 0x0FF0, which is the LUP_RETURN_ALL flag. You may have assumed that this would return all the information available about all of the currently connected networks. This assumption is not correct.

There are number of these flags defined in the winsock2.h file, as shown here:

#define LUP_DEEP                0x0001
#define LUP_CONTAINERS          0x0002
#define LUP_NOCONTAINERS        0x0004
#define LUP_NEAREST             0x0008
#define LUP_RETURN_NAME         0x0010
#define LUP_RETURN_TYPE         0x0020
#define LUP_RETURN_VERSION      0x0040
#define LUP_RETURN_COMMENT      0x0080
#define LUP_RETURN_ADDR         0x0100
#define LUP_RETURN_BLOB         0x0200
#define LUP_RETURN_ALIASES      0x0400
#define LUP_RETURN_QUERY_STRING 0x0800
#define LUP_RETURN_ALL          0x0FF0
#define LUP_RES_SERVICE         0x8000

The LUP_RETURN_ALL flag will return some information about all of the networks but it will not walk through the information contained within each network connection structure. To get everything, you must combine the LUP_RETURN_ALL flag with the LUP_DEEP flag. That will force the data being retrieved to be rebuilt from the network drivers. Doing this causes network traffic to be generated, but it will provide a richer set of data.

It is important to know that using the LUP_DEEP flag should only be performed on the call to WSAServiceLookupBegin; the subsequent calls to WSAServiceLookupNext do not require the LUP_DEEP flag to be set. Making the call to WSAServiceLookupNext with LUP_DEEP in the control flags is likely to cause an error, because the current data set being examined will be dropped from memory and then refreshed, losing track of where in the data set your code is currently pointing.

In order to clarify this point, the code needs to look something like this:

    dwControlFlags = 0x0FF1; // LUP_RETURN_ALL | LUP_DEEP 

    int nResult = WSALookupServiceBegin(qsRestrictions,
        dwControlFlags, ref valHandle);

    if (0 !=nResult)
    {
        CheckResult(nResult);
    }

    dwControlFlags = 0x0FF0; 
    while (0 == nResult)
    {. . .

Putting It All Together

You now have the ability to receive notifications, in your managed code, of changes in the network connections, and to encapsulate the information about the connected networks into a collection of Network objects. Each object provides properties describing the network's features. The sample application shows the currently connected networks in a list box. Double clicking on one of the connections displays a dialog box with the details of the connection.

Figure 1. Available network connections

Figure 2. Details for a network connection

The unit tests that were written earlier in this article must be changed to accommodate the change to the Network class to encapsulate the network information. In the accompanying sample code,I created a NetworkMock class to allow test code to set the properties of the network.

    public class NetworkMock:Network
    {
        public NetworkMock(string name):base(name)
     {
     }

        public void Setup(uint speed, 
            NLA_INTERNET internet, 
            NLA_CONNECTIVITY_TYPE connectionType)
        {
            this.speed = speed;
            this.internet = internet;
            this.connectionType = connectionType;
        }
    }

The NetworkOperationTests class then uses this class to create valid and invalid network objects to test the NetworkOperation class.

        protected Network ValidNetwork
        {
            get
            {
                NetworkMock network = new NetworkMock("Valid Network");
                network.Setup(100000, 
                    NLA_INTERNET.NLA_INTERNET_YES, 
                    NLA_CONNECTIVITY_TYPE.NLA_NETWORK_MANAGED );
                return network;
            }
        }

        protected Network InvalidNetwork
        {
            get
            {
                NetworkMock network = 
                    new NetworkMock("Invalid Network");
                network.Setup(10000, 
                    NLA_INTERNET.NLA_INTERNET_NO, 
                    NLA_CONNECTIVITY_TYPE.NLA_NETWORK_UNMANAGED );
                return network;
            }
        }

The NetworkOperationTests class contains a new test to validate that, when there is not a valid connection, the NetworkOperation does not complete:

        
        [Test]
        public void NotCompletedWhenNetworkInvalid()
        {
            networks.Add(InvalidNetwork);
            nla.Setup(networks);
            NetworkOperation operation = new NetworkOperation(nla);
            operation.Start();
            while (operation.Status == 
                NetworkOperationStatus.InProgress)
            {
                Thread.Sleep(100);   
            }
            Assert.AreEqual(NetworkOperationStatus.NotCompleted,       
              operation.Status, 
              "Should not be completed when invalid network present");
        }

In order to use the information within the NetworkOperation class, I added a method to the class that checks for the required network connection. You can change this method to check for whatever criteria you need when performing a network operation in your application. In my sample code, any network that supports Internet connectivity is considered a valid network.

    protected virtual bool RequiredNetworkAvailable(ArrayList networks)
    {
        bool available = false;
        foreach(Network network in networks)
        {
            if (NLA_INTERNET.NLA_INTERNET_YES == 
                network.Internet)
            {
                available = true;
                break;
            }
        }
        return available;
    }

In my sample that accompanies this article, the NetworkOperation class simply checks that there was a network connection available. This was done in the Start method and NetworkChanged events. These have now been changed to use the RequiredNetworkAvailable method:

        public void Start()
        {
            status = NetworkOperationStatus.NotCompleted;
            ArrayList networks = nla.GetConnectedNetworks();

            if (RequiredNetworkAvailable(networks))
            {
                StartNetworkOperationThread();
            }

        }

        private void nla_NetworkChanged(object sender, 
            NetworkChangedEventArgs e)
        {
            if ( ! RequiredNetworkAvailable(e.Networks) )
            {
                networkAvailable.Reset();
            }
            else
            {
                networkAvailable.Set();
                StartNetworkOperationThread();    
            }
        }

The unit tests should all pass and the application will carry out the network operation whenever a network is available that can connect to the Internet.

Conclusion

In this article you have learned how to import the unmanaged functions from the Windows Sockets Library. You have also explored how to use the various unmanaged structures that are required to use those functions.

First you discovered how to use the .NET Framework event model to raise events when the network connections changed, and then you continued to explore the features of the NLA library. You saw how to retrieve extra information about the connected networks and encapsulate the information into a Network class. Using that Network class, you can now create application code that will carry out network operations when certain network features are available.

This article explains the NLA functions, and also how you can validate the behavior of your code through unit tests.

The two sample applications that accompany this article can be used as a starting point for you to add network location awareness to your own applications.

Biography

English born, Dr. Neil travels the world working with software companies. He loves Australia, where he spends the summer enjoying the Sydney lifestyle and helping software development teams get more productive. Dr. Neil spends his other summer each year flying between northern Europe and the USA, working with software teams and writing about his experiences. Neil brings his business and technical skills to the companies he works with to ensure he has happy customers.

You can find out more from his website http://www.Roodyn.com or you can email Dr. Neil at Neil@Roodyn.com.

Show:
© 2014 Microsoft