Implementing UPnP Nat Traversal

When creating a end-user network application, a number of problems arise such as, for instance, connectivity through firewalls and/or routers. Luckily, there is a number of standards and/or Windows API that deal with these problems, and the only thing we have to do to ensure a state-of-art support of networking technologies is to implement standards or stubs for API.

For instance, in order to make a network-aware application available through an IGD (Internet Gateway Device), The UPnP forum defines a standard for configuring IGD using UPnP: this is called NAT Traversal (where NAT stands for Network Address Translation). Microsoft exposes an API that implement NAT Traversal. The scope of this post is to use it in C#.

The NAT Traversal API is a COM Type Library. The first step is to add a stub for this component:

  • First, add a reference to the CustomMarshalers assembly (in the Solution Explorer, right-click on the project's References node, Add References..., .NET tab),
  • Then, create a file containing the following COM Interop code:
Code Copy HideScrollFull
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.CustomMarshalers;

namespace NatTraversal.Interop {
[ComImport, Guid("624BD588-9060-4109-B0B0-1ADBBCAC32DF"), TypeLibType(4160)]
internal interface INATEventManager {
[DispId(1)]
object ExternalIPAddressCallback { [param: In, MarshalAs(UnmanagedType.IUnknown)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(1)] set; }
[DispId(2)]
object NumberOfEntriesCallback { [param: In, MarshalAs(UnmanagedType.IUnknown)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(2)] set; }
}

[ComImport, TypeLibType(4160), Guid("6F10711F-729B-41E5-93B8-F21D0F818DF1")]
internal interface IStaticPortMapping {
[DispId(1)]
string ExternalIPAddress { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(1)] get; }
[DispId(2)]
int ExternalPort { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(2)] get; }
[DispId(3)]
int InternalPort { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(3)] get; }
[DispId(4)]
string Protocol { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(4)] get; }
[DispId(5)]
string InternalClient { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(5)] get; }
[DispId(6)]
bool Enabled { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(6)] get; }
[DispId(7)]
string Description { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(7)] get; }
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(8)]
void EditInternalClient([In, MarshalAs(UnmanagedType.BStr)] string bstrInternalClient);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(9)]
void Enable([In] bool vb);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(10)]
void EditDescription([In, MarshalAs(UnmanagedType.BStr)] string bstrDescription);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(11)]
void EditInternalPort([In] int lInternalPort);
}

[ComImport, Guid("CD1F3E77-66D6-4664-82C7-36DBB641D0F1"), TypeLibType(4160)]
internal interface IStaticPortMappingCollection /*: IEnumerable*/ {
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(EnumeratorToEnumVariantMarshaler))]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(-4), TypeLibFunc(0x41)]
IEnumerator GetEnumerator();
[DispId(0)]
IStaticPortMapping this[int lExternalPort, string bstrProtocol] { [return: MarshalAs(UnmanagedType.Interface)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(0)] get; }
[DispId(1)]
int Count { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(1)] get; }
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(2)]
void Remove([In] int lExternalPort, [In, MarshalAs(UnmanagedType.BStr)] string bstrProtocol);
[return: MarshalAs(UnmanagedType.Interface)]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(3)]
IStaticPortMapping Add([In] int lExternalPort, [In, MarshalAs(UnmanagedType.BStr)] string bstrProtocol, [In] int lInternalPort, [In, MarshalAs(UnmanagedType.BStr)] string bstrInternalClient, [In] bool bEnabled, [In, MarshalAs(UnmanagedType.BStr)] string bstrDescription);
}

[ComImport, Guid("B171C812-CC76-485A-94D8-B6B3A2794E99"), TypeLibType(4160)]
internal interface IUPnPNAT {
[DispId(1)]
IStaticPortMappingCollection StaticPortMappingCollection { [return: MarshalAs(UnmanagedType.Interface)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(1)] get; }
[DispId(2)]
object /*IDynamicPortMappingCollection*/ DynamicPortMappingCollection { [return: MarshalAs(UnmanagedType.Interface)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(2)] get; }
[DispId(3)]
INATEventManager NATEventManager { [return: MarshalAs(UnmanagedType.Interface)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(3)] get; }
}

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("9C416740-A34E-446F-BA06-ABD04C3149AE")]
internal interface INATExternalIPAddressCallback {
void NewExternalIPAddress([MarshalAs(UnmanagedType.BStr)]string newExternalIPAddress);
}

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("C83A0A74-91EE-41B6-B67A-67E0F00BBD78")]
internal interface INATNumberOfEntriesCallback {
void NewNumberOfEntries(int newNumberOfEntries);
}

