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

NAT Traversal with UPnP in C#

By , 14 Jan 2009
 

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
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionMy Vote!memberLokukarawita20 May '13 - 21:53 
QuestionNot working somehow, im a noob srymemberMember 975830115 Jan '13 - 9:15 
GeneralRe: Not working somehow, im a noob srymemberharold aptroot15 Jan '13 - 9:19 
AnswerRe: Not working somehow, im a noob srymemberMember 975830115 Jan '13 - 9:33 
GeneralRe: Not working somehow, im a noob srymemberharold aptroot15 Jan '13 - 9:44 
QuestionDoubtmemberleela prasanth2 Nov '12 - 2:27 
AnswerRe: Doubtmemberharold aptroot2 Nov '12 - 4:45 
QuestionRegarding UPnPmemberbegi2127 Oct '12 - 7:07 
GeneralRe: Regarding UPnPmemberharold aptroot27 Oct '12 - 7:26 
QuestionHow to run this, what should i call? Please someone make a VB.NET equivalent conversion [modified]memberbegi2127 Oct '12 - 5:40 
GeneralRe: How to run this, what should i call? Please someone make a VB.NET equivalent conversionmemberharold aptroot27 Oct '12 - 6:19 
GeneralMessage Removedmemberbegi2127 Oct '12 - 6:33 
GeneralRe: How to run this, what should i call? Please someone make a VB.NET equivalent conversionmemberharold aptroot27 Oct '12 - 6:52 
QuestionIs this updated?memberTripleA Developers25 Sep '12 - 23:12 
AnswerRe: Is this updated?memberharold aptroot25 Sep '12 - 23:26 
Suggestionbug fixes and code cleanupmemberMailänder15 Jul '12 - 11:28 
GeneralRe: bug fixes and code cleanupmemberharold aptroot15 Jul '12 - 11:50 
QuestionSome optimisations and fixesmemberMatthew147115 Jan '12 - 9:12 
GeneralRe: Some optimisations and fixesmemberharold aptroot5 Jan '12 - 10:23 
GeneralRe: Some optimisations and fixesmemberMailänder15 Jul '12 - 11:31 
QuestionA Noob questionmemberGabriel Natan4 Nov '11 - 15:48 
GeneralRe: A Noob questionmemberharold aptroot3 Dec '11 - 7:18 
GeneralMy vote of 5membercipiripper19 Oct '11 - 2:52 
QuestionGreat code, couple of things I noticed... [modified]membercipiripper19 Oct '11 - 2:51 
GeneralRe: Great code, couple of things I noticed...memberharold aptroot19 Oct '11 - 3:02 
GeneralRe: Great code, couple of things I noticed...membercipiripper19 Oct '11 - 3:14 
GeneralALSO:membercipiripper19 Oct '11 - 4:22 
QuestionHaroldmemberMember 82078922 Sep '11 - 5:34 
GeneralRe: HaroldmemberHarold Aptroot2 Sep '11 - 6:10 
GeneralControlURL may be absolutememberMr C P5 Feb '11 - 1:15 
GeneralRe: ControlURL may be absolutememberHarold Aptroot5 Feb '11 - 1:37 
GeneralRe: ControlURL may be absolutememberMr C P5 Feb '11 - 2:01 
General2 errors [modified]membertaishanglaojun29 Oct '10 - 5:16 
GeneralIssue with IP address [modified]memberErty Hackward5 Oct '10 - 17:43 
GeneralRe: Issue with IP addressmemberharold aptroot6 Oct '10 - 0:32 
GeneralMy vote of 5membersuperior00026 Jun '10 - 7:33 
GeneralRe: My vote of 5memberharold aptroot26 Jun '10 - 7:39 
GeneralHy.. Its 2010... Update! Update! Update!=)membersuperior00026 Jun '10 - 7:32 
GeneralRe: Hy.. Its 2010... Update! Update! Update!=)memberharold aptroot26 Jun '10 - 7:39 
GeneralRe: Hy.. Its 2010... Update! Update! Update!=)memberxComaWhitex14 Jan '11 - 4:42 
GeneralNo Broadcast-standard address!membericetea9419 Mar '10 - 2:20 
GeneralRe: No Broadcast-standard address!memberharold aptroot19 Mar '10 - 2:57 
General2 routers after oneanother [modified]memberjancg8 Jul '09 - 9:39 
GeneralRe: 2 routers after oneanothermemberharold aptroot8 Jul '09 - 9:50 
GeneralRe: 2 routers after oneanothermemberjancg8 Jul '09 - 10:07 
GeneralRe: 2 routers after oneanothermemberharold aptroot8 Jul '09 - 10:25 
GeneralRe: 2 routers after oneanothermemberjancg8 Jul '09 - 11:49 
GeneralRe: 2 routers after oneanothermemberharold aptroot8 Jul '09 - 12:09 
GeneralUPnP service discoverymemberCliff Koh Zi Han7 Jun '09 - 2:47 
GeneralRe: UPnP service discoverymemberharold aptroot7 Jun '09 - 4:01 

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

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