|
Yes, I really meant root path and not relative. Sorry.
The issue is actually due to the fact that the router’s URL is: http://192.168.254.1:5431/dyndev/uuid:000c-4182-5d2c00009adc. It’s the dyndev sub-directory in the URL that causes the issue. The code assumes the last / is the URL’s root, when in this case it’s not. The concatenation should concatenate the two URLs with consideration of root or relative.
I modified it to the following and it resolves the issue. There may be a better way, but this is a quick fix.
int n;
if(relurl[0] == '/')
{
n = descUrl.IndexOf("://");
if (n > -1)
{
n = descUrl.IndexOf('/', n + 3);
if (n > -1)
serviceUrl = descUrl.Substring(0, n) + relurl;
}
} else
{
serviceUrl = descUrl.Substring(0, descUrl.LastIndexOf("/")) + relurl;
}
|
|
|
|
|
*Source updated with this code*
Does that work for that router?
|
|
|
|
|
The code makes an assumption that the Location header will contain an .xml extension. On an older LINKSYS WRT54G, the Location header looks like this “http://192.168.254.1:5431/dyndev/uuid:000c-4182-5d2c00009adc” and thus the loop at line 29 never breaks, causing an infinite loop. The router does return an XML document from this address, so it doesn’t seem necessary to require an xml extension in the Location header. Can’t this check simply be taken out?
|
|
|
|
|
Perhaps it can
I'm not entirely why it is there in the first place
|
|
|
|
|
Are you aware of the fact that upnp mapping code is already present in the mono sources for a long time now? It is in the Mono.Nat namespace.
|
|
|
|
|
Ah no, I was not aware.. well I'll have a look at that, thanks for mentioning
|
|
|
|
|
Well... I too made a upnp lib implementation and is was very interesting to learn how it actually works but when I found an implementation in mono torrent a long time ago that got merged in the mono framework I started using that implementation.
|
|
|
|
|
Hey,
I'm the developer of Mono.Nat. It's not a part of mono itself, it just happens to be in the Mono.Nat namespace. I really should rename it so that people don't assume it's part of the mono framework itself.
Anyway, it supports:
upnp router detection
port forwarding/removing
getting external ip
listing all existing port forwards
It contains both a synchronous and asynchronous API. There is near-complete support for nat-pmp aswell - so you can forward ports on airport routers aswell. I can't remember offhand what else there is, but it's all there. There's even hacks to ensure support with a wide range of routers. Not all routers fully adhere to the UPnP spec.
Also, for the record, there is documentation on the UPnP IGN specification, which is what i used to create Mono.Nat.
|
|
|
|
|
Hi thank you for writing
I'm afraid I don't understand the documentation at all
|
|
|
|
|
I think the relevant document is found here[^], specifically this[^] one. Most of it is irrelevant if all you're implementing is port forwarding etc, but everything you need is in there.
Alan.
|
|
|
|
|
Thank you for the pointers
I will try to read all that
|
|
|
|
|
I have been meaning to write a UPnP class myself, so it's good to see others trying too, however there are too many assumptions in your code for it to work properly on other NAT devices. Specifically, my DG834G doesn't comply with several assumptions you've made.
Firstly,
239.255.255.250 works perfectly for my DG834, whereas
255.255.255.255 only triggers my XBMC media center to announce itself.
Secondly, the line
s.SendTo(data, new IPEndPoint(IPAddress.Broadcast, 1900));
should be repeated three times, as per one of the specs for UPnP (I forget which, I just remember reading it the other day)
Thirdly, your code assumes certain text in the response which may not be there, specifically "location" (case sensitivity), port "80", and ".xml".
Below is the changed code (which may now not work on your NAT box. )
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Xml;
using System.IO;
namespace UPnP
{
public delegate void Method();
public class NAT
{
public static void BeginDiscover(Method callback)
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
string req = "M-SEARCH * HTTP/1.1\r\n" +
"HOST: 255.255.255.255:1900\r\n" +
"ST:upnp:rootdevice\r\n" +
"MAN:\"ssdp:discover\"\r\n" +
"MX:3\r\n\r\n";
byte[] data = Encoding.ASCII.GetBytes(req);
s.BeginSendTo(data, 0, data.Length, SocketFlags.None, new IPEndPoint(IPAddress.Broadcast, 1900),
delegate(IAsyncResult ar)
{
s.EndSendTo(ar);
byte[] buffer = new byte[0x1000];
s.BeginReceive(buffer, 0, 0x1000, SocketFlags.None, delegate(IAsyncResult rar)
{
int l = s.EndReceive(rar);
string resp = Encoding.ASCII.GetString(buffer, 0, l);
resp = resp.Substring(resp.IndexOf("Location:") + "Location:".Length);
resp = resp.Substring(0, resp.IndexOf(".xml") + 4);
descUrl = resp;
BeginGetServiceUrl(callback);
}, null);
}, null);
}
public static string Discover()
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
string req = "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";
byte[] data = Encoding.ASCII.GetBytes(req);
byte[] buffer = new byte[0x1000];
string resp;
do
{
s.SendTo(data, new IPEndPoint(IPAddress.Broadcast, 1900));
s.SendTo(data, new IPEndPoint(IPAddress.Broadcast, 1900));
s.SendTo(data, new IPEndPoint(IPAddress.Broadcast, 1900));
int l = s.Receive(buffer);
resp = Encoding.ASCII.GetString(buffer, 0, l);
if (resp.ToLower().Contains("upnp:rootdevice"))
{
resp = resp.Substring(resp.ToLower().IndexOf("location:") + "Location:".Length);
resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
if (resp.ToLower().Contains(".xml"))
{
descUrl = resp;
GetServiceUrl();
break;
}
}
} while (true);
return resp;
}
static string descUrl;
static string serviceUrl;
static void BeginGetServiceUrl(Method callback)
{
WebRequest req = WebRequest.Create(descUrl);
XmlDocument xdoc = new XmlDocument();
req.BeginGetResponse(delegate(IAsyncResult ar)
{
xdoc.Load(req.EndGetResponse(ar).GetResponseStream());
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
string relurl = xdoc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:WANIPConnection:1\"]/tns:controlURL/text()", nsMgr).Value;
serviceUrl = descUrl.Substring(0, descUrl.IndexOf(":80") + 3) + relurl;
if (callback != null) callback();
}, null);
}
static void GetServiceUrl()
{
XmlDocument desc = new XmlDocument();
desc.Load(WebRequest.Create(descUrl).GetResponse().GetResponseStream());
XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:WANIPConnection:1\"]/tns:controlURL/text()", nsMgr);
if (node != null)
{
string relurl = node.Value;
serviceUrl = descUrl.Substring(0, descUrl.LastIndexOf("/")) + relurl;
}
}
public static IPAddress GetExternalIP()
{
if (string.IsNullOrEmpty(serviceUrl))
throw new Exception("No UPnP service available or Discover() has not been called");
XmlDocument xdoc = SOAPRequest(serviceUrl, "<u:getexternalipaddress xmlns:u="\"urn:schemas-upnp-org:service:WANIPConnection:1\"">\r\n" +
"</u:getexternalipaddress>\r\n", "GetExternalIPAddress");
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
string IP = xdoc.SelectSingleNode("
return IPAddress.Parse(IP);
}
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");
}
static XmlDocument SOAPRequest(string url, string soap, string function)
{
string req = "\r\n" +
"<s:envelope xmlns:s="\"http://schemas.xmlsoap.org/soap/envelope/\"" s:encodingstyle="\"http://schemas.xmlsoap.org/soap/encoding/\"">\r\n" +
"<s:body>\r\n" +
soap +
"</s:body>\r\n" +
"</s:envelope>";
WebRequest r = HttpWebRequest.Create(url);
r.Method = "POST";
byte[] b = Encoding.UTF8.GetBytes(req);
r.Headers.Add("SOAPACTION", "urn:schemas-upnp-org:service:WANIPConnection:1#" + function);
r.ContentLength = b.Length;
r.GetRequestStream().Write(b, 0, b.Length);
XmlDocument resp = new XmlDocument();
WebResponse wres = r.GetResponse();
Stream ress = wres.GetResponseStream();
resp.Load(ress);
return resp;
}
}
}
|
|
|
|
|
Thanx, that works too, so it has to be better
Sadly I only have one NAT router to test this on, and there are still some assumptions there - but they are ones that you can make then, yes?
Do you mind if I update my code and article accordingly?
|
|
|
|
|
Sure go for it!
Also, I've just found two more problems talking to the DG834G; you need quotes around the soap action, and you need to specify the ContentType correctly.
r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + function + "\"");
r.ContentType = "text/xml; charset=\"utf-8\"";
I've just spend the last few hours tinkering with this code, and I have to say that I'm quite impressed with how well it works now.
Just for reference, I'll now post below the handshaking which goes on for the various UPnP commands. Note that in these examples my DG834G has an IP address of 192.168.0.1 , and my PC has an IP address of 192.168.0.5 .
Get External IP Address
Request
POST /upnp/control/WANIPConnection HTTP/1.1
HOST: 192.168.0.1:49153
CONTENT-LENGTH: 295
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetExternalIPAddress xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
</u:GetExternalIPAddress>
</s:Body>
</s:Envelope>
Response (Actual Address Obfuscated)
HTTP/1.1 200 OK
CONTENT-LENGTH: 331
CONTENT-TYPE: text/xml; charset="utf-8"
DATE: Tue, 22 Jul 2008 13:32:15 GMT
EXT:
SERVER: Linux/2.4.17_mvl21-malta-mips_fp_le, UPnP/1.0, Intel SDK for UPnP devices /1.2
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetExternalIPAddressResponse xmlns:u="urn:upnp-org:serviceId:WANIPConnection">
<NewExternalIPAddress>999.999.999.999</NewExternalIPAddress>
</u:GetExternalIPAddressResponse>
</s:Body>
</s:Envelope>
Get Port Mapping Entry (Successful)
Request
POST /upnp/control/WANIPConnection HTTP/1.1
HOST: 192.168.0.1:49153
CONTENT-LENGTH: 351
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#GetGenericPortMappingEntry"
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetGenericPortMappingEntry xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewPortMappingIndex>1</NewPortMappingIndex>
</u:GetGenericPortMappingEntry>
</s:Body>
</s:Envelope>
Response
HTTP/1.1 200 OK
CONTENT-LENGTH: 616
CONTENT-TYPE: text/xml; charset="utf-8"
DATE: Tue, 22 Jul 2008 13:32:14 GMT
EXT:
SERVER: Linux/2.4.17_mvl21-malta-mips_fp_le, UPnP/1.0, Intel SDK for UPnP devices /1.2
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetGenericPortMappingEntryResponse xmlns:u="urn:upnp-org:serviceId:WANIPConnection">
<NewRemoteHost>
</NewRemoteHost>
<NewExternalPort>1234</NewExternalPort>
<NewProtocol>UDP</NewProtocol>
<NewInternalPort>1234</NewInternalPort>
<NewInternalClient>192.168.0.5</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>uTorrent (UDP)</NewPortMappingDescription>
<NewLeaseDuration>0</NewLeaseDuration>
</u:GetGenericPortMappingEntryResponse>
</s:Body>
</s:Envelope>
Get Port Mapping Entry (Unsuccessful)
Request
POST /upnp/control/WANIPConnection HTTP/1.1
HOST: 192.168.0.1:49153
CONTENT-LENGTH: 351
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#GetGenericPortMappingEntry"
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetGenericPortMappingEntry xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewPortMappingIndex>2</NewPortMappingIndex>
</u:GetGenericPortMappingEntry>
</s:Body>
</s:Envelope>
Response
HTTP/1.1 500 Internal Server Error
CONTENT-LENGTH: 410
CONTENT-TYPE: text/xml; charset="utf-8"
DATE: Tue, 22 Jul 2008 13:32:14 GMT
EXT:
SERVER: Linux/2.4.17_mvl21-malta-mips_fp_le, UPnP/1.0, Intel SDK for UPnP devices /1.2
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<s:Fault>
<faultcode>s:Client</faultcode>
<faultstring>UPnPError</faultstring>
<detail>
<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
<errorCode>402</errorCode>
<errorDescription>Invalid Args</errorDescription>
</UPnPError>
</detail>
</s:Fault>
</s:Body>
</s:Envelope>
Add Port Mapping
Request
POST /upnp/control/WANIPConnection HTTP/1.1
HOST: 192.168.0.1:49153
CONTENT-LENGTH: 607
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewRemoteHost>
</NewRemoteHost>
<NewExternalPort>1234</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>1234</NewInternalPort>
<NewInternalClient>192.168.0.5</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>uTorrent (TCP)</NewPortMappingDescription>
<NewLeaseDuration>0</NewLeaseDuration>
</u:AddPortMapping>
</s:Body>
</s:Envelope>
Response
HTTP/1.1 200 OK
CONTENT-LENGTH: 259
CONTENT-TYPE: text/xml; charset="utf-8"
DATE: Tue, 22 Jul 2008 13:32:14 GMT
EXT:
SERVER: Linux/2.4.17_mvl21-malta-mips_fp_le, UPnP/1.0, Intel SDK for UPnP devices /1.2
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMappingResponse xmlns:u="urn:upnp-org:serviceId:WANIPConnection">
</u:AddPortMappingResponse>
</s:Body>
</s:Envelope>
Delete Port Mapping
Request
POST /upnp/control/WANIPConnection HTTP/1.1
HOST: 192.168.0.1:49153
CONTENT-LENGTH: 390
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:DeletePortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewRemoteHost>
</NewRemoteHost>
<NewExternalPort>1234</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
</u:DeletePortMapping>
</s:Body>
</s:Envelope>
Response
HTTP/1.1 200 OK
CONTENT-LENGTH: 265
CONTENT-TYPE: text/xml; charset="utf-8"
DATE: Tue, 22 Jul 2008 13:32:14 GMT
EXT:
SERVER: Linux/2.4.17_mvl21-malta-mips_fp_le, UPnP/1.0, Intel SDK for UPnP devices /1.2
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:DeletePortMappingResponse xmlns:u="urn:upnp-org:serviceId:WANIPConnection">
</u:DeletePortMappingResponse>
</s:Body>
</s:Envelope>
Hope this is useful to you / others.
modified on Tuesday, July 22, 2008 11:24 AM
|
|
|
|
|
Thanx
Will update soon
|
|
|
|
|
I'm going to expand it to support query of existing mappings and deletion of mappings. I'll let you know how I go.
|
|
|
|
|
Cool
Good luck, it would be nice to add those
So far I've had little success with the querying of existing mappings.. must be doing something wrong..
So I hope you get it working
|
|
|
|
|
I got a little carried away, and refactored the whole thing. Changes include:
1) Add, Delete, Query all mappings.
2) Supports multiple routers on one subnet.
3) Supports router discovery without SSDP.
4) Supports multiple network card setups.
5) Includes improved SOAP compliance, and a nicer abstraction of all soap actions.
6) Added a lot of comments to the code.
7) Broke all Async code (sorry, but you should make the async entrypoints jsut wrap the regular ones.)
**Note to all who try this, please let me know whether it works for you!**
// SNIP //
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Xml;
using System.IO;
namespace UPnP
{
//public delegate void Method();
public class NAT
{
//public static void BeginDiscover(Method callback)
//{
// Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
// String req =
// "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";
// byte[] data = Encoding.ASCII.GetBytes(req);
// s.BeginSendTo(data, 0, data.Length, SocketFlags.None, new IPEndPoint(IPAddress.Broadcast, 1900),
// delegate(IAsyncResult ar)
// {
// s.EndSendTo(ar);
// byte[] buffer = new byte[0x1000];
// s.BeginReceive(buffer, 0, 0x1000, SocketFlags.None, delegate(IAsyncResult rar)
// {
// int l = s.EndReceive(rar);
// String resp = Encoding.ASCII.GetString(buffer, 0, l);
// if (resp.ToLower().Contains("upnp:rootdevice"))
// {
// resp = resp.Substring(resp.ToLower().IndexOf("location:") + "Location:".Length);
// resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
// if (resp.ToLower().Contains(".xml"))
// {
// descUrl = resp;
// BeginGetServiceUrl(callback);
// }
// }
// }, null);
// }, null);
//}
/// <summary>
/// Below are several possible Service Description URL Suffixes
/// Some are found through personal testing, other are found
/// from Google searchs for potential SSDP query results.
/// </summary>
static String[] ServiceSuffixes = new String[] {
"/desc.xml", // DLink DWL....
"/description.xml",
"/InternetGatewayDevice.xml",
"/upnp/service/des_ppp.xml",
"/wanipconn-361.xml",
":5678/igd.xml",
":5678/rootDesc.xml",
":49000/igddesc.xml",
":49152/gateway.xml", // DG834Gv3
":49153/gateway.xml", // DG834Gv1
":54877/",
":49152/description.xml",
":1064/",
":1900/igd.xml",
""};
/// <summary>
/// Here are some possible SSDP Broadcast addresses to try.
/// The standard requires only the first, but we're trying to
/// handle devices which may not necessarily obey the standard too.
/// </summary>
static String[] SSDPAddresses = new String[] {
"239.255.255.250"/*,
"255.255.255.255"*/
};
/// <summary>
/// Here we store the results for SSDP and Default Gateway Probing.
/// </summary>
static List<String> PotentialHosts = new List<String>();
/// <summary>
/// Here we store the confirmed UPnP service control URLs
/// </summary>
static List<String> ServiceURLs = new List<String>();
static IPAddress MyAddress = null;
/// <summary>
/// Begin the discovery process.
/// </summary>
/// <returns>The number of NAT devices discovered</returns>
public static int Discover()
{
PotentialHosts.Clear();
ServiceURLs.Clear();
DefaultGateways_Probe();
SSPD_Probe();
GetServiceUrls();
return ServiceURLs.Count;
}
/// <summary>
/// Use SSDP to discover NAT routers in the local network.
/// </summary>
private static void SSPD_Probe()
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
s.ReceiveTimeout = 100;
foreach (String sSSDPAddress in SSDPAddresses)
{
String req = String.Format(
"M-SEARCH * HTTP/1.1\r\n" +
"HOST: {0}:1900\r\n" +
"ST:upnp:rootdevice\r\n" +
"MAN:\"ssdp:discover\"\r\n" +
"MX:3\r\n\r\n",
sSSDPAddress);
byte[] data = Encoding.ASCII.GetBytes(req);
byte[] buffer = new byte[0x1000];
for (int i = 0; i < 2; i++)
{
try
{
s.SendTo(data, new IPEndPoint(IPAddress.Broadcast, 1900));
while (true)
{
int l = s.Receive(buffer);
String resp = Encoding.ASCII.GetString(buffer, 0, l);
if (resp.ToLower().Contains("upnp:rootdevice"))
{
resp = resp.Substring(resp.ToLower().IndexOf("location:") + "Location:".Length);
resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
if (!PotentialHosts.Contains(resp))
{
PotentialHosts.Add(resp);
}
}
}
}
catch
{
}
}
}
}
/// <summary>
/// Iterate through all local network adaptors. Find out the
/// default gateway, and store possible addresses on the default
/// gateway for further probing later.
/// </summary>
private static void DefaultGateways_Probe()
{
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
foreach (NetworkInterface networkCard in NetworkInterface.GetAllNetworkInterfaces())
{
foreach (GatewayIPAddressInformation gatewayAddr in networkCard.GetIPProperties().GatewayAddresses)
{
// Store the first IP Address of the first Network interface which
// has a valid default gateway. (Where there are multiple network
// interfaces on a single PC, this one is most likely the one we want.
if (MyAddress == null)
{
MyAddress = networkCard.GetIPProperties().UnicastAddresses[0].Address;
}
// Transform the discovered gateway to a set of possible
// urls, based on possible Service URLS.
foreach (String sSuffix in ServiceSuffixes)
{
String sHost = String.Format(@"http://{0}{1}", gatewayAddr.Address.ToString(), sSuffix);
if (!PotentialHosts.Contains(sHost))
{
PotentialHosts.Add(sHost);
}
}
}
}
}
//static void BeginGetServiceUrl(Method callback)
//{
// WebRequest req = WebRequest.Create(descUrl);
// req.BeginGetResponse(delegate(IAsyncResult ar)
// {
// GetServiceUrl();
// if (callback != null) callback();
// }, null);
//}
/// <summary>
/// Quickly scan through all potential UPnP addresses, discarding
/// anything that looks wrong, and retreiving service URLS for
/// those that work.
/// </summary>
static void GetServiceUrls()
{
foreach (String descUrl in PotentialHosts.ToArray())
{
try
{
WebRequest web = WebRequest.Create(descUrl);
web.Timeout = 50;
XmlDocument desc = new XmlDocument();
desc.Load(web.GetResponse().GetResponseStream());
XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:WANIPConnection:1\"]/tns:controlURL/text()", nsMgr);
if (node != null)
{
String sRelativeURL = node.Value;
int n = descUrl.IndexOf("://");
if (n > -1)
{
n = descUrl.IndexOf('/', n + 3);
if (n > -1)
{
ServiceURLs.Add(descUrl.Substring(0, n) + sRelativeURL);
continue;
}
}
}
}
catch (WebException)
{ }
PotentialHosts.Remove(descUrl);
}
}
public static IPAddress GetExternalIP(int iServiceId)
{
IPAddress addr = null;
String sServiceURL = ServiceURLs[iServiceId];
SoapBuilder soap = new SoapBuilder(sServiceURL, "GetExternalIPAddress");
if (soap.TryPostMessage())
{
try
{
String IP = soap.GetValue("NewExternalIPAddress");
addr = IPAddress.Parse(IP);
}
catch { }
}
return addr;
}
public static Boolean AddPortMapping(int iServiceId, int Port, ProtocolType Protocol, String Description)
{
Boolean bRet = false;
String serviceUrl = ServiceURLs[iServiceId];
SoapBuilder soap = new SoapBuilder(serviceUrl, "AddPortMapping");
soap.AddParams("NewRemoteHost", "");
soap.AddParams("NewExternalPort", Port);
soap.AddParams("NewProtocol", Protocol);
soap.AddParams("NewInternalPort", Port);
soap.AddParams("NewInternalClient", MyAddress);
soap.AddParams("NewEnabled", 1);
soap.AddParams("NewPortMappingDescription", Description);
soap.AddParams("NewLeaseDuration", 0);
if (soap.TryPostMessage())
{
bRet = true;
}
return bRet;
}
public static Boolean GetGenericPortMapping(int iServiceId, int index, out int Port, out ProtocolType Protocol, out IPAddress Client, out Boolean Enabled, out String Description)
{
Boolean bRet = false;
Port = 0;
Protocol = ProtocolType.Unknown;
Client = IPAddress.None;
Enabled = false;
Description = String.Empty;
String sServiceURL = ServiceURLs[iServiceId];
SoapBuilder soap = new SoapBuilder(sServiceURL, "GetGenericPortMappingEntry");
soap.AddParams("NewPortMappingIndex", index);
if (soap.TryPostMessage())
{
try
{
String sPort = soap.GetValue("NewInternalPort");
String sProtocol = soap.GetValue("NewProtocol");
String sClient = soap.GetValue("NewInternalClient");
String sEnabled = soap.GetValue("NewEnabled");
String sDescription = soap.GetValue("NewPortMappingDescription");
Port = int.Parse(sPort);
Protocol = sProtocol.ToLower().Equals("udp") ? ProtocolType.Udp : ProtocolType.Tcp;
Client = IPAddress.Parse(sClient);
Enabled = int.Parse(sEnabled) > 0;
Description = sDescription;
bRet = true;
}
catch { }
}
return bRet;
}
public static Boolean DeletePortMapping(int iServiceId, int Port, ProtocolType Protocol)
{
Boolean bRet = false;
String sServiceURL = ServiceURLs[iServiceId];
SoapBuilder soap = new SoapBuilder(sServiceURL, "DeletePortMapping");
soap.AddParams("NewRemoteHost");
soap.AddParams("NewExternalPort", Port);
soap.AddParams("NewProtocol", Protocol);
if (soap.TryPostMessage())
{
bRet = true;
}
return bRet;
}
}
/// <summary>
/// Abstraction for managing simple SOAP interactions.
/// </summary>
class SoapBuilder
{
readonly String URL;
readonly String Action;
XmlDocument Response = null;
XmlNamespaceManager nsMgr = null;
/// <summary>
/// Construct a new SOAP message
/// </summary>
/// <param name="url">UPnP Control URL</param>
/// <param name="action">SOAP Action</param>
public SoapBuilder(String url, String action)
{
URL = url;
Action = action;
}
List<String> Params = new List<string>();
List<String> Types = new List<string>();
List<object> Values = new List<object>();
public void AddParams(String sParam)
{
AddParams(sParam, String.Empty);
}
public void AddParams(String sParam, object oValue)
{
Params.Add(sParam);
String sType;
if (oValue is int)
{
sType = "ui2";
}
else if (oValue is string)
{
sType = "string";
}
else if (oValue is ProtocolType)
{
sType = "string";
oValue = oValue.ToString().ToUpper();
}
else if (oValue is IPAddress)
{
sType = "string";
}
else
{
sType = "string";
}
Types.Add(sType);
Values.Add(oValue);
}
/// <summary>
/// Build up a SOAP message with the information we now have, send it off, and interpret the response.
/// </summary>
/// <returns>Returns TRUE if no error occurred. (Will return TRUE even if you are requesting an invalid deletion.</returns>
public Boolean TryPostMessage()
{
Boolean bRet = false;
StringBuilder sbRequest = new StringBuilder();
sbRequest.AppendLine("<?xml version=\"1.0\"?>");
sbRequest.Append("<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">");
sbRequest.Append("<SOAP-ENV:Body>");
sbRequest.AppendFormat("<m:{0} xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">", Action);
for (int i = 0; i < Params.Count; i++)
{
sbRequest.AppendFormat("<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", Params[i], Types[i], Values[i]);
}
sbRequest.AppendFormat("</m:{0}>", Action);
sbRequest.Append("</SOAP-ENV:Body>");
sbRequest.Append("</SOAP-ENV:Envelope>");
Byte[] soapRequest = Encoding.UTF8.GetBytes(sbRequest.ToString());
WebRequest webReq = HttpWebRequest.Create(URL);
webReq.Timeout = 500;
webReq.Method = "POST";
webReq.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + Action + "\"");
webReq.ContentType = "text/xml; charset=\"utf-8\"";
webReq.ContentLength = soapRequest.Length;
try
{
webReq.GetRequestStream().Write(soapRequest, 0, soapRequest.Length);
Response = new XmlDocument();
Response.Load(webReq.GetResponse().GetResponseStream());
nsMgr = new XmlNamespaceManager(Response.NameTable);
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
bRet = true;
}
catch { }
return bRet;
}
public String GetValue(String Param)
{
return Response.SelectSingleNode(String.Format("//{0}/text()", Param), nsMgr).Value;
}
}
}
// SNIP //
|
|
|
|
|
Nice nice
I'll work on updating everything
Thanks
|
|
|
|
|
ajiau
I tried to work with your code but my router which is Wireless-N Gigabit Router WRT310N
is returning this error
System.Net.WebException: The remote server returned an error: (500) Internal Server Error.
at System.Net.HttpWebRequest.GetResponse()
Any Idea
I am calling the Service like this
NAT.Discover();
// NAT.GetExternalIP(0);
NAT.AddPortMapping(0, 4098, ProtocolType.Tcp, "HelloWorld");
|
|
|
|
|
More info on the error:
I can see your code detecting IGD with success so Congrats over that:
Here is the request object created :
COntrol URL is fine and responding: here is the xml returned by the router:
<?xml version="1.0" ?>
- <root xmlns="urn:schemas-upnp-org:device-1-0">
- <specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<URLBase>http://192.168.1.1:5431</URLBase>
- <device>
<deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>
<presentationURL>/index.asp</presentationURL>
<friendlyName>WRT310N</friendlyName>
<manufacturer>Linksys Inc.</manufacturer>
<manufacturerURL>http://www.linksys.com/</manufacturerURL>
<modelDescription>Internet Access Server</modelDescription>
<modelName>WRT310N</modelName>
<modelNumber>v1.00.1</modelNumber>
<modelURL>http://www.linksys.com/</modelURL>
<UDN>uuid:001d7ee1-b8ed-001d-7ee1-b8ed0000e800</UDN>
- <serviceList>
- <service>
<serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType>
<serviceId>urn:upnp-org:serviceId:Layer3Forwarding:11</serviceId>
<controlURL>/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0000e800/Layer3Forwarding:1</controlURL>
<eventSubURL>/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0000e800/Layer3Forwarding:1</eventSubURL>
<SCPDURL>/dynsvc/Layer3Forwarding:1.xml</SCPDURL>
</service>
</serviceList>
- <deviceList>
- <device>
<deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
<friendlyName>urn:schemas-upnp-org:device:WANDevice:1</friendlyName>
<manufacturer>Linksys Inc.</manufacturer>
<manufacturerURL>http://www.linksys.com/</manufacturerURL>
<modelDescription>Internet Access Server</modelDescription>
<modelName>WRT310N</modelName>
<modelNumber>v1.00.1</modelNumber>
<modelURL>http://www.linksys.com/</modelURL>
<UDN>uuid:001d7ee1-b8ed-001d-7ee1-b8ed0100e800</UDN>
- <serviceList>
- <service>
<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
<controlURL>/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0100e800/WANCommonInterfaceConfig:1</controlURL>
<eventSubURL>/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0100e800/WANCommonInterfaceConfig:1</eventSubURL>
<SCPDURL>/dynsvc/WANCommonInterfaceConfig:1.xml</SCPDURL>
</service>
</serviceList>
- <deviceList>
- <device>
<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
<friendlyName>urn:schemas-upnp-org:device:WANConnectionDevice:1</friendlyName>
<manufacturer>Linksys Inc.</manufacturer>
<manufacturerURL>http://www.linksys.com/</manufacturerURL>
<modelDescription>Internet Access Server</modelDescription>
<modelName>WRT310N</modelName>
<modelNumber>v1.00.1</modelNumber>
<modelURL>http://www.linksys.com/</modelURL>
<UDN>uuid:001d7ee1-b8ed-001d-7ee1-b8ed0200e800</UDN>
- <serviceList>
- <service>
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
<controlURL>/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0200e800/WANIPConnection:1</controlURL>
<eventSubURL>/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0200e800/WANIPConnection:1</eventSubURL>
<SCPDURL>/dynsvc/WANIPConnection:1.xml</SCPDURL>
</service>
- <service>
<serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANPPPConn1</serviceId>
<controlURL>/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0200e800/WANPPPConnection:1</controlURL>
<eventSubURL>/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0200e800/WANPPPConnection:1</eventSubURL>
<SCPDURL>/dynsvc/WANPPPConnection:1.xml</SCPDURL>
</service>
</serviceList>
</device>
</deviceList>
</device>
</deviceList>
</device>
</root>
I got these results by directly putting the URL in the browser returned by your code.
anf the URI is
http://192.168.1.1:5431/dyndev/uuid:001d7ee1-b8ed-001d-7ee1-b8ed0000e800[^]
If you need more info letme know
Mean while i am also trying to debug
|
|
|
|
|
Without wanting to sound like an ass, but rather than reimplement what has already been implemented, would you consider submitting patches to Mono.Nat if you have a use-case where it doesn't work?
|
|
|
|
|
Does that case exist though?
|
|
|
|
|
I would say that it's quite possible that there are still a few routers out there that Mono.Nat won't work with.
The other thing is that Nat-Pmp support is (as of yet) incomplete, and since I lack a nat-pmp capable router, I can't complete it. So if you do have access to one and were interested in completing nat-pmp support, that'd be awesome.
I'm just of the opinion that it's better to contribute to an existing project if possible rather than recreate from scratch. It means that if someone googles for port forwarding in .NET, they have one well tested and widely used option rather than 2 not-so-well-tested options which have virtually identical APIs and do the exact same thing.
Of course, if there's a licensing issue then fair enough, a rewrite might be necessary, or if you really want to write your own for the experience.
|
|
|
|
|
I would be interested in NAT-PMP but I don't have a router that supports it..
Where is Mono.Nat though? When I search on google I just get this article back along with some very scary patch files..
|
|
|
|
|