public void Handle(IPAddress localAddres, byte[] response) { //if (!IsSearchAddress(endpoint.Address)) // return; if (response.Length != 12) { return; } if (response[0] != PmpConstants.Version) { return; } if (response[1] != PmpConstants.ServerNoop) { return; } int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); if (errorcode != 0) { NatUtility.Log("Non zero error: {0}", errorcode); } IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] }); OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(localAddres, publicIp))); }
protected HttpRequestOptions CreateRequest(string upnpMethod, string methodParameters) { string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl; NatUtility.Log("Initiating request to: {0}", ss); var req = new HttpRequestOptions(); req.LogErrors = false; // The periodic request logging may keep some devices awake req.LogRequestAsDebug = true; req.Url = ss; req.EnableKeepAlive = false; req.RequestContentType = "text/xml"; req.AppendCharsetToMimeType = true; req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); string bodyString = "<s:Envelope " + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<s:Body>" + "<u:" + upnpMethod + " " + "xmlns:u=\"" + device.ServiceType + "\">" + methodParameters + "</u:" + upnpMethod + ">" + "</s:Body>" + "</s:Envelope>\r\n\r\n"; req.RequestContentBytes = System.Text.Encoding.UTF8.GetBytes(bodyString); return(req); }
protected override Task HandleMessageReceived(IPAddress localAddress, UdpReceiveResult result, CancellationToken token) { var response = result.Buffer; var endpoint = result.RemoteEndPoint; if (response.Length != 12) { return(Task.CompletedTask); } if (response [0] != PmpConstants.Version) { return(Task.CompletedTask); } if (response [1] != PmpConstants.ServerNoop) { return(Task.CompletedTask); } int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); if (errorcode != 0) { NatUtility.Log("Non zero error: {0}", errorcode); } var publicIp = new IPAddress(new byte [] { response [8], response [9], response [10], response [11] }); RaiseDeviceFound(new PmpNatDevice(endpoint, publicIp)); return(Task.CompletedTask); }
protected WebRequest CreateRequest(string upnpMethod, string methodParameters, out byte[] body) { string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl; NatUtility.Log("Initiating request to: {0}", ss); Uri location = new Uri(ss); HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(location); req.KeepAlive = false; req.Method = "POST"; req.ContentType = "text/xml; charset=\"utf-8\""; req.Headers.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); string bodyString = "<s:Envelope " + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<s:Body>" + "<u:" + upnpMethod + " " + "xmlns:u=\"" + device.ServiceType + "\">" + methodParameters + "</u:" + upnpMethod + ">" + "</s:Body>" + "</s:Envelope>\r\n\r\n"; body = System.Text.Encoding.UTF8.GetBytes(bodyString); return(req); }
internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType) { this.LastSeen = DateTime.Now; this.localAddress = localAddress; // Split the string at the "location" section so i can extract the ipaddress and service description url string locationDetails = deviceInfo.Location.ToString(); this.serviceType = serviceType; // Make sure we have no excess whitespace locationDetails = locationDetails.Trim(); // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address // Are we going to get addresses with the "http://" attached? if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) { NatUtility.Log("Found device at: {0}", locationDetails); // This bit strings out the "http://" from the string locationDetails = locationDetails.Substring(7); this.hostEndPoint = hostEndPoint; NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString()); // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip // and port information this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/')); } else { NatUtility.Log("Couldn't decode address. Please send following string to the developer: "); } }
private async Task <Mapping> InternalCreatePortMapAsync(Mapping mapping, bool create) { var package = new List <byte>(); package.Add(PmpConstants.Version); package.Add(mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp); package.Add(0); //reserved package.Add(0); //reserved package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)mapping.PrivatePort))); package.AddRange( BitConverter.GetBytes(create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0)); package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(mapping.Lifetime))); try { byte[] buffer = package.ToArray(package.Count); int attempt = 0; int delay = PmpConstants.RetryDelay; using (var udpClient = new UdpClient()) { var cancellationTokenSource = new CancellationTokenSource(); while (attempt < PmpConstants.RetryAttempts) { await udpClient.SendAsync(buffer, buffer.Length, new IPEndPoint(LocalAddress, PmpConstants.ServerPort)); if (attempt == 0) { Task.Run(() => CreatePortMapListen(udpClient, mapping, cancellationTokenSource.Token)); } attempt++; delay *= 2; await Task.Delay(delay).ConfigureAwait(false); } cancellationTokenSource.Cancel(); } } catch (OperationCanceledException) { } catch (Exception e) { string type = create ? "create" : "delete"; string message = String.Format("Failed to {0} portmap (protocol={1}, private port={2}) {3}", type, mapping.Protocol, mapping.PrivatePort, e.Message); NatUtility.Log(message); var pmpException = e as MappingException; throw new MappingException(message, pmpException); } return(mapping); }
public static MessageBase Decode(UpnpNatDevice device, string message) { XmlNode node; XmlDocument doc = new XmlDocument(); doc.LoadXml(message); XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable); // Error messages should be found under this namespace nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0"); nsm.AddNamespace("responseNs", device.ServiceType); // Check to see if we have a fault code message. if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) { string errorCode = node["errorCode"] != null ? node["errorCode"].InnerText : ""; string errorDescription = node["errorDescription"] != null ? node["errorDescription"].InnerText : ""; return(new ErrorMessage(Convert.ToInt32(errorCode, CultureInfo.InvariantCulture), errorDescription)); } if ((doc.SelectSingleNode("//responseNs:AddPortMappingResponse", nsm)) != null) { return(new CreatePortMappingResponseMessage()); } if ((doc.SelectSingleNode("//responseNs:DeletePortMappingResponse", nsm)) != null) { return(new DeletePortMapResponseMessage()); } if ((node = doc.SelectSingleNode("//responseNs:GetExternalIPAddressResponse", nsm)) != null) { string newExternalIPAddress = node["NewExternalIPAddress"] != null ? node["NewExternalIPAddress"].InnerText : ""; return(new GetExternalIPAddressResponseMessage(newExternalIPAddress)); } if ((node = doc.SelectSingleNode("//responseNs:GetGenericPortMappingEntryResponse", nsm)) != null) { return(new GetGenericPortMappingEntryResponseMessage(node, true)); } if ((node = doc.SelectSingleNode("//responseNs:GetSpecificPortMappingEntryResponse", nsm)) != null) { return(new GetGenericPortMappingEntryResponseMessage(node, false)); } NatUtility.Log("Unknown message returned. Please send me back the following XML:"); NatUtility.Log(message); return(null); }
async Task <UpnpNatDevice> GetServicesList(IPAddress localAddress, Uri deviceServiceUri, CancellationToken token) { // Create a HTTPWebRequest to download the list of services the device offers var request = new GetServicesMessage(deviceServiceUri).Encode(out byte[] body); if (body.Length > 0) { NatUtility.Log("Error: Services Message contained a body"); } using (token.Register(() => request.Abort())) using (var response = (HttpWebResponse)await request.GetResponseAsync().ConfigureAwait(false)) return(await ServicesReceived(localAddress, deviceServiceUri, response).ConfigureAwait(false)); }
public virtual Task <UpnpNatDevice> Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) { // Convert it to a string for easy parsing string dataString = null; string urn; dataString = Encoding.UTF8.GetString(response); if (NatUtility.Verbose) { NatUtility.Log("UPnP Response: {0}", dataString); } /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. * Any other device type is no good to us for this purpose. See the IGP overview paper * page 5 for an overview of device types and their hierarchy. * http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which * version it is and apply the correct URN. */ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type * prefix. */ string log = "UPnP Response: Router advertised a '{0}' service"; StringComparison c = StringComparison.OrdinalIgnoreCase; if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) { urn = "urn:schemas-upnp-org:service:WANIPConnection:1"; NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1"); } else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) { urn = "urn:schemas-upnp-org:service:WANPPPConnection:1"; NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:"); } else { throw new NotSupportedException("Received non-supported device type"); } // We have an internet gateway device now var device = new UpnpNatDevice(localAddress, dataString, urn, Logger, HttpClient); return(Task.FromResult(device)); }
internal UpnpNatDevice(IPAddress localAddress, string deviceDetails, string serviceType, ILogger logger, IHttpClient httpClient) { _logger = logger; _httpClient = httpClient; this.LastSeen = DateTime.Now; this.localAddress = localAddress; // Split the string at the "location" section so i can extract the ipaddress and service description url string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.OrdinalIgnoreCase) + 9).Split('\r')[0]; this.serviceType = serviceType; // Make sure we have no excess whitespace locationDetails = locationDetails.Trim(); // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address // Are we going to get addresses with the "http://" attached? if (locationDetails.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) { NatUtility.Log("Found device at: {0}", locationDetails); // This bit strings out the "http://" from the string locationDetails = locationDetails.Substring(7); // We then split off the end of the string to get something like: 192.168.0.3:241 in our string string hostAddressAndPort = locationDetails.Remove(locationDetails.IndexOf('/')); // From this we parse out the IP address and Port if (hostAddressAndPort.IndexOf(':') > 0) { this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))), Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture)); } else { // there is no port specified, use default port (80) this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort), 80); } NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString()); // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip // and port information this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/')); } else { logger.Warn("Couldn't decode address: " + deviceDetails); } }
internal void GetServicesList(NatDeviceCallback callback) { // Save the callback so i can use it again later when i've finished parsing the services available this.callback = callback; // Create a HTTPWebRequest to download the list of services the device offers byte[] body; WebRequest request = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint).Encode(out body); if (body.Length > 0) { NatUtility.Log("Error: Services Message contained a body"); } request.BeginGetResponse(this.ServicesReceived, request); }
async Task <ResponseMessage> SendMessageAsync(RequestMessage message) { HttpWebRequest request = message.Encode(out byte [] body); // If this device has multiple active network devices, ensure the web request is sent from the network device which // received the response from the router. That way when we attempt to map a port, the IPAddress we are mapping to // is the same as the IPAddress which issues the WebRequest. Most uPnP implementations don't allow a device to // forward a port to a *different* IP address. request.ServicePoint.BindIPEndPointDelegate = delegate(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount) { NatUtility.Log($"The WebRequest being sent to {remoteEndPoint} has been bound to local IP address {LocalAddress}"); return(new IPEndPoint(LocalAddress, 0)); }; if (NatUtility.Logger != null) { NatUtility.Log($"uPnP Request: {Environment.NewLine}{Encoding.UTF8.GetString (body)}"); } if (body.Length > 0) { request.ContentLength = body.Length; using (var stream = await request.GetRequestStreamAsync().ConfigureAwait(false)) await stream.WriteAsync(body, 0, body.Length).ConfigureAwait(false); } try { using (var response = await request.GetResponseAsync().ConfigureAwait(false)) return(await DecodeMessageFromResponse(response.GetResponseStream(), (int)response.ContentLength)); } catch (WebException ex) { // Even if the request "failed" i want to continue on to read out the response from the router using (var response = ex.Response as HttpWebResponse) { if (response == null) { throw new MappingException("Unexpected error sending a message to the device", ex); } else { return(await DecodeMessageFromResponse(response.GetResponseStream(), (int)response.ContentLength)); } } } }
async Task <ResponseMessage> DecodeMessageFromResponse(Stream s, int length) { StringBuilder data = new StringBuilder(); int bytesRead; byte [] buffer = new byte [10240]; // Read out the content of the message, hopefully picking everything up in the case where we have no contentlength if (length != -1) { while (length > 0) { bytesRead = await s.ReadAsync(buffer, 0, Math.Min(buffer.Length, length)).ConfigureAwait(false); data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); length -= bytesRead; } } else { while ((bytesRead = await s.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0) { data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); } } // Once we have our content, we need to see what kind of message it is. If we received // an error message we will immediately throw a MappingException. var dataString = data.ToString(); if (NatUtility.Logger != null) { NatUtility.Log($"uPnP Response: {Environment.NewLine}{dataString}"); } return(ResponseMessage.Decode(this, dataString)); }
private void ServicesReceived(IAsyncResult result) { HttpWebResponse response = null; try { int abortCount = 0; int bytesRead = 0; byte[] buffer = new byte[10240]; StringBuilder servicesXml = new StringBuilder(); XmlDocument xmldoc = new XmlDocument(); HttpWebRequest request = result.AsyncState as HttpWebRequest; response = request.EndGetResponse(result) as HttpWebResponse; Stream s = response.GetResponseStream(); if (response.StatusCode != HttpStatusCode.OK) { NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode); return; // FIXME: This the best thing to do?? } while (true) { bytesRead = s.Read(buffer, 0, buffer.Length); servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); try { xmldoc.LoadXml(servicesXml.ToString()); response.Close(); break; } catch (XmlException) { // If we can't receive the entire XML within 500ms, then drop the connection // Unfortunately not all routers supply a valid ContentLength (mine doesn't) // so this hack is needed to keep testing our recieved data until it gets successfully // parsed by the xmldoc. Without this, the code will never pick up my router. if (abortCount++ > 50) { response.Close(); return; } NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint); System.Threading.Thread.Sleep(10); } } NatUtility.Log("{0}: Parsed services list", HostEndPoint); XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable); ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0"); XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns); foreach (XmlNode node in nodes) { //Go through each service there foreach (XmlNode service in node.ChildNodes) { //If the service is a WANIPConnection, then we have what we want string type = service["serviceType"].InnerText; NatUtility.Log("{0}: Found service: {1}", HostEndPoint, type); StringComparison c = StringComparison.OrdinalIgnoreCase; if (type.Equals(this.serviceType, c)) { this.controlUrl = service["controlURL"].InnerText; NatUtility.Log("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl); try { Uri u = new Uri(controlUrl); if (u.IsAbsoluteUri) { EndPoint old = hostEndPoint; this.hostEndPoint = new IPEndPoint(IPAddress.Parse(u.Host), u.Port); NatUtility.Log("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint); this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length); NatUtility.Log("{0}: New control url: {1}", HostEndPoint, controlUrl); } } catch { NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl); } NatUtility.Log("{0}: Handshake Complete", HostEndPoint); this.callback(this); return; } } } //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding //So we don't invoke the callback, so this device is never added to our lists } catch (WebException ex) { // Just drop the connection, FIXME: Should i retry? NatUtility.Log("{0}: Device denied the connection attempt: {1}", HostEndPoint, ex); } finally { if (response != null) { response.Close(); } } }
protected override async Task HandleMessageReceived(IPAddress localAddress, UdpReceiveResult result, CancellationToken token) { // Convert it to a string for easy parsing string dataString = null; var response = result.Buffer; // No matter what, this method should never throw an exception. If something goes wrong // we should still be in a position to handle the next reply correctly. try { dataString = Encoding.UTF8.GetString(response); NatUtility.Log("uPnP Search Response: {0}", dataString); /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. * Any other device type is no good to us for this purpose. See the IGP overview paper * page 5 for an overview of device types and their hierarchy. * http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which * version it is and apply the correct URN. */ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type * prefix. */ string log = "uPnP Search Response: Router advertised a '{0}' service"; StringComparison c = StringComparison.OrdinalIgnoreCase; if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) { NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1"); } else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) { NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:"); } else { return; } var location = dataString.Split(new [] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries) .Select(t => t.Trim()) .FirstOrDefault(t => t.StartsWith("LOCATION", StringComparison.OrdinalIgnoreCase)); if (location == null) { return; } var deviceLocation = location.Split(new [] { ':' }, 2).Skip(1).FirstOrDefault(); var deviceServiceUri = new Uri(deviceLocation); using (await Locker.DisposableWaitAsync(token)) { // If we send 3 requests at a time, ensure we only fetch the services list once // even if three responses are received if (LastFetched.TryGetValue(deviceServiceUri, out DateTime last)) { if ((DateTime.Now - last) < TimeSpan.FromSeconds(20)) { return; } } LastFetched [deviceServiceUri] = DateTime.Now; } // Once we've parsed the information we need, we tell the device to retrieve it's service list // Once we successfully receive the service list, the callback provided will be invoked. NatUtility.Log("Fetching service list: {0}", deviceServiceUri); var d = await GetServicesList(localAddress, deviceServiceUri, token).ConfigureAwait(false); if (d != null) { RaiseDeviceFound(d); } } catch (Exception ex) { Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: "); Trace.WriteLine("ErrorMessage:"); Trace.WriteLine(ex.Message); Trace.WriteLine("Data string:"); Trace.WriteLine(dataString); } }
private async void CreatePortMapListen(UdpClient udpClient, Mapping mapping, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { try { var result = await udpClient.ReceiveAsync().ConfigureAwait(false); var endPoint = result.RemoteEndPoint; byte[] data = data = result.Buffer; if (data.Length < 16) { continue; } if (data[0] != PmpConstants.Version) { continue; } var opCode = (byte)(data[1] & 127); var protocol = Protocol.Tcp; if (opCode == PmpConstants.OperationCodeUdp) { protocol = Protocol.Udp; } short resultCode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 2)); int epoch = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 4)); short privatePort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 8)); short publicPort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10)); var lifetime = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 12)); if (privatePort < 0 || publicPort < 0 || resultCode != PmpConstants.ResultCodeSuccess) { var errors = new[] { "Success", "Unsupported Version", "Not Authorized/Refused (e.g. box supports mapping, but user has turned feature off)" , "Network Failure (e.g. NAT box itself has not obtained a DHCP lease)", "Out of resources (NAT box cannot create any more mappings at this time)", "Unsupported opcode" }; var errorMsg = errors[resultCode]; NatUtility.Log("Error in CreatePortMapListen: " + errorMsg); return; } if (lifetime == 0) { return; //mapping was deleted } //mapping was created //TODO: verify that the private port+protocol are a match mapping.PublicPort = publicPort; mapping.Protocol = protocol; mapping.Expiration = DateTime.Now.AddSeconds(lifetime); return; } catch (Exception ex) { NatUtility.Logger.ErrorException("Error in CreatePortMapListen", ex); return; } } }
async Task <UpnpNatDevice> ServicesReceived(IPAddress localAddress, Uri deviceServiceUri, HttpWebResponse response) { int abortCount = 0; byte [] buffer = new byte [10240]; StringBuilder servicesXml = new StringBuilder(); XmlDocument xmldoc = new XmlDocument(); Stream s = response.GetResponseStream(); if (response.StatusCode != HttpStatusCode.OK) { NatUtility.Log("{0}: Couldn't get services list: {1}", response.ResponseUri, response.StatusCode); return(null); // FIXME: This the best thing to do?? } while (true) { var bytesRead = await s.ReadAsync(buffer, 0, buffer.Length); servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); try { xmldoc.LoadXml(servicesXml.ToString()); break; } catch (XmlException) { // If we can't receive the entire XML within 5 seconds, then drop the connection // Unfortunately not all routers supply a valid ContentLength (mine doesn't) // so this hack is needed to keep testing our recieved data until it gets successfully // parsed by the xmldoc. Without this, the code will never pick up my router. if (abortCount++ > 5000) { return(null); } NatUtility.Log("{0}: Couldn't parse services list", response.ResponseUri); await Task.Delay(10); } } NatUtility.Log("{0}: Parsed services list", response.ResponseUri); XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable); ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0"); XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns); foreach (XmlNode node in nodes) { //Go through each service there foreach (XmlNode service in node.ChildNodes) { //If the service is a WANIPConnection, then we have what we want string serviceType = service ["serviceType"].InnerText; NatUtility.Log("{0}: Found service: {1}", response.ResponseUri, serviceType); StringComparison c = StringComparison.OrdinalIgnoreCase; // TODO: Add support for version 2 of UPnP. if (serviceType.Equals("urn:schemas-upnp-org:service:WANPPPConnection:1", c) || serviceType.Equals("urn:schemas-upnp-org:service:WANIPConnection:1", c)) { var controlUrl = new Uri(service ["controlURL"].InnerText, UriKind.RelativeOrAbsolute); IPEndPoint deviceEndpoint = new IPEndPoint(IPAddress.Parse(response.ResponseUri.Host), response.ResponseUri.Port); NatUtility.Log("{0}: Found upnp service at: {1}", response.ResponseUri, controlUrl.OriginalString); try { if (controlUrl.IsAbsoluteUri) { deviceEndpoint = new IPEndPoint(IPAddress.Parse(controlUrl.Host), controlUrl.Port); NatUtility.Log("{0}: New control url: {1}", deviceEndpoint, controlUrl); } else { controlUrl = new Uri(deviceServiceUri, controlUrl.OriginalString); } } catch { controlUrl = new Uri(deviceServiceUri, controlUrl.OriginalString); NatUtility.Log("{0}: Assuming control Uri is relative: {1}", deviceEndpoint, controlUrl); } NatUtility.Log("{0}: Handshake Complete", deviceEndpoint); return(new UpnpNatDevice(localAddress, deviceEndpoint, controlUrl, serviceType)); } } } //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding //So we don't invoke the callback, so this device is never added to our lists return(null); }