// 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 /// /// /// This class is used when subclassing EndPoint, and provides indication /// on how to format the memory buffers that winsock uses for network addresses. /// /// 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; /// /// [To be supplied.] /// 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. // // 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? /// /// [To be supplied.] /// 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 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 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(longestSequenceStart, longestSequenceStart + longestSequenceLength - 1); } return new KeyValuePair(-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