UpnpNatDevice.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. //
  2. // Authors:
  3. // Alan McGovern alan.mcgovern@gmail.com
  4. // Ben Motmans <ben.motmans@gmail.com>
  5. //
  6. // Copyright (C) 2006 Alan McGovern
  7. // Copyright (C) 2007 Ben Motmans
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining
  10. // a copy of this software and associated documentation files (the
  11. // "Software"), to deal in the Software without restriction, including
  12. // without limitation the rights to use, copy, modify, merge, publish,
  13. // distribute, sublicense, and/or sell copies of the Software, and to
  14. // permit persons to whom the Software is furnished to do so, subject to
  15. // the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be
  18. // included in all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  22. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  24. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  25. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27. //
  28. using System;
  29. using System.IO;
  30. using System.Net;
  31. using System.Xml;
  32. using System.Text;
  33. using System.Diagnostics;
  34. using System.Threading.Tasks;
  35. using MediaBrowser.Common.Net;
  36. using MediaBrowser.Controller.Dlna;
  37. using MediaBrowser.Model.Logging;
  38. namespace Mono.Nat.Upnp
  39. {
  40. public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable<UpnpNatDevice>
  41. {
  42. private EndPoint hostEndPoint;
  43. private IPAddress localAddress;
  44. private string serviceDescriptionUrl;
  45. private string controlUrl;
  46. private string serviceType;
  47. private readonly ILogger _logger;
  48. private readonly IHttpClient _httpClient;
  49. public override IPAddress LocalAddress
  50. {
  51. get { return localAddress; }
  52. }
  53. internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType, ILogger logger, IHttpClient httpClient)
  54. {
  55. this.LastSeen = DateTime.Now;
  56. this.localAddress = localAddress;
  57. // Split the string at the "location" section so i can extract the ipaddress and service description url
  58. string locationDetails = deviceInfo.Location.ToString();
  59. this.serviceType = serviceType;
  60. _logger = logger;
  61. _httpClient = httpClient;
  62. // Make sure we have no excess whitespace
  63. locationDetails = locationDetails.Trim();
  64. // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
  65. // Are we going to get addresses with the "http://" attached?
  66. if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
  67. {
  68. NatUtility.Log("Found device at: {0}", locationDetails);
  69. // This bit strings out the "http://" from the string
  70. locationDetails = locationDetails.Substring(7);
  71. this.hostEndPoint = hostEndPoint;
  72. NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
  73. // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
  74. // and port information
  75. this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
  76. }
  77. else
  78. {
  79. NatUtility.Log("Couldn't decode address. Please send following string to the developer: ");
  80. }
  81. }
  82. internal UpnpNatDevice(IPAddress localAddress, string deviceDetails, string serviceType, ILogger logger, IHttpClient httpClient)
  83. {
  84. _logger = logger;
  85. _httpClient = httpClient;
  86. this.LastSeen = DateTime.Now;
  87. this.localAddress = localAddress;
  88. // Split the string at the "location" section so i can extract the ipaddress and service description url
  89. string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.InvariantCultureIgnoreCase) + 9).Split('\r')[0];
  90. this.serviceType = serviceType;
  91. // Make sure we have no excess whitespace
  92. locationDetails = locationDetails.Trim();
  93. // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
  94. // Are we going to get addresses with the "http://" attached?
  95. if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
  96. {
  97. NatUtility.Log("Found device at: {0}", locationDetails);
  98. // This bit strings out the "http://" from the string
  99. locationDetails = locationDetails.Substring(7);
  100. // We then split off the end of the string to get something like: 192.168.0.3:241 in our string
  101. string hostAddressAndPort = locationDetails.Remove(locationDetails.IndexOf('/'));
  102. // From this we parse out the IP address and Port
  103. if (hostAddressAndPort.IndexOf(':') > 0)
  104. {
  105. this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))),
  106. Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture));
  107. }
  108. else
  109. {
  110. // there is no port specified, use default port (80)
  111. this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort), 80);
  112. }
  113. NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
  114. // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
  115. // and port information
  116. this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
  117. }
  118. else
  119. {
  120. logger.Warn("Couldn't decode address: " + deviceDetails);
  121. }
  122. }
  123. /// <summary>
  124. /// The EndPoint that the device is at
  125. /// </summary>
  126. internal EndPoint HostEndPoint
  127. {
  128. get { return this.hostEndPoint; }
  129. }
  130. /// <summary>
  131. /// The relative url of the xml file that describes the list of services is at
  132. /// </summary>
  133. internal string ServiceDescriptionUrl
  134. {
  135. get { return this.serviceDescriptionUrl; }
  136. }
  137. /// <summary>
  138. /// The relative url that we can use to control the port forwarding
  139. /// </summary>
  140. internal string ControlUrl
  141. {
  142. get { return this.controlUrl; }
  143. }
  144. /// <summary>
  145. /// The service type we're using on the device
  146. /// </summary>
  147. public string ServiceType
  148. {
  149. get { return serviceType; }
  150. }
  151. /// <summary>
  152. /// Begins an async call to get the external ip address of the router
  153. /// </summary>
  154. public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState)
  155. {
  156. // Create the port map message
  157. GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this);
  158. return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal);
  159. }
  160. /// <summary>
  161. /// Maps the specified port to this computer
  162. /// </summary>
  163. public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
  164. {
  165. CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
  166. return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal);
  167. }
  168. public override Task CreatePortMap(Mapping mapping)
  169. {
  170. CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
  171. return _httpClient.SendAsync(message.Encode(), message.Method);
  172. }
  173. /// <summary>
  174. /// Removes a port mapping from this computer
  175. /// </summary>
  176. public override IAsyncResult BeginDeletePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
  177. {
  178. DeletePortMappingMessage message = new DeletePortMappingMessage(mapping, this);
  179. return BeginMessageInternal(message, callback, asyncState, EndDeletePortMapInternal);
  180. }
  181. public override IAsyncResult BeginGetAllMappings(AsyncCallback callback, object asyncState)
  182. {
  183. GetGenericPortMappingEntry message = new GetGenericPortMappingEntry(0, this);
  184. return BeginMessageInternal(message, callback, asyncState, EndGetAllMappingsInternal);
  185. }
  186. public override IAsyncResult BeginGetSpecificMapping(Protocol protocol, int port, AsyncCallback callback, object asyncState)
  187. {
  188. GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this);
  189. return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal));
  190. }
  191. /// <summary>
  192. ///
  193. /// </summary>
  194. /// <param name="result"></param>
  195. public override void EndCreatePortMap(IAsyncResult result)
  196. {
  197. if (result == null) throw new ArgumentNullException("result");
  198. PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
  199. if (mappingResult == null)
  200. throw new ArgumentException("Invalid AsyncResult", "result");
  201. // Check if we need to wait for the operation to finish
  202. if (!result.IsCompleted)
  203. result.AsyncWaitHandle.WaitOne();
  204. // If we have a saved exception, it means something went wrong during the mapping
  205. // so we just rethrow the exception and let the user figure out what they should do.
  206. if (mappingResult.SavedMessage is ErrorMessage)
  207. {
  208. ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
  209. throw new MappingException(msg.ErrorCode, msg.Description);
  210. }
  211. //return result.AsyncState as Mapping;
  212. }
  213. /// <summary>
  214. ///
  215. /// </summary>
  216. /// <param name="result"></param>
  217. public override void EndDeletePortMap(IAsyncResult result)
  218. {
  219. if (result == null)
  220. throw new ArgumentNullException("result");
  221. PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
  222. if (mappingResult == null)
  223. throw new ArgumentException("Invalid AsyncResult", "result");
  224. // Check if we need to wait for the operation to finish
  225. if (!mappingResult.IsCompleted)
  226. mappingResult.AsyncWaitHandle.WaitOne();
  227. // If we have a saved exception, it means something went wrong during the mapping
  228. // so we just rethrow the exception and let the user figure out what they should do.
  229. if (mappingResult.SavedMessage is ErrorMessage)
  230. {
  231. ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
  232. throw new MappingException(msg.ErrorCode, msg.Description);
  233. }
  234. // If all goes well, we just return
  235. //return true;
  236. }
  237. public override Mapping[] EndGetAllMappings(IAsyncResult result)
  238. {
  239. if (result == null)
  240. throw new ArgumentNullException("result");
  241. GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult;
  242. if (mappingResult == null)
  243. throw new ArgumentException("Invalid AsyncResult", "result");
  244. if (!mappingResult.IsCompleted)
  245. mappingResult.AsyncWaitHandle.WaitOne();
  246. if (mappingResult.SavedMessage is ErrorMessage)
  247. {
  248. ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
  249. if (msg.ErrorCode != 713)
  250. throw new MappingException(msg.ErrorCode, msg.Description);
  251. }
  252. return mappingResult.Mappings.ToArray();
  253. }
  254. /// <summary>
  255. /// Ends an async request to get the external ip address of the router
  256. /// </summary>
  257. public override IPAddress EndGetExternalIP(IAsyncResult result)
  258. {
  259. if (result == null) throw new ArgumentNullException("result");
  260. PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
  261. if (mappingResult == null)
  262. throw new ArgumentException("Invalid AsyncResult", "result");
  263. if (!result.IsCompleted)
  264. result.AsyncWaitHandle.WaitOne();
  265. if (mappingResult.SavedMessage is ErrorMessage)
  266. {
  267. ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
  268. throw new MappingException(msg.ErrorCode, msg.Description);
  269. }
  270. if (mappingResult.SavedMessage == null)
  271. return null;
  272. else
  273. return ((GetExternalIPAddressResponseMessage)mappingResult.SavedMessage).ExternalIPAddress;
  274. }
  275. public override Mapping EndGetSpecificMapping(IAsyncResult result)
  276. {
  277. if (result == null)
  278. throw new ArgumentNullException("result");
  279. GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult;
  280. if (mappingResult == null)
  281. throw new ArgumentException("Invalid AsyncResult", "result");
  282. if (!mappingResult.IsCompleted)
  283. mappingResult.AsyncWaitHandle.WaitOne();
  284. if (mappingResult.SavedMessage is ErrorMessage)
  285. {
  286. ErrorMessage message = mappingResult.SavedMessage as ErrorMessage;
  287. if (message.ErrorCode != 0x2ca)
  288. {
  289. throw new MappingException(message.ErrorCode, message.Description);
  290. }
  291. }
  292. if (mappingResult.Mappings.Count == 0)
  293. return new Mapping(Protocol.Tcp, -1, -1);
  294. return mappingResult.Mappings[0];
  295. }
  296. public override bool Equals(object obj)
  297. {
  298. UpnpNatDevice device = obj as UpnpNatDevice;
  299. return (device == null) ? false : this.Equals((device));
  300. }
  301. public bool Equals(UpnpNatDevice other)
  302. {
  303. return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint)
  304. //&& this.controlUrl == other.controlUrl
  305. && this.serviceDescriptionUrl == other.serviceDescriptionUrl);
  306. }
  307. public override int GetHashCode()
  308. {
  309. return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode());
  310. }
  311. private IAsyncResult BeginMessageInternal(MessageBase message, AsyncCallback storedCallback, object asyncState, AsyncCallback callback)
  312. {
  313. byte[] body;
  314. WebRequest request = message.Encode(out body);
  315. PortMapAsyncResult mappingResult = PortMapAsyncResult.Create(message, request, storedCallback, asyncState);
  316. if (body.Length > 0)
  317. {
  318. request.ContentLength = body.Length;
  319. request.BeginGetRequestStream(delegate (IAsyncResult result)
  320. {
  321. try
  322. {
  323. Stream s = request.EndGetRequestStream(result);
  324. s.Write(body, 0, body.Length);
  325. request.BeginGetResponse(callback, mappingResult);
  326. }
  327. catch (Exception ex)
  328. {
  329. mappingResult.Complete(ex);
  330. }
  331. }, null);
  332. }
  333. else
  334. {
  335. request.BeginGetResponse(callback, mappingResult);
  336. }
  337. return mappingResult;
  338. }
  339. private void CompleteMessage(IAsyncResult result)
  340. {
  341. PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult;
  342. mappingResult.CompletedSynchronously = result.CompletedSynchronously;
  343. mappingResult.Complete();
  344. }
  345. private MessageBase DecodeMessageFromResponse(Stream s, long length)
  346. {
  347. StringBuilder data = new StringBuilder();
  348. int bytesRead = 0;
  349. int totalBytesRead = 0;
  350. byte[] buffer = new byte[10240];
  351. // Read out the content of the message, hopefully picking everything up in the case where we have no contentlength
  352. if (length != -1)
  353. {
  354. while (totalBytesRead < length)
  355. {
  356. bytesRead = s.Read(buffer, 0, buffer.Length);
  357. data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
  358. totalBytesRead += bytesRead;
  359. }
  360. }
  361. else
  362. {
  363. while ((bytesRead = s.Read(buffer, 0, buffer.Length)) != 0)
  364. data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
  365. }
  366. // Once we have our content, we need to see what kind of message it is. It'll either a an error
  367. // or a response based on the action we performed.
  368. return MessageBase.Decode(this, data.ToString());
  369. }
  370. private void EndCreatePortMapInternal(IAsyncResult result)
  371. {
  372. EndMessageInternal(result);
  373. CompleteMessage(result);
  374. }
  375. private void EndMessageInternal(IAsyncResult result)
  376. {
  377. HttpWebResponse response = null;
  378. PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult;
  379. try
  380. {
  381. try
  382. {
  383. response = (HttpWebResponse)mappingResult.Request.EndGetResponse(result);
  384. }
  385. catch (WebException ex)
  386. {
  387. // Even if the request "failed" i want to continue on to read out the response from the router
  388. response = ex.Response as HttpWebResponse;
  389. if (response == null)
  390. mappingResult.SavedMessage = new ErrorMessage((int)ex.Status, ex.Message);
  391. }
  392. if (response != null)
  393. mappingResult.SavedMessage = DecodeMessageFromResponse(response.GetResponseStream(), response.ContentLength);
  394. }
  395. finally
  396. {
  397. if (response != null)
  398. response.Close();
  399. }
  400. }
  401. private void EndDeletePortMapInternal(IAsyncResult result)
  402. {
  403. EndMessageInternal(result);
  404. CompleteMessage(result);
  405. }
  406. private void EndGetAllMappingsInternal(IAsyncResult result)
  407. {
  408. EndMessageInternal(result);
  409. GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
  410. GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
  411. if (message != null)
  412. {
  413. Mapping mapping = new Mapping(message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration);
  414. mapping.Description = message.PortMappingDescription;
  415. mappingResult.Mappings.Add(mapping);
  416. GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this);
  417. // It's ok to do this synchronously because we should already be on anther thread
  418. // and this won't block the user.
  419. byte[] body;
  420. WebRequest request = next.Encode(out body);
  421. if (body.Length > 0)
  422. {
  423. request.ContentLength = body.Length;
  424. request.GetRequestStream().Write(body, 0, body.Length);
  425. }
  426. mappingResult.Request = request;
  427. request.BeginGetResponse(EndGetAllMappingsInternal, mappingResult);
  428. return;
  429. }
  430. CompleteMessage(result);
  431. }
  432. private void EndGetExternalIPInternal(IAsyncResult result)
  433. {
  434. EndMessageInternal(result);
  435. CompleteMessage(result);
  436. }
  437. private void EndGetSpecificMappingInternal(IAsyncResult result)
  438. {
  439. EndMessageInternal(result);
  440. GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
  441. GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
  442. if (message != null)
  443. {
  444. Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration);
  445. mapping.Description = mappingResult.SpecificMapping.Description;
  446. mappingResult.Mappings.Add(mapping);
  447. }
  448. CompleteMessage(result);
  449. }
  450. internal async Task<bool> GetServicesList()
  451. {
  452. // Create a HTTPWebRequest to download the list of services the device offers
  453. var requestOptions = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint, _logger).Encode();
  454. requestOptions.BufferContent = false;
  455. using (var response = await _httpClient.Get(requestOptions).ConfigureAwait(false))
  456. {
  457. return ServicesReceived(response);
  458. }
  459. }
  460. private bool ServicesReceived(Stream s)
  461. {
  462. int abortCount = 0;
  463. int bytesRead = 0;
  464. byte[] buffer = new byte[10240];
  465. StringBuilder servicesXml = new StringBuilder();
  466. XmlDocument xmldoc = new XmlDocument();
  467. while (true)
  468. {
  469. bytesRead = s.Read(buffer, 0, buffer.Length);
  470. servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
  471. try
  472. {
  473. xmldoc.LoadXml(servicesXml.ToString());
  474. break;
  475. }
  476. catch (XmlException)
  477. {
  478. // If we can't receive the entire XML within 500ms, then drop the connection
  479. // Unfortunately not all routers supply a valid ContentLength (mine doesn't)
  480. // so this hack is needed to keep testing our recieved data until it gets successfully
  481. // parsed by the xmldoc. Without this, the code will never pick up my router.
  482. if (abortCount++ > 50)
  483. {
  484. return false;
  485. }
  486. NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint);
  487. System.Threading.Thread.Sleep(10);
  488. }
  489. }
  490. NatUtility.Log("{0}: Parsed services list", HostEndPoint);
  491. XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable);
  492. ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0");
  493. XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns);
  494. foreach (XmlNode node in nodes)
  495. {
  496. //Go through each service there
  497. foreach (XmlNode service in node.ChildNodes)
  498. {
  499. //If the service is a WANIPConnection, then we have what we want
  500. string type = service["serviceType"].InnerText;
  501. NatUtility.Log("{0}: Found service: {1}", HostEndPoint, type);
  502. StringComparison c = StringComparison.OrdinalIgnoreCase;
  503. // TODO: Add support for version 2 of UPnP.
  504. if (type.Equals("urn:schemas-upnp-org:service:WANPPPConnection:1", c) ||
  505. type.Equals("urn:schemas-upnp-org:service:WANIPConnection:1", c))
  506. {
  507. this.controlUrl = service["controlURL"].InnerText;
  508. NatUtility.Log("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl);
  509. try
  510. {
  511. Uri u = new Uri(controlUrl);
  512. if (u.IsAbsoluteUri)
  513. {
  514. EndPoint old = hostEndPoint;
  515. this.hostEndPoint = new IPEndPoint(IPAddress.Parse(u.Host), u.Port);
  516. NatUtility.Log("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint);
  517. this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length);
  518. NatUtility.Log("{0}: New control url: {1}", HostEndPoint, controlUrl);
  519. }
  520. }
  521. catch
  522. {
  523. NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl);
  524. }
  525. NatUtility.Log("{0}: Handshake Complete", HostEndPoint);
  526. return true;
  527. }
  528. }
  529. }
  530. //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding
  531. //So we don't invoke the callback, so this device is never added to our lists
  532. return false;
  533. }
  534. /// <summary>
  535. /// Overridden.
  536. /// </summary>
  537. /// <returns></returns>
  538. public override string ToString()
  539. {
  540. //GetExternalIP is blocking and can throw exceptions, can't use it here.
  541. return String.Format(
  542. "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}",
  543. this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen);
  544. }
  545. }
  546. }