Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#5.0

Open.NAT - A NAT Traversal library for .NET and Mono

4.92/5 (42 votes)
21 Aug 2014MIT12 min read 96.4K   3K  
A class library for port forwarding in NAT devices that support Universal Plug and Play (UPNP) and/or Port Mapping Protocol (PMP)

Latest version is always in Github [Open.NAT]

Table of Content

Introduction

If you've developed some kind of server like a media server, file storage server, instant messages server or any other then surely you already know what is all this about NAT traversal and about the different techniques to make your computer reachable from outside and their pros and cons. 

One of those techniques consist in opening a port in your router and in specifying that all incoming traffic through that port must be forwarded to you computer IP:Port pair. (port forwarding)

To do this you need to deal with at least two protocols, SSDP and also UPNP or PMP. That isn't easy at all, their specs are full of details and each router implements the protocols in different ways. Testing is also a nightmare, what works perfectly fine in your router, doesn't work in somebody else's.

Open.NAT is a lightweight and easy-to-use class library to do port forwarding in NAT devices (Network Address Translator) that support Universal Plug and Play (UPNP) and/or Port Mapping Protocol (PMP).  It is written in C# and works for .NET and Mono.

Background

At its beginning, internet was a network that allowed to every computer communicate with any other computer directly, the only thing you needed to know in order to achieve it, was those other computers' IP addresses. Those must have been happy days, however, it seems nobody expected such a success simply because internet wasn't designed for being used by million and million people; instead, it was designed for being used by the Advanced Research Projects Agency's projects (U.S. Department of Defense)  at universities and research laboratories in the US. That's why IPv4 addresses are 32 bits numbers, because it provides a 2^32 address space ( 4,294,967,296 unique addresses), a big enough space for that time.

The problem

Later, Internet started to be massively adopted but, even when everything continued alright for a short while, it was self evident that soon there wouldn't be enough IP addresses for everybody, that's the reason for the development of its successor protocol, IPv6. 

IPv6 solves the IPs availability problem however, for when it appeared, there was all a huge world-wide IPv4 infrastructure; not just hardware equipment but also software involved. The transition from IPv4 to IPv6 is still ongoing, in fact, vast part of the world's countries still have IPv4.

Moreover, shortage of IPv4 addresses made them expensive. Just imagine you have 10,000 computers and need to pay for 10,000 IPs!  That was a real problem.

The solution

The solution were NATs. The basic idea is that only one computer/device owns an IP address (which also means it is reachable from outside) and the rest of the computers are behind. When one of the computers needs to send data, it does it through the NAT device, like a middleman who performs actions by you and then comes back with the results. Now, even though that's a solution in one sense, it is also a problem given that only works well for outgoing connections, that means you cannot longer host a server on your personal computer because it is not visible from outside, only the NAT is.

Consequences

We have to take into account that there are NATs at different levels, for example, all computers in my office are behind the office's NAT, the computers in my city are behind a carrier's NAT, and so on, we can see how the computer-to-computer original design has "evolved" to a hierarchical one, where you have access to Google, Facebook, The Guardian and other big companies' services but our mothers cannot have access to services we host in our home computers with the same easiness.

 

Portforwarding with Open.NAT

Installing Open.NAT

You can install Open.NAT using Nuget

Install-Package Open.NAT

Or just downloading the code from its Github repo here and including it as part of your solution.  

Once its done you are ready to use it. 

Creating a portmapping

