forked from dotnet/runtime
/
IPv6AddressHelper.Common.cs
424 lines (386 loc) · 16.1 KB
/
IPv6AddressHelper.Common.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
namespace System
{
internal static partial class IPv6AddressHelper
{
private const int NumberOfLabels = 8;
// RFC 5952 Section 4.2.3
// Longest consecutive sequence of zero segments, minimum 2.
// On equal, first sequence wins. <-1, -1> for no compression.
internal static (int longestSequenceStart, int longestSequenceLength) FindCompressionRange(ReadOnlySpan<ushort> numbers)
{
int longestSequenceLength = 0, longestSequenceStart = -1, currentSequenceLength = 0;
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == 0)
{
currentSequenceLength++;
if (currentSequenceLength > longestSequenceLength)
{
longestSequenceLength = currentSequenceLength;
longestSequenceStart = i - currentSequenceLength + 1;
}
}
else
{
currentSequenceLength = 0;
}
}
return longestSequenceLength > 1 ?
(longestSequenceStart, longestSequenceStart + longestSequenceLength) :
(-1, -1);
}
// Returns true if the IPv6 address should be formatted with an embedded IPv4 address:
// ::192.168.1.1
internal static bool ShouldHaveIpv4Embedded(ReadOnlySpan<ushort> numbers)
{
// 0:0 : 0:0 : x:x : x.x.x.x
if (numbers[0] == 0 && numbers[1] == 0 && numbers[2] == 0 && numbers[3] == 0 && numbers[6] != 0)
{
// RFC 5952 Section 5 - 0:0 : 0:0 : 0:[0 | FFFF] : x.x.x.x
if (numbers[4] == 0 && (numbers[5] == 0 || numbers[5] == 0xFFFF))
{
return true;
}
// SIIT - 0:0 : 0:0 : FFFF:0 : x.x.x.x
else if (numbers[4] == 0xFFFF && numbers[5] == 0)
{
return true;
}
}
// ISATAP
return numbers[4] == 0 && numbers[5] == 0x5EFE;
}
//
// IsValidStrict
//
// Determine whether a name is a valid IPv6 address. Rules are:
//
// * 8 groups of 16-bit hex numbers, separated by ':'
// * a *single* run of zeros can be compressed using the symbol '::'
// * an optional string of a ScopeID delimited by '%'
// * the last 32 bits in an address can be represented as an IPv4 address
//
// Difference between IsValid() and IsValidStrict() is that IsValid() expects part of the string to
// be ipv6 address where as IsValidStrict() expects strict ipv6 address.
//
// Inputs:
// <argument> name
// IPv6 address in string format
//
// Outputs:
// Nothing
//
// Assumes:
// the correct name is terminated by ']' character
//
// Returns:
// true if <name> is IPv6 address, else false
//
// Throws:
// Nothing
//
// Remarks: MUST NOT be used unless all input indexes are verified and trusted.
// start must be next to '[' position, or error is reported
internal static unsafe bool IsValidStrict(char* name, int start, ref int end)
{
int sequenceCount = 0;
int sequenceLength = 0;
bool haveCompressor = false;
bool haveIPv4Address = false;
bool expectingNumber = true;
int lastSequence = 1;
bool needsClosingBracket = false;
if (start < end && name[start] == '[')
{
start++;
needsClosingBracket = true;
// IsValidStrict() is only called if there is a ':' in the name string, i.e.
// it is a possible IPv6 address. So, if the string starts with a '[' and
// the pointer is advanced here there are still more characters to parse.
Debug.Assert(start < end);
}
// Starting with a colon character is only valid if another colon follows.
if (name[start] == ':' && (start + 1 >= end || name[start + 1] != ':'))
{
return false;
}
int i;
for (i = start; i < end; ++i)
{
if (char.IsAsciiHexDigit(name[i]))
{
++sequenceLength;
expectingNumber = false;
}
else
{
if (sequenceLength > 4)
{
return false;
}
if (sequenceLength != 0)
{
++sequenceCount;
lastSequence = i - sequenceLength;
sequenceLength = 0;
}
switch (name[i])
{
case '%':
while (i + 1 < end)
{
i++;
if (name[i] == ']')
{
goto case ']';
}
else if (name[i] == '/')
{
goto case '/';
}
}
break;
case ']':
if (!needsClosingBracket)
{
return false;
}
needsClosingBracket = false;
// If there's more after the closing bracket, it must be a port.
// We don't use the port, but we still validate it.
if (i + 1 < end && name[i + 1] != ':')
{
return false;
}
// If there is a port, it must either be a hexadecimal or decimal number.
if (i + 3 < end && name[i + 2] == '0' && name[i + 3] == 'x')
{
i += 4;
for (; i < end; i++)
{
if (!char.IsAsciiHexDigit(name[i]))
{
return false;
}
}
}
else
{
i += 2;
for (; i < end; i++)
{
if (!char.IsAsciiDigit(name[i]))
{
return false;
}
}
}
continue;
case ':':
if ((i > 0) && (name[i - 1] == ':'))
{
if (haveCompressor)
{
// can only have one per IPv6 address
return false;
}
haveCompressor = true;
expectingNumber = false;
}
else
{
expectingNumber = true;
}
break;
case '/':
return false;
case '.':
if (haveIPv4Address)
{
return false;
}
i = end;
if (!IPv4AddressHelper.IsValid(name, lastSequence, ref i, true, false, false))
{
return false;
}
// ipv4 address takes 2 slots in ipv6 address, one was just counted meeting the '.'
++sequenceCount;
lastSequence = i - sequenceLength;
sequenceLength = 0;
haveIPv4Address = true;
--i; // it will be incremented back on the next loop
break;
default:
return false;
}
sequenceLength = 0;
}
}
if (sequenceLength != 0)
{
if (sequenceLength > 4)
{
return false;
}
++sequenceCount;
}
// these sequence counts are -1 because it is implied in end-of-sequence
const int ExpectedSequenceCount = 8;
return
!expectingNumber &&
(haveCompressor ? (sequenceCount < ExpectedSequenceCount) : (sequenceCount == ExpectedSequenceCount)) &&
!needsClosingBracket;
}
//
// Parse
//
// Convert this IPv6 address into a sequence of 8 16-bit numbers
//
// Inputs:
// <member> Name
// The validated IPv6 address
//
// Outputs:
// <member> numbers
// Array filled in with the numbers in the IPv6 groups
//
// <member> PrefixLength
// Set to the number after the prefix separator (/) if found
//
// Assumes:
// <Name> has been validated and contains only hex digits in groups of
// 16-bit numbers, the characters ':' and '/', and a possible IPv4
// address
//
// Throws:
// Nothing
//
internal static void Parse(ReadOnlySpan<char> address, Span<ushort> numbers, int start, ref string? scopeId)
{
int number = 0;
int index = 0;
int compressorIndex = -1;
bool numberIsValid = true;
//This used to be a class instance member but have not been used so far
int PrefixLength = 0;
if (address[start] == '[')
{
++start;
}
for (int i = start; i < address.Length && address[i] != ']';)
{
switch (address[i])
{
case '%':
if (numberIsValid)
{
numbers[index++] = (ushort)number;
numberIsValid = false;
}
start = i;
for (++i; i < address.Length && address[i] != ']' && address[i] != '/'; ++i)
{
}
scopeId = new string(address.Slice(start, i - start));
// ignore prefix if any
for (; i < address.Length && address[i] != ']'; ++i)
{
}
break;
case ':':
numbers[index++] = (ushort)number;
number = 0;
++i;
if (address[i] == ':')
{
compressorIndex = index;
++i;
}
else if ((compressorIndex < 0) && (index < 6))
{
// no point checking for IPv4 address if we don't
// have a compressor or we haven't seen 6 16-bit
// numbers yet
break;
}
// check to see if the upcoming number is really an IPv4
// address. If it is, convert it to 2 ushort numbers
for (int j = i; j < address.Length &&
(address[j] != ']') &&
(address[j] != ':') &&
(address[j] != '%') &&
(address[j] != '/') &&
(j < i + 4); ++j)
{
if (address[j] == '.')
{
// we have an IPv4 address. Find the end of it:
// we know that since we have a valid IPv6
// address, the only things that will terminate
// the IPv4 address are the prefix delimiter '/'
// or the end-of-string (which we conveniently
// delimited with ']')
while (j < address.Length && (address[j] != ']') && (address[j] != '/') && (address[j] != '%'))
{
++j;
}
number = IPv4AddressHelper.ParseHostNumber(address, i, j);
numbers[index++] = (ushort)(number >> 16);
numbers[index++] = (ushort)number;
i = j;
// set this to avoid adding another number to
// the array if there's a prefix
number = 0;
numberIsValid = false;
break;
}
}
break;
case '/':
if (numberIsValid)
{
numbers[index++] = (ushort)number;
numberIsValid = false;
}
// since we have a valid IPv6 address string, the prefix
// length is the last token in the string
for (++i; address[i] != ']'; ++i)
{
PrefixLength = PrefixLength * 10 + (address[i] - '0');
}
break;
default:
number = number * 16 + Uri.FromHex(address[i++]);
break;
}
}
// add number to the array if its not the prefix length or part of
// an IPv4 address that's already been handled
if (numberIsValid)
{
numbers[index++] = (ushort)number;
}
// if we had a compressor sequence ("::") then we need to expand the
// numbers array
if (compressorIndex > 0)
{
int toIndex = NumberOfLabels - 1;
int fromIndex = index - 1;
// if fromIndex and toIndex are the same, it means that "zero bits" are already in the correct place
// it happens for leading and trailing compression
if (fromIndex != toIndex)
{
for (int i = index - compressorIndex; i > 0; --i)
{
numbers[toIndex--] = numbers[fromIndex];
numbers[fromIndex--] = 0;
}
}
}
}
}
}