Archive

Posts Tagged ‘xml’

UPnP Tomfoolery – Using UPnP for peer-to-peer connections

April 26, 2010 1 comment

I’m writing this post for two reasons:

  1. It is a pretty cool thing to do and very useful
  2. The amount of useful documentation and links you can find is very small

It took me about 12 hours of futsing with this and Googling things to get this correct. Most of my information actually came from bug reports of various open source projects that use UPnP for peer-to-peer connections.

I haven’t put much error checking in here, I want to keep the code short and as easy to understand as possible. I’ll leave that one up to the users to figure out.

To use this code, you must first call NAT.Discover(). This makes sure you have a UPnP device available. After that you can go ahead and Add and Delete ports as you wish. Heres the code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Xml;
using System.IO;

namespace enChatClient
{
    public class NAT
    {
        static TimeSpan _timeout = new TimeSpan(0, 0, 0, 3);
        public static TimeSpan TimeOut
        {
            get { return _timeout; }
            set { _timeout = value; }
        }
        static string _descUrl, _serviceUrl, _eventUrl;
        public static bool Discover()
        {
            System.Net.NetworkInformation.NetworkInterface nic = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0];

            System.Net.NetworkInformation.GatewayIPAddressInformation gwInfo = nic.GetIPProperties().GatewayAddresses[0];
            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: " + gwInfo.Address.ToString() + ":1900\r\n" +
            "ST:upnp:rootdevice\r\n" +
            "MAN:\"ssdp:discover\"\r\n" +
            "MX:3\r\n\r\n";
            Socket client = new Socket(AddressFamily.InterNetwork,
                SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint endPoint = new
            IPEndPoint(IPAddress.Parse(gwInfo.Address.ToString()), 1900);

            client.SetSocketOption(SocketOptionLevel.Socket,
                SocketOptionName.ReceiveTimeout, 5000);

            byte[] q = Encoding.ASCII.GetBytes(req);
            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);
            string queryResponse = "";
            queryResponse = Encoding.ASCII.GetString(data);

            DateTime start = DateTime.Now;

            string resp = queryResponse;
            if (resp.Contains("upnp:rootdevice"))
            {
                resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
                resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
                if (!string.IsNullOrEmpty(_serviceUrl = GetServiceUrl(resp)))
                {
                    _descUrl = resp;
                    return true;
                }
            }
            return false;
        }

        private static string GetServiceUrl(string resp)
        {
            XmlDocument desc = new XmlDocument();
            try
            {
                desc.Load(WebRequest.Create(resp).GetResponse().GetResponseStream());
            }
            catch (Exception)
            {
                return null;
            }   
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
            nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
            XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
            if (!typen.Value.Contains("InternetGatewayDevice"))
                return null;
            XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:WANIPConnection:1\"]/tns:controlURL/text()", nsMgr);
            if (node == null)
                return null;
            XmlNode eventnode = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:WANIPConnection:1\"]/tns:eventSubURL/text()", nsMgr);
            _eventUrl = CombineUrls(resp, eventnode.Value);
            return CombineUrls(resp, node.Value);
        }

        private static string CombineUrls(string resp, string p)
        {
            int n = resp.IndexOf("://");
            n = resp.IndexOf('/', n + 3);
            return resp.Substring(0, n) + p;
        }

        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");

            IPHostEntry ipEntry = Dns.GetHostByName(Dns.GetHostName());
            IPAddress addr = ipEntry.AddressList[0];

            XmlDocument xdoc = SOAPRequest(_serviceUrl,
                "<m:AddPortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\"><NewRemoteHost xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\"></NewRemoteHost><NewExternalPort xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui2\">" +
                port.ToString() + "</NewExternalPort><NewProtocol xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">" +
                protocol.ToString().ToUpper() + "</NewProtocol><NewInternalPort xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui2\">" +
                port.ToString() + "</NewInternalPort><NewInternalClient xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">" +
                addr + "</NewInternalClient><NewEnabled xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"boolean\">1</NewEnabled><NewPortMappingDescription xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">" +
                description + "</NewPortMappingDescription><NewLeaseDuration xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui4\">0</NewLeaseDuration></m:AddPortMapping>",
                "AddPortMapping");
        }

        public static void DeleteForwardingRule(int port, ProtocolType protocol)
        {
            if (string.IsNullOrEmpty(_serviceUrl))
                throw new Exception("No UPnP service available or Discover() has not been called");
            
            XmlDocument xdoc = SOAPRequest(_serviceUrl,
            "<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">" +
            "<NewRemoteHost></NewRemoteHost>" +
            "<NewExternalPort>" + port.ToString() + "</NewExternalPort>" +
            "<NewProtocol>" + protocol.ToString().ToUpper() + "</NewProtocol>" +
            "</u:DeletePortMapping>", "DeletePortMapping");
        }

        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\">" +
            "</u:GetExternalIPAddress>", "GetExternalIPAddress");
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
            nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
            string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value;
            return IPAddress.Parse(IP);
        }

        private static XmlDocument SOAPRequest(string url, string soap, string function)
        {
            string req = "<?xml version=\"1.0\"?>" +
            "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
            "<s:Body>" +
            soap +
            "</s:Body>" +
            "</s:Envelope>";
            WebRequest r = HttpWebRequest.Create(url);
            r.Timeout = 10000;
            r.Method = "POST";
            byte[] b = Encoding.UTF8.GetBytes(req);
            r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + function + "\"");
            r.ContentType = "text/xml; charset=\"utf-8\"";
            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;
        }
    }
}

