Click here to Skip to main content
Click here to Skip to main content

NAT Traversal with UPnP in C#

By , 14 Jan 2009
Rate this:
Please Sign up or sign in to vote.

Introduction

In writing network code, NAT can be a real problem. For most applications, requiring the user to add port forwarding rules to their router is not a good idea, or even acceptable, because often users are not allowed to change the settings of their router or do not know how to do so.

Fortunately, there are ways to automate the process of adding port forwarding rules. UPnP is one of the ways, to my knowledge the most commonly used way.

Another major problem with networking programs is that they appear to have no reliable way of finding the external IP address of the computer it is running on. UPnP partly solves this, but it is, of course, not completely reliable (not all routers support it, and routers that do may have UPnP turned off for security reasons).

If you have ever tried to use existing UPnP libraries, you may have found that most of them simply do not work, or do not cover the NAT aspects of it. This prompted me to write my own, very small, UPnP library that only covers the NAT aspects, and not even fully at that - only UPnP device discovery, basic port forwarding, and retrieving the external IP address. However, for many networking programs, this is all that is needed.

Using the code

In the source code, you will find a public class NAT, containing the following public methods:

  • static bool Discover()
  • static IPAddress GetExternalIP()
  • static void ForwardPort(int port, ProtocolType protocol, string description)
  • static void DeleteForwardingRule(int port, ProtocolType protocol)

Please note that these methods only do limited error checking, and will fail if there is no UPnP device or if the first UPnP device is too picky about the command syntax. Before GetExternalIP or ForwardPort will work, Discover has to be called. Discover can take up to 3 seconds by default (it will time out after that - you can change the time out value), possibly minutes if you set the time out high, so for any GUI application, it is advisable to place the call on a separate thread.

To forward a port the simplest way, code like this will suffice:

UPnP.NAT.Discover();
UPnP.NAT.ForwardPort(12345, ProtocolType.Tcp, "MyApp (TCP)");

In practice, it would be best to check for exceptions - and the value returned by Discover.

UPnP + NAT

For the people who want to do it themselves, or who are interested, here is what I found out about UPnP in combination with NAT routers.

The first thing to do is find the UPnP device you are interested in. Apparently, the best way to do that is by using SSDP.

