| | | 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 | | |
| | | 4 | | using System.Diagnostics; |
| | | 5 | | using System.Numerics; |
| | | 6 | | |
| | | 7 | | namespace System.Net |
| | | 8 | | { |
| | | 9 | | internal static partial class IPv6AddressHelper |
| | | 10 | | { |
| | | 11 | | private const int Hex = 16; |
| | | 12 | | private const int NumberOfLabels = 8; |
| | | 13 | | |
| | | 14 | | // RFC 5952 Section 4.2.3 |
| | | 15 | | // Longest consecutive sequence of zero segments, minimum 2. |
| | | 16 | | // On equal, first sequence wins. <-1, -1> for no compression. |
| | | 17 | | internal static (int longestSequenceStart, int longestSequenceLength) FindCompressionRange(ReadOnlySpan<ushort> |
| | 538 | 18 | | { |
| | 1614 | 19 | | int longestSequenceLength = 0, longestSequenceStart = -1, currentSequenceLength = 0; |
| | | 20 | | |
| | 9344 | 21 | | for (int i = 0; i < numbers.Length; i++) |
| | 4134 | 22 | | { |
| | 4134 | 23 | | if (numbers[i] == 0) |
| | 2645 | 24 | | { |
| | 2645 | 25 | | currentSequenceLength++; |
| | 2645 | 26 | | if (currentSequenceLength > longestSequenceLength) |
| | 2595 | 27 | | { |
| | 2595 | 28 | | longestSequenceLength = currentSequenceLength; |
| | 2595 | 29 | | longestSequenceStart = i - currentSequenceLength + 1; |
| | 2595 | 30 | | } |
| | 2645 | 31 | | } |
| | | 32 | | else |
| | 1489 | 33 | | { |
| | 1489 | 34 | | currentSequenceLength = 0; |
| | 1489 | 35 | | } |
| | 4134 | 36 | | } |
| | | 37 | | |
| | 538 | 38 | | return longestSequenceLength > 1 ? |
| | 538 | 39 | | (longestSequenceStart, longestSequenceStart + longestSequenceLength) : |
| | 538 | 40 | | (-1, 0); |
| | 538 | 41 | | } |
| | | 42 | | |
| | | 43 | | // Returns true if the IPv6 address should be formatted with an embedded IPv4 address: |
| | | 44 | | // ::192.168.1.1 |
| | | 45 | | internal static bool ShouldHaveIpv4Embedded(ReadOnlySpan<ushort> numbers) |
| | 538 | 46 | | { |
| | | 47 | | // 0:0 : 0:0 : x:x : x.x.x.x |
| | 538 | 48 | | if (numbers[0] == 0 && numbers[1] == 0 && numbers[2] == 0 && numbers[3] == 0 && numbers[6] != 0) |
| | 93 | 49 | | { |
| | | 50 | | // RFC 5952 Section 5 - 0:0 : 0:0 : 0:[0 | FFFF] : x.x.x.x |
| | 93 | 51 | | if (numbers[4] == 0 && (numbers[5] == 0 || numbers[5] == 0xFFFF)) |
| | 85 | 52 | | { |
| | 85 | 53 | | return true; |
| | | 54 | | } |
| | | 55 | | // SIIT - 0:0 : 0:0 : FFFF:0 : x.x.x.x |
| | 8 | 56 | | else if (numbers[4] == 0xFFFF && numbers[5] == 0) |
| | 0 | 57 | | { |
| | 0 | 58 | | return true; |
| | | 59 | | } |
| | 8 | 60 | | } |
| | | 61 | | |
| | | 62 | | // ISATAP |
| | 453 | 63 | | return numbers[4] == 0 && numbers[5] == 0x5EFE; |
| | 538 | 64 | | } |
| | | 65 | | |
| | | 66 | | // |
| | | 67 | | // IsValidStrict |
| | | 68 | | // |
| | | 69 | | // Determine whether a name is a valid IPv6 address. Rules are: |
| | | 70 | | // |
| | | 71 | | // * 8 groups of 16-bit hex numbers, separated by ':' |
| | | 72 | | // * a *single* run of zeros can be compressed using the symbol '::' |
| | | 73 | | // * an optional string of a ScopeID delimited by '%' |
| | | 74 | | // * the last 32 bits in an address can be represented as an IPv4 address |
| | | 75 | | // |
| | | 76 | | // Difference between IsValid() and IsValidStrict() is that IsValid() expects part of the string to |
| | | 77 | | // be ipv6 address where as IsValidStrict() expects strict ipv6 address. |
| | | 78 | | // |
| | | 79 | | // Inputs: |
| | | 80 | | // <argument> name |
| | | 81 | | // IPv6 address in string format |
| | | 82 | | // |
| | | 83 | | // Outputs: |
| | | 84 | | // Nothing |
| | | 85 | | // |
| | | 86 | | // Assumes: |
| | | 87 | | // the correct name is terminated by ']' character |
| | | 88 | | // |
| | | 89 | | // Returns: |
| | | 90 | | // true if <name> is IPv6 address, else false |
| | | 91 | | // |
| | | 92 | | // Throws: |
| | | 93 | | // Nothing |
| | | 94 | | // |
| | | 95 | | |
| | | 96 | | // Remarks: MUST NOT be used unless all input indexes are verified and trusted. |
| | | 97 | | // start must be next to '[' position, or error is reported |
| | | 98 | | internal static bool IsValidStrict<TChar>(ReadOnlySpan<TChar> name) |
| | | 99 | | where TChar : unmanaged, IBinaryInteger<TChar> |
| | 4240 | 100 | | { |
| | 4240 | 101 | | Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); |
| | | 102 | | |
| | | 103 | | // Number of components in this IPv6 address |
| | 4240 | 104 | | int sequenceCount = 0; |
| | | 105 | | // Length of the component currently being constructed |
| | 4240 | 106 | | int sequenceLength = 0; |
| | 4240 | 107 | | bool haveCompressor = false; |
| | 4240 | 108 | | bool haveIPv4Address = false; |
| | 4240 | 109 | | bool expectingNumber = true; |
| | | 110 | | // Start position of the previous component |
| | 4240 | 111 | | int lastSequence = 1; |
| | 4240 | 112 | | int start = 0; |
| | 4240 | 113 | | int end = name.Length; |
| | | 114 | | |
| | 4240 | 115 | | bool needsClosingBracket = false; |
| | | 116 | | |
| | | 117 | | // An IPv6 address may begin with a start character ('['). If it does, it must end with an end |
| | | 118 | | // character (']'). |
| | 4240 | 119 | | if (start < end && name[start] == TChar.CreateTruncating('[')) |
| | 804 | 120 | | { |
| | 804 | 121 | | start++; |
| | 804 | 122 | | needsClosingBracket = true; |
| | | 123 | | |
| | | 124 | | // IsValidStrict() is only called if there is a ':' in the name string, i.e. |
| | | 125 | | // it is a possible IPv6 address. So, if the string starts with a '[' and |
| | | 126 | | // the pointer is advanced here there are still more characters to parse. |
| | 804 | 127 | | Debug.Assert(start < end); |
| | 804 | 128 | | } |
| | | 129 | | |
| | | 130 | | // Starting with a colon character is only valid if another colon follows. |
| | 4240 | 131 | | if (name[start] == TChar.CreateTruncating(':') && (start + 1 >= end || name[start + 1] != TChar.CreateTrunca |
| | 26 | 132 | | { |
| | 26 | 133 | | return false; |
| | | 134 | | } |
| | | 135 | | |
| | | 136 | | int i; |
| | 68322 | 137 | | for (i = start; i < end; ++i) |
| | 31297 | 138 | | { |
| | 31297 | 139 | | int currentCh = IPv4AddressHelper.ToUShort(name[i]); |
| | | 140 | | |
| | 31297 | 141 | | if (HexConverter.IsHexChar(currentCh)) |
| | 18322 | 142 | | { |
| | 18322 | 143 | | ++sequenceLength; |
| | 18322 | 144 | | expectingNumber = false; |
| | 18322 | 145 | | } |
| | | 146 | | else |
| | 12975 | 147 | | { |
| | 12975 | 148 | | if (sequenceLength > 4) |
| | 16 | 149 | | { |
| | 16 | 150 | | return false; |
| | | 151 | | } |
| | 12959 | 152 | | if (sequenceLength != 0) |
| | 7957 | 153 | | { |
| | 7957 | 154 | | ++sequenceCount; |
| | 7957 | 155 | | lastSequence = i - sequenceLength; |
| | 7957 | 156 | | sequenceLength = 0; |
| | 7957 | 157 | | } |
| | | 158 | | |
| | 12959 | 159 | | switch (currentCh) |
| | | 160 | | { |
| | | 161 | | case '%': |
| | | 162 | | // An IPv6 address is separated from its scope by a '%' character. The scope |
| | | 163 | | // is terminated by the natural end of the address, the address end character (']') |
| | | 164 | | // or the start of the prefix ('/'). |
| | 19077 | 165 | | while (i + 1 < end) |
| | 18497 | 166 | | { |
| | 18497 | 167 | | i++; |
| | 18497 | 168 | | if (name[i] == TChar.CreateTruncating(']')) |
| | 426 | 169 | | { |
| | 426 | 170 | | goto case ']'; |
| | | 171 | | } |
| | 18071 | 172 | | else if (name[i] == TChar.CreateTruncating('/')) |
| | 66 | 173 | | { |
| | 66 | 174 | | goto case '/'; |
| | | 175 | | } |
| | 18005 | 176 | | } |
| | 580 | 177 | | break; |
| | | 178 | | |
| | | 179 | | case ']': |
| | 776 | 180 | | if (!needsClosingBracket) |
| | 12 | 181 | | { |
| | 12 | 182 | | return false; |
| | | 183 | | } |
| | 764 | 184 | | needsClosingBracket = false; |
| | | 185 | | |
| | | 186 | | // If there's more after the closing bracket, it must be a port. |
| | | 187 | | // We don't use the port, but we still validate it. |
| | 764 | 188 | | if (i + 1 < end && name[i + 1] != TChar.CreateTruncating(':')) |
| | 6 | 189 | | { |
| | 6 | 190 | | return false; |
| | | 191 | | } |
| | | 192 | | |
| | | 193 | | // If there is a port, it must either be a hexadecimal or decimal number. |
| | | 194 | | // If the next two characters are '0x' then it's a hexadecimal number. Skip the prefix. |
| | 758 | 195 | | if (i + 3 < end && name[i + 2] == TChar.CreateTruncating('0') && name[i + 3] == TChar.Create |
| | 210 | 196 | | { |
| | 210 | 197 | | i += 4; |
| | 4578 | 198 | | for (; i < end; i++) |
| | 2276 | 199 | | { |
| | 2276 | 200 | | int ch = IPv4AddressHelper.ToUShort(name[i]); |
| | | 201 | | |
| | 2276 | 202 | | if (!HexConverter.IsHexChar(ch)) |
| | 92 | 203 | | { |
| | 92 | 204 | | return false; |
| | | 205 | | } |
| | 2184 | 206 | | } |
| | 118 | 207 | | } |
| | | 208 | | else |
| | 548 | 209 | | { |
| | 548 | 210 | | i += 2; |
| | 9628 | 211 | | for (; i < end; i++) |
| | 4718 | 212 | | { |
| | 4718 | 213 | | if (!char.IsAsciiDigit((char)IPv4AddressHelper.ToUShort(name[i]))) |
| | 178 | 214 | | { |
| | 178 | 215 | | return false; |
| | | 216 | | } |
| | 4540 | 217 | | } |
| | 370 | 218 | | } |
| | 488 | 219 | | continue; |
| | | 220 | | |
| | | 221 | | case ':': |
| | | 222 | | // If the next character after a colon is another colon, the address contains a compressor ( |
| | 10052 | 223 | | if ((i > 0) && (name[i - 1] == TChar.CreateTruncating(':'))) |
| | 2348 | 224 | | { |
| | 2348 | 225 | | if (haveCompressor) |
| | 4 | 226 | | { |
| | | 227 | | // can only have one per IPv6 address |
| | 4 | 228 | | return false; |
| | | 229 | | } |
| | 2344 | 230 | | haveCompressor = true; |
| | 2344 | 231 | | expectingNumber = false; |
| | 2344 | 232 | | } |
| | | 233 | | else |
| | 7704 | 234 | | { |
| | 7704 | 235 | | expectingNumber = true; |
| | 7704 | 236 | | } |
| | 10048 | 237 | | break; |
| | | 238 | | |
| | | 239 | | case '/': |
| | | 240 | | // A prefix in an IPv6 address is invalid. |
| | 74 | 241 | | return false; |
| | | 242 | | |
| | | 243 | | case '.': |
| | 1291 | 244 | | if (haveIPv4Address) |
| | 0 | 245 | | { |
| | 0 | 246 | | return false; |
| | | 247 | | } |
| | | 248 | | |
| | 1291 | 249 | | if (!IPv4AddressHelper.IsValid(name.Slice(lastSequence, end - lastSequence), out int seqEnd, |
| | 782 | 250 | | { |
| | 782 | 251 | | return false; |
| | | 252 | | } |
| | 509 | 253 | | i = lastSequence + seqEnd; |
| | | 254 | | |
| | | 255 | | // An IPv4 address takes 2 slots in an IPv6 address. One was just counted meeting the '.' |
| | 509 | 256 | | ++sequenceCount; |
| | 509 | 257 | | lastSequence = i - sequenceLength; |
| | 509 | 258 | | haveIPv4Address = true; |
| | 509 | 259 | | --i; // it will be incremented back on the next loop |
| | 509 | 260 | | break; |
| | | 261 | | |
| | | 262 | | default: |
| | 186 | 263 | | return false; |
| | | 264 | | } |
| | 11137 | 265 | | sequenceLength = 0; |
| | 11137 | 266 | | } |
| | 29459 | 267 | | } |
| | | 268 | | |
| | 2864 | 269 | | if (sequenceLength != 0) |
| | 1152 | 270 | | { |
| | 1152 | 271 | | if (sequenceLength > 4) |
| | 6 | 272 | | { |
| | 6 | 273 | | return false; |
| | | 274 | | } |
| | | 275 | | |
| | 1146 | 276 | | ++sequenceCount; |
| | 1146 | 277 | | } |
| | | 278 | | |
| | | 279 | | // These sequence counts are -1 because it is implied in end-of-sequence. |
| | | 280 | | |
| | | 281 | | const int ExpectedSequenceCount = 8; |
| | 2858 | 282 | | return |
| | 2858 | 283 | | !expectingNumber && |
| | 2858 | 284 | | (haveCompressor ? (sequenceCount < ExpectedSequenceCount) : (sequenceCount == ExpectedSequenceCount)) && |
| | 2858 | 285 | | !needsClosingBracket; |
| | 4240 | 286 | | } |
| | | 287 | | |
| | | 288 | | // |
| | | 289 | | // Parse |
| | | 290 | | // |
| | | 291 | | // Convert this IPv6 address into a sequence of 8 16-bit numbers |
| | | 292 | | // |
| | | 293 | | // Inputs: |
| | | 294 | | // <member> Name |
| | | 295 | | // The validated IPv6 address |
| | | 296 | | // |
| | | 297 | | // Outputs: |
| | | 298 | | // <member> numbers |
| | | 299 | | // Array filled in with the numbers in the IPv6 groups |
| | | 300 | | // |
| | | 301 | | // <member> scopeId |
| | | 302 | | // Set to the text after the scope separator (%) if found |
| | | 303 | | // |
| | | 304 | | // Assumes: |
| | | 305 | | // <Name> has been validated and contains only hex digits in groups of |
| | | 306 | | // 16-bit numbers, the characters ':', '/' and '%', and a possible IPv4 |
| | | 307 | | // address |
| | | 308 | | // |
| | | 309 | | // Throws: |
| | | 310 | | // Nothing |
| | | 311 | | // |
| | | 312 | | |
| | | 313 | | internal static void Parse<TChar>(ReadOnlySpan<TChar> address, scoped Span<ushort> numbers, out ReadOnlySpan<TCh |
| | | 314 | | where TChar : unmanaged, IBinaryInteger<TChar> |
| | 1076 | 315 | | { |
| | 1076 | 316 | | Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); |
| | | 317 | | |
| | 1076 | 318 | | int number = 0; |
| | | 319 | | int currentCh; |
| | 1076 | 320 | | int index = 0; |
| | 1076 | 321 | | int compressorIndex = -1; |
| | 1076 | 322 | | bool numberIsValid = true; |
| | | 323 | | |
| | 1076 | 324 | | scopeId = ReadOnlySpan<TChar>.Empty; |
| | | 325 | | |
| | | 326 | | // Skip the start '[' character, if present. Stop parsing at the end IPv6 address terminator (']'). |
| | 11804 | 327 | | for (int i = (address[0] == TChar.CreateTruncating('[') ? 1 : 0); i < address.Length && address[i] != TChar. |
| | 9652 | 328 | | { |
| | 9652 | 329 | | currentCh = IPv4AddressHelper.ToUShort(address[i]); |
| | | 330 | | |
| | 9652 | 331 | | switch (currentCh) |
| | | 332 | | { |
| | | 333 | | case '%': |
| | 382 | 334 | | if (numberIsValid) |
| | 332 | 335 | | { |
| | 332 | 336 | | numbers[index++] = (ushort)number; |
| | 332 | 337 | | numberIsValid = false; |
| | 332 | 338 | | } |
| | | 339 | | |
| | | 340 | | // The scope follows a '%' and terminates at the natural end of the address, the address termina |
| | 382 | 341 | | int scopeStart = i; |
| | | 342 | | |
| | 18030 | 343 | | for (++i; i < address.Length && address[i] != TChar.CreateTruncating(']') && address[i] != TChar |
| | 8633 | 344 | | { |
| | 8633 | 345 | | } |
| | 382 | 346 | | scopeId = address.Slice(scopeStart, i - scopeStart); |
| | | 347 | | |
| | | 348 | | // Ignore the prefix (if any.) |
| | 382 | 349 | | for (; i < address.Length && address[i] != TChar.CreateTruncating(']'); ++i) |
| | 0 | 350 | | { |
| | 0 | 351 | | } |
| | 382 | 352 | | break; |
| | | 353 | | |
| | | 354 | | case ':': |
| | 3050 | 355 | | numbers[index++] = (ushort)number; |
| | 3050 | 356 | | number = 0; |
| | | 357 | | // Two sequential colons form a compressor ('::'). |
| | 3050 | 358 | | ++i; |
| | 3050 | 359 | | if (address[i] == TChar.CreateTruncating(':')) |
| | 942 | 360 | | { |
| | 942 | 361 | | compressorIndex = index; |
| | 942 | 362 | | ++i; |
| | 942 | 363 | | } |
| | 2108 | 364 | | else if ((compressorIndex < 0) && (index < 6)) |
| | 1224 | 365 | | { |
| | | 366 | | // No point checking for IPv4 address if we don't |
| | | 367 | | // have a compressor or we haven't seen 6 16-bit |
| | | 368 | | // numbers yet. |
| | 1224 | 369 | | break; |
| | | 370 | | } |
| | | 371 | | |
| | | 372 | | // Check to see if the upcoming number is really an IPv4 |
| | | 373 | | // address. If it is, convert it to 2 ushort numbers |
| | 6444 | 374 | | for (int j = i; j < address.Length && |
| | 4618 | 375 | | (address[j] != TChar.CreateTruncating(']')) && |
| | 4618 | 376 | | (address[j] != TChar.CreateTruncating(':')) && |
| | 4618 | 377 | | (address[j] != TChar.CreateTruncating('%')) && |
| | 4618 | 378 | | (address[j] != TChar.CreateTruncating('/')) && |
| | 7410 | 379 | | (j < i + 4); ++j) |
| | 2994 | 380 | | { |
| | | 381 | | |
| | 2994 | 382 | | if (address[j] == TChar.CreateTruncating('.')) |
| | 202 | 383 | | { |
| | | 384 | | // We have an IPv4 address. Find the end of it: |
| | | 385 | | // we know that since we have a valid IPv6 |
| | | 386 | | // address, the only things that will terminate |
| | | 387 | | // the IPv4 address are the prefix delimiter '/' |
| | | 388 | | // or the end-of-string (which we conveniently |
| | | 389 | | // delimited with ']'). |
| | 1560 | 390 | | while (j < address.Length && (address[j] != TChar.CreateTruncating(']')) && (address[j] |
| | 1358 | 391 | | { |
| | 1358 | 392 | | ++j; |
| | 1358 | 393 | | } |
| | 202 | 394 | | int ipv4Address = IPv4AddressHelper.ParseHostNumber(address, i, j); |
| | | 395 | | |
| | 202 | 396 | | numbers[index++] = (ushort)(ipv4Address >> 16); |
| | 202 | 397 | | numbers[index++] = (ushort)(ipv4Address & 0xFFFF); |
| | 202 | 398 | | i = j; |
| | | 399 | | |
| | | 400 | | // Set this to avoid adding another number to |
| | | 401 | | // the array if there's a prefix |
| | 202 | 402 | | number = 0; |
| | 202 | 403 | | numberIsValid = false; |
| | 202 | 404 | | break; |
| | | 405 | | } |
| | 2792 | 406 | | } |
| | 1826 | 407 | | break; |
| | | 408 | | |
| | | 409 | | case '/': |
| | 0 | 410 | | if (numberIsValid) |
| | 0 | 411 | | { |
| | 0 | 412 | | numbers[index++] = (ushort)number; |
| | 0 | 413 | | numberIsValid = false; |
| | 0 | 414 | | } |
| | | 415 | | |
| | 0 | 416 | | for (++i; i < address.Length && address[i] != TChar.CreateTruncating(']'); i++) |
| | 0 | 417 | | { |
| | 0 | 418 | | } |
| | | 419 | | |
| | 0 | 420 | | break; |
| | | 421 | | |
| | | 422 | | default: |
| | 6220 | 423 | | int characterValue = HexConverter.FromChar(currentCh); |
| | | 424 | | |
| | 6220 | 425 | | number = number * IPv6AddressHelper.Hex + characterValue; |
| | 6220 | 426 | | i++; |
| | 6220 | 427 | | break; |
| | | 428 | | } |
| | 9652 | 429 | | } |
| | | 430 | | |
| | | 431 | | // Add number to the array if it's not the prefix length or part of |
| | | 432 | | // an IPv4 address that's already been handled |
| | 1076 | 433 | | if (numberIsValid) |
| | 542 | 434 | | { |
| | 542 | 435 | | numbers[index++] = (ushort)number; |
| | 542 | 436 | | } |
| | | 437 | | |
| | | 438 | | // If we had a compressor sequence ("::") then we need to expand the |
| | | 439 | | // numbers array. |
| | 1076 | 440 | | if (compressorIndex > 0) |
| | 942 | 441 | | { |
| | 942 | 442 | | int toIndex = NumberOfLabels - 1; |
| | 942 | 443 | | int fromIndex = index - 1; |
| | | 444 | | |
| | | 445 | | // If fromIndex and toIndex are the same, it means that "zero bits" are already in the correct place. |
| | | 446 | | // This happens for leading and trailing compression. |
| | 942 | 447 | | if (fromIndex != toIndex) |
| | 890 | 448 | | { |
| | 4752 | 449 | | for (int i = index - compressorIndex; i > 0; --i) |
| | 1486 | 450 | | { |
| | 1486 | 451 | | numbers[toIndex--] = numbers[fromIndex]; |
| | 1486 | 452 | | numbers[fromIndex--] = 0; |
| | 1486 | 453 | | } |
| | 890 | 454 | | } |
| | 942 | 455 | | } |
| | 1076 | 456 | | } |
| | | 457 | | } |
| | | 458 | | } |
| | | 459 | | |