/CodingSmackdown.Services/NameService.cs

http://diybrewerytemplogger.codeplex.com · C# · 1131 lines · 706 code · 155 blank · 270 comment · 108 complexity · f65286dd673cc1e7c725c0cd72be2c7f MD5 · raw file

  1. //
  2. // Copyright 2011 Fastload-Media.be
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. using System;
  17. using Microsoft.SPOT;
  18. using System.Net.Sockets;
  19. using System.Net;
  20. using System.Threading;
  21. using System.Collections;
  22. using System.Text;
  23. using Microsoft.SPOT.Net.NetworkInformation;
  24. namespace CodingSmackdown.Services
  25. {
  26. /// <summary>
  27. /// NameService class for .NET MicroFramework.
  28. /// Programmed by Huysentruit Wouter. Copyright by Fastload-Media.
  29. /// </summary>
  30. public class NameService : IDisposable
  31. {
  32. #region Consts
  33. private const ushort NAME_TRN_ID = 0x6703; // unique transaction id
  34. private const int BCAST_REQ_RETRY_TIMEOUT = 250;
  35. private const int BCAST_REQ_RETRY_COUNT = 3;
  36. private const int BCAST_NS_PORT = 137;
  37. private const int NAME_UPDATE_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes, represented as ms
  38. #endregion
  39. #region Definitions
  40. /// <summary>
  41. /// Available name types.
  42. /// </summary>
  43. public enum NameType
  44. {
  45. /// <summary>
  46. /// Unique name. (F.e. workstation)
  47. /// </summary>
  48. Unique,
  49. /// <summary>
  50. /// Group name. (F.e. domain)
  51. /// </summary>
  52. Group
  53. }
  54. /// <summary>
  55. /// Represents a single name as added to the namelist.
  56. /// </summary>
  57. private struct Name
  58. {
  59. public string UncompressedName;
  60. public NameType Type;
  61. }
  62. /// <summary>
  63. /// Most used Microsoft suffixes for NetBIOS names.
  64. /// </summary>
  65. public enum MsSuffix : byte
  66. {
  67. /// <summary>
  68. /// Unique workstation computername or default group name.
  69. /// </summary>
  70. Default = 0x00,
  71. /// <summary>
  72. /// Group name for master browser (&lt;\\--__MSBROWSE__&gt;).
  73. /// </summary>
  74. MasterBrowserGroup = 0x01,
  75. /// <summary>
  76. /// Unique domainname.
  77. /// </summary>
  78. MasterBrowserUnique = 0x1D,
  79. /// <summary>
  80. /// Group domain name.
  81. /// </summary>
  82. BrowserServiceElections = 0x1E,
  83. /// <summary>
  84. /// Unique computername for file server.
  85. /// </summary>
  86. FileServerService = 0x20,
  87. }
  88. /// <summary>
  89. /// Available flags used in the header.
  90. /// </summary>
  91. [Flags]
  92. private enum HeaderFlags : byte
  93. {
  94. /// <summary>
  95. /// Broadcast flag.
  96. /// </summary>
  97. Broadcast = 0x01,
  98. /// <summary>
  99. /// Recursion available flag. Only valid in responses from a NetBIOS Name Server.
  100. /// Must be zero in all other responses.
  101. /// </summary>
  102. RecursionAvailable = 0x08,
  103. /// <summary>
  104. /// Recursion desired flag. May only be set on a request to a NetBIOS Name Server.
  105. /// </summary>
  106. RecursionDesired = 0x10,
  107. /// <summary>
  108. /// Truncation flag.
  109. /// Set if this message was truncated because the datagram carrying it would be greater than 576 bytes in length.
  110. /// </summary>
  111. Truncation = 0x20,
  112. /// <summary>
  113. /// Must be zero if Response is false.
  114. /// If set, then the node responing is an authority for the domain name.
  115. /// </summary>
  116. AuthoritativeAnswer = 0x40
  117. }
  118. /// <summary>
  119. /// Available opcodes used in the header.
  120. /// </summary>
  121. private enum HeaderOpcode : byte
  122. {
  123. Query = 0,
  124. Registration = 5,
  125. Release = 6,
  126. WACK = 7,
  127. Refresh = 8
  128. }
  129. /// <summary>
  130. /// Packet header.
  131. /// </summary>
  132. private struct Header
  133. {
  134. /// <summary>
  135. /// Transaction ID (chosen by requestor).
  136. /// </summary>
  137. public ushort NameTrnId;
  138. /// <summary>
  139. /// True for response, false for request.
  140. /// </summary>
  141. public bool Response;
  142. /// <summary>
  143. /// Operation code.
  144. /// </summary>
  145. public HeaderOpcode Opcode;
  146. /// <summary>
  147. /// Flags.
  148. /// </summary>
  149. public HeaderFlags Flags;
  150. /// <summary>
  151. /// Result codes of request.
  152. /// </summary>
  153. public byte Rcode;
  154. /// <summary>
  155. /// Number of entries in the question section of a Name Service packet.
  156. /// Always zero for response. Must be non-zero for all NetBIOS Name requests.
  157. /// </summary>
  158. public ushort QdCount;
  159. /// <summary>
  160. /// Number of resource records in the answer section of a Name Service packet.
  161. /// </summary>
  162. public ushort AnCount;
  163. /// <summary>
  164. /// Number of resource records in the authority section of a Name Service packet.
  165. /// </summary>
  166. public ushort NsCount;
  167. /// <summary>
  168. /// Number of resource records in the additional records section of a Name Service packet.
  169. /// </summary>
  170. public ushort ArCount;
  171. /// <summary>
  172. /// Parse a header represented as a byte array.
  173. /// Useful when receiving messages.
  174. /// </summary>
  175. /// <param name="data">The byte array of the header.</param>
  176. /// <returns>A header object.</returns>
  177. public static Header Parse(byte[] data)
  178. {
  179. if (data.Length < 12)
  180. throw new ArgumentException("Minimum 12 bytes are required");
  181. Header header = new Header();
  182. int offset = 0;
  183. header.NameTrnId = (ushort)((data[offset++] << 8) + data[offset++]);
  184. ushort temp = (ushort)((data[offset++] << 8) + data[offset++]);
  185. header.Response = temp >= 0x8000;
  186. header.Opcode = (HeaderOpcode)((temp >> 11) & 0x0F);
  187. header.Flags = (HeaderFlags)((temp >> 4) & 0x7F);
  188. header.Rcode = (byte)(temp & 0x0F);
  189. header.QdCount = (ushort)((data[offset++] << 8) + data[offset++]);
  190. header.AnCount = (ushort)((data[offset++] << 8) + data[offset++]);
  191. header.NsCount = (ushort)((data[offset++] << 8) + data[offset++]);
  192. header.ArCount = (ushort)((data[offset++] << 8) + data[offset++]);
  193. return header;
  194. }
  195. /// <summary>
  196. /// Gets the number of bytes that will be returned by the ToArray method.
  197. /// </summary>
  198. public int ByteSize
  199. {
  200. get { return 12; }
  201. }
  202. /// <summary>
  203. /// Convert a header to a byte array.
  204. /// Useful for sending messages.
  205. /// </summary>
  206. /// <returns>The byte array representation of the header.</returns>
  207. public byte[] ToArray()
  208. {
  209. byte[] data = new byte[ByteSize];
  210. int offset = 0;
  211. data[offset++] = (byte)(NameTrnId >> 8);
  212. data[offset++] = (byte)NameTrnId;
  213. ushort temp = (ushort)(((ushort)Opcode << 11) + ((ushort)Flags << 4) + Rcode);
  214. if (Response) temp += 0x8000;
  215. data[offset++] = (byte)(temp >> 8);
  216. data[offset++] = (byte)temp;
  217. data[offset++] = (byte)(QdCount >> 8);
  218. data[offset++] = (byte)QdCount;
  219. data[offset++] = (byte)(AnCount >> 8);
  220. data[offset++] = (byte)AnCount;
  221. data[offset++] = (byte)(NsCount >> 8);
  222. data[offset++] = (byte)NsCount;
  223. data[offset++] = (byte)(ArCount >> 8);
  224. data[offset++] = (byte)ArCount;
  225. return data;
  226. }
  227. }
  228. /// <summary>
  229. /// Available question types.
  230. /// </summary>
  231. private enum QuestionType : ushort
  232. {
  233. /// <summary>
  234. /// NetBIOS general Name Service Resource Record.
  235. /// </summary>
  236. NB = 0x0020,
  237. /// <summary>
  238. /// NetBIOS NODE STATUS Resource Record.
  239. /// </summary>
  240. NBSTAT = 0x0021
  241. }
  242. /// <summary>
  243. /// Available question classes.
  244. /// </summary>
  245. private enum QuestionClass : ushort
  246. {
  247. /// <summary>
  248. /// Internet class.
  249. /// </summary>
  250. IN = 0x0001
  251. }
  252. /// <summary>
  253. /// Packet question name record.
  254. /// </summary>
  255. private struct QuestionName
  256. {
  257. /// <summary>
  258. /// The NetBIOS name for the request.
  259. /// </summary>
  260. public string UncompressedName;
  261. /// <summary>
  262. /// The type of request.
  263. /// </summary>
  264. public QuestionType Type;
  265. /// <summary>
  266. /// The class of request.
  267. /// </summary>
  268. public QuestionClass Class;
  269. /// <summary>
  270. /// Extract an uncompressed name from an array.
  271. /// </summary>
  272. /// <param name="data">The byte array.</param>
  273. /// <param name="offset">The offset to start extracting from.</param>
  274. /// <returns>The uncompressed name.</returns>
  275. internal static string ExtractName(byte[] data, ref int offset)
  276. {
  277. string result = "";
  278. while (true)
  279. {
  280. byte length = data[offset++];
  281. if (length == 0)
  282. break; // Reached end of record.
  283. if (result.Length > 0)
  284. { // Add a '.' in uncompressed format
  285. result += "CO";
  286. }
  287. if ((length & 0xC0) != 0x00)
  288. { // Whooo, we have a pointer
  289. int address = (ushort)(((length & 0x3F) << 8) + data[offset++]);
  290. return ExtractName(data, ref address);
  291. }
  292. for (int i = 0; i < length; i++)
  293. result += (char)data[offset++];
  294. }
  295. return result;
  296. }
  297. /// <summary>
  298. /// Parse a QuestionName represented as a byte array.
  299. /// </summary>
  300. /// <param name="data">The byte array.</param>
  301. /// <param name="offset">The offset to start parsing from.</param>
  302. /// <returns>A parsed QuestionName object.</returns>
  303. public static QuestionName Parse(byte[] data, ref int offset)
  304. {
  305. QuestionName name = new QuestionName();
  306. name.UncompressedName = ExtractName(data, ref offset);
  307. name.Type = (QuestionType)((data[offset++] << 8) + data[offset++]);
  308. name.Class = (QuestionClass)((data[offset++] << 8) + data[offset++]);
  309. return name;
  310. }
  311. /// <summary>
  312. /// Gets the number of bytes that will be returned by the ToArray method.
  313. /// </summary>
  314. public int ByteSize
  315. {
  316. get
  317. {
  318. if (UncompressedName == null)
  319. throw new Exception("UncompressedName not set");
  320. return UncompressedName.Length + 2 + 4;
  321. }
  322. }
  323. /// <summary>
  324. /// Convert a QuestionName to a byte array.
  325. /// Useful for sending messages.
  326. /// </summary>
  327. /// <returns>The byte array representation of the QuestionName.</returns>
  328. public byte[] ToArray()
  329. { // TODO: support '.' in names? Not needed though for normal netbios name services
  330. if (UncompressedName == null)
  331. throw new Exception("UncompressedName not set");
  332. byte[] result = new byte[ByteSize];
  333. int offset = 0;
  334. result[offset++] = (byte)UncompressedName.Length;
  335. for (int i = 0; i < UncompressedName.Length; i++)
  336. result[offset++] = (byte)UncompressedName[i];
  337. result[offset++] = 0;
  338. result[offset++] = (byte)((ushort)Type >> 8);
  339. result[offset++] = (byte)Type;
  340. result[offset++] = (byte)((ushort)Class >> 8);
  341. result[offset++] = (byte)Class;
  342. if (offset != result.Length)
  343. throw new Exception("Length mismatch");
  344. return result;
  345. }
  346. }
  347. /// <summary>
  348. /// Available resource record types.
  349. /// </summary>
  350. private enum ResourceRecordType : ushort
  351. {
  352. /// <summary>
  353. /// IP address record.
  354. /// </summary>
  355. A = 0x0001,
  356. /// <summary>
  357. /// Name Server record.
  358. /// </summary>
  359. NS = 0x0002,
  360. /// <summary>
  361. /// NULL resource record (waiting for acknowledgement response).
  362. /// </summary>
  363. NULL = 0x000A,
  364. /// <summary>
  365. /// General NetBIOS record.
  366. /// </summary>
  367. NB = 0x0020,
  368. /// <summary>
  369. /// NetBIOS Node Status resource record.
  370. /// </summary>
  371. NBSTAT = 0x0021
  372. }
  373. /// <summary>
  374. /// Available resource record classes.
  375. /// </summary>
  376. private enum ResourceRecordClass : ushort
  377. {
  378. /// <summary>
  379. /// Internet class.
  380. /// </summary>
  381. IN = 0x0001
  382. }
  383. /// <summary>
  384. /// Packet resource record.
  385. /// </summary>
  386. private struct ResourceRecord
  387. {
  388. /// <summary>
  389. /// The NetBIOS name corresponding to this resource record.
  390. /// </summary>
  391. public string UncompressedName;
  392. /// <summary>
  393. /// The record type code.
  394. /// </summary>
  395. public ResourceRecordType Type;
  396. /// <summary>
  397. /// The record class code.
  398. /// </summary>
  399. public ResourceRecordClass Class;
  400. /// <summary>
  401. /// Time to Live of the resource record's name.
  402. /// </summary>
  403. public uint TTL;
  404. /// <summary>
  405. /// Class & Type dependent field. Contains the resource information.
  406. /// </summary>
  407. public byte[] Data;
  408. /// <summary>
  409. /// Parse a ResourceRecord represented as a byte array.
  410. /// </summary>
  411. /// <param name="data">The byte array.</param>
  412. /// <param name="offset">The offset to start parsing from.</param>
  413. /// <returns>A parsed ResourceRecord.</returns>
  414. public static ResourceRecord Parse(byte[] data, ref int offset)
  415. {
  416. ResourceRecord record = new ResourceRecord();
  417. record.UncompressedName = QuestionName.ExtractName(data, ref offset);
  418. record.Type = (ResourceRecordType)((data[offset++] << 8) + data[offset++]);
  419. record.Class = (ResourceRecordClass)((data[offset++] << 8) + data[offset++]);
  420. record.TTL = (uint)((data[offset++] << 24) + (data[offset++] << 16) + (data[offset++] << 8) + data[offset++]);
  421. int length = (ushort)((data[offset++] << 8) + data[offset++]);
  422. if (length > 0)
  423. {
  424. record.Data = new byte[length];
  425. for (int i = 0; i < length; i++)
  426. record.Data[i] = data[offset++];
  427. }
  428. else
  429. record.Data = null;
  430. return record;
  431. }
  432. /// <summary>
  433. /// Gets the number of bytes that will be returned by the ToArray method.
  434. /// </summary>
  435. public int ByteSize
  436. {
  437. get
  438. {
  439. if (UncompressedName == null)
  440. throw new Exception("UncompressedName not set");
  441. return UncompressedName.Length + 2 + 4 + 4 + 2 + (Data != null ? Data.Length : 0);
  442. }
  443. }
  444. /// <summary>
  445. /// Convert a ResourceRecord to a byte array.
  446. /// Useful for sending messages.
  447. /// </summary>
  448. /// <returns>The byte array representation of the ResourceRecord.</returns>
  449. public byte[] ToArray()
  450. {
  451. if (UncompressedName == null)
  452. throw new Exception("UncompressedName not set");
  453. byte[] result = new byte[ByteSize];
  454. int offset = 0;
  455. result[offset++] = (byte)UncompressedName.Length;
  456. for (int i = 0; i < UncompressedName.Length; i++)
  457. result[offset++] = (byte)UncompressedName[i];
  458. result[offset++] = 0;
  459. result[offset++] = (byte)((ushort)Type >> 8);
  460. result[offset++] = (byte)Type;
  461. result[offset++] = (byte)((ushort)Class >> 8);
  462. result[offset++] = (byte)Class;
  463. result[offset++] = (byte)(TTL >> 24);
  464. result[offset++] = (byte)(TTL >> 16);
  465. result[offset++] = (byte)(TTL >> 8);
  466. result[offset++] = (byte)TTL;
  467. int length = Data != null ? Data.Length : 0;
  468. result[offset++] = (byte)(length >> 8);
  469. result[offset++] = (byte)length;
  470. for (int i = 0; i < length; i++)
  471. result[offset++] = Data[i];
  472. if (offset != result.Length)
  473. throw new Exception("Length mismatch");
  474. return result;
  475. }
  476. }
  477. /// <summary>
  478. /// Full packet (header + data).
  479. /// </summary>
  480. private struct Packet
  481. {
  482. public Header Header;
  483. public QuestionName[] QuestionEntries;
  484. public ResourceRecord[] AnswerResourceRecords;
  485. public ResourceRecord[] AuthorityResourceRecords;
  486. public ResourceRecord[] AdditionalResourceRecords;
  487. /// <summary>
  488. /// Parses a packet from incomming data.
  489. /// </summary>
  490. /// <param name="data">Byte array containing the incomming data.</param>
  491. /// <returns>A parsed Packet.</returns>
  492. public static Packet Parse(byte[] data)
  493. {
  494. Packet packet = new Packet();
  495. packet.Header = Header.Parse(data);
  496. int offset = packet.Header.ByteSize;
  497. if (packet.Header.QdCount > 0)
  498. {
  499. packet.QuestionEntries = new QuestionName[packet.Header.QdCount];
  500. for (int i = 0; i < packet.Header.QdCount; i++)
  501. packet.QuestionEntries[i] = QuestionName.Parse(data, ref offset);
  502. }
  503. else
  504. packet.QuestionEntries = null;
  505. if (packet.Header.AnCount > 0)
  506. {
  507. packet.AnswerResourceRecords = new ResourceRecord[packet.Header.AnCount];
  508. for (int i = 0; i < packet.Header.AnCount; i++)
  509. packet.AnswerResourceRecords[i] = ResourceRecord.Parse(data, ref offset);
  510. }
  511. else
  512. packet.AnswerResourceRecords = null;
  513. if (packet.Header.NsCount > 0)
  514. {
  515. packet.AuthorityResourceRecords = new ResourceRecord[packet.Header.NsCount];
  516. for (int i = 0; i < packet.Header.NsCount; i++)
  517. packet.AuthorityResourceRecords[i] = ResourceRecord.Parse(data, ref offset);
  518. }
  519. else
  520. packet.AuthorityResourceRecords = null;
  521. if (packet.Header.ArCount > 0)
  522. {
  523. packet.AdditionalResourceRecords = new ResourceRecord[packet.Header.ArCount];
  524. for (int i = 0; i < packet.Header.ArCount; i++)
  525. packet.AdditionalResourceRecords[i] = ResourceRecord.Parse(data, ref offset);
  526. }
  527. else
  528. packet.AdditionalResourceRecords = null;
  529. return packet;
  530. }
  531. /// <summary>
  532. /// Gets the number of bytes that will be returned by the ToArray method.
  533. /// </summary>
  534. public int ByteSize
  535. {
  536. get
  537. {
  538. int result = Header.ByteSize;
  539. if (QuestionEntries != null)
  540. foreach (QuestionName name in QuestionEntries)
  541. result += name.ByteSize;
  542. if (AnswerResourceRecords != null)
  543. foreach (ResourceRecord record in AnswerResourceRecords)
  544. result += record.ByteSize;
  545. if (AuthorityResourceRecords != null)
  546. foreach (ResourceRecord record in AuthorityResourceRecords)
  547. result += record.ByteSize;
  548. if (AdditionalResourceRecords != null)
  549. foreach (ResourceRecord record in AdditionalResourceRecords)
  550. result += record.ByteSize;
  551. return result;
  552. }
  553. }
  554. /// <summary>
  555. /// Prepares a packet for transmission.
  556. /// </summary>
  557. /// <returns>A byte array containing the packet data.</returns>
  558. public byte[] ToArray()
  559. {
  560. byte[] result = new byte[ByteSize];
  561. int offset = 0;
  562. byte[] source = Header.ToArray();
  563. Array.Copy(source, 0, result, offset, source.Length);
  564. offset += source.Length;
  565. if (QuestionEntries != null)
  566. foreach (QuestionName name in QuestionEntries)
  567. {
  568. source = name.ToArray();
  569. Array.Copy(source, 0, result, offset, source.Length);
  570. offset += source.Length;
  571. }
  572. if (AnswerResourceRecords != null)
  573. foreach (ResourceRecord record in AnswerResourceRecords)
  574. {
  575. source = record.ToArray();
  576. Array.Copy(source, 0, result, offset, source.Length);
  577. offset += source.Length;
  578. }
  579. if (AuthorityResourceRecords != null)
  580. foreach (ResourceRecord record in AuthorityResourceRecords)
  581. {
  582. source = record.ToArray();
  583. Array.Copy(source, 0, result, offset, source.Length);
  584. offset += source.Length;
  585. }
  586. if (AdditionalResourceRecords != null)
  587. foreach (ResourceRecord record in AdditionalResourceRecords)
  588. {
  589. source = record.ToArray();
  590. Array.Copy(source, 0, result, offset, source.Length);
  591. offset += source.Length;
  592. }
  593. if (offset != result.Length)
  594. throw new Exception("Length mismatch");
  595. return result;
  596. }
  597. }
  598. #endregion
  599. #region Declarations
  600. private Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  601. private EndPoint broadcastEndPoint = new IPEndPoint(IPAddress.Parse("255.255.255.255"), BCAST_NS_PORT);
  602. private byte[] localIP = null;
  603. private byte[] localMacAddress = null;
  604. private Thread thread;
  605. private ExtendedTimer updateTimer;
  606. private bool terminate = false;
  607. private ArrayList nameList = new ArrayList();
  608. private bool capture = false;
  609. private bool denyCaptured = false;
  610. #endregion
  611. #region Construction / destruction
  612. /// <summary>
  613. /// Creates a brand new name service object.
  614. /// Since there is only one UDP 137 port, you should use this class as singleton.
  615. /// </summary>
  616. public NameService()
  617. {
  618. foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
  619. {
  620. localIP = IPAddress.Parse(networkInterface.IPAddress).GetAddressBytes();
  621. localMacAddress = networkInterface.PhysicalAddress;
  622. break;
  623. }
  624. socket.Bind(new IPEndPoint(IPAddress.Any, BCAST_NS_PORT));
  625. updateTimer = new ExtendedTimer(new TimerCallback(OnUpdate), null, Timeout.Infinite, Timeout.Infinite);
  626. thread = new Thread(new ThreadStart(SocketThread));
  627. thread.Start();
  628. thread.Priority = ThreadPriority.AboveNormal;
  629. Thread.Sleep(0);
  630. }
  631. /// <summary>
  632. /// Releases used resources.
  633. /// </summary>
  634. public void Dispose()
  635. {
  636. if (updateTimer != null)
  637. {
  638. updateTimer.Dispose();
  639. updateTimer = null;
  640. }
  641. // Shut down socket first, so the ReceiveFrom method in thread gets unblocked
  642. if (socket != null)
  643. {
  644. socket.Close();
  645. socket = null;
  646. }
  647. if (thread != null)
  648. {
  649. terminate = true;
  650. thread.Join();
  651. thread = null;
  652. }
  653. }
  654. #endregion
  655. #region Static methods
  656. /// <summary>
  657. /// Converts an uncompressed NetBIOS name to a compressed, human-readable name.
  658. /// </summary>
  659. /// <param name="name">The uncompressed NetBIOS name.</param>
  660. /// <param name="suffix">The name suffix as introduced by Microsoft.</param>
  661. /// <returns>The compressed, human-readable name.</returns>
  662. private static string CompressName(string name, out MsSuffix suffix)
  663. {
  664. if (name.Length != 32)
  665. throw new ArgumentException("Unsupported name length, should be 32 characters", "name");
  666. suffix = MsSuffix.Default;
  667. int offset = 0;
  668. char[] result = new char[15];
  669. for (int i = 0; i < 16; i++)
  670. {
  671. byte b1 = (byte)(name[offset++] - 'A');
  672. byte b2 = (byte)(name[offset++] - 'A');
  673. if ((b1 > 15) || (b2 > 15))
  674. throw new ArgumentException("Invalid characters in name", "name");
  675. b1 <<= 4;
  676. b1 += b2;
  677. if (i < 15)
  678. result[i] = (char)b1;
  679. else
  680. suffix = (MsSuffix)b1;
  681. }
  682. return new string(result).TrimEnd(new char[] { ' ' });
  683. }
  684. /// <summary>
  685. /// Converts a human-readable name to an uncompressed NetBIOS name.
  686. /// </summary>
  687. /// <param name="name">The compressed, human-readable name.</param>
  688. /// <param name="suffix">The name suffix as introduced by Microsoft.</param>
  689. /// <returns>The uncompressed NetBIOS name.</returns>
  690. private static string UncompressName(string name, MsSuffix suffix)
  691. {
  692. if (name.Length > 15)
  693. throw new ArgumentException("Name cannot contain more than 15 characters");
  694. char[] result = new char[32];
  695. int offset = 0;
  696. for (int i = 0; i < 15; i++)
  697. {
  698. char c = i < name.Length ? name[i] : ' ';
  699. result[offset++] = (char)('A' + (c >> 4));
  700. result[offset++] = (char)('A' + (c & 15));
  701. }
  702. result[offset++] = (char)('A' + ((byte)suffix >> 4));
  703. result[offset++] = (char)('A' + ((byte)suffix & 15));
  704. return new string(result);
  705. }
  706. #endregion
  707. #region Thread & timer
  708. private void SocketThread()
  709. {
  710. byte[] buffer = new byte[1024];
  711. EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 137);
  712. while (!terminate)
  713. {
  714. try
  715. {
  716. int count = socket.ReceiveFrom(buffer, ref remoteEndPoint); // Blocking call, returns 0 when socket is closed
  717. if (count == 0)
  718. break; // Socket has been closed
  719. // Don't check own messages
  720. if ((remoteEndPoint as IPEndPoint).Address.Equals(IPAddress.Loopback))
  721. continue;
  722. ProcessReceivedPacket(buffer, count, remoteEndPoint);
  723. }
  724. catch (Exception ex)
  725. {
  726. System.Diagnostics.Debug.WriteLine(ex.Message);
  727. }
  728. }
  729. }
  730. private void OnUpdate(object o)
  731. {
  732. lock (nameList)
  733. foreach (Name name in nameList)
  734. Request(name, HeaderOpcode.Refresh);
  735. updateTimer.Change(NAME_UPDATE_INTERVAL_MS, Timeout.Infinite);
  736. }
  737. #endregion
  738. #region Private methods
  739. private void ProcessReceivedPacket(byte[] data, int size, EndPoint remoteEndPoint)
  740. {
  741. // TODO: verify the size one day
  742. Packet packet = Packet.Parse(data);
  743. //Header header = Header.Parse(data); // Only capture header initially and not the full packet (need 4 speed)
  744. if (capture && (packet.Header.NameTrnId == NAME_TRN_ID) && (packet.Header.Opcode != 0))
  745. denyCaptured = true; // Other node denied our registration request
  746. if (!packet.Header.Response && (packet.Header.QdCount > 0))
  747. { // We received a request
  748. switch (packet.Header.Opcode)
  749. {
  750. case HeaderOpcode.Query:
  751. if (packet.QuestionEntries[0].UncompressedName == "CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
  752. { // "*" received
  753. StatusResponse(packet.Header.NameTrnId, remoteEndPoint);
  754. }
  755. else
  756. {
  757. lock (nameList)
  758. {
  759. foreach (Name name in nameList)
  760. {
  761. if (name.UncompressedName == packet.QuestionEntries[0].UncompressedName)
  762. { // We own the name
  763. Response(name, HeaderOpcode.Query, 0, packet.Header.NameTrnId, remoteEndPoint);
  764. }
  765. }
  766. }
  767. }
  768. break;
  769. case HeaderOpcode.Registration:
  770. lock (nameList)
  771. {
  772. foreach (Name name in nameList)
  773. {
  774. if (name.Type == NameType.Group)
  775. continue;
  776. if (name.UncompressedName == packet.QuestionEntries[0].UncompressedName)
  777. { // We own the name, send negative response (0x06 ACT_ERR : Active error, name owned)
  778. Response(name, HeaderOpcode.Registration, 6, packet.Header.NameTrnId, remoteEndPoint);
  779. }
  780. }
  781. }
  782. break;
  783. }
  784. }
  785. }
  786. private void StartCapture()
  787. {
  788. denyCaptured = false;
  789. capture = true;
  790. }
  791. private bool StopCapture()
  792. {
  793. capture = false;
  794. return !denyCaptured;
  795. }
  796. private void Request(Name name, HeaderOpcode opcode)
  797. {
  798. Packet packet = new Packet();
  799. packet.Header.NameTrnId = NAME_TRN_ID;
  800. packet.Header.Opcode = opcode;
  801. packet.Header.Flags = HeaderFlags.Broadcast | HeaderFlags.RecursionDesired;
  802. packet.Header.Rcode = 0;
  803. packet.Header.QdCount = 1;
  804. packet.Header.ArCount = 1;
  805. packet.QuestionEntries = new QuestionName[1];
  806. packet.QuestionEntries[0].UncompressedName = name.UncompressedName;
  807. packet.QuestionEntries[0].Type = QuestionType.NB;
  808. packet.QuestionEntries[0].Class = QuestionClass.IN;
  809. packet.AdditionalResourceRecords = new ResourceRecord[1];
  810. packet.AdditionalResourceRecords[0].UncompressedName = name.UncompressedName; // TODO: Should use a pointer here
  811. packet.AdditionalResourceRecords[0].Type = ResourceRecordType.NB;
  812. packet.AdditionalResourceRecords[0].Class = ResourceRecordClass.IN;
  813. packet.AdditionalResourceRecords[0].TTL = 0;
  814. byte[] data = new byte[6];
  815. data[0] = (byte)(name.Type == NameType.Group ? 0x80 : 0x00); // NB_FLAGS / bit 15: Group name flag, bit 16-15: 00 B node, 01 P node, 10 M node, 11 reserved, bit 14-8: reserved
  816. data[1] = 0x00; // NB_FLAGS
  817. // NB_ADDRESS
  818. for (int i = 0; i < 4; i++)
  819. data[i + 2] = localIP[i];
  820. packet.AdditionalResourceRecords[0].Data = data;
  821. data = packet.ToArray();
  822. lock (socket)
  823. socket.SendTo(data, broadcastEndPoint);
  824. }
  825. private void Response(Name name, HeaderOpcode opcode, byte rcode, ushort nameTrnId, EndPoint remoteEndPoint)
  826. {
  827. Packet packet = new Packet();
  828. packet.Header.Response = true;
  829. packet.Header.NameTrnId = nameTrnId;
  830. packet.Header.Opcode = opcode;
  831. packet.Header.Flags = HeaderFlags.AuthoritativeAnswer | HeaderFlags.RecursionDesired;
  832. packet.Header.Rcode = rcode;
  833. packet.Header.AnCount = 1;
  834. packet.AnswerResourceRecords = new ResourceRecord[1];
  835. packet.AnswerResourceRecords[0].UncompressedName = name.UncompressedName;
  836. packet.AnswerResourceRecords[0].Type = ResourceRecordType.NB;
  837. packet.AnswerResourceRecords[0].Class = ResourceRecordClass.IN;
  838. packet.AnswerResourceRecords[0].TTL = 0;
  839. byte[] data = new byte[6];
  840. data[0] = (byte)(name.Type == NameType.Group ? 0x80 : 0x00); // NB_FLAGS / bit 15: Group name flag, bit 16-15: 00 B node, 01 P node, 10 M node, 11 reserved, bit 14-8: reserved
  841. data[1] = 0x00; // NB_FLAGS
  842. // NB_ADDRESS
  843. for (int i = 0; i < 4; i++)
  844. data[i + 2] = localIP[i];
  845. packet.AnswerResourceRecords[0].Data = data;
  846. data = packet.ToArray();
  847. try
  848. {
  849. lock (socket)
  850. socket.SendTo(data, remoteEndPoint);
  851. }
  852. catch
  853. {
  854. // Handles situations where the remote host is not accessable through the network.
  855. }
  856. }
  857. private void StatusResponse(ushort nameTrnId, EndPoint remoteEndPoint)
  858. {
  859. lock (nameList)
  860. if (nameList.Count == 0)
  861. return;
  862. Packet packet = new Packet();
  863. packet.Header.Response = true;
  864. packet.Header.NameTrnId = nameTrnId;
  865. packet.Header.Opcode = (HeaderOpcode)0;
  866. packet.Header.Flags = HeaderFlags.AuthoritativeAnswer;
  867. packet.Header.Rcode = 0;
  868. packet.Header.AnCount = 1;
  869. packet.AnswerResourceRecords = new ResourceRecord[1];
  870. packet.AnswerResourceRecords[0].UncompressedName = "CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";//uncompressedName;
  871. packet.AnswerResourceRecords[0].Type = ResourceRecordType.NBSTAT;
  872. packet.AnswerResourceRecords[0].Class = ResourceRecordClass.IN;
  873. packet.AnswerResourceRecords[0].TTL = 0;
  874. int length = 0;
  875. byte[] data;
  876. lock (nameList)
  877. {
  878. length += 1; // NUM_NAMES
  879. foreach (Name name in nameList)
  880. {
  881. length += 16;
  882. length += 2; // NAME_FLAGS
  883. }
  884. length += 46; // STATISTICS
  885. int offset = 0;
  886. data = new byte[length];
  887. data[offset++] = (byte)nameList.Count; // NUM_NAMES
  888. bool first = true;
  889. foreach (Name name in nameList)
  890. {
  891. MsSuffix suffix;
  892. string value = CompressName(name.UncompressedName, out suffix);
  893. while (value.Length < 15)
  894. value += ' ';
  895. Array.Copy(Encoding.UTF8.GetBytes(value), 0, data, offset, 15);
  896. offset += 15;
  897. data[offset++] = (byte)suffix;
  898. // NAME_FLAGS
  899. //+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
  900. //| G | ONT |DRG|CNF|ACT|PRM| RESERVED |
  901. //+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
  902. data[offset++] = (byte)((name.Type == NameType.Group ? 0x80 : 0x00) + 0x04 + (first ? 0x02 : 0x00)); // treat first name as permanent name
  903. data[offset++] = 0;
  904. first = false;
  905. }
  906. // Statistics - only fill in mac address
  907. Array.Copy(localMacAddress, 0, data, offset, localMacAddress.Length);
  908. offset += localMacAddress.Length;
  909. }
  910. packet.AnswerResourceRecords[0].Data = data;
  911. data = packet.ToArray();
  912. try
  913. {
  914. lock (socket)
  915. socket.SendTo(data, remoteEndPoint);
  916. }
  917. catch
  918. {
  919. // Handles situations where the remote host is not accessable through the network.
  920. }
  921. }
  922. #endregion
  923. #region Public methods
  924. public bool AddName(string name, NameType type, MsSuffix suffix)
  925. {
  926. Name node = new Name();
  927. node.UncompressedName = UncompressName(name, suffix);
  928. node.Type = type;
  929. // Send request
  930. StartCapture();
  931. for (int i = 0; i < BCAST_REQ_RETRY_COUNT; i++)
  932. {
  933. Request(node, HeaderOpcode.Registration);
  934. Thread.Sleep(3 * BCAST_REQ_RETRY_TIMEOUT); // Three times, otherwise FEZ can't follow :(
  935. if (denyCaptured)
  936. break;
  937. }
  938. if (!StopCapture())
  939. return false; // Name in use
  940. Request(node, HeaderOpcode.Refresh);
  941. lock (nameList)
  942. nameList.Add(node);
  943. updateTimer.Change(NAME_UPDATE_INTERVAL_MS, Timeout.Infinite);
  944. return true;
  945. }
  946. public bool RemoveName(string name, NameType type, MsSuffix suffix)
  947. {
  948. Name node = new Name();
  949. node.UncompressedName = UncompressName(name, suffix);
  950. node.Type = type;
  951. lock (nameList)
  952. {
  953. int i = 0;
  954. for (; i < nameList.Count; i++)
  955. {
  956. Name n = (Name)nameList[i];
  957. if ((n.UncompressedName == node.UncompressedName) && (n.Type == node.Type))
  958. break;
  959. }
  960. if (i >= nameList.Count)
  961. return false; // Name not found
  962. nameList.RemoveAt(i);
  963. if (nameList.Count == 0)
  964. updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
  965. }
  966. // Send request
  967. for (int i = 0; i < BCAST_REQ_RETRY_COUNT; i++)
  968. {
  969. Request(node, HeaderOpcode.Release);
  970. Thread.Sleep(BCAST_REQ_RETRY_TIMEOUT);
  971. }
  972. return true;
  973. }
  974. #endregion
  975. }
  976. }