//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Channels { using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net; using System.Net.Sockets; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using Microsoft.Win32.SafeHandles; static class PeerWinsock { [DllImport("ws2_32.dll", SetLastError = true, EntryPoint = "WSAIoctl")] internal static extern int WSAIoctl( [In] IntPtr socketHandle, [In] int ioControlCode, [In] IntPtr inBuffer, [In] int inBufferSize, [Out] IntPtr outBuffer, [In] int outBufferSize, [Out] out int bytesTransferred, [In] IntPtr overlapped, [In] IntPtr completionRoutine); } [Serializable, StructLayout(LayoutKind.Sequential)] struct SocketAddress { IntPtr sockAddr; int sockAddrLength; public IntPtr SockAddr { get { return sockAddr; } } public int SockAddrLength { get { return sockAddrLength; } } public void InitializeFromCriticalAllocHandleSocketAddress(CriticalAllocHandleSocketAddress sockAddr) { this.sockAddr = (IntPtr)sockAddr; this.sockAddrLength = sockAddr.Size; } } [StructLayout(LayoutKind.Sequential)] struct SocketAddressList { int count; internal const int maxAddresses = 50; [MarshalAs(UnmanagedType.ByValArray, SizeConst = maxAddresses)] SocketAddress[] addresses; public SocketAddress[] Addresses { get { return addresses; } } public int Count { get { return count; } } public SocketAddressList(SocketAddress[] addresses, int count) { this.addresses = addresses; this.count = count; } public static ReadOnlyCollection SortAddresses(Socket socket, IPAddress listenAddress, ReadOnlyCollection addresses) { ReadOnlyCollection sortedAddresses = null; // Skip sort if ipv6 isn't installed or if address array has a single address if (socket == null || addresses.Count <= 1) { sortedAddresses = addresses; } else { CriticalAllocHandleSocketAddressList inputBuffer = null; CriticalAllocHandleSocketAddressList outputBuffer = null; try { inputBuffer = CriticalAllocHandleSocketAddressList.FromAddressList(addresses); outputBuffer = CriticalAllocHandleSocketAddressList.FromAddressCount(0); // Invoke ioctl to sort the addresses int realOutputBufferSize; int error = UnsafeNativeMethods.ERROR_SUCCESS; int errorCode = PeerWinsock.WSAIoctl(socket.Handle, unchecked((int)IOControlCode.AddressListSort), (IntPtr)inputBuffer, inputBuffer.Size, (IntPtr)outputBuffer, outputBuffer.Size, out realOutputBufferSize, IntPtr.Zero, IntPtr.Zero); if (errorCode == -1) { // Get the Win32 error code before doing anything else error = Marshal.GetLastWin32Error(); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SocketException(error)); } // Marshal the sorted SOCKET_ADDRESS_LIST into IPAddresses sortedAddresses = outputBuffer.ToAddresses(); } finally { if (inputBuffer != null) inputBuffer.Dispose(); if (outputBuffer != null) outputBuffer.Dispose(); } } return sortedAddresses; } } // Type that converts from sockaddr_in6 to IPAddress and vice-versa [Serializable, StructLayout(LayoutKind.Sequential)] struct sockaddr_in6 { short sin6_family; ushort sin6_port; uint sin6_flowinfo; [MarshalAs(UnmanagedType.ByValArray, SizeConst = addrByteCount)] byte[] sin6_addr; uint sin6_scope_id; const int addrByteCount = 16; // if the addr is v4-mapped-v6, 10th and 11th byte contain 0xFF. the last 4 bytes contain the ipv4 address const int v4MapIndex = 10; const int v4Index = v4MapIndex + 2; public sockaddr_in6(IPAddress address) { if (address.AddressFamily == AddressFamily.InterNetworkV6) { this.sin6_addr = address.GetAddressBytes(); this.sin6_scope_id = (uint)address.ScopeId; } else { // Map v4 address to v4-mapped v6 addr (i.e., ::FFFF:XX.XX.XX.XX) byte[] v4AddressBytes = address.GetAddressBytes(); this.sin6_addr = new byte[addrByteCount]; for (int i = 0; i < v4MapIndex; i++) this.sin6_addr[i] = 0; this.sin6_addr[v4MapIndex] = 0xff; this.sin6_addr[v4MapIndex + 1] = 0xff; for (int i = v4Index; i < addrByteCount; i++) this.sin6_addr[i] = v4AddressBytes[i - v4Index]; this.sin6_scope_id = 0; // V4 address doesn't have a scope ID } this.sin6_family = (short)AddressFamily.InterNetworkV6; this.sin6_port = 0; this.sin6_flowinfo = 0; } public short Family { get { return this.sin6_family; } } public uint FlowInfo { get { return this.sin6_flowinfo; } } // Returns true if the address is a v4-mapped v6 address // Adapted from ws2ipdef.w's IN6_IS_ADDR_V4MAPPED macro private bool IsV4Mapped { get { // A v4-mapped v6 address will have the last 4 bytes contain the IPv4 address. // The preceding 2 bytes contain 0xFFFF. All others are 0. if (sin6_addr[v4MapIndex] != 0xff || sin6_addr[v4MapIndex + 1] != 0xff) return false; for (int i = 0; i < v4MapIndex; i++) if (sin6_addr[i] != 0) return false; return true; } } public ushort Port { get { return this.sin6_port; } } // Converts a sockaddr_in6 to IPAddress // A v4 mapped v6 address is converted to a v4 address public IPAddress ToIPAddress() { if (!(this.sin6_family == (short)AddressFamily.InterNetworkV6)) { throw Fx.AssertAndThrow("AddressFamily expected to be InterNetworkV6"); } if (IsV4Mapped) { byte[] addr = { this.sin6_addr[v4Index], this.sin6_addr[v4Index + 1], this.sin6_addr[v4Index + 2], this.sin6_addr[v4Index + 3] }; return new IPAddress(addr); } else { return new IPAddress(this.sin6_addr, this.sin6_scope_id); } } } class CriticalAllocHandleSocketAddressList : CriticalAllocHandle { int count; int size; CriticalAllocHandleSocketAddress[] socketHandles; public int Count { get { return count; } } public int Size { get { return size; } } public static CriticalAllocHandleSocketAddressList FromAddressList(ICollection addresses) { if (addresses == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("addresses"); } int count = addresses.Count; CriticalAllocHandleSocketAddress[] socketHandles = new CriticalAllocHandleSocketAddress[SocketAddressList.maxAddresses]; SocketAddressList socketAddressList = new SocketAddressList(new SocketAddress[SocketAddressList.maxAddresses], count); int i = 0; foreach (IPAddress address in addresses) { if (i == SocketAddressList.maxAddresses) break; // due to Marshalling fixed sized array of SocketAddresses. socketHandles[i] = CriticalAllocHandleSocketAddress.FromIPAddress(address); socketAddressList.Addresses[i].InitializeFromCriticalAllocHandleSocketAddress(socketHandles[i]); ++i; } int size = Marshal.SizeOf(socketAddressList); CriticalAllocHandleSocketAddressList result = CriticalAllocHandleSocketAddressList.FromSize(size); result.count = count; result.socketHandles = socketHandles; Marshal.StructureToPtr(socketAddressList, result, false); return result; } public static CriticalAllocHandleSocketAddressList FromAddressCount(int count) { SocketAddressList socketAddressList = new SocketAddressList(new SocketAddress[SocketAddressList.maxAddresses], 0); int size = Marshal.SizeOf(socketAddressList); CriticalAllocHandleSocketAddressList result = CriticalAllocHandleSocketAddressList.FromSize(size); result.count = count; Marshal.StructureToPtr(socketAddressList, result, false); return result; } static new CriticalAllocHandleSocketAddressList FromSize(int size) { CriticalAllocHandleSocketAddressList result = new CriticalAllocHandleSocketAddressList(); RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { result.SetHandle(Marshal.AllocHGlobal(size)); result.size = size; } return result; } public ReadOnlyCollection ToAddresses() { SocketAddressList socketAddressList = (SocketAddressList)Marshal.PtrToStructure(this, typeof(SocketAddressList)); IPAddress[] addresses = new IPAddress[socketAddressList.Count]; for (int i = 0; i < addresses.Length; i++) { if (!(socketAddressList.Addresses[i].SockAddrLength == Marshal.SizeOf(typeof(sockaddr_in6)))) { throw Fx.AssertAndThrow("sockAddressLength in SOCKET_ADDRESS expected to be valid"); } sockaddr_in6 sockAddr = (sockaddr_in6)Marshal.PtrToStructure(socketAddressList.Addresses[i].SockAddr, typeof(sockaddr_in6)); addresses[i] = sockAddr.ToIPAddress(); } return Array.AsReadOnly(addresses); } } class CriticalAllocHandleSocketAddress : CriticalAllocHandle { int size; public int Size { get { return size; } } public static CriticalAllocHandleSocketAddress FromIPAddress(IPAddress input) { if (input == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("input"); } CriticalAllocHandleSocketAddress result = null; int size = Marshal.SizeOf(typeof(sockaddr_in6)); result = CriticalAllocHandleSocketAddress.FromSize(size); sockaddr_in6 sa = new sockaddr_in6(input); Marshal.StructureToPtr(sa, (IntPtr)result, false); return result; } public static new CriticalAllocHandleSocketAddress FromSize(int size) { CriticalAllocHandleSocketAddress result = new CriticalAllocHandleSocketAddress(); RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { result.SetHandle(Marshal.AllocHGlobal(size)); result.size = size; } return result; } } class CriticalAllocHandle : CriticalHandleZeroOrMinusOneIsInvalid { [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static implicit operator IntPtr(CriticalAllocHandle safeHandle) { return (safeHandle != null) ? safeHandle.handle : (IntPtr)null; } protected override bool ReleaseHandle() { Marshal.FreeHGlobal(handle); return true; } public static CriticalAllocHandle FromSize(int size) { CriticalAllocHandle result = new CriticalAllocHandle(); RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { result.SetHandle(Marshal.AllocHGlobal(size)); } return result; } } class CriticalAllocHandleBlob : CriticalAllocHandle { [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical] public static CriticalAllocHandle FromBlob(T id) { int size = Marshal.SizeOf(typeof(T)); CriticalAllocHandle result = CriticalAllocHandle.FromSize(size); Marshal.StructureToPtr(id, (IntPtr)result, false); return result; } } class CriticalAllocHandleGuid : CriticalAllocHandle { public static CriticalAllocHandle FromGuid(Guid input) { int guidSize = Marshal.SizeOf(typeof(Guid)); CriticalAllocHandle result = CriticalAllocHandle.FromSize(guidSize); Marshal.Copy(input.ToByteArray(), 0, (IntPtr)result, guidSize); return result; } } }