UPnP code to open ports on nat/firewall

N

nezoat

Here is some working code to open the specified TCP port on the gateway
nat device or firewall, and forward it to the calling machine. Great
for p2p apps. The newsgroups are such a great resource and have helped
me so much, I hope this helps others. Let me know if you find it
useful!

Lee Carlson
Lee (at) Carlson (dot) net
-----------------------------------

using System;
using System.Text;
using System.Diagnostics;
using System.Net.Sockets;
using System.Net;

namespace Woodchop.Net
{
public class UPnP
{
public UPnP()
{

}
public static void OpenFirewallPort(int port)
{
System.Net.NetworkInformation.NetworkInterface[] nics =
System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();

//for each nic in computer...
foreach (System.Net.NetworkInformation.NetworkInterface nic
in nics)
{
try
{
string machineIP =
nic.GetIPProperties().UnicastAddresses[0].Address.ToString();

//send msg to each gateway configured on this nic
foreach
(System.Net.NetworkInformation.GatewayIPAddressInformation gwInfo in
nic.GetIPProperties().GatewayAddresses)
{
try
{
OpenFirewallPort(machineIP,
gwInfo.Address.ToString(), port);
}
catch
{ }
}
}
catch { }
}

}
public static void OpenFirewallPort(string machineIP, string
firewallIP, int openPort)
{
string svc = getServicesFromDevice(firewallIP);

openPortFromService(svc,"urn:schemas-upnp-org:service:WANIPConnection:1",machineIP,
firewallIP, 80, openPort);
openPortFromService(svc,
"urn:schemas-upnp-org:service:WANPPPConnection:1", machineIP,
firewallIP, 80, openPort);
}
private static string getServicesFromDevice(string firewallIP)
{
//To send a broadcast and get responses from all, send to
239.255.255.250
string queryResponse = "";
try
{
string query = "M-SEARCH * HTTP/1.1\r\n" +
"Host:" + firewallIP + ":1900\r\n" +
"ST:upnp:rootdevice\r\n" +
"Man:\"ssdp:discover\"\r\n" +
"MX:3\r\n" +
"\r\n" +
"\r\n";

//use sockets instead of UdpClient so we can set a
timeout easier
Socket client = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
IPEndPoint endPoint = new
IPEndPoint(IPAddress.Parse(firewallIP), 1900);

//1.5 second timeout because firewall should be on same
segment (fast)
client.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout, 1500);

byte[] q = Encoding.ASCII.GetBytes(query);
client.SendTo(q, q.Length, SocketFlags.None, endPoint);
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
EndPoint senderEP = (EndPoint)sender;

byte[] data = new byte[1024];
int recv = client.ReceiveFrom(data, ref senderEP);
queryResponse = Encoding.ASCII.GetString(data);
}
catch { }

if(queryResponse.Length == 0)
return "";


/* QueryResult is somthing like this:
*
HTTP/1.1 200 OK
Cache-Control:max-age=60
Location:http://10.10.10.1:80/upnp/service/des_ppp.xml
Server:NT/5.0 UPnP/1.0
ST:upnp:rootdevice
EXT:

USN:uuid:upnp-InternetGatewayDevice-1_0-00095bd945a2::upnp:rootdevice
*/

string location = "";
string[] parts = queryResponse.Split(new string[] {
System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
foreach (string part in parts)
{
if (part.ToLower().StartsWith("location"))
{
location = part.Substring(part.IndexOf(':') + 1);
break;
}
}
if (location.Length == 0)
return "";

//then using the location url, we get more information:

System.Net.WebClient webClient = new WebClient();
try
{
string ret = webClient.DownloadString(location);
Debug.WriteLine(ret);
return ret;//return services
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
webClient.Dispose();
}
return "";
}
private static void openPortFromService(string services, string
serviceType, string machineIP, string firewallIP, int gatewayPort, int
portToForward)
{
if (services.Length == 0)
return;
int svcIndex = services.IndexOf(serviceType);
if (svcIndex == -1)
return;
string controlUrl = services.Substring(svcIndex);
string tag1 = "<controlURL>";
string tag2 = "</controlURL>";
controlUrl = controlUrl.Substring(controlUrl.IndexOf(tag1)
+ tag1.Length);
controlUrl =
controlUrl.Substring(0,controlUrl.IndexOf(tag2));


string soapBody = "<s:Envelope " +
"xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/ \" " +

"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/ \">" +
"<s:Body>" +
"<u:AddPortMapping xmlns:u=\"" + serviceType + "\">" +
"<NewRemoteHost></NewRemoteHost>" +
"<NewExternalPort>" + portToForward.ToString() +
"</NewExternalPort>" +
"<NewProtocol>TCP</NewProtocol>" +
"<NewInternalPort>" + portToForward.ToString() +
"</NewInternalPort>" +
"<NewInternalClient>" + machineIP +
"</NewInternalClient>" +
"<NewEnabled>1</NewEnabled>" +
"<NewPortMappingDescription>Woodchop
Client</NewPortMappingDescription>" +
"<NewLeaseDuration>0</NewLeaseDuration>" +
"</u:AddPortMapping>" +
"</s:Body>" +
"</s:Envelope>";

byte[] body =
System.Text.UTF8Encoding.ASCII.GetBytes(soapBody);

string url = "http://" + firewallIP + ":" +
gatewayPort.ToString() + controlUrl;
System.Net.WebRequest wr =
System.Net.WebRequest.Create(url);//+ controlUrl);
wr.Method = "POST";
wr.Headers.Add("SOAPAction","\"" + serviceType +
"#AddPortMapping\"");
wr.ContentType = "text/xml;charset=\"utf-8\"";
wr.ContentLength = body.Length;

System.IO.Stream stream = wr.GetRequestStream();
stream.Write(body, 0, body.Length);
stream.Flush();
stream.Close();

WebResponse wres = wr.GetResponse();
System.IO.StreamReader sr = new
System.IO.StreamReader(wres.GetResponseStream());
string ret = sr.ReadToEnd();
sr.Close();

Debug.WriteLine("Setting port forwarding:" +
portToForward.ToString() + "\r\r" + ret);
}
}
}
 
Joined
Jun 19, 2013
Messages
2
Reaction score
0
This is fantastic, but there is one little glitch (at least when it comes to my router).

In my case, the 'location' URL discovered by getServicesFromDevice is not on port 80, while you've hard-coded the actual port forwarding request to use that port (although you've left it as a parameter, so obviously considered needing to make it dynamic).