Here we have a typical scenario where we need to know our external IP address (the NAT device's IP) and create a port mapping for TCP protocol external_ip:1700 --> host_machine:1600.

C#
var discoverer = new NatDiscoverer();

// using SSDP protocol, it discovers NAT device.
var device = await discoverer.DiscoverDeviceAsync();

// display the NAT's IP address
Console.WriteLine("The external IP Address is: {0} ", await device.GetExternalIPAsync());

// create a new mapping in the router [external_ip:1702 -> host_machine:1602]
await device.CreatePortMapAsync(new Mapping(Protocol.Tcp, 1602, 1702, "For testing"));

// configure a TCP socket listening on port 1602
var endPoint = new IPEndPoint(IPAddress.Any, 1602);
var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.SetIPProtectionLevel(IPProtectionLevel.Unrestricted);
socket.Bind(endPoint);
socket.Listen(4);

After running this we can verify that the port was open.  I did it using www.canyouseeme.org online tool. Note how even when our socket listens on port 1602, we got access through 1702

Listing portmappings

And the following code lets you list the existing mappings in the router:

C#
var nat = new NatDiscoverer();

// we don't want to discover forever, just 5 senconds or less
var cts = new CancellationTokenSource(5000);

// we are only interested in Upnp NATs because PMP protocol doesn't allow to list mappings
var device = await nat.DiscoverDeviceAsync(PortMapper.Upnp, cts);

foreach (var mapping in await device.GetAllMappingsAsync())
{
     Console.WriteLine(mapping);
}

 

Deleteing portmappings

Lets delete some mappings:

C#
var nat = new NatDiscoverer();
var cts = new CancellationTokenSource(5000);
var device = await nat.DiscoverDeviceAsync(PortMapper.Upnp, cts);

foreach (var mapping in await device.GetAllMappingsAsync())
{
     // in this example we want to delete the "Skype" mappings
     if(mapping.Description.Contains("Skype"))
     {
        Console.WriteLine("Deleting {0}", mapping);
        await device.DeletePortMapAsync(mapping);
     }
}

 

Handing error

How to handle exceptions? What if the specified mapping already exists for other service? Well, the following code provides a clue:

C#
try
{
    var nat = new NatDiscoverer();

    var cts = new CancellationTokenSource(5000);
    var device = await nat.DiscoverDeviceAsync(PortMapper.Upnp, cts);

    await device.CreatePortMapAsync(new Mapping(Protocol.Tcp, 1600, 1700, "The mapping name"));
}
catch(NatDeviceNotFoundException e)
{
    Console.WriteLine("Open.NAT wasn't able to find an Upnp device ;(")
}
catch(MappingException me)
{
    switch(me.ErrorCode)
    {
        case 718:
            Console.WriteLine("The external port already in use.");
            break;
        case 728: 
            Console.WriteLine("The router's mapping table is full.");
            break;
        .......
        ....
        ..
    }
}

Of course, in case of errors, you can do something smarter than display the problem in console, for example, when external port is already in use you could try with other port number. 

 

Open.NAT and Mono.Nat

Open.NAT was born as a fork of Mono.Nat so, comparisons are unavoidable.

First of all is important to state that there isn't anything wrong with Mono.Nat, it is a very very good library. The main motivation for Open.NAT was make it work in my routers and once I did it, I continued adding features, refactoring and updating the code in order to use some benefits offered by .NET 4.5 and C# 5, as well as changing the discovery process nature.

Here you have a list of some of the changes:

The Name

Open.NAT is a different library with different ideas and features so, it had to have a different name but not very different. The problem with Mono.Nat is that it is not a part of mono itself, it just happens to be in the Mono.Nat namespace and for that reason people ssume it's part of the mono framework. 

Probably Open.NAT is not a better name but it resembles Mono.Nat and plays with the idea that it is an open source to open NATs

NAT type discovery selection

There are projects that are interested only in Upnp devices and don't want to discover PMP NATs. Others, only want to discover and handle PMP NATs instead of UPNP ones. Finally, there are others which want to discover both. This is possible with Open.NAT.

Auto release portmappings

Applications that add portmappings should remove them when they are no longer required or remove them as part of the shutdown steps. By default, Open.NAT keeps track of the added portmappings in order to release them automatically. Developers don't need to take care of this kind of details in their projects. Anyway, a ReleaseAll method is provided just in case a deterministic portmappings release is required. 

This doesn't mean you cannot create permanet portmappings (mappings that never expire), you can do it.

Auto renew portmappings

Developers don't know how long a portmapping can be required in advance and for that reason they tend to create Permanet portmappings (a mapping that never expires). Given that an application can finish unexpectedly (because someone unplug the computer, for example) permanent mappings remain opened and that is not okay in many cases. Open.NAT allows developers to specify a mapping lifetime and it renews the mapping automatically before the expiration so if someone unplug the computer the NAT will release the portmapping after a certain time.

Discovery timeout

If after some time a NAT is not discovered, you can be sure there isn't any NAT available for Open.NAT. This can be because the NAT doesn't support Upnp nor Pmp (or they are not enabled) or because there is simply no NATs. Open.NAT allows to specify a discovery timeout in order to stop the discovery process and let the host application know that no NATs were found.

Open.NAT doesn't make use of a long run thread in order to being continuously discovering NAT devices, like Mono.Nat does it. This isn't better nor worse, it is just different. The idea behind this change is to avoid resources consumption (thread and bandwidth). If you think is a good idea to discover devices all the time, in fact sometimes it is, you are free to do it with your own thread, timer or the cheapest resource you count on. 

Performance, performance and ... performance

The discovery process in Mono.Nat is a very time consuming task because it asks all devices in the LAN for all the services they support. It generates a lot of network traffic and then, it has to process all the responses. Open.NAT only asks for those services capable to map ports, improving drastically the discovery process.

Smart mapping strategy using Upnp

When it comes to UPnP, different routers support different kind of mappings, some of them only support permanent mappings, others require the same external and internal port number, others only support wildcard in the remote host, etc. Oh! and there are a few that have more than just one of these restrictions. Open.NAT does its best to deal with routers that have restrictions of that type. For example, if you need to map a port for 10 minutes and the router only supports permanent mappings, Open.NAT creates a permanent mapping and also takes care of release it when application exits.

WANIPConnection and WANPPPConnection

Open.NAT works not only for NATs supporting WANIPConnection service type but also WANPPPConnection service type. To do this, it has to deal with some inexpensive ADSL modems that respond with wrong service type and that used to support only WANPPPConnection.

Async operations

All the operations with Open.NAT are asynchronous because it is the real nature of operations and developers don't need to wait for an operation blocking the thread, they can do a lot of useful stuff instead of being waiting. Remember, these are client-server operations. Developers who want synchronous operations just need to wait for the operation completion. 

Excellent Tracing capabilities

If developers find problems and need support, they can enable the code tracing in Verbose (or All) level and get the full detail about what is going on inside, with the requests/responses, warnings, errors, stacktraces, etc. They can also use the Open.Nat.ConsoleTest project to play with Open.NAT and reproduce problems.

Other things to have into account

Documentation. Who need it?

You! Of course you need it and for that reason Open.NAT has a wiki page with the API reference, Code examples, Troubleshooting, Errors, Warnings and an always improving Home page. (Disclaimer: documentation for version 2 is still in progress)

Support, of course

I am working hard to make Open.NAT the best library in its type and to achieve that goal getting feedback and giving support are on top of the priority list. You will never feel alone ;)

