372 lines
12 KiB
C#
372 lines
12 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Globalization;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
|
|
namespace Microsoft.AspNetCore.HttpSys.Internal
|
|
{
|
|
// a little perf app measured these times when comparing the internal
|
|
// buffer implemented as a managed byte[] or unmanaged memory IntPtr
|
|
// that's why we use byte[]
|
|
// byte[] total ms:19656
|
|
// IntPtr total ms:25671
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// This class is used when subclassing EndPoint, and provides indication
|
|
/// on how to format the memory buffers that winsock uses for network addresses.
|
|
/// </para>
|
|
/// </devdoc>
|
|
internal class SocketAddress
|
|
{
|
|
private const int NumberOfIPv6Labels = 8;
|
|
// Lower case hex, no leading zeros
|
|
private const string IPv6NumberFormat = "{0:x}";
|
|
private const string IPv6StringSeparator = ":";
|
|
private const string IPv4StringFormat = "{0:d}.{1:d}.{2:d}.{3:d}";
|
|
|
|
internal const int IPv6AddressSize = 28;
|
|
internal const int IPv4AddressSize = 16;
|
|
|
|
private const int WriteableOffset = 2;
|
|
|
|
private int _size;
|
|
private byte[] _buffer;
|
|
private int _hash;
|
|
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public SocketAddress(AddressFamily family, int size)
|
|
{
|
|
if (size < WriteableOffset)
|
|
{
|
|
// it doesn't make sense to create a socket address with less tha
|
|
// 2 bytes, that's where we store the address family.
|
|
|
|
throw new ArgumentOutOfRangeException("size");
|
|
}
|
|
_size = size;
|
|
_buffer = new byte[((size / IntPtr.Size) + 2) * IntPtr.Size]; // sizeof DWORD
|
|
|
|
#if BIGENDIAN
|
|
m_Buffer[0] = unchecked((byte)((int)family>>8));
|
|
m_Buffer[1] = unchecked((byte)((int)family ));
|
|
#else
|
|
_buffer[0] = unchecked((byte)((int)family));
|
|
_buffer[1] = unchecked((byte)((int)family >> 8));
|
|
#endif
|
|
}
|
|
|
|
internal byte[] Buffer
|
|
{
|
|
get { return _buffer; }
|
|
}
|
|
|
|
internal AddressFamily Family
|
|
{
|
|
get
|
|
{
|
|
int family;
|
|
#if BIGENDIAN
|
|
family = ((int)m_Buffer[0]<<8) | m_Buffer[1];
|
|
#else
|
|
family = _buffer[0] | ((int)_buffer[1] << 8);
|
|
#endif
|
|
return (AddressFamily)family;
|
|
}
|
|
}
|
|
|
|
internal int Size
|
|
{
|
|
get
|
|
{
|
|
return _size;
|
|
}
|
|
}
|
|
|
|
// access to unmanaged serialized data. this doesn't
|
|
// allow access to the first 2 bytes of unmanaged memory
|
|
// that are supposed to contain the address family which
|
|
// is readonly.
|
|
//
|
|
// <SECREVIEW> you can still use negative offsets as a back door in case
|
|
// winsock changes the way it uses SOCKADDR. maybe we want to prohibit it?
|
|
// maybe we should make the class sealed to avoid potentially dangerous calls
|
|
// into winsock with unproperly formatted data? </SECREVIEW>
|
|
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
private byte this[int offset]
|
|
{
|
|
get
|
|
{
|
|
// access
|
|
if (offset < 0 || offset >= Size)
|
|
{
|
|
throw new ArgumentOutOfRangeException("offset");
|
|
}
|
|
return _buffer[offset];
|
|
}
|
|
}
|
|
|
|
internal int GetPort()
|
|
{
|
|
return (int)((_buffer[2] << 8 & 0xFF00) | (_buffer[3]));
|
|
}
|
|
|
|
public override bool Equals(object comparand)
|
|
{
|
|
SocketAddress castedComparand = comparand as SocketAddress;
|
|
if (castedComparand == null || this.Size != castedComparand.Size)
|
|
{
|
|
return false;
|
|
}
|
|
for (int i = 0; i < this.Size; i++)
|
|
{
|
|
if (this[i] != castedComparand[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
if (_hash == 0)
|
|
{
|
|
int i;
|
|
int size = Size & ~3;
|
|
|
|
for (i = 0; i < size; i += 4)
|
|
{
|
|
_hash ^= (int)_buffer[i]
|
|
| ((int)_buffer[i + 1] << 8)
|
|
| ((int)_buffer[i + 2] << 16)
|
|
| ((int)_buffer[i + 3] << 24);
|
|
}
|
|
if ((Size & 3) != 0)
|
|
{
|
|
int remnant = 0;
|
|
int shift = 0;
|
|
|
|
for (; i < Size; ++i)
|
|
{
|
|
remnant |= ((int)_buffer[i]) << shift;
|
|
shift += 8;
|
|
}
|
|
_hash ^= remnant;
|
|
}
|
|
}
|
|
return _hash;
|
|
}
|
|
|
|
internal IPAddress GetIPAddress()
|
|
{
|
|
if (Family == AddressFamily.InterNetworkV6)
|
|
{
|
|
return GetIpv6Address();
|
|
}
|
|
else if (Family == AddressFamily.InterNetwork)
|
|
{
|
|
return GetIPv4Address();
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private IPAddress GetIpv6Address()
|
|
{
|
|
Contract.Assert(Size >= IPv6AddressSize);
|
|
byte[] bytes = new byte[NumberOfIPv6Labels * 2];
|
|
Array.Copy(_buffer, 8, bytes, 0, NumberOfIPv6Labels * 2);
|
|
return new IPAddress(bytes); // TODO: Does scope id matter?
|
|
}
|
|
|
|
private IPAddress GetIPv4Address()
|
|
{
|
|
Contract.Assert(Size >= IPv4AddressSize);
|
|
return new IPAddress(new byte[] { _buffer[4], _buffer[5], _buffer[6], _buffer[7] });
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
StringBuilder bytes = new StringBuilder();
|
|
for (int i = WriteableOffset; i < this.Size; i++)
|
|
{
|
|
if (i > WriteableOffset)
|
|
{
|
|
bytes.Append(",");
|
|
}
|
|
bytes.Append(this[i].ToString(NumberFormatInfo.InvariantInfo));
|
|
}
|
|
return Family.ToString() + ":" + Size.ToString(NumberFormatInfo.InvariantInfo) + ":{" + bytes.ToString() + "}";
|
|
}
|
|
|
|
internal string GetIPAddressString()
|
|
{
|
|
if (Family == AddressFamily.InterNetworkV6)
|
|
{
|
|
return GetIpv6AddressString();
|
|
}
|
|
else if (Family == AddressFamily.InterNetwork)
|
|
{
|
|
return GetIPv4AddressString();
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private string GetIPv4AddressString()
|
|
{
|
|
Contract.Assert(Size >= IPv4AddressSize);
|
|
|
|
return string.Format(CultureInfo.InvariantCulture, IPv4StringFormat,
|
|
_buffer[4], _buffer[5], _buffer[6], _buffer[7]);
|
|
}
|
|
|
|
// TODO: Does scope ID ever matter?
|
|
private unsafe string GetIpv6AddressString()
|
|
{
|
|
Contract.Assert(Size >= IPv6AddressSize);
|
|
|
|
fixed (byte* rawBytes = _buffer)
|
|
{
|
|
// Convert from bytes to shorts.
|
|
ushort* rawShorts = stackalloc ushort[NumberOfIPv6Labels];
|
|
int numbersOffset = 0;
|
|
// The address doesn't start at the beginning of the buffer.
|
|
for (int i = 8; i < ((NumberOfIPv6Labels * 2) + 8); i += 2)
|
|
{
|
|
rawShorts[numbersOffset++] = (ushort)(rawBytes[i] << 8 | rawBytes[i + 1]);
|
|
}
|
|
return GetIPv6AddressString(rawShorts);
|
|
}
|
|
}
|
|
|
|
private static unsafe string GetIPv6AddressString(ushort* numbers)
|
|
{
|
|
// RFC 5952 Sections 4 & 5 - Compressed, lower case, with possible embedded IPv4 addresses.
|
|
|
|
// Start to finish, inclusive. <-1, -1> for no compression
|
|
KeyValuePair<int, int> range = FindCompressionRange(numbers);
|
|
bool ipv4Embedded = ShouldHaveIpv4Embedded(numbers);
|
|
|
|
StringBuilder builder = new StringBuilder();
|
|
for (int i = 0; i < NumberOfIPv6Labels; i++)
|
|
{
|
|
if (ipv4Embedded && i == (NumberOfIPv6Labels - 2))
|
|
{
|
|
// Write the remaining digits as an IPv4 address
|
|
builder.Append(IPv6StringSeparator);
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, IPv4StringFormat,
|
|
numbers[i] >> 8, numbers[i] & 0xFF, numbers[i + 1] >> 8, numbers[i + 1] & 0xFF));
|
|
break;
|
|
}
|
|
|
|
// Compression; 1::1, ::1, 1::
|
|
if (range.Key == i)
|
|
{
|
|
// Start compression, add :
|
|
builder.Append(IPv6StringSeparator);
|
|
}
|
|
if (range.Key <= i && range.Value == (NumberOfIPv6Labels - 1))
|
|
{
|
|
// Remainder compressed; 1::
|
|
builder.Append(IPv6StringSeparator);
|
|
break;
|
|
}
|
|
if (range.Key <= i && i <= range.Value)
|
|
{
|
|
continue; // Compressed
|
|
}
|
|
|
|
if (i != 0)
|
|
{
|
|
builder.Append(IPv6StringSeparator);
|
|
}
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, IPv6NumberFormat, numbers[i]));
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
// RFC 5952 Section 4.2.3
|
|
// Longest consecutive sequence of zero segments, minimum 2.
|
|
// On equal, first sequence wins.
|
|
// <-1, -1> for no compression.
|
|
private static unsafe KeyValuePair<int, int> FindCompressionRange(ushort* numbers)
|
|
{
|
|
int longestSequenceLength = 0;
|
|
int longestSequenceStart = -1;
|
|
|
|
int currentSequenceLength = 0;
|
|
for (int i = 0; i < NumberOfIPv6Labels; i++)
|
|
{
|
|
if (numbers[i] == 0)
|
|
{
|
|
// In a sequence
|
|
currentSequenceLength++;
|
|
if (currentSequenceLength > longestSequenceLength)
|
|
{
|
|
longestSequenceLength = currentSequenceLength;
|
|
longestSequenceStart = i - currentSequenceLength + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentSequenceLength = 0;
|
|
}
|
|
}
|
|
|
|
if (longestSequenceLength >= 2)
|
|
{
|
|
return new KeyValuePair<int, int>(longestSequenceStart,
|
|
longestSequenceStart + longestSequenceLength - 1);
|
|
}
|
|
|
|
return new KeyValuePair<int, int>(-1, -1); // No compression
|
|
}
|
|
|
|
// Returns true if the IPv6 address should be formated with an embedded IPv4 address:
|
|
// ::192.168.1.1
|
|
private static unsafe bool ShouldHaveIpv4Embedded(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
|
|
if (numbers[4] == 0 && numbers[5] == 0x5EFE)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
} // class SocketAddress
|
|
} // namespace System.Net
|