[ComImport, Guid("B171C812-CC76-485A-94D8-B6B3A2794E99"), CoClass(typeof(UPnPNATCreator))]
internal interface UPnPNAT : IUPnPNAT {}

[ComImport, TypeLibType(2), Guid("AE1E00AA-3FD5-403C-8A27-2BBDC30CD0E1"), ClassInterface(ClassInterfaceType.None)]
internal class UPnPNATCreator {}
}
. . .

According to the documentation, the IUPnPNAT.DynamicPortMappingCollection property is not implemented: I removed the definition of IDynamicPortMappingCollection and replaced it with a simple object in the interface definition.

Now we can work with the API. First, create a simple object that will represents the UPnP-compatible IGD, if present. The object initializer will create the COM object and check for availability of the StaticPortMappingCollection property:

Code Copy HideScrollFull
#region References

using System;
using System.Collections;
using System.Net;
using NatTraversal.Interop;

#endregion

namespace
NatTraversal {
public class UPnPNat {
#region Fields
private UPnPNAT uPnpNat;
#endregion

#region
Instance Management

public UPnPNat() {
try {
UPnPNAT nat = new UPnPNAT();
if (nat.NATEventManager != null && nat.StaticPortMappingCollection != null)
uPnpNat = nat;
}
catch { }

// No configurable UPNP NAT is available.
if (uPnpNat == null)
throw new NotSupportedException();
}

#endregion
}
}
. . .

In order to enumerate port mappings, we have to represent each mapping by a single object instead of a set of variables : the PortMappingInfo class is a helper that contains all the settings of a port mapping :

Code Copy HideScrollFull
#region References

using System.Net;

#endregion

namespace
NatTraversal {
public class PortMappingInfo {
#region Fields

private bool enabled;
private string description;
private string internalHostName;
private int internalPort;
private IPAddress externalIPAddress;
private int externalPort;
private string protocol;

#endregion

#region
Instance Management

public PortMappingInfo(
string description,
string protocol,
string internalHostName,
int internalPort,
IPAddress externalIPAddress,
int externalPort,
bool enabled){

// Initializes fields
this.enabled = enabled;
this.description = description;
this.internalHostName = internalHostName;
this.internalPort = internalPort;
this.externalIPAddress = externalIPAddress;
this.externalPort = externalPort;
this.protocol = protocol;
}

#endregion

#region
Properties

public string InternalHostName {
get {
return internalHostName;
}
}

public int InternalPort {
get {
return internalPort;
}
}

public IPAddress ExternalIPAddress {
get {
return externalIPAddress;
}
}

public int ExternalPort {
get {
return externalPort;
}
}

public string Protocol {
get {
return protocol;
}
}

public bool Enabled {
get {
return enabled;
}
}

public string Description {
get {
return description;
}
}

#endregion
}
}
. . .

Now, we can enumerate the existing port mappings. Since the StaticPortMappingCollection is an enumerator we should use the foreach statement, but unfortunately it does not work and throw an exception. That's why we enumerate the items using IEnumerator.MoveNext:

