|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Contents
IntroductionUniversal 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. Requirements for UPnPTo be able to remotely configure a router over a network from a local machine, you need the following:
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. The debate: Convenience vs. SecurityBefore 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. The demo programThe 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 The engineThe 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 // simplified view of PortForwardEngine.h class CPortForwardEngine { public: CPortForwardEngine(); virtual ~CPortForwardEngine(); HRESULT ListenForUpnpChanges( 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 The For device discovery, your application should call the Here's an explanation of the inner workings of IUPnPDevice::IconURL() IUPnPDevice::get_Property() IUPnPDevice::get_Children() IUPnPDevice::get_Description() IUPnPDevice::get_FriendlyName() IUPnPDevice::get_HasChildren() IUPnPDevice::get_IsRootDevice() IUPnPDevice::get_ManufacturerName() IUPnPDevice::get_ManufacturerURL() IUPnPDevice::get_ModelNumber() IUPnPDevice::get_ModelName() IUPnPDevice::get_ModelNumber() IUPnPDevice::get_ModelURL() IUPnPDevice::get_ParentDevice() IUPnPDevice::get_PresentationURL() IUPnPDevice::get_RootDevice() IUPnPDevice::get_SerialNumber() IUPnPDevice::get_Services() IUPnPDevice::get_Type Uniform() IUPnPDevice::get_UniqueDeviceName() IUPnPDevice::get_UPC() Frankly speaking, once the For retrieval of mappings, your application should call the The Inside the thread created by the IStaticPortMapping::get_ExternalIPAddress() IStaticPortMapping::get_ExternalPort() IStaticPortMapping::get_InternalPort() IStaticPortMapping::get_Protocol() IStaticPortMapping::get_InternalClient() IStaticPortMapping::get_Enabled() IStaticPortMapping::get_Description() Much the same processing is performed inside the threads created by the other port-mapping functions (i.e., inside the threads created by IStaticPortMapping::Enable() IStaticPortMapping::EditDescription() IStaticPortMapping::EditInternalPort() IStaticPortMapping::EditInternalClient() 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 Because To get these notifications in your application, call class CPortForwardChangeCallbacks { public: CPortForwardChangeCallbacks(); virtual ~CPortForwardChangeCallbacks(); virtual HRESULT OnNewNumberOfEntries( long lNewNumberOfEntries ); virtual HRESULT OnNewExternalIPAddress( BSTR bstrNewExternalIPAddress ); }; To use your 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. The UIThe UI uses the engine in fairly unsurprising ways. When the program is started, it immediately calls
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
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; afx_msg LRESULT CPortForwardView::OnMappingThreadNotificationMessage( WPARAM wParam, LPARAM lParam) { switch ( wParam & 0x00F0 ) { case msgPortRetrieve: if ( wParam == CPortForwardEngine::EnumPortRetrieveInterval ) { // this is a periodic notification message; // update the progress control m_ctlProgressComUpdate.SetPos( lParam ); } else if ( wParam == CPortForwardEngine::EnumPortRetrieveDone ) { // the thread is finished if ( !SUCCEEDED(lParam) ) { // error: display message and take other action } else { // finished with no error, // get the vector of mappings and use it std::vector<CPortForwardEngine:: PortMappingContainer> mappingContainer; mappingContainer = m_PortForwardEngine.GetPortMappingVector(); // display the port mapping and otherwise use them, etc.... } // restore the appearance of the UI } break; case msgDeviceInfo: if ( wParam == CPortForwardEngine::EnumDeviceInfoInterval ) { // this is a periodic notification message; // update the progress control m_ctlProgressIgdDeviceInfo.SetPos( lParam ); } else if ( wParam == CPortForwardEngine::EnumDeviceInfoDone ) { if ( SUCCEEDED(lParam) ) { // finished with no error; // get device information and use it m_DeviceInfoContainer = m_PortForwardEngine.GetDeviceInformationContainer( ); // display the device information // and otherwise use it, etc... } else { // error: display message and take other action } // restore the appearance of the UI } break; case msgAddMapping: if ( wParam == CPortForwardEngine::EnumAddMappingInterval ) { // this is a periodic notification message; // update the progress control m_ctlProgressAddUpdate.SetPos( lParam ); } else if ( wParam == CPortForwardEngine::EnumAddMappingDone ) { // the thread is finished if ( !SUCCEEDED(lParam) ) { // error: display message and take other action } else { // finished with no error } // restore the appearance of the UI } break; case msgEditMapping: // ... same as above for msgAddMapping break; case msgDeleteMapping: // ... same as above for msgAddMapping break; default: ASSERT ( FALSE ); // should never get here } return 0L; } The The precise values of the 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
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. Installation packageThe 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. A quick rantAlthough 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 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 Likewise, the documentation for 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. Bibliography and reference materialsHere, 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:
COM-related links:
Inno Setup:
License informationThe 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. History
| ||||||||||||||||||||