These SOAP requests use two UPnP “commands”:

  • AddPortMapping
  • DeletePortMapping

AddPortMapping needs the following parameters:

  1. NewRemoteHost – Used to make it so that only one remote host can connect. Generally not used and OK to leave blank.
  2. NewExternalPort – The port that the incoming outside connection should be connecting on.
  3. NewProtocol – This is the protocol that will be running on the port. should be either “TCP” or “UDP”.
  4. NewInternalPort – The port on the inside of the network that the connection will be forwarded to.
  5. NewInternalClient – The internal IP address the connection should be forwarded to.
  6. NewEnabled – Whether the connection should be enabled or not. You want to set this to 1 so it actually works.
  7. NewPortMappingDescription – Just a short description of what the port is actually being used for. Generally the program name.
  8. NewLeaseDuration – Just set this to 0.

If the AddPortMapping doesn’t work for some reason you will get a server 500 error. Otherwise you will get an xml response

DeletePortMapping needs the following parameters:

  1. NewRemoteHost – Same thing as AddPortMapping. Not really used.
  2. NewExternalPort – The external port that the forwarding you’re deleting is running on.
  3. NewProtocol – The protocol it is running. “TCP” or “UDP”

If there is no such port mapping in the table, you will get a server 500 error, so be careful.

You can also check to see if a mapping exists by using GetSpecificPortMapping entry which takes NewRemoteHost, NewExternalPort, and NewPortMappingProtocol, and it returns a bunch of data on the connection. However the downside is that if it doesn’t exist, you get a server 500 error, and these can be trick to handle if you’re using a WebRequest, as it just throws it as a WebException.

The other way to do it is to GetGenericPortMappingEntry which returns the table of them and you can search it for the one you want.

NOTE: AddPortMapping will overwrite the previous mapping on the same port and protocol.

Thoughts About Distributed Computing

January 20, 2008 3 comments

For me, distributed and parallel computing is one of the most interesting areas in computer science that I have found so far. I would really love to do work/research with it.

Distributed and Parallel Computing, at least from how I see it, seems to be the future of computers and processing power. Processors are starting to reach towards their physical limitations. So instead of making one processor even better, why not just use several, or even hundreds.

Slowly, I’m putting together my spare parts that I have at home into more computers to use. I got two more working in the last 2 days, both of which are semi decent machines. The main thing that I want to do with them is to try to make some of my own distributed applications that will be able to spread its processes out over several computers. Right now I have 3 computers that I want to use in my distributed system, the most powerful of which will be the main computer that is in charge of assigning the tasks. the other two, which are not as powerful, but still can handle plenty, will be sent instructions that will be processed and then they will send results back to the main server.

Now seeing as the 2 secondary nodes that will be in the system do not have the same specs, I have to make sure the server pays attention to the % of their resources that are being used when it goes to assign instructions. If the slower computer has been assigned less, but is using more of its resources, the next task that needs to be processed will be sent to the other computer.

Currently both of my secondary nodes are running Windows XP. This is fine I guess, but it seems like a large waste of system resources to have to run full windows. Though I realize that there is no way in hell that I can actually do this, I would love to write my own very basic OS just to handle being a secondary node in a distributed system. A node that when it turns on, simply connects to the central server, waits for its tasks, and then executes and sends back reports. No need for hogging resources with a gui or anything. All that is particularly necessary is enough to execute instructions and send/receive data over the network. But, when you actually think about it, that would take a very long time for me to figure out how to do. I don’t know the first thing about where an operating system starts really. Maybe I could use a linux kernel and just build off of that and get rid of things that aren’t really needed. I need to do a lot of research on this one.

This seems like it has the opportunity to be extremely efficient. Just a lightweight OS meant solely for being a secondary machine in a distributed  system would be so much faster than running anything i could create as a windows application. Unfortunately, I don’t know where to begin. Maybe I will set this one aside as a very long term project, slowly do research on it, and spend the bulk of my time on other projects are already have going like Fizzure. Haven’t worked on that one in a few days. Need to get back to it. I’ve been doing some little practice applications with sending and receiving data with the TcpClient and TcpListener class in C#. Now that I think I have a much better handle on how those work, it should be much easier to get over the hurdle I was stuck on with Fizzure.

Another goal I have is to make the Fizzure central server able to be split into nodes. Have different sections of all the XML data to be searched stored on different nodes. when a query comes in, the server sends a request to each node, each node searches the part of the data that it has stored and returns its results to the server, which than returns all of the results to the client. This seems more efficient than just having it all done by one computer, though because of network bandwidth, I don’t quite know if it would be in actuality.

Follow

Get every new post delivered to your Inbox.