Code Copy HideScrollFull
public PortMappingInfo[] PortMappings {
get {
// Builds port mappings list
ArrayList portMappings = new ArrayList();

// Enumerates the ports without using the foreach statement (causes the interop to fail).
int count = uPnpNat.StaticPortMappingCollection.Count;
IEnumerator enumerator = uPnpNat.StaticPortMappingCollection.GetEnumerator();
enumerator.Reset();

for (int i = 0; i <= count; i++) {
IStaticPortMapping mapping = null;
try {
if (enumerator.MoveNext())
mapping = (IStaticPortMapping)enumerator.Current;
}
catch { }

if (mapping != null) {
portMappings.Add(new PortMappingInfo(
mapping.Description,
mapping.Protocol.ToUpper(),
mapping.InternalClient,
mapping.InternalPort,
IPAddress.Parse(mapping.ExternalIPAddress),
mapping.ExternalPort,
mapping.Enabled));
}
}

// Now copies the ArrayList to an array of PortMappingInfo.
PortMappingInfo[] portMappingInfos = new PortMappingInfo[portMappings.Count];
portMappings.CopyTo(portMappingInfos);

return portMappingInfos;
}
}
. . .

Invoking this property is done this way:

Code Copy
UPnPNat nat = new UPnPNat();

foreach (PortMappingInfo info in nat.PortMappings)
Console.WriteLine("{0} : {1} -> {2}:{3} ({4})", info.Description, info.ExternalPort, info.InternalHostName, info.InternalPort, info.Protocol);

In order to use UPnP to configure the IGD, we have to implement stubs for adding and removing port mappings :

Code Copy
public void AddPortMapping(PortMappingInfo portMapping) {
uPnpNat.StaticPortMappingCollection.Add(portMapping.ExternalPort, portMapping.Protocol, portMapping.InternalPort, portMapping.InternalHostName, portMapping.Enabled, portMapping.Description);
}

public void RemovePortMapping(PortMappingInfo portMapping) {
uPnpNat.StaticPortMappingCollection.Remove(portMapping.ExternalPort, portMapping.Protocol);
}

Now, last but not least, you have to configure both your IGD and Windows box to use UPnP !

posted on Thursday, March 31, 2005 10:29 AM

Feedback

# re: Implementing UPnP Nat Traversal 9/6/2005 8:46 AM ShadowDrakken

How would this be done in VB.NET?

# re: Implementing UPnP Nat Traversal 2/25/2006 1:10 AM Krys

Is there a way to develop a "UPnP & IGD" installation, automatically !