Finding the UPnP device using SSDP comes down to broadcasting a single packet over UDP and examining the replies. The packet looks like this (or at least, when it looks like this, it'll work):

"M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"ST:upnp:rootdevice\r\n" +
"MAN:\"ssdp:discover\"\r\n" +
"MX:3\r\n\r\n";

in ASCII. While the Host is 239.255.255.250, the packet still has to be broadcasted to the standard IPAddress.Broadcast address.

Examining the replies is pretty straightforward. You should only get one, but if you get more anyway, the right one is identifiable. A standard reply from a NAT device looks like this:

HTTP/1.1 200 OK
ST:upnp:rootdevice
USN:uuid:00-0C-F6-12-95-D3-FE7BA8C00::upnp:rootdevice
Location:http://192.168.123.254:80/desc.xml
Cache-Control:max-age=1800
Server:IGD-HTTP/1.1 UPnP/1.0 UPnP-Device-Host/1.0
Ext:

The ST should be "upnp:rootdevice"; if it's not, it isn't the right device, and it shouldn't have replied in the first place, but one never knows what happens in a network.

Anyhow, we're interested in the location, since at that location there will be an XML file containing information about the device - we will need this to find out how to send commands to the device.

Sending commands to a UPnP device is made possible through something they called a "service". The XML file describing the device is quite long, so suffice it to know that the XPath to the service we're interested in is:

"//tns:service[tns:serviceType=\"urn:schemas-upnp-
     org:service:WANIPConnection:1\"]/tns:controlURL/text()"

Since it uses namespaces, you will have to use an XmlNamespaceManager, but that's a whole different subject.

The result of this query should look somewhat like "/serv3.xml", once again an XML file. Note that this is a relative location, so you will need to append it to the base-URL, which is "http://192.168.123.254:80" in this example.

This file has a double purpose: first, its contents tell you something about what kinds of commands it accepts and how it will reply to them, and second, you will be POST-ing SOAP to it. That's correct, XML again.

Before this, I had never used a HTTP-POST on an XML file, and it greatly surprised me, but apparently, this is the way it should work.

Sending commands then is just using SOAP in the right way. However, this is not very straightforward - if you are using a WebRequest (as am I), then you will not only have to set the Method to "POST", but also add a header, and change the content-type:

Headers.Add("SOAPACTION", 
  "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + 
  function + "\"");
ContentType = "text/xml; charset=\"utf-8\"";

The variable function here must be the same as the function you are sending in the contents.

That is pretty much all there is to it. With this information, you should be able to implement your own UPnP NAT traversal methods.

Points of interest

While writing the code for this tiny library, I was absolutely stunned by the complete absence of clear information on the subject. Most sites deal only with UPnP for media and such, or explain the lack of security of UPnP NAT-traversal, without going very far into details on how to actually do it. When there is such information, it is generally written in such a way that at least I do not understand half of it.

In the end, I used WireShark to examine µTorrent's UPnP traffic, and reverse-engineered the process. I hope that this article changes matters for some people so that they will not have to reverse-engineer anything to get UPnP NAT-traversal working.

History

  • July 18, 2008: Wrote the first version of the UPnP NAT library.
  • July 22, 2008: Wrote the first version of this article.
  • July 22, 2008 (later that day): Updated the source and the article to work on some stricter routers and in cases where the port is not 80 (with thanks to ajiau).
  • July 23, 2008: Updated source to work with some additional routers (thanks to Chris Harper)
  • January 14, 2009: Updated source and article to (hopefully) work with yet some more additional routers, with thanks to everyone who posted feedback in the past half year.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication

About the Author

harold aptroot

Netherlands Netherlands
No Biography provided

Comments and Discussions

 
QuestionAmazing! :) PinmemberHans77Lindgren21-Feb-14 21:48 
SuggestionWindows 8 (x64) + miniupnpd = Internal Server Error 500 PinmemberCNefarius9-Dec-13 12:23 
GeneralRe: Windows 8 (x64) + miniupnpd = Internal Server Error 500 Pinmemberharold aptroot9-Dec-13 22:23 
QuestionThis code will not work reliably Pinmembercodeprojectsucks19-Dec-13 4:27 
GeneralRe: This code will not work reliably Pinmemberharold aptroot9-Dec-13 4:49 
GeneralRe: This code will not work reliably Pinmembercodeprojectsucks110-Dec-13 5:24 
GeneralRe: This code will not work reliably Pinmemberharold aptroot10-Dec-13 6:41 
QuestionMono.Nat is too complicated and it isn't cross platform. PinmemberCleveland Mark Blakemore25-Jun-13 15:23 
QuestionNot working with Hyper-V virtual switch Pinmembersensboston3-Jun-13 8:44 
GeneralRe: Not working with Hyper-V virtual switch Pinmemberharold aptroot3-Jun-13 22:35 
GeneralRe: Not working with Hyper-V virtual switch [modified] Pinmembersensboston4-Jun-13 7:56 
QuestionFurther question PinmemberJohnson.Gao.12623-May-13 20:14 
AnswerRe: Further question Pinmemberharold aptroot23-May-13 21:38 
GeneralRe: Further question PinmemberJohnson.Gao.12624-May-13 19:05 
QuestionMy Vote! PinmemberLokukarawita20-May-13 21:53 
QuestionNot working somehow, im a noob sry PinmemberMember 975830115-Jan-13 9:15 
GeneralRe: Not working somehow, im a noob sry Pinmemberharold aptroot15-Jan-13 9:19 
AnswerRe: Not working somehow, im a noob sry PinmemberMember 975830115-Jan-13 9:33 
GeneralRe: Not working somehow, im a noob sry Pinmemberharold aptroot15-Jan-13 9:44 
QuestionDoubt Pinmemberleela prasanth2-Nov-12 2:27 
AnswerRe: Doubt Pinmemberharold aptroot2-Nov-12 4:45 
QuestionRegarding UPnP Pinmemberbegi2127-Oct-12 7:07 
GeneralRe: Regarding UPnP Pinmemberharold aptroot27-Oct-12 7:26 
QuestionHow to run this, what should i call? Please someone make a VB.NET equivalent conversion [modified] Pinmemberbegi2127-Oct-12 5:40 
GeneralRe: How to run this, what should i call? Please someone make a VB.NET equivalent conversion Pinmemberharold aptroot27-Oct-12 6:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 14 Jan 2009
Article Copyright 2008 by harold aptroot
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid