Universal Plug-n-Play ("UPnP") is an attempt to extend the concept of ordinary plug-n-play, so that it applies to more than just your own machine: it applies to the whole network. For example, with ordinary plug-n-play, when a new peripheral is connected to your machine, it is automatically discovered and configured from your machine without access to the peripheral itself. UPnP extends this idea to the network: when a new network device is connected to the network, it can be automatically discovered over the network, and configured remotely from your machine over the network.
The idea is that a device can dynamically join a wired or wireless network, obtain an IP address, convey its capabilities, and learn about the presence and capabilities of other devices all over the network. UPnP envisions a future where all devices are networkable and controllable over the network, such as light switches, thermostats, toasters, automobiles, etc. More information can be found here.
Since 2002, most routers have UPnP capability. This allows you to solve one of the more vexing problems for users of network programs that must accept an incoming connection from the Internet. Examples of these programs include P2P file sharing programs, interactive games and gaming, video conferencing, and web or proxy servers. To allow others on the Internet to connect to these programs, it is necessary to configure the router to accept incoming connections and to route the connection to a local machine on the LAN behind the router. This process is called "port forwarding" or "NAT traversal" ("Network Address Translation").
For the ordinary user, this process can be daunting. Moreover, it's often not easy to explain how to configure a router for port forwarding, for the reason that the method differs for each different type of router by each different manufacturer.
UPnP works perfectly in this situation. With it, you can map a port-forwarding programmatically without user interaction.
This article describes a utility that discovers current port mappings on a UPnP-enabled router, and allows you to add/edit/delete mappings. The utility is conceptually broken into two pieces: an engine that performs the actual work, and the UI that uses the engine. This way, it should be possible for you to re-use the engine for your own purposes.
go back to top
To be able to remotely configure a router over a network from a local machine, you need the following:
- UPnP on the local machine: Basically, you must have Windows® XP, any service pack. Older versions of Windows, including the very-popular Windows® 2000, will not work, as they do not have UPnP capability. It might also be necessary to enable UPnP, since a UPnP-capable OS does not necessarily have it turned on by default.
- UPnP on your router: Most routers manufactured since 2002 will have UPnP capability. Again, it might be necessary to enable UPnP on the router, since it might not be turned on by default.
In addition, if there is a firewall on the local machine, it must be configured to allow the underlying TCP and UDP communications on which UPnP relies. Specifically, it must be configured to pass TCP port 2869 and UDP port 1900.
go back to top
Before going on, it's worthwhile to point out the current debate over whether it makes any sense to include UPnP capability on a router.
Proponents argue based on convenience: It's hard to configure a router for port forwarding, and average users are not able to do it easily. There are different methods for each different router, and even if you can find the correct method, understanding it requires the user to learn confusing network terminology.
Opponents argue that there's a significant security risk: Routers insulate local networks from the wilds of the Internet, by blocking incoming connections that are almost always malicious. This insulation is at the hardware level, and as such, it is often more effective than software such as software firewalls. Most users rely on this added layer of insulation to protect machines on their local network, and the average user relies on it without even being aware of it. But since UPnP allows any program, even malicious programs, to create a port mapping through the router, this added layer of insulation disappears. Moreover, with UPnP, the port mapping can be created even without any knowledge of the administrative password to the router, and thus can be created without the knowledge or consent of the user.
For me, I side with the opponents of UPnP for routers. The added layer of security is a true benefit, and it benefits those most likely to need it (i.e., relatively unsophisticated users who are most often targeted by malicious programs). Moreover, although manual configuration of port mappings is complicated, the programs that absolutely need it are also complicated, and require a relatively higher level of user sophistication anyway. Finally, there are many alternative program architectures that do not rely on accepting incoming connections from the Internet (and hence do not need port mappings at all); these architectures usually require a third machine somewhere on the Internet (such as a rendezvous server or a relay server), but they eliminate the need for incoming connections, and operate just fine with outgoing connections only.
But "security through obscurity" is never the answer. UPnP is here. Here's how to use it.
go back to top
The demo program is conceptually broken into two parts: an engine for the discovery of port mappings and for changing them, and a UI that uses the engine to allow the user to do what he wants. The source code itself is set up in a VC++ 6.0 workspace with four different configurations: Unicode and non-Unicode, debug and release for both.
Microsoft's implementation of UPnP relies on COM, so you might need to become familiar with COM-style types such as
VARIANT. The source code makes liberal use of ATL macros such as
T2OLE for conversion, where needed.
go back to top
The engine actually performs three distinct types of tasks: device discovery (i.e., finding the router and getting the device information about it), retrieval of and changes to port mappings, and change-event notifications. Here are the
CPortForwardChangeCallbacks *pCallbacks = NULL);
HRESULT StopListeningForUpnpChanges( );
BOOL GetDeviceInformationUsingThread( HWND hWnd );
BOOL GetMappingsUsingThread( HWND hWnd );
BOOL EditMappingUsingThread( PortMappingContainer& oldMapping,
PortMappingContainer& newMapping, HWND hWnd );
BOOL AddMappingUsingThread( PortMappingContainer& newMapping,
HWND hWnd );
BOOL DeleteMappingUsingThread( PortMappingContainer& oldMapping,
HWND hWnd );
std::vector<PortMappingContainer> GetPortMappingVector() const;
DeviceInformationContainer GetDeviceInformationContainer() const;
BOOL IsAnyThreadRunning() const;
The first thing you might notice is the reliance on threads. As implemented by Microsoft, UPnP relies on COM, and in this instance, COM is slow, usually requiring around three (3) seconds to complete, and sometimes requiring as many as ten (10) seconds. Not all the COM-related UPnP methods block during the time that they execute, but many do. The engine is therefore multi-threaded so that calls to its methods will not block your UI.
The threads created by the engine post notification messages to your UI to advise your application of progress through the thread's execution, and to advise you when the thread is complete. This is the reason why the threaded functions each take a
HWND as a parameter; this parameter is the window to which messages are posted. The same message is used for all of the engine's functions; the message is a
UWM_PORT_FORWARD_ENGINE_THREAD_NOTIFICATION. The meaning of the message is encoded in the
LPARAM values of the message. The actual value of the message is obtained by a call to
::RegisterWindowMessage(), so your UI must be prepared to handle registered messages. For MFC users, this means that your message map will use the
IsAnyThreadRunning() function is provided so that you can test whether any thread is running, before the shut-down of the program. This allows the thread object to
delete itself, and prevents unintended memory leaks of
For device discovery, your application should call the
GetDeviceInformationUsingThread() function, which tries to find a UPnP-enabled router on the local network and to obtain information about the router (such as model name, manufacturer etc.) if a router is found. When the thread is finished (as signified by the thread's posting of a
UWM_PORT_FORWARD_ENGINE_THREAD_NOTIFICATION message), the UI can call
GetDeviceInformationContainer() to get a structure containing information about the device. Device discovery is based on the COM interface
IUPnPDeviceFinder, which is part of Microsoft's "Control Point API". Documentation can be found at MSDN entitled "Control Point API Reference".
Here's an explanation of the inner workings of
GetDeviceInformationUsingThread(). Inside the thread created by the
CoCreateInstance() is called to get an instance of
IUPnPDeviceFinder::FindByType() is called to get a
IUPnPDevices collection of devices that match the requested type of device. The collection is enumerated/traversed to find each individual
IUPnPDevice interface, and the following functions are called on the
IUPnPDevice interface (see the MSDN documentation for
Frankly speaking, once the
IUPnPDevices collection is obtained, the code is a bit tedious. The code is based in part on the sample code found at MSDN: "Device Collections Returned by Synchronous Searches".
For retrieval of mappings, your application should call the
GetMappingsUsingThread() function. When the function's thread completes (again, as signified by the thread's posting of a
UWM_PORT_FORWARD_ENGINE_THREAD_NOTIFICATION message to your UI), the UI can call
GetPortMappingVector() to get a
std::vector which contains a collection of structures, each containing information about one mapping. Your application can also make changes to the mappings using the three self-explanatory functions of
GetMappingsUsingThread() function is based on the COM interface
IUPnPNAT, which is part of Microsoft's "NAT Traversal API". Documentation on this interface is scarce. For some reason, Microsoft has chosen to group this API with its API for "Internet Connection Sharing and Internet Connection Firewall". In addition to the difficulties caused by grouping NAT traversal with connection sharing, the on-line MSDN documentation for the NAT Traversal API does not have a separate entry in the table of contents, and does not sync well. The base page for documentation on
IUPnPNAT is found at MSDN, entitled (simply) "IUPnPNAT".
Inside the thread created by the
CoCreateInstance() is called to get an instance of
IUPnPNAT::get_StaticPortMappingCollection() is called to get a
IStaticPortMappingCollection collection of static port mappings. The collection is enumerated/traversed to find each individual
IStaticPortMapping interface, and the following functions are called on the
IStaticPortMapping interface (see the MSDN documentation for
Much the same processing is performed inside the threads created by the other port-mapping functions (i.e., inside the threads created by
DeleteMappingUsingThread()), except that different ones of the
IStaticPortMapping functions are called, as follows:
Event notification is interesting: every time there is a change in your router's configuration, it broadcasts (UDP) the change over the network. Microsoft's COM interface to UPnP can be configured to listen for these broadcasts, and to call callbacks within your program (if you register the callbacks properly). The changes are most commonly a change in a port mapping, but a notification is also received when there is a change in the router's external IP address.
Implementation of event notification requires an actual implementation of all the
virtual functions for the two COM interfaces of
INATNumberOfEntriesCallback. Then, an interface to the
IUPnPNAT's event manager (
INATEventManager) is obtained through a call to
IUPnPNAT::get_NATEventManager(). Using the
INATEventManager interface, it's possible to register the implementation of the derived
INATExternalIPAddressCallback interface (
INATEventManager::put_ExternalIPAddressCallback()) and to register the implementation of the derived
INATNumberOfEntriesCallback interface (
INATNumberOfEntriesCallback are, in a sense, COM servers, it is necessary to run them in the same thread as your main program, and that's how they're implemented (in a single-threaded apartment ("STA") model). Thus, event notifications are not run in a separate thread, unlike all the other functions we have discussed so far.
To get these notifications in your application, call
ListenForUpnpChanges(). The function takes a pointer to a
CPortForwardChangeCallbacks object, but if you pass in
NULL, the engine will use a default object. (
CPortForwardChangeCallbacks is defined in the same source and header files as
CPortForwardEngine, which is usually not recommended but which helps to keep these classes all in one place.) The default object simply displays a
::MessageBox() indicating that there has been a change. For more elaborate handling of change-notification-events, derive your own class from
CPortForwardChangeCallbacks and override the
OnNewExternalIPAddress(). Here is the definition of the
virtual HRESULT OnNewNumberOfEntries(
long lNewNumberOfEntries );
virtual HRESULT OnNewExternalIPAddress(
BSTR bstrNewExternalIPAddress );
To use your
new one of them on the heap and pass its pointer to the
ListenForUpnpChanges() function, like so:
ListenForUpnpChanges( new CMyDerivedPortForwardChangeCallbacks() );
You do not need to keep track of the new'd pointer; the engine will automatically delete the object for you when it's finished with it.
go back to top
The UI uses the engine in fairly unsurprising ways. When the program is started, it immediately calls
GetDeviceInformationUsingThread() to get and display information about any UPnP-enabled routers on the LAN. If a router is found, its name is displayed, and more information about it can be displayed by clicking on the "More information ..." button, which displays all the information obtained from the
GetDeviceInformationUsingThread() function in the following dialog:
Below the list of port mappings are four buttons that allow the user to retrieve port mappings from the router and to edit/add/delete them. Clicking one of these buttons invokes a corresponding one of the thread functions
DeleteMappingUsingThread(). In addition, hidden progress bar controls are shown, and other changes are made to the appearance of the UI. Here's an example of the dialog that you see when a port mapping is added; a similar dialog is displayed when a mapping is edited. Only a confirmation-style dialog is displayed when a mapping is deleted.
As mentioned above, the threads post messages to the UI to advise it of the thread's progress. Here's a simplified view of the message-handler in the UI, which responds to these messages:
static const int msgPortRetrieve =
0x00F0 & CPortForwardEngine::EnumPortRetrieveDone;
static const int msgDeviceInfo =
0x00F0 & CPortForwardEngine::EnumDeviceInfoDone;
static const int msgAddMapping =
0x00F0 & CPortForwardEngine::EnumAddMappingDone;
static const int msgEditMapping =
0x00F0 & CPortForwardEngine::EnumEditMappingDone;
static const int msgDeleteMapping =
0x00F0 & CPortForwardEngine::EnumDeleteMappingDone;
WPARAM wParam, LPARAM lParam)
switch ( wParam & 0x00F0 )
if ( wParam == CPortForwardEngine::EnumPortRetrieveInterval )
m_ctlProgressComUpdate.SetPos( lParam );
else if ( wParam == CPortForwardEngine::EnumPortRetrieveDone )
if ( !SUCCEEDED(lParam) )
if ( wParam == CPortForwardEngine::EnumDeviceInfoInterval )
m_ctlProgressIgdDeviceInfo.SetPos( lParam );
else if ( wParam == CPortForwardEngine::EnumDeviceInfoDone )
if ( SUCCEEDED(lParam) )
if ( wParam == CPortForwardEngine::EnumAddMappingInterval )
m_ctlProgressAddUpdate.SetPos( lParam );
else if ( wParam == CPortForwardEngine::EnumAddMappingDone )
if ( !SUCCEEDED(lParam) )
ASSERT ( FALSE ); }
enums are defined in the
CPortForwardEngine class. Basically, there are five kinds of messages (as measured by the
WPARAM value of the message): messages for device information, for port mapping retrieval, for editing a mapping, for deleting a mapping, and for adding a mapping. There's a separate section of a
switch statement for each different type of message, and each section then interprets the value passed by the
LPARAM of the message.
The precise values of the
WPARAMs and the
LPARAMs are explained in the comments in the source code for the engine.
Change-event notifications are selected by checking the box labeled "Automatically listen for changes in the router". When this box is checked, the UI calls the engine's
ListenForUpnpChanges() function, providing the engine with a pointer to a
CPortForwardChangeCallbacks-derived object. The derived class is only a bit more complex than the default class: it simply displays a message asking the user if he wants to update the list of port mappings automatically now or manually later. Here's an example of the notification dialog when a change is detected in the number of mappings; a similar dialog is also displayed if a change is detected in the external IP address of the router:
The UI also has a built-in web server, which is a nice feature for testing whether a port-forward mapping is operational. The user checks the box labeled "Start Web server", which in turn enables the web-server controls on the right-hand side of the display. The user can then test for incoming Internet connectivity, with the test results being displayed in the user's default browser.
To get this to work, the user must create a port mapping on the same port as the listening port (which defaults to the arbitrary value of "9542" in the program). Once the port mapping has been created, and the program is set to listen for incoming connections, the test will cause a connection to be made to an external web-based proxy, which in turn will connect back through the user's machine using the newly-created port mapping. A successful test will look like this:
If the test fails, it's possible that the external proxy is not working properly. The program is pre-configured with two proxies, so one of them should always be working. New proxies can be added (and non-working ones deleted) through modifications to the program's PortForward.ini file. Other changes can also be made to this ini file, which is largely self-explanatory.
go back to top
The download includes an installation package that installs the PortForward executable along with a short "Help" file (PortForward.chm). The installation package is named "PortForward100-Setup.exe", and the only changes made to your machine are the creation of a new directory (usually "c:\\Program Files\PortForward") and installation there of the executable and a few support files. There are no other changes made to your machine, not in the registry, not in the Windows folder, not anywhere else. There's even an un-installation program, so you can install the program with confidence. This installation package was built using Inno Setup, which I highly recommend.
go back to top
Although this article is finished, I can't resist the opportunity for a quick rant.
Why is it so hard to find documentation about this stuff? COM is difficult enough as it is (at least for me) without those difficulties being compounded by documentation that's hard to find, poorly organized, and incomplete. Microsoft's documentation for the
IUPnPNAT interface, for example, is terrible. Just learning that
IUPnPNAT is the interface you need is hard (search MSDN for "NAT Traversal API" and you won't find it). Once you determine that
IUPnPNAT is the right interface, you're faced with a documentation that's poorly organized (why is it grouped as part of the ICS/Firewall API?) and largely incomplete. As one example, you can use the
IUPnPNAT interface to get a collection of port mappings called
IStaticPortMappingCollection, from which you can get an enumerator for each individual mapping. Not terribly straightforward, but normal enough in the COM world. But the enumerator that's returned is actually an
IUnknown, and you need to guess at which one of the ga-jillion different kinds of
IEnumXXXXs it actually is (it's an
IEnumVARIANT, in case you're interested, which might seem evident after the fact but it certainly was not clear to me beforehand).
And that's nothing compared to the pain of implementing callback interfaces for the
Web searching doesn't help much either. At the time of writing (February 2006), a Google search for
IUPnPNAT turned up exactly 36 hits. 36!! And most of those hits were from people looking for help.
Likewise, the documentation for
IUPnPDeviceFinder could be much better. For example, to find a router, you need to give
BSTR that "specifies the type URI for the device". What's that? Searching at the UPnP.org website provided no help; there's nothing there that gives examples of the URI naming convention, or provides examples of the commonly-used URIs.
At the end of the day, the "type URI" for a router turns out to be "urn:schemas-upnp-org:device:InternetGatewayDevice:1". No kidding. Does that surprise you or in any way seem to you like it might be self-explanatory or intuitive?
Rant over. Thanks for listening.
go back to top
Here, in one place, is a list of all the articles and links mentioned in the article, as well as a few extra articles that might be helpful. Clicking the link will open a new window.
General UPnP and NAT translation links:
go back to top
The source code is licensed under the MIT X11-style open source license. Basically, under this license, you can use/modify the code for almost anything you want. For a comparison with the BSD-style license and the much more restrictive GNU GPL-style license, click here.
- 2nd March, 2006
- Original release of the code and this article.
- 12th March, 2006
- Updated the article to include a few more graphics and other changes. No change to the code.
go back to top