StunMessage.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. using STUN.Message.Enums;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Net;
  5. using System.Text;
  6. namespace STUN.Message
  7. {
  8. /// <summary>
  9. /// Implements STUN message. Defined in RFC 5389.
  10. /// </summary>
  11. /// <remarks>
  12. /// https://tools.ietf.org/html/rfc5389#section-6
  13. /// </remarks>
  14. public class StunMessage
  15. {
  16. #region Properties Implementation
  17. /// <summary>
  18. /// Gets STUN message type.
  19. /// </summary>
  20. public StunMessageType Type { get; set; } = StunMessageType.BindingRequest;
  21. /// <summary>
  22. /// Gets magic cookie value. This is always 0x2112A442.
  23. /// </summary>
  24. public int MagicCookie { get; private set; }
  25. /// <summary>
  26. /// Gets transaction ID.
  27. /// </summary>
  28. public byte[] TransactionId { get; private set; }
  29. /// <summary>
  30. /// Gets or sets IP end point what was actually connected to STUN server. Returns null if not specified.
  31. /// </summary>
  32. public IPEndPoint MappedAddress { get; set; }
  33. /// <summary>
  34. /// Gets or sets IP end point where to STUN client likes to receive response.
  35. /// Value null means not specified.
  36. /// </summary>
  37. public IPEndPoint ResponseAddress { get; set; }
  38. /// <summary>
  39. /// Gets or sets how and where STUN server must send response back to STUN client.
  40. /// Value null means not specified.
  41. /// </summary>
  42. public StunChangeRequest ChangeRequest { get; set; }
  43. /// <summary>
  44. /// Gets or sets STUN server IP end point what sent response to STUN client. Value null
  45. /// means not specified.
  46. /// </summary>
  47. public IPEndPoint SourceAddress { get; set; }
  48. /// <summary>
  49. /// Gets or sets IP end point where STUN server will send response back to STUN client
  50. /// if the "change IP" and "change port" flags had been set in the ChangeRequest.
  51. /// </summary>
  52. public IPEndPoint ChangedAddress { get; set; }
  53. /// <summary>
  54. /// Gets or sets user name. Value null means not specified.
  55. /// </summary>
  56. public string UserName { get; set; }
  57. /// <summary>
  58. /// Gets or sets password. Value null means not specified.
  59. /// </summary>
  60. public string Password { get; set; }
  61. //public MessageIntegrity
  62. /// <summary>
  63. /// Gets or sets error info. Returns null if not specified.
  64. /// </summary>
  65. public StunErrorCode ErrorCode { get; set; }
  66. /// <summary>
  67. /// Gets or sets IP endpoint from which IP end point STUN server got STUN client request.
  68. /// Value null means not specified.
  69. /// </summary>
  70. public IPEndPoint ReflectedFrom { get; set; }
  71. /// <summary>
  72. /// Gets or sets server name.
  73. /// </summary>
  74. public string ServerName { get; set; }
  75. #endregion
  76. public StunMessage()
  77. {
  78. TransactionId = new byte[12];
  79. new Random().NextBytes(TransactionId);
  80. }
  81. #region method Parse
  82. /// <summary>
  83. /// Parses STUN message from raw data packet.
  84. /// </summary>
  85. /// <param name="data">Raw STUN message.</param>
  86. /// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
  87. public void Parse(byte[] data)
  88. {
  89. if (data == null)
  90. {
  91. throw new ArgumentNullException(nameof(data));
  92. }
  93. /* RFC 5389 6.
  94. All STUN messages MUST start with a 20-byte header followed by zero
  95. or more Attributes. The STUN header contains a STUN message type,
  96. magic cookie, transaction ID, and message length.
  97. 0 1 2 3
  98. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  99. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  100. |0 0| STUN Message Type | Message Length |
  101. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  102. | Magic Cookie |
  103. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  104. | |
  105. | Transaction ID (96 bits) |
  106. | |
  107. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  108. The message length is the count, in bytes, of the size of the
  109. message, not including the 20 byte header.
  110. */
  111. if (data.Length < 20)
  112. {
  113. throw new ArgumentException(@"Invalid STUN message value !");
  114. }
  115. var offset = 0;
  116. //--- message header --------------------------------------------------
  117. // STUN Message Type
  118. var messageType = data[offset++] << 8 | data[offset++];
  119. if (messageType == (int)StunMessageType.BindingErrorResponse)
  120. {
  121. Type = StunMessageType.BindingErrorResponse;
  122. }
  123. else if (messageType == (int)StunMessageType.BindingRequest)
  124. {
  125. Type = StunMessageType.BindingRequest;
  126. }
  127. else if (messageType == (int)StunMessageType.BindingResponse)
  128. {
  129. Type = StunMessageType.BindingResponse;
  130. }
  131. else if (messageType == (int)StunMessageType.SharedSecretErrorResponse)
  132. {
  133. Type = StunMessageType.SharedSecretErrorResponse;
  134. }
  135. else if (messageType == (int)StunMessageType.SharedSecretRequest)
  136. {
  137. Type = StunMessageType.SharedSecretRequest;
  138. }
  139. else if (messageType == (int)StunMessageType.SharedSecretResponse)
  140. {
  141. Type = StunMessageType.SharedSecretResponse;
  142. }
  143. else
  144. {
  145. throw new ArgumentException(@"Invalid STUN message type value !");
  146. }
  147. // Message Length
  148. var messageLength = data[offset++] << 8 | data[offset++];
  149. // Magic Cookie
  150. MagicCookie = data[offset++] << 24 | data[offset++] << 16 | data[offset++] << 8 | data[offset++];
  151. // Transaction ID
  152. TransactionId = new byte[12];
  153. Array.Copy(data, offset, TransactionId, 0, 12);
  154. offset += 12;
  155. //--- Message attributes ---------------------------------------------
  156. while (offset - 20 < messageLength)
  157. {
  158. ParseAttribute(data, ref offset);
  159. }
  160. }
  161. #endregion
  162. #region method ToByteData
  163. /// <summary>
  164. /// Converts this to raw STUN packet.
  165. /// </summary>
  166. /// <returns>Returns raw STUN packet.</returns>
  167. public byte[] ToByteData()
  168. {
  169. /* RFC 5389 6.
  170. All STUN messages MUST start with a 20-byte header followed by zero
  171. or more Attributes. The STUN header contains a STUN message type,
  172. magic cookie, transaction ID, and message length.
  173. 0 1 2 3
  174. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  175. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  176. |0 0| STUN Message Type | Message Length |
  177. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  178. | Magic Cookie |
  179. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  180. | |
  181. | Transaction ID (96 bits) |
  182. | |
  183. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  184. The message length is the count, in bytes, of the size of the
  185. message, not including the 20 byte header.
  186. */
  187. // We allocate 512 for header, that should be more than enough.
  188. var msg = new byte[512];
  189. var offset = 0;
  190. //--- message header -------------------------------------
  191. // STUN Message Type (2 bytes)
  192. msg[offset++] = (byte)(((int)Type >> 8) & 0x3F);
  193. msg[offset++] = (byte)((int)Type & 0xFF);
  194. // Message Length (2 bytes) will be assigned at last.
  195. msg[offset++] = 0;
  196. msg[offset++] = 0;
  197. // Magic Cookie
  198. msg[offset++] = (byte)((MagicCookie >> 24) & 0xFF);
  199. msg[offset++] = (byte)((MagicCookie >> 16) & 0xFF);
  200. msg[offset++] = (byte)((MagicCookie >> 8) & 0xFF);
  201. msg[offset++] = (byte)(MagicCookie & 0xFF);
  202. // Transaction ID (16 bytes)
  203. Array.Copy(TransactionId, 0, msg, offset, 12);
  204. offset += 12;
  205. //--- Message attributes ------------------------------------
  206. /* RFC 3489 11.2.
  207. After the header are 0 or more attributes. Each attribute is TLV
  208. encoded, with a 16 bit type, 16 bit length, and variable value:
  209. 0 1 2 3
  210. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  211. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  212. | Type | Length |
  213. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  214. | Value ....
  215. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  216. */
  217. if (MappedAddress != null)
  218. {
  219. StoreEndPoint(AttributeType.MappedAddress, MappedAddress, msg, ref offset);
  220. }
  221. else if (ResponseAddress != null)
  222. {
  223. StoreEndPoint(AttributeType.ResponseAddress, ResponseAddress, msg, ref offset);
  224. }
  225. else if (ChangeRequest != null)
  226. {
  227. /*
  228. The CHANGE-REQUEST attribute is used by the client to request that
  229. the server use a different address and/or port when sending the
  230. response. The attribute is 32 bits long, although only two bits (A
  231. and B) are used:
  232. 0 1 2 3
  233. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  234. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  235. |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0|
  236. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  237. The meaning of the flags is:
  238. A: This is the "change IP" flag. If true, it requests the server
  239. to send the Binding Response with a different IP address than the
  240. one the Binding Request was received on.
  241. B: This is the "change port" flag. If true, it requests the
  242. server to send the Binding Response with a different port than the
  243. one the Binding Request was received on.
  244. */
  245. // Attribute header
  246. msg[offset++] = 0;
  247. msg[offset++] = (int)AttributeType.ChangeRequest & 0xFF;
  248. msg[offset++] = 0;
  249. msg[offset++] = 4;
  250. msg[offset++] = 0;
  251. msg[offset++] = 0;
  252. msg[offset++] = 0;
  253. msg[offset++] = (byte)(Convert.ToInt32(ChangeRequest.ChangeIp) << 2 | Convert.ToInt32(ChangeRequest.ChangePort) << 1);
  254. }
  255. else if (SourceAddress != null)
  256. {
  257. StoreEndPoint(AttributeType.SourceAddress, SourceAddress, msg, ref offset);
  258. }
  259. else if (ChangedAddress != null)
  260. {
  261. StoreEndPoint(AttributeType.ChangedAddress, ChangedAddress, msg, ref offset);
  262. }
  263. else if (UserName != null)
  264. {
  265. var userBytes = Encoding.ASCII.GetBytes(UserName);
  266. // Attribute header
  267. msg[offset++] = (int)AttributeType.Username >> 8;
  268. msg[offset++] = (int)AttributeType.Username & 0xFF;
  269. msg[offset++] = (byte)(userBytes.Length >> 8);
  270. msg[offset++] = (byte)(userBytes.Length & 0xFF);
  271. Array.Copy(userBytes, 0, msg, offset, userBytes.Length);
  272. offset += userBytes.Length;
  273. }
  274. else if (Password != null)
  275. {
  276. var userBytes = Encoding.ASCII.GetBytes(UserName);
  277. // Attribute header
  278. msg[offset++] = (int)AttributeType.Password >> 8;
  279. msg[offset++] = (int)AttributeType.Password & 0xFF;
  280. msg[offset++] = (byte)(userBytes.Length >> 8);
  281. msg[offset++] = (byte)(userBytes.Length & 0xFF);
  282. Array.Copy(userBytes, 0, msg, offset, userBytes.Length);
  283. offset += userBytes.Length;
  284. }
  285. else if (ErrorCode != null)
  286. {
  287. /* 3489 11.2.9.
  288. 0 1 2 3
  289. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  290. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  291. | 0 |Class| Number |
  292. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  293. | Reason Phrase (variable) ..
  294. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  295. */
  296. var reasonBytes = Encoding.ASCII.GetBytes(ErrorCode.ReasonText);
  297. // Header
  298. msg[offset++] = 0;
  299. msg[offset++] = (int)AttributeType.ErrorCode;
  300. msg[offset++] = 0;
  301. msg[offset++] = (byte)(4 + reasonBytes.Length);
  302. // Empty
  303. msg[offset++] = 0;
  304. msg[offset++] = 0;
  305. // Class
  306. msg[offset++] = (byte)Math.Floor(ErrorCode.Code / 100.0);
  307. // Number
  308. msg[offset++] = (byte)(ErrorCode.Code & 0xFF);
  309. // ReasonPhrase
  310. Array.Copy(reasonBytes, msg, reasonBytes.Length);
  311. offset += reasonBytes.Length;
  312. }
  313. else if (ReflectedFrom != null)
  314. {
  315. StoreEndPoint(AttributeType.ReflectedFrom, ReflectedFrom, msg, ref offset);
  316. }
  317. // Update Message Length. NOTE: 20 bytes header not included.
  318. msg[2] = (byte)((offset - 20) >> 8);
  319. msg[3] = (byte)((offset - 20) & 0xFF);
  320. // Make retVal with actual size.
  321. var retVal = new byte[offset];
  322. Array.Copy(msg, retVal, retVal.Length);
  323. return retVal;
  324. }
  325. #endregion
  326. #region method ParseAttribute
  327. /// <summary>
  328. /// Parses attribute from data.
  329. /// </summary>
  330. /// <param name="data">SIP message data.</param>
  331. /// <param name="offset">Offset in data.</param>
  332. private void ParseAttribute(byte[] data, ref int offset)
  333. {
  334. /* RFC 3489 11.2.
  335. Each attribute is TLV encoded, with a 16 bit type, 16 bit length, and variable value:
  336. 0 1 2 3
  337. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  338. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  339. | Type | Length |
  340. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  341. | Value ....
  342. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  343. */
  344. // Type
  345. var type = (AttributeType)(data[offset++] << 8 | data[offset++]);
  346. // Length
  347. var length = data[offset++] << 8 | data[offset++];
  348. // MAPPED-ADDRESS
  349. if (type == AttributeType.MappedAddress)
  350. {
  351. MappedAddress = ParseEndPoint(data, ref offset);
  352. }
  353. // RESPONSE-ADDRESS
  354. else if (type == AttributeType.ResponseAddress)
  355. {
  356. ResponseAddress = ParseEndPoint(data, ref offset);
  357. }
  358. // CHANGE-REQUEST
  359. else if (type == AttributeType.ChangeRequest)
  360. {
  361. /*
  362. The CHANGE-REQUEST attribute is used by the client to request that
  363. the server use a different address and/or port when sending the
  364. response. The attribute is 32 bits long, although only two bits (A
  365. and B) are used:
  366. 0 1 2 3
  367. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  368. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  369. |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0|
  370. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  371. The meaning of the flags is:
  372. A: This is the "change IP" flag. If true, it requests the server
  373. to send the Binding Response with a different IP address than the
  374. one the Binding Request was received on.
  375. B: This is the "change port" flag. If true, it requests the
  376. server to send the Binding Response with a different port than the
  377. one the Binding Request was received on.
  378. */
  379. // Skip 3 bytes
  380. offset += 3;
  381. ChangeRequest = new StunChangeRequest((data[offset] & 4) != 0, (data[offset] & 2) != 0);
  382. offset++;
  383. }
  384. // SOURCE-ADDRESS
  385. else if (type == AttributeType.SourceAddress)
  386. {
  387. SourceAddress = ParseEndPoint(data, ref offset);
  388. }
  389. // CHANGED-ADDRESS
  390. else if (type == AttributeType.ChangedAddress)
  391. {
  392. ChangedAddress = ParseEndPoint(data, ref offset);
  393. }
  394. // USERNAME
  395. else if (type == AttributeType.Username)
  396. {
  397. UserName = Encoding.Default.GetString(data, offset, length);
  398. offset += length;
  399. }
  400. // PASSWORD
  401. else if (type == AttributeType.Password)
  402. {
  403. Password = Encoding.Default.GetString(data, offset, length);
  404. offset += length;
  405. }
  406. // MESSAGE-INTEGRITY
  407. else if (type == AttributeType.MessageIntegrity)
  408. {
  409. offset += length;
  410. }
  411. // ERROR-CODE
  412. else if (type == AttributeType.ErrorCode)
  413. {
  414. /* 3489 11.2.9.
  415. 0 1 2 3
  416. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  417. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  418. | 0 |Class| Number |
  419. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  420. | Reason Phrase (variable) ..
  421. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  422. */
  423. var errorCode = (data[offset + 2] & 0x7) * 100 + (data[offset + 3] & 0xFF);
  424. ErrorCode = new StunErrorCode(errorCode, Encoding.Default.GetString(data, offset + 4, length - 4));
  425. offset += length;
  426. }
  427. // UNKNOWN-ATTRIBUTES
  428. else if (type == AttributeType.UnknownAttribute)
  429. {
  430. offset += length;
  431. }
  432. // REFLECTED-FROM
  433. else if (type == AttributeType.ReflectedFrom)
  434. {
  435. ReflectedFrom = ParseEndPoint(data, ref offset);
  436. }
  437. // XorMappedAddress
  438. // XorOnly
  439. // Software
  440. else if (type == AttributeType.Software)
  441. {
  442. ServerName = Encoding.Default.GetString(data, offset, length);
  443. offset += length;
  444. }
  445. // Unknown
  446. else
  447. {
  448. offset += length;
  449. }
  450. }
  451. #endregion
  452. #region method ParseEndPoint
  453. /// <summary>
  454. /// Parses IP endpoint attribute.
  455. /// </summary>
  456. /// <param name="data">STUN message data.</param>
  457. /// <param name="offset">Offset in data.</param>
  458. /// <returns>Returns parsed IP end point.</returns>
  459. private static IPEndPoint ParseEndPoint(IReadOnlyList<byte> data, ref int offset)
  460. {
  461. /*
  462. It consists of an eight bit address family, and a sixteen bit
  463. port, followed by a fixed length value representing the IP address.
  464. 0 1 2 3
  465. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  466. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  467. |x x x x x x x x| Family | Port |
  468. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  469. | Address |
  470. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  471. */
  472. // Skip family
  473. offset++;
  474. offset++;
  475. // Port
  476. var port = data[offset++] << 8 | data[offset++];
  477. // Address
  478. var ip = new byte[4];
  479. ip[0] = data[offset++];
  480. ip[1] = data[offset++];
  481. ip[2] = data[offset++];
  482. ip[3] = data[offset++];
  483. return new IPEndPoint(new IPAddress(ip), port);
  484. }
  485. #endregion
  486. #region method StoreEndPoint
  487. /// <summary>
  488. /// Stores ip end point attribute to buffer.
  489. /// </summary>
  490. /// <param name="type">Attribute type.</param>
  491. /// <param name="endPoint">IP end point.</param>
  492. /// <param name="message">Buffer where to store.</param>
  493. /// <param name="offset">Offset in buffer.</param>
  494. private static void StoreEndPoint(AttributeType type, IPEndPoint endPoint, IList<byte> message, ref int offset)
  495. {
  496. /*
  497. It consists of an eight bit address family, and a sixteen bit
  498. port, followed by a fixed length value representing the IP address.
  499. 0 1 2 3
  500. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  501. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  502. |x x x x x x x x| Family | Port |
  503. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  504. | Address |
  505. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  506. */
  507. // Header
  508. message[offset++] = (byte)((int)type >> 8);
  509. message[offset++] = (byte)((int)type & 0xFF);
  510. message[offset++] = 0;
  511. message[offset++] = 8;
  512. // Unused
  513. message[offset++] = 0;
  514. // Family
  515. message[offset++] = (byte)IpFamily.IPv4;
  516. // Port
  517. message[offset++] = (byte)(endPoint.Port >> 8);
  518. message[offset++] = (byte)(endPoint.Port & 0xFF);
  519. // Address
  520. var ipBytes = endPoint.Address.GetAddressBytes();
  521. message[offset++] = ipBytes[0];
  522. message[offset++] = ipBytes[1];
  523. message[offset++] = ipBytes[2];
  524. message[offset++] = ipBytes[3];
  525. }
  526. #endregion
  527. }
  528. }