aspnetcore/shared/Microsoft.AspNetCore.HttpSy.../NativeInterop/SocketAddress.cs

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