Click here to Skip to main content
15,868,164 members
Articles / Programming Languages / C#

NAT Traversal with UPnP in C#

Rate me:
Please Sign up or sign in to vote.
4.89/5 (44 votes)
14 Jan 2009Public Domain5 min read 519.7K   10.2K   145   219
NAT traversal with UPnP in C#, without any libraries.

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:

C#
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:

C#
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



Comments and Discussions

 
GeneralRe: Fail with ForwardPort Pin
SDI200329-Dec-08 12:45
SDI200329-Dec-08 12:45 
GeneralRe: Fail with ForwardPort Pin
triplebit4-Jan-09 9:33
triplebit4-Jan-09 9:33 
GeneralRe: Fail with ForwardPort Pin
SDI20034-Jan-09 18:44
SDI20034-Jan-09 18:44 
GeneralRe: Fail with ForwardPort Pin
triplebit6-Jan-09 5:13
triplebit6-Jan-09 5:13 
GeneralRe: Fail with ForwardPort Pin
SDI20036-Jan-09 5:39
SDI20036-Jan-09 5:39 
GeneralRe: Fail with ForwardPort Pin
triplebit7-Jan-09 8:35
triplebit7-Jan-09 8:35 
GeneralRe: Fail with ForwardPort Pin
SDI20038-Jan-09 8:56
SDI20038-Jan-09 8:56 
GeneralRe: Fail with ForwardPort Pin
triplebit9-Jan-09 4:07
triplebit9-Jan-09 4:07 
Thanks Steve,
1) What is the value of the serviceurl when the program enters the routine.

The value is null.

2) What is the router that you are using.

The router is AZTECH DSL-600E

3) if you comment out the condition,the exception line leaving only the soap request line you should get a better exception

I commented out the condition in ForwardPort(since this is the line that fails), but I still I get an exception in the same line SoapRequest, as indicated before.
Thanks in advance
I. Sher

public static void ForwardPort(int port, ProtocolType protocol, string description)
{
//if (string.IsNullOrEmpty(serviceUrl))
// throw new Exception("No UPnP service available or Discover() has not been called");
XmlDocument xdoc = SOAPRequest(serviceUrl, "<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + port.ToString() + "</NewExternalPort><NewProtocol>" + protocol.ToString().ToUpper() + "</NewProtocol>"+
"<NewInternalPort>" + port.ToString() + "</NewInternalPort><NewInternalClient>" + Dns.GetHostAddresses(Dns.GetHostName())[0].ToString() +
"</NewInternalClient><NewEnabled>1</NewEnabled><NewPortMappingDescription>" + description +
"</NewPortMappingDescription><NewLeaseDuration>0</NewLeaseDuration></u:AddPortMapping>\r\n", "AddPortMapping");
}
GeneralRe: Fail with ForwardPort Pin
harold aptroot9-Jan-09 6:44
harold aptroot9-Jan-09 6:44 
GeneralRe: Fail with ForwardPort Pin
SDI20039-Jan-09 7:42
SDI20039-Jan-09 7:42 
GeneralRe: Fail with ForwardPort Pin
triplebit9-Jan-09 20:23
triplebit9-Jan-09 20:23 
GeneralRe: Fail with ForwardPort Pin
SDI200310-Jan-09 3:06
SDI200310-Jan-09 3:06 
GeneralRe: Fail with ForwardPort Pin
triplebit10-Jan-09 7:43
triplebit10-Jan-09 7:43 
GeneralRe: Fail with ForwardPort Pin
SDI200310-Jan-09 8:30
SDI200310-Jan-09 8:30 
GeneralRe: Fail with ForwardPort Pin
triplebit10-Jan-09 9:28
triplebit10-Jan-09 9:28 
GeneralRe: Fail with ForwardPort Pin
SDI200310-Jan-09 12:14
SDI200310-Jan-09 12:14 
GeneralRe: Fail with ForwardPort Pin
triplebit10-Jan-09 17:45
triplebit10-Jan-09 17:45 
GeneralRe: Fail with ForwardPort Pin
triplebit10-Jan-09 17:45
triplebit10-Jan-09 17:45 
GeneralRe: Fail with ForwardPort Pin
SDI200310-Jan-09 18:22
SDI200310-Jan-09 18:22 
GeneralRe: Fail with ForwardPort Pin
triplebit11-Jan-09 6:12
triplebit11-Jan-09 6:12 
GeneralRe: Fail with ForwardPort Pin
triplebit11-Jan-09 6:16
triplebit11-Jan-09 6:16 
GeneralRe: Fail with ForwardPort Pin
SDI200311-Jan-09 9:42
SDI200311-Jan-09 9:42 
GeneralRe: Fail with ForwardPort Pin
triplebit11-Jan-09 10:22
triplebit11-Jan-09 10:22 
GeneralRe: Fail with ForwardPort Pin
SDI200311-Jan-09 13:57
SDI200311-Jan-09 13:57 
GeneralRe: Fail with ForwardPort Pin
triplebit12-Jan-09 7:48
triplebit12-Jan-09 7:48 

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

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