No very good reasons

Fixes and code quality

Open.NAT uses .NET Framework 4.5 and thanks to that its code is a lot clearer and easy to read, maintain and debug. It also includes several new advantages and fixes. However, that is not good news if your project uses a older version of .NET.

 

Troubleshooting

ConsoleTest

The Open.NAT solution includes the Open.NAT.ConsoleTest project that can be used for Troubleshooting. Just download the code and run it.

Your IP: 181.110.171.21
Added mapping: 181.110.171.21:1700 -> 127.0.0.1:1600Mapping List

+------+-------------------------------+--------------------------------+----------------------------------+
| PROT | PUBLIC (Reacheable)           | PRIVATE (Your computer)        | Descriptopn                      |
+------+----------------------+--------+-----------------------+--------+----------------------------------+
|      | IP Address           | Port   | IP Address            | Port   |                                  |
+------+----------------------+--------+-----------------------+--------+----------------------------------+
|  TCP | 181.110.171.21       |  21807 | 10.0.0.5              |  32400 | Plex Media Server                |
|  UDP | 181.110.171.21       |  25911 | 10.0.0.6              |  25911 | Skype UDP at 10.0.0.6:25911 (2693)|
|  TCP | 181.110.171.21       |  25911 | 10.0.0.6              |  25911 | Skype TCP at 10.0.0.6:25911 (2693)|
|  TCP | 181.110.171.21       |   1700 | 10.0.0.6              |   1600 | Open.Nat Testing                 |
+------+----------------------+--------+-----------------------+--------+----------------------------------+

