UpnpNatDevice.cs 23 KB

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