IPAddressParser.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Net.Sockets;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. namespace System.Net
  10. {
  11. internal class IPAddressParser
  12. {
  13. private const int MaxIPv4StringLength = 15; // 4 numbers separated by 3 periods, with up to 3 digits per number
  14. internal static unsafe IPAddress Parse(ReadOnlySpan<char> ipSpan, bool tryParse)
  15. {
  16. if (ipSpan.Contains(':'))
  17. {
  18. // The address is parsed as IPv6 if and only if it contains a colon. This is valid because
  19. // we don't support/parse a port specification at the end of an IPv4 address.
  20. ushort* numbers = stackalloc ushort[IPAddressParserStatics.IPv6AddressShorts];
  21. new Span<ushort>(numbers, IPAddressParserStatics.IPv6AddressShorts).Clear();
  22. if (Ipv6StringToAddress(ipSpan, numbers, IPAddressParserStatics.IPv6AddressShorts, out uint scope))
  23. {
  24. return new IPAddress(numbers, IPAddressParserStatics.IPv6AddressShorts, scope);
  25. }
  26. }
  27. else if (Ipv4StringToAddress(ipSpan, out long address))
  28. {
  29. return new IPAddress(address);
  30. }
  31. if (tryParse)
  32. {
  33. return null;
  34. }
  35. throw new FormatException(SR.dns_bad_ip_address, new SocketException(SocketError.InvalidArgument));
  36. }
  37. internal static unsafe string IPv4AddressToString(uint address)
  38. {
  39. char* addressString = stackalloc char[MaxIPv4StringLength];
  40. int charsWritten = IPv4AddressToStringHelper(address, addressString);
  41. return new string(addressString, 0, charsWritten);
  42. }
  43. internal static unsafe void IPv4AddressToString(uint address, StringBuilder destination)
  44. {
  45. char* addressString = stackalloc char[MaxIPv4StringLength];
  46. int charsWritten = IPv4AddressToStringHelper(address, addressString);
  47. destination.Append(addressString, charsWritten);
  48. }
  49. internal static unsafe bool IPv4AddressToString(uint address, Span<char> formatted, out int charsWritten)
  50. {
  51. if (formatted.Length < MaxIPv4StringLength)
  52. {
  53. charsWritten = 0;
  54. return false;
  55. }
  56. fixed (char* formattedPtr = &MemoryMarshal.GetReference(formatted))
  57. {
  58. charsWritten = IPv4AddressToStringHelper(address, formattedPtr);
  59. }
  60. return true;
  61. }
  62. private static unsafe int IPv4AddressToStringHelper(uint address, char* addressString)
  63. {
  64. int offset = 0;
  65. FormatIPv4AddressNumber((int)(address & 0xFF), addressString, ref offset);
  66. addressString[offset++] = '.';
  67. FormatIPv4AddressNumber((int)((address >> 8) & 0xFF), addressString, ref offset);
  68. addressString[offset++] = '.';
  69. FormatIPv4AddressNumber((int)((address >> 16) & 0xFF), addressString, ref offset);
  70. addressString[offset++] = '.';
  71. FormatIPv4AddressNumber((int)((address >> 24) & 0xFF), addressString, ref offset);
  72. return offset;
  73. }
  74. internal static string IPv6AddressToString(ushort[] address, uint scopeId)
  75. {
  76. Debug.Assert(address != null);
  77. Debug.Assert(address.Length == IPAddressParserStatics.IPv6AddressShorts);
  78. StringBuilder buffer = IPv6AddressToStringHelper(address, scopeId);
  79. return StringBuilderCache.GetStringAndRelease(buffer);
  80. }
  81. internal static bool IPv6AddressToString(ushort[] address, uint scopeId, Span<char> destination, out int charsWritten)
  82. {
  83. Debug.Assert(address != null);
  84. Debug.Assert(address.Length == IPAddressParserStatics.IPv6AddressShorts);
  85. StringBuilder buffer = IPv6AddressToStringHelper(address, scopeId);
  86. if (destination.Length < buffer.Length)
  87. {
  88. StringBuilderCache.Release(buffer);
  89. charsWritten = 0;
  90. return false;
  91. }
  92. buffer.CopyTo(0, destination, buffer.Length);
  93. charsWritten = buffer.Length;
  94. StringBuilderCache.Release(buffer);
  95. return true;
  96. }
  97. internal static StringBuilder IPv6AddressToStringHelper(ushort[] address, uint scopeId)
  98. {
  99. const int INET6_ADDRSTRLEN = 65;
  100. StringBuilder buffer = StringBuilderCache.Acquire(INET6_ADDRSTRLEN);
  101. if (IPv6AddressHelper.ShouldHaveIpv4Embedded(address))
  102. {
  103. // We need to treat the last 2 ushorts as a 4-byte IPv4 address,
  104. // so output the first 6 ushorts normally, followed by the IPv4 address.
  105. AppendSections(address, 0, 6, buffer);
  106. if (buffer[buffer.Length - 1] != ':')
  107. {
  108. buffer.Append(':');
  109. }
  110. IPv4AddressToString(ExtractIPv4Address(address), buffer);
  111. }
  112. else
  113. {
  114. // No IPv4 address. Output all 8 sections as part of the IPv6 address
  115. // with normal formatting rules.
  116. AppendSections(address, 0, 8, buffer);
  117. }
  118. // If there's a scope ID, append it.
  119. if (scopeId != 0)
  120. {
  121. buffer.Append('%').Append(scopeId);
  122. }
  123. return buffer;
  124. }
  125. private static unsafe void FormatIPv4AddressNumber(int number, char* addressString, ref int offset)
  126. {
  127. // Math.DivRem has no overload for byte, assert here for safety
  128. Debug.Assert(number < 256);
  129. offset += number > 99 ? 3 : number > 9 ? 2 : 1;
  130. int i = offset;
  131. do
  132. {
  133. number = Math.DivRem(number, 10, out int rem);
  134. addressString[--i] = (char)('0' + rem);
  135. } while (number != 0);
  136. }
  137. public static unsafe bool Ipv4StringToAddress(ReadOnlySpan<char> ipSpan, out long address)
  138. {
  139. int end = ipSpan.Length;
  140. long tmpAddr;
  141. fixed (char* ipStringPtr = &MemoryMarshal.GetReference(ipSpan))
  142. {
  143. tmpAddr = IPv4AddressHelper.ParseNonCanonical(ipStringPtr, 0, ref end, notImplicitFile: true);
  144. }
  145. if (tmpAddr != IPv4AddressHelper.Invalid && end == ipSpan.Length)
  146. {
  147. // IPv4AddressHelper.ParseNonCanonical returns the bytes in the inverse order.
  148. // Reverse them and return success.
  149. address =
  150. ((0xFF000000 & tmpAddr) >> 24) |
  151. ((0x00FF0000 & tmpAddr) >> 8) |
  152. ((0x0000FF00 & tmpAddr) << 8) |
  153. ((0x000000FF & tmpAddr) << 24);
  154. return true;
  155. }
  156. else
  157. {
  158. // Failed to parse the address.
  159. address = 0;
  160. return false;
  161. }
  162. }
  163. public static unsafe bool Ipv6StringToAddress(ReadOnlySpan<char> ipSpan, ushort* numbers, int numbersLength, out uint scope)
  164. {
  165. Debug.Assert(numbers != null);
  166. Debug.Assert(numbersLength >= IPAddressParserStatics.IPv6AddressShorts);
  167. int end = ipSpan.Length;
  168. bool isValid = false;
  169. fixed (char* ipStringPtr = &MemoryMarshal.GetReference(ipSpan))
  170. {
  171. isValid = IPv6AddressHelper.IsValidStrict(ipStringPtr, 0, ref end);
  172. }
  173. if (isValid || (end != ipSpan.Length))
  174. {
  175. string scopeId = null;
  176. IPv6AddressHelper.Parse(ipSpan, numbers, 0, ref scopeId);
  177. long result = 0;
  178. if (!string.IsNullOrEmpty(scopeId))
  179. {
  180. if (scopeId.Length < 2)
  181. {
  182. scope = 0;
  183. return false;
  184. }
  185. for (int i = 1; i < scopeId.Length; i++)
  186. {
  187. char c = scopeId[i];
  188. if (c < '0' || c > '9')
  189. {
  190. scope = 0;
  191. #if MONO // zoneId can be a string, see https://github.com/dotnet/corefx/issues/27529
  192. return true;
  193. #else
  194. return false;
  195. #endif
  196. }
  197. result = (result * 10) + (c - '0');
  198. if (result > uint.MaxValue)
  199. {
  200. scope = 0;
  201. return false;
  202. }
  203. }
  204. }
  205. scope = (uint)result;
  206. return true;
  207. }
  208. scope = 0;
  209. return false;
  210. }
  211. /// <summary>
  212. /// Appends each of the numbers in address in indexed range [fromInclusive, toExclusive),
  213. /// while also replacing the longest sequence of 0s found in that range with "::", as long
  214. /// as the sequence is more than one 0.
  215. /// </summary>
  216. private static void AppendSections(ushort[] address, int fromInclusive, int toExclusive, StringBuilder buffer)
  217. {
  218. // Find the longest sequence of zeros to be combined into a "::"
  219. ReadOnlySpan<ushort> addressSpan = new ReadOnlySpan<ushort>(address, fromInclusive, toExclusive - fromInclusive);
  220. (int zeroStart, int zeroEnd) = IPv6AddressHelper.FindCompressionRange(addressSpan);
  221. bool needsColon = false;
  222. // Output all of the numbers before the zero sequence
  223. for (int i = fromInclusive; i < zeroStart; i++)
  224. {
  225. if (needsColon)
  226. {
  227. buffer.Append(':');
  228. }
  229. needsColon = true;
  230. AppendHex(address[i], buffer);
  231. }
  232. // Output the zero sequence if there is one
  233. if (zeroStart >= 0)
  234. {
  235. buffer.Append("::");
  236. needsColon = false;
  237. fromInclusive = zeroEnd;
  238. }
  239. // Output everything after the zero sequence
  240. for (int i = fromInclusive; i < toExclusive; i++)
  241. {
  242. if (needsColon)
  243. {
  244. buffer.Append(':');
  245. }
  246. needsColon = true;
  247. AppendHex(address[i], buffer);
  248. }
  249. }
  250. /// <summary>Appends a number as hexadecimal (without the leading "0x") to the StringBuilder.</summary>
  251. private static unsafe void AppendHex(ushort value, StringBuilder buffer)
  252. {
  253. const int MaxLength = sizeof(ushort) * 2; // two hex chars per byte
  254. char* chars = stackalloc char[MaxLength];
  255. int pos = MaxLength;
  256. do
  257. {
  258. int rem = value % 16;
  259. value /= 16;
  260. chars[--pos] = rem < 10 ? (char)('0' + rem) : (char)('a' + (rem - 10));
  261. Debug.Assert(pos >= 0);
  262. }
  263. while (value != 0);
  264. buffer.Append(chars + pos, MaxLength - pos);
  265. }
  266. /// <summary>Extracts the IPv4 address from the end of the IPv6 address byte array.</summary>
  267. private static uint ExtractIPv4Address(ushort[] address) => (uint)(Reverse(address[7]) << 16) | Reverse(address[6]);
  268. /// <summary>Reverses the two bytes in the ushort.</summary>
  269. private static ushort Reverse(ushort number) => (ushort)(((number >> 8) & 0xFF) | ((number << 8) & 0xFF00));
  270. }
  271. }