[Removing TCP mapping] 181.110.171.21:1700 -> 127.0.0.1:1600
[Done]
[SUCCESS]: Test mapping effectively removed ;)

Press any kay to exit...

 

Enabling tracing in your project

Open.NAT provides its own TraceSource in order to enable applications to trace the execution of the library's code and find issues. To enable the tracing you can add the following two lines of code:

C#
NatDiscoverer.TraceSource.Switch.Level = SourceLevels.Verbose;
NatDiscoverer.TraceSource.Listeners.Add(new ConsoleListener());

The first line specifies the tracing level to use and the levels used by Open.NAT are: Verbose, Error, Warning and Information. You should set it to Information first and switch to Verbose if errors occur. The second line adds a trace listener and you can choose the one that is better for you, some of the available listeners are:

Here we can see an real trace output:

OpenNat - Information > Initializing

OpenNat - Information > StartDiscovery
OpenNat - Information > Searching
OpenNat - Information > Searching for: UpnpSearcher
OpenNat - Information > UPnP Response: Router advertised a 'WANPPPConnection:1' service!!!
OpenNat - Information > Found device at: http://10.0.0.2:5431/dyndev/uuid:0000e068-20a0-00e0-20a0-48a8000808e0
OpenNat - Information > 10.0.0.2:5431: Fetching service list
OpenNat - Information > 10.0.0.2:5431: Parsed services list
OpenNat - Information > 10.0.0.2:5431: Found service: urn:schemas-upnp-org:service:Layer3Forwarding:1
OpenNat - Information > 10.0.0.2:5431: Found service: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
OpenNat - Information > 10.0.0.2:5431: Found service: urn:schemas-upnp-org:service:WANPPPConnection:1
OpenNat - Information > 10.0.0.2:5431: Found upnp service at: /uuid:0000e068-20a0-00e0-20a0-48a802086048/WANPPPConnection:1
OpenNat - Information > 10.0.0.2:5431: Handshake Complete
OpenNat - Information > UpnpNatDevice device found.
OpenNat - Information > ---------------------VVV
EndPoint: 10.0.0.2:5431
Control Url: http://10.0.0.2:5431/uuid:0000e068-20a0-00e0-20a0-48a802086048/WANPPPConnection:1
Service Description Url: http://10.0.0.2:5431/dyndev/uuid:0000e068-20a0-00e0-20a0-48a8000808e0
Service Type: urn:schemas-upnp-org:service:WANPPPConnection:1
Last Seen: 15/05/2014 10:43:23 p.m.
It got it!!
The external IP Address is: 186.108.237.5
OpenNat - Information > UPnP Response: Router advertised a 'WANPPPConnection:1' service!!!
OpenNat - Information > Found device at: http://10.0.0.2:5431/dyndev/uuid:0000e068-20a0-00e0-20a0-48a8000808e0
OpenNat - Information > Already found - Ignored

Other frameworks and articles

CodeProjects has several articles about NAT portforwarding that we should take a look at:

NAT Traversal with UPnP in C# is an excellent article and the author says a great truth:

Quote:

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.

harold aptroot

Other aspect to consider about the Harol's article is the large number of comments reporting problems and making questions. It is evident that this is a difficult topic.
 

Conclusion

Setup up Port Forwarding on your local router is very convenient for users of your networking application and you should automatically set it up for them. This can be done with Open.NAT, and also a big headache is avoided. 

History

  • 15th August, 2014 - First version.
  • 20th August, 2014 - Fix some typos
  • 21st August, 2014 - Minor wording changes.

License

This article, along with any associated source code and files, is licensed under The MIT License