I m currenctly trying to install UPnP... without Succes !!! It is to become crazy :-(

cdemez2@hotmail.com

# re: Implementing UPnP Nat Traversal 4/30/2006 1:24 AM Gene Jones

Is there a way to get the external IP of the IGD when there are no PortMapping(s) to read the ExternalIPAddress property of?

# re: Implementing UPnP Nat Traversal 5/13/2006 1:59 AM Stephen

I would also like to gain the external IP address. I would also love to know how to connect to a specific computer on a network using ip addresses and sockets.
For example say I have a client that wishes to connect up to a server app on a computer located on a network behind a router.
Let me know if you can here

omecron87@hotmail.com

# re: Implementing UPnP Nat Traversal 5/21/2006 11:23 PM Stephen

To get an external ip address you could use the shortcut--->


//**Getting the public ip address via a website***

System.Net.WebClient myWebClient = new System.Net.WebClient();
System.IO.Stream myStream = myWebClient.OpenRead("http://www.twistedchat.com/css/myip.cfm");
System.IO.StreamReader myStreamReader = new System.IO.StreamReader(myStream);
return myStreamReader.ReadToEnd();

//***********************************

Is there some way you can show people how to implement your code into their projects. For example how to use it and why. Thanks!!

Also come see www.twistedchat.com and give me some suggestions on making it better.

# re: Implementing UPnP Nat Traversal 5/22/2006 5:12 PM Buz

Hi all,

Thanks for your feedback. A long time have passed since I wrote this snippet, so I'm not sure I perfectly remember the underlying API...

I'll try to answer your question :

As Stephen notes, there is no way to get the external IP address of the IGD when no port mapping is configured.

There is a "INATExternalIPAddressCallback" interface that helps receiving notifications when the external IP address has changed, but it does not help having the address at startup...

Anyway, getting the external IP address from the IGD itself would not guarantee that you're getting the "real" public address. Here at work, we have a router configuration with two nat traversal; So the external IP address of the first router is only an address on the second router local network...

The best solution I've found is to get the IP address from a computer anywhere on the Internet. For instance, we at www.pixvillage.com are using our servers to determine the public IP addresses of our users.

This solution (the one Stephen finally used) is an elegant one, but the drawback is that you are dependant on the server availability.

Please note, Stephen, that you omit to dispose both the WebClient and StreamReader objects in your snippet.

# re: Implementing UPnP Nat Traversal 6/23/2006 4:59 PM Sumit

Can you please tell me what will be the class hierarchy of the codes that have been written.
Please mention the class names elaborately.
Contact me indianbill007@yahoo.co.in

# re: Implementing UPnP Nat Traversal 6/27/2006 10:16 AM Buz

Here is the class hierarchy :

The root namespace is NatTraversal : it contains the public classes, UPnPNat and PortMappingInfo

The interop types and classes (the first block of code above) are located in the NatTraversal.Interop namespace.

# re: Implementing UPnP Nat Traversal 6/29/2006 11:16 AM lapsy

I am kinda new to C# and I not able to find the implementation for the namespace NetTraversal.Interop. Can you please help me out
Thnx
Lapsy

# re: Implementing UPnP Nat Traversal 7/6/2006 3:43 PM Buz

The NatTraversal.Interop objects are given in the very first part of code :

Click the "Copy" link under the line "Then, create a file containing the following COM Interop code:", and it will be copied to your clipboard.

# re: Implementing UPnP Nat Traversal 7/9/2006 2:48 AM Robert

Thank you very much for the good article! I'm implementing a UPnP based control for changing enabled state for existing port mappings, on my router, via a ssl-reached form. However, after changing a few things back and forth I got the message :

"An exception of type 'System.Runtime.InteropServices.COMException' occurred in NatTraversal.UPnP.DLL but was not handled in user code

Additional information: The owner of the PerUser subscription is not logged on to the system specified (Exception from HRESULT: 0x80040210)"

The message is now shown every time I try to index the StaticPortMappingCollection. If I restart the web server I can access the StaticPortMappingCollection[21,"TCP"] once again, but on second attempt it gives me the same error message again. Is there any special release (dispose) I need to do, to avoid this?

Again, thank you very much for a very good article which has helped me quite a lot!

# re: Implementing UPnP Nat Traversal 7/10/2006 4:23 PM Buz

It seems to be an impersonation problem.

Did you try to modify the impersonation / authentication on your ASP.Net web application ?

The default user for ASP.Net applications may not have sufficient permissions for using the NatTraversal API

# re: Implementing UPnP Nat Traversal 7/14/2006 7:45 PM Robert

Well, that might have been the case, had it not been for the fact that it worked correctly a couple of times before that exception (and even now works the first attempt). I will try your tip at a later time, since I'm currently on vacation going to France. I don't think it will do the trick, but every possitility is worth an attempt.

# re: Implementing UPnP Nat Traversal 10/17/2006 4:52 PM Deepak Agarwal

hi. please provide the code for PortMappingInfo too..

# re: Implementing UPnP Nat Traversal 10/17/2006 4:56 PM Buz

The code is already provided in the third block of code (minimized by default, you just have to click the "scroll" or "full" radio button).

# re: Implementing UPnP Nat Traversal 10/17/2006 5:05 PM Deepak Agarwal

Nope. it is showing only three dots. and when i click on the radio buttons nothing is happening.
please guide me...

# re: Implementing UPnP Nat Traversal 10/17/2006 5:10 PM Buz

I suppose you're using Firefox... code snippets does not work under FF.

You can either use IE to display the page, or display the page source and search for the snippet inside.

Sorry for the inconvenience...

# re: Implementing UPnP Nat Traversal 10/17/2006 5:15 PM Deepak Agarawl

Thnx A Lot!! i was def using firefox :-). great work by the way on external ip as i couldnt find this anywhere on the web...

# re: Implementing UPnP Nat Traversal 10/17/2006 5:51 PM Deepak Agarwal

Actually i m new to c# , migrating from java to c#, just 1 week with the c#. please tell me how to use this as i want to bind my server with my external ip....

# re: Implementing UPnP Nat Traversal 10/18/2006 9:40 AM Deepak Agarawl