I've made the following changes...

An additional parameter to getServicesFromDevice that returns an int...

private static string getServicesFromDevice(string firewallIP, out int port)
{
//To send a broadcast and get responses from all, send to 239.255.255.250
port = 80;​

Code (in the same function) to take the location address and extract the port number...


//Strip the port number from the location...
Uri U = new Uri(location);
port = U.Port;​

And finally a change to OpenFirewallPort to get and use that port number...


public static void OpenFirewallPort(string machineIP, string firewallIP, int openPort)
{
int upnpPort = 80;
string svc = getServicesFromDevice(firewallIP,out upnpPort);

openPortFromService(svc,"urn:schemas-upnp-org:service:WANIPConnection:1",machineIP,firewallIP, upnpPort, openPort);
openPortFromService(svc,"urn:schemas-upnp-org:service:WANPPPConnection:1", machineIP,firewallIP, upnpPort, openPort);
}​
 
Joined
Jun 19, 2013
Messages
2
Reaction score
0
One additional change

It's possible to have an interface that has both an IPv6 and an IPv4 address. The trick of course is that if you're using an IPv4 gateway, you need to pass your interfaces IPv4 address, not your IPv6 one or your router will probably fail to open the port.

Here's the (rough) changed code...

try
{
System.Net.NetworkInformation.IPInterfaceProperties IPs = nic.GetIPProperties();

//send msg to each gateway configured on this nic
foreach (System.Net.NetworkInformation.GatewayIPAddressInformation gwInfo in nic.GetIPProperties().GatewayAddresses)
{
try
{
bool GatewayIPV6 = false;
bool NICIPV6 = false;
if (gwInfo.Address.ToString().IndexOf(':') != -1) GatewayIPV6 = true;

string machineIP = "";
for (int x = 0; x < IPs.UnicastAddresses.Count; x++)
{
machineIP = IPs.UnicastAddresses[x].Address.ToString();
if (machineIP.IndexOf(':') != -1)
NICIPV6 = true;
else
NICIPV6 = false;

if (GatewayIPV6 == NICIPV6) break;
}

OpenFirewallPort(machineIP,gwInfo.Address.ToString(), port);
}
catch
{
}
}
}
catch
{
}​
 
Last edited:
Top