SsdpHandler.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.Events;
  3. using MediaBrowser.Controller.Configuration;
  4. using MediaBrowser.Dlna.Server;
  5. using MediaBrowser.Model.Logging;
  6. using System;
  7. using System.Collections.Concurrent;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Net;
  11. using System.Net.Sockets;
  12. using System.Text;
  13. using System.Threading;
  14. namespace MediaBrowser.Dlna.Ssdp
  15. {
  16. public class SsdpHandler : IDisposable
  17. {
  18. private Socket _socket;
  19. private readonly ILogger _logger;
  20. private readonly IServerConfigurationManager _config;
  21. const string SSDPAddr = "239.255.255.250";
  22. const int SSDPPort = 1900;
  23. private readonly string _serverSignature;
  24. private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr);
  25. private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
  26. private Timer _queueTimer;
  27. private Timer _notificationTimer;
  28. private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
  29. private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
  30. private bool _isDisposed;
  31. private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>();
  32. public SsdpHandler(ILogger logger, IServerConfigurationManager config, string serverSignature)
  33. {
  34. _logger = logger;
  35. _config = config;
  36. _serverSignature = serverSignature;
  37. _config.NamedConfigurationUpdated += _config_ConfigurationUpdated;
  38. }
  39. void _config_ConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
  40. {
  41. if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
  42. {
  43. ReloadAliveNotifier();
  44. }
  45. }
  46. public event EventHandler<SsdpMessageEventArgs> MessageReceived;
  47. private void OnMessageReceived(SsdpMessageEventArgs args)
  48. {
  49. if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase))
  50. {
  51. RespondToSearch(args.EndPoint, args.Headers["st"]);
  52. }
  53. EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
  54. }
  55. public IEnumerable<UpnpDevice> RegisteredDevices
  56. {
  57. get
  58. {
  59. return _devices.Values.SelectMany(i => i).ToList();
  60. }
  61. }
  62. public void Start()
  63. {
  64. _socket = CreateMulticastSocket();
  65. _logger.Info("SSDP service started");
  66. Receive();
  67. ReloadAliveNotifier();
  68. }
  69. public void SendSearchMessage(IPEndPoint localIp)
  70. {
  71. var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  72. values["HOST"] = "239.255.255.250:1900";
  73. values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2";
  74. values["MAN"] = "\"ssdp:discover\"";
  75. // Search target
  76. values["ST"] = "ssdp:all";
  77. // Seconds to delay response
  78. values["MX"] = "3";
  79. SendDatagram("M-SEARCH * HTTP/1.1", values, localIp);
  80. }
  81. public void SendDatagram(string header,
  82. Dictionary<string, string> values,
  83. IPEndPoint localAddress,
  84. int sendCount = 1)
  85. {
  86. SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
  87. }
  88. public void SendDatagram(string header,
  89. Dictionary<string, string> values,
  90. IPEndPoint endpoint,
  91. IPEndPoint localAddress,
  92. int sendCount = 1)
  93. {
  94. var msg = new SsdpMessageBuilder().BuildMessage(header, values);
  95. var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount);
  96. if (_messageQueue.Count == 0)
  97. {
  98. dgram.Send();
  99. return;
  100. }
  101. _messageQueue.Enqueue(dgram);
  102. StartQueueTimer();
  103. }
  104. public void SendDatagramFromDevices(string header,
  105. Dictionary<string, string> values,
  106. IPEndPoint endpoint,
  107. string deviceType)
  108. {
  109. foreach (var d in RegisteredDevices)
  110. {
  111. if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
  112. string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
  113. {
  114. SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0));
  115. }
  116. }
  117. }
  118. private void RespondToSearch(IPEndPoint endpoint, string deviceType)
  119. {
  120. if (_config.GetDlnaConfiguration().EnableDebugLogging)
  121. {
  122. _logger.Debug("RespondToSearch");
  123. }
  124. const string header = "HTTP/1.1 200 OK";
  125. foreach (var d in RegisteredDevices)
  126. {
  127. if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
  128. string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
  129. {
  130. var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  131. values["CACHE-CONTROL"] = "max-age = 600";
  132. values["DATE"] = DateTime.Now.ToString("R");
  133. values["EXT"] = "";
  134. values["LOCATION"] = d.Descriptor.ToString();
  135. values["SERVER"] = _serverSignature;
  136. values["ST"] = d.Type;
  137. values["USN"] = d.USN;
  138. SendDatagram(header, values, endpoint, null);
  139. if (_config.GetDlnaConfiguration().EnableDebugLogging)
  140. {
  141. _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
  142. }
  143. }
  144. }
  145. }
  146. private readonly object _queueTimerSyncLock = new object();
  147. private void StartQueueTimer()
  148. {
  149. lock (_queueTimerSyncLock)
  150. {
  151. if (_queueTimer == null)
  152. {
  153. _queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
  154. }
  155. else
  156. {
  157. _queueTimer.Change(1000, Timeout.Infinite);
  158. }
  159. }
  160. }
  161. private void QueueTimerCallback(object state)
  162. {
  163. while (_messageQueue.Count != 0)
  164. {
  165. Datagram msg;
  166. if (!_messageQueue.TryPeek(out msg))
  167. {
  168. continue;
  169. }
  170. if (msg != null && (!_isDisposed || msg.TotalSendCount > 1))
  171. {
  172. msg.Send();
  173. if (msg.SendCount > msg.TotalSendCount)
  174. {
  175. _messageQueue.TryDequeue(out msg);
  176. }
  177. break;
  178. }
  179. _messageQueue.TryDequeue(out msg);
  180. }
  181. _datagramPosted.Set();
  182. if (_messageQueue.Count > 0)
  183. {
  184. StartQueueTimer();
  185. }
  186. else
  187. {
  188. DisposeQueueTimer();
  189. }
  190. }
  191. private void Receive()
  192. {
  193. try
  194. {
  195. var buffer = new byte[1024];
  196. EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
  197. _socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer);
  198. }
  199. catch (ObjectDisposedException)
  200. {
  201. }
  202. }
  203. private void ReceiveCallback(IAsyncResult result)
  204. {
  205. if (_isDisposed)
  206. {
  207. return;
  208. }
  209. try
  210. {
  211. EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
  212. var length = _socket.EndReceiveFrom(result, ref endpoint);
  213. var received = (byte[])result.AsyncState;
  214. if (_config.GetDlnaConfiguration().EnableDebugLogging)
  215. {
  216. _logger.Debug(Encoding.ASCII.GetString(received));
  217. }
  218. var args = SsdpHelper.ParseSsdpResponse(received, (IPEndPoint)endpoint);
  219. if (_config.GetDlnaConfiguration().EnableDebugLogging)
  220. {
  221. var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
  222. var headerText = string.Join(",", headerTexts.ToArray());
  223. _logger.Debug("{0} message received from {1} on {3}. Headers: {2}", args.Method, args.EndPoint, headerText, _socket.LocalEndPoint);
  224. }
  225. OnMessageReceived(args);
  226. }
  227. catch (Exception ex)
  228. {
  229. _logger.ErrorException("Failed to read SSDP message", ex);
  230. }
  231. if (_socket != null)
  232. {
  233. Receive();
  234. }
  235. }
  236. public void Dispose()
  237. {
  238. _config.NamedConfigurationUpdated -= _config_ConfigurationUpdated;
  239. _isDisposed = true;
  240. while (_messageQueue.Count != 0)
  241. {
  242. _datagramPosted.WaitOne();
  243. }
  244. DisposeSocket();
  245. DisposeQueueTimer();
  246. DisposeNotificationTimer();
  247. _datagramPosted.Dispose();
  248. }
  249. private void DisposeSocket()
  250. {
  251. if (_socket != null)
  252. {
  253. _socket.Close();
  254. _socket.Dispose();
  255. _socket = null;
  256. }
  257. }
  258. private void DisposeQueueTimer()
  259. {
  260. lock (_queueTimerSyncLock)
  261. {
  262. if (_queueTimer != null)
  263. {
  264. _queueTimer.Dispose();
  265. _queueTimer = null;
  266. }
  267. }
  268. }
  269. private Socket CreateMulticastSocket()
  270. {
  271. var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  272. socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
  273. socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
  274. socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
  275. socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(_ssdpIp, 0));
  276. socket.Bind(new IPEndPoint(IPAddress.Any, SSDPPort));
  277. return socket;
  278. }
  279. private void NotifyAll()
  280. {
  281. if (_config.GetDlnaConfiguration().EnableDebugLogging)
  282. {
  283. _logger.Debug("Sending alive notifications");
  284. }
  285. foreach (var d in RegisteredDevices)
  286. {
  287. NotifyDevice(d, "alive");
  288. }
  289. }
  290. private void NotifyDevice(UpnpDevice dev, string type, int sendCount = 1)
  291. {
  292. const string header = "NOTIFY * HTTP/1.1";
  293. var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  294. // If needed later for non-server devices, these headers will need to be dynamic
  295. values["HOST"] = "239.255.255.250:1900";
  296. values["CACHE-CONTROL"] = "max-age = 600";
  297. values["LOCATION"] = dev.Descriptor.ToString();
  298. values["SERVER"] = _serverSignature;
  299. values["NTS"] = "ssdp:" + type;
  300. values["NT"] = dev.Type;
  301. values["USN"] = dev.USN;
  302. if (_config.GetDlnaConfiguration().EnableDebugLogging)
  303. {
  304. _logger.Debug("{0} said {1}", dev.USN, type);
  305. }
  306. SendDatagram(header, values, new IPEndPoint(dev.Address, 0), sendCount);
  307. }
  308. public void RegisterNotification(Guid uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
  309. {
  310. List<UpnpDevice> list;
  311. lock (_devices)
  312. {
  313. if (!_devices.TryGetValue(uuid, out list))
  314. {
  315. _devices.TryAdd(uuid, list = new List<UpnpDevice>());
  316. }
  317. }
  318. list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
  319. NotifyAll();
  320. _logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri);
  321. }
  322. public void UnregisterNotification(Guid uuid)
  323. {
  324. List<UpnpDevice> dl;
  325. if (_devices.TryRemove(uuid, out dl))
  326. {
  327. foreach (var d in dl.ToList())
  328. {
  329. NotifyDevice(d, "byebye", 2);
  330. }
  331. _logger.Debug("Unregistered mount {0}", uuid);
  332. }
  333. }
  334. private readonly object _notificationTimerSyncLock = new object();
  335. private int _aliveNotifierIntervalMs;
  336. private void ReloadAliveNotifier()
  337. {
  338. if (!_config.GetDlnaConfiguration().BlastAliveMessages)
  339. {
  340. DisposeNotificationTimer();
  341. return;
  342. }
  343. var intervalMs = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds * 1000;
  344. if (_notificationTimer == null || _aliveNotifierIntervalMs != intervalMs)
  345. {
  346. lock (_notificationTimerSyncLock)
  347. {
  348. if (_notificationTimer == null)
  349. {
  350. _logger.Debug("Starting alive notifier");
  351. const int initialDelayMs = 3000;
  352. _notificationTimer = new Timer(state => NotifyAll(), null, initialDelayMs, intervalMs);
  353. }
  354. else
  355. {
  356. _logger.Debug("Updating alive notifier");
  357. _notificationTimer.Change(intervalMs, intervalMs);
  358. }
  359. _aliveNotifierIntervalMs = intervalMs;
  360. }
  361. }
  362. }
  363. private void DisposeNotificationTimer()
  364. {
  365. lock (_notificationTimerSyncLock)
  366. {
  367. if (_notificationTimer != null)
  368. {
  369. _logger.Debug("Stopping alive notifier");
  370. _notificationTimer.Dispose();
  371. _notificationTimer = null;
  372. }
  373. }
  374. }
  375. }
  376. }