could u please do me a favor.. could you please zip the code in a zip with complete hirearchy and send to me ???

# re: Implementing UPnP Nat Traversal 10/18/2006 10:03 AM Deepak Agarwal

ok. i m on the rite path now i guess, but tell me how to setup IGD and Windows Box??

# re: Implementing UPnP Nat Traversal 10/18/2006 2:48 PM Deepak Agarawl

I have understood how the code work and implemented that but getting NotSupportedException...What to do..? i m on the LAN, which is connected to a switch and switch is connected to a ADSL modem ... Please guide me...

# re: Implementing UPnP Nat Traversal 10/18/2006 4:46 PM Buz

You have to enable uPnP both on Windows (start "SSDP Discovery Service" and "Universal Plug and Play Device Host Service") and on the IGD (depends on the IGD manufacturer and model; usually can enable uPnP using the IGD administration web tool).

Concerning your NotSupportedException, where does it happen ?

# re: Implementing UPnP Nat Traversal 10/18/2006 5:30 PM Deepak Agarwal

the exception is at this line :
if (uPnpNat == null)
throw new NotSupportedException();

actually i m using microsoft windows server 2003, and i dont have any router access, i just wrote one peer to peer program which is working fine on LAN, but not on internet as i cannt bind my server to the external IP... i heard that UPnP is not available in ms windows 2003, is taht true!!?











# re: Implementing UPnP Nat Traversal 10/18/2006 5:35 PM Deepak Agarwal

I m newbie on C#.
i just wrote this program.
http://rapidshare.de/files/37220472/PurePeerToPeer.rar.html

# re: Implementing UPnP Nat Traversal 10/18/2006 6:03 PM Buz

I tried your application and did not encounter any error (it successfully enumerates port mappings on the router).

Windows Server 2003 does not seem to have UPnP installed and, since you do not have access to you router, you have only few chances to make UPnP working with your infrastructure...

# re: Implementing UPnP Nat Traversal 11/16/2006 4:03 PM Philippe

Hello from France,

I've put the first block in a file called NatTraversal.Interop.cs
I've put the 2nd and 3rd block in a file called NatRaversal.cs

Where do I have to put the 4th, 5th, 6yh blocks of code : in my main form ?

Can you put a sample or email-me please ?
ordinoir at gmail.com

Thanks a lot. Best regards

# re: Implementing UPnP Nat Traversal 4/20/2007 3:27 PM Key

Hello

I am trying to follow your example.
But I get alway get into NotSupportedException.
I am using Microsoft Visual Studio 2005.
I created a project. In the project I have two namespaces, one called: NatTraversal.Interop and the other called: NatTraversal
In the first one I just added the first code (Interop)
and in the second one I added both classes: UPnPNat and PortMappingInfo.

But when I run it I I always get the exception.
I have a router with UPnP enable and also my win XP has the UPnP service enable. I used another application that shows all my UPnP devices available and it worked fine. But when I try to run this code I get the exception.
could you tell me what I could be ding wrong? or you could send me a running example to d_garcia_ch at hotmail.com.

Thanks for your time.

# re: Implementing UPnP Nat Traversal 7/9/2007 9:46 PM RobCube

Sweet piece of writing Buz. I noticed it didn't traverse through items with dual protocols set up, i.e. Both or TCP/UDP. Any way we can do this?

# re: Implementing UPnP Nat Traversal 7/20/2007 8:32 AM Flora

Hey:

I alway get into NotSupportedException..
is this because i run on windows vista??
is there a way i can go around about it?

You can add me to msn
lin_jialu@hotmail.com

cheers

# re: Implementing UPnP Nat Traversal 8/1/2008 10:47 AM Giuseppe

Hi Buz from Italy!

I have an issue with your code.
I alway get into NotSupportedException at this point:

if (uPnpNat == null)
throw new NotSupportedException();

I use Windows XP SP2 and uPnP is installed and enabled on service. But nat.NATEventManager and nat.StaticPortMappingCollection are NULL.

What is the problem in your opinion?
Thanks a lot in advance

Title
 
Name
 
Url
Comments   
Protected by Clearscreen.SharpHIPEnter the code you see: