using System; using System.IO; using System.Xml; using System.Net; using System.Net.Sockets; using System.Threading; namespace Lidgren.Network { /// /// Status of the UPnP capabilities /// public enum UPnPStatus { /// /// Still discovering UPnP capabilities /// Discovering, /// /// UPnP is not available /// NotAvailable, /// /// UPnP is available and ready to use /// Available } /// /// UPnP support class /// public class NetUPnP { private const int c_discoveryTimeOutMillis = 1000; private string m_serviceUrl; private string m_serviceName = ""; private NetPeer m_peer; private ManualResetEvent m_discoveryComplete = new ManualResetEvent(false); internal float m_discoveryResponseDeadline; private UPnPStatus m_status; /// /// Status of the UPnP capabilities of this NetPeer /// public UPnPStatus Status { get { return m_status; } } /// /// NetUPnP constructor /// public NetUPnP(NetPeer peer) { m_peer = peer; m_discoveryResponseDeadline = float.MinValue; } internal void Discover(NetPeer peer) { string str = "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"; m_status = UPnPStatus.Discovering; byte[] arr = System.Text.Encoding.UTF8.GetBytes(str); m_peer.LogDebug("Attempting UPnP discovery"); peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); peer.RawSend(arr, 0, arr.Length, new IPEndPoint(IPAddress.Broadcast, 1900)); peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false); // allow some extra time for router to respond // System.Threading.Thread.Sleep(50); m_discoveryResponseDeadline = (float)NetTime.Now + 6.0f; // arbitrarily chosen number, router gets 6 seconds to respond m_status = UPnPStatus.Discovering; } internal void ExtractServiceUrl(string resp) { #if !DEBUG try { #endif XmlDocument desc = new XmlDocument(); desc.Load(WebRequest.Create(resp).GetResponse().GetResponseStream()); 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; m_serviceName = "WANIPConnection"; XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr); if (node == null) { //try another service name m_serviceName = "WANPPPConnection"; node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr); if (node == null) return; } m_serviceUrl = CombineUrls(resp, node.Value); m_peer.LogDebug("UPnP service ready"); m_status = UPnPStatus.Available; m_discoveryComplete.Set(); #if !DEBUG } catch { m_peer.LogVerbose("Exception ignored trying to parse UPnP XML response"); return; } #endif } private static string CombineUrls(string gatewayURL, string subURL) { // Is Control URL an absolute URL? if ((subURL.Contains("http:")) || (subURL.Contains("."))) return subURL; gatewayURL = gatewayURL.Replace("http://", ""); // strip any protocol int n = gatewayURL.IndexOf("/"); if (n != -1) gatewayURL = gatewayURL.Substring(0, n); // Use first portion of URL return "http://" + gatewayURL + subURL; } private bool CheckAvailability() { switch (m_status) { case UPnPStatus.NotAvailable: return false; case UPnPStatus.Available: return true; case UPnPStatus.Discovering: if (m_discoveryComplete.WaitOne(c_discoveryTimeOutMillis)) return true; if (NetTime.Now > m_discoveryResponseDeadline) m_status = UPnPStatus.NotAvailable; return false; } return false; } /// /// Add a forwarding rule to the router using UPnP /// public bool ForwardPort(int port, string description) { if (!CheckAvailability()) return false; IPAddress mask; var client = NetUtility.GetMyAddress(out mask); if (client == null) return false; try { SOAPRequest(m_serviceUrl, "" + "" + "" + port.ToString() + "" + "" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "" + "" + port.ToString() + "" + "" + client.ToString() + "" + "1" + "" + description + "" + "0" + "", "AddPortMapping"); m_peer.LogDebug("Sent UPnP port forward request"); System.Threading.Thread.Sleep(50); } catch (Exception ex) { m_peer.LogWarning("UPnP port forward failed: " + ex.Message); return false; } return true; } /// /// Delete a forwarding rule from the router using UPnP /// public bool DeleteForwardingRule(int port) { if (!CheckAvailability()) return false; try { SOAPRequest(m_serviceUrl, "" + "" + "" + "" + port + "" + "" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "" + "", "DeletePortMapping"); return true; } catch (Exception ex) { m_peer.LogWarning("UPnP delete forwarding rule failed: " + ex.Message); return false; } } /// /// Retrieve the extern ip using UPnP /// public IPAddress GetExternalIP() { if (!CheckAvailability()) return null; try { XmlDocument xdoc = SOAPRequest(m_serviceUrl, "" + "", "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); } catch (Exception ex) { m_peer.LogWarning("Failed to get external IP: " + ex.Message); return null; } } private XmlDocument SOAPRequest(string url, string soap, string function) { string req = "" + "" + "" + soap + "" + ""; WebRequest r = HttpWebRequest.Create(url); r.Method = "POST"; byte[] b = System.Text.Encoding.UTF8.GetBytes(req); r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:" + m_serviceName + ":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; } } }